[pitivi] undo: Restore the transitions details when readding a clip



commit 6e87daba02da82e03984be3bb352df600a014d76
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Sun Jun 19 23:10:25 2016 +0200

    undo: Restore the transitions details when readding a clip
    
    When a clip which generated a transition is removed, the transition is
    also removed. When the operation is undone, the transition is
    recreated. The props were being lost though.
    
    Now the action log has a new ExpandableUndoableAction class which is
    able to include (merge) the actions coming right after it. We use this
    for the ClipRemoved action pushed when an UriClip is removed. In our
    case, first the UriClip is removed and then the transition clip(s). The
    ClipRemoved is able to include now the following TransitionClipRemoved
    actions and applies them when undoing *after* it has added the clip
    back.
    
    TransitionClipRemoved searches for the corresponding transition clip in
    the same layer and applies the props of the video element.
    
    Differential Revision: https://phabricator.freedesktop.org/D1095

 pitivi/undo/timeline.py     |   76 ++++++++++++++++++++++++++++++++++++++++--
 pitivi/undo/undo.py         |   24 +++++++++++++
 tests/test_undo_timeline.py |    8 ++++
 3 files changed, 104 insertions(+), 4 deletions(-)
---
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index d3f649b..4cdbf79 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -21,6 +21,7 @@ from gi.repository import GObject
 from gi.repository import Gst
 
 from pitivi.effects import PROPS_TO_IGNORE
+from pitivi.undo.undo import ExpandableUndoableAction
 from pitivi.undo.undo import FinalizingAction
 from pitivi.undo.undo import GObjectObserver
 from pitivi.undo.undo import MetaContainerObserver
@@ -29,6 +30,9 @@ from pitivi.undo.undo import UndoableAction
 from pitivi.utils.loggable import Loggable
 
 
+TRANSITION_PROPS = ["border", "invert", "transition-type"]
+
+
 def child_property_name(pspec):
     return "%s::%s" % (pspec.owner_type.name, pspec.name)
 
@@ -265,12 +269,19 @@ class ClipAdded(UndoableAction):
         return st
 
 
-class ClipRemoved(UndoableAction):
+class ClipRemoved(ExpandableUndoableAction):
 
     def __init__(self, layer, clip):
-        UndoableAction.__init__(self)
+        ExpandableUndoableAction.__init__(self)
         self.layer = layer
         self.clip = clip
+        self.transition_removed_actions = []
+
+    def expand(self, action):
+        if not isinstance(action, TransitionClipRemovedAction):
+            return False
+        self.transition_removed_actions.append(action)
+        return True
 
     def do(self):
         self.layer.remove_clip(self.clip)
@@ -280,6 +291,9 @@ class ClipRemoved(UndoableAction):
         self.clip.set_name(None)
         self.layer.add_clip(self.clip)
         self.layer.get_timeline().get_asset().pipeline.commit_timeline()
+        # Update the automatically created transitions.
+        for action in self.transition_removed_actions:
+            action.undo()
 
     def asScenarioAction(self):
         timeline = self.layer.get_timeline()
@@ -292,6 +306,57 @@ class ClipRemoved(UndoableAction):
         return st
 
 
