[pitivi] timeline: Use a single separator widget between layers



commit a99b40abdf3a3cccfb114231c620f2b70ff7ae91
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Thu Oct 20 16:30:16 2016 +0200

    timeline: Use a single separator widget between layers
    
    There was no good reason to keep the two-separator-widgets
    implementation.
    
    This change simplifies the drag&drop on the separator logic. As a result
    the _get_separators and __getDroppedLayer methods have been removed.
    
    The widgets hierarchy is now thinner.
    
    One disadvantage is that the position of a layer in the layers box is
    not directly related to the layer's priority property. The added
    complexity is covered by new unit tests for layer creation, removal and
    movement, and they check everything.
    
    As part of the optimization, the __update_layers call from _removeLayer
    has been removed so __update_layers being used in a single place it has
    been merged into __layerPriorityChangedCb which if you read the first
    part of the method fits quite nice.
    
    Reviewed-by: Thibault Saunier <tsaunier gnome org>
    Differential Revision: https://phabricator.freedesktop.org/D1403

 pitivi/timeline/layer.py        |   52 +++----------
 pitivi/timeline/timeline.py     |  158 +++++++++++++++++++++++----------------
 pitivi/utils/timeline.py        |    9 ++-
 pitivi/utils/ui.py              |    2 +
 tests/test_timeline_timeline.py |  112 ++++++++++++++++++++++++++--
 5 files changed, 219 insertions(+), 114 deletions(-)
---
diff --git a/pitivi/timeline/layer.py b/pitivi/timeline/layer.py
index 49f687e..69c3614 100644
--- a/pitivi/timeline/layer.py
+++ b/pitivi/timeline/layer.py
@@ -30,6 +30,7 @@ from pitivi.utils.loggable import Loggable
 from pitivi.utils.timeline import Zoomable
 from pitivi.utils.ui import LAYER_HEIGHT
 from pitivi.utils.ui import PADDING
+from pitivi.utils.ui import SEPARATOR_HEIGHT
 
 
 class SpacedSeparator(Gtk.EventBox):
