/* * 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.gui.swing.referencing; // Time import java.util.Date; import java.util.TimeZone; import java.util.Calendar; // Geometry and coordinates import java.awt.Dimension; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; // User interface (Swing) import java.awt.Insets; import java.awt.Component; import java.awt.BorderLayout; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JComboBox; import javax.swing.JTextField; import javax.swing.JOptionPane; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.JRadioButton; import javax.swing.BorderFactory; import javax.swing.AbstractButton; import javax.swing.JSpinner; import javax.swing.SpinnerModel; import javax.swing.SpinnerDateModel; import javax.swing.SpinnerNumberModel; import javax.swing.AbstractSpinnerModel; import javax.swing.JFormattedTextField; import javax.swing.text.InternationalFormatter; // Events import java.awt.EventQueue; import java.util.EventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; // Parsing and formating import java.text.Format; import java.text.DateFormat; import java.text.NumberFormat; import java.text.ParseException; // Miscellaneous import java.util.Arrays; import java.util.Locale; // Geotools dependencies import org.geotools.measure.Angle; import org.geotools.measure.Latitude; import org.geotools.measure.Longitude; import org.geotools.measure.AngleFormat; // Resources import org.geotools.resources.SwingUtilities; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.resources.geometry.XDimension2D; /** * A pane of controls designed to allow a user to select spatio-temporal coordinates. * Current implementation uses geographic coordinates (longitudes/latitudes) and dates * according some locale calendar. Future version may allow the use of user-specified * coordinate system. Latitudes are constrained in the range 90°S to 90°N inclusive. * Longitudes are constrained in the range 180°W to 180°E inclusive. By default, dates * are constrained in the range January 1st, 1970 up to the date at the time the widget * was created. * * <p> </p> * <p align="center"><img src="doc-files/CoordinateChooser.png"></p> * <p> </p> * * @since 2.3 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public class CoordinateChooser extends JPanel { /** * An enumeration constant for showing or hidding the geographic area selector. * Used as argument for {@link #isSelectorVisible} and {@link #setSelectorVisible}. * * @see #TIME_RANGE * @see #RESOLUTION * @see #isSelectorVisible * @see #setSelectorVisible * @see #addChangeListener * @see #removeChangeListener */ public static final int GEOGRAPHIC_AREA = 1; /** * An enumeration constant for showing or hidding the time range selector. * Used as argument for {@link #isSelectorVisible} and {@link #setSelectorVisible}. * * @see #GEOGRAPHIC_AREA * @see #RESOLUTION * @see #isSelectorVisible * @see #setSelectorVisible * @see #addChangeListener * @see #removeChangeListener */ public static final int TIME_RANGE = 2; /** * An enumeration constant for showing or hidding the resolution selector. * Used as argument for {@link #isSelectorVisible} and {@link #setSelectorVisible}. * * @see #GEOGRAPHIC_AREA * @see #TIME_RANGE * @see #isSelectorVisible * @see #setSelectorVisible * @see #addChangeListener * @see #removeChangeListener */ public static final int RESOLUTION = 4; /** * The three mean panels in this dialog box: * geographic area, time and preferred resolution. */ private final JComponent areaPanel, timePanel, resoPanel; /** * Liste de choix dans laquelle l'utilisateur * choisira le fuseau horaire de ses dates. */ private final JComboBox timezone; /** * Dates de début et de fin de la plage de temps demandée par l'utilisateur. * Ces dates sont gérées par un modèle {@link SpinnerDateModel}. */ private final JSpinner tmin, tmax; /** * Longitudes et latitudes minimales et maximales demandées par l'utilisateur. * Ces coordonnées sont gérées par un modèle {@link SpinnerNumberModel}. */ private final JSpinner xmin, xmax, ymin, ymax; /** * Résolution (en minutes de longitudes et de latitudes) demandée par l'utilisateur. * Ces résolution sont gérées par un modèle {@link SpinnerNumberModel}. */ private final JSpinner xres, yres; /** * Bouton radio pour sélectioner la meilleure résolution possible. */ private final AbstractButton radioBestRes; /** * Bouton radio pour sélectioner la résolution spécifiée. */ private final AbstractButton radioPrefRes; /** * Composante facultative à afficher à la droite du paneau {@code CoordinateChooser}. */ private JComponent accessory; /** * Class encompassing various listeners for users selections. * * @version $Id$ * @author Martin Desruisseaux (IRD) */ private final class Listeners implements ActionListener, ChangeListener { /** * List of components to toggle. */ private final JComponent[] toggle; /** * Constructs a {@code Listeners} object. */ public Listeners(final JComponent[] toggle) { this.toggle=toggle; } /** * Invoked when user select a new timezone. */ public void actionPerformed(final ActionEvent event) { update(getTimeZone()); } /** * Invoked when user change the button radio state * ("use best resolution" / "set resolution"). */ public void stateChanged(final ChangeEvent event) { setEnabled(radioPrefRes.isSelected()); } /** * Enable or disable {@link #toggle} components. */ final void setEnabled(final boolean state) { for (int i=0; i<toggle.length; i++) { toggle[i].setEnabled(state); } } } /** * Constructs a default coordinate chooser. Date will be constrained in the range from * January 1st, 1970 00:00 UTC up to the {@linkplain System#currentTimeMillis current time}. */ public CoordinateChooser() { this(new Date(0), new Date()); } /** * Constructs a coordinate chooser with date constrained in the specified range. * Note that the {@code [minTime..maxTime]} range is not the same than the * range given to {@link #setTimeRange}. The later set only the time range shown * in the widget, while this constructor set also the minimum and maximum dates * allowed. * * @param minTime The minimal date allowed. * @param maxTime the maximal date allowed. */ public CoordinateChooser(final Date minTime, final Date maxTime) { super(new GridBagLayout()); final Locale locale = getDefaultLocale(); final int timeField = Calendar.DAY_OF_YEAR; final Vocabulary resources = Vocabulary.getResources(locale); radioBestRes = new JRadioButton(resources.getString(VocabularyKeys.USE_BEST_RESOLUTION), true); radioPrefRes = new JRadioButton(resources.getString(VocabularyKeys.SET_PREFERRED_RESOLUTION)); tmin = new JSpinner(new SpinnerDateModel(minTime, minTime, maxTime, timeField)); tmax = new JSpinner(new SpinnerDateModel(maxTime, minTime, maxTime, timeField)); xmin = new JSpinner(new SpinnerAngleModel(new Longitude(Longitude.MIN_VALUE))); xmax = new JSpinner(new SpinnerAngleModel(new Longitude(Longitude.MAX_VALUE))); ymin = new JSpinner(new SpinnerAngleModel(new Latitude( Latitude.MIN_VALUE))); ymax = new JSpinner(new SpinnerAngleModel(new Latitude( Latitude.MAX_VALUE))); xres = new JSpinner(new SpinnerNumberModel(1, 0, 360*60, 1)); yres = new JSpinner(new SpinnerNumberModel(1, 0, 180*60, 1)); final AngleFormat angleFormat = new AngleFormat("D°MM.m'", locale); final DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); final NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); xmin.setEditor(new SpinnerAngleModel.Editor(xmin, angleFormat)); xmax.setEditor(new SpinnerAngleModel.Editor(xmax, angleFormat)); ymin.setEditor(new SpinnerAngleModel.Editor(ymin, angleFormat)); ymax.setEditor(new SpinnerAngleModel.Editor(ymax, angleFormat)); setup(tmin, 10, dateFormat); setup(tmax, 10, dateFormat); setup(xmin, 7, null); setup(xmax, 7, null); setup(ymin, 7, null); setup(ymax, 7, null); setup(xres, 3, numberFormat); setup(yres, 3, numberFormat); final String[] timezones = TimeZone.getAvailableIDs(); Arrays.sort(timezones); timezone = new JComboBox(timezones); timezone.setSelectedItem(dateFormat.getTimeZone().getID()); final JLabel labelSize1 = new JLabel(resources.getLabel(VocabularyKeys.SIZE_IN_MINUTES)); final JLabel labelSize2 = new JLabel("\u00D7" /* Multiplication symbol */); final ButtonGroup group = new ButtonGroup(); group.add(radioBestRes); group.add(radioPrefRes); final Listeners listeners = new Listeners(new JComponent[] { labelSize1, labelSize2, xres, yres}); listeners .setEnabled(false); timezone .addActionListener(listeners); radioPrefRes.addChangeListener(listeners); areaPanel = getPanel(resources.getString(VocabularyKeys.GEOGRAPHIC_COORDINATES)); timePanel = getPanel(resources.getString(VocabularyKeys.TIME_RANGE )); resoPanel = getPanel(resources.getString(VocabularyKeys.PREFERRED_RESOLUTION )); final GridBagConstraints c = new GridBagConstraints(); c.weightx=1; c.gridx=1; c.gridy=0; areaPanel.add(ymax, c); c.gridx=0; c.gridy=1; areaPanel.add(xmin, c); c.gridx=2; c.gridy=1; areaPanel.add(xmax, c); c.gridx=1; c.gridy=2; areaPanel.add(ymin, c); JLabel label; c.gridx=0; c.anchor=c.WEST; c.insets.right=3; c.weightx=0; c.gridy=0; timePanel.add(label=new JLabel(resources.getLabel(VocabularyKeys.START_TIME)), c); label.setLabelFor(tmin); c.gridy=1; timePanel.add(label=new JLabel(resources.getLabel(VocabularyKeys.END_TIME )), c); label.setLabelFor(tmax); c.gridy=2; timePanel.add(label=new JLabel(resources.getLabel(VocabularyKeys.TIME_ZONE )), c); label.setLabelFor(timezone); c.gridwidth=4; c.gridy=0; resoPanel.add(radioBestRes, c); c.gridy=1; resoPanel.add(radioPrefRes, c); c.gridy=2; c.gridwidth=1; c.anchor=c.EAST; c.insets.right=c.insets.left=1; c.weightx=1; c.gridx=0; resoPanel.add(labelSize1, c); labelSize1.setLabelFor(xres); c.weightx=0; c.gridx=1; resoPanel.add(xres, c); c.gridx=2; resoPanel.add(labelSize2, c); labelSize2.setLabelFor(yres); c.gridx=3; resoPanel.add(yres, c); c.gridx=1; c.fill=c.HORIZONTAL; c.insets.right=c.insets.left=0; c.weightx=1; c.gridy=0; timePanel.add(tmin, c); c.gridy=1; timePanel.add(tmax, c); c.gridy=2; timePanel.add(timezone, c); c.insets.right=c.insets.left=c.insets.top=c.insets.bottom=3; c.gridx=0; c.anchor=c.CENTER; c.fill=c.BOTH; c.weighty=1; c.gridy=0; add(areaPanel, c); c.gridy=1; add(timePanel, c); c.gridy=2; add(resoPanel, c); } /** * Retourne un panneau avec une bordure titrée. */ private static JPanel getPanel(final String title) { final JPanel panel=new JPanel(new GridBagLayout()); panel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder(title), BorderFactory.createEmptyBorder(6,6,6,6))); return panel; } /** * Définit la largeur (en nombre de colonnes) d'un champ. * Eventuellement, cette méthode peut aussi redéfinir le format. */ private static void setup(final JSpinner spinner, final int width, final Format format) { final JFormattedTextField field=((JSpinner.DefaultEditor)spinner.getEditor()).getTextField(); field.setMargin(new Insets(/*top*/0, /*left*/6, /*bottom*/0, /*right*/3)); field.setColumns(width); if (format!=null) { ((InternationalFormatter)field.getFormatter()).setFormat(format); } } /** * Tells if a selector is currently visible or not. The default {@code CoordinateChooser} * contains three selectors: one for geographic area, one for time range and one for the * preferred resolution. * * @param selector One of the following constants: * {@link #GEOGRAPHIC_AREA}, * {@link #TIME_RANGE} or * {@link #RESOLUTION}. * @return {@code true} if the specified selector is visible, or {@code false} otherwise. * @throws IllegalArgumentException if {@code selector} is not legal. */ public boolean isSelectorVisible(final int selector) { switch (selector) { case GEOGRAPHIC_AREA: return areaPanel.isVisible(); case TIME_RANGE: return timePanel.isVisible(); case RESOLUTION: return resoPanel.isVisible(); default: throw new IllegalArgumentException(); // TODO: provide some error message. } } /** * Set the visible state of one or many selectors. * All selectors are visible by default. * * @param selectors Any bitwise combinaisons of * {@link #GEOGRAPHIC_AREA}, * {@link #TIME_RANGE} and/or * {@link #RESOLUTION}. * @param visible {@code true} to show the selectors, or {@code false} to hide them. * @throws IllegalArgumentException if {@code selectors} contains illegal bits. */ public void setSelectorVisible(final int selectors, final boolean visible) { ensureValidSelectors(selectors); if ((selectors & GEOGRAPHIC_AREA) != 0) areaPanel.setVisible(visible); if ((selectors & TIME_RANGE ) != 0) timePanel.setVisible(visible); if ((selectors & RESOLUTION ) != 0) resoPanel.setVisible(visible); } /** * Ensure that the specified bitwise combinaison of selectors is valid. * * @param selectors Any bitwise combinaisons of * {@link #GEOGRAPHIC_AREA}, * {@link #TIME_RANGE} and/or * {@link #RESOLUTION}. * @throws IllegalArgumentException if {@code selectors} contains illegal bits. * * @todo Provide a better error message. */ private static void ensureValidSelectors(final int selectors) throws IllegalArgumentException { if ((selectors & ~(GEOGRAPHIC_AREA | TIME_RANGE | RESOLUTION)) != 0) { throw new IllegalArgumentException(String.valueOf(selectors)); } } /** * Returns the value for the specified number, or NaN if {@code value} is not a number. */ private static double doubleValue(final JSpinner spinner) { final Object value = spinner.getValue(); return (value instanceof Number) ? ((Number)value).doubleValue() : Double.NaN; } /** * Returns the value for the specified angle, or NaN if {@code value} is not an angle. */ private static double degrees(final JSpinner spinner, final boolean expectLatitude) { final Object value = spinner.getValue(); if (value instanceof Angle) { if (expectLatitude ? (value instanceof Longitude) : (value instanceof Latitude)) { return Double.NaN; } return ((Angle) value).degrees(); } return Double.NaN; } /** * Gets the geographic area, in latitude and longitude degrees. */ public Rectangle2D getGeographicArea() { final double xmin = degrees(this.xmin, false); final double ymin = degrees(this.ymin, true); final double xmax = degrees(this.xmax, false); final double ymax = degrees(this.ymax, true); return new Rectangle2D.Double(Math.min(xmin,xmax), Math.min(ymin,ymax), Math.abs(xmax-xmin), Math.abs(ymax-ymin)); } /** * Sets the geographic area, in latitude and longitude degrees. */ public void setGeographicArea(final Rectangle2D area) { xmin.setValue(new Longitude(area.getMinX())); xmax.setValue(new Longitude(area.getMaxX())); ymin.setValue(new Latitude(area.getMinY())); ymax.setValue(new Latitude(area.getMaxY())); } /** * Returns the preferred resolution. A {@code null} value means that the * best available resolution should be used. */ public Dimension2D getPreferredResolution() { if (radioPrefRes.isSelected()) { return new XDimension2D.Double(doubleValue(xres), doubleValue(yres)); } return null; } /** * Sets the preferred resolution. A {@code null} value means that the best * available resolution should be used. */ public void setPreferredResolution(final Dimension2D resolution) { if (resolution!=null) { xres.setValue(new Double(resolution.getWidth ()*60)); yres.setValue(new Double(resolution.getHeight()*60)); radioPrefRes.setSelected(true); } else { radioBestRes.setSelected(true); } } /** * Returns the time zone used for displaying dates. */ public TimeZone getTimeZone() { return TimeZone.getTimeZone(timezone.getSelectedItem().toString()); } /** * Sets the time zone. This method change the control's display. * It doesn't change the date values, i.e. it have no effect * on previous or future call to {@link #setTimeRange}. */ public void setTimeZone(final TimeZone timezone) { this.timezone.setSelectedItem(timezone.getID()); } /** * Updates the time zone in text fields. This method is automatically invoked * by {@link JComboBox} on user's selection. It is also (indirectly) invoked * on {@link #setTimeZone} call. */ private void update(final TimeZone timezone) { boolean refresh=true; try { tmin.commitEdit(); tmax.commitEdit(); } catch (ParseException exception) { refresh = false; } ((JSpinner.DateEditor)tmin.getEditor()).getFormat().setTimeZone(timezone); ((JSpinner.DateEditor)tmax.getEditor()).getFormat().setTimeZone(timezone); if (refresh) { // TODO: If a "JSpinner.reformat()" method was available, we would use it here. fireStateChanged((AbstractSpinnerModel)tmin.getModel()); fireStateChanged((AbstractSpinnerModel)tmax.getModel()); } } /** * Run each {@link ChangeListener#stateChanged()} method for the specified spinner model. */ private static void fireStateChanged(final AbstractSpinnerModel model) { final ChangeEvent changeEvent = new ChangeEvent(model); final EventListener[] listeners = model.getListeners(ChangeListener.class); for (int i=listeners.length; --i>=0;) { ((ChangeListener)listeners[i]).stateChanged(changeEvent); } } /** * Returns the start time, or {@code null} if there is none. */ public Date getStartTime() { return (Date) tmin.getValue(); } /** * Returns the end time, or {@code null} if there is none. */ public Date getEndTime() { return (Date) tmax.getValue(); } /** * Sets the time range. * * @param startTime The start time. * @param endTime The end time. * * @see #getStartTime * @see #getEndTime */ public void setTimeRange(final Date startTime, final Date endTime) { tmin.setValue(startTime); tmax.setValue( endTime); } /** * Returns the accessory component. * * @return The accessory component, or {@code null} if there is none. */ public JComponent getAccessory() { return accessory; } /** * Sets the accessory component. An accessory is often used to show available data. * However, it can be used for anything that the programmer wishes, such as extra * custom coordinate chooser controls. * <p> * <strong>Note:</strong> If there was a previous accessory, you should unregister any * listeners that the accessory might have registered with the coordinate chooser. * * @param accessory The accessory component, or {@code null} to remove any previous accessory. */ public void setAccessory(final JComponent accessory) { synchronized (getTreeLock()) { if (this.accessory!=null) { remove(this.accessory); } this.accessory = accessory; if (accessory!=null) { final GridBagConstraints c=new GridBagConstraints(); c.insets.right=c.insets.left=c.insets.top=c.insets.bottom=3; c.gridx=1; c.weightx=1; c.gridwidth=1; c.gridy=0; c.weighty=1; c.gridheight=3; c.anchor=c.CENTER; c.fill=c.BOTH; add(accessory, c); } validate(); } } /** * Check if an angle is of expected type (latitude or longitude). */ private void checkAngle(final JSpinner field, final boolean expectLatitude) throws ParseException { final Object angle=field.getValue(); if (expectLatitude ? (angle instanceof Longitude) : (angle instanceof Latitude)) { throw new ParseException(Errors.getResources(getLocale()).getString( ErrorKeys.BAD_COORDINATE_$1, angle), 0); } } /** * Commits the currently edited values. If commit fails, focus will be set on the offending * field. * * @throws ParseException If at least one of currently edited value couldn't be commited. */ public void commitEdit() throws ParseException { JSpinner focus=null; try { (focus=tmin).commitEdit(); (focus=tmax).commitEdit(); (focus=xmin).commitEdit(); (focus=xmax).commitEdit(); (focus=ymin).commitEdit(); (focus=ymax).commitEdit(); (focus=xres).commitEdit(); (focus=yres).commitEdit(); checkAngle(focus=xmin, false); checkAngle(focus=xmax, false); checkAngle(focus=ymin, true); checkAngle(focus=ymax, true); } catch (ParseException exception) { focus.requestFocus(); throw exception; } } /** * Prend en compte les valeurs des champs édités par l'utilisateur. * Si les entrés ne sont pas valide, affiche un message d'erreur en * utilisant la fenêtre parente {@code owner} spécifiée. * * @param owner Fenêtre dans laquelle faire apparaître d'eventuels messages d'erreur. * @return {@code true} si la prise en compte des paramètres à réussie. */ private boolean commitEdit(final Component owner) { try { commitEdit(); } catch (ParseException exception) { SwingUtilities.showMessageDialog(owner, exception.getLocalizedMessage(), Errors.getResources(getLocale()).getString(ErrorKeys.BAD_ENTRY), JOptionPane.ERROR_MESSAGE); return false; } return true; } /** * Adds a change listener to the listener list. This change listener will be notify when * a value changed. The change may be in a geographic coordinate field, a date field, a * resolution field, etc. The watched values depend on the {@code selectors} arguments: * {@link #GEOGRAPHIC_AREA} will watches for the bounding box (East, West, North and South * value); {@link #TIME_RANGE} watches for start time and end time; {@link #RESOLUTION} * watches for the resolution along East-West and North-South axis. Bitwise combinaisons * are allowed. For example, <code>GEOGRAPHIC_AREA | TIME_RANGE</code> will register a * listener for both geographic area and time range. * <p> * The source of {@link ChangeEvent}s delivered to {@link ChangeListener}s will be in most * case the {@link SpinnerModel} for the edited field. * * @param selectors Any bitwise combinaisons of * {@link #GEOGRAPHIC_AREA}, * {@link #TIME_RANGE} and/or * {@link #RESOLUTION}. * @param listener The listener to add to the specified selectors. * @throws IllegalArgumentException if {@code selectors} contains illegal bits. */ public void addChangeListener(final int selectors, final ChangeListener listener) { ensureValidSelectors(selectors); if ((selectors & GEOGRAPHIC_AREA) != 0) { xmin.getModel().addChangeListener(listener); xmax.getModel().addChangeListener(listener); ymin.getModel().addChangeListener(listener); ymax.getModel().addChangeListener(listener); } if ((selectors & TIME_RANGE) != 0) { tmin.getModel().addChangeListener(listener); tmax.getModel().addChangeListener(listener); } if ((selectors & RESOLUTION) != 0) { xres.getModel().addChangeListener(listener); yres.getModel().addChangeListener(listener); radioPrefRes.getModel().addChangeListener(listener); } } /** * Removes a change listener from the listener list. * * @param selectors Any bitwise combinaisons of * {@link #GEOGRAPHIC_AREA}, * {@link #TIME_RANGE} and/or * {@link #RESOLUTION}. * @param listener The listener to remove from the specified selectors. * @throws IllegalArgumentException if {@code selectors} contains illegal bits. */ public void removeChangeListener(final int selectors, final ChangeListener listener) { ensureValidSelectors(selectors); if ((selectors & GEOGRAPHIC_AREA) != 0) { xmin.getModel().removeChangeListener(listener); xmax.getModel().removeChangeListener(listener); ymin.getModel().removeChangeListener(listener); ymax.getModel().removeChangeListener(listener); } if ((selectors & TIME_RANGE) != 0) { tmin.getModel().removeChangeListener(listener); tmax.getModel().removeChangeListener(listener); } if ((selectors & RESOLUTION) != 0) { xres.getModel().removeChangeListener(listener); yres.getModel().removeChangeListener(listener); radioPrefRes.getModel().removeChangeListener(listener); } } /** * Shows a dialog box requesting input from the user. The dialog box will be * parented to {@code owner}. If {@code owner} is contained into a * {@link javax.swing.JDesktopPane}, the dialog box will appears as an internal * frame. This method can be invoked from any thread (may or may not be the * <cite>Swing</cite> thread). * * @param owner The parent component for the dialog box, or {@code null} if there is no parent. * @return {@code true} if user pressed the "Ok" button, or {@code false} otherwise * (e.g. pressing "Cancel" or closing the dialog box from the title bar). */ public boolean showDialog(final Component owner) { return showDialog(owner, Vocabulary.getResources(getLocale()). getString(VocabularyKeys.COORDINATES_SELECTION)); } /** * Shows a dialog box requesting input from the user. If {@code owner} is contained into a * {@link javax.swing.JDesktopPane}, the dialog box will appears as an internal frame. This * method can be invoked from any thread (may or may not be the <cite>Swing</cite> thread). * * @param owner The parent component for the dialog box, or {@code null} if there is no parent. * @param title The dialog box title. * @return {@code true} if user pressed the "Ok" button, or {@code false} otherwise * (e.g. pressing "Cancel" or closing the dialog box from the title bar). */ public boolean showDialog(final Component owner, final String title) { while (SwingUtilities.showOptionDialog(owner, this, title)) { if (commitEdit(owner)) { return true; } } return false; } /** * Show the dialog box. This method is provided only as an easy * way to test the dialog appearance from the command line. */ public static void main(final String[] args) { new CoordinateChooser().showDialog(null); } }