package org.openjump.core.ui.plugin.tools; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.feature.FeatureCollectionWrapper; import com.vividsolutions.jump.workbench.WorkbenchContext; import com.vividsolutions.jump.workbench.model.MeasureLayerFinder; import com.vividsolutions.jump.workbench.ui.GUIUtil; import com.vividsolutions.jump.workbench.ui.OptionsDialog; import com.vividsolutions.jump.workbench.ui.cursortool.CoordinateListMetrics; import com.vividsolutions.jump.workbench.ui.cursortool.PolygonTool; import com.vividsolutions.jump.workbench.ui.cursortool.QuasimodeTool; import java.awt.Cursor; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import javax.swing.Icon; import com.vividsolutions.jump.workbench.ui.images.IconLoader; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionListener; import java.awt.geom.NoninvertibleTransformException; import java.util.List; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JTabbedPane; import javax.swing.JToggleButton; import javax.swing.Timer; import org.openide.awt.DropDownButtonFactory; /** * A tool for measurment of distance or area. * * * @author Matthias Scholz <ms@jammerhund.de> * @version 0.1 */ /* * TODO: * - Icons and Cursors */ public class AdvancedMeasureTool extends PolygonTool implements ActionListener { /** * Measure mode distance. */ public static final int MEASURE_MODE_DISTANCE = 1; /** * Measure mode area. */ public static final int MEASURE_MODE_AREA = 2; // the JPopupMenu of the DropDownToggleButton JPopupMenu popupMenu; // the menuitems JMenuItem distanceMenuItem; JMenuItem areaMenuItem; JMenuItem optionsMenuItem; // in which mode we are? private int measureMode = MEASURE_MODE_DISTANCE; // the JToggleButton in the WorkbenchToolBar private JToggleButton toolbarButton = null; WorkbenchContext context; Point mousePosition = null; Shape lastShape = null; // the Timer for checking a double click private static Timer doubleClickTimer; /** * Build a new AdvancedMeasureTool instance. * * @param context */ public AdvancedMeasureTool(WorkbenchContext context) { this.context = context; allowSnapping(); setMetricsDisplay(new CoordinateListMetrics()); setCloseRing(false); // distance mode // build the popup menu popupMenu = new JPopupMenu(); distanceMenuItem = new JMenuItem(I18N.get("org.openjump.core.ui.plugin.tools.AdvancedMeasureTool.distance-measuring"), IconLoader.icon("Ruler.gif")); distanceMenuItem.addActionListener(this); popupMenu.add(distanceMenuItem); areaMenuItem = new JMenuItem(I18N.get("org.openjump.core.ui.plugin.tools.AdvancedMeasureTool.area-measuring"), IconLoader.icon("Ruler_area.gif")); areaMenuItem.addActionListener(this); popupMenu.add(areaMenuItem); popupMenu.addSeparator(); optionsMenuItem = new JMenuItem(I18N.get("org.openjump.core.ui.plugin.tools.AdvancedMeasureTool.options")); optionsMenuItem.addActionListener(this); popupMenu.add(optionsMenuItem); // the Button for the ToolBar toolbarButton = DropDownButtonFactory.createDropDownToggleButton(getIcon(), getPopupMenu()); } /** * Returns the Icon depending on the measureMode. */ public Icon getIcon() { String iconName = "Ruler.gif"; switch (measureMode) { case MEASURE_MODE_DISTANCE: iconName = "Ruler.gif"; break; case MEASURE_MODE_AREA: iconName = "Ruler_area.gif"; break; } return IconLoader.icon(iconName); } /** * Returns the Cursor depending on the measureMode. * * @return the Cursor */ @Override public Cursor getCursor() { String cursorName = "RulerCursor.gif"; switch (measureMode) { case MEASURE_MODE_DISTANCE: cursorName = "RulerCursor.gif"; break; case MEASURE_MODE_AREA: cursorName = "RulerCursor_area.gif"; break; } return createCursor(IconLoader.icon(cursorName).getImage()); } /** * Handle mouse location changes. * * @param e */ @Override public void mouseLocationChanged(MouseEvent e) { mousePosition = e.getPoint(); try { if (isShapeOnScreen()) { ArrayList currentCoordinates = new ArrayList(getCoordinates()); currentCoordinates.add(getPanel().getViewport().toModelCoordinate(e.getPoint())); } super.mouseLocationChanged(e); } catch (Throwable t) { getPanel().getContext().handleThrowable(t); } } /** * Clickhandler for the measuretool. If the user starts a new measure with * a sinle click, then all old mesurements (features on the mesure layer) * will be deleted. But if the user starts with a double click, then this * measuremt will be added! * Because of the event behaviour during a double click, this code is a * little bit tricky. During a double click you get first a single click * (getClickCount()=1) and after them the double click (getClickCount()=2). * So we must check after the first click if later comes the double click. * This is done by a Timer. The maximum time to detect a double click we can * get through the desktop property "awt.multiClickInterval". * Second is the overridden method MultiClickTool.isFinishingRelease() * important. * * @param e */ @Override public void mouseClicked(MouseEvent e) { // only do some things if this is the first click in this gesture with the left mouse button if (getCoordinates().size() == 1 && e.getButton() == MouseEvent.BUTTON1) { final int clickCount = e.getClickCount(); if (clickCount == 1) { // single click starts a Timer for checking a double click int multiClickInterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval"); doubleClickTimer = new Timer(multiClickInterval, new ActionListener() { // if Timer is done, then we have a single first click and delete all old mesasurements public void actionPerformed(ActionEvent e) { MeasureLayerFinder measureLayerFinder = new MeasureLayerFinder(getPanel(), context); if (measureLayerFinder.getLayer() != null) { // no Layer, no old measurment final FeatureCollectionWrapper featureCollectionWrapper = measureLayerFinder.getMeasureLayer().getFeatureCollectionWrapper(); if (featureCollectionWrapper.getFeatures().size() > 0) { //only if we have an old measurment (Features on the Layer) featureCollectionWrapper.clear(); } } } }); doubleClickTimer.setRepeats(false); doubleClickTimer.start(); } else if (clickCount == 2) { // a double click stops the Timer, so no deletion is done! doubleClickTimer.stop(); } } } /** * For the possibility to start a gesture (measurement) with a double click, * only finish, if we have more then one click previously done * (coordinates > 1). See mouseClicked() method. * * @param e * @return true if double clicked and more the one click */ @Override protected boolean isFinishingRelease(MouseEvent e) { boolean finishingRelease = super.isFinishingRelease(e); return finishingRelease && getCoordinates().size() > 1; } /** * Check if the user has a double click at the first. * * @return */ private boolean doubleClicked() { return getCoordinates().size() == 1; } /** * Gesture is finished, now do the work, paint the measurement. * * @throws NoninvertibleTransformException */ protected void gestureFinished() throws NoninvertibleTransformException { reportNothingToUndoYet(); getMetrics().displayMetrics(getCoordinates(), getPanel(), measureMode == MEASURE_MODE_AREA); Geometry measureGeometry = null; // check if we have only one point if (doubleClicked()) { // with one point measurement makes no sense! measureGeometry = null; } else { // we have more than on points, so we can start to build the Geometry if (measureMode == MEASURE_MODE_DISTANCE) { List coordinates = getCoordinates(); measureGeometry = new GeometryFactory().createLineString(toArray(coordinates)); } else if (measureMode == MEASURE_MODE_AREA) { if (!checkPolygon()) { return; } //Don't want viewport to change at this stage. [Jon Aquino] getPanel().setViewportInitialized(true); measureGeometry = getPolygon(); } } // add the Geometry to the measure layer MeasureLayerFinder measureLayerFinder = new MeasureLayerFinder(getPanel(), context); measureLayerFinder.setMeasure(measureGeometry); // and set it visible if (!measureLayerFinder.getLayer().isVisible()) { measureLayerFinder.getLayer().setVisible(true); } } /** * Returns the popup menu for this tool. * * @return the popup menu */ public JPopupMenu getPopupMenu() { return popupMenu; } /** * Returns the toolbar button for this tool. * * @return the toolbarButton */ public JToggleButton getToolbarButton() { return toolbarButton; } /** * ActionListener for the JMenuItems. * We must set the Icon, the TooltipText, Cursor and the CloseRing Mode. * Second the tool will be activated through a JMenuItem ActionEvent. * So you do not need a second click ;-) * * @param e */ public void actionPerformed(ActionEvent e) { if (e.getSource() == distanceMenuItem) { // Distance measureMode = MEASURE_MODE_DISTANCE; if (toolbarButton != null) { toolbarButton.setIcon(IconLoader.icon("Ruler.gif")); toolbarButton.setToolTipText(I18N.get("org.openjump.core.ui.plugin.tools.AdvancedMeasureTool.distance-measuring")); // activate this tool toolbarButton.setSelected(true); context.getLayerViewPanel().setCurrentCursorTool(QuasimodeTool.addStandardQuasimodes(this)); } setCloseRing(false); } else if (e.getSource() == areaMenuItem) { // Area measureMode = MEASURE_MODE_AREA; if (toolbarButton != null) { toolbarButton.setIcon(IconLoader.icon("Ruler_area.gif")); toolbarButton.setToolTipText(I18N.get("org.openjump.core.ui.plugin.tools.AdvancedMeasureTool.area-measuring")); // activate this tool toolbarButton.setSelected(true); context.getLayerViewPanel().setCurrentCursorTool(QuasimodeTool.addStandardQuasimodes(this)); } setCloseRing(true); } else if (e.getSource() == optionsMenuItem) { // Options // display the OptionsDialog with the right Tab! OptionsDialog optionsDialog = OptionsDialog.instance(context.getWorkbench()); JTabbedPane tabbedPane = optionsDialog.getTabbedPane(); for (int i = 0; i < tabbedPane.getTabCount(); i++) { if (tabbedPane.getComponentAt(i) instanceof AdvancedMeasureOptionsPanel) { tabbedPane.setSelectedIndex(i); break; } } GUIUtil.centreOnWindow(optionsDialog); optionsDialog.setVisible(true); } context.getLayerViewPanel().setCursor(getCursor()); } }