/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui.cursortool; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.util.Blackboard; import com.vividsolutions.jump.util.StringUtil; import com.vividsolutions.jump.workbench.JUMPWorkbench; import com.vividsolutions.jump.workbench.model.UndoableCommand; import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn; import com.vividsolutions.jump.workbench.plugin.EnableCheck; import com.vividsolutions.jump.workbench.ui.*; import com.vividsolutions.jump.workbench.ui.EditTransaction; import com.vividsolutions.jump.workbench.ui.LayerViewPanel; import com.vividsolutions.jump.workbench.ui.LayerViewPanelListener; import com.vividsolutions.jump.workbench.ui.TaskFrame; import com.vividsolutions.jump.workbench.ui.WorkbenchFrame; import com.vividsolutions.jump.workbench.ui.snap.SnapManager; import com.vividsolutions.jump.workbench.ui.snap.SnapPolicy; import com.vividsolutions.jump.workbench.ui.snap.SnapToFeaturesPolicy; import com.vividsolutions.jump.workbench.ui.snap.SnapToGridPolicy; import com.vividsolutions.jump.workbench.ui.snap.SnapToVerticesPolicy; import com.vividsolutions.jump.workbench.ui.plugin.PersistentBlackboardPlugIn; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.Window; import java.awt.event.MouseEvent; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; /** * A tool that draws an XOR visual indicator. Subclasses need not keep track of * the XOR state of the indicator -- that logic is all handled by this class. * Even if the LayerViewPanel is repainted while the XOR indicator is on-screen. */ public abstract class AbstractCursorTool implements CursorTool { private static Logger LOG = Logger.getLogger(AbstractCursorTool.class); private boolean snappingConfigured = false; private boolean configuringSnapping = false; private boolean controlPressed; private boolean shiftPressed; private Color color = Color.red; private boolean filling = false; private Shape lastShapeDrawn; private LayerViewPanelListener layerViewPanelListener = new LayerViewPanelListener() { public void cursorPositionChanged(String x, String y) { } public void selectionChanged() { } public void fenceChanged() { } public void painted(Graphics graphics) { try { //If panel is repainted, force a redraw of the shape. Examples // of when the //panel is repainted: (1) the user Alt-Tabs away from the app //(2) the user fires an APPEARANCE_CHANGED LayerEvent. [Jon // Aquino] if (shapeOnScreen) { setShapeOnScreen(false); redrawShape((Graphics2D) graphics); } } catch (Throwable t) { panel.getContext().handleThrowable(t); } } }; private Color originalColor; private Stroke originalStroke; protected LayerViewPanel panel; private boolean shapeOnScreen = false; private SnapManager snapManager = new SnapManager(); private Stroke stroke = new BasicStroke(1); private ArrayList listeners = new ArrayList(); private Cursor cursor; public AbstractCursorTool() { } /** * Makes this CursorTool obey the snapping settings in the Options dialog. */ public void allowSnapping() { configuringSnapping = true; } protected boolean wasShiftPressed() { return shiftPressed; } protected boolean wasControlPressed() { return controlPressed; } /** * The cursor will look best if the image is a 32 x 32 transparent GIF. */ public static Cursor createCursor(Image image) { //<<TODO>> Compute image center rather than hardcoding 16, 16. [Jon // Aquino] return createCursor(image, new Point(16, 16)); } public static Cursor createCursor(Image image, Point hotSpot) { return GUIUtil.createCursor(image, hotSpot); } public Cursor getCursor() { if (cursor == null) { cursor = getIcon() instanceof ImageIcon ? GUIUtil.createCursorFromIcon(((ImageIcon) getIcon()) .getImage()) : Cursor.getDefaultCursor(); } return cursor; } /** * Used by OrCompositeTool to determine whether a CursorTool is busy * interacting with the user. */ public boolean isGestureInProgress() { //For most CursorTools, the presence of the shape on the screen // indicates //that the user is making a gesture. An exception, however, is //SnapIndicatorTool -- it provides its own implementation of this // method. //[Jon Aquino] return isShapeOnScreen(); } public boolean isRightMouseButtonUsed() { return false; } /** * Important for XOR drawing. Even if #getShape returns null, this method * will return true between calls of #redrawShape and #clearShape. */ public boolean isShapeOnScreen() { return shapeOnScreen; } public void activate(LayerViewPanel layerViewPanel) { if (workbenchFrame(layerViewPanel) != null) { workbenchFrame(layerViewPanel).log( I18N.get("ui.cursortool.AbstractCursorTool.activating")+" " + getName()); } if (this.panel != null) { this.panel.removeListener(layerViewPanelListener); } this.panel = layerViewPanel; this.panel.addListener(layerViewPanelListener); if (configuringSnapping && !snappingConfigured) { //Must wait until now because #getWorkbench needs the panel to be set. [Jon Aquino] //getSnapManager().addPolicies( //createStandardSnappingPolicies(getWorkbench().getBlackboard())); //fix bug 1713295 - change blackboard to PersistentBlackboard. //Snap options have been broken since PersistentBlackboard has //replaced blackboard in InstallGridPlugIn [Michael Michaud 2007-05-12] getSnapManager().addPolicies(createStandardSnappingPolicies( PersistentBlackboardPlugIn.get(getWorkbench().getContext()))); snappingConfigured = true; } } public static WorkbenchFrame workbenchFrame(LayerViewPanel layerViewPanel) { Window window = SwingUtilities.windowForComponent(layerViewPanel); //Will not be a WorkbenchFrame in apps that don't use the workbench //e.g. LayerViewPanelDemoFrame. [Jon Aquino] return (window instanceof WorkbenchFrame) ? (WorkbenchFrame) window : null; } public static List createStandardSnappingPolicies(Blackboard blackboard) { return Arrays.asList(new SnapPolicy[]{ new SnapToVerticesPolicy(blackboard), new SnapToFeaturesPolicy(blackboard), new SnapToGridPolicy(blackboard)}); } protected boolean isRollingBackInvalidEdits() { return getWorkbench().getBlackboard().get( EditTransaction.ROLLING_BACK_INVALID_EDITS_KEY, false); } public void deactivate() { cancelGesture(); } public void mouseClicked(MouseEvent e) { } public void mouseDragged(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } public void mousePressed(MouseEvent e) { controlPressed = e.isControlDown(); shiftPressed = e.isShiftDown(); } public void mouseReleased(MouseEvent e) { } public void setColor(Color color) { this.color = color; } protected void setFilling(boolean filling) { this.filling = filling; } /** * @deprecated Use #setStroke instead. */ protected void setStrokeWidth(int strokeWidth) { setStroke(new BasicStroke(strokeWidth)); } protected void setStroke(Stroke stroke) { this.stroke = stroke; } protected void setup(Graphics2D graphics) { originalColor = graphics.getColor(); originalStroke = graphics.getStroke(); graphics.setColor(color); graphics.setXORMode(Color.white); graphics.setStroke(stroke); } protected LayerViewPanel getPanel() { return panel; } /** * @return null if nothing should be drawn */ protected abstract Shape getShape() throws Exception; protected void cleanup(Graphics2D graphics) { graphics.setPaintMode(); graphics.setColor(originalColor); graphics.setStroke(originalStroke); } protected void clearShape() { clearShape(getGraphics2D()); } private Graphics2D getGraphics2D() { Graphics2D g = (Graphics2D) panel.getGraphics(); if (g != null) { //Not sure why g is null sometimes [Jon Aquino] g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } return g; } public void cancelGesture() { clearShape(); } protected void drawShapeXOR(Graphics2D g) throws Exception { Shape newShape = getShape(); drawShapeXOR(newShape, g); lastShapeDrawn = newShape; } protected void drawShapeXOR(Shape shape, Graphics2D graphics) { setup(graphics); try { //Pan tool returns a null shape. [Jon Aquino] if (shape != null) { //Can't both draw and fill, because we're using XOR. [Jon // Aquino] if (filling) { graphics.fill(shape); } else { graphics.draw(shape); } } } finally { cleanup(graphics); } } protected void redrawShape() throws Exception { redrawShape(getGraphics2D()); } protected Coordinate snap(Point2D viewPoint) throws NoninvertibleTransformException { return snap(getPanel().getViewport().toModelCoordinate(viewPoint)); } protected Coordinate snap(Coordinate modelCoordinate) { return snapManager.snap(getPanel(), modelCoordinate); } private void setShapeOnScreen(boolean shapeOnScreen) { this.shapeOnScreen = shapeOnScreen; } private void clearShape(Graphics2D graphics) { if (!shapeOnScreen) { return; } drawShapeXOR(lastShapeDrawn, graphics); setShapeOnScreen(false); } private void redrawShape(Graphics2D graphics) throws Exception { clearShape(graphics); drawShapeXOR(graphics); //<<TODO:INVESTIGATE>> Race conditions on the shapeOnScreen field? //Might we need synchronization? [Jon Aquino] setShapeOnScreen(true); } /** * @return null if the LayerViewPanel is not inside a TaskFrame */ protected TaskFrame getTaskFrame() { return (TaskFrame) SwingUtilities.getAncestorOfClass(TaskFrame.class, getPanel()); } public JUMPWorkbench getWorkbench() { return workbench(getPanel()); } public static JUMPWorkbench workbench(LayerViewPanel panel) { return ((WorkbenchFrame) SwingUtilities.getAncestorOfClass( WorkbenchFrame.class, panel)).getContext().getWorkbench(); } protected abstract void gestureFinished() throws Exception; protected void fireGestureFinished() throws Exception { getPanel().getContext().setStatusMessage(""); if (getTaskFrame() != null) { // Log if a WorkbenchFrame is available. [Sheldon Young 2004-06-03] WorkbenchFrame workbenchFrame = (WorkbenchFrame) SwingUtilities .getAncestorOfClass(WorkbenchFrame.class, getTaskFrame()); if (workbenchFrame != null) { workbenchFrame.log(I18N.get("ui.cursortool.AbstractCursorTool.gesture-finished")+": " + getName()); } } getPanel().getLayerManager().getUndoableEditReceiver().startReceiving(); try { gestureFinished(); } finally { getPanel().getLayerManager().getUndoableEditReceiver() .stopReceiving(); } for (Iterator i = listeners.iterator(); i.hasNext();) { Listener listener = (Listener) i.next(); listener.gestureFinished(); } } public void add(Listener listener) { listeners.add(listener); } /** * Optional means of execution, with undoability. */ protected void execute(UndoableCommand command) { AbstractPlugIn.execute(command, getPanel()); } /** * Notifies the UndoManager that this PlugIn did not modify any model * states, and therefore the undo history should remain unchanged. Call this * method inside #execute(PlugInContext). */ protected void reportNothingToUndoYet() { getPanel().getLayerManager().getUndoableEditReceiver() .reportNothingToUndoYet(); } public String toString() { return getName(); } public String getName() { return name(this); } public static String name(CursorTool tool) { try { return I18N.get(tool.getClass().getName()); } catch(java.util.MissingResourceException e){ // No I18N for the PlugIn so log it, but don't stop LOG.error(e.getMessage()+" "+tool.getClass().getName()); return StringUtil.toFriendlyName(tool.getClass().getName(), I18N.get("ui.cursortool.AbstractCursorTool.tool")); } } protected boolean check(EnableCheck check) { String warning = check.check(null); if (warning != null) { getPanel().getContext().warnUser(warning); return false; } return true; } public SnapManager getSnapManager() { return snapManager; } public Color getColor() { return color; } public static interface Listener { public void gestureFinished(); } }