001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package net.refractions.udig.tools.edit;
017:
018: import java.util.ArrayList;
019: import java.util.Collections;
020: import java.util.List;
021: import java.util.Set;
022: import java.util.concurrent.CopyOnWriteArrayList;
023: import java.util.concurrent.CopyOnWriteArraySet;
024: import java.util.concurrent.locks.Lock;
025: import java.util.concurrent.locks.ReentrantLock;
026:
027: import net.refractions.udig.project.EditManagerEvent;
028: import net.refractions.udig.project.IBlackboard;
029: import net.refractions.udig.project.IEditManagerListener;
030: import net.refractions.udig.project.ILayer;
031: import net.refractions.udig.project.command.UndoableMapCommand;
032: import net.refractions.udig.project.internal.Map;
033: import net.refractions.udig.project.ui.commands.IDrawCommand;
034: import net.refractions.udig.project.ui.render.displayAdapter.MapMouseEvent;
035: import net.refractions.udig.project.ui.tool.IToolContext;
036: import net.refractions.udig.tools.edit.support.EditBlackboard;
037: import net.refractions.udig.tools.edit.support.EditGeom;
038: import net.refractions.udig.tools.edit.support.EditUtils;
039: import net.refractions.udig.tools.edit.support.PrimitiveShape;
040:
041: import org.eclipse.core.runtime.NullProgressMonitor;
042: import org.eclipse.swt.graphics.Cursor;
043: import org.eclipse.swt.widgets.Display;
044: import org.eclipse.ui.PlatformUI;
045:
046: /**
047: * This is the class that does all the work. An Edit tool adds Mode objects to the EditToolHandler.
048: * Each Mode has attributes that indicate whether it is valid.
049: *
050: * @author jones
051: * @since 1.1.0
052: */
053: public class EditToolHandler {
054:
055: /**
056: * The key for the currently selected/edit state.
057: * It is put on the map referenced by the context (see {@link #getContext()})
058: */
059: public static final String EDITSTATE = "EDIT_TOOL_HANDLER_EDIT_STATE_KEY_33847562"; //$NON-NLS-1$
060: /**
061: * The key for the lock required if modifying the edit state or shape.
062: * It is put on the map referenced by the context (see {@link #getContext()})
063: */
064: private static final String LOCK = "EDIT_TOOL_HANDLER_LOCK_KEY_345280194"; //$NON-NLS-1$
065: /**
066: * The key for the currently selected/edit shape.
067: * It is put on the map referenced by the context (see {@link #getContext()})
068: */
069: public static final String CURRENT_SHAPE = "EDIT_TOOL_HANDLER_CURRENT_SHAPE_KEY_872839"; //$NON-NLS-1$
070: /** When there is a switch in the currently selected layer the current state is stored on the old layer so if the
071: * layer selected layer the state can be restored.
072: *
073: * <p>Modify with <em>Care</em> this is primarily used by the framework for its workflow but if the workflow
074: * is not pleasing then modification is permitted. </p>
075: */
076: public static final String STORED_CURRENT_STATE = "STORED_CURRENT_STATE"; //$NON-NLS-1$
077: /** When there is a switch in the currently selected layer the current shape is stored on the old layer so if the
078: * layer selected layer the state can be restored.
079: *
080: * <p>Modify with <em>Care</em> this is primarily used by the framework for its workflow but if the workflow
081: * is not pleasing then modification is permitted. </p>
082: */
083: public static final String STORED_CURRENT_SHAPE = "STORED_CURRENT_SHAPE"; //$NON-NLS-1$
084: /**
085: * Cursor that should be set when a selection can occur.
086: */
087: public final Cursor selectionCursor;
088: /**
089: * Cursor that should be set when editing can occur.
090: */
091: public final Cursor editCursor;
092:
093: private List<EventBehaviour> behaviour = new CopyOnWriteArrayList<EventBehaviour>();
094: private List<EnablementBehaviour> enablementBehaviours = new CopyOnWriteArrayList<EnablementBehaviour>();
095: private List<Behaviour> acceptBehaviours = new CopyOnWriteArrayList<Behaviour>();
096: private List<Behaviour> cancelBehaviours = new CopyOnWriteArrayList<Behaviour>();
097: private Set<Activator> activators = new CopyOnWriteArraySet<Activator>();
098: private List<IDrawCommand> drawCommands = Collections
099: .synchronizedList(new ArrayList<IDrawCommand>());
100: private IToolContext context;
101: private MouseTracker mouseTracker = new MouseTracker(this );
102: protected boolean testing = false;
103:
104: // see #lock
105: Object behaviourLock;
106:
107: private volatile boolean needRepaint;
108: private volatile boolean processingEvent;
109:
110: public EditToolHandler(Cursor selectionCursor, Cursor editCursor) {
111: this .selectionCursor = selectionCursor;
112: this .editCursor = editCursor;
113: }
114:
115: /**
116: * Called by AbstractEditTool when activated.
117: *
118: * @param active
119: */
120: protected void setActive(boolean active) {
121: if (active) {
122: oldState = EditState.NONE;
123:
124: // if current geom no longer on BB then delete
125: ILayer editLayer = getEditLayer();
126: if (!getEditBlackboard(editLayer).getGeoms().contains(
127: getCurrentGeom())) {
128: setCurrentShape(null);
129: }
130: basicEnablement();
131: enableListeners();
132:
133: } else {
134: basicDisablement();
135: disableListeners();
136:
137: List<Behaviour> list = acceptBehaviours;
138:
139: BehaviourCommand command = getCommand(list);
140: getContext().sendASyncCommand(command);
141: setCurrentState(EditState.NONE);
142: }
143: }
144:
145: /**
146: * disables the activators and stops listening
147: */
148: void basicDisablement() {
149: for (Activator runnable : activators) {
150: try {
151: runnable.deactivate(this );
152: } catch (Throwable error) {
153: runnable.handleDeactivateError(this , error);
154: }
155: }
156:
157: EditUtils.instance.clearLayerStateShapeCache(getContext()
158: .getMapLayers());
159:
160: for (IDrawCommand drawCommand : drawCommands) {
161: drawCommand.setValid(false);
162: }
163: drawCommands.clear();
164:
165: }
166:
167: private void disableListeners() {
168: EditBlackboardUtil.doneListening();
169:
170: EditBlackboardUtil.disableClearBlackboardCommand();
171: }
172:
173: /**
174: * enables the activators and starts listening
175: */
176: void basicEnablement() {
177: for (Activator runnable : activators) {
178: try {
179: runnable.activate(this );
180: } catch (Throwable error) {
181: runnable.handleActivateError(this , error);
182: }
183: }
184: }
185:
186: private void enableListeners() {
187: EditManagerListener.enableEditManagerListener(this );
188:
189: EditBlackboardUtil.enableClearBlackboardCommand(context);
190: }
191:
192: // This state is used to store the state before it is set to Illegal by
193: // the enablement behaviours. Since enablement behaviours can set the state to
194: // illegal based on unknown reasons the previous state has to be maintaned
195: // by the framework to remove that burden from the
196: // enablement behaviour implementors.
197: private EditState oldState;
198:
199: /**
200: * Runs a list of behaviours. Expected uses are
201: * handler.runBehaviours(handler.getAcceptBehaviours()); or
202: * handler.runBehaviours(handler.getCancelBehaviours());
203: *
204: * @param list
205: */
206: public BehaviourCommand getCommand(List<Behaviour> list) {
207: return new BehaviourCommand(list, this );
208: }
209:
210: /**
211: * Runs through the list of modes and runs all the modes that are valid in the current context.
212: *
213: * @param e mouse event that just occurred.
214: * @param eventType the type of event that just occurred
215: */
216: protected void handleEvent(MapMouseEvent e, EventType eventType) {
217:
218: synchronized (this ) {
219: needRepaint = false;
220: this .processingEvent = true;
221: }
222: try {
223: if (getCurrentState() == EditState.BUSY)
224: return;
225:
226: runEnablementBehaviours(e, eventType);
227:
228: if (getCurrentState() == EditState.ILLEGAL)
229: return;
230:
231: mouseTracker.updateState(e, eventType);
232:
233: runEventBehaviours(e, eventType);
234: } finally {
235: synchronized (this ) {
236: if (needRepaint) {
237: getContext().getViewportPane().repaint();
238: }
239: needRepaint = false;
240: this .processingEvent = false;
241: }
242: }
243: }
244:
245: private void runEnablementBehaviours(MapMouseEvent e,
246: EventType eventType) {
247: String errorMessage = null;
248: for (EnablementBehaviour b : enablementBehaviours) {
249: errorMessage = b.isEnabled(this , e, eventType);
250: if (errorMessage != null) {
251: break;
252: }
253: }
254:
255: if (errorMessage == null) {
256: if (getCurrentState() == EditState.ILLEGAL) {
257: setCurrentState(oldState);
258: getContext().getActionBars().getStatusLineManager()
259: .setErrorMessage(errorMessage);
260: }
261: } else {
262: getContext().getActionBars().getStatusLineManager()
263: .setErrorMessage(errorMessage);
264: if (getCurrentState() != EditState.ILLEGAL) {
265: oldState = getCurrentState();
266: setCurrentState(EditState.ILLEGAL);
267: }
268: }
269: }
270:
271: private void runEventBehaviours(MapMouseEvent e, EventType eventType) {
272: for (EventBehaviour b : behaviour) {
273:
274: if (canUnlock(b) && b.isValid(this , e, eventType)) {
275: UndoableMapCommand c = null;
276: c = b.getCommand(this , e, eventType);
277: if (c == null)
278: continue;
279:
280: if (testing) {
281: c.setMap((Map) getContext().getMap());
282: try {
283: NullProgressMonitor nullProgressMonitor = new NullProgressMonitor();
284: c.run(nullProgressMonitor);
285: } catch (Exception e1) {
286: throw (RuntimeException) new RuntimeException()
287: .initCause(e1);
288: }
289: } else {
290: getContext().sendASyncCommand(c);
291: }
292: }
293: }
294: }
295:
296: /**
297: * Returns true if the handler is unlocked or the behaviour has the correct key.
298: *
299: * @param behaviour trying to run
300: * @return Returns true if the handler is unlocked or the behaviour has the correct key.
301: */
302: private boolean canUnlock(EventBehaviour behaviour) {
303: if (!isLocked())
304: return true;
305: if (behaviour instanceof LockingBehaviour) {
306: LockingBehaviour locker = (LockingBehaviour) behaviour;
307:
308: return behaviourLock == locker.getKey(this );
309: }
310: return false;
311: }
312:
313: /**
314: * @return Returns the currentGeom.
315: */
316: public EditGeom getCurrentGeom() {
317: Lock lock2 = getLock();
318: (lock2).lock();
319: try {
320: PrimitiveShape currentShape = getCurrentShape();
321: return currentShape == null ? null : currentShape
322: .getEditGeom();
323: } finally {
324: lock2.unlock();
325: }
326: }
327:
328: /**
329: */
330: public void setCurrentShape(PrimitiveShape currentShape) {
331: Lock lock2 = getLock();
332: lock2.lock();
333: try {
334: getContext().getMap().getBlackboard().put(CURRENT_SHAPE,
335: currentShape);
336: } finally {
337: lock2.unlock();
338: }
339: }
340:
341: /**
342: * @return Returns the currentShape.
343: */
344: public PrimitiveShape getCurrentShape() {
345: Lock lock2 = getLock();
346: lock2.lock();
347: try {
348: return (PrimitiveShape) getContext().getMap()
349: .getBlackboard().get(CURRENT_SHAPE);
350: } finally {
351: lock2.unlock();
352: }
353: }
354:
355: /**
356: * @return Returns the currentState.
357: */
358: public EditState getCurrentState() {
359: Lock lock2 = getLock();
360: lock2.lock();
361: try {
362: EditState editState2 = (EditState) getContext().getMap()
363: .getBlackboard().get(EDITSTATE);
364: return editState2 == null ? EditState.NONE : editState2;
365: } finally {
366: lock2.unlock();
367: }
368: }
369:
370: synchronized Lock getLock() {
371: Lock lock2 = (Lock) getContext().getMap().getBlackboard().get(
372: LOCK);
373: if (lock2 == null) {
374: lock2 = new ReentrantLock();
375: getContext().getMap().getBlackboard().put(LOCK, lock2);
376: }
377: return lock2;
378: }
379:
380: /**
381: * @param currentState The currentState to set.
382: */
383: public void setCurrentState(EditState currentState) {
384: if (currentState == null)
385: throw new NullPointerException("Edit state is null"); //$NON-NLS-1$
386: if (currentState == getCurrentState())
387: return;
388: getContext().getMap().getBlackboard().put(EDITSTATE,
389: currentState);
390:
391: }
392:
393: /**
394: * Returns the EventBehaviours that may be run when an event occurs. This list is thread safe and may be
395: * modified.
396: *
397: * @return the EventBehaviours that may be run when an event occurs. This list is thread safe and may be
398: * modified.
399: */
400: public List<EventBehaviour> getBehaviours() {
401: return behaviour;
402: }
403:
404: /**
405: * Returns the behaviours that determine whether the tool is active at the current locations
406: *
407: * @return the behaviours that determine whether the tool is active at the current locations
408: */
409: public List<EnablementBehaviour> getEnablementBehaviours() {
410: return enablementBehaviours;
411: }
412:
413: /**
414: * Gets the EditBlackboard of the map.
415: *
416: * @return
417: */
418: public EditBlackboard getEditBlackboard(ILayer layer) {
419:
420: return EditBlackboardUtil
421: .getEditBlackboard(getContext(), layer);
422: }
423:
424: /**
425: * Returns the currently selected layer, or if the EditManager is locked,
426: * it will return the edit layer.
427: *
428: * @return
429: */
430: public ILayer getEditLayer() {
431: ILayer editLayer = getContext().getSelectedLayer();
432: if (getContext().getEditManager().getEditLayer() != null
433: && getContext().getEditManager().isEditLayerLocked()) {
434: editLayer = getContext().getEditManager().getEditLayer();
435: }
436: return editLayer;
437: }
438:
439: /**
440: * Returns the Activators that are run during activation and deactivation This list is thread
441: * safe and may be modified.
442: *
443: * @return Returns the activationActions.
444: */
445: public Set<Activator> getActivators() {
446: return activators;
447: }
448:
449: /**
450: * Returns the draw actions that need to be deactivated when the tool is deactivated.
451: * <p>
452: * This list is thread safe and may be modified.
453: * </p>
454: *
455: * @return Returns the drawCommands.
456: */
457: public List<IDrawCommand> getDrawCommands() {
458: return drawCommands;
459: }
460:
461: /**
462: * Gets the tool context object that Modes and Activators may use.
463: *
464: * @return
465: */
466: public IToolContext getContext() {
467: return context;
468: }
469:
470: /**
471: * @param context2 The context to set.
472: */
473: protected void setContext(IToolContext context2) {
474: this .context = context2;
475: }
476:
477: /**
478: * Sets the ViewportPane's cursor
479: *
480: * @param cursor_id the SWT.CURSOR_XXX id of the cursor to set.
481: * @deprecated
482: */
483: public void setCursor(final int cursor_id) {
484: if (Display.getCurrent() != null) {
485:
486: if (tool != null) {
487: tool.setCursorID(cursor_id + ""); //$NON-NLS-1$
488: }
489: // setCursor(Display.getCurrent().getSystemCursor(cursor_id));
490: } else {
491: final Display display = PlatformUI.getWorkbench()
492: .getDisplay();
493: display.asyncExec(new Runnable() {
494:
495: public void run() {
496: if (tool != null) {
497: tool.setCursorID(cursor_id + ""); //$NON-NLS-1$
498: }
499: // setCursor(display.getSystemCursor(cursor_id));
500: }
501: });
502: }
503: }
504:
505: /**
506: * The method gets ID of the cursor as configured by extension or
507: * by <code>ModalTool.*_CURSOR</code> value corresponding to <i>SWT.CURSOR_*</i> constants
508: * and delegates the call to <code>ModalTool</code> to find the cursor
509: * in cache and set it.
510: *
511: * @param cursorID
512: */
513: public void setCursor(final String cursorID) {
514:
515: if (Display.getCurrent() != null) {
516: if (tool != null) {
517: tool.setCursorID(cursorID);
518: }
519: } else {
520: final Display display = PlatformUI.getWorkbench()
521: .getDisplay();
522: display.asyncExec(new Runnable() {
523: public void run() {
524: if (tool != null) {
525: tool.setCursorID(cursorID);
526: }
527: }
528: });
529: }
530: }
531:
532: /**
533: * Sets the ViewportPane's cursor
534: *
535: * @param cursor new cursor
536: * @deprecated
537: */
538: public void setCursor(final Cursor cursor) {
539:
540: if (Display.getCurrent() != null) {
541: getContext().getViewportPane().setCursor(cursor);
542: } else {
543: final Display display = PlatformUI.getWorkbench()
544: .getDisplay();
545: display.asyncExec(new Runnable() {
546:
547: public void run() {
548: getContext().getViewportPane().setCursor(cursor);
549: }
550: });
551: }
552: }
553:
554: /**
555: * @return Returns the mouseTracker.
556: */
557: public MouseTracker getMouseTracker() {
558: return mouseTracker;
559: }
560:
561: /**
562: * Returns the list of behaviours that are run when the Enter key is pressed. EventBehaviours
563: * are welcome to run these behaviours as well if they wish to accept the current edit. The list
564: * is thread safe and can be modified.
565: *
566: * @return Returns the acceptBehaviours.
567: */
568: public List<Behaviour> getAcceptBehaviours() {
569: return acceptBehaviours;
570: }
571:
572: /**
573: * Returns the list of behaviours that are run when the Esc key is pressed. The list is thread
574: * safe and can be modified.
575: *
576: * @see #getCommand(List)
577: * @return Returns the cancelBehaviours.
578: */
579: public List<Behaviour> getCancelBehaviours() {
580: return cancelBehaviours;
581: }
582:
583: /**
584: * Locks the handler so only the only behaviours that can run are {@link LockingBehaviour}s
585: * who's {@link LockingBehaviour#getKey(EditToolHandler)} method returns the same object as the
586: * locking {@link LockingBehaviour}'s {@link LockingBehaviour#getKey(EditToolHandler)} method.
587: * <p>
588: * This is not a reentrant lock so it cannot be locked multiple times. Also the lock cannot be
589: * null
590: * </p>
591: *
592: * @param behaviour the behaviour that is locking the handler
593: */
594: public void lock(LockingBehaviour behaviour) {
595: if (behaviourLock != null) {
596: throw new IllegalArgumentException(
597: "Handler is locked and cannot be relocked"); //$NON-NLS-1$
598: }
599: this .behaviourLock = behaviour.getKey(this );
600: if (behaviourLock == null)
601: throw new IllegalArgumentException(
602: "Null is not a legal key"); //$NON-NLS-1$
603: }
604:
605: /**
606: * Returns true if Handler has been locked by {@link #lock(LockingBehaviour)}
607: *
608: * @return Returns true if Handler has been locked by {@link #lock(LockingBehaviour)}
609: */
610: public boolean isLocked() {
611: return behaviourLock != null;
612: }
613:
614: /**
615: * Unlocks the handler so all behaviours can run. The behaviour's
616: * {@link LockingBehaviour#getKey(EditToolHandler)} method must return the same object as the
617: * locking behaviours {@link LockingBehaviour#getKey(EditToolHandler)} method.
618: *
619: * @param behaviour
620: */
621: public void unlock(LockingBehaviour behaviour) {
622: if (behaviour.getKey(this ) != behaviourLock) {
623: throw new IllegalArgumentException(
624: "Locking behaviour does not have the correct key"); //$NON-NLS-1$
625: }
626: this .behaviourLock = null;
627: }
628:
629: /**
630: * Returns true if the behaviour's {@link LockingBehaviour#getKey(EditToolHandler)} returns the
631: * key for the lock.
632: *
633: * @param behaviour the behaviour to test
634: * @return Returns true if the behaviour's {@link LockingBehaviour#getKey(EditToolHandler)}
635: * returns the key for the lock.
636: */
637: public boolean isLockOwner(LockingBehaviour behaviour) {
638: if (behaviour.getKey(this ) == null)
639: throw new IllegalArgumentException(
640: "Null is not a legal key"); //$NON-NLS-1$
641:
642: return behaviour.getKey(this ) == behaviourLock;
643: }
644:
645: @Override
646: public String toString() {
647: return getCurrentState()
648: + ", " + getCurrentShape().getEditGeom() + ", " + mouseTracker; //$NON-NLS-1$//$NON-NLS-2$
649: }
650:
651: /**
652: * All behaviours and listeners should call this method so that only one redraw is done per
653: * mouse event.
654: */
655: public synchronized void repaint() {
656: if (!processingEvent) {
657: getContext().getViewportPane().repaint();
658: } else {
659: needRepaint = true;
660: }
661: }
662:
663: protected AbstractEditTool tool;
664:
665: public void setTool(AbstractEditTool tool) {
666: this .tool = tool;
667: }
668:
669: public AbstractEditTool getTool() {
670: return tool;
671: }
672:
673: }
|