Package qubx :: Module undo
[hide private]
[frames] | no frames]

Source Code for Module qubx.undo

  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   
25 -class UndoStack(object):
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']
52 - def __init__(self, depth=None):
53 self.OnChange = WeakEvent() # (undostack) 54 self._working = False 55 self._enabled = True 56 self._depth = depth 57 self._depth0 = depth 58 self.clear()
59 - def clear(self):
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):
67 self._enabled = enabled
68 - def get_can_undo(self):
69 return bool(self.uu) # and isinstance(self.uu[-1], str))
70 can_undo = property(get_can_undo)
71 - def get_undo_lbl(self):
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)
78 - def get_can_redo(self):
79 return bool(self.rr) # and isinstance(self.rr[-1], str))
80 can_redo = property(get_can_redo)
81 - def get_redo_lbl(self):
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)
88 - def push_undo(self, undo, redo):
89 """Adds undo() and redo() to the stack.""" 90 if self._working or not self._enabled: return 91 self.uu.append( (undo, redo) )
92 - def seal_undo(self, 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()
101 - def push_redo(self, redo, undo):
102 if self._working: return 103 self.rr.append( (redo, undo) )
104 - def seal_redo(self, lbl):
105 if self._working: return 106 self.rr.append( lbl ) 107 self.OnChange(self)
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 #if self._working: return 113 self._working = True 114 # leftover/unsealed 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)
134 - def redo(self):
135 """Does whatever was undone by undo(), transfers it back to the undo stack.""" 136 if not self.can_redo: return 137 #if self._working: return 138 self._working = True 139 while self.rr and not isinstance(self.rr[-1], str): 140 redo, undo = self.rr.pop() 141 redo() 142 self.uu.append( (undo, redo) ) 143 lbl = self.rr.pop() 144 while self.rr and not isinstance(self.rr[-1], str): 145 redo, undo = self.rr.pop() 146 redo() 147 self.uu.append( (undo, redo) ) 148 self.uu.append( lbl ) 149 self.add_check_depth() 150 self._working = False 151 self.OnChange(self)
152 - def add_check_depth(self):
153 if not (self._depth is None): 154 if self._depth: 155 self._depth -= 1 156 else: # forget the oldest 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