[pitivi] undo: Reimplement undo/redo for Keyframes.



commit d43b1a28ca012362022e58d8bd1ee820956a94ea
Author: Thibault Saunier <tsaunier gnome org>
Date:   Tue Sep 30 16:24:47 2014 +0200

    undo: Reimplement undo/redo for Keyframes.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=739251

 pitivi/timeline/elements.py |   43 ++++++++++++
 pitivi/undo/timeline.py     |  149 ++++++++++++++++++++++++-------------------
 2 files changed, 127 insertions(+), 65 deletions(-)
---
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 4d72fa3..4e3cb11 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -286,6 +286,7 @@ class TimelineElement(Clutter.Actor, Zoomable):
         self.keyframedElement = None
         self.rightHandle = None
         self.isSelected = False
+        self.updating_keyframes = False
         size = self.bElement.get_duration()
 
         self.background = self._createBackground()
@@ -317,6 +318,24 @@ class TimelineElement(Clutter.Actor, Zoomable):
 
         self._connectToEvents()
 
+    def _valueChanged(self, source, value):
+        if self.updating_keyframes is True:
+            return
+
+        self.updateKeyframes()
+
+    def _valueAddedCb(self, source, value):
+        if self.updating_keyframes is True:
+            return
+
+        self.updateKeyframes()
+
+    def _valueRemovedCb(self, source, value):
+        if self.updating_keyframes is True:
+            return
+
+        self.updateKeyframes()
+
     # Public API
 
     def set_size(self, width, height, ease):
@@ -354,14 +373,22 @@ class TimelineElement(Clutter.Actor, Zoomable):
             self.restore_easing_state()
 
     def addKeyframe(self, value, timestamp):
+        self.timeline._container.app.action_log.begin("Add KeyFrame")
+        self.updating_keyframes = True
         self.source.set(timestamp, value)
         self.updateKeyframes()
+        self.timeline._container.app.action_log.commit()
+        self.updating_keyframes = False
 
     def removeKeyframe(self, kf):
+        self.timeline._container.app.action_log.begin("Remove KeyFrame")
+        self.updating_keyframes = True
         self.source.unset(kf.value.timestamp)
         self.keyframes = sorted(
             self.keyframes, key=lambda keyframe: keyframe.value.timestamp)
         self.updateKeyframes()
+        self.timeline._container.app.action_log.commit()
+        self.updating_keyframes = False
 
     def showKeyframes(self, element, propname, isDefault=False):
         binding = element.get_control_binding(propname.name)
@@ -377,6 +404,9 @@ class TimelineElement(Clutter.Actor, Zoomable):
         self.prop = propname
         self.keyframedElement = element
         self.source = self.binding.props.control_source
+        self.source.connect("value-added", self._valueAddedCb)
+        self.source.connect("value-removed", self._valueRemovedCb)
+        self.source.connect("value-changed", self._valueChanged)
 
         if isDefault:
             self.default_prop = propname
@@ -426,6 +456,8 @@ class TimelineElement(Clutter.Actor, Zoomable):
         if not self.source:
             return
 
+        updating = self.updating_keyframes
+        self.updating_keyframes = True
         values = self.source.get_all()
         if len(values) < 2 and self.bElement.props.duration > 0:
             self.source.unset_all()
@@ -447,6 +479,7 @@ class TimelineElement(Clutter.Actor, Zoomable):
             self.keyframes.append(keyframe)
 
         self.drawLines()
+        self.updating_keyframes = updating
 
     def cleanup(self):
         Zoomable.__del__(self)
@@ -761,6 +794,8 @@ class Line(Clutter.Actor):
         pass
 
     def _dragBeginCb(self, unused_action, unused_actor, event_x, event_y, unused_modifiers):
+        self.timelineElement.timeline._container.app.action_log.begin(
+            "Dragging keyframe line")
         self.dragBeginStartX = event_x
         self.dragBeginStartY = event_y
         self.origY = self.props.y
@@ -783,6 +818,7 @@ class Line(Clutter.Actor):
         self.nextKeyframe.endDrag()
         if self.timelineElement.timeline.getActorUnderPointer() != self:
             self._ungrab()
