[pitivi] undo: Allow undo/redo layer moving



commit 3f85813af0d8651634ce8363e1948b3248f8dd53
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Sun Apr 24 02:15:58 2016 +0200

    undo: Allow undo/redo layer moving
    
    Differential Revision: https://phabricator.freedesktop.org/D961

 pitivi/timeline/timeline.py     |   32 +++++++++++----------
 pitivi/undo/timeline.py         |   32 +++++++++++++++++++++
 tests/common.py                 |    1 +
 tests/test_timeline_timeline.py |    2 +-
 tests/test_undo_timeline.py     |   58 ++++++++++++++++++++++++++++++---------
 5 files changed, 96 insertions(+), 29 deletions(-)
---
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index a685bdd..fb28a1d 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -40,6 +40,7 @@ from pitivi.timeline.elements import TrimHandle
 from pitivi.timeline.layer import Layer
 from pitivi.timeline.layer import LayerControls
 from pitivi.timeline.ruler import ScaleRuler
+from pitivi.undo.timeline import CommitTimelineFinalizingAction
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.timeline import EditingContext
 from pitivi.utils.timeline import SELECT
@@ -257,9 +258,9 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         # A lot of operations go through these callbacks.
         self.add_events(Gdk.EventType.BUTTON_PRESS | Gdk.EventType.BUTTON_RELEASE)
-        self.connect("button-press-event", self.__buttonPressEventCb)
-        self.connect("button-release-event", self.__buttonReleaseEventCb)
-        self.connect("motion-notify-event", self.__motionNotifyEventCb)
+        self.connect("button-press-event", self._button_press_event_cb)
+        self.connect("button-release-event", self._button_release_event_cb)
+        self.connect("motion-notify-event", self._motion_notify_event_cb)
 
         self._layers = []
         # Whether the user is dragging a layer.
@@ -318,11 +319,11 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         self.info("Faking %s", event)
         if event.type == Gdk.EventType.BUTTON_PRESS:
-            self.__buttonPressEventCb(self, event)
+            self._button_press_event_cb(self, event)
         elif event.type == Gdk.EventType.BUTTON_RELEASE:
-            self.__buttonReleaseEventCb(self, event)
+            self._button_release_event_cb(self, event)
         elif event.type == Gdk.EventType.MOTION_NOTIFY:
-            self.__motionNotifyEventCb(self, event)
+            self._motion_notify_event_cb(self, event)
         else:
             self.parent.sendFakeEvent(event)
 
@@ -609,7 +610,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         sources = self.get_sources_at_position(self.__last_position)
         self.app.gui.viewer.overlay_stack.set_current_sources(sources)
 
-    def __buttonPressEventCb(self, unused_widget, event):
+    def _button_press_event_cb(self, unused_widget, event):
         self.debug("PRESSED %s", event)
         self.app.gui.focusTimeline()
 
@@ -629,6 +630,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
                 layer_controls = self._getParentOfType(event_widget, LayerControls)
                 if layer_controls:
                     self.__moving_layer = layer_controls.ges_layer
+                    self.app.action_log.begin("move layer",
+                                              CommitTimelineFinalizingAction(self._project.pipeline))
                 else:
                     self.__marquee.setStartPosition(event)
 
@@ -646,7 +649,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         return False
 
-    def __buttonReleaseEventCb(self, unused_widget, event):
+    def _button_release_event_cb(self, unused_widget, event):
         allow_seek = not self.__got_dragged
 
         res, button = event.get_button()
@@ -670,7 +673,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         return False
 
-    def __motionNotifyEventCb(self, unused_widget, event):
+    def _motion_notify_event_cb(self, unused_widget, event):
         if self.draggingElement:
             if type(self.draggingElement) == TransitionClip and \
                     not self.__clickedHandle:
@@ -690,7 +693,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         elif self.__moving_layer:
             event_widget = self.get_event_widget(event)
             unused_x, y = event_widget.translate_coordinates(self, event.x, event.y)
