KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > DefaultUndoManager


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jface.text;
13
14
15 import java.util.ArrayList JavaDoc;
16 import java.util.List JavaDoc;
17
18 import org.eclipse.swt.SWT;
19 import org.eclipse.swt.custom.StyledText;
20 import org.eclipse.swt.events.KeyEvent;
21 import org.eclipse.swt.events.KeyListener;
22 import org.eclipse.swt.events.MouseEvent;
23 import org.eclipse.swt.events.MouseListener;
24 import org.eclipse.swt.widgets.Display;
25 import org.eclipse.swt.widgets.Shell;
26
27 import org.eclipse.core.commands.ExecutionException;
28 import org.eclipse.core.commands.operations.AbstractOperation;
29 import org.eclipse.core.commands.operations.IOperationHistory;
30 import org.eclipse.core.commands.operations.IOperationHistoryListener;
31 import org.eclipse.core.commands.operations.IUndoContext;
32 import org.eclipse.core.commands.operations.IUndoableOperation;
33 import org.eclipse.core.commands.operations.ObjectUndoContext;
34 import org.eclipse.core.commands.operations.OperationHistoryEvent;
35 import org.eclipse.core.commands.operations.OperationHistoryFactory;
36
37 import org.eclipse.core.runtime.IAdaptable;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.Status;
41
42 import org.eclipse.jface.dialogs.MessageDialog;
43
44
45 /**
46  * Standard implementation of {@link org.eclipse.jface.text.IUndoManager}.
47  * <p>
48  * It registers with the connected text viewer as text input listener and
49  * document listener and logs all changes. It also monitors mouse and keyboard
50  * activities in order to partition the stream of text changes into undo-able
51  * edit commands.
52  * </p>
53  * <p>
54  * Since 3.1 this undo manager is a facade to the global operation history.
55  * </p>
56  * <p>
57  * The usage of {@link org.eclipse.core.runtime.IAdaptable} in the JFace
58  * layer has been approved by Platform UI, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=87669#c9
59  * </p>
60  * <p>
61  * This class is not intended to be subclassed.
62  * </p>
63  *
64  * @see org.eclipse.jface.text.ITextViewer
65  * @see org.eclipse.jface.text.ITextInputListener
66  * @see org.eclipse.jface.text.IDocumentListener
67  * @see org.eclipse.core.commands.operations.IUndoableOperation
68  * @see org.eclipse.core.commands.operations.IOperationHistory
69  * @see MouseListener
70  * @see KeyListener
71  * @deprecated As of 3.2, replaced by {@link TextViewerUndoManager}
72  */

