Updated comments.
[aprog.git] / Aprog / src / net / sourceforge / aprog / swing / SwingTools.java
blob8e75ed95d5847890ca9347ad9916e7b5c463eefe
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 * Packs and updates {@code window}'s minimum size so that it cannot be resized to be smaller than its packed size.
259 * @param <W> The actual type of {@code window}
260 * @param window
261 * <br>Not null
262 * <br>input-output
263 * @return {@code window}
264 * <br>Not null
266 public static final <W extends Window> W packAndUpdateMinimumSize(final W window) {
267 checkAWT();
269 window.setMinimumSize(null);
270 window.pack();
271 window.setMinimumSize(window.getSize());
273 return window;
277 * Packs and centers {@code window} on the screen.
279 * @param <W> The actual type of {@code window}
280 * @param window
281 * <br>Not null
282 * <br>input-output
283 * @return {@code window}
284 * <br>Not null
286 public static final <W extends Window> W packAndCenter(final W window) {
287 checkAWT();
289 window.pack();
291 return center(window);
295 * Centers {@code window} on the screen.
297 * @param <W> the actual type of {@code window}
298 * @param window
299 * <br>Not null
300 * <br>input-output
301 * <br>Shared
302 * @return {@code window}
303 * <br>Not null
304 * <br>Shared
306 public static final <W extends Window> W center(final W window) {
307 checkAWT();
309 window.setLocationRelativeTo(null);
311 return window;
316 * @param menus
317 * <br>Not null
318 * @return
319 * <br>Not null
320 * <br>New
322 public static final JMenuBar menuBar(final JMenu... menus) {
323 checkAWT();
325 final JMenuBar result = new JMenuBar();
327 for (final JMenu menu : menus) {
328 result.add(menu);
331 return result;
336 * @param title
337 * <br>Not null
338 * @param subMenuItems
339 * <br>Not null
340 * @return
341 * <br>Not null
342 * <br>New
344 public static final JMenu menu(final String title, final JMenuItem... subMenuItems) {
345 checkAWT();
347 final JMenu result = new JMenu(title);
349 for (final JMenuItem subMenu : subMenuItems) {
350 if (subMenu == null) {
351 result.addSeparator();
352 } else {
353 result.add(subMenu);
357 return result;
362 * @param event
363 * <br>Not null
364 * <br>Input-output
365 * @return a list of files, or an empty list if {@code event} cannot provide a list of files or a string
366 * <br>Not null
367 * <br>Maybe new
368 * @throws RuntimeException if an error occurs
370 @SuppressWarnings("unchecked")
371 public static final List<File> getFiles(final DropTargetDropEvent event) {
372 event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
374 try {
375 if (event.getCurrentDataFlavorsAsList().contains(DataFlavor.javaFileListFlavor)) {
376 return (List<File>)event.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
379 if (event.getCurrentDataFlavorsAsList().contains(DataFlavor.stringFlavor)) {
380 return Arrays.asList(new File((String)event.getTransferable().getTransferData(DataFlavor.stringFlavor)));
383 return Collections.emptyList();
384 } catch (final Exception exception) {
385 return Tools.throwUnchecked(exception);
389 public static final void useSystemLookAndFeel() {
390 try {
391 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
392 } catch (final Exception exception) {
393 Tools.getLoggerForThisMethod().log(Level.WARNING, "", exception);
398 * Executes in the AWT Event Dispatching Thread a runnable invoking
399 * the caller method with the specified arguments,
400 * or does nothing if the method is called in that thread.
401 * <br>This method can be used to simplify code that needs to be executed in AWT
402 * by taking care of generating an anonymous inner class implementing {@link Runnable}.
403 * <br>Example:
404 * <pre>
405 * public final void f() {
406 * // Warning: this section might get executed 2 times in different threads
408 * if (SwingTools.canInvokeThisMethodInAWT(this)) {
409 * // This section is executed only once in the AWT Event Dispatching Thread
410 * // For instance, the following instruction doesn't throw
411 * SwingTools.checkAWT();
414 * // Warning: this section might get executed 2 times in different threads
416 * </pre>
418 * @param object The caller object or {@code null} if the caller is static
419 * <br>Maybe null
420 * <br>Shared
421 * @param arguments
422 * <br>Not null
423 * <br>Shared
424 * @return {@code true} if and only if the method is called in the AWT Event Dispatching Thread
425 * @throws RuntimeException if an error occurs
427 public static final boolean canInvokeThisMethodInAWT(final Object object, final Object... arguments) {
428 if (SwingUtilities.isEventDispatchThread()) {
429 return true;
432 final Class<?> callerClass = Tools.getCallerClass();
433 final String callerMethodName = Tools.getCallerMethodName();
435 try {
436 SwingUtilities.invokeAndWait(new Runnable() {
438 @Override
439 public final void run() {
440 Tools.invoke(object == null ? callerClass : object, callerMethodName, arguments);
444 } catch (final InterruptedException exception) {
445 Tools.getLoggerForThisMethod().log(Level.WARNING, null, exception);
446 } catch (final InvocationTargetException exception) {
447 Tools.throwUnchecked(exception.getCause());
450 return false;
455 * @throws IllegalStateException if the current thread is not the AWT Event Dispatching Thread
457 public static final void checkAWT() {
458 if (!SwingUtilities.isEventDispatchThread()) {
459 throw new IllegalStateException("This section must be executed in the AWT Event Dispatching Thread");
465 * @throws IllegalStateException if the current thread is the AWT Event Dispatching Thread
467 public static final void checkNotAWT() {
468 if (SwingUtilities.isEventDispatchThread()) {
469 throw new IllegalStateException("This section must not be executed in the AWT Event Dispatching Thread");