001package arcommon.ui.other; 002 003import java.awt.event.ActionEvent; 004import java.util.ArrayList; 005 006import javax.swing.AbstractAction; 007import javax.swing.KeyStroke; 008import javax.swing.event.UndoableEditEvent; 009import javax.swing.event.UndoableEditListener; 010import javax.swing.text.Document; 011import javax.swing.text.JTextComponent; 012import javax.swing.undo.CannotRedoException; 013import javax.swing.undo.CannotUndoException; 014import javax.swing.undo.UndoManager; 015import javax.swing.undo.UndoableEdit; 016 017import arcommon.DateUtil; 018 019/** 020 * An UndoManager that can handle the double undo and redo that is created by a {@link javax.swing.text.JTextComponent#setText(String)} call. 021 * <br> 022 * The double undo is a remove (all) and a insert (all). These are combined by this UndoManager.<br> 023 * NOTE: The {@link #nextUndoIsDouble()} method MUST be MANUALLY called before the call to {@link javax.swing.text.JTextComponent#setText(String)}!<br> 024 * Or the two undos must be executed fast (< 1msec) after each other (and {@link #FAST_EDITS_ARE_THE_SAME_EDIT} = true).<br> 025 * Fast like selecting text and then start writing. This is with this UndoManager also only one undo / redo. 026 */ 027public class MyUndoManager extends UndoManager { 028 private static final long serialVersionUID = 4127345062834612017L; 029 030 public static final boolean FAST_EDITS_ARE_THE_SAME_EDIT = true; 031 032 public static MyUndoManager addUndoToTextComponent(JTextComponent textComponent) { 033 // Code copied from http://www.exampledepot.com/egs/javax.swing.undo/UndoText.html 034 // and modified 035 final MyUndoManager myUndoManager = new MyUndoManager(); 036 Document document = textComponent.getDocument(); 037 038 // Listen for undo and redo events 039 document.addUndoableEditListener(new UndoableEditListener() { 040 public void undoableEditHappened(UndoableEditEvent event) { 041 myUndoManager.addEdit(event.getEdit()); 042 } 043 }); 044 045 // Create an undo action and add it to the text component 046 textComponent.getActionMap().put("Undo", new AbstractAction("Undo") { 047 public void actionPerformed(ActionEvent event) { 048 try { 049 if (myUndoManager.canUndo()) { 050 myUndoManager.undo(); 051 } 052 } catch (CannotUndoException e) { 053 ; 054 } 055 } 056 057 private static final long serialVersionUID = 7732797549184931993L; 058 }); 059 060 // Bind the undo action to control Z 061 textComponent.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo"); 062 063 // Create a redo action and add it to the text component 064 textComponent.getActionMap().put("Redo", new AbstractAction("Redo") { 065 public void actionPerformed(ActionEvent event) { 066 try { 067 if (myUndoManager.canRedo()) { 068 myUndoManager.redo(); 069 } 070 } catch (CannotRedoException e) { 071 ; 072 } 073 } 074 075 private static final long serialVersionUID = 6838362662435784818L; 076 }); 077 078 // Bind the redo action to control shift Z 079 textComponent.getInputMap().put(KeyStroke.getKeyStroke("control shift Z"), "Redo"); 080 081 return myUndoManager; 082 } 083 084 private ArrayList<Boolean> doubleUndo; 085 private int index; 086 private boolean nextUndoIsDouble; 087 private long lastChangedTimeNano; 088 089 public MyUndoManager() { 090 reset(); 091 } 092 093 private void reset() { 094 doubleUndo = new ArrayList<Boolean>(); 095 index = -1; 096 lastChangedTimeNano = -1; 097 } 098 099 public void nextUndoIsDouble() { 100 nextUndoIsDouble = true; 101 } 102 103 @Override 104 public synchronized boolean addEdit(UndoableEdit anEdit) { 105 if (index + 1 != doubleUndo.size()) { 106 doubleUndo = new ArrayList<Boolean>(doubleUndo.subList(0, index + 1)); 107 } 108 doubleUndo.add(nextUndoIsDouble); 109 index++; 110 nextUndoIsDouble = false; 111 112 long currentTime = System.nanoTime(); 113 long diff = currentTime - lastChangedTimeNano; 114 if (diff < DateUtil.MILLISECOND_IN_NANOSECONDS && FAST_EDITS_ARE_THE_SAME_EDIT) { 115 doubleUndo.set(index - 1, Boolean.TRUE); 116 } 117 lastChangedTimeNano = currentTime; 118 119 return super.addEdit(anEdit); 120 } 121 122 @Override 123 public void undo() throws CannotUndoException { 124 if (index > 0 && doubleUndo.get(index - 1)) { 125 index--; 126 super.undo(); 127 } 128 index--; 129 super.undo(); 130 } 131 132 @Override 133 public void redo() throws CannotRedoException { 134 if (index + 1 < doubleUndo.size() && doubleUndo.get(index + 1)) { 135 index++; 136 super.redo(); 137 } 138 index++; 139 super.redo(); 140 } 141 142 @Override 143 public synchronized void discardAllEdits() { 144 reset(); 145 super.discardAllEdits(); 146 } 147 148 @Override 149 public String toString() { 150 return "index: " + index + ", doubleUndo: " + doubleUndo + ", nextUndoIsDouble: " + nextUndoIsDouble; 151 } 152}