73 public class DefaultUndoManager implements IUndoManager, IUndoManagerExtension {
74
75     /**
76      * Represents an undo-able edit command.
77      * <p>
78      * Since 3.1 this implements the interface for IUndoableOperation.
79      * </p>
80      */

81     class TextCommand extends AbstractOperation {
82
83         /** The start index of the replaced text. */
84         protected int fStart= -1;
85         /** The end index of the replaced text. */
86         protected int fEnd= -1;
87         /** The newly inserted text. */
88         protected String JavaDoc fText;
89         /** The replaced text. */
90         protected String JavaDoc fPreservedText;
91
92         /** The undo modification stamp. */
93         protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
94         /** The redo modification stamp. */
95         protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
96
97         /**
98          * Creates a new text command.
99          *
100          * @param context the undo context for this command
101          * @since 3.1
102          */

103         TextCommand(IUndoContext context) {
104             super(JFaceTextMessages.getString("DefaultUndoManager.operationLabel")); //$NON-NLS-1$
105
addContext(context);
106         }
107
108         /**
109          * Re-initializes this text command.
110          */

111         protected void reinitialize() {
112             fStart= fEnd= -1;
113             fText= fPreservedText= null;
114             fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
115             fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
116         }
117
118         /**
119          * Sets the start and the end index of this command.
120          *
121          * @param start the start index
122          * @param end the end index
123          */

124         protected void set(int start, int end) {
125             fStart= start;
126             fEnd= end;
127             fText= null;
128             fPreservedText= null;
129         }
130
131         /*
132          * @see org.eclipse.core.commands.operations.IUndoableOperation#dispose()
133          * @since 3.1
134          */

135         public void dispose() {
136             reinitialize();
137         }
138
139         /**
140          * Undo the change described by this command.
141          *
142          * @since 2.0
143          */

144         protected void undoTextChange() {
145             try {
146                 IDocument document= fTextViewer.getDocument();
147                 if (document instanceof IDocumentExtension4)
148                     ((IDocumentExtension4)document).replace(fStart, fText.length(), fPreservedText, fUndoModificationStamp);
149                 else
150                     document.replace(fStart, fText.length(), fPreservedText);
151             } catch (BadLocationException x) {
152             }
153         }
154
155         /*
156          * @see org.eclipse.core.commands.operations.IUndoableOperation#canUndo()
157          * @since 3.1
158          */

159         public boolean canUndo() {
160             
161             if (isConnected() && isValid()) {
162                 IDocument doc= fTextViewer.getDocument();
163                 if (doc instanceof IDocumentExtension4) {
164                     long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
165                     
166                     // Normal case: an undo is valid if its redo will restore document
167
// to its current modification stamp
168
boolean canUndo= docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP ||
169                         docStamp == getRedoModificationStamp();
170                     
171                     /* Special case to check if the answer is false.
172                      * If the last document change was empty, then the document's
173                      * modification stamp was incremented but nothing was committed.
174                      * The operation being queried has an older stamp. In this case only,
175                      * the comparison is different. A sequence of document changes that
176                      * include an empty change is handled correctly when a valid commit
177                      * follows the empty change, but when #canUndo() is queried just after
178                      * an empty change, we must special case the check. The check is very
179                      * specific to prevent false positives.
180                      * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245
181                      */

182                     if (!canUndo &&
183                             this == fHistory.getUndoOperation(fUndoContext) && // this is the latest operation
184
this != fCurrent && // there is a more current operation not on the stack
185
!fCurrent.isValid() && // the current operation is not a valid document modification
186
fCurrent.fUndoModificationStamp != // the invalid current operation has a document stamp
187
IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
188                         canUndo= fCurrent.fRedoModificationStamp == docStamp;
189                     }
190                     /*
191                      * When the composite is the current command, it may hold the timestamp
192                      * of a no-op change. We check this here rather than in an override of
193                      * canUndo() in CompoundTextCommand simply to keep all the special case checks
194                      * in one place.
195                      */

196                     if (!canUndo &&
197                             this == fHistory.getUndoOperation(fUndoContext) && // this is the latest operation
198
this instanceof CompoundTextCommand &&
199                             this == fCurrent && // this is the current operation
200
this.fStart == -1 && // the current operation text is not valid
201
fCurrent.fRedoModificationStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { // but it has a redo stamp
202
canUndo= fCurrent.fRedoModificationStamp == docStamp;
203                     }
204
205                 }
206                 // if there is no timestamp to check, simply return true per the 3.0.1 behavior
207
return true;
208             }
209             return false;
210         }
211
212         /*
213          * @see org.eclipse.core.commands.operations.IUndoableOperation#canRedo()
214          * @since 3.1
215          */

216         public boolean canRedo() {
217             if (isConnected() && isValid()) {
218                 IDocument doc= fTextViewer.getDocument();
219                 if (doc instanceof IDocumentExtension4) {
220                     long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
221                     return docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP ||
222                         docStamp == getUndoModificationStamp();
223                 }
224                 // if there is no timestamp to check, simply return true per the 3.0.1 behavior
225
return true;
226             }
227             return false;
228         }
229
230         /*
231          * @see org.eclipse.core.commands.operations.IUndoableOperation#canExecute()
232          * @since 3.1
233          */

234         public boolean canExecute() {
235             return isConnected();
236         }
237
238         /*
239          * @see org.eclipse.core.commands.operations.IUndoableOperation#execute(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable)
240          * @since 3.1
241          */

242         public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) {
243             // Text commands execute as they are typed, so executing one has no effect.
244
return Status.OK_STATUS;
245         }
246
247         /*
248          * Undo the change described by this command. Also selects and
249          * reveals the change.
250          */

251
252         /**
253          * Undo the change described by this command. Also selects and
254          * reveals the change.
255          *
256          * @param monitor the progress monitor to use if necessary
257          * @param uiInfo an adaptable that can provide UI info if needed
258          * @return the status
259          */

260         public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
261             if (isValid()) {
262                 undoTextChange();
263                 selectAndReveal(fStart, fPreservedText == null ? 0 : fPreservedText.length());
264                 resetProcessChangeSate();
265                 return Status.OK_STATUS;
266             }
267             return IOperationHistory.OPERATION_INVALID_STATUS;
268         }
269         
270         /**
271          * Re-applies the change described by this command.
272          *
273          * @since 2.0
274          */

275         protected void redoTextChange() {
276             try {
277                 IDocument document= fTextViewer.getDocument();
278                 if (document instanceof IDocumentExtension4)
279                     ((IDocumentExtension4)document).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp);
280                 else
281                     fTextViewer.getDocument().replace(fStart, fEnd - fStart, fText);
282             } catch (BadLocationException x) {
283             }
284         }
285
286         /**
287          * Re-applies the change described by this command that previously been
288          * rolled back. Also selects and reveals the change.
289          *
290          * @param monitor the progress monitor to use if necessary
291          * @param uiInfo an adaptable that can provide UI info if needed
292          * @return the status
293          */

294         public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
295             if (isValid()) {
296                 redoTextChange();
297                 resetProcessChangeSate();
298                 selectAndReveal(fStart, fText == null ? 0 : fText.length());
299                 return Status.OK_STATUS;
300             }
301             return IOperationHistory.OPERATION_INVALID_STATUS;
302         }
303
304         /**
305          * Update the command in response to a commit.
306          *
307          * @since 3.1
308          */

309
310         protected void updateCommand() {
311             fText= fTextBuffer.toString();
312             fTextBuffer.setLength(0);
313             fPreservedText= fPreservedTextBuffer.toString();
314             fPreservedTextBuffer.setLength(0);
315         }
316
317         /**
318          * Creates a new uncommitted text command depending on whether
319          * a compound change is currently being executed.
320          *
321          * @return a new, uncommitted text command or a compound text command
322          */

