Commit dd262c24 authored by swayne's avatar swayne Committed by Alexandru Băluț

timeline: Removed current_group

Instead of keeping the current_group and the Selection object in sync,
we now use only the Selection object.

When a clip was removed from its layer, the layer was taking care of
removing it from the Selection object. This is not the case anymore,
as it simplifies the drag&drop logic. Previously as clips were dragged
from one layer to another, they were being removed from the Selection
object. Now the Selection object is stable and the logic is simpler.

Fixes #2288
parent f3db09ab
Pipeline #91603 failed with stages
in 94 minutes and 26 seconds
......@@ -49,7 +49,6 @@ The interesting part is the `__clicked_cb` callback method, which is connected t
timeline = self.app.gui.editor.timeline_ui.timeline
clips = sorted(timeline.selection, key=lambda x : x.get_start())
if len(clips) >= 2:
timeline.current_group.ungroup(False) # https://gitlab.gnome.org/GNOME/pitivi/issues/2288
previous = clips[0]
for clip in clips[1:]:
clip.set_start(previous.get_start() + previous.get_duration())
......@@ -72,4 +71,4 @@ The plugin can be improved by:
- Disabling the button automatically when `timeline.selection` contains less than two clips.
- Making it work for the entire timeline in case there is no selection.
Check out the Pitivi code and the [GES API](http://lazka.github.io/pgi-docs/#GES-1.0) to see what can be done, and tell us what you are hacking!
\ No newline at end of file
Check out the Pitivi code and the [GES API](http://lazka.github.io/pgi-docs/#GES-1.0) to see what can be done, and tell us what you are hacking!
......@@ -1124,8 +1124,6 @@ class Clip(Gtk.EventBox, Zoomable, Loggable):
if target.name() == EFFECT_TARGET_ENTRY.target:
self.info("Adding effect %s", self.timeline.dropData)
self.timeline.resetSelectionGroup()
self.timeline.current_group.add(self.ges_clip)
self.timeline.selection.setSelection([self.ges_clip], SELECT)
self.app.gui.editor.switchContextTab(self.ges_clip)
......@@ -1256,17 +1254,11 @@ class Clip(Gtk.EventBox, Zoomable, Loggable):
if self.timeline.get_parent()._controlMask:
if not self.get_state_flags() & Gtk.StateFlags.SELECTED:
mode = SELECT_ADD
self.timeline.current_group.add(
self.ges_clip.get_toplevel_parent())
else:
self.timeline.current_group.remove(
self.ges_clip.get_toplevel_parent())
mode = UNSELECT
clicked_layer, click_pos = self.timeline.get_clicked_layer_and_pos(event)
self.timeline.set_selection_meta_info(clicked_layer, click_pos, mode)
else:
self.timeline.resetSelectionGroup()
self.timeline.current_group.add(self.ges_clip.get_toplevel_parent())
self.app.gui.editor.switchContextTab(self.ges_clip)
parent = self.ges_clip.get_toplevel_parent()
......
......@@ -377,8 +377,6 @@ class Layer(Gtk.Layout, Zoomable, Loggable):
ges_clip.disconnect_by_func(self._childAddedToClipCb)
ges_clip.disconnect_by_func(self._childRemovedFromClipCb)
self.timeline.selection.unselect([ges_clip])
def updatePosition(self):
for ges_clip in self.ges_layer.get_clips():
if hasattr(ges_clip, "ui"):
......
......@@ -367,16 +367,16 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
# Clip selection.
self.selection = Selection()
self.current_group = None
# The last layer where the user clicked.
self.last_clicked_layer = None
# Position where the user last clicked.
self.last_click_pos = 0
self.resetSelectionGroup()
# Clip editing.
# Which clip is being edited.
# Which clip widget is being edited.
self.draggingElement = None
# The GES.Group in case there are one or more clips being dragged.
self.dragging_group = None
# Which handle of the draggingElement has been clicked, if any.
# If set, it means we are in a trim operation.
self.__clickedHandle = None
......@@ -445,14 +445,6 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
return media_types
def resetSelectionGroup(self):
self.debug("Reset selection group")
if self.current_group:
self.current_group.ungroup(recursive=False)
self.current_group = GES.Group()
self.current_group.props.serialize = False
def setProject(self, project):
"""Connects to the GES.Timeline holding the project."""
# Avoid starting/closing preview generation like crazy while tearing down project
......@@ -709,6 +701,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
self.__next_seek_position = next_seek_position
def _button_press_event_cb(self, unused_widget, event):
"""Handles a mouse button press event."""
self.debug("PRESSED %s", event)
self.app.gui.editor.focusTimeline()
......@@ -721,9 +714,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
self.__clickedHandle = event_widget
self.debug("Dragging element is %s", self.draggingElement)
if self.draggingElement is not None:
if self.draggingElement:
self.__drag_start_x = event.x
self._on_layer = self.draggingElement.layer.ges_layer
self.dragging_group = self.selection.group()
else:
layer_controls = self._getParentOfType(event_widget, LayerControls)
if layer_controls:
......@@ -746,8 +740,6 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
self._scroll_start_x = event.x
self._scroll_start_y = event.y
return False
def _button_release_event_cb(self, unused_widget, event):
allow_seek = not self.__got_dragged
......@@ -781,13 +773,10 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
clicked_layer, click_pos = self.get_clicked_layer_and_pos(event)
self.set_selection_meta_info(clicked_layer, click_pos, SELECT)
else:
self.resetSelectionGroup()
last_click_pos = self.last_click_pos
cur_clicked_layer, cur_click_pos = self.get_clicked_layer_and_pos(event)
clips = self.get_clips_in_between(
last_clicked_layer, cur_clicked_layer, last_click_pos, cur_click_pos)
for clip in clips:
self.current_group.add(clip.get_toplevel_parent())
self.selection.setSelection(clips, SELECT)
elif not self.get_parent()._controlMask:
clicked_layer, click_pos = self.get_clicked_layer_and_pos(event)
......@@ -838,7 +827,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
# Also include those clips which are grouped with currently selected clips.
for clip in clips:
toplevel = clip.get_toplevel_parent()
if isinstance(toplevel, GES.Group) and toplevel != self.current_group:
if isinstance(toplevel, GES.Group):
grouped_clips.update([c for c in toplevel.get_children(True)
if isinstance(c, GES.Clip)])
......@@ -895,11 +884,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
self.vadj.set_value(self.vadj.get_value() + y_diff)
def _selectUnderMarquee(self):
self.resetSelectionGroup()
if self.layout.marquee.props.width_request > 0:
clips = self.layout.marquee.find_clips()
for clip in clips:
self.current_group.add(clip.get_toplevel_parent())
else:
clips = []
self.selection.setSelection(clips, SELECT)
......@@ -919,12 +905,13 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
"""
placement = 0
self.draggingElement = None
self.resetSelectionGroup()
self.selection.setSelection([], SELECT)
assets = self._project.assetsForUris(self.dropData)
if not assets:
self._project.addUris(self.dropData)
return False
ges_clips = []
for asset in assets:
if asset.is_image():
clip_duration = self.app.settings.imageClipLength * \
......@@ -945,17 +932,21 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
clip_duration,
asset.get_supported_formats())
placement += clip_duration
self.current_group.add(ges_clip.get_toplevel_parent())
self.selection.setSelection([], SELECT_ADD)
ges_clip.first_placement = True
self._project.pipeline.commit_timeline()
if not self.draggingElement:
self.draggingElement = ges_clip.ui
self._on_layer = ges_layer
ges_clips.append(ges_clip)
if ges_clips:
ges_clip = ges_clips[0]
self.draggingElement = ges_clip.ui
self._on_layer = ges_layer
self.dropping_clips = True
self.selection.setSelection(ges_clips, SELECT)
self.dragging_group = self.selection.group()
return True
def _drag_motion_cb(self, widget, context, x, y, timestamp):
......@@ -985,19 +976,19 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
target = self.drag_dest_find_target(context, None)
if self.draggingElement:
self.__last_clips_on_leave = [(clip.get_layer(), clip)
for clip in self.current_group.get_children(False)]
for clip in self.dragging_group.get_children(False)]
self.dropDataReady = False
if self.dropping_clips:
clips = self.current_group.get_children(False)
self.resetSelectionGroup()
self.selection.setSelection([], SELECT)
for clip in clips:
for clip in self.dragging_group.get_children(False):
clip.get_layer().remove_clip(clip)
self._project.pipeline.commit_timeline()
self.draggingElement = None
self.__got_dragged = False
self.dropping_clips = False
self.dragging_group.ungroup(recursive=False)
self.dragging_group = None
elif target == URI_TARGET_ENTRY.target:
self.cleanDropData()
......@@ -1325,7 +1316,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
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):
self.dragging_group.props.height > 1):
# When dragging clips from more than one layer, do not allow
# them to be dragged between layers to create a new layer.
self.__on_separators = []
......@@ -1374,6 +1365,9 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
self.editing_context.finish()
self.draggingElement = None
if self.dragging_group is not None:
self.dragging_group.ungroup(recursive=False)
self.dragging_group = None
self.__clickedHandle = None
self.__got_dragged = False
self.editing_context = None
......@@ -1761,7 +1755,6 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
def _deleteSelected(self, unused_action, unused_parameter):
if self.ges_timeline:
self.timeline.resetSelectionGroup()
with Previewer.manager.paused():
with self.app.action_log.started("delete clip",
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
......@@ -1824,13 +1817,11 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
with self.app.action_log.started("ungroup",
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
toplevel=True):
for obj in self.timeline.selection:
toplevel = obj.get_toplevel_parent()
if toplevel == self.timeline.current_group:
for child in toplevel.get_children(False):
child.ungroup(recursive=False)
toplevels = self.timeline.selection.toplevels
for toplevel in toplevels:
if isinstance(toplevel, GES.Group) or len(toplevels) == 1:
toplevel.ungroup(recursive=False)
self.timeline.resetSelectionGroup()
self.timeline.selection.setSelection([], SELECT)
def _group_selected_cb(self, unused_action, unused_parameter):
......@@ -1841,24 +1832,9 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
with self.app.action_log.started("group",
finalizing_action=CommitTimelineFinalizingAction(self._project.pipeline),
toplevel=True):
containers = set()
new_group = None
for obj in self.timeline.selection:
toplevel = obj.get_toplevel_parent()
if toplevel == self.timeline.current_group:
for child in toplevel.get_children(False):
containers.add(child)
toplevel.ungroup(False)
else:
containers.add(toplevel)
if containers:
new_group = GES.Container.group(list(containers))
self.timeline.resetSelectionGroup()
if new_group:
self.timeline.current_group.add(new_group)
toplevels = self.timeline.selection.toplevels
if toplevels:
GES.Container.group(list(toplevels))
# timeline.selection doesn't change during grouping,
# we need to manually update group actions.
......@@ -1866,9 +1842,13 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.updateActions()
def __copyClipsCb(self, unused_action, unused_parameter):
if self.timeline.current_group:
self.__copied_group = self.timeline.current_group.copy(True)
self.updateActions()
group = self.timeline.selection.group()
try:
self.__copied_group = group.copy(deep=True)
self.__copied_group.props.serialize = False
finally:
group.ungroup(recursive=False)
self.updateActions()
def __pasteClipsCb(self, unused_action, unused_parameter):
if not self.__copied_group:
......@@ -1880,8 +1860,11 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
toplevel=True):
position = self._project.pipeline.getPosition()
copied_group_shallow_copy = self.__copied_group.paste(position)
self.__copied_group = copied_group_shallow_copy.copy(True)
copied_group_shallow_copy.ungroup(recursive=False)
try:
self.__copied_group = copied_group_shallow_copy.copy(True)
self.__copied_group.props.serialize = False
finally:
copied_group_shallow_copy.ungroup(recursive=False)
def __add_layer_cb(self, unused_action, unused_parameter):
with self.app.action_log.started("add layer",
......
......@@ -262,8 +262,6 @@ class TitleEditor(Loggable):
assert res
# Select it so the Title editor becomes active.
self._selection.setSelection([title_clip], SELECT)
self.app.gui.editor.timeline_ui.timeline.resetSelectionGroup()
self.app.gui.editor.timeline_ui.timeline.current_group.add(title_clip)
def _propertyChangedCb(self, source, unused_gstelement, pspec):
if self._setting_props:
......
......@@ -143,11 +143,14 @@ class Selection(GObject.Object, Loggable):
def set_can_group_ungroup(self):
can_ungroup = False
toplevels = self.toplevels
for toplevel in toplevels:
if isinstance(toplevel, GES.Group) or len(toplevel.get_children(False)) > 1:
can_ungroup = True
break
self.can_group = len(toplevels) > 1
# If we allow grouping, we disallow ungrouping.
if not self.can_group:
for toplevel in toplevels:
if (isinstance(toplevel, GES.Group) or
(isinstance(toplevel, GES.Container) and len(toplevel.get_children(False)) > 1)):
can_ungroup = True
break
self.can_ungroup = can_ungroup and not self.can_group
def __get_selection_changes(self, old_selection):
......@@ -212,6 +215,27 @@ class Selection(GObject.Object, Loggable):
for child in self.__serializable_toplevels(element):
yield child
def group(self):
"""Groups the serializable toplevel elements into a new GES.Group.
Returns:
GES.Group: The serializable elements which have no parent or
have only non-serializable ancestors.
"""
toplevels = set()
for obj in self.selected:
if not obj.timeline:
# The element has been removed from the timeline. Ignore it.
continue
toplevel = obj.get_toplevel_parent()
toplevels.add(toplevel)
group = GES.Group()
group.props.serialize = False
for toplevel in toplevels:
group.add(toplevel)
return group
def __len__(self):
return len(self.selected)
......
......@@ -256,6 +256,7 @@ class TestCase(unittest.TestCase, Loggable):
with mock.patch.object(ges_clip.ui.timeline, "_get_layer_at") as _get_layer_at:
_get_layer_at.return_value = ges_clip.props.layer, None
ges_clip.ui._button_release_event_cb(None, event)
ges_clip.ui.timeline._button_release_event_cb(None, event)
self.assertEqual(bool(ges_clip.ui.get_state_flags() & Gtk.StateFlags.SELECTED),
expect_selected)
......
......@@ -333,25 +333,18 @@ class TestGrouping(BaseTestTimeline):
# Select the 2 clips
for clip in clips:
self.assertIsNone(clip.get_parent())
self.toggle_clip_selection(clip, expect_selected=True)
before_grouping_timeline_group = timeline.current_group
for clip in clips:
self.assertEqual(clip.get_parent(), timeline.current_group)
timeline_container.group_action.emit("activate", None)
self.assertNotEqual(timeline.current_group, before_grouping_timeline_group)
for clip in clips:
# Check that we created a new group and that this group is not
# the timeline current_group
# Check that we created a new group
self.assertTrue(isinstance(clip.get_parent(), GES.Group))
self.assertNotEqual(clip.get_parent(), timeline.current_group)
# The newly created group has been selected
self.assertEqual(clip.get_toplevel_parent(), timeline.current_group)
for selected_clip in timeline.selection:
self.assertEqual(clip.get_toplevel_parent(), selected_clip.get_toplevel_parent())
for clip in clips:
self.assertEqual(clips[0].get_parent(), clip.get_parent())
self.assertTrue(bool(clip.ui.get_state_flags() & Gtk.StateFlags.SELECTED))
self.assertTrue(clip.selected.selected)
......@@ -683,7 +676,6 @@ class TestShiftSelection(BaseTestTimeline):
timeline._button_release_event_cb(None, event)
self.__check_selected([ges_clip1, ges_clip2], [ges_clip3, ges_clip4])
self.__reset_clips_selection(timeline)
timeline.resetSelectionGroup()
# Simiulate shift+click before first and after fourth clip.
timeline.get_clicked_layer_and_pos.return_value = (ges_layer, 1 * Gst.SECOND)
......@@ -692,7 +684,6 @@ class TestShiftSelection(BaseTestTimeline):
timeline._button_release_event_cb(None, event)
self.__check_selected([ges_clip1, ges_clip2, ges_clip3, ges_clip4], [])
self.__reset_clips_selection(timeline)
timeline.resetSelectionGroup()
# Simiulate shift+click on first, after fourth and before third clip.
timeline.get_clicked_layer_and_pos.return_value = (ges_layer, 6 * Gst.SECOND)
......@@ -703,7 +694,6 @@ class TestShiftSelection(BaseTestTimeline):
timeline._button_release_event_cb(None, event)
self.__check_selected([ges_clip1, ges_clip2], [ges_clip3, ges_clip4])
self.__reset_clips_selection(timeline)
timeline.resetSelectionGroup()
# Simulate shift+click twice on the same clip.
timeline.get_clicked_layer_and_pos.return_value = (ges_layer, 6 * Gst.SECOND)
......@@ -759,7 +749,6 @@ class TestShiftSelection(BaseTestTimeline):
self.__check_selected([ges_clip11, ges_clip12, ges_clip22, ges_clip23],
[ges_clip13, ges_clip21, ges_clip31, ges_clip32, ges_clip33])
self.__reset_clips_selection(timeline)
timeline.resetSelectionGroup()
timeline.get_clicked_layer_and_pos.return_value = (ges_layer1, 3 * Gst.SECOND)
timeline._button_release_event_cb(None, event)
......
......@@ -199,9 +199,6 @@ class TestTimelineObserver(BaseTestUndoTimeline):
self.assertEqual(len(clips[1].get_children(False)), 1)
timeline.selection.select(clips)
timeline.resetSelectionGroup()
for clip in clips:
timeline.current_group.add(clip)
self.timeline_container.group_action.activate(None)
clips = list(self.getTimelineClips())
self.assertEqual(len(clips), 1, clips)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment