[pitivi] effects: Allow reordering the effects of a clip
- From: Thibault Saunier <tsaunier src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] effects: Allow reordering the effects of a clip
- Date: Wed, 24 Sep 2014 17:07:57 +0000 (UTC)
commit 8bc597f7ed671c1aecad57e67b6c79819d41b582
Author: Alexandru Băluț <alexandru balut gmail com>
Date: Fri May 30 22:58:06 2014 +0200
effects: Allow reordering the effects of a clip
pitivi/clipproperties.py | 143 +++++++++++++++++++++++++++++++++---------
pitivi/effects.py | 46 ++++----------
tests/Makefile.am | 1 +
tests/test_clipproperties.py | 48 ++++++++++++++
4 files changed, 174 insertions(+), 64 deletions(-)
---
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index 8d81036..861381a 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -20,6 +20,7 @@
# Boston, MA 02110-1301, USA.
import os
+import pickle
from gi.repository import Gtk
from gi.repository import Gdk
@@ -179,7 +180,14 @@ class EffectProperties(Gtk.Expander, Loggable):
Gtk.PolicyType.AUTOMATIC)
self.treeview_scrollwin.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
- self.storemodel = Gtk.ListStore(bool, str, str, str, str, object)
+ # We need to specify Gtk.TreeDragSource because otherwise we are hitting
+ # bug https://bugzilla.gnome.org/show_bug.cgi?id=730740.
+ class EffectsListStore(Gtk.ListStore, Gtk.TreeDragSource):
+ def do_drag_data_get(self, path, selection_data):
+ data = pickle.dumps(path.get_indices())
+ selection_data.set(Gdk.Atom.intern("pitivi/effect", False), 0, data)
+
+ self.storemodel = EffectsListStore(bool, str, str, str, str, object)
self.treeview = Gtk.TreeView(model=self.storemodel)
self.treeview_scrollwin.add(self.treeview)
self.treeview.set_property("has_tooltip", True)
@@ -211,10 +219,15 @@ class EffectProperties(Gtk.Expander, Loggable):
namecol.add_attribute(namecell, "text", COL_NAME_TEXT)
self.treeview.append_column(namecol)
- self.treeview.drag_dest_set(Gtk.DestDefaults.ALL,
- [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY)
+ # Allow the treeview to accept EFFECT_TARGET_ENTRY when drag&dropping.
+ self.treeview.enable_model_drag_dest([EFFECT_TARGET_ENTRY],
+ Gdk.DragAction.COPY)
+
+ # Enable reordering by drag&drop.
+ self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
+ [EFFECT_TARGET_ENTRY],
+ Gdk.DragAction.MOVE)
- self.treeview.drag_dest_add_text_targets()
self.selection = self.treeview.get_selection()
self._infobar = clip_properties.createInfoBar(
@@ -235,9 +248,9 @@ class EffectProperties(Gtk.Expander, Loggable):
# Connect all the widget signals
self.selection.connect("changed", self._treeviewSelectionChangedCb)
- self.treeview.connect("drag-leave", self._dragLeaveCb)
- self.treeview.connect("drag-drop", self._dragDropCb)
self.treeview.connect("drag-motion", self._dragMotionCb)
+ self.treeview.connect("drag-leave", self._dragLeaveCb)
+ self.treeview.connect("drag-data-received", self._dragDataReceivedCb)
self.treeview.connect("query-tooltip", self._treeViewQueryTooltipCb)
self._vcontent.connect("notify", self._vcontentNotifyCb)
removeEffectButton.connect("clicked", self._removeEffectCb)
@@ -314,32 +327,36 @@ class EffectProperties(Gtk.Expander, Loggable):
self._removeEffectConfigurationWidget()
self.effects_properties_manager.cleanCache(effect)
effect.get_parent().remove(effect)
- self._updateTreeview()
self.app.action_log.commit()
+ self._updateTreeview()
- def addEffectToClip(self, clip, bin_desc):
- media_type = self.app.effects.getFactoryFromName(bin_desc).media_type
-
+ def addEffectToClip(self, clip, factory_name, priority=None):
+ """Adds the specified effect if it can be applied to the clip."""
+ model = self.treeview.get_model()
+ media_type = self.app.effects.getFactoryFromName(factory_name).media_type
for track_element in clip.get_children(False):
track_type = track_element.get_track_type()
if track_type == GES.TrackType.AUDIO and media_type == AUDIO_EFFECT or \
track_type == GES.TrackType.VIDEO and media_type == VIDEO_EFFECT:
- effect = GES.Effect.new(bin_description=bin_desc)
+ # Actually add the effect
+ self.app.action_log.begin("add effect")
+ effect = GES.Effect.new(bin_description=factory_name)
clip.add(effect)
- self.updateAll()
+ if priority is not None and priority < len(model):
+ clip.set_top_effect_priority(effect, priority)
self.app.project_manager.current_project.timeline.commit()
self.app.action_log.commit()
self.app.project_manager.current_project.pipeline.flushSeek()
+ self.updateAll()
break
- def addEffectToCurrentSelection(self, bin_desc):
- if self.clips:
- # Trying to apply effect only on the first object of the selection
- clip = self.clips[0]
-
- # Checking that this effect can be applied on this track object
- # Which means, it has the corresponding media_type
- self.addEffectToClip(clip, bin_desc)
+ def addEffectToCurrentSelection(self, factory_name):
+ if not self.clips or len(self.clips) > 1:
+ return
+ clip = self.clips[0]
+ # Checking that this effect can be applied on this track object
+ # Which means, it has the corresponding media_type
+ self.addEffectToClip(clip, factory_name)
def _dragMotionCb(self, unused_tree_view, unused_drag_context, unused_x, unused_y, unused_timestamp):
self.debug("Something is being dragged in the clip properties' effects list")
@@ -349,9 +366,75 @@ class EffectProperties(Gtk.Expander, Loggable):
self.info("The item being dragged has left the clip properties' effects list")
self.drag_unhighlight()
- def _dragDropCb(self, unused_tree_view, unused_drag_context, unused_x, unused_y, unused_timestamp):
- self.info("An item has been dropped onto the clip properties' effects list")
- self.addEffectToCurrentSelection(self.app.gui.effectlist.getSelectedItems())
+ def _dragDataReceivedCb(self, treeview, drag_context, x, y, selection_data, unused_info, timestamp):
+ if not self.clips or len(self.clips) > 1:
+ # Indicate that a drop will not be accepted.
+ Gdk.drag_status(drag_context, 0, timestamp)
+ return
+ clip = self.clips[0]
+ model = treeview.get_model()
+ if drag_context.get_suggested_action() == Gdk.DragAction.COPY:
+ # An effect dragged probably from the effects list.
+ factory_name = str(selection_data.get_data(), "UTF-8")
+ # Target
+ dest_row = treeview.get_dest_row_at_pos(x, y)
+ if dest_row:
+ drop_path, drop_pos = dest_row
+ drop_index = drop_path.get_indices()[0]
+ if drop_pos != Gtk.TreeViewDropPosition.BEFORE:
+ drop_index += 1
+ else:
+ # This should happen when dragging after the last row.
+ drop_index = None
+ self.addEffectToClip(clip, factory_name, drop_index)
+ elif drag_context.get_suggested_action() == Gdk.DragAction.MOVE:
+ # An effect dragged from the same treeview to change its position.
+ # Source
+ source_indices = pickle.loads(selection_data.get_data())
+ source_index = source_indices[0]
+ # Target
+ dest_row = treeview.get_dest_row_at_pos(x, y)
+ if dest_row:
+ drop_path, drop_pos = dest_row
+ drop_index = drop_path.get_indices()[0]
+ drop_index = self.calculateEffectPriority(source_index, drop_index, drop_pos)
+ else:
+ # This should happen when dragging after the last row.
+ drop_index = len(model) - 1
+ drop_pos = Gtk.TreeViewDropPosition.INTO_OR_BEFORE
+ self.moveEffect(clip, source_index, drop_index)
+ drag_context.finish(True, False, timestamp)
+
+ def moveEffect(self, clip, source_index, drop_index):
+ if source_index == drop_index:
+ # Noop.
+ return
+ # The paths are different.
+ effects = clip.get_top_effects()
+ effect = effects[source_index]
+ self.app.action_log.begin("move effect")
+ clip.set_top_effect_priority(effect, drop_index)
+ self.app.project_manager.current_project.timeline.commit()
+ self.app.action_log.commit()
+ self.app.project_manager.current_project.pipeline.flushSeek()
+ new_path = Gtk.TreePath.new()
+ new_path.append_index(drop_index)
+ self.updateAll(path=new_path)
+
+ @staticmethod
+ def calculateEffectPriority(source_index, drop_index, drop_pos):
+ """
+ Return where the effect from source_index will end up
+ """
+ if drop_pos in (Gtk.TreeViewDropPosition.INTO_OR_BEFORE, Gtk.TreeViewDropPosition.INTO_OR_AFTER):
+ return drop_index
+ if drop_pos == Gtk.TreeViewDropPosition.BEFORE:
+ if source_index < drop_index:
+ return drop_index - 1
+ elif drop_pos == Gtk.TreeViewDropPosition.AFTER:
+ if source_index > drop_index:
+ return drop_index + 1
+ return drop_index
def _effectActiveToggleCb(self, cellrenderertoggle, path):
iter = self.storemodel.get_iter(path)
@@ -377,12 +460,13 @@ class EffectProperties(Gtk.Expander, Loggable):
tooltip.set_text("%s\n%s" % (bin_description, description))
return True
- def updateAll(self):
+ def updateAll(self, path=None):
if self.get_expanded():
if len(self.clips) == 1:
self._setEffectDragable()
self._updateTreeview()
- self._updateEffectConfigUi()
+ if path:
+ self.selection.select_path(path)
else:
self._removeEffectConfigurationWidget()
self.storemodel.clear()
@@ -393,9 +477,8 @@ class EffectProperties(Gtk.Expander, Loggable):
def _updateTreeview(self):
self.storemodel.clear()
-
- obj = self.clips[0]
- for effect in obj.get_top_effects():
+ clip = self.clips[0]
+ for effect in clip.get_top_effects():
if effect.props.bin_description in HIDDEN_EFFECTS:
continue
asset = self.app.effects.getFactoryFromName(
@@ -431,9 +514,9 @@ class EffectProperties(Gtk.Expander, Loggable):
if self._config_ui_h_pos is None:
self._config_ui_h_pos = self.app.gui.settings.mainWindowHeight // 3
- tree_iter = self.selection.get_selected()[1]
+ model, tree_iter = self.selection.get_selected()
if tree_iter:
- effect = self.storemodel.get_value(tree_iter, COL_TRACK_EFFECT)
+ effect = model.get_value(tree_iter, COL_TRACK_EFFECT)
self._showEffectConfigurationWidget(effect)
else:
self._removeEffectConfigurationWidget()
diff --git a/pitivi/effects.py b/pitivi/effects.py
index c82d26b..98e83f6 100644
--- a/pitivi/effects.py
+++ b/pitivi/effects.py
@@ -36,7 +36,6 @@ Effects global handling
"""
import re
import os
-import time
from gi.repository import GLib
from gi.repository import Gst
@@ -51,7 +50,7 @@ from pitivi.configure import get_ui_dir, get_pixmap_dir
from pitivi.settings import GlobalSettings
from pitivi.utils.loggable import Loggable
-from pitivi.utils.ui import SPACING, TYPE_PITIVI_EFFECT
+from pitivi.utils.ui import EFFECT_TARGET_ENTRY, SPACING
from pitivi.utils.widgets import GstElementSettingsWidget, FractionWidget
@@ -75,7 +74,7 @@ class EffectFactory():
"""
def __init__(self, effect_name, media_type, categories=[_("Uncategorized")],
human_name="", description="", icon=None):
- self.effec_tname = effect_name
+ self.effect_name = effect_name
self.media_type = media_type
self.categories = categories
self.description = description
@@ -407,17 +406,11 @@ class EffectListWidget(Gtk.VBox, Loggable):
self.view.append_column(icon_col)
self.view.append_column(text_col)
- self.view.drag_source_set(0, [], Gdk.DragAction.COPY)
- self.view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [("pitivi/effect", 0,
TYPE_PITIVI_EFFECT)], Gdk.DragAction.COPY)
- #self.view.drag_source_set_target_list([("pitivi/effect", 0, TYPE_PITIVI_EFFECT)])
- self.view.drag_source_set_target_list([])
- self.view.drag_source_add_uri_targets()
- self.view.drag_source_add_text_targets()
+ # Make the treeview a drag source which provides effects.
+ self.view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [EFFECT_TARGET_ENTRY],
Gdk.DragAction.COPY)
self.view.connect("button-press-event", self._buttonPressEventCb)
self.view.connect("select-cursor-row", self._enterPressEventCb)
- self.view.connect("drag-begin", self._dndDragBeginCb)
- self.view.connect("drag-end", self._dndDragEndCb)
self.view.connect("drag-data-get", self._dndDragDataGetCb)
scrollwin = Gtk.ScrolledWindow()
@@ -479,24 +472,10 @@ class EffectListWidget(Gtk.VBox, Loggable):
self.categoriesWidget.set_active(0)
- def _dndDragBeginCb(self, unused_view, context):
- self.info("Drag operation begun")
- model, paths = self.view.get_selection().get_selected_rows()
-
- if not paths:
- context.drag_abort(int(time.time()))
- return
-
- path = paths[0]
- pixbuf = model.get_value(model.get_iter(path), COL_ICON)
- if pixbuf:
- Gtk.drag_set_icon_pixbuf(context, pixbuf, 0, 0)
-
- def _dndDragEndCb(self, unused_view, unused_context):
- self.info("Drag operation ended")
-
- def _dndDragDataGetCb(self, unused_view, unused_context, data, unused_info, unused_timestamp):
- data.set_uris([self.getSelectedItems()])
+ def _dndDragDataGetCb(self, unused_view, drag_context, selection_data, unused_info, unused_timestamp):
+ factory_name = bytes(self.getSelectedEffectFactoryName(), "UTF-8")
+ selection_data.set(drag_context.list_targets()[0], 0, factory_name)
+ return True
def _rowUnderMouseSelected(self, view, event):
result = view.get_path_at_pos(int(event.x), int(event.y))
@@ -508,7 +487,7 @@ class EffectListWidget(Gtk.VBox, Loggable):
return False
def _enterPressEventCb(self, unused_view, unused_event=None):
- factory_name = self.getSelectedItems()
+ factory_name = self.getSelectedEffectFactoryName()
if factory_name is not None:
self.app.gui.clipconfig.effect_expander.addEffectToCurrentSelection(factory_name)
@@ -518,7 +497,7 @@ class EffectListWidget(Gtk.VBox, Loggable):
if event.button == 3:
chain_up = False
elif event.type == getattr(Gdk.EventType, '2BUTTON_PRESS'):
- factory_name = self.getSelectedItems()
+ factory_name = self.getSelectedEffectFactoryName()
if factory_name is not None:
self.app.gui.clipconfig.effect_expander.addEffectToCurrentSelection(factory_name)
else:
@@ -527,17 +506,16 @@ class EffectListWidget(Gtk.VBox, Loggable):
if chain_up:
self._draggedItems = None
else:
- self._draggedItems = self.getSelectedItems()
+ self._draggedItems = self.getSelectedEffectFactoryName()
Gtk.TreeView.do_button_press_event(view, event)
return True
- def getSelectedItems(self):
+ def getSelectedEffectFactoryName(self):
if self._draggedItems:
return self._draggedItems
model, rows = self.view.get_selection().get_selected_rows()
path = self.modelFilter.convert_path_to_child_path(rows[0])
-
return self.storemodel[path][COL_ELEMENT_NAME]
def _toggleViewTypeCb(self, widget):
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 63478af..180ffd9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -5,6 +5,7 @@
tests = \
test_application.py \
test_check.py \
+ test_clipproperties.py \
test_common.py \
test_log.py \
test_mainwindow.py \
diff --git a/tests/test_clipproperties.py b/tests/test_clipproperties.py
new file mode 100644
index 0000000..d4d7fac
--- /dev/null
+++ b/tests/test_clipproperties.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2014, Alex Băluț <alexandru balut gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+import unittest
+
+from gi.repository import Gtk
+
+from pitivi.clipproperties import EffectProperties
+
+
+class EffectPropertiesTest(unittest.TestCase):
+
+ def testCalculateEffectPriority(self):
+ # Dragging 1 onto itself and nearby.
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 0, Gtk.TreeViewDropPosition.AFTER))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 1, Gtk.TreeViewDropPosition.BEFORE))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 1,
Gtk.TreeViewDropPosition.INTO_OR_BEFORE))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 1,
Gtk.TreeViewDropPosition.INTO_OR_AFTER))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 1, Gtk.TreeViewDropPosition.AFTER))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(1, 2, Gtk.TreeViewDropPosition.BEFORE))
+
+ # Dragging 0 and 3 between rows 1 and 2.
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(0, 1, Gtk.TreeViewDropPosition.AFTER))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(0, 2, Gtk.TreeViewDropPosition.BEFORE))
+ self.assertEqual(2, EffectProperties.calculateEffectPriority(3, 1, Gtk.TreeViewDropPosition.AFTER))
+ self.assertEqual(2, EffectProperties.calculateEffectPriority(3, 2, Gtk.TreeViewDropPosition.BEFORE))
+
+ # Dragging 0 and 2 onto 1.
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(0, 1,
Gtk.TreeViewDropPosition.INTO_OR_BEFORE))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(0, 1,
Gtk.TreeViewDropPosition.INTO_OR_AFTER))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(2, 1,
Gtk.TreeViewDropPosition.INTO_OR_BEFORE))
+ self.assertEqual(1, EffectProperties.calculateEffectPriority(2, 1,
Gtk.TreeViewDropPosition.INTO_OR_AFTER))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]