OSDN Git Service

layer move: make undoable, distance weighting etc
[mypaint-anime/master.git] / lib / command.py
1 # This file is part of MyPaint.
2 # Copyright (C) 2007-2008 by Martin Renold <martinxyz@gmx.ch>
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8
9 import layer
10 import helpers
11 from gettext import gettext as _
12
13 class CommandStack:
14     def __init__(self):
15         self.call_before_action = []
16         self.stack_observers = []
17         self.clear()
18     
19     def clear(self):
20         self.undo_stack = []
21         self.redo_stack = []
22         self.notify_stack_observers()
23
24     def do(self, command):
25         for f in self.call_before_action: f()
26         self.redo_stack = [] # discard
27         command.redo()
28         self.undo_stack.append(command)
29         self.reduce_undo_history()
30         self.notify_stack_observers()
31
32     def undo(self):
33         if not self.undo_stack: return
34         for f in self.call_before_action: f()
35         command = self.undo_stack.pop()
36         command.undo()
37         self.redo_stack.append(command)
38         self.notify_stack_observers()
39         return command
40         
41     def redo(self):
42         if not self.redo_stack: return
43         for f in self.call_before_action: f()
44         command = self.redo_stack.pop()
45         command.redo()
46         self.undo_stack.append(command)
47         self.notify_stack_observers()
48         return command
49
50     def reduce_undo_history(self):
51         stack = self.undo_stack
52         self.undo_stack = []
53         steps = 0
54         for item in reversed(stack):
55             self.undo_stack.insert(0, item)
56             if not item.automatic_undo:
57                 steps += 1
58             if steps == 30: # and memory > ...
59                 break
60
61     def get_last_command(self):
62         if not self.undo_stack: return None
63         return self.undo_stack[-1]
64
65     def notify_stack_observers(self):
66         for func in self.stack_observers:
67             func(self)
68
69 class Action:
70     '''Base class for all undo/redoable actions. Subclasses must implement the
71     undo and redo methods. They should have a reference to the document in 
72     self.doc'''
73     automatic_undo = False
74     display_name = _("Unknown Action")
75
76     def redo(self):
77         raise NotImplementedError
78     def undo(self):
79         raise NotImplementedError
80
81     # Utility functions
82     def _notify_canvas_observers(self, affected_layers):
83         bbox = helpers.Rect()
84         for layer in affected_layers:
85             layer_bbox = layer.get_bbox()
86             bbox.expandToIncludeRect(layer_bbox)
87         for func in self.doc.canvas_observers:
88             func(*bbox)
89
90     def _notify_document_observers(self):
91         self.doc.call_doc_observers()
92
93 class Stroke(Action):
94     display_name = _("Painting")
95     def __init__(self, doc, stroke, snapshot_before):
96         """called only when the stroke was just completed and is now fully rendered"""
97         self.doc = doc
98         assert stroke.finished
99         self.stroke = stroke # immutable; not used for drawing any more, just for inspection
100         self.before = snapshot_before
101         self.doc.layer.add_stroke(stroke, snapshot_before)
102         # this snapshot will include the updated stroke list (modified by the line above)
103         self.after = self.doc.layer.save_snapshot()
104     def undo(self):
105         self.doc.layer.load_snapshot(self.before)
106     def redo(self):
107         self.doc.layer.load_snapshot(self.after)
108
109 class ClearLayer(Action):
110     display_name = _("Clear Layer")
111     def __init__(self, doc):
112         self.doc = doc
113     def redo(self):
114         self.before = self.doc.layer.save_snapshot()
115         self.doc.layer.clear()
116         self._notify_document_observers()
117     def undo(self):
118         self.doc.layer.load_snapshot(self.before)
119         del self.before
120         self._notify_document_observers()
121
122 class LoadLayer(Action):
123     display_name = _("Load Layer")
124     def __init__(self, doc, tiledsurface):
125         self.doc = doc
126         self.tiledsurface = tiledsurface
127     def redo(self):
128         layer = self.doc.layer
129         self.before = layer.save_snapshot()
130         layer.load_from_surface(self.tiledsurface)
131     def undo(self):
132         self.doc.layer.load_snapshot(self.before)
133         del self.before
134
135 class MergeLayer(Action):
136     """merge the current layer into dst"""
137     display_name = _("Merge Layers")
138     def __init__(self, doc, dst_idx):
139         self.doc = doc
140         self.dst_layer = self.doc.layers[dst_idx]
141         self.remove_src = RemoveLayer(doc)
142     def redo(self):
143         self.dst_before = self.dst_layer.save_snapshot()
144         assert self.doc.layer is not self.dst_layer
145         self.doc.layer.merge_into(self.dst_layer)
146         self.remove_src.redo()
147         self.select_dst = SelectLayer(self.doc, self.doc.layers.index(self.dst_layer))
148         self.select_dst.redo()
149         self._notify_document_observers()
150     def undo(self):
151         self.select_dst.undo()
152         del self.select_dst
153         self.remove_src.undo()
154         self.dst_layer.load_snapshot(self.dst_before)
155         del self.dst_before
156         self._notify_document_observers()
157
158 class AddLayer(Action):
159     display_name = _("Add Layer")
160     def __init__(self, doc, insert_idx=None, after=None, name=''):
161         self.doc = doc
162         self.insert_idx = insert_idx
163         if after:
164             l_idx = self.doc.layers.index(after)
165             self.insert_idx = l_idx + 1
166         self.layer = layer.Layer(name)
167         self.layer.content_observers.append(self.doc.layer_modified_cb)
168     def redo(self):
169         self.doc.layers.insert(self.insert_idx, self.layer)
170         self.prev_idx = self.doc.layer_idx
171         self.doc.layer_idx = self.insert_idx
172         self._notify_document_observers()
173     def undo(self):
174         self.doc.layers.remove(self.layer)
175         self.doc.layer_idx = self.prev_idx
176         self._notify_document_observers()
177
178 class RemoveLayer(Action):
179     display_name = _("Remove Layer")
180     def __init__(self, doc,layer=None):
181         self.doc = doc
182         self.layer = layer
183     def redo(self):
184         assert len(self.doc.layers) > 1
185         if self.layer:
186             self.idx = self.doc.layers.index(self.layer)
187             self.doc.layers.remove(self.layer)
188         else:
189             self.idx = self.doc.layer_idx
190             self.layer = self.doc.layers.pop(self.doc.layer_idx)
191         if self.doc.layer_idx == len(self.doc.layers):
192             self.doc.layer_idx -= 1
193         self._notify_canvas_observers([self.layer])
194         self._notify_document_observers()
195     def undo(self):
196         self.doc.layers.insert(self.idx, self.layer)
197         self.doc.layer_idx = self.idx
198         self._notify_canvas_observers([self.layer])
199         self._notify_document_observers()
200
201 class SelectLayer(Action):
202     display_name = _("Select Layer")
203     automatic_undo = True
204     def __init__(self, doc, idx):
205         self.doc = doc
206         self.idx = idx
207     def redo(self):
208         assert self.idx >= 0 and self.idx < len(self.doc.layers)
209         self.prev_idx = self.doc.layer_idx
210         self.doc.layer_idx = self.idx
211         self._notify_document_observers()
212     def undo(self):
213         self.doc.layer_idx = self.prev_idx
214         self._notify_document_observers()
215
216 class MoveLayer(Action):
217     display_name = _("Move Layer on Canvas")
218     # NOT "Move Layer" for now - old translatable string with different sense
219     def __init__(self, doc, layer_idx, dx, dy, ignore_first_redo=True):
220         self.doc = doc
221         self.layer_idx = layer_idx
222         self.dx = dx
223         self.dy = dy
224         self.ignore_first_redo = ignore_first_redo
225     def redo(self):
226         layer = self.doc.layers[self.layer_idx]
227         if self.ignore_first_redo:
228             # these are typically created interactively, after
229             # the entire layer has been moved
230             self.ignore_first_redo = False
231         else:
232             layer.translate(self.dx, self.dy)
233         self._notify_canvas_observers([layer])
234         self._notify_document_observers()
235     def undo(self):
236         layer = self.doc.layers[self.layer_idx]
237         layer.translate(-self.dx, -self.dy)
238         self._notify_canvas_observers([layer])
239         self._notify_document_observers()
240
241 class ReorderSingleLayer(Action):
242     display_name = _("Reorder Layer in Stack")
243     def __init__(self, doc, was_idx, new_idx, select_new=False):
244         self.doc = doc
245         self.was_idx = was_idx
246         self.new_idx = new_idx
247         self.select_new = select_new
248     def redo(self):
249         moved_layer = self.doc.layers[self.was_idx]
250         self.doc.layers.remove(moved_layer)
251         self.doc.layers.insert(self.new_idx, moved_layer)
252         if self.select_new:
253             self.was_selected = self.doc.layer_idx
254             self.doc.layer_idx = self.new_idx
255         self._notify_canvas_observers([moved_layer])
256         self._notify_document_observers()
257     def undo(self):
258         moved_layer = self.doc.layers[self.new_idx]
259         self.doc.layers.remove(moved_layer)
260         self.doc.layers.insert(self.was_idx, moved_layer)
261         if self.select_new:
262             self.doc.layer_idx = self.was_selected
263             self.was_selected = None
264         self._notify_canvas_observers([moved_layer])
265         self._notify_document_observers()
266
267 class DuplicateLayer(Action):
268     display_name = _("Duplicate Layer")
269     def __init__(self, doc, insert_idx=None, name=''):
270         self.doc = doc
271         self.insert_idx = insert_idx
272         snapshot = self.doc.layers[self.insert_idx].save_snapshot()
273         self.new_layer = layer.Layer(name)
274         self.new_layer.load_snapshot(snapshot)
275         self.new_layer.content_observers.append(self.doc.layer_modified_cb)
276     def redo(self):
277         self.doc.layers.insert(self.insert_idx+1, self.new_layer)
278         self.duplicate_layer = self.doc.layers[self.insert_idx+1]
279         self._notify_canvas_observers([self.duplicate_layer])
280         self._notify_document_observers()
281     def undo(self):
282         self.doc.layers.remove(self.duplicate_layer)
283         original_layer = self.doc.layers[self.insert_idx]
284         self._notify_canvas_observers([original_layer])
285         self._notify_document_observers()
286
287 class ReorderLayers(Action):
288     display_name = _("Reorder Layer Stack")
289     def __init__(self, doc, new_order):
290         self.doc = doc
291         self.old_order = doc.layers[:]
292         self.selection = self.old_order[doc.layer_idx]
293         self.new_order = new_order
294         for layer in new_order:
295             assert layer in self.old_order
296         assert len(self.old_order) == len(new_order)
297     def redo(self):
298         self.doc.layers[:] = self.new_order
299         self.doc.layer_idx = self.doc.layers.index(self.selection)
300         self._notify_canvas_observers(self.doc.layers)
301         self._notify_document_observers()
302     def undo(self):
303         self.doc.layers[:] = self.old_order
304         self.doc.layer_idx = self.doc.layers.index(self.selection)
305         self._notify_canvas_observers(self.doc.layers)
306         self._notify_document_observers()
307
308 class SetLayerVisibility(Action):
309     def __init__(self, doc, visible, layer):
310         self.doc = doc
311         self.new_visibility = visible
312         self.layer = layer
313     def redo(self):
314         self.old_visibility = self.layer.visible
315         self.layer.visible = self.new_visibility
316         self._notify_canvas_observers([self.layer])
317         self._notify_document_observers()
318     def undo(self):
319         self.layer.visible = self.old_visibility
320         self._notify_canvas_observers([self.layer])
321         self._notify_document_observers()
322     @property
323     def display_name(self):
324         if self.new_visibility:
325             return _("Make Layer Visible")
326         else:
327             return _("Make Layer Invisible")
328
329 class SetLayerLocked (Action):
330     def __init__(self, doc, locked, layer):
331         self.doc = doc
332         self.new_locked = locked
333         self.layer = layer
334     def redo(self):
335         self.old_locked = self.layer.locked
336         self.layer.locked = self.new_locked
337         self._notify_canvas_observers([self.layer])
338         self._notify_document_observers()
339     def undo(self):
340         self.layer.locked = self.old_locked
341         self._notify_canvas_observers([self.layer])
342         self._notify_document_observers()
343     @property
344     def display_name(self):
345         if self.new_locked:
346             return _("Lock Layer")
347         else:
348             return _("Unlock Layer")
349
350 class SetLayerOpacity(Action):
351     display_name = _("Change Layer Visibility")
352     def __init__(self, doc, opacity, layer=None):
353         self.doc = doc
354         self.new_opacity = opacity
355         self.layer = layer
356     def redo(self):
357         if self.layer:
358             l = self.layer
359         else:
360             l = self.doc.layer
361         self.old_opacity = l.opacity
362         l.opacity = self.new_opacity
363         self._notify_canvas_observers([l])
364         self._notify_document_observers()
365     def undo(self):
366         if self.layer:
367             l = self.layer
368         else:
369             l = self.doc.layer
370         l.opacity = self.old_opacity
371         self._notify_canvas_observers([l])
372         self._notify_document_observers()
373
374 class SetLayerCompositeOp(Action):
375     display_name = _("Change Layer Blending Mode")
376     def __init__(self, doc, compositeop, layer=None):
377         self.doc = doc
378         self.new_compositeop = compositeop
379         self.layer = layer
380     def redo(self):
381         if self.layer:
382             l = self.layer
383         else:
384             l = self.doc.layer
385         self.old_compositeop = l.compositeop
386         l.compositeop = self.new_compositeop
387         self._notify_canvas_observers([l])
388         self._notify_document_observers()
389     def undo(self):
390         if self.layer:
391             l = self.layer
392         else:
393             l = self.doc.layer
394         l.compositeop = self.old_compositeop
395         self._notify_canvas_observers([l])
396         self._notify_document_observers()
397