[pitivi] timeline: Avoid creating new layer by mistake



commit 5f0898735095bc349a6b54460f16829ceb612b3d
Author: Yash Agrawal <yagrawal900 gmail com>
Date:   Sun Jan 20 15:29:37 2019 +0530

    timeline: Avoid creating new layer by mistake
    
    Whenever the mouse cursor moves during a clip drag operation, we start a
    one second timer which highlights the separator and enables
    dropping-to-create-a-new-layer.
    
    Fixes #2268

 pitivi/timeline/timeline.py | 24 +++++++++++++--
 tests/test_undo_timeline.py | 74 +++++++++++++++++++++++++++++----------------
 2 files changed, 69 insertions(+), 29 deletions(-)
---
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 04f2ca20..5271d21f 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -65,6 +65,10 @@ from pitivi.utils.ui import URI_TARGET_ENTRY
 from pitivi.utils.widgets import ZoomBox
 
 
+# Creates new layer if a clip is held at layers separator after this time interval
+SEPARATOR_ACCEPTING_DROP_INTERVAL_MS = 1000
+
+
 GlobalSettings.addConfigOption('edgeSnapDeadband',
                                section="user-interface",
                                key="edge-snap-deadband",
@@ -340,6 +344,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         # Whether the user is dragging a layer.
         self.__moving_layer = None
 
+        self._separator_accepting_drop = False
+        self._separator_accepting_drop_id = 0
         self.__last_position = 0
         self._scrubbing = False
         self._scrolling = False
@@ -593,7 +599,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         event_widget = Gtk.get_event_widget(event)
         if event.get_state() & (Gdk.ModifierType.CONTROL_MASK |
-                                  Gdk.ModifierType.MOD1_MASK):
+                                Gdk.ModifierType.MOD1_MASK):
             # Zoom.
             x, unused_y = event_widget.translate_coordinates(self.layout.layers_vbox, event.x, event.y)
             # Figure out first where to scroll at the end.
@@ -1284,10 +1290,22 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
             # 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 = []
-        self._setSeparatorsPrelight(True)
+
+        self._separator_accepting_drop = False
+        if self._separator_accepting_drop_id:
+            GLib.source_remove(self._separator_accepting_drop_id)
+            self._separator_accepting_drop_id = 0
+        if self.__on_separators:
+            self._separator_accepting_drop_id = GLib.timeout_add(SEPARATOR_ACCEPTING_DROP_INTERVAL_MS,
+                                                                 self._separator_accepting_drop_timeout_cb)
 
         self.editing_context.edit_to(position, self._on_layer)
 
+    def _separator_accepting_drop_timeout_cb(self):
+        self._separator_accepting_drop_id = 0
+        self._setSeparatorsPrelight(True)
+        self._separator_accepting_drop = True
+
     def create_layer(self, priority):
         """Adds a new layer to the GES timeline."""
         self.debug("Creating layer: priority = %s", priority)
@@ -1308,7 +1326,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         if self.editing_context:
             self.__end_snap()
 
-            if self.__on_separators and self.__got_dragged and not self.__clickedHandle:
+            if self._separator_accepting_drop and self.__on_separators and self.__got_dragged and not 
self.__clickedHandle:
                 priority = self.separator_priority(self.__on_separators[1])
                 ges_layer = self.create_layer(priority)
                 position = self.editing_context.new_position
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index 7bca226e..99cdff38 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -21,6 +21,7 @@ from unittest import mock
 
 from gi.repository import Gdk
 from gi.repository import GES
+from gi.repository import GLib
 from gi.repository import Gst
 from gi.repository import GstController
 from gi.repository import Gtk
@@ -985,7 +986,8 @@ class TestGObjectObserver(BaseTestUndoTimeline):
 
 class TestDragDropUndo(BaseTestUndoTimeline):
 
-    def test_clip_dragged_to_create_layer_below(self):
+    def clip_dragged_to_create_layer(self, below):
+        """Simulates dragging a clip on a separator, without dropping it."""
         self.setup_timeline_container()
         timeline_ui = self.timeline_container.timeline
         layers = self.timeline.get_layers()
@@ -995,7 +997,7 @@ class TestDragDropUndo(BaseTestUndoTimeline):
         self.layer.add_clip(clip)
 
         # Drag a clip on a separator to create a layer.
