[pitivi] undo: Restore the transitions details when readding a clip
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] undo: Restore the transitions details when readding a clip
- Date: Thu, 23 Jun 2016 06:54:58 +0000 (UTC)
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]