[pitivi/wip-speed-control: 4/8] undo: Join successive similar operations on title clips




commit 313e8f071ae4f9ee076ad3758bf2c5cdbc6c7454
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Sun Jan 24 20:44:25 2021 +0100

    undo: Join successive similar operations on title clips
    
    Fixes #2022

 pitivi/clip_properties/title.py |  2 +-
 pitivi/undo/timeline.py         | 13 +++++++++
 pitivi/undo/undo.py             | 60 ++++++++++++++++++++++++++++++++++-------
 3 files changed, 64 insertions(+), 11 deletions(-)
---
diff --git a/pitivi/clip_properties/title.py b/pitivi/clip_properties/title.py
index 7a612b7a0..00889ccea 100644
--- a/pitivi/clip_properties/title.py
+++ b/pitivi/clip_properties/title.py
@@ -115,7 +115,7 @@ class TitleProperties(Gtk.Expander, Loggable):
         self.show_all()
 
     def _set_child_property(self, name, value):
-        with self.app.action_log.started("Title change property",
+        with self.app.action_log.started("Title change property %s" % name,
                                          toplevel=True):
             self._setting_props = True
             try:
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index 1b478754d..6330ada01 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -77,6 +77,19 @@ class TrackElementPropertyChanged(UndoableAction):
         st['value'] = GObject.Value(pspec.value_type, value)
         return st
 
+    def expand(self, action):
+        if not isinstance(action, TrackElementPropertyChanged):
+            return False
+
+        if not action.track_element == self.track_element:
+            return False
+
+        if not action.property_name == self.property_name:
+            return False
+
+        self.new_value = action.new_value
+        return True
+
 
 class TimelineElementObserver(Loggable):
     """Monitors the props of an element and all its children.
diff --git a/pitivi/undo/undo.py b/pitivi/undo/undo.py
index 39de7b247..e65fa428e 100644
--- a/pitivi/undo/undo.py
+++ b/pitivi/undo/undo.py
@@ -132,15 +132,40 @@ class UndoableActionStack(UndoableAction, Loggable):
         self.done_actions = []
         self.finalizing_action = finalizing_action
 
+    def __len__(self):
+        return len(self.done_actions)
+
     def __repr__(self):
         return "%s: %s" % (self.action_group_name, self.done_actions)
 
+    def attempt_merge(self, stack, action):
+        """Merges the action into the previous one if possible.
+
+        Returns:
+            bool: Whether the merge has been done.
+        """
+        if not self.done_actions:
+            return False
+
+        if not self.action_group_name == stack.action_group_name:
+            return False
+
+        return self.attempt_expand_action(action)
+
+    def attempt_expand_action(self, action):
+        """Expands the last action with the specified action if possible."""
+        if not self.done_actions:
+            return False
+
+        last_action = self.done_actions[-1]
+        return last_action.expand(action)
+
     def push(self, action):
-        if self.done_actions:
-            last_action = self.done_actions[-1]
-            if last_action.expand(action):
-                # The action has been included in the previous one.
-                return
+        """Adds an action unless it's possible to expand the previous."""
+        if self.attempt_expand_action(action):
+            # The action has been merged into the last one.
+            return
+
         self.done_actions.append(action)
 
     def _run_action(self, actions, method_name):
@@ -225,7 +250,7 @@ class UndoableActionLog(GObject.Object, Loggable):
         self.emit("begin", stack)
 
     def push(self, action):
-        """Reports a change.
+        """Records a change noticed by the monitoring system.
 
         Args:
             action (Action): The action representing the change.
@@ -249,11 +274,24 @@ class UndoableActionLog(GObject.Object, Loggable):
         try:
             stack = self._get_last_stack()
         except UndoWrongStateError as e:
-            self.warning("Failed pushing '%s' because: %s", action, e)
+            self.warning("Failed pushing '%s' because no transaction started: %s", action, e)
             return
-        stack.push(action)
-        self.debug("push action %s in action group %s",
-                   action, stack.action_group_name)
+
+        merged = False
+        if len(self.stacks[0]) == 0 and self.undo_stacks:
+            # The current undoable operation is empty, this is the first action.
+            # Check if it can be merged with the previous operation.
+            previous_operation = self.undo_stacks[-1]
+            if previous_operation.attempt_merge(stack, action):
+                self.debug("Merging undoable operations")
+                self.stacks = [self.undo_stacks.pop()]
+                merged = True
+
+        if not merged:
+            stack.push(action)
+            self.debug("push action %s in action group %s",
+                       action, stack.action_group_name)
+
         self.emit("push", stack, action)
 
     def rollback(self, undo=True):
@@ -299,9 +337,11 @@ class UndoableActionLog(GObject.Object, Loggable):
         stack = self._get_last_stack(pop=True)
         if action_group_name != stack.action_group_name:
             raise UndoWrongStateError("Unexpected commit", action_group_name, stack, self.stacks)
+
         if not stack.done_actions:
             self.debug("Ignore empty stack %s", stack.action_group_name)
             return
+
         if not self.stacks:
             self.undo_stacks.append(stack)
             stack.finish_operation()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]