/******************************************************************************* * Copyright 2013 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.application.widgets; import gov.nasa.worldwind.geom.Position; import java.text.NumberFormat; import java.util.EventListener; import java.util.EventObject; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; /** * A widget that allows for the viewing / editing of a {@link Position} object. * <p/> * If style specifies {@code READ_ONLY}, the widget will present the * {@link Position} fields as read-only labels. Otherwise the editor will * present editable text fields. * * <dl> * <dt><b>Styles:</b></dt> * <dd>READ_ONLY</dd> * <dt><b>Events:</b></dt> * <dd>SWT.Modify ({@code data} is a 3 element array of {@code String} with * [lat, lon, elevation])</dd> * </dl> * * @author James Navin (james.navin@ga.gov.au) * */ public class PositionEditor extends Composite { private final Text latitude; private final Text longitude; private final Text elevation; private int numDecimalPlaces = 8; private final AtomicBoolean quiet = new AtomicBoolean(false); private final Vector<PositionEditorListener> listeners = new Vector<PositionEditorListener>(); /** * Create a new {@link PositionEditor} */ public PositionEditor(Composite parent, int style) { super(parent, style); GridLayout layout = new GridLayout(3, false); layout.horizontalSpacing = 2; setLayout(layout); int fieldStyle = style | SWT.BORDER; GridData fieldLayoutData = new GridData(GridData.FILL_HORIZONTAL); ModifyListener modifyListener = new ModifyListener() { @Override public void modifyText(ModifyEvent e) { notifyModify(); } }; addLabel(style, Messages.PositionEditor_LatitudeLabel); latitude = addText(fieldStyle, fieldLayoutData, modifyListener); addLabel(style, Messages.PositionEditor_LatitudeUnits); addLabel(style, Messages.PositionEditor_LongitudeLabel); longitude = addText(fieldStyle, fieldLayoutData, modifyListener); addLabel(style, Messages.PositionEditor_LongitudeUnits); addLabel(style, Messages.PositionEditor_ElevationLabel); elevation = addText(fieldStyle, fieldLayoutData, modifyListener); addLabel(style, Messages.PositionEditor_ElevationUnits); } private Label addLabel(int style, String text) { Label result = new Label(this, style); result.setText(text); return result; } private Text addText(int style, Object layoutData, ModifyListener listener) { Text result = new Text(this, style); result.setLayoutData(layoutData); result.addModifyListener(listener); return result; } /** * Returns the current {@link Position} value this editor reflects, or * <code>null</code> if it is invalid. */ public Position getPositionValue() { try { double lat = Double.parseDouble(latitude.getText().trim()); double lon = Double.parseDouble(longitude.getText().trim()); double el = Double.parseDouble(elevation.getText().trim()); return Position.fromDegrees(lat, lon, el); } catch (Exception e) { return null; } } /** * Set the current {@link Position} value on this editor. * <p/> * This will reset the fields on the editor to reflect those of the provided * {@link Position}. * <p/> * If <code>null</code> is provided, the fields of this editor will be * cleared. */ public void setPositionValue(Position p) { quiet.set(true); if (p != null) { NumberFormat numberFormat = getNumberFormat(); latitude.setText(numberFormat.format(p.latitude.degrees)); longitude.setText(numberFormat.format(p.longitude.degrees)); elevation.setText(numberFormat.format(p.elevation)); } else { latitude.setText(""); //$NON-NLS-1$ longitude.setText(""); //$NON-NLS-1$ elevation.setText(""); //$NON-NLS-1$ } quiet.set(false); notifyModify(); } /** * Set the number of decimal places to use when representing position values */ public void setNumDecimalPlaces(int numDecimalPlaces) { this.numDecimalPlaces = numDecimalPlaces; } private NumberFormat getNumberFormat() { NumberFormat numberFormat = NumberFormat.getNumberInstance(); numberFormat.setMaximumFractionDigits(numDecimalPlaces); numberFormat.setMinimumFractionDigits(numDecimalPlaces); numberFormat.setGroupingUsed(false); return numberFormat; } /** * Register a {@link PositionEditorListener} on this editor */ public void addPositionEditorListener(PositionEditorListener l) { if (l == null) { return; } listeners.add(l); } /** * Remove the {@link PositionEditorListener} from this editor */ public void removePositionEditorListener(PositionEditorListener l) { if (l == null) { return; } listeners.remove(l); } private void notifyModify() { if (quiet.get()) { return; } // Send a low-level event Event e = new Event(); e.item = this; e.widget = this; e.type = SWT.Modify; e.display = Display.getCurrent(); e.data = new String[] { latitude.getText(), longitude.getText(), elevation.getText() }; notifyListeners(SWT.Modify, e); // Then a high-level event if (listeners.isEmpty()) { return; } PositionChangedEvent pce = new PositionChangedEvent(this, getPositionValue(), latitude.getText(), longitude.getText(), elevation.getText()); for (PositionEditorListener l : listeners) { l.positionChanged(pce); } } /** * An event that is fired when a user's text change alters the value of a * {@link Position} within a {@link PositionEditor} */ public final static class PositionChangedEvent extends EventObject { private final boolean valid; private final Position p; private final String lat; private final String lon; private final String el; private PositionChangedEvent(Object source, Position p, String lat, String lon, String el) { super(source); this.p = p; this.valid = p != null; this.lat = lat; this.lon = lon; this.el = el; } public boolean isValid() { return valid; } public Position getPosition() { return p; } public String getLatitudeText() { return lat; } public String getLongitudeText() { return lon; } public String getElevationText() { return el; } } /** * An interface for classes that wish to be notified of changes to the * {@link Position} value backing a {@link PositionEditor}. */ public static interface PositionEditorListener extends EventListener { /** * Invoked when changes to the editor have resulted in a changed * position */ void positionChanged(PositionChangedEvent e); } }