/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.gui.swing.referencing;
import java.util.Date;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Calendar;
import java.util.EventListener;
import java.awt.Component;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JComboBox;
import javax.swing.ButtonGroup;
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;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.text.Format;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import org.apache.sis.measure.Angle;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.AngleFormat;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.internal.swing.SwingUtilities;
import org.geotoolkit.display.shape.DoubleDimension2D;
import org.geotoolkit.gui.swing.Dialog;
/**
* 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.
*
* <table cellspacing="24" cellpadding="12" align="center"><tr valign="top"><td>
* <img src="doc-files/CoordinateChooser.png">
* </td><td width="500" bgcolor="lightblue">
* {@section Demo}
* The image on the left side gives an example of this widget appearance.
* To try this component in your browser, see the
* <a href="http://www.geotoolkit.org/demos/geotk-simples/applet/CoordinateChooser.html">demonstration applet</a>.
* </td></tr></table>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.14
*
* @since 2.3
* @module
*/
@SuppressWarnings("serial")
public class CoordinateChooser extends JComponent implements Dialog {
/**
* The factory by which to multiply the resolution. Current value is 60, because
* the resolution is displayed in minutes of angle. A future Geotk version may
* replace that factor by something inferred from the CRS.
*/
private static final double RESOLUTION_FACTOR = 60;
/**
* An enumeration constant for showing or hiding 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 hiding 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 hiding 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<String> 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.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.3
* @module
*/
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.
*/
@Override
public void actionPerformed(final ActionEvent event) {
update(getTimeZone());
}
/**
* Invoked when user change the button radio state
* ("use best resolution" / "set resolution").
*/
@Override
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) {
setLayout(new GridBagLayout());
final Locale locale = getDefaultLocale();
final int timeField = Calendar.DAY_OF_YEAR;
final Vocabulary resources = Vocabulary.getResources(locale);
radioBestRes = new JRadioButton(resources.getString(Vocabulary.Keys.UseBestResolution), true);
radioPrefRes = new JRadioButton(resources.getString(Vocabulary.Keys.SetPreferredResolution));
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(1d, 0d, 360d*60, 1d));
yres = new JSpinner(new SpinnerNumberModel(1d, 0d, 180d*60, 1d));
final AngleFormat angleFormat = AngleFormat.getInstance(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(Vocabulary.Keys.SizeInMinutes));
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(Vocabulary.Keys.GeographicCoordinates));
timePanel = getPanel(resources.getString(Vocabulary.Keys.TimeRange ));
resoPanel = getPanel(resources.getString(Vocabulary.Keys.PreferredResolution ));
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=GridBagConstraints.WEST; c.insets.right=3; c.weightx=0;
c.gridy=0; timePanel.add(label=new JLabel(resources.getLabel(Vocabulary.Keys.StartTime)), c); label.setLabelFor(tmin);
c.gridy=1; timePanel.add(label=new JLabel(resources.getLabel(Vocabulary.Keys.EndTime )), c); label.setLabelFor(tmax);
c.gridy=2; timePanel.add(label=new JLabel(resources.getLabel(Vocabulary.Keys.TimeZone )), 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=GridBagConstraints.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=GridBagConstraints.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=GridBagConstraints.CENTER; c.fill=GridBagConstraints.BOTH; c.weighty=1;
c.gridy=0; add(areaPanel, c);
c.gridy=1; add(timePanel, c);
c.gridy=2; add(resoPanel, c);
}
/**
* Creates a panel with a titled border.
*/
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)));
panel.setOpaque(false);
return panel;
}
/**
* Sets the width of the given field, in amount of columns.
* As a side effect, this method set also the format.
*/
private static void setup(final JSpinner spinner, final int width, final Format format) {
final JFormattedTextField field = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
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.
}
}
/**
* Sets the visible state of one or many selectors.
* All selectors are visible by default.
*
* @param selectors Any bitwise combinations 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);
}
/**
* Ensures that the specified bitwise combination of selectors is valid.
*
* @param selectors Any bitwise combinations 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));
}
}
/**
* Same as {@link Math#min(double, double}, except that the {@code ceil}
* argument is returned if the {@code value} argument is {@code NaN}.
*/
private static double min(final double ceil, final double value) {
return (value < ceil) ? value : ceil;
}
/**
* Same as {@link Math#max(double, double}, except that the {@code floor}
* argument is returned if the {@code value} argument is {@code NaN}.
*/
private static double max(final double floor, final double value) {
return (value > floor) ? value : floor;
}
/**
* 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.
*
* @return The current geographic area of interest.
*/
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.
*
* @param area The new geographic area of interest.
*/
public void setGeographicArea(final Rectangle2D area) {
// We allow [-360…360]° range, since the [0…360]° range is sometime used.
xmin.setValue(new Longitude(max(2*Longitude.MIN_VALUE, area.getMinX())));
xmax.setValue(new Longitude(min(2*Longitude.MAX_VALUE, area.getMaxX())));
ymin.setValue(new Latitude(max( Latitude.MIN_VALUE, area.getMinY())));
ymax.setValue(new Latitude(min( Latitude.MAX_VALUE, area.getMaxY())));
}
/**
* Returns the preferred resolution. A {@code null} value means that the
* best available resolution should be used.
*
* @return The current preferred resolution, or {@code null} for the best available one.
*/
public Dimension2D getPreferredResolution() {
if (radioPrefRes.isSelected()) {
return new DoubleDimension2D(
doubleValue(xres) / RESOLUTION_FACTOR,
doubleValue(yres) / RESOLUTION_FACTOR);
}
return null;
}
/**
* Sets the preferred resolution. A {@code null} value means that the best
* available resolution should be used.
*
* @param resolution The new preferred resolution, or {@code null} for the best available one.
*/
public void setPreferredResolution(final Dimension2D resolution) {
if (resolution != null) {
xres.setValue(Double.valueOf(max(0, resolution.getWidth () * RESOLUTION_FACTOR)));
yres.setValue(Double.valueOf(max(0, resolution.getHeight() * RESOLUTION_FACTOR)));
radioPrefRes.setSelected(true);
} else {
radioBestRes.setSelected(true);
}
}
/**
* Returns the time zone used for displaying dates.
*
* @return The current timezone.
*/
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 has no effect on previous or future call to {@link #setTimeRange}.
*
* @param timezone The new timezone.
*/
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.
*
* @return The start time, or {@code null} if none.
*/
public Date getStartTime() {
return (Date) tmin.getValue();
}
/**
* Returns the end time, or {@code null} if there is none.
*
* @return The end time, or {@code null} if 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=GridBagConstraints.CENTER; c.fill=GridBagConstraints.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(
Errors.Keys.IllegalCoordinate_1, angle), 0);
}
}
/**
* {@inheritDoc}
*/
@Override
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;
}
}
/**
* 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 combinations
* 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 combinations 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 combinations 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.
* <p>
* 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(Vocabulary.Keys.CoordinatesSelection));
}
/**
* {@inheritDoc}
*/
@Override
public boolean showDialog(final Component owner, final String title) {
return SwingUtilities.showDialog(owner, this, title);
}
}