/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.resources; import java.util.Locale; import java.awt.Component; import java.awt.Dialog; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Frame; import java.awt.event.ActionListener; import java.awt.event.WindowListener; import java.awt.IllegalComponentStateException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDesktopPane; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JOptionPane; import javax.swing.JTextArea; import javax.swing.LookAndFeel; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; /** * A collection of utility methods for Swing. All <code>show*</code> methods delegate * their work to the corresponding method in {@link JOptionPane}, with two differences: * * <ul> * <li>{@code SwingUtilities}'s method may be invoked from any thread. If they * are invoked from a non-Swing thread, execution will be delegate to the Swing * thread and the calling thread will block until completion.</li> * <li>If a parent component is a {@link JDesktopPane}, dialogs will be rendered as * internal frames instead of frames.</li> * </ul> * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public final class SwingUtilities { /** * Do not allow any instance * of this class to be created. */ private SwingUtilities() { } /** * Insert a Swing component into a frame. The kind of frame depends on the owner: * * <ul> * <li>If {@code owner} or one of its parent is a {@link JDesktopPane}, * then {@code panel} is added into a {@link JInternalFrame}.</li> * <li>If {@code owner} or one of its parent is a {@link Frame} or a {@link Dialog}, * then {@code panel} is added into a {@link JDialog}.</li> * <li>Otherwise, {@code panel} is added into a {@link JFrame}.</li> * </ul> * * @param owner The frame's owner, or {@code null} if none. * @param panel The panel to insert into a frame. * @param title The frame's title. * @param listener A listener to receives frame events. If non-null, then this listener will * be registered to whatever kind of frame this method will constructs. In the special * case where this method constructs an {@linkplain JInternalFrame internal frame} and * the {@code listener} is not an instance of {@link javax.swing.event.InternalFrameListener}, * then this method will wrap the {@code listener} into an {@code InternalFrameListener}. * @return The frame. This frame is not initially visible. The method * {@code Component.setVisible(true)} must be invoked in order to show the frame. */ public static Component toFrame(Component owner, final JComponent panel, final String title, final WindowListener listener) { while (owner != null) { if (owner == panel) { throw new IllegalArgumentException(); } // NOTE: All 'addFooListener(...)' below ignore null argument. No need to check ourself. if (owner instanceof JDesktopPane) { final JInternalFrame frame = new JInternalFrame(title, true, true, true, true); frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE); frame.addInternalFrameListener(InternalWindowListener.wrap(listener)); ((JDesktopPane) owner).add(frame); frame.getContentPane().add(panel); frame.pack(); return frame; } if (owner instanceof Frame) { final JDialog dialog = new JDialog((Frame) owner, title); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.addWindowListener(listener); dialog.getContentPane().add(panel); dialog.pack(); return dialog; } if (owner instanceof Dialog) { final JDialog dialog = new JDialog((Dialog) owner, title); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.addWindowListener(listener); dialog.getContentPane().add(panel); dialog.pack(); return dialog; } owner = owner.getParent(); } // // Add the panel as a standalone window. // This window has its own button on the task bar. // final JFrame frame = new JFrame(title); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.addWindowListener(listener); frame.getContentPane().add(panel); frame.pack(); return frame; } /** * Set the title of the parent frame or internal frame of the specified component. */ public static void setTitle(Component component, final String title) { while (component != null) { if (component instanceof JInternalFrame) { ((JInternalFrame) component).setTitle(title); } if (component instanceof Frame) { ((Frame) component).setTitle(title); return; } if (component instanceof Dialog) { ((Dialog) component).setTitle(title); return; } } } /** * Brings up a "Ok/Cancel" dialog with no icon. This method can be invoked * from any thread and blocks until the user click on "Ok" or "Cancel". * * @param owner The parent component. Dialog will apears on top of this owner. * @param dialog The dialog content to show. * @param title The title string for the dialog. * @return {@code true} if user clicked "Ok", {@code false} otherwise. */ public static boolean showOptionDialog(final Component owner, final Object dialog, final String title) { return showOptionDialog(owner, dialog, title, null); } /** * Brings up a "Ok/Cancel/Reset" dialog with no icon. This method can be invoked * from any thread and blocks until the user click on "Ok" or "Cancel". * * @param owner The parent component. Dialog will apears on top of this owner. * @param dialog The dialog content to show. * @param title The title string for the dialog. * @param reset Action to execute when user press "Reset", or {@code null} * if there is no "Reset" button. If {@code reset} is an * instance of {@link Action}, the button label will be set * according the action's properties. * @return {@code true} if user clicked "Ok", {@code false} otherwise. */ public static boolean showOptionDialog(final Component owner, final Object dialog, final String title, final ActionListener reset) { // Delegates to Swing thread if this method is invoked from an other thread. if (!EventQueue.isDispatchThread()) { final boolean result[] = new boolean[1]; invokeAndWait(new Runnable() { public void run() { result[0] = showOptionDialog(owner, dialog, title, reset); } }); return result[0]; } // Constructs the buttons bar. Object[] options = null; Object initialValue = null; int okChoice = JOptionPane.OK_OPTION; if (reset != null) { final Vocabulary resources = Vocabulary.getResources(owner!=null ? owner.getLocale() : null); final JButton button; if (reset instanceof Action) { button = new JButton((Action)reset); } else { button = new JButton(resources.getString(VocabularyKeys.RESET)); button.addActionListener(reset); } options = new Object[] { resources.getString(VocabularyKeys.OK), resources.getString(VocabularyKeys.CANCEL), button }; initialValue = options[okChoice=0]; } // Brings ups the dialog box. final int choice; if (JOptionPane.getDesktopPaneForComponent(owner)!=null) { choice=JOptionPane.showInternalOptionDialog( owner, // Composante parente dialog, // Message title, // Titre de la boîte de dialogue JOptionPane.OK_CANCEL_OPTION, // Boutons à placer JOptionPane.PLAIN_MESSAGE, // Type du message null, // Icone options, // Liste des boutons initialValue); // Bouton par défaut } else { choice=JOptionPane.showOptionDialog( owner, // Composante parente dialog, // Message title, // Titre de la boîte de dialogue JOptionPane.OK_CANCEL_OPTION, // Boutons à placer JOptionPane.PLAIN_MESSAGE, // Type du message null, // Icone options, // Liste des boutons initialValue); // Bouton par défaut } return choice==okChoice; } /** * Brings up a message dialog with a "Ok" button. This method can be invoked * from any thread and blocks until the user click on "Ok". * * @param owner The parent component. Dialog will apears on top of this owner. * @param message The dialog content to show. * @param title The title string for the dialog. * @param type The message type * ({@link JOptionPane#ERROR_MESSAGE}, * {@link JOptionPane#INFORMATION_MESSAGE}, * {@link JOptionPane#WARNING_MESSAGE}, * {@link JOptionPane#QUESTION_MESSAGE} or * {@link JOptionPane#PLAIN_MESSAGE}). */ public static void showMessageDialog(final Component owner, final Object message, final String title, final int type) { if (!EventQueue.isDispatchThread()) { invokeAndWait(new Runnable() { public void run() { showMessageDialog(owner, message, title, type); } }); return; } if (JOptionPane.getDesktopPaneForComponent(owner)!=null) { JOptionPane.showInternalMessageDialog( owner, // Composante parente message, // Message title, // Titre de la boîte de dialogue type); // Type du message } else { JOptionPane.showMessageDialog( owner, // Composante parente message, // Message title, // Titre de la boîte de dialogue type); // Type du message } } /** * Brings up a confirmation dialog with "Yes/No" buttons. This method can be * invoked from any thread and blocks until the user click on "Yes" or "No". * * @param owner The parent component. Dialog will apears on top of this owner. * @param message The dialog content to show. * @param title The title string for the dialog. * @param type The message type * ({@link JOptionPane#ERROR_MESSAGE}, * {@link JOptionPane#INFORMATION_MESSAGE}, * {@link JOptionPane#WARNING_MESSAGE}, * {@link JOptionPane#QUESTION_MESSAGE} or * {@link JOptionPane#PLAIN_MESSAGE}). * @return {@code true} if user clicked on "Yes", {@code false} otherwise. */ public static boolean showConfirmDialog(final Component owner, final Object message, final String title, final int type) { if (!EventQueue.isDispatchThread()) { final boolean result[] = new boolean[1]; invokeAndWait(new Runnable() { public void run() { result[0]=showConfirmDialog(owner, message, title, type); } }); return result[0]; } final int choice; if (JOptionPane.getDesktopPaneForComponent(owner)!=null) { choice=JOptionPane.showInternalConfirmDialog( owner, // Composante parente message, // Message title, // Titre de la boîte de dialogue JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître type); // Type du message } else { choice=JOptionPane.showConfirmDialog( owner, // Composante parente message, // Message title, // Titre de la boîte de dialogue JOptionPane.YES_NO_OPTION, // Boutons à faire apparaître type); // Type du message } return choice==JOptionPane.YES_OPTION; } /** * Retourne une étiquette pour la composante spécifiée. * Le texte de l'étiquette pourra éventuellement être * distribué sur plusieurs lignes. * * @param owner Composante pour laquelle on construit une étiquette. * L'étiquette aura la même largeur que {@code owner}. * @param text Texte à placer dans l'étiquette. */ public static JComponent getMultilineLabelFor(final JComponent owner, final String text) { final JTextArea label=new JTextArea(text); final Dimension size=owner.getPreferredSize(); size.height=label.getMaximumSize().height; label.setMaximumSize (size); label.setWrapStyleWord(true); label.setLineWrap (true); label.setEditable (false); label.setFocusable (false); label.setOpaque (false); label.setBorder (null); // Certains L&F placent une bordure. LookAndFeel.installColorsAndFont(label, "Label.background", "Label.foreground", "Label.font"); return label; } /** * Returns the locale for the specified component, or a default one if the component * is not yet part of a container hierarchy. */ public static Locale getLocale(final Component component) { if (component != null) try { return component.getLocale(); } catch (IllegalComponentStateException ignore) { // Ignore. Will returns de default locale below. } return JComponent.getDefaultLocale(); } /** * Causes runnable to have its run method called in the dispatch thread of * the event queue. This will happen after all pending events are processed. * The call blocks until this has happened. */ public static void invokeAndWait(final Runnable runnable) { if (EventQueue.isDispatchThread()) { runnable.run(); } else { try { EventQueue.invokeAndWait(runnable); } catch (InterruptedException exception) { // Someone don't want to let us sleep. Go back to work. } catch (InvocationTargetException target) { final Throwable exception=target.getTargetException(); if (exception instanceof RuntimeException) { throw (RuntimeException) exception; } if (exception instanceof Error) { throw (Error) exception; } // Should not happen, since {@link Runnable#run} do not allow checked exception. throw new UndeclaredThrowableException(exception, exception.getLocalizedMessage()); } } } }