323         protected TextCommand createCurrent() {
324             return fFoldingIntoCompoundChange ? new CompoundTextCommand(fUndoContext) : new TextCommand(fUndoContext);
325         }
326
327         /**
328          * Commits the current change into this command.
329          */

330         protected void commit() {
331             if (fStart < 0) {
332                 if (fFoldingIntoCompoundChange) {
333                     fCurrent= createCurrent();
334                 } else {
335                     reinitialize();
336                 }
337             } else {
338                 updateCommand();
339                 fCurrent= createCurrent();
340             }
341             resetProcessChangeSate();
342         }
343
344         /**
345          * Updates the text from the buffers without resetting
346          * the buffers or adding anything to the stack.
347          *
348          * @since 3.1
349          */

350         protected void pretendCommit() {
351             if (fStart > -1) {
352                 fText= fTextBuffer.toString();
353                 fPreservedText= fPreservedTextBuffer.toString();
354             }
355         }
356         
357         /**
358          * Attempt a commit of this command and answer true if a new
359          * fCurrent was created as a result of the commit.
360          *
361          * @return true if the command was committed and created a
362          * new fCurrent, false if not.
363          * @since 3.1
364          */

365         protected boolean attemptCommit() {
366             pretendCommit();
367             if (isValid()) {
368                 DefaultUndoManager.this.commit();
369                 return true;
370             }
371             return false;
372         }
373
374         /**
375          * Checks whether this text command is valid for undo or redo.
376          *
377          * @return <code>true</code> if the command is valid for undo or redo
378          * @since 3.1
379          */

380         protected boolean isValid() {
381             return fStart > -1 &&
382                 fEnd > -1 &&
383                 fText != null;
384         }
385
386         /*
387          * @see java.lang.Object#toString()
388          * @since 3.1
389          */

390         public String JavaDoc toString() {
391             String JavaDoc delimiter= ", "; //$NON-NLS-1$
392
StringBuffer JavaDoc text= new StringBuffer JavaDoc(super.toString());
393             text.append("\n"); //$NON-NLS-1$
394
text.append(this.getClass().getName());
395             text.append(" undo modification stamp: "); //$NON-NLS-1$
396
text.append(fUndoModificationStamp);
397             text.append(" redo modification stamp: "); //$NON-NLS-1$
398
text.append(fRedoModificationStamp);
399             text.append(" start: "); //$NON-NLS-1$
400
text.append(fStart);
401             text.append(delimiter);
402             text.append("end: "); //$NON-NLS-1$
403
text.append(fEnd);
404             text.append(delimiter);
405             text.append("text: '"); //$NON-NLS-1$
406
text.append(fText);
407             text.append('\'');
408             text.append(delimiter);
409             text.append("preservedText: '"); //$NON-NLS-1$
410
text.append(fPreservedText);
411             text.append('\'');
412             return text.toString();
413         }
414         
415         /**
416          * Return the undo modification stamp
417          *
418          * @return the undo modification stamp for this command
419          * @since 3.1
420          */

421         protected long getUndoModificationStamp() {
422             return fUndoModificationStamp;
423         }
424         
425         /**
426          * Return the redo modification stamp
427          *
428          * @return the redo modification stamp for this command
429          * @since 3.1
430          */

431         protected long getRedoModificationStamp() {
432             return fRedoModificationStamp;
433         }
434     }
435
436     /**
437      * Represents an undo-able edit command consisting of several
438      * individual edit commands.
439      */

440     class CompoundTextCommand extends TextCommand {
441
442         /** The list of individual commands */
443         private List JavaDoc fCommands= new ArrayList JavaDoc();
444
445         /**
446          * Creates a new compound text command.
447          *
448          * @param context the undo context for this command
449          * @since 3.1
450          */

451         CompoundTextCommand(IUndoContext context) {
452             super(context);
453         }
454
455         /**
456          * Adds a new individual command to this compound command.
457          *
458          * @param command the command to be added
459          */

460         protected void add(TextCommand command) {
461             fCommands.add(command);
462         }
463
464         /*
465          * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#undo()
466          */

467         public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
468             resetProcessChangeSate();
469             
470             int size= fCommands.size();
471             if (size > 0) {
472
473                 TextCommand c;
474
475                 for (int i= size -1; i > 0; --i) {
476                     c= (TextCommand) fCommands.get(i);
477                     c.undoTextChange();
478                 }
479
480                 c= (TextCommand) fCommands.get(0);
481                 c.undo(monitor, uiInfo);
482             }
483
484             return Status.OK_STATUS;
485         }
486
487         /*
488          * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#redo()
489          */

490         public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
491             resetProcessChangeSate();
492
493             int size= fCommands.size();
494             if (size > 0) {
495
496                 TextCommand c;
497
498                 for (int i= 0; i < size -1; ++i) {
499                     c= (TextCommand) fCommands.get(i);
500                     c.redoTextChange();
501                 }
502
503                 c= (TextCommand) fCommands.get(size -1);
504                 c.redo(monitor, uiInfo);
505             }
506             return Status.OK_STATUS;
507         }
508
509         /*
510          * @see TextCommand#updateCommand
511
512          */

