[pitivi] undo: Allow undo/redo layer moving
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] undo: Allow undo/redo layer moving
- Date: Sat, 30 Apr 2016 16:04:27 +0000 (UTC)
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]