001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.resources;
018:
019: // J2SE library
020: import java.util.Locale;
021: import java.awt.Component;
022: import java.awt.Dialog;
023: import java.awt.Dimension;
024: import java.awt.EventQueue;
025: import java.awt.Frame;
026: import java.awt.event.ActionListener;
027: import java.awt.event.WindowListener;
028: import java.awt.IllegalComponentStateException;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.UndeclaredThrowableException;
031:
032: // Swing dependencies
033: import javax.swing.Action;
034: import javax.swing.JButton;
035: import javax.swing.JComponent;
036: import javax.swing.JDesktopPane;
037: import javax.swing.JDialog;
038: import javax.swing.JFrame;
039: import javax.swing.JInternalFrame;
040: import javax.swing.JOptionPane;
041: import javax.swing.JTextArea;
042: import javax.swing.LookAndFeel;
043: import javax.swing.event.InternalFrameListener;
044:
045: // Geotools dependencies
046: import org.geotools.resources.i18n.Vocabulary;
047: import org.geotools.resources.i18n.VocabularyKeys;
048:
049: /**
050: * A collection of utility methods for Swing. All <code>show*</code> methods delegate
051: * their work to the corresponding method in {@link JOptionPane}, with two differences:
052: *
053: * <ul>
054: * <li>{@code SwingUtilities}'s method may be invoked from any thread. If they
055: * are invoked from a non-Swing thread, execution will be delegate to the Swing
056: * thread and the calling thread will block until completion.</li>
057: * <li>If a parent component is a {@link JDesktopPane}, dialogs will be rendered as
058: * internal frames instead of frames.</li>
059: * </ul>
060: *
061: * @since 2.0
062: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/resources/SwingUtilities.java $
063: * @version $Id: SwingUtilities.java 22443 2006-10-27 20:47:22Z desruisseaux $
064: * @author Martin Desruisseaux
065: */
066: public final class SwingUtilities {
067: /**
068: * Do not allow any instance
069: * of this class to be created.
070: */
071: private SwingUtilities() {
072: }
073:
074: /**
075: * Insert a Swing component into a frame. The kind of frame depends on the owner:
076: *
077: * <ul>
078: * <li>If {@code owner} or one of its parent is a {@link JDesktopPane},
079: * then {@code panel} is added into a {@link JInternalFrame}.</li>
080: * <li>If {@code owner} or one of its parent is a {@link Frame} or a {@link Dialog},
081: * then {@code panel} is added into a {@link JDialog}.</li>
082: * <li>Otherwise, {@code panel} is added into a {@link JFrame}.</li>
083: * </ul>
084: *
085: * @param owner The frame's owner, or {@code null} if none.
086: * @param panel The panel to insert into a frame.
087: * @param title The frame's title.
088: * @param listener A listener to receives frame events. If non-null, then this listener will
089: * be registered to whatever kind of frame this method will constructs. In the special
090: * case where this method constructs an {@linkplain JInternalFrame internal frame} and
091: * the {@code listener} is not an instance of {@link InternalFrameListener}, then
092: * this method will wrap the {@code listener} into an {@code InternalFrameListener}.
093: * @return The frame. This frame is not initially visible. The method
094: * {@code Component.setVisible(true)} must be invoked in order to show the frame.
095: */
096: public static Component toFrame(Component owner,
097: final JComponent panel, final String title,
098: final WindowListener listener) {
099: while (owner != null) {
100: if (owner == panel) {
101: throw new IllegalArgumentException();
102: }
103: // NOTE: All 'addFooListener(...)' below ignore null argument. No need to check ourself.
104: if (owner instanceof JDesktopPane) {
105: final JInternalFrame frame = new JInternalFrame(title,
106: true, true, true, true);
107: frame
108: .setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
109: frame.addInternalFrameListener(InternalWindowListener
110: .wrap(listener));
111: ((JDesktopPane) owner).add(frame);
112: frame.getContentPane().add(panel);
113: frame.pack();
114: return frame;
115: }
116: if (owner instanceof Frame) {
117: final JDialog dialog = new JDialog((Frame) owner, title);
118: dialog
119: .setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
120: dialog.addWindowListener(listener);
121: dialog.getContentPane().add(panel);
122: dialog.pack();
123: return dialog;
124: }
125: if (owner instanceof Dialog) {
126: final JDialog dialog = new JDialog((Dialog) owner,
127: title);
128: dialog
129: .setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
130: dialog.addWindowListener(listener);
131: dialog.getContentPane().add(panel);
132: dialog.pack();
133: return dialog;
134: }
135: owner = owner.getParent();
136: }
137: //
138: // Add the panel as a standalone window.
139: // This window has its own button on the task bar.
140: //
141: final JFrame frame = new JFrame(title);
142: frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
143: frame.addWindowListener(listener);
144: frame.getContentPane().add(panel);
145: frame.pack();
146: return frame;
147: }
148:
149: /**
150: * Set the title of the parent frame or internal frame of the specified component.
151: */
152: public static void setTitle(Component component, final String title) {
153: while (component != null) {
154: if (component instanceof JInternalFrame) {
155: ((JInternalFrame) component).setTitle(title);
156: }
157: if (component instanceof Frame) {
158: ((Frame) component).setTitle(title);
159: return;
160: }
161: if (component instanceof Dialog) {
162: ((Dialog) component).setTitle(title);
163: return;
164: }
165: }
166: }
167:
168: /**
169: * Brings up a "Ok/Cancel" dialog with no icon. This method can be invoked
170: * from any thread and blocks until the user click on "Ok" or "Cancel".
171: *
172: * @param owner The parent component. Dialog will apears on top of this owner.
173: * @param dialog The dialog content to show.
174: * @param title The title string for the dialog.
175: * @return {@code true} if user clicked "Ok", {@code false} otherwise.
176: */
177: public static boolean showOptionDialog(final Component owner,
178: final Object dialog, final String title) {
179: return showOptionDialog(owner, dialog, title, null);
180: }
181:
182: /**
183: * Brings up a "Ok/Cancel/Reset" dialog with no icon. This method can be invoked
184: * from any thread and blocks until the user click on "Ok" or "Cancel".
185: *
186: * @param owner The parent component. Dialog will apears on top of this owner.
187: * @param dialog The dialog content to show.
188: * @param title The title string for the dialog.
189: * @param reset Action to execute when user press "Reset", or {@code null}
190: * if there is no "Reset" button. If {@code reset} is an
191: * instance of {@link Action}, the button label will be set
192: * according the action's properties.
193: * @return {@code true} if user clicked "Ok", {@code false} otherwise.
194: */
195: public static boolean showOptionDialog(final Component owner,
196: final Object dialog, final String title,
197: final ActionListener reset) {
198: // Delegates to Swing thread if this method is invoked from an other thread.
199: if (!EventQueue.isDispatchThread()) {
200: final boolean result[] = new boolean[1];
201: invokeAndWait(new Runnable() {
202: public void run() {
203: result[0] = showOptionDialog(owner, dialog, title,
204: reset);
205: }
206: });
207: return result[0];
208: }
209: // Constructs the buttons bar.
210: Object[] options = null;
211: Object initialValue = null;
212: int okChoice = JOptionPane.OK_OPTION;
213: if (reset != null) {
214: final Vocabulary resources = Vocabulary
215: .getResources(owner != null ? owner.getLocale()
216: : null);
217: final JButton button;
218: if (reset instanceof Action) {
219: button = new JButton((Action) reset);
220: } else {
221: button = new JButton(resources
222: .getString(VocabularyKeys.RESET));
223: button.addActionListener(reset);
224: }
225: options = new Object[] {
226: resources.getString(VocabularyKeys.OK),
227: resources.getString(VocabularyKeys.CANCEL), button };
228: initialValue = options[okChoice = 0];
229: }
230:
231: // Brings ups the dialog box.
232: final int choice;
233: if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
234: choice = JOptionPane.showInternalOptionDialog(owner, // Composante parente
235: dialog, // Message
236: title, // Titre de la boîte de dialogue
237: JOptionPane.OK_CANCEL_OPTION, // Boutons à placer
238: JOptionPane.PLAIN_MESSAGE, // Type du message
239: null, // Icone
240: options, // Liste des boutons
241: initialValue); // Bouton par défaut
242: } else {
243: choice = JOptionPane.showOptionDialog(owner, // Composante parente
244: dialog, // Message
245: title, // Titre de la boîte de dialogue
246: JOptionPane.OK_CANCEL_OPTION, // Boutons à placer
247: JOptionPane.PLAIN_MESSAGE, // Type du message
248: null, // Icone
249: options, // Liste des boutons
250: initialValue); // Bouton par défaut
251: }
252: return choice == okChoice;
253: }
254:
255: /**
256: * Brings up a message dialog with a "Ok" button. This method can be invoked
257: * from any thread and blocks until the user click on "Ok".
258: *
259: * @param owner The parent component. Dialog will apears on top of this owner.
260: * @param message The dialog content to show.
261: * @param title The title string for the dialog.
262: * @param type The message type
263: * ({@link JOptionPane#ERROR_MESSAGE},
264: * {@link JOptionPane#INFORMATION_MESSAGE},
265: * {@link JOptionPane#WARNING_MESSAGE},
266: * {@link JOptionPane#QUESTION_MESSAGE} or
267: * {@link JOptionPane#PLAIN_MESSAGE}).
268: */
269: public static void showMessageDialog(final Component owner,
270: final Object message, final String title, final int type) {
271: if (!EventQueue.isDispatchThread()) {
272: invokeAndWait(new Runnable() {
273: public void run() {
274: showMessageDialog(owner, message, title, type);
275: }
276: });
277: return;
278: }
279: if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
280: JOptionPane.showInternalMessageDialog(owner, // Composante parente
281: message, // Message
282: title, // Titre de la boîte de dialogue
283: type); // Type du message
284: } else {
285: JOptionPane.showMessageDialog(owner, // Composante parente
286: message, // Message
287: title, // Titre de la boîte de dialogue
288: type); // Type du message
289: }
290: }
291:
292: /**
293: * Brings up a confirmation dialog with "Yes/No" buttons. This method can be
294: * invoked from any thread and blocks until the user click on "Yes" or "No".
295: *
296: * @param owner The parent component. Dialog will apears on top of this owner.
297: * @param message The dialog content to show.
298: * @param title The title string for the dialog.
299: * @param type The message type
300: * ({@link JOptionPane#ERROR_MESSAGE},
301: * {@link JOptionPane#INFORMATION_MESSAGE},
302: * {@link JOptionPane#WARNING_MESSAGE},
303: * {@link JOptionPane#QUESTION_MESSAGE} or
304: * {@link JOptionPane#PLAIN_MESSAGE}).
305: * @return {@code true} if user clicked on "Yes", {@code false} otherwise.
306: */
307: public static boolean showConfirmDialog(final Component owner,
308: final Object message, final String title, final int type) {
309: if (!EventQueue.isDispatchThread()) {
310: final boolean result[] = new boolean[1];
311: invokeAndWait(new Runnable() {
312: public void run() {
313: result[0] = showConfirmDialog(owner, message,
314: title, type);
315: }
316: });
317: return result[0];
318: }
319: final int choice;
320: if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
321: choice = JOptionPane.showInternalConfirmDialog(owner, // Composante parente
322: message, // Message
323: title, // Titre de la boîte de dialogue
324: JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître
325: type); // Type du message
326: } else {
327: choice = JOptionPane.showConfirmDialog(owner, // Composante parente
328: message, // Message
329: title, // Titre de la boîte de dialogue
330: JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître
331: type); // Type du message
332: }
333: return choice == JOptionPane.YES_OPTION;
334: }
335:
336: /**
337: * Retourne une étiquette pour la composante spécifiée.
338: * Le texte de l'étiquette pourra éventuellement être
339: * distribué sur plusieurs lignes.
340: *
341: * @param owner Composante pour laquelle on construit une étiquette.
342: * L'étiquette aura la même largeur que {@code owner}.
343: * @param text Texte à placer dans l'étiquette.
344: */
345: public static JComponent getMultilineLabelFor(
346: final JComponent owner, final String text) {
347: final JTextArea label = new JTextArea(text);
348: final Dimension size = owner.getPreferredSize();
349: size.height = label.getMaximumSize().height;
350: label.setMaximumSize(size);
351: label.setWrapStyleWord(true);
352: label.setLineWrap(true);
353: label.setEditable(false);
354: label.setFocusable(false);
355: label.setOpaque(false);
356: label.setBorder(null); // Certains L&F placent une bordure.
357: LookAndFeel.installColorsAndFont(label, "Label.background",
358: "Label.foreground", "Label.font");
359: return label;
360: }
361:
362: /**
363: * Returns the locale for the specified component, or a default one if the component
364: * is not yet part of a container hierarchy.
365: */
366: public static Locale getLocale(final Component component) {
367: if (component != null)
368: try {
369: return component.getLocale();
370: } catch (IllegalComponentStateException ignore) {
371: // Ignore. Will returns de default locale below.
372: }
373: return JComponent.getDefaultLocale();
374: }
375:
376: /**
377: * Causes runnable to have its run method called in the dispatch thread of
378: * the event queue. This will happen after all pending events are processed.
379: * The call blocks until this has happened.
380: */
381: public static void invokeAndWait(final Runnable runnable) {
382: if (EventQueue.isDispatchThread()) {
383: runnable.run();
384: } else {
385: try {
386: EventQueue.invokeAndWait(runnable);
387: } catch (InterruptedException exception) {
388: // Someone don't want to let us sleep. Go back to work.
389: } catch (InvocationTargetException target) {
390: final Throwable exception = target.getTargetException();
391: if (exception instanceof RuntimeException) {
392: throw (RuntimeException) exception;
393: }
394: if (exception instanceof Error) {
395: throw (Error) exception;
396: }
397: // Should not happen, since {@link Runnable#run} do not allow checked exception.
398: throw new UndeclaredThrowableException(exception,
399: exception.getLocalizedMessage());
400: }
401: }
402: }
403: }
|