513
514         protected void updateCommand() {
515             // first gather the data from the buffers
516
super.updateCommand();
517
518             // the result of the command update is stored as a child command
519
TextCommand c= new TextCommand(fUndoContext);
520             c.fStart= fStart;
521             c.fEnd= fEnd;
522             c.fText= fText;
523             c.fPreservedText= fPreservedText;
524             c.fUndoModificationStamp= fUndoModificationStamp;
525             c.fRedoModificationStamp= fRedoModificationStamp;
526             add(c);
527
528             // clear out all indexes now that the child is added
529
reinitialize();
530         }
531
532         /*
533          * @see TextCommand#createCurrent
534          */

535         protected TextCommand createCurrent() {
536
537             if (!fFoldingIntoCompoundChange)
538                 return new TextCommand(fUndoContext);
539
540             reinitialize();
541             return this;
542         }
543
544         /*
545          * @see org.eclipse.jface.text.DefaultUndoManager.TextCommand#commit()
546          */

547         protected void commit() {
548             // if there is pending data, update the command
549
if (fStart > -1)
550                 updateCommand();
551             fCurrent= createCurrent();
552             resetProcessChangeSate();
553         }
554
555         /**
556          * Checks whether the command is valid for undo or redo.
557          *
558          * @return true if the command is valid.
559          * @since 3.1
560          */

561         protected boolean isValid() {
562             if (isConnected())
563                 return (fStart > -1 || fCommands.size() > 0);
564             return false;
565         }
566         
567         /**
568          * Returns the undo modification stamp.
569          *
570          * @return the undo modification stamp
571          * @since 3.1
572          */

573         protected long getUndoModificationStamp() {
574             if (fStart > -1)
575                 return super.getUndoModificationStamp();
576             else if (fCommands.size() > 0)
577                 return ((TextCommand)fCommands.get(0)).getUndoModificationStamp();
578
579             return fUndoModificationStamp;
580         }
581         
582         /**
583          * Returns the redo modification stamp.
584          *
585          * @return the redo modification stamp
586          * @since 3.1
587          */

588         protected long getRedoModificationStamp() {
589             if (fStart > -1)
590                 return super.getRedoModificationStamp();
591             else if (fCommands.size() > 0)
592                 return ((TextCommand)fCommands.get(fCommands.size()-1)).getRedoModificationStamp();
593
594             return fRedoModificationStamp;
595         }
596     }
597
598     /**
599      * Internal listener to mouse and key events.
600      */

601     class KeyAndMouseListener implements MouseListener, KeyListener {
602
603         /*
604          * @see MouseListener#mouseDoubleClick
605          */

606         public void mouseDoubleClick(MouseEvent e) {
607         }
608
609         /*
610          * If the right mouse button is pressed, the current editing command is closed
611          * @see MouseListener#mouseDown
612          */

613         public void mouseDown(MouseEvent e) {
614             if (e.button == 1)
615                 commit();
616         }
617
618         /*
619          * @see MouseListener#mouseUp
620          */

621         public void mouseUp(MouseEvent e) {
622         }
623
624         /*
625          * @see KeyListener#keyPressed
626          */

627         public void keyReleased(KeyEvent e) {
628         }
629
630         /*
631          * On cursor keys, the current editing command is closed
632          * @see KeyListener#keyPressed
633          */

634         public void keyPressed(KeyEvent e) {
635             switch (e.keyCode) {
636                 case SWT.ARROW_UP:
637                 case SWT.ARROW_DOWN:
638                 case SWT.ARROW_LEFT:
639                 case SWT.ARROW_RIGHT:
640                     commit();
641                     break;
642             }
643         }
644     }
645
646     /**
647      * Internal listener to document changes.
648      */

649     class DocumentListener implements IDocumentListener {
650
651         private String JavaDoc fReplacedText;
652
653         /*
654          * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
655          */

656         public void documentAboutToBeChanged(DocumentEvent event) {
657             try {
658                 fReplacedText= event.getDocument().get(event.getOffset(), event.getLength());
659                 fPreservedUndoModificationStamp= event.getModificationStamp();
660             } catch (BadLocationException x) {
661                 fReplacedText= null;
662             }
663         }
664
665         /*
666          * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
667          */

668         public void documentChanged(DocumentEvent event) {
669             fPreservedRedoModificationStamp= event.getModificationStamp();
670
671             // record the current valid state for the top operation in case it remains the
672
// top operation but changes state.
673
IUndoableOperation op= fHistory.getUndoOperation(fUndoContext);
674             boolean wasValid= false;
675             if (op != null)
676                 wasValid= op.canUndo();
677             // Process the change, providing the before and after timestamps
678
processChange(event.getOffset(), event.getOffset() + event.getLength(), event.getText(), fReplacedText, fPreservedUndoModificationStamp, fPreservedRedoModificationStamp);
679
680             // now update fCurrent with the latest buffers from the document change.
681
fCurrent.pretendCommit();
682
683             if (op == fCurrent) {
684                 // if the document change did not cause a new fCurrent to be created, then we should
685
// notify the history that the current operation changed if its validity has changed.
686
if (wasValid != fCurrent.isValid())
687                     fHistory.operationChanged(op);
688             }
689             else {
690                 // if the change created a new fCurrent that we did not yet add to the
691
// stack, do so if it's valid and we are not in the middle of a compound change.
692
if (fCurrent != fLastAddedCommand && fCurrent.isValid()) {
693                     addToCommandStack(fCurrent);
694                 }
695             }
696         }
697     }
698
699     /**
700      * Internal text input listener.
701      */

