4 * Copyright 2010 Codist Monk.
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
25 package net
.sourceforge
.aprog
.swing
;
27 import static net
.sourceforge
.aprog
.i18n
.Messages
.translate
;
28 import static net
.sourceforge
.aprog
.tools
.Tools
.getCallerClass
;
29 import static net
.sourceforge
.aprog
.tools
.Tools
.getCallerMethodName
;
30 import static net
.sourceforge
.aprog
.tools
.Tools
.getLoggerForThisMethod
;
31 import static net
.sourceforge
.aprog
.tools
.Tools
.getResourceAsStream
;
32 import static net
.sourceforge
.aprog
.tools
.Tools
.ignore
;
33 import static net
.sourceforge
.aprog
.tools
.Tools
.invoke
;
34 import static net
.sourceforge
.aprog
.tools
.Tools
.unchecked
;
36 import java
.awt
.Color
;
37 import java
.awt
.Component
;
38 import java
.awt
.Container
;
39 import java
.awt
.Dimension
;
40 import java
.awt
.GridBagConstraints
;
41 import java
.awt
.GridBagLayout
;
42 import java
.awt
.Window
;
43 import java
.awt
.datatransfer
.DataFlavor
;
44 import java
.awt
.dnd
.DnDConstants
;
45 import java
.awt
.dnd
.DropTargetDropEvent
;
46 import java
.awt
.event
.MouseAdapter
;
47 import java
.awt
.event
.MouseEvent
;
48 import java
.awt
.image
.BufferedImage
;
50 import java
.io
.IOException
;
51 import java
.io
.UnsupportedEncodingException
;
52 import java
.lang
.reflect
.InvocationTargetException
;
53 import java
.net
.URLDecoder
;
54 import java
.util
.ArrayList
;
55 import java
.util
.Arrays
;
56 import java
.util
.Collections
;
57 import java
.util
.HashMap
;
58 import java
.util
.List
;
60 import java
.util
.Scanner
;
61 import java
.util
.logging
.Level
;
63 import javax
.imageio
.ImageIO
;
64 import javax
.swing
.AbstractButton
;
65 import javax
.swing
.Box
;
66 import javax
.swing
.ImageIcon
;
67 import javax
.swing
.JDialog
;
68 import javax
.swing
.JFrame
;
69 import javax
.swing
.JLabel
;
70 import javax
.swing
.JMenu
;
71 import javax
.swing
.JMenuBar
;
72 import javax
.swing
.JMenuItem
;
73 import javax
.swing
.JScrollPane
;
74 import javax
.swing
.JSplitPane
;
75 import javax
.swing
.KeyStroke
;
76 import javax
.swing
.SwingUtilities
;
77 import javax
.swing
.UIManager
;
78 import javax
.swing
.WindowConstants
;
80 import net
.sourceforge
.aprog
.af
.MacOSXTools
;
81 import net
.sourceforge
.aprog
.i18n
.Messages
;
82 import net
.sourceforge
.aprog
.tools
.IllegalInstantiationException
;
85 * This class provides utility static methods to help build Swing GUIs.
86 * <br>According to the JDK, accessing and modifying AWT components should only be done
87 * in the AWT Event Dispatching Thread.
88 * <br>The methods in this class enforce this rule by calling {@link #checkAWT()} to make sure
89 * that they are used in the proper thread.
91 * @author codistmonk (creation 2010-06-26)
93 public final class SwingTools
{
96 * @throws IllegalInstantiationException To prevent instantiation
98 private SwingTools() {
99 throw new IllegalInstantiationException();
102 private static DataFlavor uriListAsStringFlavor
= null;
107 public static final String META
= MacOSXTools
.MAC_OS_X ?
"meta" : "control";
112 public static final String DEFAULT_IMAGES_BASE
= "images/";
117 public static final String ICON_FORMAT
= "png";
122 public static final String ROLLOVER_DISABLED_ICON_SUFFIX
= "_disabled." + ICON_FORMAT
;
127 public static final String ROLLOVER_NORMAL_ICON_SUFFIX
= "." + ICON_FORMAT
;
132 public static final String ROLLOVER_SELECTED_ICON_SUFFIX
= "_selected." + ICON_FORMAT
;
137 public static final String ROLLOVER_ROLLOVER_ICON_SUFFIX
= "_rollover." + ICON_FORMAT
;
142 public static final String ROLLOVER_ROLLOVER_SELECTED_ICON_SUFFIX
= "_rollover_selected." + ICON_FORMAT
;
144 private static final Map
<String
, ImageIcon
> iconCache
= new HashMap
<String
, ImageIcon
>();
146 private static String imagesBase
= DEFAULT_IMAGES_BASE
;
154 public static final String
getImagesBase() {
164 public static final void setImagesBase(final String imagesBase
) {
165 SwingTools
.imagesBase
= imagesBase
;
169 * Returns the icon located at {@code getImageBase() + resourceName}.
170 * <br>Icons are cached using {@code resourceName} as a key.
172 * @param resourceName
177 * @throws RuntimeException if the resource cannot be loaded
179 public static final ImageIcon
getIcon(final String resourceName
) {
181 final ImageIcon cachedIcon
= iconCache
.get(resourceName
);
183 if (cachedIcon
!= null) {
187 final ImageIcon icon
= new ImageIcon(ImageIO
.read(getResourceAsStream(getImagesBase() + resourceName
)));
189 iconCache
.put(resourceName
, icon
);
192 } catch (final IOException exception
) {
193 throw unchecked(exception
);
199 * @param resourceName
205 public static final ImageIcon
getIconOrNull(final String resourceName
) {
207 return getIcon(resourceName
);
208 } catch (final Exception exception
) {
221 * <br>Range: any boolean
223 public static final void show(final BufferedImage image
, final String title
, final boolean modal
) {
224 final JLabel imageLabel
= new JLabel(new ImageIcon(image
));
226 imageLabel
.addMouseMotionListener(new MouseAdapter() {
229 public final void mouseMoved(final MouseEvent event
) {
230 final int x
= event
.getX();
231 final int y
= event
.getY();
233 if (0 <= x
&& x
< image
.getWidth() && 0 <= y
&& y
< image
.getHeight()) {
234 final Color color
= new Color(image
.getRGB(x
, y
));
236 invoke(imageLabel
.getRootPane().getParent(), "setTitle",
237 title
+ " (x: " + x
+ ") (y: " + y
+ ") (r: " + color
.getRed() +
238 ") (g: " + color
.getGreen() + ") (b: " + color
.getBlue() + ") (a: " + color
.getAlpha() + ")");
244 show(new JScrollPane(imageLabel
), title
, modal
);
253 * <br>Range: any boolean
255 public static final void show(final Component component
, final String title
, final boolean modal
) {
256 final Runnable runnable
= new Runnable() {
259 public final void run() {
260 final JDialog frame
= new JDialog((JFrame
) null, title
, true);
262 frame
.add(component
);
263 frame
.setDefaultCloseOperation(WindowConstants
.DISPOSE_ON_CLOSE
);
265 packAndCenter(frame
).setVisible(true);
272 SwingUtilities
.invokeAndWait(runnable
);
273 } catch (final Exception exception
) {
274 throw unchecked(exception
);
277 SwingUtilities
.invokeLater(runnable
);
293 public static final void add(final Container container
, final Component component
, final GridBagConstraints constraints
) {
296 if (!(container
.getLayout() instanceof GridBagLayout
)) {
297 container
.setLayout(new GridBagLayout());
300 final GridBagLayout layout
= (GridBagLayout
) container
.getLayout();
302 layout
.setConstraints(component
, constraints
);
304 container
.add(component
);
309 * @param <T> the actual type of {@code button}
315 * @param borderPainted if {@code false}, then the preferred size is set to the size of the image,
316 * and the background and border are not drawn; if {@code true}, then {@code button} is left in its current state
317 * @return {@code button}
320 public static final <T
extends AbstractButton
> T
rollover(final T button
, final String imageName
, final boolean borderPainted
) {
323 button
.setRolloverEnabled(true);
324 button
.setDisabledIcon(getIconOrNull(imageName
+ ROLLOVER_DISABLED_ICON_SUFFIX
));
325 button
.setIcon(getIconOrNull(imageName
+ ROLLOVER_NORMAL_ICON_SUFFIX
));
326 button
.setSelectedIcon(getIconOrNull(imageName
+ ROLLOVER_SELECTED_ICON_SUFFIX
));
327 button
.setRolloverIcon(getIconOrNull(imageName
+ ROLLOVER_ROLLOVER_ICON_SUFFIX
));
328 button
.setRolloverSelectedIcon(getIconOrNull(imageName
+ ROLLOVER_ROLLOVER_SELECTED_ICON_SUFFIX
));
330 if (!borderPainted
) {
331 if (button
.getIcon() != null) {
332 button
.setPreferredSize(new Dimension(button
.getIcon().getIconWidth(), button
.getIcon().getIconHeight()));
335 button
.setBorderPainted(false);
342 * Encloses {@code component} in a scroll pane.
351 public static final JScrollPane
scrollable(final Component component
) {
354 return new JScrollPane(component
);
358 * Packs and updates {@code window}'s minimum size so that it cannot be resized to be smaller than its packed size.
360 * @param <W> The actual type of {@code window}
364 * @return {@code window}
367 public static final <W
extends Window
> W
packAndUpdateMinimumSize(final W window
) {
370 window
.setMinimumSize(null);
372 window
.setMinimumSize(window
.getSize());
378 * Packs and centers {@code window} on the screen.
380 * @param <W> The actual type of {@code window}
384 * @return {@code window}
387 public static final <W
extends Window
> W
packAndCenter(final W window
) {
392 return center(window
);
396 * Centers {@code window} on the screen.
398 * @param <W> the actual type of {@code window}
403 * @return {@code window}
407 public static final <W
extends Window
> W
center(final W window
) {
410 window
.setLocationRelativeTo(null);
416 * Creates a menu bar from the nonnull elements of {@code menus}.
424 public static final JMenuBar
menuBar(final JMenu
... menus
) {
427 final JMenuBar result
= new JMenuBar();
429 for (final JMenu menu
: menus
) {
431 boolean menuHasNonnullItems
= false;
433 for (int i
= 0; i
< menu
.getItemCount(); ++i
) {
434 menuHasNonnullItems
|= menu
.getItem(i
) != null;
437 if (menuHasNonnullItems
) {
447 * Creates a menu from the elements in {@code items}.
448 * <br>A null element generates a separator.
449 * <br>Consecutive null elements are coalesced into only one null element.
450 * <br>If all the elements are null, then the generated menu is empty
451 * (it doesn't even contain a separator).
461 public static final JMenu
menu(final String title
, final JMenuItem
... items
) {
464 final JMenu result
= new JMenu(title
);
466 boolean lastItemWasNull
= true;
468 for (final JMenuItem item
: items
) {
471 } else if (!lastItemWasNull
) {
472 result
.addSeparator();
475 lastItemWasNull
= item
== null;
485 public static final DataFlavor
getURIListAsStringFlavor() {
486 if (uriListAsStringFlavor
== null) {
488 uriListAsStringFlavor
= new DataFlavor("text/uri-list; class=java.lang.String");
489 } catch (final ClassNotFoundException exception
) {
490 getLoggerForThisMethod().log(Level
.SEVERE
, "", exception
);
494 return uriListAsStringFlavor
;
502 * @return a list of files, or an empty list if {@code event} cannot provide a list of files or a string
505 * @throws RuntimeException if an error occurs
507 @SuppressWarnings("unchecked")
508 public static final List
<File
> getFiles(final DropTargetDropEvent event
) {
509 event
.acceptDrop(DnDConstants
.ACTION_COPY_OR_MOVE
);
512 if (event
.isDataFlavorSupported(DataFlavor
.javaFileListFlavor
)) {
513 return (List
<File
>) event
.getTransferable().getTransferData(DataFlavor
.javaFileListFlavor
);
516 final String transferDataString
= (String
) event
.getTransferable().getTransferData(DataFlavor
.stringFlavor
);
518 if (event
.isDataFlavorSupported(getURIListAsStringFlavor())) {
519 final Scanner scanner
= new Scanner(transferDataString
);
520 final List
<File
> result
= new ArrayList
<File
>();
522 while (scanner
.hasNext()) {
523 final String fileURL
= URLDecoder
.decode(scanner
.nextLine(), "UTF-8");
524 final String protocol
= "file:";
526 result
.add(new File(fileURL
.startsWith(protocol
) ? fileURL
.substring(protocol
.length()) : fileURL
));
532 if (event
.isDataFlavorSupported(DataFlavor
.stringFlavor
)) {
533 return Arrays
.asList(new File(transferDataString
));
536 return Collections
.emptyList();
537 } catch (final Exception exception
) {
538 throw unchecked(exception
);
542 public static final void useSystemLookAndFeel() {
544 UIManager
.setLookAndFeel(UIManager
.getSystemLookAndFeelClassName());
545 } catch (final Exception exception
) {
546 getLoggerForThisMethod().log(Level
.WARNING
, "", exception
);
551 * Executes in the AWT Event Dispatching Thread a runnable invoking
552 * the caller method with the specified arguments,
553 * or does nothing if the method is called in that thread.
554 * <br>This method can be used to simplify code that needs to be executed in AWT
555 * by taking care of generating an anonymous inner class implementing {@link Runnable}.
558 * public final void f() {
559 * // Warning: this section might get executed 2 times in different threads
561 * if (SwingTools.canInvokeThisMethodInAWT(this)) {
562 * // This section is executed only once in the AWT Event Dispatching Thread
563 * // For instance, the following instruction doesn't throw
564 * SwingTools.checkAWT();
567 * // Warning: this section might get executed 2 times in different threads
571 * @param object The caller object or {@code null} if the caller is static
577 * @return {@code true} if and only if the method is called in the AWT Event Dispatching Thread
578 * @throws RuntimeException if an error occurs
580 public static final boolean canInvokeThisMethodInAWT(final Object object
, final Object
... arguments
) {
581 if (SwingUtilities
.isEventDispatchThread()) {
585 final Class
<?
> callerClass
= getCallerClass();
586 final String callerMethodName
= getCallerMethodName();
589 SwingUtilities
.invokeAndWait(createInvoker(object
, callerClass
, callerMethodName
, arguments
));
590 } catch (final InterruptedException exception
) {
591 getLoggerForThisMethod().log(Level
.WARNING
, null, exception
);
592 } catch (final InvocationTargetException exception
) {
593 throw unchecked(exception
.getCause());
600 * Non-blocking version of {@link #canInvokeThisMethodInAWT(java.lang.Object, java.lang.Object[])}.
608 * @return {@code true} if and only if the method is called in the AWT Event Dispatching Thread
610 public static final boolean canInvokeLaterThisMethodInAWT(final Object object
, final Object
... arguments
) {
611 if (SwingUtilities
.isEventDispatchThread()) {
615 final Class
<?
> callerClass
= getCallerClass();
616 final String callerMethodName
= getCallerMethodName();
618 SwingUtilities
.invokeLater(createInvoker(object
, callerClass
, callerMethodName
, arguments
));
624 * Creates an action that will invoke the specified method with
625 * the specified arguments when it is performed.
627 * @param objectOrClass
640 public static final InvokerAction
action(final Object objectOrClass
,
641 final String methodName
, final Object
... arguments
) {
642 return new InvokerAction(objectOrClass
, methodName
, arguments
);
647 * @throws IllegalStateException if the current thread is not the AWT Event Dispatching Thread
649 public static final void checkAWT() {
650 if (!SwingUtilities
.isEventDispatchThread()) {
651 throw new IllegalStateException("This section must be executed in the AWT Event Dispatching Thread");
657 * @throws IllegalStateException if the current thread is the AWT Event Dispatching Thread
659 public static final void checkNotAWT() {
660 if (SwingUtilities
.isEventDispatchThread()) {
661 throw new IllegalStateException("This section must not be executed in the AWT Event Dispatching Thread");
673 * @param callerMethodName
683 private static final Runnable
createInvoker(final Object object
, final Class
<?
> callerClass
, final String callerMethodName
, final Object
... arguments
) {
684 return new Runnable() {
687 public final void run() {
688 invoke(object
== null ? callerClass
: object
, callerMethodName
, arguments
);
701 public static final Box
horizontalBox(final Component
... components
) {
704 final Box result
= Box
.createHorizontalBox();
706 for (final Component component
: components
) {
707 result
.add(component
);
720 public static final Box
verticalBox(final Component
... components
) {
723 final Box result
= Box
.createVerticalBox();
725 for (final Component component
: components
) {
726 result
.add(component
);
733 * @param leftComponent
735 * <br>Will become reference
736 * @param rightComponent
738 * <br>Will become reference
743 public static final JSplitPane
horizontalSplit(final Component leftComponent
, final Component rightComponent
) {
746 return new JSplitPane(JSplitPane
.HORIZONTAL_SPLIT
, leftComponent
, rightComponent
);
750 * @param leftComponent
752 * <br>Will become reference
753 * @param rightComponent
755 * <br>Will become reference
760 public static final JSplitPane
verticalSplit(final Component leftComponent
, final Component rightComponent
) {
763 return new JSplitPane(JSplitPane
.VERTICAL_SPLIT
, leftComponent
, rightComponent
);
767 * The methods in this class create localized menu elements using {@link Messages#translate(java.lang.Object, java.lang.Object[])}.
769 * @author codistmonk (creation 2012-04-15)
771 public static final class I18N
{
774 * @throws IllegalInstantiationException To prevent instantiation
777 throw new IllegalInstantiationException();
781 * @param translationKey
783 * <br>Will become reference
790 public static final JMenu
menu(final String translationKey
, final JMenuItem
... items
) {
791 return translate(SwingTools
.menu(translationKey
, items
));
795 * @param translationKey
797 * <br>Will become reference
798 * @param objectOrClass
800 * <br>Will become reference
803 * <br>Will become reference
806 * <br>Will become reference
811 public static final JMenuItem
item(final String translationKey
,
812 final Object objectOrClass
, final String methodName
, final Object
... arguments
) {
813 return translate(new JMenuItem(
814 action(objectOrClass
, methodName
, arguments
)
815 .setName(translationKey
)));
819 * @param translationKey
821 * <br>Will become reference
824 * <br>Will become reference
825 * @param objectOrClass
827 * <br>Will become reference
830 * <br>Will become reference
833 * <br>Will become reference
838 public static final JMenuItem
item(final String translationKey
, final KeyStroke shortcut
,
839 final Object objectOrClass
, final String methodName
, final Object
... arguments
) {
840 return translate(new JMenuItem(
841 action(objectOrClass
, methodName
, arguments
)
842 .setName(translationKey
)
843 .setShortcut(shortcut
)));