Added method canInvokeThisMethodInAWT() to SwingTools.
[aprog.git] / Aprog / src / net / sourceforge / aprog / swing / SwingTools.java
blob621eee8df428d11ffb1afb8e725ed41f32004ebc
1 /*
2 * The MIT License
3 *
4 * Copyright 2010 Codist Monk.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 package net.sourceforge.aprog.swing;
27 import java.awt.Component;
28 import java.awt.Container;
29 import java.awt.Dimension;
30 import java.awt.GridBagConstraints;
31 import java.awt.GridBagLayout;
32 import java.awt.Window;
33 import java.awt.datatransfer.DataFlavor;
34 import java.awt.dnd.DnDConstants;
35 import java.awt.dnd.DropTargetDropEvent;
36 import java.io.File;
37 import java.io.IOException;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Method;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.logging.Level;
47 import javax.imageio.ImageIO;
48 import javax.swing.AbstractButton;
49 import javax.swing.ImageIcon;
50 import javax.swing.JMenu;
51 import javax.swing.JMenuBar;
52 import javax.swing.JMenuItem;
53 import javax.swing.JScrollPane;
54 import javax.swing.SwingUtilities;
55 import javax.swing.UIManager;
57 import net.sourceforge.aprog.tools.Tools;
59 /**
60 * This class provides utility static methods to help build Swing GUIs.
61 * <br>According to the JDK, accessing and modifying AWT components should only be done
62 * in the AWT Event Dispatching Thread.
63 * <br>The methods in this class enforce this rule by calling {@link #checkAWT()} to make sure
64 * that they are used in the proper thread.
66 * @author codistmonk (creation 2010-06-26)
68 public final class SwingTools {
70 /**
71 * Private default constructor to prevent instantiation.
73 private SwingTools() {
74 // Do nothing
77 /**
78 * {@value}.
80 public static final String DEFAULT_IMAGES_BASE = "images/";
82 /**
83 * {@value}.
85 public static final String ICON_FORMAT = "png";
87 /**
88 * {@value}.
90 public static final String ROLLOVER_DISABLED_ICON_SUFFIX = "_disabled." + ICON_FORMAT;
92 /**
93 * {@value}.
95 public static final String ROLLOVER_NORMAL_ICON_SUFFIX = "." + ICON_FORMAT;
97 /**
98 * {@value}.
100 public static final String ROLLOVER_SELECTED_ICON_SUFFIX = "_selected." + ICON_FORMAT;
103 * {@value}.
105 public static final String ROLLOVER_ROLLOVER_ICON_SUFFIX = "_rollover." + ICON_FORMAT;
108 * {@value}.
110 public static final String ROLLOVER_ROLLOVER_SELECTED_ICON_SUFFIX = "_rollover_selected." + ICON_FORMAT;
112 private static final Map<String, ImageIcon> iconCache = new HashMap<String, ImageIcon>();
114 private static String imagesBase = DEFAULT_IMAGES_BASE;
118 * @return
119 * <br>Not null
120 * <br>Shared
122 public static final String getImagesBase() {
123 return imagesBase;
128 * @param imagesBase
129 * <br>Not null
130 * <br>Shared
132 public static final void setImagesBase(final String imagesBase) {
133 SwingTools.imagesBase = imagesBase;
138 * @param resourceName
139 * <br>Not null
140 * @return
141 * <br>Not null
142 * <br>New
143 * @throws RuntimeException if the resource cannot be loaded
145 public static final ImageIcon getIcon(final String resourceName) {
146 try {
147 final ImageIcon cachedIcon = iconCache.get(resourceName);
149 if (cachedIcon != null) {
150 return cachedIcon;
153 final ImageIcon icon = new ImageIcon(ImageIO.read(
154 SwingTools.class.getClassLoader().getResourceAsStream(getImagesBase() + resourceName)));
156 iconCache.put(resourceName, icon);
158 return icon;
159 } catch (final IOException exception) {
160 return Tools.throwUnchecked(exception);
166 * @param resourceName
167 * <br>Not null
168 * @return
169 * <br>Maybe null
170 * <br>New
172 public static final ImageIcon getIconOrNull(final String resourceName) {
173 try {
174 return getIcon(resourceName);
175 } catch (final Exception exception) {
176 return null;
182 * @param container
183 * <br>Not null
184 * <br>Input-output
185 * @param component
186 * <br>Not null
187 * <br>Input-output
188 * <br>Shared
189 * @param constraints
190 * <br>Not null
192 public static final void add(final Container container, final Component component, final GridBagConstraints constraints) {
193 checkAWT();
195 if (!(container.getLayout() instanceof GridBagLayout)) {
196 container.setLayout(new GridBagLayout());
199 final GridBagLayout layout = (GridBagLayout) container.getLayout();
201 layout.setConstraints(component, constraints);
203 container.add(component);
208 * @param <T> the actual type of {@code button}
209 * @param button
210 * <br>Not null
211 * <br>Input-output
212 * @param imageName
213 * <br>Not null
214 * @param borderPainted if {@code false}, then the preferred size is set to the size of the image,
215 * and the background and border are not drawn; if {@code true}, then {@code button} is left in its current state
216 * @return {@code button}
217 * <br>Not null
219 public static final <T extends AbstractButton> T rollover(final T button, final String imageName, final boolean borderPainted) {
220 checkAWT();
222 button.setRolloverEnabled(true);
223 button.setDisabledIcon(getIconOrNull(imageName + ROLLOVER_DISABLED_ICON_SUFFIX));
224 button.setIcon(getIconOrNull(imageName + ROLLOVER_NORMAL_ICON_SUFFIX));
225 button.setSelectedIcon(getIconOrNull(imageName + ROLLOVER_SELECTED_ICON_SUFFIX));
226 button.setRolloverIcon(getIconOrNull(imageName + ROLLOVER_ROLLOVER_ICON_SUFFIX));
227 button.setRolloverSelectedIcon(getIconOrNull(imageName + ROLLOVER_ROLLOVER_SELECTED_ICON_SUFFIX));
229 if (!borderPainted) {
230 if (button.getIcon() != null) {
231 button.setPreferredSize(new Dimension(button.getIcon().getIconWidth(), button.getIcon().getIconHeight()));
234 button.setBorderPainted(false);
237 return button;
241 * Encloses {@code component} in a scroll pane.
243 * @param component
244 * <br>Not null
245 * <br>Input-output
246 * @return
247 * <br>Not null
248 * <br>New
250 public static final JScrollPane scrollable(final Component component) {
251 checkAWT();
253 return new JScrollPane(component);
257 * Centers {@code window} on the screen.
259 * @param <W> the actual type of {@code window}
260 * @param window
261 * <br>Not null
262 * <br>input-output
263 * <br>Shared
264 * @return {@code window}
265 * <br>Not null
266 * <br>Shared
268 public static final <W extends Window> W center(final W window) {
269 checkAWT();
271 window.setLocationRelativeTo(null);
273 return window;
278 * @param menus
279 * <br>Not null
280 * @return
281 * <br>Not null
282 * <br>New
284 public static final JMenuBar menuBar(final JMenu... menus) {
285 checkAWT();
287 final JMenuBar result = new JMenuBar();
289 for (final JMenu menu : menus) {
290 result.add(menu);
293 return result;
298 * @param title
299 * <br>Not null
300 * @param subMenuItems
301 * <br>Not null
302 * @return
303 * <br>Not null
304 * <br>New
306 public static final JMenu menu(final String title, final JMenuItem... subMenuItems) {
307 checkAWT();
309 final JMenu result = new JMenu(title);
311 for (final JMenuItem subMenu : subMenuItems) {
312 if (subMenu == null) {
313 result.addSeparator();
314 } else {
315 result.add(subMenu);
319 return result;
324 * @param event
325 * <br>Not null
326 * <br>Input-output
327 * @return a list of files, or an empty list if {@code event} cannot provide a list of files or a string
328 * <br>Not null
329 * <br>Maybe new
330 * @throws RuntimeException if an error occurs
332 @SuppressWarnings("unchecked")
333 public static final List<File> getFiles(final DropTargetDropEvent event) {
334 event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
336 try {
337 if (event.getCurrentDataFlavorsAsList().contains(DataFlavor.javaFileListFlavor)) {
338 return (List<File>)event.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
341 if (event.getCurrentDataFlavorsAsList().contains(DataFlavor.stringFlavor)) {
342 return Arrays.asList(new File((String)event.getTransferable().getTransferData(DataFlavor.stringFlavor)));
345 return Collections.emptyList();
346 } catch (final Exception exception) {
347 return Tools.throwUnchecked(exception);
351 public static final void useSystemLookAndFeel() {
352 try {
353 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
354 } catch (final Exception exception) {
355 Tools.getLoggerForThisMethod().log(Level.WARNING, "", exception);
360 * Executes in the AWT Event Dispatching Thread a runnable invoking
361 * the caller method with the specified arguments,
362 * or does nothing if the method is called in that thread.
363 * <br>This method can be used to simplify code that needs to be executed in AWT
364 * by taking care of generating an anonymous inner class implementing {@link Runnable}.
365 * <br>Example:
366 * <pre>
367 * public final void f() {
368 * // Warning: this section might get executed 2 times in different threads
370 * if (SwingTools.canInvokeThisMethodInAWT(this)) {
371 * // This section is executed only once in the AWT Event Dispatching Thread
372 * // For instance, the following instruction doesn't throw
373 * SwingTools.checkAWT();
376 * // Warning: this section might get executed 2 times in different threads
378 * </pre>
380 * @param object The caller object or {@code null} if the caller is static
381 * <br>Maybe null
382 * @param arguments
383 * <br>Not null
384 * @return {@code true} if and only if the method is called in the AWT Event Dispatching Thread
385 * @throws RuntimeException if an error occurs
387 public static final boolean canInvokeThisMethodInAWT(final Object object, final Object... arguments) {
388 if (SwingUtilities.isEventDispatchThread()) {
389 return true;
392 final Class<?> callerClass = Tools.getCallerClass();
393 final String callerMethodName = Tools.getCallerMethodName();
395 try {
396 SwingUtilities.invokeAndWait(new Runnable() {
398 @Override
399 public final void run() {
400 for (final Method method : Tools.add(callerClass.getMethods(), callerClass.getDeclaredMethods())) {
401 if (method.getName().equals(callerMethodName)) {
402 try {
403 method.invoke(object, arguments);
404 return;
405 } catch (final InvocationTargetException exception) {
406 Tools.throwUnchecked(exception.getCause());
407 } catch (final Exception exception) {
408 // Ignore
414 } catch (final InterruptedException exception) {
415 Tools.getLoggerForThisMethod().log(Level.WARNING, null, exception);
416 } catch (final InvocationTargetException exception) {
417 Tools.throwUnchecked(exception.getCause());
420 return false;
425 * @throws IllegalStateException if the current thread is not the AWT Event Dispatching Thread
427 public static final void checkAWT() {
428 if (!SwingUtilities.isEventDispatchThread()) {
429 throw new IllegalStateException("This section must be executed in the AWT Event Dispatching Thread");
435 * @throws IllegalStateException if the current thread is the AWT Event Dispatching Thread
437 public static final void checkNotAWT() {
438 if (SwingUtilities.isEventDispatchThread()) {
439 throw new IllegalStateException("This section must not be executed in the AWT Event Dispatching Thread");