702     class TextInputListener implements ITextInputListener {
703
704         /*
705          * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
706          */

707         public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
708             if (oldInput != null && fDocumentListener != null) {
709                 oldInput.removeDocumentListener(fDocumentListener);
710                 commit();
711             }
712         }
713
714         /*
715          * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
716          */

717         public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
718             if (newInput != null) {
719                 if (fDocumentListener == null)
720                     fDocumentListener= new DocumentListener();
721                 newInput.addDocumentListener(fDocumentListener);
722             }
723         }
724
725     }
726
727     /*
728      * @see IOperationHistoryListener
729      * @since 3.1
730      */

731     class HistoryListener implements IOperationHistoryListener {
732         private IUndoableOperation fOperation;
733
734         public void historyNotification(final OperationHistoryEvent event) {
735             final int type= event.getEventType();
736             switch (type) {
737             case OperationHistoryEvent.ABOUT_TO_UNDO:
738             case OperationHistoryEvent.ABOUT_TO_REDO:
739                 // if this is one of our operations
740
if (event.getOperation().hasContext(fUndoContext)) {
741                     fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable JavaDoc() {
742                         public void run() {
743                             // if we are undoing/redoing a command we generated, then ignore
744
// the document changes associated with this undo or redo.
745
if (event.getOperation() instanceof TextCommand) {
746                                 if (fTextViewer instanceof TextViewer)
747                                     ((TextViewer)fTextViewer).ignoreAutoEditStrategies(true);
748                                 listenToTextChanges(false);
749     
750                                 // in the undo case only, make sure compounds are closed
751
if (type == OperationHistoryEvent.ABOUT_TO_UNDO) {
752                                     if (fFoldingIntoCompoundChange) {
753                                         endCompoundChange();
754                                     }
755                                 }
756                             } else {
757                                 // the undo or redo has our context, but it is not one of
758
// our commands. We will listen to the changes, but will
759
// reset the state that tracks the undo/redo history.
760
commit();
761                                 fLastAddedCommand= null;
762                             }
763                         }
764                     });
765                     fOperation= event.getOperation();
766                 }
767                 break;
768             case OperationHistoryEvent.UNDONE:
769             case OperationHistoryEvent.REDONE:
770             case OperationHistoryEvent.OPERATION_NOT_OK:
771                 if (event.getOperation() == fOperation) {
772                     fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable JavaDoc() {
773                         public void run() {
774                             listenToTextChanges(true);
775                             fOperation= null;
776                             if (fTextViewer instanceof TextViewer)
777                                 ((TextViewer)fTextViewer).ignoreAutoEditStrategies(false);
778                          }
779                     });
780                 }
781                 break;
782             }
783         }
784
785     }
786
787     /** Text buffer to collect text which is inserted into the viewer */
788     private StringBuffer JavaDoc fTextBuffer= new StringBuffer JavaDoc();
789     /** Text buffer to collect viewer content which has been replaced */
790     private StringBuffer JavaDoc fPreservedTextBuffer= new StringBuffer JavaDoc();
791     /** The document modification stamp for undo. */
792     protected long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
793     /** The document modification stamp for redo. */
794     protected long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
795     /** The internal key and mouse event listener */
796     private KeyAndMouseListener fKeyAndMouseListener;
797     /** The internal document listener */
798     private DocumentListener fDocumentListener;
799     /** The internal text input listener */
800     private TextInputListener fTextInputListener;
801
802
803     /** Indicates inserting state */
804     private boolean fInserting= false;
805     /** Indicates overwriting state */
806     private boolean fOverwriting= false;
807     /** Indicates whether the current change belongs to a compound change */
808     private boolean fFoldingIntoCompoundChange= false;
809
810     /** The text viewer the undo manager is connected to */
811     private ITextViewer fTextViewer;
812
813     /** Supported undo level */
814     private int fUndoLevel;
815     /** The currently constructed edit command */
816     private TextCommand fCurrent;
817     /** The last delete edit command */
818     private TextCommand fPreviousDelete;
819
820     /**
821      * The undo context.
822      * @since 3.1
823      */

824     private IOperationHistory fHistory;
825     /**
826      * The operation history.
827      * @since 3.1
828      */

829     private IUndoContext fUndoContext;
830     /**
831      * The operation history listener used for managing undo and redo before
832      * and after the individual commands are performed.
833      * @since 3.1
834      */

835     private IOperationHistoryListener fHistoryListener= new HistoryListener();
836
837     /**
838      * The command last added to the operation history. This must be tracked
839      * internally instead of asking the history, since outside parties may be placing
840      * items on our undo/redo history.
841      */

842     private TextCommand fLastAddedCommand= null;
843
844     /**
845      * Creates a new undo manager who remembers the specified number of edit commands.
846      *
847      * @param undoLevel the length of this manager's history
848      */