@@ -38,33 +39,11 @@ class SpacedSeparator(Gtk.EventBox):
     Inherits from EventBox since we want to change background color.
     """
 
-    def __init__(self, position):
+    def __init__(self):
         Gtk.EventBox.__init__(self)
 
-        self.__position = position
-
         self.get_style_context().add_class("SpacedSeparator")
-        self._update()
-
-    def do_state_flags_changed(self, old_flags):
-        self._update()
-        Gtk.EventBox.do_state_flags_changed(self, old_flags)
-
-    def _update(self):
-        HIGLIGHTED_PADDING = 3
-        total_height = PADDING + HIGLIGHTED_PADDING
-        if not self.get_state_flags() & Gtk.StateFlags.PRELIGHT:
-            self.props.height_request = 1
-            self.props.margin_bottom = (total_height - 1) / 2
-            self.props.margin_top = (total_height - 1) / 2
-        else:
-            self.props.height_request = PADDING
-            if self.__position == Gtk.PositionType.TOP:
-                self.props.margin_bottom = HIGLIGHTED_PADDING
-                self.props.margin_top = 0
-            else:
-                self.props.margin_bottom = 0
-                self.props.margin_top = HIGLIGHTED_PADDING
+        self.props.height_request = SEPARATOR_HEIGHT
 
 
 class LayerControls(Gtk.EventBox, Loggable):
@@ -81,7 +60,7 @@ class LayerControls(Gtk.EventBox, Loggable):
         self.app = app
 
         # Half the height because we display only the video strip when empty.
-        self.props.height_request = LAYER_HEIGHT / 2 + PADDING * 3
+        self.props.height_request = LAYER_HEIGHT / 2
         self.props.hexpand = True
         self.props.valign = Gtk.Align.FILL
 
@@ -92,16 +71,12 @@ class LayerControls(Gtk.EventBox, Loggable):
         hbox.pack_start(vbox, True, True, 0)
 
         rightside_separator = Gtk.Separator.new(Gtk.Orientation.VERTICAL)
-        rightside_separator.props.margin_top = PADDING / 2
-        rightside_separator.props.margin_bottom = PADDING / 2
         hbox.pack_start(rightside_separator, False, False, 0)
 
-        self.before_sep = SpacedSeparator(Gtk.PositionType.TOP)
-        vbox.pack_start(self.before_sep, False, False, 0)
-
         name_row = Gtk.Box()
         name_row.set_orientation(Gtk.Orientation.HORIZONTAL)
         name_row.props.spacing = PADDING
+        name_row.props.margin_top = PADDING
         name_row.props.margin_left = PADDING
         name_row.props.margin_right = PADDING
         vbox.pack_start(name_row, False, False, 0)
@@ -128,9 +103,6 @@ class LayerControls(Gtk.EventBox, Loggable):
         space.props.vexpand = True
         vbox.pack_start(space, False, False, 0)
 
-        self.after_sep = SpacedSeparator(Gtk.PositionType.BOTTOM)
-        vbox.pack_start(self.after_sep, False, False, 0)
-
         self.ges_layer.connect("notify::priority", self.__layerPriorityChangedCb)
         self.ges_timeline.connect("layer-added", self.__timelineLayerAddedCb)
         self.ges_timeline.connect("layer-removed", self.__timelineLayerRemovedCb)
@@ -242,7 +214,7 @@ class LayerControls(Gtk.EventBox, Loggable):
         self.app.project_manager.current_project.pipeline.commit_timeline()
 
     def update(self, media_types):
-        self.props.height_request = self.ges_layer.ui.props.height_request + PADDING * 3
+        self.props.height_request = self.ges_layer.ui.props.height_request
 
         if media_types & GES.TrackType.VIDEO:
             icon = "video-x-generic"
@@ -295,7 +267,7 @@ class LayerLayout(Gtk.Layout, Loggable):
 
 
 class Layer(Gtk.EventBox, Zoomable, Loggable):
-    """Container for a layer plus decorations (separators)."""
+    """Container for a layer."""
 
     __gtype_name__ = "PitiviLayer"
 
@@ -324,9 +296,6 @@ class Layer(Gtk.EventBox, Zoomable, Loggable):
         for clip in ges_layer.get_clips():
             self._addClip(clip)
 
-        self.before_sep = SpacedSeparator(Gtk.PositionType.TOP)
-        self.after_sep = SpacedSeparator(Gtk.PositionType.BOTTOM)
-
     def setName(self, name):
         self.ges_layer.set_meta("video::name", name)
 
@@ -351,8 +320,9 @@ class Layer(Gtk.EventBox, Zoomable, Loggable):
         return name
 
     def release(self):
-        for clip in self.ges_layer.get_clips():
-            self._removeClip(clip)
+        self._layout.disconnect_by_func(self.__childWidgetRemovedCb)
+        for ges_clip in self.ges_layer.get_clips():
+            self._removeClip(ges_clip)
         self.ges_layer.disconnect_by_func(self._clipAddedCb)
         self.ges_layer.disconnect_by_func(self._clipRemovedCb)
 
@@ -433,7 +403,7 @@ class Layer(Gtk.EventBox, Zoomable, Loggable):
         self._layout.remove(ges_clip.ui)
         self.timeline.selection.unselect([ges_clip])
 
-    def __childWidgetRemovedCb(self, layout, clip):
+    def __childWidgetRemovedCb(self, unused_layer_layout, clip):
         ges_clip = clip.ges_clip
         if self.timeline.draggingElement is None:
             ges_clip.ui.release()
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index b7f4fe4..17c9c18 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -36,6 +36,7 @@ from pitivi.timeline.elements import TransitionClip
 from pitivi.timeline.elements import TrimHandle
 from pitivi.timeline.layer import Layer
 from pitivi.timeline.layer import LayerControls
+from pitivi.timeline.layer import SpacedSeparator
 from pitivi.timeline.ruler import ScaleRuler
 from pitivi.undo.timeline import CommitTimelineFinalizingAction
 from pitivi.utils.loggable import Loggable
@@ -51,6 +52,7 @@ from pitivi.utils.ui import EXPANDED_SIZE
 from pitivi.utils.ui import LAYER_HEIGHT
 from pitivi.utils.ui import PLAYHEAD_COLOR
 from pitivi.utils.ui import PLAYHEAD_WIDTH
+from pitivi.utils.ui import SEPARATOR_HEIGHT
 from pitivi.utils.ui import set_cairo_color
 from pitivi.utils.ui import set_children_state_recurse
 from pitivi.utils.ui import SNAPBAR_COLOR
@@ -297,15 +299,15 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         hbox.pack_end(self.layout, True, True, 0)
 
         # Stuff the layers controls in a Viewport so it can be scrolled.
-        self.__layers_controls_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        self.__layers_controls_vbox.props.hexpand = False
-        self.__layers_controls_vbox.props.valign = Gtk.Align.START
+        self._layers_controls_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        self._layers_controls_vbox.props.hexpand = False
+        self._layers_controls_vbox.props.valign = Gtk.Align.START
         if size_group:
-            size_group.add_widget(self.__layers_controls_vbox)
+            size_group.add_widget(self._layers_controls_vbox)
 
         # Stuff the layers controls in a viewport so it can be scrolled.
         viewport = Gtk.Viewport(vadjustment=self.vadj)
-        viewport.add(self.__layers_controls_vbox)
+        viewport.add(self._layers_controls_vbox)
         clear_styles(viewport)
         hbox.pack_start(viewport, False, False, 0)
 
@@ -321,6 +323,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self.zoomed_fitted = True
 
         self._layers = []
+        # A list of (controls separator, layers separator) tuples.
+        self._separators = []
         # Whether the user is dragging a layer.
         self.__moving_layer = None
 
@@ -349,7 +353,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self.__drag_start_x = 0
         # The current layer on which the operation is performed.
         self._on_layer = None
-        # The one or two separators immediately above or below _on_layer
+        # The separators immediately above or below _on_layer
         # on which the operation will be performed.
         # Implies a new layer will be created.
         self.__on_separators = []
@@ -876,7 +880,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
                 with self.app.action_log.started("add clip",
                                                  CommitTimelineFinalizingAction(pipeline)):
                     if self.__on_separators:
-                        created_layer = self.__getDroppedLayer()
+                        priority = self.separator_priority(self.__on_separators[1])
+                        created_layer = self.createLayer(priority)
                     else:
                         created_layer = None
                     for layer, clip in self.__last_clips_on_leave:
@@ -914,62 +919,87 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self._addLayer(ges_layer)
 
     def moveLayer(self, ges_layer, index):
-        layers = self.ges_timeline.get_layers()
-        layer = layers.pop(ges_layer.get_priority())
-        layers.insert(index, layer)
-
-        for i, layer in enumerate(layers):
-            layer.set_priority(i)
+        self.debug("Moving layer %s to %s", ges_layer.props.priority, index)
+        ges_layers = self.ges_timeline.get_layers()
+        ges_layer = ges_layers.pop(ges_layer.props.priority)
+        ges_layers.insert(index, ges_layer)
+        for i, ges_layer in enumerate(ges_layers):
+            if ges_layer.props.priority != i:
+                ges_layer.props.priority = i
 
     def _addLayer(self, ges_layer):
         layer = Layer(ges_layer, self)
         ges_layer.ui = layer
+
+        if not self._separators:
+            # Make sure the first layer has separators above it.
+            self.__add_separators()
         self._layers.append(layer)
 
         control = LayerControls(ges_layer, self.app)
         control.show_all()
-        self.__layers_controls_vbox.pack_start(control, False, False, 0)
+        self._layers_controls_vbox.pack_start(control, False, False, 0)
         ges_layer.control_ui = control
         # Check the media types so the controls are set up properly.
         layer.checkMediaTypes()
 
-        layer_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-        layer_box.get_style_context().add_class("LayerBox")
-        layer_box.pack_start(layer.before_sep, False, False, 0)
-        layer_box.pack_start(layer, True, True, 0)
-        layer_box.pack_start(layer.after_sep, False, False, 0)
-        layer_box.show_all()
-        self.layers_vbox.pack_start(layer_box, True, True, 0)
+        self.layers_vbox.pack_start(layer, False, False, 0)
+        layer.show()
+
+        self.__add_separators()
 
         ges_layer.connect("notify::priority", self.__layerPriorityChangedCb)
 
-    def __layerPriorityChangedCb(self, ges_layer, pspec):
-        self.__update_layers()
+    def __add_separators(self):
+        """Adds separators to separate layers."""
+        controls_separator = SpacedSeparator()
+        controls_separator.show()
+        self._layers_controls_vbox.pack_start(controls_separator, False, False, 0)
+
+        separator = SpacedSeparator()
+        separator.show()
+        self.layers_vbox.pack_start(separator, False, False, 0)
 
-    def __update_layers(self, reset=False):
+        self._separators.append((controls_separator, separator))
+
+    def __layerPriorityChangedCb(self, ges_layer, pspec):
+        ges_layers = self.ges_timeline.get_layers()
+        priorities = [ges_layer.props.priority for ges_layer in ges_layers]
+        if priorities != list(range(len(priorities))):
+            self.debug("Layers still being shuffled, not updating widgets: %s", priorities)
+            return
         self._layers.sort(key=lambda layer: layer.ges_layer.props.priority)
-        self.debug("Reseting layers priorities")
+        self.debug("Updating layers widgets positions")
         for i, layer in enumerate(self._layers):
-            ges_layer = layer.ges_layer
-            if reset:
-                ges_layer.props.priority = i
-            self.__update_layer(ges_layer)
+            self.__update_layer(layer.ges_layer)
 
     def _removeLayer(self, ges_layer):
         self.info("Removing layer: %s", ges_layer.props.priority)
-        self.layers_vbox.remove(ges_layer.ui.get_parent())
-        self.__layers_controls_vbox.remove(ges_layer.control_ui)
+        self.layers_vbox.remove(ges_layer.ui)
+        self._layers_controls_vbox.remove(ges_layer.control_ui)
         ges_layer.disconnect_by_func(self.__layerPriorityChangedCb)
 
+        # Remove extra separators.
+        controls_separator, separator = self._separators.pop()
+        self.layers_vbox.remove(separator)
+        self._layers_controls_vbox.remove(controls_separator)
+
         self._layers.remove(ges_layer.ui)
         ges_layer.ui.release()
         ges_layer.ui = None
         ges_layer.control_ui = None
 
-        self.__update_layers(True)
-
-    def _layerRemovedCb(self, unused_ges_timeline, ges_layer):
+    def _layerRemovedCb(self, ges_timeline, ges_layer):
         self._removeLayer(ges_layer)
+        removed_priority = ges_layer.props.priority
+        for priority, ges_layer in enumerate(ges_timeline.get_layers()):
+            if priority >= removed_priority:
+                ges_layer.props.priority -= 1
+
+    def separator_priority(self, separator):
+        position = self.layers_vbox.child_get_property(separator, "position")
+        assert position % 2 == 0
+        return int(position / 2)
 
     # Interface Zoomable
     def zoomChanged(self):
@@ -1030,16 +1060,13 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
             return GES.EditMode.EDIT_TRIM
         return GES.EditMode.EDIT_NORMAL
 
-    def _get_separators(self, ges_layer, sep_name):
-        return [getattr(ges_layer.ui, sep_name), getattr(ges_layer.control_ui, sep_name)]
-
     def _get_layer_at(self, y, prefer_ges_layer=None, past_middle_when_adjacent=False):
         ges_layers = self.ges_timeline.get_layers()
-        if y < 20:
+        if y < SEPARATOR_HEIGHT:
             # The cursor is at the top, above the first layer.
             self.debug("Returning very first layer")
             ges_layer = ges_layers[0]
-            separators = self._get_separators(ges_layer, "before_sep")
+            separators = self._separators[0]
             return ges_layer, separators
 
         # This means if an asset is dragged directly on a separator,
@@ -1068,13 +1095,13 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
                         return prefer_ges_layer, []
                 return ges_layer, []
 
-            separators = self._get_separators(ges_layer, "after_sep")
+            # Check if there are more layers.
             try:
                 next_ges_layer = ges_layers[i + 1]
             except IndexError:
-                # The cursor is below the last layer.
+                # Nope, the cursor is below the last layer.
                 self.debug("Returning very last layer")
-                return ges_layer, separators
+                return ges_layer, self._separators[i + 1]
 
             if ges_layer == prefer_ges_layer:
                 # Choose a layer as close to prefer_ges_layer as possible.
@@ -1082,9 +1109,9 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
             if layer_y + layer_height <= y < next_ges_layer.ui.get_allocation().y:
                 # The cursor is between this layer and the one below.
-                separators.extend(self._get_separators(next_ges_layer, "before_sep"))
                 if prefer_after:
                     ges_layer = next_ges_layer
+                separators = self._separators[i + 1]
                 self.debug("Returning layer %s, separators: %s", ges_layer, separators)
                 return ges_layer, separators
 
@@ -1139,10 +1166,11 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
             self.__on_separators = []
         self._setSeparatorsPrelight(True)
 
-        priority = self._on_layer.props.priority
-        self.editing_context.editTo(position, priority)
+        self.editing_context.edit_to(position, self._on_layer)
 
     def createLayer(self, priority):
+        """Adds a new layer to the GES timeline."""
+        self.debug("Creating layer: priority = %s", priority)
         new_ges_layer = GES.Layer.new()
         new_ges_layer.props.priority = priority
         self.ges_timeline.add_layer(new_ges_layer)
@@ -1162,33 +1190,31 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         return new_ges_layer
 
     def __update_layer(self, ges_layer):
-        """Updates the position child prop of the layer and layer control."""
-        priority = ges_layer.props.priority
-
-        layer_box = ges_layer.ui.get_parent()
-        self.layers_vbox.child_set_property(layer_box, "position", priority)
-
-        self.__layers_controls_vbox.child_set_property(ges_layer.control_ui,
-                                                       "position",
-                                                       priority)
-
-    def __getDroppedLayer(self):
-        """Creates the layer for a clip dropped on a separator."""
-        priority = self._on_layer.props.priority
-        if self.__on_separators[0] == self._on_layer.ui.after_sep:
-            priority = self._on_layer.props.priority + 1
-
-        self.createLayer(max(0, priority))
-        return self.ges_timeline.get_layers()[priority]
+        """Sets the position of the layer and its controls in their parent."""
+        position = ges_layer.props.priority * 2 + 1
+
+        # Update the position of the LayerControls and Layer widgets and
+        # also the position of the separators below them.
+        controls_separator, layers_separator = self._separators[ges_layer.props.priority + 1]
+        self.layers_vbox.child_set_property(ges_layer.ui, "position", position)
+        self.layers_vbox.child_set_property(layers_separator, "position", position + 1)
+
+        self._layers_controls_vbox.child_set_property(ges_layer.control_ui,
+                                                      "position",
+                                                      position)
+        self._layers_controls_vbox.child_set_property(controls_separator,
+                                                      "position",
+                                                      position + 1)
 
     def dragEnd(self):
         if self.editing_context:
             self._snapEndedCb()
 
             if self.__on_separators and self.__got_dragged and not self.__clickedHandle:
-                layer = self.__getDroppedLayer()
-                self.editing_context.editTo(self.editing_context.new_position,
-                                            layer.get_priority())
+                priority = self.separator_priority(self.__on_separators[1])
+                layer = self.createLayer(priority)
+                position = self.editing_context.new_position
+                self.editing_context.edit_to(position, layer)
             self.layout.props.width = self._timelineLengthInPixels()
 
             self.editing_context.finish()
diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py
index e400ec9..236c3ee 100644
--- a/pitivi/utils/timeline.py
+++ b/pitivi/utils/timeline.py
@@ -266,8 +266,15 @@ class EditingContext(GObject.Object, Loggable):
         """
         self.mode = mode
 