+        self.timelineElement.timeline._container.app.action_log.commit()
 
 
 class KeyframeMenu(GtkClutter.Actor):
@@ -923,6 +959,8 @@ class Keyframe(Clutter.Actor):
         if not self.has_changeable_time:
             newTs = self.lastTs
 
+        updating = self.timelineElement.updating_keyframes
+        self.timelineElement.updating_keyframes = True
         self.timelineElement.source.unset(self.lastTs)
         if (self.timelineElement.source.set(newTs, newValue)):
             self.value = Gst.TimedValue()
@@ -942,7 +980,11 @@ class Keyframe(Clutter.Actor):
                 self.timelineElement.timeline._container.seekInPosition(
                     newTs + self.start)
 
+        self.timelineElement.updating_keyframes = updating
+
     def _dragBeginCb(self, unused_action, unused_actor, event_x, event_y, unused_modifiers):
+        self.timelineElement.timeline._container.app.action_log.begin(
+            "Dragging keyframe")
         self.dragProgressed = False
         self.startDrag(event_x, event_y)
 
@@ -958,6 +1000,7 @@ class Keyframe(Clutter.Actor):
         self.endDrag()
         if self.timelineElement.timeline.getActorUnderPointer() != self:
             self._unselect()
+        self.timelineElement.timeline._container.app.action_log.commit()
 
 
 class URISourceElement(TimelineElement):
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index 319716c..4749f08 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -225,42 +225,44 @@ class KeyframeChangeTracker(GObject.Object):
     }
 
     def __init__(self):
+        GObject.Object.__init__(self)
         self.keyframes = None
-        self.obj = None
+        self.control_source = None
 
-    def connectToObject(self, obj):
-        self.obj = obj
-        self.keyframes = self._takeCurrentSnapshot(obj)
-        obj.connect("keyframe-added", self._keyframeAddedCb)
-        obj.connect("keyframe-removed", self._keyframeRemovedCb)
-        obj.connect("keyframe-moved", self._keyframeMovedCb)
+    def connectToObject(self, control_source):
+        self.control_source = control_source
+        self.keyframes = self._takeCurrentSnapshot(control_source)
+        control_source.connect("value-added", self._keyframeAddedCb)
+        control_source.connect("value-removed", self._keyframeRemovedCb)
+        control_source.connect("value-changed", self._keyframeMovedCb)
 
-    def _takeCurrentSnapshot(self, obj):
+    def _takeCurrentSnapshot(self, control_source):
         keyframes = {}
-        for keyframe in self.obj.getKeyframes():
-            keyframes[keyframe] = self._getKeyframeSnapshot(keyframe)
+        for keyframe in self.control_source.get_all():
+            keyframes[keyframe.timestamp] = self._getKeyframeSnapshot(keyframe)
 
         return keyframes
 
-    def disconnectFromObject(self, obj):
-        self.obj = None
-        obj.disconnect_by_func(self._keyframeMovedCb)
+    def disconnectFromObject(self, control_source):
+        self.control_source = None
+        control_source.disconnect_by_func(self._keyframeMovedCb)
 
-    def _keyframeAddedCb(self, interpolator, keyframe):
-        self.keyframes[keyframe] = self._getKeyframeSnapshot(keyframe)
+    def _keyframeAddedCb(self, control_source, keyframe):
+        self.keyframes[keyframe.timestamp] = self._getKeyframeSnapshot(keyframe)
 
-    def _keyframeRemovedCb(self, interpolator, keyframe, old_value=None):
+    def _keyframeRemovedCb(self, control_source, keyframe, old_value=None):
         pass  # FIXME: this has not been implemented
 
-    def _keyframeMovedCb(self, interpolator, keyframe, old_value=None):
-        old_snapshot = self.keyframes[keyframe]
+    def _keyframeMovedCb(self, control_source, keyframe, old_value=None):
+        old_snapshot = self.keyframes[keyframe.timestamp]
         new_snapshot = self._getKeyframeSnapshot(keyframe)