849     public DefaultUndoManager(int undoLevel) {
850         fHistory= OperationHistoryFactory.getOperationHistory();
851         setMaximalUndoLevel(undoLevel);
852     }
853
854     /**
855      * Returns whether this undo manager is connected to a text viewer.
856      *
857      * @return <code>true</code> if connected, <code>false</code> otherwise
858      * @since 3.1
859      */

860     private boolean isConnected() {
861         return fTextViewer != null;
862     }
863
864     /*
865      * @see IUndoManager#beginCompoundChange
866      */

867     public void beginCompoundChange() {
868         if (isConnected()) {
869             fFoldingIntoCompoundChange= true;
870             commit();
871         }
872     }
873
874
875     /*
876      * @see IUndoManager#endCompoundChange
877      */

878     public void endCompoundChange() {
879         if (isConnected()) {
880             fFoldingIntoCompoundChange= false;
881             commit();
882         }
883     }
884
885     /**
886      * Registers all necessary listeners with the text viewer.
887      */

888     private void addListeners() {
889         StyledText text= fTextViewer.getTextWidget();
890         if (text != null) {
891             fKeyAndMouseListener= new KeyAndMouseListener();
892             text.addMouseListener(fKeyAndMouseListener);
893             text.addKeyListener(fKeyAndMouseListener);
894             fTextInputListener= new TextInputListener();
895             fTextViewer.addTextInputListener(fTextInputListener);
896             fHistory.addOperationHistoryListener(fHistoryListener);
897             listenToTextChanges(true);
898         }
899     }
900
901     /**
902      * Unregister all previously installed listeners from the text viewer.
903      */

904     private void removeListeners() {
905         StyledText text= fTextViewer.getTextWidget();
906         if (text != null) {
907             if (fKeyAndMouseListener != null) {
908                 text.removeMouseListener(fKeyAndMouseListener);
909                 text.removeKeyListener(fKeyAndMouseListener);
910                 fKeyAndMouseListener= null;
911             }
912             if (fTextInputListener != null) {
913                 fTextViewer.removeTextInputListener(fTextInputListener);
914                 fTextInputListener= null;
915             }
916             listenToTextChanges(false);
917             fHistory.removeOperationHistoryListener(fHistoryListener);
918         }
919     }
920
921     /**
922      * Adds the given command to the operation history if it is not part of
923      * a compound change.
924      *
925      * @param command the command to be added
926      * @since 3.1
927      */

928     private void addToCommandStack(TextCommand command){
929         if (!fFoldingIntoCompoundChange || command instanceof CompoundTextCommand) {
930             fHistory.add(command);
931             fLastAddedCommand= command;
932         }
933     }
934
935     /**
936      * Disposes the command stack.
937      *
938      * @since 3.1
939      */

940     private void disposeCommandStack() {
941         fHistory.dispose(fUndoContext, true, true, true);
942     }
943
944     /**
945      * Initializes the command stack.
946      *
947      * @since 3.1
948      */

949     private void initializeCommandStack() {
950         if (fHistory != null && fUndoContext != null)
951             fHistory.dispose(fUndoContext, true, true, false);
952
953     }
954
955     /**
956      * Switches the state of whether there is a text listener or not.
957      *
958      * @param listen the state which should be established
959      */

960     private void listenToTextChanges(boolean listen) {
961         if (listen) {
962             if (fDocumentListener == null && fTextViewer.getDocument() != null) {
963                 fDocumentListener= new DocumentListener();
964                 fTextViewer.getDocument().addDocumentListener(fDocumentListener);
965             }
966         } else if (!listen) {
967             if (fDocumentListener != null && fTextViewer.getDocument() != null) {
968                 fTextViewer.getDocument().removeDocumentListener(fDocumentListener);
969                 fDocumentListener= null;
970             }
971         }
972     }
973
974     /**
975      * Closes the current editing command and opens a new one.
976      */

977     private void commit() {
978         // if fCurrent has never been placed on the command stack, do so now.
979
// this can happen when there are multiple programmatically commits in a single
980
// document change.
981
if (fLastAddedCommand != fCurrent) {
982             fCurrent.pretendCommit();
983             if (fCurrent.isValid())
984                 addToCommandStack(fCurrent);
985         }
986         fCurrent.commit();
987     }
988     
989     /**
990      * Reset processChange state.
991      *
992      * @since 3.2
993      */

994     private void resetProcessChangeSate() {
995         fInserting= false;
996         fOverwriting= false;
997         fPreviousDelete.reinitialize();
998     }
999
1000    /**
1001     * Checks whether the given text starts with a line delimiter and
1002     * subsequently contains a white space only.
1003     *
1004     * @param text the text to check
1005     * @return <code>true</code> if the text is a line delimiter followed by whitespace, <code>false</code> otherwise
1006     */