+class TransitionClipRemovedAction(UndoableAction):
+
+    def __init__(self, ges_layer, ges_clip, track_element):
+        UndoableAction.__init__(self)
+        self.ges_layer = ges_layer
+        self.start = ges_clip.props.start
+        self.duration = ges_clip.props.duration
+        self.track_element = track_element
+
+        self.properties = []
+        for property_name in TRANSITION_PROPS:
+            field_name = property_name.replace("-", "_")
+            value = track_element.get_property(field_name)
+            self.properties.append((property_name, value))
+
+    @classmethod
+    def new(cls, ges_layer, ges_clip):
+        track_element = cls.get_video_element(ges_clip)
+        if not track_element:
+            return None
+        return cls(ges_layer, ges_clip, track_element)
+
+    @staticmethod
+    def get_video_element(ges_clip):
+        for track_element in ges_clip.get_children(True):
+            if isinstance(track_element, GES.VideoTransition):
+                return track_element
+        return None
+
+    def do(self):
+        # The transition is being removed, nothing to do.
+        pass
+
+    def undo(self):
+        # Search the transition clip created automatically to update it.
+        transition_clip = None
+        for ges_clip in self.ges_layer.get_clips():
+            if isinstance(ges_clip, GES.TransitionClip) and \
+                    ges_clip.props.start == self.start and \
+                    ges_clip.props.duration == self.duration:
+                # Got the transition clip, now find its video element, if any.
+                track_element = self.get_video_element(ges_clip)
+                if not track_element:
+                    # Probably the audio transition clip.
+                    continue
+                # Double lucky!
+                for prop_name, value in self.properties:
+                    track_element.set_property(prop_name, value)
+                break
+
+
 class LayerAdded(UndoableAction):
 
     def __init__(self, ges_timeline, ges_layer):
@@ -513,8 +578,8 @@ class LayerObserver(MetaContainerObserver, Loggable):
 
     def _connectToTrackElement(self, track_element):
         if isinstance(track_element, GES.VideoTransition):
-            props = ["border", "invert", "transition-type"]
-            observer = GObjectObserver(track_element, props, self.action_log)
+            observer = GObjectObserver(track_element, TRANSITION_PROPS,
+                                       self.action_log)
             self.track_element_observers[track_element] = observer
             return
 
@@ -558,6 +623,9 @@ class LayerObserver(MetaContainerObserver, Loggable):
     def _clipRemovedCb(self, layer, clip):
         self._disconnectFromClip(clip)
         if isinstance(clip, GES.TransitionClip):
+            action = TransitionClipRemovedAction.new(layer, clip)
+            if action:
+                self.action_log.push(action)
             return
         action = ClipRemoved(layer, clip)
         self.action_log.push(action)
diff --git a/pitivi/undo/undo.py b/pitivi/undo/undo.py
index f315e1e..99b2810 100644
--- a/pitivi/undo/undo.py
+++ b/pitivi/undo/undo.py
@@ -55,6 +55,22 @@ class UndoableAction(GObject.Object, Loggable):
         raise NotImplementedError()
 
 
+class ExpandableUndoableAction(GObject.Object, Loggable):
+    """An action which can include immediately following actions."""
+
+    def expand(self, action):
+        """Expands including the specified action.
+
+        Args:
+            action (UndoableAction): The action to include.
+
+        Returns:
+            bool: Whether the action has been included, in which case
+                it should not be used for anything else.
+        """
+        raise NotImplementedError()
+
+
 class SimpleUndoableAction(UndoableAction):
 
     def do(self):
@@ -85,6 +101,12 @@ class UndoableActionStack(UndoableAction):
         return "%s: %s" % (self.action_group_name, self.done_actions)
 
     def push(self, action):
+        if self.done_actions:
+            last_action = self.done_actions[-1]
+            if isinstance(last_action, ExpandableUndoableAction):
+                if last_action.expand(action):
+                    # The action has been included in the previous one.
+                    return
         self.done_actions.append(action)
 
     def _runAction(self, action_list, method_name):
@@ -222,6 +244,7 @@ class UndoableActionLog(GObject.Object, Loggable):
             raise UndoWrongStateError("Nothing to undo")
 
         stack = self.undo_stacks.pop(-1)
+        self.debug("Undo %s", stack)
         self._run(stack.undo)
         self.redo_stacks.append(stack)
         self.emit("move", stack)
@@ -234,6 +257,7 @@ class UndoableActionLog(GObject.Object, Loggable):
             raise UndoWrongStateError("Nothing to redo")
 
         stack = self.redo_stacks.pop(-1)
+        self.debug("Redo %s", stack)
         self._run(stack.do)
         self.undo_stacks.append(stack)
         self.emit("move", stack)
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index 81580e1..fad9218 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -383,6 +383,14 @@ 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)
+
 
 class TestControlSourceObserver(BaseTestUndoTimeline):
 


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