-        self.keyframes[keyframe] = new_snapshot
-        self.emit("keyframe-moved", interpolator,
+        self.keyframes[keyframe.timestamp] = new_snapshot
+
+        self.emit("keyframe-moved", control_source,
                   keyframe, old_snapshot, new_snapshot)
 
     def _getKeyframeSnapshot(self, keyframe):
-        return (keyframe.mode, keyframe.time, keyframe.value)
+        return (keyframe.timestamp, keyframe.value)
 
 
 class ClipPropertyChanged(UndoableAction):
@@ -373,40 +375,44 @@ class LayerRemoved(UndoableAction):
         return st
 
 
-class InterpolatorKeyframeAdded(UndoableAction):
+class ControlSourceValueAdded(UndoableAction):
 
-    def __init__(self, track_element, keyframe):
+    def __init__(self, control_source, keyframe,
+                 property_name):
         UndoableAction.__init__(self)
-        self.track_element = track_element
+        self.control_source = control_source
         self.keyframe = keyframe
+        self.property_name = property_name
 
     def do(self):
-        self.track_element.newKeyframe(self.keyframe)
+        self.control_source.set(self.keyframe.timestamp,
+                                self.keyframe.value)
         self._done()
 
     def undo(self):
-        self.track_element.removeKeyframe(self.keyframe)
+        self.control_source.unset(self.keyframe.timestamp)
         self._undone()
 
 
-class InterpolatorKeyframeRemoved(UndoableAction):
+class ControlSourceValueRemoved(UndoableAction):
 
-    def __init__(self, track_element, keyframe):
+    def __init__(self, control_source, keyframe, property_name):
         UndoableAction.__init__(self)
-        self.track_element = track_element
+        self.control_source = control_source
         self.keyframe = keyframe
+        self.property_name = property_name
 
     def do(self):
-        self.track_element.removeKeyframe(self.keyframe)
+        self.control_source.unset(self.keyframe.timestamp)
         self._undone()
 
     def undo(self):
-        self.track_element.newKeyframe(self.keyframe.time,
-                                       self.keyframe.value, self.keyframe.mode)
+        self.control_source.set(self.keyframe.timestamp,
+                                self.keyframe.value)
         self._done()
 
 
-class InterpolatorKeyframeChanged(UndoableAction):
+class ControlSourceKeyframeChanged(UndoableAction):
 
     def __init__(self, track_element, keyframe, old_snapshot, new_snapshot):
         UndoableAction.__init__(self)
@@ -424,8 +430,7 @@ class InterpolatorKeyframeChanged(UndoableAction):
         self._undone()
 
     def _setSnapshot(self, snapshot):
-        mode, time, value = snapshot
-        self.keyframe.setMode(mode)
+        time, value = snapshot
         self.keyframe.setTime(time)
         self.keyframe.setValue(value)
 
@@ -450,15 +455,12 @@ class ActivePropertyChanged(UndoableAction):
 
 class TimelineLogObserver(object):
     timelinePropertyChangedAction = ClipPropertyChanged
-    interpolatorKeyframeAddedAction = InterpolatorKeyframeAdded
-    interpolatorKeyframeRemovedAction = InterpolatorKeyframeRemoved
-    interpolatorKeyframeChangedAction = InterpolatorKeyframeChanged
     activePropertyChangedAction = ActivePropertyChanged
 
     def __init__(self, log):
         self.log = log
         self.clip_property_trackers = {}
-        self.interpolator_keyframe_trackers = {}
+        self.control_source_keyframe_trackers = {}
         self.children_props_tracker = TrackElementChildPropertyTracker(log)
         self._pipeline = None
 
@@ -525,34 +527,49 @@ class TimelineLogObserver(object):
         tracker.disconnectFromObject(clip)
         tracker.disconnect_by_func(self._clipPropertyChangedCb)
 
+    def _controlBindingAddedCb(self, track_element, binding):
+        self._connectToControlSource(track_element, binding)
+
     def _connectToTrackElement(self, track_element):
-        # FIXME: keyframes are disabled:
-        # for prop, interpolator in track_element.getInterpolators().itervalues():
-            # self._connectToInterpolator(interpolator)
+        for prop, binding in track_element.get_all_control_bindings().items():
+            self._connectToControlSource(track_element, binding)
+        track_element.connect("control-binding-added",
+                              self._controlBindingAddedCb)
         if isinstance(track_element, GES.BaseEffect):
             self.children_props_tracker.addTrackElement(track_element)
         elif isinstance(track_element, GES.TitleSource):
             self.children_props_tracker.addTrackElement(track_element)
 
     def _disconnectFromTrackElement(self, track_element):
-        pass
-        # for prop, interpolator in track_element.getInterpolators().values():
-        #    self._disconnectFromInterpolator(interpolator)
-
-    def _connectToInterpolator(self, interpolator):
-        interpolator.connect(
-            "keyframe-added", self._interpolatorKeyframeAddedCb)
-        interpolator.connect(
-            "keyframe-removed", self._interpolatorKeyframeRemovedCb)
+        for prop, binding in track_element.get_all_control_bindings().items():
+            self._disconnectFromControlSource(binding)
+
+    def _connectToControlSource(self, track_element, binding):
+        control_source = binding.props.control_source
+
+        control_source.connect("value-added",
+                               self._controlSourceKeyFrameAddedCb,
+                               track_element,
+                               binding.props.name)
+
+        control_source.connect("value-removed",
+                               self._controlSourceKeyFrameRemovedCb,
+                               track_element,
+                               binding.props.name)
+
         tracker = KeyframeChangeTracker()
-        tracker.connectToObject(interpolator)
-        tracker.connect("keyframe-moved", self._interpolatorKeyframeMovedCb)
-        self.interpolator_keyframe_trackers[interpolator] = tracker
+        tracker.connectToObject(control_source)
+        tracker.connect("keyframe-moved", self._controlSourceKeyFrameMovedCb)
+        self.control_source_keyframe_trackers[control_source] = tracker
+
+    def _disconnectFromControlSource(self, binding):
+        control_source = binding.props.control_source
+        control_source.disconnect_by_func(self._controlSourceKeyFrameAddedCb)
+        control_source.disconnect_by_func(self._controlSourceKeyFrameRemovedCb)
 
-    def _disconnectFromInterpolator(self, interpolator):
-        tracker = self.interpolator_keyframe_trackers.pop(interpolator)
-        tracker.disconnectFromObject(interpolator)
-        tracker.disconnect_by_func(self._interpolatorKeyframeMovedCb)
+        tracker = self.control_source_keyframe_trackers.pop(control_source)
+        tracker.disconnectFromObject(control_source)
+        tracker.disconnect_by_func(self._controlSourceKeyFrameMovedCb)
 
     def _clipAddedCb(self, layer, clip):
         if isinstance(clip, GES.TransitionClip):
@@ -592,12 +609,14 @@ class TimelineLogObserver(object):
                                          self.children_props_tracker)
             self.log.push(action)
 