-        with mock.patch.object(Gtk, 'get_event_widget') as get_event_widget:
+        with mock.patch.object(Gtk, "get_event_widget") as get_event_widget:
             get_event_widget.return_value = clip.ui
 
             event = mock.Mock()
@@ -1009,10 +1011,36 @@ class TestDragDropUndo(BaseTestUndoTimeline):
             event = mock.Mock()
             event.get_state.return_value = Gdk.ModifierType.BUTTON1_MASK
             event.x = 1
-            event.y = LAYER_HEIGHT * 2
+            if below:
+                event.y = LAYER_HEIGHT * 2
+            else:
+                event.y = -1
             event.get_button.return_value = True, 1
             timeline_ui._motion_notify_event_cb(None, event)
 
+        return clip, event, timeline_ui
+
+    def test_clip_dragged_to_create_layer_below_denied(self):
+        """Checks clip dropped onto the separator below without hovering."""
+        clip, event, timeline_ui = self.clip_dragged_to_create_layer(True)
+
+        timeline_ui._button_release_event_cb(None, event)
+
+        layers = self.timeline.get_layers()
+        self.assertEqual(len(layers), 1)
+        self.assertEqual(layers[0], self.layer)
+        self.check_layers(layers)
+        self.assertEqual(layers[0].get_clips(), [clip])
+
+        stack, = self.action_log.undo_stacks
+        # Only the clip creation action should be on the stack.
+        self.assertEqual(len(stack.done_actions), 1, stack.done_actions)
+
+    def test_clip_dragged_to_create_layer_below(self):
+        """Checks clip dropped onto the separator below after hovering."""
+        clip, event, timeline_ui = self.clip_dragged_to_create_layer(True)
+
+        timeline_ui._separator_accepting_drop_timeout_cb()
         timeline_ui._button_release_event_cb(None, event)
 
         layers = self.timeline.get_layers()
@@ -1037,34 +1065,28 @@ class TestDragDropUndo(BaseTestUndoTimeline):
         self.assertEqual(layers[0].get_clips(), [])
         self.assertEqual(layers[1].get_clips(), [clip])
 
-    def test_clip_dragged_to_create_layer_above(self):
-        self.setup_timeline_container()
-        timeline_ui = self.timeline_container.timeline
-        layers = self.timeline.get_layers()
-        self.assertEqual(len(layers), 1)
+        return clip, event, timeline_ui
 
-        clip = GES.TitleClip()
-        self.layer.add_clip(clip)
+    def test_clip_dragged_to_create_layer_above_denied(self):
+        """Checks clip dropped onto the separator above without hovering."""
+        clip, event, timeline_ui = self.clip_dragged_to_create_layer(False)
 
-        # Drag a clip on a separator to create a layer.
-        with mock.patch.object(Gtk, 'get_event_widget') as get_event_widget:
-            get_event_widget.return_value = clip.ui
+        timeline_ui._button_release_event_cb(None, event)
 
-            event = mock.Mock()
-            event.x = 0
-            event.get_button.return_value = True, 1
-            timeline_ui._button_press_event_cb(None, event)
+        layers = self.timeline.get_layers()
+        self.assertEqual(len(layers), 1)
+        self.check_layers(layers)
+        self.assertEqual(layers[0].get_clips(), [clip])
 
-            def translate_coordinates(widget, x, y):
-                return x, y
-            clip.ui.translate_coordinates = translate_coordinates
-            event = mock.Mock()
-            event.get_state.return_value = Gdk.ModifierType.BUTTON1_MASK
-            event.x = 1
-            event.y = -1
-            event.get_button.return_value = True, 1
-            timeline_ui._motion_notify_event_cb(None, event)
+        stack, = self.action_log.undo_stacks
+        # Only the clip creation action should be on the stack.
+        self.assertEqual(len(stack.done_actions), 1, stack.done_actions)
+
+    def test_clip_dragged_to_create_layer_above(self):
+        """Checks clip dropped onto the separator above after hovering."""
+        clip, event, timeline_ui = self.clip_dragged_to_create_layer(False)
 
+        timeline_ui._separator_accepting_drop_timeout_cb()
         timeline_ui._button_release_event_cb(None, event)
 
         layers = self.timeline.get_layers()


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