-    def editTo(self, position, priority):
+    def edit_to(self, position, layer):
+        """Updates the position and priority of the edited clip or element.
+
+        Args:
+            position (int): The time in nanoseconds.
+            layer (GES.Layer): The layer on which it should be placed.
+        """
         position = max(0, position)
+        priority = layer.props.priority
         if self.edge in [GES.Edge.EDGE_START, GES.Edge.EDGE_END]:
             priority = -1
         else:
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index a580479..74e81ab 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -58,6 +58,8 @@ PLAYHEAD_COLOR = (255, 0, 0)
 SNAPBAR_WIDTH = 5
 SNAPBAR_COLOR = (127, 153, 204)
 LAYER_HEIGHT = 130
+# The space between two layers.
+SEPARATOR_HEIGHT = PADDING
 
 SMALL_THUMB_WIDTH = 64
 # 128 is the normal size for thumbnails, but for *icons* it looks insane.
diff --git a/tests/test_timeline_timeline.py b/tests/test_timeline_timeline.py
index 7efe92b..53f0223 100644
--- a/tests/test_timeline_timeline.py
+++ b/tests/test_timeline_timeline.py
@@ -22,13 +22,13 @@ from gi.repository import Gdk
 from gi.repository import GES
 from gi.repository import Gtk
 
