~ubuntu-branches/ubuntu/trusty/pitivi/trusty

« back to all changes in this revision

Viewing changes to pitivi/undo.py

* New upstream pre-release:
  + debian/control:
    - Update dependencies.
* debian/control:
  + Update Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# PiTiVi , Non-linear video editor
 
2
#
 
3
#       pitivi/undo.py
 
4
#
 
5
# Copyright (c) 2009, Alessandro Decina <alessandro.d@gmail.com>
 
6
#
 
7
# This program is free software; you can redistribute it and/or
 
8
# modify it under the terms of the GNU Lesser General Public
 
9
# License as published by the Free Software Foundation; either
 
10
# version 2.1 of the License, or (at your option) any later version.
 
11
#
 
12
# This program is distributed in the hope that it will be useful,
 
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
# Lesser General Public License for more details.
 
16
#
 
17
# You should have received a copy of the GNU Lesser General Public
 
18
# License along with this program; if not, write to the
 
19
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
20
# Boston, MA 02111-1307, USA.
 
21
 
 
22
from pitivi.signalinterface import Signallable
 
23
from pitivi.log.loggable import Loggable
 
24
 
 
25
class UndoError(Exception):
 
26
    pass
 
27
 
 
28
class UndoWrongStateError(UndoError):
 
29
    pass
 
30
 
 
31
class UndoableAction(Signallable):
 
32
    __signals__ = {
 
33
        "done": [],
 
34
        "undone": [],
 
35
        "undone": [],
 
36
    }
 
37
 
 
38
    def do(self):
 
39
        raise NotImplementedError()
 
40
 
 
41
    def undo(self):
 
42
        raise NotImplementedError()
 
43
 
 
44
    def clean(self):
 
45
        pass
 
46
 
 
47
    def _done(self):
 
48
        self.emit("done")
 
49
 
 
50
    def _undone(self):
 
51
        self.emit("undone")
 
52
 
 
53
class UndoableActionStack(UndoableAction):
 
54
    __signals__ = {
 
55
        "done": [],
 
56
        "undone": [],
 
57
        "cleaned": [],
 
58
    }
 
59
 
 
60
    def __init__(self, action_group_name):
 
61
        self.action_group_name = action_group_name
 
62
        self.done_actions = []
 
63
        self.undone_actions = []
 
64
        self.actions = []
 
65
 
 
66
    def push(self, action):
 
67
        self.done_actions.append(action)
 
68
 
 
69
    def _runAction(self, action_list, method_name):
 
70
        for action in action_list[::-1]:
 
71
            method = getattr(action, method_name)
 
72
            method()
 
73
 
 
74
    def do(self):
 
75
        self._runAction(self.undone_actions, "do")
 
76
        self.done_actions = self.undone_actions[::-1]
 
77
        self.emit("done")
 
78
 
 
79
    def undo(self):
 
80
        self._runAction(self.done_actions, "undo")
 
81
        self.undone_actions = self.done_actions[::-1]
 
82
        self.emit("undone")
 
83
 
 
84
    def clean(self):
 
85
        actions = self.done_actions + self.undone_actions
 
86
        self.undone_actions = []
 
87
        self.done_actions = []
 
88
        self._runAction(actions, "clean")
 
89
        self.emit("cleaned")
 
90
 
 
91
 
 
92
class UndoableActionLog(Signallable):
 
93
    __signals__ = {
 
94
        "begin": ["stack", "nested"],
 
95
        "push": ["stack", "action"],
 
96
        "rollback": ["stack", "nested"],
 
97
        "commit": ["stack", "nested"],
 
98
        "undo": ["stack"],
 
99
        "redo": ["stack"],
 
100
        "cleaned": [],
 
101
    }
 
102
    def __init__(self):
 
103
        self.undo_stacks = []
 
104
        self.redo_stacks = []
 
105
        self.stacks = []
 
106
        self.running = False
 
107
        self._checkpoint = self._takeSnapshot()
 
108
 
 
109
    def begin(self, action_group_name):
 
110
        if self.running:
 
111
            return
 
112
 
 
113
        stack = UndoableActionStack(action_group_name)
 
114
        nested = self._stackIsNested(stack)
 
115
        self.stacks.append(stack)
 
116
        self.emit("begin", stack, nested)
 
117
 
 
118
    def push(self, action):
 
119
        if self.running:
 
120
            return
 
121
 
 
122
        try:
 
123
            stack = self._getTopmostStack()
 
124
        except UndoWrongStateError:
 
125
            return
 
126
 
 
127
        stack.push(action)
 
128
        self.emit("push", stack, action)
 
129
 
 
130
    def rollback(self):
 
131
        if self.running:
 
132
            return
 
