[pitivi] undo: Support grouping and ungrouping
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] undo: Support grouping and ungrouping
- Date: Wed, 14 Sep 2016 09:57:31 +0000 (UTC)
commit 88dfc987fbd0a2d0cf84925408cb67b4e0d97699
Author: Alexandru Băluț <alexandru balut gmail com>
Date: Sun Sep 4 02:10:46 2016 +0200
undo: Support grouping and ungrouping
Fixes https://phabricator.freedesktop.org/T7466
Differential Revision: https://phabricator.freedesktop.org/D1297
pitivi/timeline/timeline.py | 12 +++---
pitivi/undo/timeline.py | 90 +++++++++++++++++++++++++++++++++++++++++--
pitivi/undo/undo.py | 2 +-
tests/test_undo_timeline.py | 55 ++++++++++++++++++++++++++
4 files changed, 148 insertions(+), 11 deletions(-)
---
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 59c9689..d1fe861 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -331,7 +331,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
def resetSelectionGroup(self):
self.debug("Reset selection group")
if self.current_group:
- GES.Container.ungroup(self.current_group, False)
+ self.current_group.ungroup(recursive=False)
self.current_group = GES.Group()
self.current_group.props.serialize = False
@@ -1400,13 +1400,13 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
_("Delete selected clips"))
self.group_action = Gio.SimpleAction.new("group-selected-clips", None)
- self.group_action.connect("activate", self._groupSelected)
+ self.group_action.connect("activate", self._group_selected_cb)
group.add_action(self.group_action)
self.app.shortcuts.add("timeline.group-selected-clips", ["<Control>g"],
_("Group selected clips together"))
self.ungroup_action = Gio.SimpleAction.new("ungroup-selected-clips", None)
- self.ungroup_action.connect("activate", self._ungroupSelected)
+ self.ungroup_action.connect("activate", self._ungroup_selected_cb)
group.add_action(self.ungroup_action)
self.app.shortcuts.add("timeline.ungroup-selected-clips", ["<Shift><Control>g"],
_("Ungroup selected clips"))
@@ -1577,7 +1577,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.timeline.selection.setSelection([], SELECT)
- def _ungroupSelected(self, unused_action, unused_parameter):
+ def _ungroup_selected_cb(self, unused_action, unused_parameter):
if not self.ges_timeline:
self.info("No ges_timeline set yet!")
return
@@ -1588,12 +1588,12 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
toplevel = obj.get_toplevel_parent()
if toplevel == self.timeline.current_group:
for child in toplevel.get_children(False):
- child.ungroup(False)
+ child.ungroup(recursive=False)
self.timeline.resetSelectionGroup()
self.timeline.selection.setSelection([], SELECT)
- def _groupSelected(self, unused_action, unused_parameter):
+ def _group_selected_cb(self, unused_action, unused_parameter):
if not self.ges_timeline:
self.info("No timeline set yet?")
return
diff --git a/pitivi/undo/timeline.py b/pitivi/undo/timeline.py
index 8d18d16..6864eb6 100644
--- a/pitivi/undo/timeline.py
+++ b/pitivi/undo/timeline.py
@@ -318,7 +318,7 @@ class TransitionClipAction(UndoableAction):
@staticmethod
def get_video_element(ges_clip):
- for track_element in ges_clip.get_children(True):
+ for track_element in ges_clip.get_children(recursive=True):
if isinstance(track_element, GES.VideoTransition):
return track_element
return None
@@ -385,7 +385,6 @@ class TransitionClipRemovedAction(TransitionClipAction):
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 \
@@ -591,7 +590,7 @@ class LayerObserver(MetaContainerObserver, Loggable):
ges_clip.connect("child-added", self._clipTrackElementAddedCb)
ges_clip.connect("child-removed", self._clipTrackElementRemovedCb)
- for track_element in ges_clip.get_children(True):
+ for track_element in ges_clip.get_children(recursive=True):
self._connectToTrackElement(track_element)
if isinstance(ges_clip, GES.TransitionClip):
@@ -605,7 +604,7 @@ class LayerObserver(MetaContainerObserver, Loggable):
ges_clip.disconnect_by_func(self._clipTrackElementAddedCb)
ges_clip.disconnect_by_func(self._clipTrackElementRemovedCb)
- for child in ges_clip.get_children(True):
+ for child in ges_clip.get_children(recursive=True):
self._disconnectFromTrackElement(child)
if isinstance(ges_clip, GES.TransitionClip):
@@ -706,6 +705,62 @@ class LayerObserver(MetaContainerObserver, Loggable):
self.priority = current
+class TimelineElementAddedToGroup(UndoableAction):
+
+ def __init__(self, ges_group, ges_timeline_element):
+ UndoableAction.__init__(self)
+ self.ges_group = ges_group
+ self.ges_timeline_element = ges_timeline_element
+
+ def do(self):
+ self.ges_group.add(self.ges_timeline_element)
+
+ def undo(self):
+ self.ges_group.remove(self.ges_timeline_element)
+
+
+class TimelineElementRemovedFromGroup(UndoableAction):
+
+ def __init__(self, ges_group, ges_timeline_element):
+ UndoableAction.__init__(self)
+ self.ges_group = ges_group
+ self.ges_timeline_element = ges_timeline_element
+
+ def do(self):
+ self.ges_group.remove(self.ges_timeline_element)
+
+ def undo(self):
+ self.ges_group.add(self.ges_timeline_element)
+
+
+class GroupObserver(Loggable):
+ """Monitors a Group and reports UndoableActions.
+
+ Args:
+ ges_group (GES.Group): The group to observe.
+
+ Attributes:
+ action_log (UndoableActionLog): The action log where to report actions.
+ """
+
+ def __init__(self, ges_group, action_log):
+ Loggable.__init__(self)
+ self.log("INIT %s", ges_group)
+ self.ges_group = ges_group
+ self.action_log = action_log
+
+ ges_group.connect_after("child-added", self.__child_added_cb)
+ ges_group.connect("child-removed", self.__child_removed_cb)
+
+ def __child_added_cb(self, ges_group, ges_timeline_element):
+ action = TimelineElementAddedToGroup(ges_group, ges_timeline_element)
+ self.action_log.push(action)
+
+ def __child_removed_cb(self, ges_group, ges_timeline_element):
+ action = TimelineElementRemovedFromGroup(ges_group, ges_timeline_element)
+ self.action_log.push(action)
+
+
class TimelineObserver(Loggable):
"""Monitors a project's timeline and reports UndoableActions.
@@ -720,12 +775,20 @@ class TimelineObserver(Loggable):
self.action_log = action_log
self.layer_observers = {}
+ self.group_observers = {}
for ges_layer in ges_timeline.get_layers():
self._connect_to_layer(ges_layer)
ges_timeline.connect("layer-added", self.__layer_added_cb)
ges_timeline.connect("layer-removed", self.__layer_removed_cb)
+ for ges_group in ges_timeline.get_groups():
+ self._connect_to_group(ges_group)
+
+ ges_timeline.connect("group-added", self.__group_added_cb)
+ # We don't care about the group-removed signal because this greatly
+ # simplifies the logic.
+
def __layer_added_cb(self, ges_timeline, ges_layer):
self._connect_to_layer(ges_layer)
@@ -738,3 +801,22 @@ class TimelineObserver(Loggable):
def __layer_removed_cb(self, ges_timeline, ges_layer):
action = LayerRemoved(ges_timeline, ges_layer)
self.action_log.push(action)
+
+ def _connect_to_group(self, ges_group):
+ if not ges_group.props.serialize:
+ return
+
+ # A group is added when it gets its first element, thus
+ # when undoing/redoing a group can be added multiple times.
+ # This is the only complexity caused by the fact that we keep alive
+ # all the GroupObservers which have been created.
+ if ges_group not in self.group_observers:
+ group_observer = GroupObserver(ges_group, self.action_log)
+ self.group_observers[ges_group] = group_observer
+
+ def __group_added_cb(self, unused_ges_timeline, ges_group):
+ self._connect_to_group(ges_group)
+ # This should be a single clip.
+ for ges_clip in ges_group.get_children(recursive=False):
+ action = TimelineElementAddedToGroup(ges_group, ges_clip)
+ self.action_log.push(action)
diff --git a/pitivi/undo/undo.py b/pitivi/undo/undo.py
index 5c3d30e..625480e 100644
--- a/pitivi/undo/undo.py
+++ b/pitivi/undo/undo.py
@@ -220,7 +220,7 @@ class UndoableActionLog(GObject.Object, Loggable):
self.emit("pre-push", action)
if self.running:
- self.debug("Ignore push because running")
+ self.debug("Ignore push because running: %s", action)
return
try:
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index 29e9105..70287e4 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -28,6 +28,7 @@ from gi.repository import Gtk
from pitivi.timeline.layer import Layer
from pitivi.timeline.timeline import Timeline
+from pitivi.timeline.timeline import TimelineContainer
from pitivi.undo.project import AssetAddedAction
from pitivi.undo.timeline import ClipAdded
from pitivi.undo.timeline import ClipRemoved
@@ -48,6 +49,15 @@ class BaseTestUndoTimeline(TestCase):
self.layer = self.timeline.append_layer()
self.action_log = self.app.action_log
+ def setup_timeline_container(self):
+ project = self.app.project_manager.current_project
+ self.timeline_container = TimelineContainer(self.app)
+ self.timeline_container.setProject(project)
+
+ timeline = self.timeline_container.timeline
+ timeline.app.project_manager.current_project = project
+ timeline.get_parent = mock.MagicMock(return_value=self.timeline_container)
+
def getTimelineClips(self):
for layer in self.timeline.layers:
for clip in layer.get_clips():
@@ -99,6 +109,51 @@ class TestTimelineObserver(BaseTestUndoTimeline):
self.assertEqual([l.props.priority for l in [layer1, layer3]],
list(range(2)))
+ def test_group_ungroup_clips(self):
+ self.setup_timeline_container()
+
+ clip1 = common.create_test_clip(GES.TitleClip)
+ clip1.set_start(0 * Gst.SECOND)
+ clip1.set_duration(1 * Gst.SECOND)
+
+ uri = common.get_sample_uri("tears_of_steel.webm")
+ asset = GES.UriClipAsset.request_sync(uri)
+ clip2 = asset.extract()
+
+ self.layer.add_clip(clip1)
+ self.layer.add_clip(clip2)
+ # The selection does not care about GES.Groups, only about GES.Clips.
+ self.timeline_container.timeline.selection.select([clip1, clip2])
+
+ self.timeline_container.group_action.activate(None)
+ self.assertTrue(isinstance(clip1.get_parent(), GES.Group))
+ self.assertEqual(clip1.get_parent(), clip2.get_parent())
+
+ self.timeline_container.ungroup_action.activate(None)
+ self.assertIsNone(clip1.get_parent())
+ self.assertIsNone(clip2.get_parent())
+
+ for i in range(4):
+ # Undo ungrouping.
+ self.action_log.undo()
+ self.assertTrue(isinstance(clip1.get_parent(), GES.Group))
+ self.assertEqual(clip1.get_parent(), clip2.get_parent())
+
+ # Undo grouping.
+ self.action_log.undo()
+ self.assertIsNone(clip1.get_parent())
+ self.assertIsNone(clip2.get_parent())
+
+ # Redo grouping.
+ self.action_log.redo()
+ self.assertTrue(isinstance(clip1.get_parent(), GES.Group))
+ self.assertEqual(clip1.get_parent(), clip2.get_parent())
+
+ # Redo ungrouping.
+ self.action_log.redo()
+ self.assertIsNone(clip1.get_parent())
+ self.assertIsNone(clip2.get_parent())
+
class TestLayerObserver(BaseTestUndoTimeline):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]