1 """Unlimited undo/redo.
2
3 Copyright 2007-2015 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 from qubx.util_types import *
23 import traceback
24
26 """
27
28 Stack of undo and redo actions.
29
30 An action consists of one or more callables (no arguments).
31 When the user does something, you push one or more pairs of functions
32 that undo and redo it, then seal the action with a label.
33 So, the top of the stack should be a seal (label),
34 and "undo" reverses all actions between the top seal and the next one down.
35
36 >>> def Undo():
37 ... print "undo"
38 >>> def Redo():
39 ... print "redo"
40 >>> undos = UndoStack()
41 >>> undos.push_undo(Undo, Redo)
42 >>> undos.seal_undo('bogus test')
43 >>> undos.undo()
44 >>> undos.redo()
45
46 @ivar can_undo: True if there is an undo action
47 @ivar undo_lbl: string describing the undo action
48 @ivar can_redo: True if there is a redo action
49 @ivar redo_lbl: string describing the redo action
50 """
51 __slots__ = ['OnChange', '_working', '_enabled', '_depth', '_depth0', 'uu', 'rr']
60 """Resets the stack so there are no undo or redo actions."""
61 if self._working: return
62 self.uu = []
63 self.rr = []
64 self._depth = self._depth0
65 self.OnChange(self)
66 - def enable(self, enabled=True):
70 can_undo = property(get_can_undo)
72 if self.can_undo:
73 for i in xrange(1, len(self.uu)+1):
74 if isinstance(self.uu[-i], str):
75 return self.uu[-i]
76 return ""
77 undo_lbl = property(get_undo_lbl)
80 can_redo = property(get_can_redo)
82 if self.can_redo:
83 for i in xrange(1, len(self.rr)+1):
84 if isinstance(self.rr[-i], str):
85 return self.rr[-i]
86 return ""
87 redo_lbl = property(get_redo_lbl)
93 """Groups the preceding calls to push_undo(), sets undo_lbl,
94 clears the redo stack."""
95 if self._working or (not self.uu) or not self._enabled: return
96 if isinstance(self.uu[-1], str):
97 return
98 self.uu.append( lbl )
99 self.rr = []
100 self.add_check_depth()
108 - def undo(self, single_item=False):
109 """Calls all unsealed undo functions, and all between the last two seal_undo()s,
110 and transfers them to the redo stack."""
111 if not self.can_undo: return
112
113 self._working = True
114
115 while self.uu and not isinstance(self.uu[-1], str):
116 undo, redo = self.uu.pop()
117 undo()
118 self.rr.append( (redo, undo) )
119 if single_item:
120 self._working = False
121 return
122 lbl = self.uu and self.uu.pop() or ""
123 while self.uu and not isinstance(self.uu[-1], str):
124 undo, redo = self.uu.pop()
125 undo()
126 self.rr.append( (redo, undo) )
127 if single_item:
128 break
129 self.rr.append( lbl )
130 self._working = False
131 if not (self._depth is None):
132 self._depth += 1
133 self.OnChange(self)
153 if not (self._depth is None):
154 if self._depth:
155 self._depth -= 1
156 else:
157 for i, x in enumerate(self.uu):
158 if isinstance(x, str):
159 self.uu[:i+1] = []
160 break
161 self.OnChange(self)
162