/
_history.py
174 lines (136 loc) · 4.81 KB
/
_history.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# -*- coding: utf-8 -*-
"""History class for undo stack."""
#------------------------------------------------------------------------------
# Imports
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# History class
#------------------------------------------------------------------------------
class History(object):
"""Implement a history of actions with an undo stack."""
def __init__(self, base_item=None):
self.clear(base_item)
def clear(self, base_item=None):
"""Clear the history."""
# List of changes, contains at least the base item.
self._history = [base_item]
# Index of the current item.
self._index = 0
@property
def current_item(self):
"""Return the current element."""
if self._history and self._index >= 0:
self._check_index()
return self._history[self._index]
@property
def current_position(self):
"""Current position in the history."""
return self._index
def _check_index(self):
"""Check that the index is without the bounds of _history."""
assert 0 <= self._index <= len(self._history) - 1
# There should always be the base item at least.
assert len(self._history) >= 1
def is_first(self):
return self._index == 1
def is_last(self):
return self._index == len(self._history) - 1
def iter(self, start=0, end=None):
"""Iterate through successive history items.
Parameters
----------
end : int
Index of the last item to loop through + 1.
start : int
Initial index for the loop (0 by default).
"""
if end is None:
end = self._index + 1
elif end == 0:
raise StopIteration()
if start >= end:
raise StopIteration()
# Check arguments.
assert 0 <= end <= len(self._history)
assert 0 <= start <= end - 1
for i in range(start, end):
yield self._history[i]
def __iter__(self):
return self.iter()
def __len__(self):
return len(self._history)
def add(self, item):
"""Add an item in the history."""
self._check_index()
# Possibly truncate the history up to the current point.
self._history = self._history[:self._index + 1]
# Append the item
self._history.append(item)
# Increment the index.
self._index += 1
self._check_index()
# Check that the current element is what was provided to the function.
assert id(self.current_item) == id(item)
def back(self):
"""Go back in history if possible.
Return the undone item.
"""
if self._index <= 0:
return None
undone = self.current_item
self._index -= 1
self._check_index()
return undone
def undo(self):
return self.back()
def forward(self):
"""Go forward in history if possible.
Return the current item after going forward.
"""
if self._index >= len(self._history) - 1:
return None
self._index += 1
self._check_index()
return self.current_item
def redo(self):
return self.forward()
class GlobalHistory(History):
"""Merge several controllers with different undo stacks."""
def __init__(self, process_ups=None):
super(GlobalHistory, self).__init__(())
self.process_ups = process_ups
def action(self, *controllers):
"""Register one or several controllers for this action."""
self.add(tuple(controllers))
def add_to_current_action(self, controller):
"""Add a controller to the current action."""
item = self.current_item
self._history[self._index] = item + (controller,)
def undo(self):
"""Undo the last action.
This will call `undo()` on all controllers involved in this action.
"""
controllers = self.back()
if controllers is None:
ups = ()
else:
ups = tuple([controller.undo()
for controller in controllers])
if self.process_ups is not None:
return self.process_ups(ups)
else:
return ups
def redo(self):
"""Redo the last action.
This will call `redo()` on all controllers involved in this action.
"""
controllers = self.forward()
if controllers is None:
ups = ()
else:
ups = tuple([controller.redo() for
controller in controllers])
if self.process_ups is not None:
return self.process_ups(ups)
else:
return ups