-from pitivi.utils import ui
+from pitivi.utils.ui import LAYER_HEIGHT
+from pitivi.utils.ui import SEPARATOR_HEIGHT
 from tests import common
 from tests.common import create_timeline_container
 
-SEPARATOR_HEIGHT = 4
-THIN = ui.LAYER_HEIGHT / 2
-THICK = ui.LAYER_HEIGHT
+THIN = LAYER_HEIGHT / 2
+THICK = LAYER_HEIGHT
 
 
 class BaseTestTimeline(common.TestCase):
@@ -79,6 +79,7 @@ class TestLayers(BaseTestTimeline):
         timeline_container = create_timeline_container()
         timeline = timeline_container.timeline
 
+        # Allocate layers
         y = 0
         for priority, height in enumerate(heights):
             ges_layer = timeline.createLayer(priority=priority)
@@ -140,6 +141,105 @@ class TestLayers(BaseTestTimeline):
         self.assertEqual(len(timeline.__on_separators), 1,
                          "The separators must be forgotten only in dragEnd()")
 
+    def test_create_layer(self):
+        self.check_create_layer([0, 0, 0, 0], [3, 2, 1, 0])
+        self.check_create_layer([0, 1, 1, 1], [0, 3, 2, 1])
+        self.check_create_layer([0, 1, 1, 2], [0, 3, 1, 2])
+        self.check_create_layer([0, 1, 0, 2], [1, 3, 0, 2])
+        self.check_create_layer([0, 1, 2, 3], [0, 1, 2, 3])
+
+    def check_create_layer(self, start_priorities, expected_priorities):
+        timeline = create_timeline_container().timeline
+        ges_layers = []
+        for priority in start_priorities:
+            ges_layer = timeline.createLayer(priority)
+            self.assertEqual(ges_layer.props.priority, priority)
+            ges_layers.append(ges_layer)
+        self.check_priorities_and_positions(timeline, ges_layers, expected_priorities)
+
+    def check_priorities_and_positions(self, timeline, ges_layers,
+                                       expected_priorities):
+        layers_vbox = timeline.layers_vbox
+
+        # Check the layers priorities.
+        priorities = [ges_layer.props.priority for ges_layer in ges_layers]
+        self.assertListEqual(priorities, expected_priorities)
+
+        # Check the positions of the Layer widgets.
+        positions = [layers_vbox.child_get_property(ges_layer.ui, "position")
+                     for ges_layer in ges_layers]
+        expected_positions = [priority * 2 + 1
+                              for priority in expected_priorities]
+        self.assertListEqual(positions, expected_positions, layers_vbox.get_children())
+
+        # Check the positions of the LayerControl widgets.
+        controls_vbox = timeline._layers_controls_vbox
+        positions = [controls_vbox.child_get_property(ges_layer.control_ui, "position")
+                     for ges_layer in ges_layers]
+        self.assertListEqual(positions, expected_positions)
+
+        # Check the number of the separators.
+        count = len(ges_layers) + 1
+        self.assertEqual(len(timeline._separators), count)
+        controls_separators, layers_separators = list(zip(*timeline._separators))
+
+        # Check the positions of the LayerControl separators.
+        expected_positions = [2 * index for index in range(count)]
+        positions = [layers_vbox.child_get_property(separator, "position")
+                     for separator in layers_separators]
+        self.assertListEqual(positions, expected_positions)
+
+        # Check the positions of the Layer separators.
+        positions = [controls_vbox.child_get_property(separator, "position")
+                     for separator in controls_separators]
+        self.assertListEqual(positions, expected_positions)
+
+    def test_remove_layer(self):
+        self.check_remove_layer([0, 0, 0, 0])
+        self.check_remove_layer([0, 0, 1, 0])
+        self.check_remove_layer([0, 1, 0, 0])
+        self.check_remove_layer([0, 2, 1, 0])
+        self.check_remove_layer([1, 0, 1, 0])
+        self.check_remove_layer([2, 2, 0, 0])
+        self.check_remove_layer([3, 2, 1, 0])
+
+    def check_remove_layer(self, removal_order):
+        timeline = create_timeline_container().timeline
+
+        # Add layers to remove them later.
+        ges_layers = []
+        for priority in range(len(removal_order)):
+            ges_layer = timeline.createLayer(priority)
+            ges_layers.append(ges_layer)
+
+        # Remove the layers in the specified order.
+        for priority in removal_order:
+            ges_layer = ges_layers[priority]
+            self.assertTrue(timeline.ges_timeline.remove_layer(ges_layer))
+            ges_layers.remove(ges_layer)
+            self.check_priorities_and_positions(timeline, ges_layers, list(range(len(ges_layers))))
+
+    def test_move_layer(self):
+        self.check_move_layer(0, 0, [0, 1, 2, 3, 4])
+        self.check_move_layer(0, 1, [1, 0, 2, 3, 4])
+        self.check_move_layer(0, 4, [4, 0, 1, 2, 3])
+        self.check_move_layer(2, 0, [1, 2, 0, 3, 4])
+        self.check_move_layer(2, 3, [0, 1, 3, 2, 4])
+        self.check_move_layer(4, 0, [1, 2, 3, 4, 0])
+        self.check_move_layer(4, 3, [0, 1, 2, 4, 3])
+
+    def check_move_layer(self, from_priority, to_priority, expected_priorities):
+        timeline = create_timeline_container().timeline
+
+        # Add layers to move them later.
+        ges_layers = []
+        for priority in range(len(expected_priorities)):
+            ges_layer = timeline.createLayer(priority)
+            ges_layers.append(ges_layer)
+
+        timeline.moveLayer(ges_layers[from_priority], to_priority)
+        self.check_priorities_and_positions(timeline, ges_layers, expected_priorities)
+
 
 class TestGrouping(BaseTestTimeline):
 
@@ -316,7 +416,7 @@ class TestGrouping(BaseTestTimeline):
             with mock.patch.object(clip1.ui, "translate_coordinates") as translate_coordinates:
                 translate_coordinates.return_value = (40, 0)
                 with mock.patch.object(timeline, "_get_layer_at") as _get_layer_at:
-                    _get_layer_at.return_value = layer1, [layer1.ui.after_sep]
+                    _get_layer_at.return_value = layer1, timeline._separators[1]
                     timeline._motion_notify_event_cb(None, event)
             self.assertTrue(timeline.got_dragged)
 
@@ -400,7 +500,7 @@ class TestEditing(BaseTestTimeline):
             with mock.patch.object(clip.ui.rightHandle, "translate_coordinates") as translate_coordinates:
                 translate_coordinates.return_value = (0, 0)
                 with mock.patch.object(timeline, "_get_layer_at") as _get_layer_at:
-                    _get_layer_at.return_value = layer, [layer.ui.after_sep]
+                    _get_layer_at.return_value = layer, timeline._separators[1]
                     timeline._motion_notify_event_cb(None, event)
             self.assertTrue(timeline.got_dragged)
 


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