Source code: org/geotools/resources/SwingUtilities.java
1 /*
2 * -*- mode: java; c-basic-indent: 4; indent-tabs-mode: nil -*-
3 * :indentSize=4:noTabs=true:tabSize=4:indentOnTab=true:indentOnEnter=true:mode=java:
4 * ex: set tabstop=4 expandtab:
5 *
6 * MrPostman - webmail <-> email gateway
7 * Copyright (C) 2002-2003 MrPostman Development Group
8 * Projectpage: http://mrbook.org/mrpostman/
9 *
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 * In particular, this implies that users are responsible for
21 * using MrPostman after reading the terms and conditions given
22 * by their web-mail provider.
23 *
24 * You should have received a copy of the GNU General Public License
25 * Named LICENSE in the base directory of this distribution,
26 * if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 */
29
30 /*
31 * Geotools - OpenSource mapping toolkit
32 * (C) 2002, Centre for Computational Geography
33 * (C) 2001, Institut de Recherche pour le Développement
34 *
35 * This library is free software; you can redistribute it and/or
36 * modify it under the terms of the GNU Lesser General Public
37 * License as published by the Free Software Foundation; either
38 * version 2.1 of the License, or (at your option) any later version.
39 *
40 * This library is distributed in the hope that it will be useful,
41 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43 * Lesser General Public License for more details.
44 *
45 * You should have received a copy of the GNU Lesser General Public
46 * License along with this library; if not, write to the Free Software
47 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
48 *
49 *
50 * Contacts:
51 * UNITED KINGDOM: James Macgill
52 * mailto:j.macgill@geog.leeds.ac.uk
53 *
54 * FRANCE: Surveillance de l'Environnement Assistée par Satellite
55 * Institut de Recherche pour le Développement / US-Espace
56 * mailto:seasnet@teledetection.fr
57 *
58 * CANADA: Observatoire du Saint-Laurent
59 * Institut Maurice-Lamontagne
60 * mailto:osl@osl.gc.ca
61 */
62 package org.geotools.resources;
63
64 import org.geotools.resources.gui.ResourceKeys;
65 // Geotools dependencies
66 import org.geotools.resources.gui.Resources;
67
68 import java.awt.Component;
69 import java.awt.Dialog;
70 import java.awt.Dimension;
71 import java.awt.EventQueue;
72 import java.awt.Frame;
73 import java.awt.event.ActionListener;
74 import java.awt.event.WindowListener;
75
76 import java.lang.reflect.InvocationTargetException;
77 import java.lang.reflect.UndeclaredThrowableException;
78
79 import javax.swing.Action;
80 import javax.swing.JButton;
81 import javax.swing.JComponent;
82 import javax.swing.JDesktopPane;
83 import javax.swing.JDialog;
84 import javax.swing.JFrame;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JOptionPane;
87 import javax.swing.JTextArea;
88 import javax.swing.LookAndFeel;
89
90
91 /**
92 * A collection of utility methods for Swing. All <code>show*</code> methods delegate
93 * their work to the corresponding method in {@link JOptionPane}, with two differences:
94 *
95 * <ul>
96 * <li><code>SwingUtilities</code>'s method may be invoked from any thread. If they
97 * are invoked from a non-Swing thread, execution will be delegate to the Swing
98 * thread and the calling thread will block until completion.</li>
99 * <li>If a parent component is a {@link JDesktopPane}, dialogs will be rendered as
100 * internal frames instead of frames.</li>
101 * </ul>
102 *
103 * @version $Id: SwingUtilities.java,v 1.5 2003/02/09 23:38:12 lbruand Exp $
104 * @author Martin Desruisseaux
105 */
106 public final class SwingUtilities {
107 public static final String CVSID = "$Id: SwingUtilities.java,v 1.5 2003/02/09 23:38:12 lbruand Exp $";
108
109 /**
110 * Do not allow any instance
111 * of this class to be created.
112 */
113 private SwingUtilities() {
114 }
115
116 /**
117 * Insert a Swing component into a frame. The kind of frame depends on the owner:
118 *
119 * <ul>
120 * <li>If <code>owner</code> or one of its parent is a {@link JDesktopPane},
121 * then <code>panel</code> is added into a {@link JInternalFrame}.</li>
122 * <li>If <code>owner</code> or one of its parent is a {@link Frame} or a {@link Dialog},
123 * then <code>panel</code> is added into a {@link JDialog}.</li>
124 * <li>Otherwise, <code>panel</code> is added into a {@link JFrame}.</li>
125 * </ul>
126 *
127 * @param owner The frame's owner, or <code>null</code> if none.
128 * @param panel The panel to insert into a frame.
129 * @param title The frame's title.
130 * @param listener A listener to receives frame events. If non-null, then this listener will
131 * be registered to whatever kind of frame this method will constructs. In the special
132 * case where this method constructs an {@linkplain JInternalFrame internal frame} and
133 * the <code>listener</code> is not an instance of {@link InternalFrameListener}, then
134 * this method will wrap the <code>listener</code> into an
135 * <code>InternalFrameListener</code>.
136 * @return The frame. This frame is not initially visible. The method
137 * <code>Component.setVisible(true)</code> must be invoked
138 * in order to show the frame.
139 */
140 public static Component toFrame(Component owner, final JComponent panel, final String title,
141 final WindowListener listener) {
142 while (owner != null) {
143 if (owner == panel) {
144 throw new IllegalArgumentException();
145 }
146
147 if (owner instanceof JDesktopPane) {
148 final JInternalFrame frame = new JInternalFrame(title, true, true, true, true);
149 frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
150 frame.addInternalFrameListener(InternalWindowListener.wrap(listener));
151 ((JDesktopPane) owner).add(frame);
152 frame.getContentPane().add(panel);
153 frame.pack();
154 return frame;
155 }
156
157 if (owner instanceof Frame) {
158 final JDialog dialog = new JDialog((Frame) owner, title);
159 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
160 dialog.addWindowListener(listener);
161 dialog.getContentPane().add(panel);
162 dialog.pack();
163 return dialog;
164 }
165
166 if (owner instanceof Dialog) {
167 final JDialog dialog = new JDialog((Dialog) owner, title);
168 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
169 dialog.addWindowListener(listener);
170 dialog.getContentPane().add(panel);
171 dialog.pack();
172 return dialog;
173 }
174 owner = owner.getParent();
175 }
176
177 //
178 // Add the panel as a standalone window.
179 // This window has its own button on the task bar.
180 //
181 final JFrame frame = new JFrame(title);
182 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
183 frame.addWindowListener(listener);
184 frame.getContentPane().add(panel);
185 frame.pack();
186 return frame;
187 }
188
189 /**
190 * Brings up a "Ok/Cancel" dialog with no icon. This method can be invoked
191 * from any thread and blocks until the user click on "Ok" or "Cancel".
192 *
193 * @param owner The parent component. Dialog will apears on top of this owner.
194 * @param dialog The dialog content to show.
195 * @param title The title string for the dialog.
196 * @return <code>true</code> if user clicked "Ok", <code>false</code> otherwise.
197 */
198 public static boolean showOptionDialog(final Component owner, final Object dialog, final String title) {
199 return showOptionDialog(owner, dialog, title, null);
200 }
201
202 /**
203 * Brings up a "Ok/Cancel/Reset" dialog with no icon. This method can be invoked
204 * from any thread and blocks until the user click on "Ok" or "Cancel".
205 *
206 * @param owner The parent component. Dialog will apears on top of this owner.
207 * @param dialog The dialog content to show.
208 * @param title The title string for the dialog.
209 * @param reset Action to execute when user press "Reset", or <code>null</code>
210 * if there is no "Reset" button. If <code>reset</code> is an
211 * instance of {@link Action}, the button label will be set
212 * according the action's properties.
213 * @return <code>true</code> if user clicked "Ok", <code>false</code> otherwise.
214 */
215 public static boolean showOptionDialog(final Component owner, final Object dialog, final String title,
216 final ActionListener reset) {
217 // Delegate to Swing thread if this method
218 // is invoked from an other thread.
219 if (!EventQueue.isDispatchThread()) {
220 final boolean[] result = new boolean[1];
221 invokeAndWait(new Runnable() {
222 public void run() {
223 result[0] = showOptionDialog(owner, dialog, title, reset);
224 }
225 });
226 return result[0];
227 }
228
229 // Construct the buttons bar.
230 Object[] options = null;
231 Object initialValue = null;
232 int okChoice = JOptionPane.OK_OPTION;
233
234 if (reset != null) {
235 final Resources resources = Resources.getResources((owner != null) ? owner.getLocale() : null);
236 final JButton button;
237
238 if (reset instanceof Action) {
239 button = new JButton((Action) reset);
240 } else {
241 button = new JButton(resources.getString(ResourceKeys.RESET));
242 button.addActionListener(reset);
243 }
244 options = new Object[] {
245 resources.getString(ResourceKeys.OK), resources.getString(ResourceKeys.CANCEL), button
246 };
247 initialValue = options[okChoice = 0];
248 }
249
250 // Bring ups the dialog box.
251 final int choice;
252
253 if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
254 choice = JOptionPane.showInternalOptionDialog(owner, // Composante parente
255 dialog, // Message
256 title, // Titre de la boîte de dialogue
257 JOptionPane.OK_CANCEL_OPTION, // Boutons à placer
258 JOptionPane.PLAIN_MESSAGE, // Type du message
259 null, // Icone
260 options, // Liste des boutons
261 initialValue); // Bouton par défaut
262 } else {
263 choice = JOptionPane.showOptionDialog(owner, // Composante parente
264 dialog, // Message
265 title, // Titre de la boîte de dialogue
266 JOptionPane.OK_CANCEL_OPTION, // Boutons à placer
267 JOptionPane.PLAIN_MESSAGE, // Type du message
268 null, // Icone
269 options, // Liste des boutons
270 initialValue); // Bouton par défaut
271 }
272 return choice == okChoice;
273 }
274
275 /**
276 * Brings up a message dialog with a "Ok" button. This method can be invoked
277 * from any thread and blocks until the user click on "Ok".
278 *
279 * @param owner The parent component. Dialog will apears on top of this owner.
280 * @param message The dialog content to show.
281 * @param title The title string for the dialog.
282 * @param type The message type
283 * ({@link JOptionPane#ERROR_MESSAGE},
284 * {@link JOptionPane#INFORMATION_MESSAGE},
285 * {@link JOptionPane#WARNING_MESSAGE},
286 * {@link JOptionPane#QUESTION_MESSAGE} or
287 * {@link JOptionPane#PLAIN_MESSAGE}).
288 */
289 public static void showMessageDialog(final Component owner, final Object message, final String title, final int type) {
290 if (!EventQueue.isDispatchThread()) {
291 invokeAndWait(new Runnable() {
292 public void run() {
293 showMessageDialog(owner, message, title, type);
294 }
295 });
296 return;
297 }
298
299 if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
300 JOptionPane.showInternalMessageDialog(owner, // Composante parente
301 message, // Message
302 title, // Titre de la boîte de dialogue
303 type); // Type du message
304 } else {
305 JOptionPane.showMessageDialog(owner, // Composante parente
306 message, // Message
307 title, // Titre de la boîte de dialogue
308 type); // Type du message
309 }
310 }
311
312 /**
313 * Brings up a confirmation dialog with "Yes/No" buttons. This method can be
314 * invoked from any thread and blocks until the user click on "Yes" or "No".
315 *
316 * @param owner The parent component. Dialog will apears on top of this owner.
317 * @param message The dialog content to show.
318 * @param title The title string for the dialog.
319 * @param type The message type
320 * ({@link JOptionPane#ERROR_MESSAGE},
321 * {@link JOptionPane#INFORMATION_MESSAGE},
322 * {@link JOptionPane#WARNING_MESSAGE},
323 * {@link JOptionPane#QUESTION_MESSAGE} or
324 * {@link JOptionPane#PLAIN_MESSAGE}).
325 * @return <code>true</code> if user clicked on "Yes", <code>false</code> otherwise.
326 */
327 public static boolean showConfirmDialog(final Component owner, final Object message, final String title,
328 final int type) {
329 if (!EventQueue.isDispatchThread()) {
330 final boolean[] result = new boolean[1];
331 invokeAndWait(new Runnable() {
332 public void run() {
333 result[0] = showConfirmDialog(owner, message, title, type);
334 }
335 });
336 return result[0];
337 }
338 final int choice;
339
340 if (JOptionPane.getDesktopPaneForComponent(owner) != null) {
341 choice = JOptionPane.showInternalConfirmDialog(owner, // Composante parente
342 message, // Message
343 title, // Titre de la boîte de dialogue
344 JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître
345 type); // Type du message
346 } else {
347 choice = JOptionPane.showConfirmDialog(owner, // Composante parente
348 message, // Message
349 title, // Titre de la boîte de dialogue
350 JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître
351 type); // Type du message
352 }
353 return choice == JOptionPane.YES_OPTION;
354 }
355
356 /**
357 * Retourne une étiquette pour la composante spécifiée.
358 * Le texte de l'étiquette pourra éventuellement être
359 * distribué sur plusieurs lignes.
360 *
361 * @param owner Composante pour laquelle on construit une étiquette.
362 * L'étiquette aura la même largeur que <code>owner</code>.
363 * @param text Texte à placer dans l'étiquette.
364 */
365 public static JComponent getMultilineLabelFor(final JComponent owner, final String text) {
366 final JTextArea label = new JTextArea(text);
367 final Dimension size = owner.getPreferredSize();
368 size.height = label.getMaximumSize().height;
369 label.setMaximumSize(size);
370 label.setWrapStyleWord(true);
371 label.setLineWrap(true);
372 label.setEditable(false);
373 label.setFocusable(false);
374 label.setOpaque(false);
375 label.setBorder(null); // Certains L&F placent une bordure.
376 LookAndFeel.installColorsAndFont(label, "Label.background", "Label.foreground", "Label.font");
377 return label;
378 }
379
380 /**
381 * Causes runnable to have its run method called in the dispatch thread of
382 * the event queue. This will happen after all pending events are processed.
383 * The call blocks until this has happened.
384 */
385 public static void invokeAndWait(final Runnable runnable) {
386 if (EventQueue.isDispatchThread()) {
387 runnable.run();
388 } else {
389 try {
390 EventQueue.invokeAndWait(runnable);
391 } catch (InterruptedException exception) {
392 // Someone don't want to let us sleep. Go back to work.
393 } catch (InvocationTargetException target) {
394 final Throwable exception = target.getTargetException();
395
396 if (exception instanceof RuntimeException) {
397 throw (RuntimeException) exception;
398 }
399
400 if (exception instanceof Error) {
401 throw (Error) exception;
402 }
403
404 // Should not happen, since {@link Runnable#run} do not allow checked exception.
405 throw new UndeclaredThrowableException(exception, exception.getLocalizedMessage());
406 }
407 }
408 }
409 }