-            layer, unused_on_sep = self._getLayerAt(
+            layer, unused_on_sep = self._get_layer_at(
                 y, prefer_ges_layer=self.__moving_layer,
                 past_middle_when_adjacent=True)
             if layer != self.__moving_layer:
@@ -759,7 +762,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
             else:
                 clip_duration = asset.get_duration()
 
-            ges_layer, unused_on_sep = self._getLayerAt(y)
+            ges_layer, unused_on_sep = self._get_layer_at(y)
             if not placement:
                 placement = self.pixelToNs(x)
             placement = max(0, placement)
@@ -889,8 +892,6 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         for i, layer in enumerate(layers):
             layer.set_priority(i)
 
-        self._project.setModificationState(True)
-
     def _addLayer(self, ges_layer):
         layer = Layer(ges_layer, self)
         ges_layer.ui = layer
@@ -969,7 +970,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
     def __layerGetSeps(self, ges_layer, sep_name):
         return [getattr(ges_layer.ui, sep_name), getattr(ges_layer.control_ui, sep_name)]
 
-    def _getLayerAt(self, y, prefer_ges_layer=None, past_middle_when_adjacent=False):
+    def _get_layer_at(self, y, prefer_ges_layer=None, past_middle_when_adjacent=False):
         """ Used in the testsuite """
         ges_layers = self.ges_timeline.get_layers()
         if y < 20:
@@ -1065,7 +1066,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
             position = self.pixelToNs(x - self.__drag_start_x)
 
         self._setSeparatorsPrelight(False)
-        res = self._getLayerAt(y, prefer_ges_layer=self._on_layer)
+        res = self._get_layer_at(y, prefer_ges_layer=self._on_layer)
         self._on_layer, self.__on_separators = res
         if (mode != GES.EditMode.EDIT_NORMAL or
                 self.current_group.props.height > 1):
@@ -1147,6 +1148,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self.queue_draw()
 
     def __endMovingLayer(self):
+        self.app.action_log.commit("move layer")
         self._project.pipeline.commit_timeline()
         self.__moving_layer = None
 
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index 8c8558c..3ae2ace 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -419,6 +419,26 @@ class LayerRemoved(UndoableAction):
         return st
 
 
+class LayerMoved(UndoableAction):
+
+    def __init__(self, ges_layer, old_priority, priority):
+        UndoableAction.__init__(self)
+        self.ges_layer = ges_layer
+        self.old_priority = old_priority
+        self.priority = priority
+
+    def do(self):
+        self.ges_layer.props.priority = self.priority
+
+    def undo(self):
+        self.ges_layer.props.priority = self.old_priority
+
+    def asScenarioAction(self):
+        st = Gst.Structure.new_empty("move-layer")
+        st.set_value("priority", self.ges_layer.props.priority)
+        return st
+
+
 class ControlSourceValueAdded(UndoableAction):
 
     def __init__(self, track_element,
@@ -534,6 +554,7 @@ class TimelineObserver(Loggable):
         self.clip_property_trackers = {}
         self.control_source_keyframe_trackers = {}
         self.children_props_tracker = TrackElementChildPropertyTracker(self.action_log)
+        self._layers_priorities = {}
 
     def startObserving(self, ges_timeline):
         """Starts monitoring the specified timeline.
@@ -552,12 +573,16 @@ class TimelineObserver(Loggable):
                 self._connectToClip(ges_clip)
 
     def _connect_to_layer(self, ges_layer):
+        self._layers_priorities[ges_layer] = ges_layer.props.priority
         ges_layer.connect("clip-added", self._clipAddedCb)
         ges_layer.connect("clip-removed", self._clipRemovedCb)
+        ges_layer.connect("notify::priority", self._layer_moved_cb)
 
     def _disconnect_from_layer(self, ges_layer):
+        del self._layers_priorities[ges_layer]
         ges_layer.disconnect_by_func(self._clipAddedCb)
         ges_layer.disconnect_by_func(self._clipRemovedCb)
+        ges_layer.disconnect_by_func(self._layer_moved_cb)
 
     def _connectToClip(self, clip):
         tracker = ClipPropertyChangeTracker()
@@ -714,6 +739,13 @@ class TimelineObserver(Loggable):
                                               old_snapshot, new_snapshot)
         self.action_log.push(action)
 
+    def _layer_moved_cb(self, ges_layer, unused_param):
+        previous = self._layers_priorities[ges_layer]
+        current = ges_layer.props.priority
+        self._layers_priorities[ges_layer] = current
+        action = LayerMoved(ges_layer, previous, current)
+        self.action_log.push(action)
+
     def _layerAddedCb(self, ges_timeline, ges_layer):
         self._connect_to_layer(ges_layer)
         action = LayerAdded(ges_timeline, ges_layer)
diff --git a/tests/common.py b/tests/common.py
index 4470a3c..bec78a4 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -83,6 +83,7 @@ def create_project():
 def create_pitivi(**settings):
     app = Pitivi()
     app._setup()
+    app.gui = mock.Mock()
     app.settings = __create_settings(**settings)
     return app
 
diff --git a/tests/test_timeline_timeline.py b/tests/test_timeline_timeline.py
index 3cfafdf..29e0717 100644
--- a/tests/test_timeline_timeline.py
+++ b/tests/test_timeline_timeline.py
@@ -117,7 +117,7 @@ class TestLayers(BaseTestTimeline):
         s = SEPARATOR_HEIGHT
 
         def assertLayerAt(ges_layer, y):
-            result = timeline._getLayerAt(
+            result = timeline._get_layer_at(
                 int(y),
                 prefer_ges_layer=preferred_ges_layer,
                 past_middle_when_adjacent=past_middle_when_adjacent)
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index 6570090..1607e4f 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -26,6 +26,8 @@ from gi.repository import GES
 from gi.repository import Gst
 from gi.repository import GstController
 
+from pitivi.timeline.timeline import Timeline
+from pitivi.undo.project import AssetAddedAction
 from pitivi.undo.timeline import ClipAdded
 from pitivi.undo.timeline import ClipPropertyChanged
 from pitivi.undo.timeline import ClipRemoved
@@ -87,14 +89,12 @@ class TestTimelineLogObserver(TestCase):
 class TestTimelineUndo(TestCase):
 
     def setUp(self):
-        app = common.create_pitivi()
-        app.project_manager.newBlankProject()
+        self.app = common.create_pitivi()
+        self.app.project_manager.newBlankProject()
 
-        self.timeline = app.project_manager.current_project.timeline
+        self.timeline = self.app.project_manager.current_project.timeline
         self.layer = self.timeline.append_layer()
-        self.action_log = UndoableActionLog()
-        self.observer = TimelineObserverSpy(self.action_log, app=mock.Mock())
-        self.observer.startObserving(self.timeline)
+        self.action_log = self.app.action_log
 
     def getTimelineClips(self):
         for layer in self.timeline.layers:
@@ -109,16 +109,48 @@ class TestTimelineUndo(TestCase):
         layer1 = self.layer
         layer2 = self.timeline.append_layer()
         layer3 = self.timeline.append_layer()
-        self.assertEqual([layer1, layer2, layer3], self.timeline.get_layers())
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer2, layer3])
 
         with self.action_log.started("layer removed"):
             self.timeline.remove_layer(layer2)
-        self.assertEqual([layer1, layer3], self.timeline.get_layers())
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer3])
 
         self.action_log.undo()
-        self.assertEqual([layer1, layer2, layer3], self.timeline.get_layers())
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer2, layer3])
         self.action_log.redo()
-        self.assertEqual([layer1, layer3], self.timeline.get_layers())
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer3])
+
+    def testLayerMoved(self):
+        layer1 = self.layer
+        layer2 = self.timeline.append_layer()
+        layer3 = self.timeline.append_layer()
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer2, layer3])
+
+        timeline_ui = Timeline(container=None, app=self.app)
+        timeline_ui.setProject(self.app.project_manager.current_project)
+
+        # Click and drag a layer control box to move the layer.
+        with mock.patch.object(timeline_ui, 'get_event_widget') as get_event_widget:
+            event = mock.Mock()
+            event.get_button.return_value = True, 1
+
+            get_event_widget.return_value = layer1.control_ui
+            timeline_ui._button_press_event_cb(None, event=event)
+
+            with mock.patch.object(layer1.control_ui, "translate_coordinates") as translate_coordinates:
+                translate_coordinates.return_value = (0, 0)
+                with mock.patch.object(timeline_ui, "_get_layer_at") as _get_layer_at:
+                    _get_layer_at.return_value = layer3, None
+                    timeline_ui._motion_notify_event_cb(None, event=event)
+
+            timeline_ui._button_release_event_cb(None, event=event)
+        self.assertEqual(self.timeline.get_layers(), [layer2, layer3, layer1])
+
+        self.action_log.undo()
+        self.assertEqual(self.timeline.get_layers(), [layer1, layer2, layer3])
+
+        self.action_log.redo()
+        self.assertEqual(self.timeline.get_layers(), [layer2, layer3, layer1])
 
     def testControlSourceValueAdded(self):
         uri = common.getSampleUri("tears_of_steel.webm")
@@ -179,9 +211,9 @@ class TestTimelineUndo(TestCase):
 
         self.assertEqual(1, len(stacks))
         stack = stacks[0]
-        self.assertEqual(1, len(stack.done_actions))
-        action = stack.done_actions[0]
-        self.assertTrue(isinstance(action, ClipAdded))
+        self.assertEqual(2, len(stack.done_actions), stack.done_actions)
+        self.assertTrue(isinstance(stack.done_actions[0], ClipAdded))
+        self.assertTrue(isinstance(stack.done_actions[1], AssetAddedAction))
         self.assertTrue(clip1 in self.getTimelineClips())
 
         self.action_log.undo()


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