1007    private boolean isWhitespaceText(String JavaDoc text) {
1008
1009        if (text == null || text.length() == 0)
1010            return false;
1011
1012        String JavaDoc[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
1013        int index= TextUtilities.startsWith(delimiters, text);
1014        if (index > -1) {
1015            char c;
1016            int length= text.length();
1017            for (int i= delimiters[index].length(); i < length; i++) {
1018                c= text.charAt(i);
1019                if (c != ' ' && c != '\t')
1020                    return false;
1021            }
1022            return true;
1023        }
1024
1025        return false;
1026    }
1027
1028    private void processChange(int modelStart, int modelEnd, String JavaDoc insertedText, String JavaDoc replacedText, long beforeChangeModificationStamp, long afterChangeModificationStamp) {
1029
1030        if (insertedText == null)
1031            insertedText= ""; //$NON-NLS-1$
1032

1033        if (replacedText == null)
1034            replacedText= ""; //$NON-NLS-1$
1035

1036        int length= insertedText.length();
1037        int diff= modelEnd - modelStart;
1038        
1039        if (fCurrent.fUndoModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
1040            fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1041
1042        // normalize
1043
if (diff < 0) {
1044            int tmp= modelEnd;
1045            modelEnd= modelStart;
1046            modelStart= tmp;
1047        }
1048
1049        if (modelStart == modelEnd) {
1050            // text will be inserted
1051
if ((length == 1) || isWhitespaceText(insertedText)) {
1052                // by typing or whitespace
1053
if (!fInserting || (modelStart != fCurrent.fStart + fTextBuffer.length())) {
1054                    fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1055                    if (fCurrent.attemptCommit())
1056                        fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1057                        
1058                    fInserting= true;
1059                }
1060                if (fCurrent.fStart < 0)
1061                    fCurrent.fStart= fCurrent.fEnd= modelStart;
1062                if (length > 0)
1063                    fTextBuffer.append(insertedText);
1064            } else if (length >= 0) {
1065                // by pasting or model manipulation
1066
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1067                if (fCurrent.attemptCommit())
1068                    fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1069
1070                fCurrent.fStart= fCurrent.fEnd= modelStart;
1071                fTextBuffer.append(insertedText);
1072                fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1073                if (fCurrent.attemptCommit())
1074                    fCurrent.fUndoModificationStamp= afterChangeModificationStamp;
1075
1076            }
1077        } else {
1078            if (length == 0) {
1079                // text will be deleted by backspace or DEL key or empty clipboard
1080
length= replacedText.length();
1081                String JavaDoc[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
1082
1083                if ((length == 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
1084
1085                    // whereby selection is empty
1086

1087                    if (fPreviousDelete.fStart == modelStart && fPreviousDelete.fEnd == modelEnd) {
1088                        // repeated DEL
1089

1090                            // correct wrong settings of fCurrent
1091
if (fCurrent.fStart == modelEnd && fCurrent.fEnd == modelStart) {
1092                            fCurrent.fStart= modelStart;
1093                            fCurrent.fEnd= modelEnd;
1094                        }
1095                            // append to buffer && extend command range
1096
fPreservedTextBuffer.append(replacedText);
1097                        ++fCurrent.fEnd;
1098
1099                    } else if (fPreviousDelete.fStart == modelEnd) {
1100                        // repeated backspace
1101

1102                            // insert in buffer and extend command range
1103
fPreservedTextBuffer.insert(0, replacedText);
1104                        fCurrent.fStart= modelStart;
1105
1106                    } else {
1107                        // either DEL or backspace for the first time
1108

1109                        fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1110                        if (fCurrent.attemptCommit())
1111                            fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1112
1113                        // as we can not decide whether it was DEL or backspace we initialize for backspace
1114
fPreservedTextBuffer.append(replacedText);
1115                        fCurrent.fStart= modelStart;
1116                        fCurrent.fEnd= modelEnd;
1117                    }
1118
1119                    fPreviousDelete.set(modelStart, modelEnd);
1120
1121                } else if (length > 0) {
1122                    // whereby selection is not empty
1123
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1124                    if (fCurrent.attemptCommit())
1125                        fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1126
1127                    fCurrent.fStart= modelStart;
1128                    fCurrent.fEnd= modelEnd;
1129                    fPreservedTextBuffer.append(replacedText);
1130                }
1131            } else {
1132                // text will be replaced
1133

1134                if (length == 1) {
1135                    length= replacedText.length();
1136                    String JavaDoc[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
1137
1138                    if ((length == 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
1139                        // because of overwrite mode or model manipulation
1140
if (!fOverwriting || (modelStart != fCurrent.fStart + fTextBuffer.length())) {
1141                            fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1142                            if (fCurrent.attemptCommit())
1143                                fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1144
1145                            fOverwriting= true;
1146                        }
1147
1148                        if (fCurrent.fStart < 0)
1149                            fCurrent.fStart= modelStart;
1150
1151                        fCurrent.fEnd= modelEnd;
1152                        fTextBuffer.append(insertedText);
1153                        fPreservedTextBuffer.append(replacedText);
1154                        fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1155                        return;
1156                    }
1157                }
1158                // because of typing or pasting whereby selection is not empty
1159
fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
1160                if (fCurrent.attemptCommit())
1161                    fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
1162
1163                fCurrent.fStart= modelStart;
1164                fCurrent.fEnd= modelEnd;
1165                fTextBuffer.append(insertedText);
1166                fPreservedTextBuffer.append(replacedText);
1167            }
1168        }
1169        // in all cases, the redo modification stamp is updated on the open command
1170
fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
1171    }
1172
1173    /**
1174     * Shows the given exception in an error dialog.
1175     *
1176     * @param title the dialog title
1177     * @param ex the exception
1178     * @since 3.1
1179     */

1180    private void openErrorDialog(final String JavaDoc title, final Exception JavaDoc ex) {
1181        Shell shell= null;
1182        if (isConnected()) {
1183            StyledText st= fTextViewer.getTextWidget();
1184            if (st != null && !st.isDisposed())
1185                shell= st.getShell();
1186        }
1187        if (Display.getCurrent() != null)
1188            MessageDialog.openError(shell, title, ex.getLocalizedMessage());
1189        else {
1190            Display display;
1191            final Shell finalShell= shell;
1192            if (finalShell != null)
1193                display= finalShell.getDisplay();
1194            else
1195                display= Display.getDefault();
1196            display.syncExec(new Runnable JavaDoc() {
1197                public void run() {
1198                    MessageDialog.openError(finalShell, title, ex.getLocalizedMessage());
1199                }
1200            });
1201        }
1202    }
1203
1204    /*
1205     * @see org.eclipse.jface.text.IUndoManager#setMaximalUndoLevel(int)
1206     */

1207    public void setMaximalUndoLevel(int undoLevel) {
1208        fUndoLevel= Math.max(0, undoLevel);
1209        if (isConnected()) {
1210            fHistory.setLimit(fUndoContext, fUndoLevel);
1211        }
1212    }
1213
1214    /*
1215     * @see org.eclipse.jface.text.IUndoManager#connect(org.eclipse.jface.text.ITextViewer)
1216     */

1217    public void connect(ITextViewer textViewer) {
1218        if (!isConnected() && textViewer != null) {
1219            fTextViewer= textViewer;
1220            if (fUndoContext == null)
1221                fUndoContext= new ObjectUndoContext(this);
1222
1223            fHistory.setLimit(fUndoContext, fUndoLevel);
1224
1225            initializeCommandStack();
1226
1227            // open up the current command
1228
fCurrent= new TextCommand(fUndoContext);
1229
1230            fPreviousDelete= new TextCommand(fUndoContext);
1231            addListeners();
1232        }
1233    }
1234
1235    /*
1236     * @see org.eclipse.jface.text.IUndoManager#disconnect()
1237     */

1238    public void disconnect() {
1239        if (isConnected()) {
1240
1241            removeListeners();
1242
1243            fCurrent= null;
1244            fTextViewer= null;
1245            disposeCommandStack();
1246            fTextBuffer= null;
1247            fPreservedTextBuffer= null;
1248            fUndoContext= null;
1249        }
1250    }
1251
1252    /*
1253     * @see org.eclipse.jface.text.IUndoManager#reset()
1254     */

1255    public void reset() {
1256        if (isConnected()) {
1257            initializeCommandStack();
1258            fCurrent= new TextCommand(fUndoContext);
1259            fFoldingIntoCompoundChange= false;
1260            fInserting= false;
1261            fOverwriting= false;
1262            fTextBuffer.setLength(0);
1263            fPreservedTextBuffer.setLength(0);
1264            fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
1265            fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
1266        }
1267    }
1268
1269    /*
1270     * @see org.eclipse.jface.text.IUndoManager#redoable()
1271     */

1272    public boolean redoable() {
1273        return fHistory.canRedo(fUndoContext);
1274    }
1275
1276    /*
1277     * @see org.eclipse.jface.text.IUndoManager#undoable()
1278     */

1279    public boolean undoable() {
1280        return fHistory.canUndo(fUndoContext);
1281    }
1282
1283    /*
1284     * @see org.eclipse.jface.text.IUndoManager#redo()
1285     */

1286    public void redo() {
1287        if (isConnected() && redoable()) {
1288            try {
1289                fHistory.redo(fUndoContext, null, null);
1290            } catch (ExecutionException ex) {
1291                openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.redoFailed.title"), ex); //$NON-NLS-1$
1292
}
1293        }
1294    }
1295
1296    /*
1297     * @see org.eclipse.jface.text.IUndoManager#undo()
1298     */

1299    public void undo() {
1300        if (isConnected() && undoable()) {
1301            try {
1302                fHistory.undo(fUndoContext, null, null);
1303            } catch (ExecutionException ex) {
1304                openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.undoFailed.title"), ex); //$NON-NLS-1$
1305
}
1306        }
1307    }
1308
1309    /**
1310     * Selects and reveals the specified range.
1311     *
1312     * @param offset the offset of the range
1313     * @param length the length of the range
1314     * @since 3.0
1315     */

1316    protected void selectAndReveal(int offset, int length) {
1317        if (fTextViewer instanceof ITextViewerExtension5) {
1318            ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
1319            extension.exposeModelRange(new Region(offset, length));
1320        } else if (!fTextViewer.overlapsWithVisibleRegion(offset, length))
1321            fTextViewer.resetVisibleRegion();
1322
1323        fTextViewer.setSelectedRange(offset, length);
1324        fTextViewer.revealRange(offset, length);
1325    }
1326
1327    /*
1328     * @see org.eclipse.jface.text.IUndoManagerExtension#getUndoContext()
1329     * @since 3.1
1330     */

1331    public IUndoContext getUndoContext() {
1332        return fUndoContext;
1333    }
1334
1335}
1336
Popular Tags