133
 
 
134
        stack = self._getTopmostStack(pop=True)
 
135
        if stack is None:
 
136
            return
 
137
        nested = self._stackIsNested(stack)
 
138
        self.emit("rollback", stack, nested)
 
139
        stack.undo()
 
140
 
 
141
    def commit(self):
 
142
        if self.running:
 
143
            return
 
144
 
 
145
        stack = self._getTopmostStack(pop=True)
 
146
        if stack is None:
 
147
            return
 
148
        nested = self._stackIsNested(stack)
 
149
        if not self.stacks:
 
150
            self.undo_stacks.append(stack)
 
151
        else:
 
152
            self.stacks[-1].push(stack)
 
153
 
 
154
        if self.redo_stacks:
 
155
            self.redo_stacks = []
 
156
 
 
157
        self.emit("commit", stack, nested)
 
158
 
 
159
    def undo(self):
 
160
        if self.stacks or not self.undo_stacks:
 
161
            raise UndoWrongStateError()
 
162
 
 
163
        stack = self.undo_stacks.pop(-1)
 
164
 
 
165
        self._runStack(stack, stack.undo)
 
166
 
 
167
        self.redo_stacks.append(stack)
 
168
        self.emit("undo", stack)
 
169
 
 
170
    def redo(self):
 
171
        if self.stacks or not self.redo_stacks:
 
172
            raise UndoWrongStateError()
 
173
 
 
174
        stack = self.redo_stacks.pop(-1)
 
175
 
 
176
        self._runStack(stack, stack.do)
 
177
        self.undo_stacks.append(stack)
 
178
        self.emit("redo", stack)
 
179
 
 
180
    def clean(self):
 
181
        stacks = self.redo_stacks + self.undo_stacks
 
182
        self.redo_stacks = []
 
183
        self.undo_stacks = []
 
184
 
 
185
        for stack in stacks:
 
186
            self._runStack(stack, stack.clean)
 
187
        self.emit("cleaned")
 
188
 
 
189
    def _takeSnapshot(self):
 
190
        return list(self.undo_stacks)
 
191
 
 
192
    def checkpoint(self):
 
193
        if self.stacks:
 
194
            raise UndoWrongStateError()
 
195
 
 
196
        self._checkpoint = self._takeSnapshot()
 
197
 
 
198
    def dirty(self):
 
199
        current_snapshot = self._takeSnapshot()
 
200
        return current_snapshot != self._checkpoint
 
201
 
 
202
    def _runStack(self, stack, run):
 
203
        self.running = True
 
204
        try:
 
205
            run()
 
206
        finally:
 
207
            self.running = False
 
208
 
 
209
    def _getTopmostStack(self, pop=False):
 
210
        stack = None
 
211
        try:
 
212
            if pop:
 
213
                stack = self.stacks.pop(-1)
 
214
            else:
 
215
                stack = self.stacks[-1]
 
216
        except IndexError:
 
217
            raise UndoWrongStateError()
 
218
 
 
219
        return stack
 
220
 
 
221
    def _stackIsNested(self, stack):
 
222
        return bool(len(self.stacks))
 
223
 
 
224
class DebugActionLogObserver(Loggable):
 
225
    def startObserving(self, log):
 
226
        self._connectToActionLog(log)
 
227
 
 
228
    def stopObserving(self, log):
 
229
        self._disconnectFromActionLog(log)
 
230
 
 
231
    def _connectToActionLog(self, log):
 
232
        log.connect("begin", self._actionLogBeginCb)
 
233
        log.connect("commit", self._actionLogCommitCb)
 
234
        log.connect("rollback", self._actionLogRollbackCb)
 
235
        log.connect("push", self._actionLogPushCb)
 
236
 
 
237
    def _disconnectFromActionLog(self, log):
 
238
        for method in (self._actionLogBeginCb, self._actionLogCommitCb,
 
239
                self._actionLogrollbackCb, self._actionLogPushCb):
 
240
            log.disconnect_by_func(method)
 
241
 
 
242
    def _actionLogBeginCb(self, log, stack, nested):
 
243
        self.debug("begin action %s nested %s",
 
244
                stack.action_group_name, nested)
 
245
 
 
246
    def _actionLogCommitCb(self, log, stack, nested):
 
247
        self.debug("commit action %s nested %s",
 
248
                stack.action_group_name, nested)
 
249
 
 
250
    def _actionLogRollbackCb(self, log, stack, nested):
 
251
        self.debug("rollback action %s nested %s",
 
252
                stack.action_group_name, nested)
 
253
 
 
254
    def _actionLogPushCb(self, log, stack, action):
 
255
        self.debug("push %s in %s", action, stack.action_group_name)