[pitivi] undo: Fix undo ops on obsolete video transitions



commit 6f558f1bd7fa011128562d087c2822bd65cf1eeb
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Mon Jun 20 00:14:51 2016 +0200

    undo: Fix undo ops on obsolete video transitions
    
    If the user changes a transition and then later removes the clip and
    then even later starts undoing and gets back to the
    PropertyChangedAction corresponding to the transition change, the
    action was keeping the old transition clip object and working on it
    instead of the replacement.
    
    The new UndoableAutomaticObjectAction superclass of
    PropertyChangedAction keeps track of the latest object and the mapping
    from old to new objects. It's notified when a replacement is found in
    the TransitionClipRemoved.undo method, for now. This allows
    PropertyChangedAction to work on the latest object.
    
    PropertyChangedAction is used also for other types of objects, not only
    GES.VideoTransition, but the overhead is only a dictionarly lookup when
    undoing or redoing.
    
    Differential Revision: https://phabricator.freedesktop.org/D1096

 pitivi/undo/timeline.py     |    4 ++-
 pitivi/undo/undo.py         |   44 ++++++++++++++++++++++++++++++++++++++----
 tests/test_undo_timeline.py |   31 +++++++++++++++++++++++------
 3 files changed, 66 insertions(+), 13 deletions(-)
---
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index 4cdbf79..50a5c02 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -27,6 +27,7 @@ from pitivi.undo.undo import GObjectObserver
 from pitivi.undo.undo import MetaContainerObserver
 from pitivi.undo.undo import SimpleUndoableAction
 from pitivi.undo.undo import UndoableAction
+from pitivi.undo.undo import UndoableAutomaticObjectAction
 from pitivi.utils.loggable import Loggable
 
 
@@ -318,7 +319,7 @@ class TransitionClipRemovedAction(UndoableAction):
         self.properties = []
         for property_name in TRANSITION_PROPS:
             field_name = property_name.replace("-", "_")
-            value = track_element.get_property(field_name)
+            value = self.track_element.get_property(field_name)
             self.properties.append((property_name, value))
 
     @classmethod
@@ -352,6 +353,7 @@ class TransitionClipRemovedAction(UndoableAction):
                     # Probably the audio transition clip.
                     continue
                 # Double lucky!
+                UndoableAutomaticObjectAction.update_object(self.track_element, track_element)
                 for prop_name, value in self.properties:
                     track_element.set_property(prop_name, value)
                 break
diff --git a/pitivi/undo/undo.py b/pitivi/undo/undo.py
index 99b2810..5c3d30e 100644
--- a/pitivi/undo/undo.py
+++ b/pitivi/undo/undo.py
@@ -55,6 +55,41 @@ class UndoableAction(GObject.Object, Loggable):
         raise NotImplementedError()
 
 
+class UndoableAutomaticObjectAction(UndoableAction):
+    """An action on an automatically created object.
+
+    Attributes:
+        auto_object (object): The object which has been automatically created
+            and might become obsolete later.
+    """
+
+    __updates = {}
+
+    def __init__(self, auto_object):
+        UndoableAction.__init__(self)
+        self.__auto_object = auto_object
+
+    @property
+    def auto_object(self):
+        """The latest object which identifies the same thing as the original."""
+        return self.__updates.get(self.__auto_object, self.__auto_object)
+
+    @classmethod
+    def update_object(cls, auto_object, new_auto_object):
+        """Provides a replacement for an object.
+
+        Args:
+            auto_object (object): The object being replaced.
+            new_auto_object (object): The replacement.
+        """
+        cls.__updates[auto_object] = new_auto_object
+        others = [key
+                  for key, value in cls.__updates.items()
+                  if value == auto_object]
+        for other in others:
+            cls.__updates[other] = new_auto_object
+
+
 class ExpandableUndoableAction(GObject.Object, Loggable):
     """An action which can include immediately following actions."""
 
@@ -345,20 +380,19 @@ class MetaContainerObserver(GObject.Object):
         self.meta_container = None
 
 
-class PropertyChangedAction(UndoableAction):
+class PropertyChangedAction(UndoableAutomaticObjectAction):
 
     def __init__(self, gobject, field_name, old_value, new_value):
-        UndoableAction.__init__(self)
-        self.gobject = gobject
+        UndoableAutomaticObjectAction.__init__(self, gobject)
         self.field_name = field_name
         self.old_value = old_value
         self.new_value = new_value
 
     def do(self):
-        self.gobject.set_property(self.field_name, self.new_value)
+        self.auto_object.set_property(self.field_name, self.new_value)
 
     def undo(self):
-        self.gobject.set_property(self.field_name, self.old_value)
+        self.auto_object.set_property(self.field_name, self.old_value)
 
 
 class GObjectObserver(GObject.Object):
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index fad9218..efe180f 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -383,13 +383,30 @@ class TestLayerObserver(BaseTestUndoTimeline):
         self.assertEqual(transition_element.get_transition_type(),
                          GES.VideoStandardTransitionType.BAR_WIPE_LR)
 
-        # Remove the clip and add it back. This recreates the transition clip.
-        with self.action_log.started("remove clip"):
-            self.layer.remove_clip(clip2)
-        self.action_log.undo()
-        transition_element = get_transition_element(self.layer)
-        self.assertEqual(transition_element.get_transition_type(),
-                         GES.VideoStandardTransitionType.BAR_WIPE_LR)
+        for unused_repeat in range(4):
+            # Remove the clip and add it back.
+            # This recreates the transition clip.
+            with self.action_log.started("remove clip"):
+                self.layer.remove_clip(clip2)
+            self.action_log.undo()
+            transition_element = get_transition_element(self.layer)
+            self.assertEqual(transition_element.get_transition_type(),
+                             GES.VideoStandardTransitionType.BAR_WIPE_LR)
+
+            # Undo a transition change operation done on a now obsolete
+            # transition clip.
+            self.action_log.undo()
+            transition_element = get_transition_element(self.layer)
+            self.assertEqual(transition_element.get_transition_type(),
+                             GES.VideoStandardTransitionType.CROSSFADE)
+
+            self.action_log.redo()
+            transition_element = get_transition_element(self.layer)
+            self.assertEqual(transition_element.get_transition_type(),
+                             GES.VideoStandardTransitionType.BAR_WIPE_LR,
+                             "The auto objects map in "
+                             "UndoableAutomaticObjectAction is not updated when "
+                             "undoing clip remove.")
 
 
 class TestControlSourceObserver(BaseTestUndoTimeline):


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