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:
033: package com.vividsolutions.jump.workbench.model;
034:
035: import java.util.ArrayList;
036: import java.util.Iterator;
037:
038: import javax.swing.undo.UndoManager;
039: import javax.swing.undo.UndoableEdit;
040:
041: import com.vividsolutions.jts.util.Assert;
042:
043: /**
044: * Receives UndoableEdits from PlugIns and CursorTools. Also provides access to
045: * a Task's UndoManager.
046: * <P>
047: * In the documentation, the "receiving phase" refers to the time between the
048: * calls to #start and #stop.
049: * <P>
050: * If there is an exception that leaves this UndoableCommand execution
051: * partially complete and non-unexecutable, be sure to call
052: * #reportIrreversibleChange()
053: */
054: public class UndoableEditReceiver {
055: private UndoManager undoManager = new UndoManager();
056: private ArrayList newUndoableEdits = new ArrayList();
057: /** Handle nested calls to UndoableEditReceiver */
058: private int transactions = 0;
059: private boolean nothingToUndoReported = false;
060: private boolean irreversibleChangeReported = false;
061: private boolean undoManagerCouldUndoAtStart = false;
062: private ArrayList listeners = new ArrayList();
063:
064: public UndoableEditReceiver() {
065: }
066:
067: public void startReceiving() {
068: transactions++;
069: setNothingToUndoReported(false);
070: irreversibleChangeReported = false;
071: undoManagerCouldUndoAtStart = undoManager.canUndo();
072: }
073:
074: /**
075: * Specifies that the undo history should not be modified at the end of
076: * the current receiving phase, if neither #receive nor #reportIrreversibleChange
077: * is called. If none of the three methods are called during the receiving
078: * phase, an irreversible change is assumed to have occurred, and the
079: * undo history will be truncated.
080: */
081: public void reportNothingToUndoYet() {
082: Assert.isTrue(isReceiving());
083: setNothingToUndoReported(true);
084: }
085:
086: /**
087: * Notifies this UndoableEditReceiver that something non-undoable has
088: * happened. Be sure to call this if an Exception occurs during the execution
089: * of an UndoableCommand, leaving it partially complete and non-unexecutable.
090: */
091: public void reportIrreversibleChange() {
092: Assert.isTrue(isReceiving());
093: irreversibleChangeReported = true;
094: }
095:
096: public void stopReceiving() {
097: transactions--;
098: try {
099: if ((newUndoableEdits.isEmpty() && !wasNothingToUndoReported())
100: || irreversibleChangeReported) {
101: undoManager.discardAllEdits();
102:
103: return;
104: }
105:
106: for (Iterator i = newUndoableEdits.iterator(); i.hasNext();) {
107: UndoableEdit undoableEdit = (UndoableEdit) i.next();
108: undoManager.addEdit(undoableEdit);
109: }
110: newUndoableEdits.clear();
111: } finally {
112: fireUndoHistoryChanged();
113:
114: if (undoManagerCouldUndoAtStart && !undoManager.canUndo()) {
115: fireUndoHistoryTruncated();
116: }
117: }
118: }
119:
120: private void fireUndoHistoryTruncated() {
121: for (Iterator i = listeners.iterator(); i.hasNext();) {
122: Listener listener = (Listener) i.next();
123: listener.undoHistoryTruncated();
124: }
125: }
126:
127: private void fireUndoHistoryChanged() {
128: for (Iterator i = listeners.iterator(); i.hasNext();) {
129: Listener listener = (Listener) i.next();
130: listener.undoHistoryChanged();
131: }
132: }
133:
134: public void add(Listener listener) {
135: listeners.add(listener);
136: }
137:
138: /**
139: * If the currently executing PlugIn or AbstractCursorTool is not undoable,
140: * it should simply not call this method; the undo history will be cleared.
141: */
142: public void receive(UndoableEdit undoableEdit) {
143: Assert.isTrue(isReceiving());
144:
145: //Don't add the UndoableEdit to the UndoManager right away; the caller may
146: //call #clearNewUndoableEdits. [Jon Aquino]
147: newUndoableEdits.add(undoableEdit);
148: }
149:
150: public UndoManager getUndoManager() {
151: return undoManager;
152: }
153:
154: private void setNothingToUndoReported(boolean nothingToUndoReported) {
155: this .nothingToUndoReported = nothingToUndoReported;
156: }
157:
158: private boolean wasNothingToUndoReported() {
159: return nothingToUndoReported;
160: }
161:
162: public static interface Listener {
163: public void undoHistoryChanged();
164:
165: public void undoHistoryTruncated();
166: }
167:
168: public boolean isReceiving() {
169: return transactions > 0;
170: }
171:
172: }
|