001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.workbench.ui.cursortool;
033:
034: import com.vividsolutions.jts.geom.Coordinate;
035:
036: import com.vividsolutions.jump.I18N;
037: import com.vividsolutions.jump.util.Blackboard;
038: import com.vividsolutions.jump.util.StringUtil;
039: import com.vividsolutions.jump.workbench.JUMPWorkbench;
040: import com.vividsolutions.jump.workbench.model.UndoableCommand;
041: import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
042: import com.vividsolutions.jump.workbench.plugin.EnableCheck;
043: import com.vividsolutions.jump.workbench.ui.*;
044: import com.vividsolutions.jump.workbench.ui.EditTransaction;
045: import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
046: import com.vividsolutions.jump.workbench.ui.LayerViewPanelListener;
047: import com.vividsolutions.jump.workbench.ui.TaskFrame;
048: import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
049: import com.vividsolutions.jump.workbench.ui.snap.SnapManager;
050: import com.vividsolutions.jump.workbench.ui.snap.SnapPolicy;
051: import com.vividsolutions.jump.workbench.ui.snap.SnapToFeaturesPolicy;
052: import com.vividsolutions.jump.workbench.ui.snap.SnapToGridPolicy;
053: import com.vividsolutions.jump.workbench.ui.snap.SnapToVerticesPolicy;
054: import com.vividsolutions.jump.workbench.ui.plugin.PersistentBlackboardPlugIn;
055:
056: import java.awt.BasicStroke;
057: import java.awt.Color;
058: import java.awt.Cursor;
059: import java.awt.Graphics;
060: import java.awt.Graphics2D;
061: import java.awt.Image;
062: import java.awt.Point;
063: import java.awt.RenderingHints;
064: import java.awt.Shape;
065: import java.awt.Stroke;
066: import java.awt.Window;
067: import java.awt.event.MouseEvent;
068: import java.awt.geom.NoninvertibleTransformException;
069: import java.awt.geom.Point2D;
070:
071: import java.util.ArrayList;
072: import java.util.Arrays;
073: import java.util.Iterator;
074: import java.util.List;
075:
076: import javax.swing.ImageIcon;
077: import javax.swing.SwingUtilities;
078:
079: import org.apache.log4j.Logger;
080:
081: /**
082: * A tool that draws an XOR visual indicator. Subclasses need not keep track of
083: * the XOR state of the indicator -- that logic is all handled by this class.
084: * Even if the LayerViewPanel is repainted while the XOR indicator is on-screen.
085: */
086: public abstract class AbstractCursorTool implements CursorTool {
087: private static Logger LOG = Logger
088: .getLogger(AbstractCursorTool.class);
089:
090: private boolean snappingConfigured = false;
091:
092: private boolean configuringSnapping = false;
093:
094: private boolean controlPressed;
095:
096: private boolean shiftPressed;
097:
098: private Color color = Color.red;
099:
100: private boolean filling = false;
101:
102: private Shape lastShapeDrawn;
103:
104: private LayerViewPanelListener layerViewPanelListener = new LayerViewPanelListener() {
105:
106: public void cursorPositionChanged(String x, String y) {
107: }
108:
109: public void selectionChanged() {
110: }
111:
112: public void fenceChanged() {
113: }
114:
115: public void painted(Graphics graphics) {
116: try {
117: //If panel is repainted, force a redraw of the shape. Examples
118: // of when the
119: //panel is repainted: (1) the user Alt-Tabs away from the app
120: //(2) the user fires an APPEARANCE_CHANGED LayerEvent. [Jon
121: // Aquino]
122: if (shapeOnScreen) {
123: setShapeOnScreen(false);
124: redrawShape((Graphics2D) graphics);
125: }
126: } catch (Throwable t) {
127: panel.getContext().handleThrowable(t);
128: }
129: }
130: };
131:
132: private Color originalColor;
133:
134: private Stroke originalStroke;
135:
136: private LayerViewPanel panel;
137:
138: private boolean shapeOnScreen = false;
139:
140: private SnapManager snapManager = new SnapManager();
141:
142: private Stroke stroke = new BasicStroke(1);
143:
144: private ArrayList listeners = new ArrayList();
145:
146: private Cursor cursor;
147:
148: public AbstractCursorTool() {
149: }
150:
151: /**
152: * Makes this CursorTool obey the snapping settings in the Options dialog.
153: */
154: public void allowSnapping() {
155: configuringSnapping = true;
156: }
157:
158: protected boolean wasShiftPressed() {
159: return shiftPressed;
160: }
161:
162: protected boolean wasControlPressed() {
163: return controlPressed;
164: }
165:
166: /**
167: * The cursor will look best if the image is a 32 x 32 transparent GIF.
168: */
169: public static Cursor createCursor(Image image) {
170: //<<TODO>> Compute image center rather than hardcoding 16, 16. [Jon
171: // Aquino]
172: return createCursor(image, new Point(16, 16));
173: }
174:
175: public static Cursor createCursor(Image image, Point hotSpot) {
176: return GUIUtil.createCursor(image, hotSpot);
177: }
178:
179: public Cursor getCursor() {
180: if (cursor == null) {
181: cursor = getIcon() instanceof ImageIcon ? GUIUtil
182: .createCursorFromIcon(((ImageIcon) getIcon())
183: .getImage()) : Cursor.getDefaultCursor();
184: }
185: return cursor;
186: }
187:
188: /**
189: * Used by OrCompositeTool to determine whether a CursorTool is busy
190: * interacting with the user.
191: */
192: public boolean isGestureInProgress() {
193: //For most CursorTools, the presence of the shape on the screen
194: // indicates
195: //that the user is making a gesture. An exception, however, is
196: //SnapIndicatorTool -- it provides its own implementation of this
197: // method.
198: //[Jon Aquino]
199: return isShapeOnScreen();
200: }
201:
202: public boolean isRightMouseButtonUsed() {
203: return false;
204: }
205:
206: /**
207: * Important for XOR drawing. Even if #getShape returns null, this method
208: * will return true between calls of #redrawShape and #clearShape.
209: */
210: public boolean isShapeOnScreen() {
211: return shapeOnScreen;
212: }
213:
214: public void activate(LayerViewPanel layerViewPanel) {
215: if (workbenchFrame(layerViewPanel) != null) {
216: workbenchFrame(layerViewPanel)
217: .log(
218: I18N
219: .get("ui.cursortool.AbstractCursorTool.activating")
220: + " " + getName());
221: }
222:
223: if (this .panel != null) {
224: this .panel.removeListener(layerViewPanelListener);
225: }
226:
227: this .panel = layerViewPanel;
228: this .panel.addListener(layerViewPanelListener);
229:
230: if (configuringSnapping && !snappingConfigured) {
231: //Must wait until now because #getWorkbench needs the panel to be set. [Jon Aquino]
232: //getSnapManager().addPolicies(
233: //createStandardSnappingPolicies(getWorkbench().getBlackboard()));
234:
235: //fix bug 1713295 - change blackboard to PersistentBlackboard.
236: //Snap options have been broken since PersistentBlackboard has
237: //replaced blackboard in InstallGridPlugIn [Michael Michaud 2007-05-12]
238: getSnapManager()
239: .addPolicies(
240: createStandardSnappingPolicies(PersistentBlackboardPlugIn
241: .get(getWorkbench().getContext())));
242: snappingConfigured = true;
243: }
244: }
245:
246: public static WorkbenchFrame workbenchFrame(
247: LayerViewPanel layerViewPanel) {
248: Window window = SwingUtilities
249: .windowForComponent(layerViewPanel);
250:
251: //Will not be a WorkbenchFrame in apps that don't use the workbench
252: //e.g. LayerViewPanelDemoFrame. [Jon Aquino]
253: return (window instanceof WorkbenchFrame) ? (WorkbenchFrame) window
254: : null;
255: }
256:
257: public static List createStandardSnappingPolicies(
258: Blackboard blackboard) {
259: return Arrays.asList(new SnapPolicy[] {
260: new SnapToVerticesPolicy(blackboard),
261: new SnapToFeaturesPolicy(blackboard),
262: new SnapToGridPolicy(blackboard) });
263: }
264:
265: protected boolean isRollingBackInvalidEdits() {
266: return getWorkbench().getBlackboard().get(
267: EditTransaction.ROLLING_BACK_INVALID_EDITS_KEY, false);
268: }
269:
270: public void deactivate() {
271: cancelGesture();
272: }
273:
274: public void mouseClicked(MouseEvent e) {
275: }
276:
277: public void mouseDragged(MouseEvent e) {
278: }
279:
280: public void mouseEntered(MouseEvent e) {
281: }
282:
283: public void mouseExited(MouseEvent e) {
284: }
285:
286: public void mouseMoved(MouseEvent e) {
287: }
288:
289: public void mousePressed(MouseEvent e) {
290: controlPressed = e.isControlDown();
291: shiftPressed = e.isShiftDown();
292: }
293:
294: public void mouseReleased(MouseEvent e) {
295: }
296:
297: public void setColor(Color color) {
298: this .color = color;
299: }
300:
301: protected void setFilling(boolean filling) {
302: this .filling = filling;
303: }
304:
305: /**
306: * @deprecated Use #setStroke instead.
307: */
308: protected void setStrokeWidth(int strokeWidth) {
309: setStroke(new BasicStroke(strokeWidth));
310: }
311:
312: protected void setStroke(Stroke stroke) {
313: this .stroke = stroke;
314: }
315:
316: protected void setup(Graphics2D graphics) {
317: originalColor = graphics.getColor();
318: originalStroke = graphics.getStroke();
319: graphics.setColor(color);
320: graphics.setXORMode(Color.white);
321: graphics.setStroke(stroke);
322: }
323:
324: protected LayerViewPanel getPanel() {
325: return panel;
326: }
327:
328: /**
329: * @return null if nothing should be drawn
330: */
331: protected abstract Shape getShape() throws Exception;
332:
333: protected void cleanup(Graphics2D graphics) {
334: graphics.setPaintMode();
335: graphics.setColor(originalColor);
336: graphics.setStroke(originalStroke);
337: }
338:
339: protected void clearShape() {
340: clearShape(getGraphics2D());
341: }
342:
343: private Graphics2D getGraphics2D() {
344: Graphics2D g = (Graphics2D) panel.getGraphics();
345:
346: if (g != null) {
347: //Not sure why g is null sometimes [Jon Aquino]
348: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
349: RenderingHints.VALUE_ANTIALIAS_ON);
350: }
351:
352: return g;
353: }
354:
355: public void cancelGesture() {
356: clearShape();
357: }
358:
359: protected void drawShapeXOR(Graphics2D g) throws Exception {
360: Shape newShape = getShape();
361: drawShapeXOR(newShape, g);
362: lastShapeDrawn = newShape;
363: }
364:
365: protected void drawShapeXOR(Shape shape, Graphics2D graphics) {
366: setup(graphics);
367:
368: try {
369: //Pan tool returns a null shape. [Jon Aquino]
370: if (shape != null) {
371: //Can't both draw and fill, because we're using XOR. [Jon
372: // Aquino]
373: if (filling) {
374: graphics.fill(shape);
375: } else {
376: graphics.draw(shape);
377: }
378: }
379: } finally {
380: cleanup(graphics);
381: }
382: }
383:
384: protected void redrawShape() throws Exception {
385: redrawShape(getGraphics2D());
386: }
387:
388: protected Coordinate snap(Point2D viewPoint)
389: throws NoninvertibleTransformException {
390: return snap(getPanel().getViewport().toModelCoordinate(
391: viewPoint));
392: }
393:
394: protected Coordinate snap(Coordinate modelCoordinate) {
395: return snapManager.snap(getPanel(), modelCoordinate);
396: }
397:
398: private void setShapeOnScreen(boolean shapeOnScreen) {
399: this .shapeOnScreen = shapeOnScreen;
400: }
401:
402: private void clearShape(Graphics2D graphics) {
403: if (!shapeOnScreen) {
404: return;
405: }
406:
407: drawShapeXOR(lastShapeDrawn, graphics);
408: setShapeOnScreen(false);
409: }
410:
411: private void redrawShape(Graphics2D graphics) throws Exception {
412: clearShape(graphics);
413: drawShapeXOR(graphics);
414:
415: //<<TODO:INVESTIGATE>> Race conditions on the shapeOnScreen field?
416: //Might we need synchronization? [Jon Aquino]
417: setShapeOnScreen(true);
418: }
419:
420: /**
421: * @return null if the LayerViewPanel is not inside a TaskFrame
422: */
423: protected TaskFrame getTaskFrame() {
424: return (TaskFrame) SwingUtilities.getAncestorOfClass(
425: TaskFrame.class, getPanel());
426: }
427:
428: public JUMPWorkbench getWorkbench() {
429: return workbench(getPanel());
430: }
431:
432: public static JUMPWorkbench workbench(LayerViewPanel panel) {
433: return ((WorkbenchFrame) SwingUtilities.getAncestorOfClass(
434: WorkbenchFrame.class, panel)).getContext()
435: .getWorkbench();
436: }
437:
438: protected abstract void gestureFinished() throws Exception;
439:
440: protected void fireGestureFinished() throws Exception {
441: getPanel().getContext().setStatusMessage("");
442:
443: if (getTaskFrame() != null) {
444: // Log if a WorkbenchFrame is available. [Sheldon Young 2004-06-03]
445: WorkbenchFrame workbenchFrame = (WorkbenchFrame) SwingUtilities
446: .getAncestorOfClass(WorkbenchFrame.class,
447: getTaskFrame());
448: if (workbenchFrame != null) {
449: workbenchFrame
450: .log(I18N
451: .get("ui.cursortool.AbstractCursorTool.gesture-finished")
452: + ": " + getName());
453: }
454: }
455:
456: getPanel().getLayerManager().getUndoableEditReceiver()
457: .startReceiving();
458:
459: try {
460: gestureFinished();
461: } finally {
462: getPanel().getLayerManager().getUndoableEditReceiver()
463: .stopReceiving();
464: }
465:
466: for (Iterator i = listeners.iterator(); i.hasNext();) {
467: Listener listener = (Listener) i.next();
468: listener.gestureFinished();
469: }
470: }
471:
472: public void add(Listener listener) {
473: listeners.add(listener);
474: }
475:
476: /**
477: * Optional means of execution, with undoability.
478: */
479: protected void execute(UndoableCommand command) {
480: AbstractPlugIn.execute(command, getPanel());
481: }
482:
483: /**
484: * Notifies the UndoManager that this PlugIn did not modify any model
485: * states, and therefore the undo history should remain unchanged. Call this
486: * method inside #execute(PlugInContext).
487: */
488: protected void reportNothingToUndoYet() {
489: getPanel().getLayerManager().getUndoableEditReceiver()
490: .reportNothingToUndoYet();
491: }
492:
493: public String toString() {
494: return getName();
495: }
496:
497: public String getName() {
498: return name(this );
499: }
500:
501: public static String name(CursorTool tool) {
502: try {
503: return I18N.get(tool.getClass().getName());
504: } catch (java.util.MissingResourceException e) {
505: // No I18N for the PlugIn so log it, but don't stop
506: LOG.error(e.getMessage() + " " + tool.getClass().getName());
507: return StringUtil.toFriendlyName(tool.getClass().getName(),
508: I18N.get("ui.cursortool.AbstractCursorTool.tool"));
509: }
510: }
511:
512: protected boolean check(EnableCheck check) {
513: String warning = check.check(null);
514:
515: if (warning != null) {
516: getPanel().getContext().warnUser(warning);
517:
518: return false;
519: }
520:
521: return true;
522: }
523:
524: public SnapManager getSnapManager() {
525: return snapManager;
526: }
527:
528: public Color getColor() {
529: return color;
530: }
531:
532: public static interface Listener {
533:
534: public void gestureFinished();
535: }
536: }
|