-    def _interpolatorKeyframeAddedCb(self, track_element, keyframe):
-        action = self.interpolatorKeyframeAddedAction(track_element, keyframe)
+    def _controlSourceKeyFrameAddedCb(self, source, keyframe, track_element,
+                                     property_name):
+        action = ControlSourceValueAdded(source, keyframe, property_name)
         self.log.push(action)
 
-    def _interpolatorKeyframeRemovedCb(self, track_element, keyframe, old_value=None):
-        action = self.interpolatorKeyframeRemovedAction(track_element, keyframe)
+    def _controlSourceKeyFrameRemovedCb(self, source, keyframe, track_element,
+                                        property_name):
+        action = ControlSourceValueRemoved(source, keyframe, property_name)
         self.log.push(action)
 
     def _trackElementActiveChangedCb(self, track_element, active, add_effect_action):
@@ -607,10 +626,10 @@ class TimelineLogObserver(object):
         action = self.activePropertyChangedAction(add_effect_action, active)
         self.log.push(action)
 
-    def _interpolatorKeyframeMovedCb(self, tracker, track_element,
+    def _controlSourceKeyFrameMovedCb(self, tracker, track_element,
                                      keyframe, old_snapshot, new_snapshot):
-        action = self.interpolatorKeyframeChangedAction(track_element,
-                                                        keyframe, old_snapshot, new_snapshot)
+        action = ControlSourceKeyframeChanged(track_element, keyframe,
+                                              old_snapshot, new_snapshot)
         self.log.push(action)
 
     def _layerAddedCb(self, timeline, layer):


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