1 # This file is part of MyPaint.
2 # Copyright (C) 2007-2008 by Martin Renold <martinxyz@gmx.ch>
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.
11 from gettext import gettext as _
15 self.call_before_action = []
16 self.stack_observers = []
22 self.notify_stack_observers()
24 def do(self, command):
25 for f in self.call_before_action: f()
26 self.redo_stack = [] # discard
28 self.undo_stack.append(command)
29 self.reduce_undo_history()
30 self.notify_stack_observers()
33 if not self.undo_stack: return
34 for f in self.call_before_action: f()
35 command = self.undo_stack.pop()
37 self.redo_stack.append(command)
38 self.notify_stack_observers()
42 if not self.redo_stack: return
43 for f in self.call_before_action: f()
44 command = self.redo_stack.pop()
46 self.undo_stack.append(command)
47 self.notify_stack_observers()
50 def reduce_undo_history(self):
51 stack = self.undo_stack
54 for item in reversed(stack):
55 self.undo_stack.insert(0, item)
56 if not item.automatic_undo:
58 if steps == 30: # and memory > ...
61 def get_last_command(self):
62 if not self.undo_stack: return None
63 return self.undo_stack[-1]
65 def notify_stack_observers(self):
66 for func in self.stack_observers:
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
73 automatic_undo = False
74 display_name = _("Unknown Action")
77 raise NotImplementedError
79 raise NotImplementedError
82 def _notify_canvas_observers(self, affected_layers):
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:
90 def _notify_document_observers(self):
91 self.doc.call_doc_observers()
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"""
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()
105 self.doc.layer.load_snapshot(self.before)
107 self.doc.layer.load_snapshot(self.after)
109 class ClearLayer(Action):
110 display_name = _("Clear Layer")
111 def __init__(self, doc):
114 self.before = self.doc.layer.save_snapshot()
115 self.doc.layer.clear()
116 self._notify_document_observers()
118 self.doc.layer.load_snapshot(self.before)
120 self._notify_document_observers()
122 class LoadLayer(Action):
123 display_name = _("Load Layer")
124 def __init__(self, doc, tiledsurface):
126 self.tiledsurface = tiledsurface
128 layer = self.doc.layer
129 self.before = layer.save_snapshot()
130 layer.load_from_surface(self.tiledsurface)
132 self.doc.layer.load_snapshot(self.before)
135 class MergeLayer(Action):
136 """merge the current layer into dst"""
137 display_name = _("Merge Layers")
138 def __init__(self, doc, dst_idx):
140 self.dst_layer = self.doc.layers[dst_idx]
141 self.remove_src = RemoveLayer(doc)
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()
151 self.select_dst.undo()
153 self.remove_src.undo()
154 self.dst_layer.load_snapshot(self.dst_before)
156 self._notify_document_observers()
158 class AddLayer(Action):
159 display_name = _("Add Layer")
160 def __init__(self, doc, insert_idx=None, after=None, name=''):
162 self.insert_idx = insert_idx
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)
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()
174 self.doc.layers.remove(self.layer)
175 self.doc.layer_idx = self.prev_idx
176 self._notify_document_observers()
178 class RemoveLayer(Action):
179 display_name = _("Remove Layer")
180 def __init__(self, doc,layer=None):
184 assert len(self.doc.layers) > 1
186 self.idx = self.doc.layers.index(self.layer)
187 self.doc.layers.remove(self.layer)
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()
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()
201 class SelectLayer(Action):
202 display_name = _("Select Layer")
203 automatic_undo = True
204 def __init__(self, doc, idx):
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()
213 self.doc.layer_idx = self.prev_idx
214 self._notify_document_observers()
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):
221 self.layer_idx = layer_idx
224 self.ignore_first_redo = ignore_first_redo
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
232 layer.translate(self.dx, self.dy)
233 self._notify_canvas_observers([layer])
234 self._notify_document_observers()
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()
241 class ReorderSingleLayer(Action):
242 display_name = _("Reorder Layer in Stack")
243 def __init__(self, doc, was_idx, new_idx, select_new=False):
245 self.was_idx = was_idx
246 self.new_idx = new_idx
247 self.select_new = select_new
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)
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()
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)
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()
267 class DuplicateLayer(Action):
268 display_name = _("Duplicate Layer")
269 def __init__(self, doc, insert_idx=None, name=''):
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)
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()
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()
287 class ReorderLayers(Action):
288 display_name = _("Reorder Layer Stack")
289 def __init__(self, doc, new_order):
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)
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()
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()
308 class SetLayerVisibility(Action):
309 def __init__(self, doc, visible, layer):
311 self.new_visibility = visible
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()
319 self.layer.visible = self.old_visibility
320 self._notify_canvas_observers([self.layer])
321 self._notify_document_observers()
323 def display_name(self):
324 if self.new_visibility:
325 return _("Make Layer Visible")
327 return _("Make Layer Invisible")
329 class SetLayerLocked (Action):
330 def __init__(self, doc, locked, layer):
332 self.new_locked = locked
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()
340 self.layer.locked = self.old_locked
341 self._notify_canvas_observers([self.layer])
342 self._notify_document_observers()
344 def display_name(self):
346 return _("Lock Layer")
348 return _("Unlock Layer")
350 class SetLayerOpacity(Action):
351 display_name = _("Change Layer Visibility")
352 def __init__(self, doc, opacity, layer=None):
354 self.new_opacity = opacity
361 self.old_opacity = l.opacity
362 l.opacity = self.new_opacity
363 self._notify_canvas_observers([l])
364 self._notify_document_observers()
370 l.opacity = self.old_opacity
371 self._notify_canvas_observers([l])
372 self._notify_document_observers()
374 class SetLayerCompositeOp(Action):
375 display_name = _("Change Layer Blending Mode")
376 def __init__(self, doc, compositeop, layer=None):
378 self.new_compositeop = compositeop
385 self.old_compositeop = l.compositeop
386 l.compositeop = self.new_compositeop
387 self._notify_canvas_observers([l])
388 self._notify_document_observers()
394 l.compositeop = self.old_compositeop
395 self._notify_canvas_observers([l])
396 self._notify_document_observers()