[pitivi/ges] Port to the new GES timeline edition API



commit 608ab1a87caa1d6f5efcbe666d3134e27fcb0dfc
Author: Thibault Saunier <thibault saunier collabora com>
Date:   Fri Jan 20 17:24:51 2012 -0300

    Port to the new GES timeline edition API

 pitivi/mainwindow.py        |    2 +
 pitivi/timeline/timeline.py |   81 +++--
 pitivi/timeline/track.py    |  101 ++++--
 pitivi/utils/timeline.py    |  830 +++----------------------------------------
 4 files changed, 168 insertions(+), 846 deletions(-)
---
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index 7f07668..d5d6901 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -756,6 +756,8 @@ class PitiviMainWindow(gtk.Window, Loggable):
         ideal_zoom_ratio = float(ruler_width) / timeline_duration_s
         nearest_zoom_level = Zoomable.computeZoomLevel(ideal_zoom_ratio)
         Zoomable.setZoomLevel(nearest_zoom_level)
+        self.app.current.timeline.props.snapping_distance = \
+            Zoomable.pixelToNs(self.app.settings.edgeSnapDeadband)
 
     def _projectManagerNewProjectLoadingCb(self, projectManager, uri):
         if uri:
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index a8ace4a..895e3a9 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -42,7 +42,7 @@ from pitivi.settings import GlobalSettings
 
 from curve import KW_LABEL_Y_OVERFLOW
 from track import TrackControls, TRACK_CONTROL_WIDTH, Track, TrackObject
-from pitivi.utils.timeline import Controller, MoveContext, SELECT, Zoomable
+from pitivi.utils.timeline import EditingContext, SELECT, Zoomable
 
 from pitivi.dialogs.depsmanager import DepsManager
 from pitivi.dialogs.filelisterrordialog import FileListErrorDialog
@@ -235,7 +235,6 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
         return True
 
     def do_expose_event(self, event):
-        self.debug("exposing TimelineCanvas %s", list(event.area))
         allocation = self.get_allocation()
         width = allocation.width
         height = allocation.height
@@ -358,7 +357,6 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
     position = 0
 
     def timelinePositionChanged(self, position):
-        self.debug("value : %r" % position)
         self.position = position
         self._playhead.props.x = self.nsToPixel(position)
 
@@ -378,13 +376,8 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
 
     def zoomChanged(self):
         self.queue_draw()
-        if self._timeline:
-            self._timeline.dead_band = self.pixelToNs(
-                self.settings.edgeSnapDeadband)
-            self.timelinePositionChanged(self.position)
 
 ## settings callbacks
-
     def _setSettings(self):
         self.zoomChanged()
 
@@ -398,14 +391,14 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
 
     def setTimeline(self, timeline):
         while self._tracks:
-            self._trackRemoved(None, 0)
+            self._trackRemovedCb(None, 0)
 
         self._timeline = timeline
         if self._timeline:
             for track in self._timeline.get_tracks():
-                self._trackAdded(None, track)
-            self._timeline.connect("track-added", self._trackAdded)
-            self._timeline.connect("track-removed", self._trackRemoved)
+                self._trackAddedCb(None, track)
+            self._timeline.connect("track-added", self._trackAddedCb)
+            self._timeline.connect("track-removed", self._trackRemovedCb)
         self.zoomChanged()
 
     def getTimeline(self):
@@ -413,20 +406,25 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
 
     timeline = property(getTimeline, setTimeline, None, "The timeline property")
 
-    def _trackAdded(self, timeline, track):
+    def _trackAddedCb(self, timeline, track):
         track = Track(self.app, track, self._timeline)
         self._tracks.append(track)
         track.set_canvas(self)
         self.tracks.add_child(track)
         self.regroupTracks()
 
-    def _trackRemoved(self, unused_timeline, position):
+    def _trackRemovedCb(self, unused_timeline, position):
         track = self._tracks[position]
         del self._tracks[position]
         track.remove()
         self.regroupTracks()
 
     def regroupTracks(self):
+        """
+        Make it so we have a real differentiation between the Audio tracks
+        and video tracks
+        This method should be called each time a change happen in the timeline
+        """
         height = 0
         for i, track in enumerate(self._tracks):
             track.set_simple_transform(0, height, 1, 0)
@@ -568,12 +566,17 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         self._project = None
         self._timeline = None
         self._creating_tckobjs_sigid = {}
+        self._move_context = None
 
         #Ids of the tracks notify::duration signals
         self._tcks_sig_ids = {}
         #Ids of the layer-added and layer-removed signals
         self._layer_sig_ids = []
 
+        self._settings = self.app.settings
+        self._settings.connect("edgeSnapDeadbandChanged",
+                self._snapDistanceChangedCb)
+
     def _createUI(self):
         self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
         self.props.row_spacing = 2
@@ -742,7 +745,6 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         self.connect("drag-leave", self._dragLeaveCb)
         self.connect("drag-drop", self._dragDropCb)
         self.connect("drag-motion", self._dragMotionCb)
-        self.app.connect("new-project-created", self._newProjectCreatedCb)
         self._canvas.connect("key-press-event", self._keyPressEventCb)
         self._canvas.connect("scroll-event", self._scrollEventCb)
 
@@ -791,39 +793,35 @@ class Timeline(gtk.Table, Loggable, Zoomable):
             if context.targets not in DND_EFFECT_LIST:
                 if not self._temp_objects and not self._creating_tckobjs_sigid:
                     self.timeline.enable_update(False)
-                    self._create_temp_source(-1, -1)
+                    self._create_temp_source(x, y)
 
                 # Let some time for TrackObject-s to be created
                 if self._temp_objects and not self._creating_tckobjs_sigid:
                     focus = self._temp_objects[0]
-                    self._move_context = MoveContext(self.timeline,
-                            focus, set(self._temp_objects[1:]))
+
+                    self._move_context = EditingContext(focus, self.timeline,
+                        ges.EDIT_MODE_NORMAL, ges.EDGE_NONE, set(self._temp_objects[1:]),
+                        self.app.settings)
+
                     self._move_temp_source(self.hadj.props.value + x, y)
         return True
 
     def _dragLeaveCb(self, unused_layout, context, unused_tstamp):
-        """
-        During a drag and drop operation to the timeline, when the mouse exits
-        the timeline area, ensure the temporary objects we created are removed.
-        """
-        for tlobj in self._temp_objects:
-            layer = tlobj.get_layer()
-            layer.remove_object(tlobj)
         self._temp_objects = []
         self.drag_unhighlight()
-        self._move_context.finish()
-
-    def _recreateSource(self, x, y):
-        self.app.action_log.begin("add clip")
-        self.added = 0
-        self._create_temp_source(x, y)
-        self.app.action_log.commit()
-        self._factories = []
+        self.timeline.enable_update(True)
 
     def _dragDropCb(self, widget, context, x, y, timestamp):
         if  context.targets not in DND_EFFECT_LIST:
-            gobject.timeout_add(300, self._recreateSource, x, y)
+            self.app.action_log.begin("add clip")
+            self.selected = self._temp_objects
+            self._project.emit("selected-changed", set(self.selected))
+
+            self._move_context.finish()
+            self.app.action_log.commit()
             context.drop_finish(True, timestamp)
+            self._factories = []
+
             return True
 
         elif context.targets in DND_EFFECT_LIST:
@@ -991,8 +989,10 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         del self._creating_tckobjs_sigid[tlobj]
         if x != -1 and not self.added:
             focus = self._temp_objects[0]
-            self._move_context = MoveContext(self.timeline, focus,
-                                             set(self._temp_objects[1:]))
+            self._move_context = EditingContext(focus, self.timeline,
+                        ges.EDIT_MODE_NORMAL, ges.EDGE_NONE, set(self._temp_objects[1:]),
+                       self.app.settings)
+
             self._move_temp_source(self.hadj.props.value + x, y)
             self.selected = self._temp_objects
             self._project.emit("selected-changed", set(self.selected))
@@ -1064,6 +1064,8 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         # GTK crack
         self._updateZoom = False
         Zoomable.setZoomLevel(int(adjustment.get_value()))
+        self._timeline.props.snapping_distance = \
+            Zoomable.pixelToNs(self._settings.edgeSnapDeadband)
         self._updateZoom = True
 
     def _zoomSliderScrollCb(self, unused_widget, event):
@@ -1123,6 +1125,11 @@ class Timeline(gtk.Table, Loggable, Zoomable):
     def _rulerSizeAllocateCb(self, ruler, allocation):
         self._canvas.props.redraw_when_scrolled = False
 
+    def _snapDistanceChangedCb(self, settings):
+        if self._timeline:
+            self._timeline.props.snapping_distance = \
+                Zoomable.pixelToNs(settings.edgeSnapDeadband)
+
 ## Project callbacks
 
     def _newProjectCreatedCb(self, app, project):
@@ -1175,6 +1182,8 @@ class Timeline(gtk.Table, Loggable, Zoomable):
 
         # Make sure to set the current layer in use
         self._layerAddedCb(None, None)
+        self._timeline.props.snapping_distance = \
+            Zoomable.pixelToNs(self._settings.edgeSnapDeadband)
 
     def getTimeline(self):
         return self._timeline
diff --git a/pitivi/timeline/track.py b/pitivi/timeline/track.py
index d8d6832..e86c43f 100644
--- a/pitivi/timeline/track.py
+++ b/pitivi/timeline/track.py
@@ -22,6 +22,7 @@
 
 import goocanvas
 import ges
+import gst
 import gobject
 import gtk
 import os.path
@@ -40,8 +41,7 @@ from pitivi.utils.ui import Point, info_name
 from pitivi.settings import GlobalSettings
 from pitivi.utils.signal import Signallable
 from pitivi.utils.timeline import SELECT, SELECT_ADD, UNSELECT, \
-    SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext, Controller, \
-    View, Zoomable
+    SELECT_BETWEEN, EditingContext, Controller, View, Zoomable
 from pitivi.utils.ui import LAYER_HEIGHT_EXPANDED,\
         LAYER_HEIGHT_COLLAPSED, LAYER_SPACING, \
         unpack_cairo_pattern, unpack_cairo_gradient
@@ -151,7 +151,7 @@ class Selected (Signallable):
     selected = property(getSelected, setSelected)
 
 
-class TimelineController(Controller):
+class TrackObjectController(Controller):
 
     _cursor = ARROW
     _context = None
@@ -160,6 +160,12 @@ class TimelineController(Controller):
     next_previous_x = None
     ref = None
 
+    def __init__(self, instance, default_mode, view=None):
+        # Used to force the editing mode in use
+        Controller.__init__(self, instance, view)
+
+        self.default_mode = default_mode
+
     def enter(self, unused, unused2):
         self._view.focus()
 
@@ -167,15 +173,21 @@ class TimelineController(Controller):
         self._view.unfocus()
 
     def drag_start(self, item, target, event):
+        """
+            Start draging an element in the Track
+        """
         self.debug("Drag started")
+
         if not self._view.element.selected:
             self._view.timeline.selection.setToObj(self._view.element, SELECT)
+
         if self.previous_x != None:
             ratio = float(self.ref / Zoomable.pixelToNs(10000000000))
             self.previous_x = self.previous_x * ratio
+
         self.ref = Zoomable.pixelToNs(10000000000)
-        self._view.app.projectManager.current.timeline.enable_update(False)
         tx = self._view.props.parent.get_transform()
+
         # store y offset for later priority calculation
         self._y_offset = tx[5]
         # zero y component of mousdown coordiante
@@ -185,16 +197,12 @@ class TimelineController(Controller):
         self.debug("Drag end")
         self._context.finish()
         self._context = None
-        self._view.app.projectManager.current.timeline.enable_update(True)
         self._view.app.action_log.commit()
-        self._view.element.starting_start = self._view.element.props.start
-        obj = self._view.element.get_timeline_object()
-        obj.starting_start = obj.props.start
-        self.previous_x = self.next_previous_x
 
     def set_pos(self, item, pos):
         x, y = pos
         x = x + self._hadj.get_value()
+
         position = Zoomable.pixelToNs(x)
         priority = int((y - self._y_offset + self._vadj.get_value()) //
             (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))
@@ -205,10 +213,10 @@ class TimelineController(Controller):
 
     def _getMode(self):
         if self._shift_down:
-            return self._context.RIPPLE
+            return ges.EDIT_MODE_RIPPLE
         elif self._control_down:
-            return self._context.ROLL
-        return self._context.DEFAULT
+            return ges.EDIT_MODE_ROLL
+        return self.default_mode
 
     def key_press(self, keyval):
         if self._context:
@@ -233,7 +241,7 @@ class TrimHandle(View, goocanvas.Image, Loggable, Zoomable):
             line_width=0,
             pointer_events=goocanvas.EVENTS_FILL,
             **kwargs)
-        View.__init__(self)
+        View.__init__(self, instance, ges.EDIT_MODE_TRIM)
         Zoomable.__init__(self)
         Loggable.__init__(self)
 
@@ -248,7 +256,7 @@ class StartHandle(TrimHandle):
 
     """Subclass of TrimHandle wich sets the object's start time"""
 
-    class Controller(TimelineController, Signallable):
+    class Controller(TrackObjectController):
 
         _cursor = LEFT_SIDE
 
@@ -277,18 +285,21 @@ class EndHandle(TrimHandle):
 
     """Subclass of TrimHandle which sets the objects's end time"""
 
-    class Controller(TimelineController):
+    class Controller(TrackObjectController):
 
         _cursor = RIGHT_SIDE
 
         def drag_start(self, item, target, event):
             self.debug("Trim end %s" % target)
-            TimelineController.drag_start(self, item, target, event)
+            TrackObjectController.drag_start(self, item, target, event)
+
             if self._view.element.is_locked():
                 elem = self._view.element.get_timeline_object()
             else:
                 elem = self._view.element
-            self._context = TrimEndContext(self._view.timeline, elem, set([]))
+            self._context = EditingContext(elem, self._view.timeline,
+                ges.EDIT_MODE_TRIM, ges.EDGE_END, set([]),
+                self.app.settings)
             self._context.connect("clip-trim", self.clipTrimCb)
             self._context.connect("clip-trim-finished", self.clipTrimFinishedCb)
             self._view.app.action_log.begin("trim object")
@@ -304,22 +315,25 @@ class EndHandle(TrimHandle):
 
 class TrackObject(View, goocanvas.Group, Zoomable):
 
-    class Controller(TimelineController):
+    class Controller(TrackObjectController):
 
         _handle_enter_leave = True
 
         def drag_start(self, item, target, event):
             point = self.from_item_event(item, event)
-            TimelineController.drag_start(self, item, target, event)
-            self._context = MoveContext(self._view.timeline,
-                        self._view.element,
-                        self._view.timeline.selection.getSelectedTrackObjs())
+            TrackObjectController.drag_start(self, item, target, event)
+
+            self._context = EditingContext(self._view.element,
+                self._view.timeline, ges.EDIT_MODE_NORMAL, ges.EDGE_NONE,
+                self._view.timeline.selection.getSelectedTrackObjs(),
+                self.app.settings)
+
             self._view.app.action_log.begin("move object")
 
         def _getMode(self):
             if self._shift_down:
-                return self._context.RIPPLE
-            return self._context.DEFAULT
+                return ges.EDIT_MODE_RIPPLE
+            return ges.EDIT_MODE_NORMAL
 
         def click(self, pos):
             timeline = self._view.timeline
@@ -340,7 +354,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
 
     def __init__(self, instance, element, track, timeline, utrack):
         goocanvas.Group.__init__(self)
-        View.__init__(self)
+        View.__init__(self, instance)
         Zoomable.__init__(self)
         self.ref = Zoomable.nsToPixel(10000000000)
         self.app = instance
@@ -539,24 +553,32 @@ class TrackObject(View, goocanvas.Group, Zoomable):
             self._selec_indic.props.visibility = goocanvas.ITEM_INVISIBLE
 
     def _update(self):
+        # Calculating the new position
         try:
             x = self.nsToPixel(self.element.get_start())
         except Exception, e:
             raise Exception(e)
-        priority = (self.element.get_priority()) / 1000
-        if priority < 0:
-            priority = 0
+
+        priority = self.element.get_timeline_object().get_layer().get_priority()
         y = (self.height + LAYER_SPACING) * priority
+
+        # Setting new position
         self.set_simple_transform(x, y, 1, 0)
         width = self.nsToPixel(self.element.get_duration())
-        min_width = self.start_handle.props.width * 2
+
+        # Handle a duration of 0
+        handles_width = self.start_handle.props.width
+        min_width = handles_width * 2
         if width < min_width:
             width = min_width
-        w = width - self.end_handle.props.width
-        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (0, 0, w, self.height, w)
+        w = width - handles_width
+        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (
+            0, 0, w, self.height, w)
         self.bg.props.width = width
+
         self._selec_indic.props.width = width
         self.end_handle.props.x = w
+
         if self.expanded:
             if w - NAME_HOFFSET > 0:
                 self.namebg.props.height = self.nameheight + NAME_PADDING2X
@@ -565,6 +587,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
                 self.namebg.props.visibility = goocanvas.ITEM_VISIBLE
             else:
                 self.namebg.props.visibility = goocanvas.ITEM_INVISIBLE
+
         self.app.gui.timeline_ui._canvas.regroupTracks()
         self.app.gui.timeline_ui.unsureVadjHeight()
 
@@ -639,19 +662,21 @@ class TrackControls(gtk.Label, Loggable):
         self.set_padding(0, LAYER_SPACING * 2)
         self.set_markup(self._getTrackName(track))
         self.track = track
+        self.timeline = track.get_timeline()
         self._setSize(layers_count=1)
 
     def _setTrack(self):
+        self.timeline = self.track.get_timeline()
         if self.track:
             self._maxPriorityChanged(None, self.track.max_priority)
 
-    # FIXME Stop using the receiver
-    #
-    # TODO implement in GES
-    #track = receiver(_setTrack)
-    # handler(track, "max-priority-changed")
-    #def _maxPriorityChanged(self, track, max_priority):
-    #    self._setSize(max_priority + 1)
+    def _layerAddedCb(self, timeline, unused_layer):
+        max_priority = len(timeline.get_layers())
+        self._setSize(max_priority)
+
+    def _layerRemovedCb(self, timeline, unused_layer):
+        max_priority = len(timeline.get_layers())
+        self._setSize(max_priority)
 
     def _setSize(self, layers_count):
         assert layers_count >= 1
diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py
index a637f33..6c365b9 100644
--- a/pitivi/utils/timeline.py
+++ b/pitivi/utils/timeline.py
@@ -50,176 +50,6 @@ class TimelineError(Exception):
     pass
 
 
-def previous_track_source(focus, layer, start):
-    """
-    Get the source before @start in @track
-    """
-    tckobjs = focus.get_track().get_objects()
-
-    # tckobjs is in order, we want to iter in the reverse order
-    #FIXME optimize this algotithm, probably using bisect
-    for tckobj in reversed(tckobjs):
-        tckstart = tckobj.get_start()
-        tckduration = tckobj.get_duration()
-        if tckobj != focus and \
-                tckobj.get_timeline_object().get_layer() == layer and \
-                (tckstart + tckduration < start or\
-                (tckstart < start < tckstart + tckduration)) and \
-                isinstance(tckobj, ges.TrackSource):
-            return tckobj
-    return None
-
-
-def next_track_source(focus, layer, start, duration):
-    """
-    Get the source before @start in @track
-    """
-    tckobjs = focus.get_track().get_objects()
-    end = start + duration
-
-    #FIXME optimize this algotithm, probably using bisect
-    for tckobj in tckobjs:
-        tckstart = tckobj.get_start()
-        tckduration = tckobj.get_duration()
-        if tckobj != focus and \
-                tckobj.get_timeline_object().get_layer() == layer and \
-                (end < tckstart or (tckstart < end < tckstart + tckduration)) \
-                and isinstance(tckobj, ges.TrackSource):
-            return tckobj
-    return None
-
-
-class Gap(object):
-    """
-    """
-    def __init__(self, left_object, right_object, start, duration):
-        self.left_object = left_object
-        self.right_object = right_object
-        self.start = start
-        self.initial_duration = duration
-
-    def __cmp__(self, other):
-        if other is None or other is invalid_gap:
-            return -1
-        return cmp(self.duration, other.duration)
-
-    @classmethod
-    def findAroundObject(self, timeline_object, priority=-1, tracks=None):
-        layer = timeline_object.get_layer()
-        tlobjs = layer.get_objects()
-        index = tlobjs.index(timeline_object)
-
-        try:
-            prev = [obj for obj in tlobjs[:index - 1]\
-                    if isinstance(obj, ges.TimelineSource) and \
-                    obj != timeline_object].pop()
-            left_object = prev
-            right_object = timeline_object
-            start = prev.props.start + prev.props.duration
-            duration = timeline_object.props.start - start
-        except IndexError:
-            left_object = None
-            right_object = timeline_object
-            start = 0
-            duration = timeline_object.props.start
-
-        left_gap = Gap(left_object, right_object, start, duration)
-
-        try:
-            next = [obj for obj in tlobjs[index + 1:]\
-                   if isinstance(obj, ges.TimelineSource) and \
-                    obj != timeline_object][0]
-
-            left_object = timeline_object
-            right_object = next
-            start = timeline_object.props.start + timeline_object.props.duration
-            duration = next.props.start - start
-
-        except IndexError:
-            left_object = timeline_object
-            right_object = None
-            start = timeline_object.props.start + timeline_object.props.duration
-            duration = infinity
-
-        right_gap = Gap(left_object, right_object, start, duration)
-
-        return left_gap, right_gap
-
-    @classmethod
-    def findAllGaps(self, objs):
-        """Find all the gaps in a given set of objects: i.e. find all the
-        spans of time which are covered by no object in the given set"""
-        duration = 0
-        gaps = []
-        prev = None
-
-        # examine each object in order of increasing start time
-        for obj in sorted(objs, key=lambda x: x.props.start):
-            start = obj.props.start
-            end = obj.props.start + obj.props.duration
-
-            # only if the current object starts after the total timeline
-            # duration is a gap created.
-            if start > duration:
-                gaps.append(Gap(prev, obj, duration, start - duration))
-            duration = max(duration, end)
-            prev = obj
-        return gaps
-
-    @property
-    def duration(self):
-        if self.left_object is None and self.right_object is None:
-            return self.initial_duration
-
-        if self.initial_duration is infinity:
-            return self.initial_duration
-
-        if self.left_object is None:
-            return self.right_object.props.start
-
-        if self.right_object is None:
-            return infinity
-
-        res = self.right_object.props.start - \
-                (self.left_object.props.start + self.left_object.props.duration)
-        return res
-
-
-class InvalidGap(object):
-    pass
-
-invalid_gap = InvalidGap()
-
-
-class SmallestGapsFinder(object):
-    def __init__(self, internal_objects):
-        self.left_gap = None
-        self.right_gap = None
-        self.internal_objects = internal_objects
-
-    def update(self, left_gap, right_gap):
-        self.updateGap(left_gap, "left_gap")
-        self.updateGap(right_gap, "right_gap")
-
-    def updateGap(self, gap, min_gap_name):
-        if self.isInternalGap(gap):
-            return
-
-        min_gap = getattr(self, min_gap_name)
-
-        if min_gap is invalid_gap or gap.duration < 0:
-            setattr(self, min_gap_name, invalid_gap)
-            return
-
-        if min_gap is None or gap < min_gap:
-            setattr(self, min_gap_name, gap)
-
-    def isInternalGap(self, gap):
-        gap_objects = set([gap.left_object, gap.right_object])
-
-        return gap_objects.issubset(self.internal_objects)
-
-
 class Selection(Signallable):
     """
     A collection of L{ges.TimelineObject}.
@@ -334,636 +164,89 @@ class Selection(Signallable):
 
 
 #-----------------------------------------------------------------------------#
-#                           Timeline edition modes helpers                    #
-class EditingContext(object):
-
-    DEFAULT = 0
-    ROLL = 1
-    RIPPLE = 2
-    SLIP_SLIDE = 3
-
-    """Encapsulates interactive editing.
+#                       Timeline edition modes helper                         #
+class EditingContext(Signallable):
+    """
+        Encapsulates interactive editing.
 
-    This is the base class for interactive editing contexts.
+        This is the main class for interactive edition.
     """
 
-    def __init__(self, timeline, focus, other):
-        """
-        @param timeline: the timeline to edit
-        @type timeline: instance of L{ges.Timeline}
+    __signals__ = {
+        "clip-trim": ["uri", "position"],
+        "clip-trim-finished": [],
+    }
 
+    def __init__(self, focus, timeline, mode, edge, other, settings):
+        """
         @param focus: the TimelineObject or TrackObject which is to be the
         main target of interactive editing, such as the object directly under the
         mouse pointer
-        @type focus: L{ges.TimelineObject} or
-        L{ges.TrackObject}
+        @type focus: L{ges.TimelineObject} or L{ges.TrackObject}
+
+        @param timeline: the timeline to edit
+        @type timeline: instance of L{ges.Timeline}
+
+        @param edge: The edge on which the edition will happen, this parametter
+        can be change during the time using the same context.
+        @type edge: L{ges.Edge}
+
+        @param mode: The mode in which the edition will happen, this parametter
+        can be change during the time using the same context.
+        @type mode: L{ges.EditMode}
 
         @param other: a set of objects which are the secondary targets of
         interactive editing, such as objects in the current selection.
         @type other: a set() of L{TimelineObject}s or L{TrackObject}s
 
-        @returns: An instance of L{pitivi.utils.timeline.TimelineEditContex}
+        @param setting: The PiTiVi settings, used to get the snap_distance
+        parametter
+
+        @returns: An instance of L{pitivi.utils.timeline.EditingContext}
         """
+        Signallable.__init__(self)
 
         # make sure focus is not in secondary object list
         other.difference_update(set((focus,)))
 
         self.other = other
-        self.focus = focus
+        if isinstance(focus, ges.TrackObject):
+            self.focus = focus.get_timeline_object()
+        else:
+            self.focus = focus
         self.timeline = timeline
-        self._snap = True
-        self._mode = self.DEFAULT
-        self._last_position = focus.props.start
-        self._last_priority = focus.props.priority
-
-        self.timeline.enable_update(False)
-
-    def _getOffsets(self, start_offset, priority_offset, timeline_objects):
-        offsets = {}
-        for tlobj in timeline_objects:
-            offsets[tlobj] = (tlobj.props.start - start_offset,
-                        tlobj.get_layer().props.priority - priority_offset)
-
-        return offsets
 
-    def _getTimelineObjectValues(self, tlobj):
-        return (tlobj.props.start, tlobj.props.duration,
-                tlobj.props.in_point,
-                tlobj.props.priority)
+        self.edge = edge
+        self.mode = mode
 
-    def _saveValues(self, timeline_objects):
-        return dict(((tlobj,
-            self._getTimelineObjectValues(tlobj))
-                for tlobj in timeline_objects))
-
-    def _restoreValues(self, values):
-        for tlobj, (start, duration, in_point, pri) in \
-            values.iteritems():
-            tlobj.props.start = start
-            tlobj.props.duration = duration
-            tlobj.props.in_point = in_point
-            tlobj.props.priority = pri
-
-    def _getSpan(self, earliest, objs):
-        return max((obj.start + obj.duration for obj in objs)) - earliest
+        self.timeline.enable_update(False)
 
     def finish(self):
         """Clean up timeline for normal editing"""
         # TODO: post undo / redo action here
         self.timeline.enable_update(True)
+        self.emit("clip-trim-finished")
 
     def setMode(self, mode):
         """Set the current editing mode.
-        @param mode: the editing mode. Must be one of DEFAULT, ROLL, or
-        RIPPLE.
+        @param mode: the editing mode. Must be a ges.EditMode
         """
-        if mode != self._mode:
-            self._finishMode(self._mode)
-            self._beginMode(mode)
-            self._mode = mode
-
-    def _finishMode(self, mode):
-        if mode == self.DEFAULT:
-            self._finishDefault()
-        elif mode == self.ROLL:
-            self._finishRoll()
-        elif mode == self.RIPPLE:
-            self._finishRipple()
-
-    def _beginMode(self, mode):
-        if self._last_position:
-            if mode == self.DEFAULT:
-                self._defaultTo(self._last_position, self._last_priority)
-            elif mode == self.ROLL:
-                self._rollTo(self._last_position, self._last_priority)
-            elif mode == self.RIPPLE:
-                self._rippleTo(self._last_position, self._last_priority)
-
-    def _finishRoll(self):
-        pass
-
-    def _rollTo(self, position, priority):
-        return position, priority
-
-    def _finishRipple(self):
-        pass
-
-    def _rippleTo(self, position, priority):
-        return position, priority
-
-    def _finishDefault(self):
-        pass
-
-    def _defaultTo(self, position, priority):
-        return position, priority
-
-    def snap(self, snap):
-        """Set whether edge snapping is currently enabled"""
-        self.debug("Setting snap to %s", snap)
-        if snap != self._snap:
-            self.editTo(self._last_position, self._last_priority)
-        self._snap = snap
+        self.mode = mode
 
     def editTo(self, position, priority):
-        if self._mode == self.DEFAULT:
-            position, priority = self._defaultTo(position, priority)
-        if self._mode == self.ROLL:
-            position, priority = self._rollTo(position, priority)
-        elif self._mode == self.RIPPLE:
-            position, priority = self._rippleTo(position, priority)
-        self._last_position = position
-        self._last_priority = priority
-
-        return position, priority
-
-    def _getGapsForLayer(self, timeline_objects):
-        gaps = SmallestGapsFinder(timeline_objects)
-
-        for tlobj in timeline_objects:
-            left_gap, right_gap = Gap.findAroundObject(tlobj)
-            gaps.update(left_gap, right_gap)
-
-        return gaps.left_gap, gaps.right_gap
-
-
-class MoveContext(EditingContext, Loggable):
-
-    """
-    An editing context which sets the start point of the editing targets.
-    It has support for ripple, slip-and-slide editing modes.
-
-    @tracks: {track: [earliest: latest]} with @earliest the earliest #TrackObject in @track
-            and @latest the latest #TrackObject in @track
-    """
-
-    # FIXME Refactor... this is too long!
-    def __init__(self, timeline, focus, other):
-        EditingContext.__init__(self, timeline, focus, other)
-        Loggable.__init__(self)
-
-        min_priority = infinity
-        earliest = infinity
-        latest = 0
-        self.default_originals = {}
-        self.timeline_objects = set([])
-        self.tracks = {}
-        self.tckobjs = set([])
-        all_objects = set(other)
-        all_objects.add(focus)
-
-        for obj in all_objects:
-            if isinstance(obj, ges.TrackObject):
-                tlobj = obj.get_timeline_object()
-                tckobjs = [obj]
-            else:
-                tlobj = obj
-                tckobjs = tlobj.get_track_objects()
-
-            self.timeline_objects.add(tlobj)
-            self.default_originals[tlobj] = \
-                    self._getTimelineObjectValues(tlobj)
-
-            # Check TrackObject-s as we can have unlocked objects
-            for tckobj in tckobjs:
-                track = tckobj.get_track()
-                if not tckobj.get_track() in self.tracks:
-                    self.tracks[track] = [tckobj, tckobj]
-
-                earliest = min(earliest, tckobj.props.start)
-                if earliest == tckobj.props.start:
-                    curr_early_late = self.tracks[track]
-                    self.tracks[track] = [tckobj, curr_early_late[1]]
-
-                latest = max(latest, tckobj.props.start + tckobj.props.duration)
-                if latest == tckobj.props.start:
-                    curr_early_late = self.tracks[track]
-                    self.tracks[track] = [curr_early_late[0], tckobj]
-
-            self.tckobjs.update(tckobjs)
-
-            # Always work with TimelineObject-s for priorities
-            min_priority = min(min_priority, tlobj.props.priority)
-
-        # Get focus various properties we need
-        focus_start = focus.props.start
-        if isinstance(focus, ges.TrackObject):
-            layer = focus.get_timeline_object().get_layer()
+        position = max(0, position)
+        if self.edge in [ges.EDGE_START, ges.EDGE_END]:
+            priority = -1
         else:
-            layer = focus.get_layer()
-
-        focus_prio = layer.props.priority
-        self.offsets = self._getOffsets(focus_start,
-                focus_prio, self.timeline_objects)
-
-        self.min_priority = focus_prio - min_priority
-        self.min_position = focus_start - earliest
-
-        # get the span over all clips for edge snapping
-        self.default_span = latest - earliest
-
-        ripple = [obj for obj in layer.get_objects() if obj.props.start >= latest]
-        self.ripple_offsets = self._getOffsets(focus_start, focus_prio, ripple)
-
-        # get the span over all clips for ripple editing
-        for tlobj in ripple:
-            latest = max(latest, tlobj.props.start + tlobj.props.duration)
-        self.ripple_span = latest - earliest
+            priority = max(0, priority)
 
-        # save default values
-        self.ripple_originals = self._saveValues(ripple)
-
-        self.timeline_objects_plus_ripple = set(self.timeline_objects)
-        self.timeline_objects_plus_ripple.update(ripple)
-
-    def _getGapsForLayer(self):
-        if self._mode == self.RIPPLE:
-            timeline_objects = self.timeline_objects_plus_ripple
-        else:
-            timeline_objects = self.timeline_objects
-
-        return EditingContext._getGapsForLayer(self, timeline_objects)
-
-    def setMode(self, mode):
-        if mode == self.ROLL:
-            raise Exception("invalid mode ROLL")
-        EditingContext.setMode(self, mode)
-
-    def _finishDefault(self):
-        self._restoreValues(self.default_originals)
-
-    def finish(self):
-
-        if isinstance(self.focus, ges.TrackObject):
-            focus_timeline_object = self.focus.get_timeline_object()
-        else:
-            focus_timeline_object = self.focus
-
-        initial_position = self.default_originals[focus_timeline_object][0]
-        initial_priority = self.default_originals[focus_timeline_object][-1]
-
-        final_priority = focus_timeline_object.props.priority
-        final_position = self.focus.props.start
-
-        priority = final_priority
-
-        # special case for transitions. Allow a single object to overlap
-        # either of its two neighbors if it overlaps no other objects
-        if len(self.timeline_objects) == 1:
-            EditingContext.finish(self)
-            return
-
-        # adjust layer
-        overlap = False
-        while True:
-            left_gap, right_gap = self._getGapsForLayer()
-
-            if left_gap is invalid_gap or right_gap is invalid_gap:
-                overlap = True
-
-                if priority == initial_priority:
-                    break
-
-                if priority > initial_priority:
-                    priority -= 1
-                else:
-                    priority += 1
-
-                self._defaultTo(final_position, priority)
-            else:
-                overlap = False
-                break
-
-        if not overlap:
-            EditingContext.finish(self)
-            return
-
-        self._defaultTo(initial_position, priority)
-        delta = final_position - initial_position
-        left_gap, right_gap = self._getGapsForLayer()
-
-        if delta > 0 and right_gap.duration < delta:
-            final_position = initial_position + right_gap.duration
-        elif delta < 0 and left_gap.duration < abs(delta):
-            final_position = initial_position - left_gap.duration
-
-        self._defaultTo(final_position, priority)
-        EditingContext.finish(self)
-
-    def snapToEdge(self, start, end=None):
-        """
-        Snaps the given start/end value to the closest edge if it is within
-        the timeline's dead_band.
-
-        @param start: The start position to snap.
-        @param end: The stop position to snap.
-        @returns: The snapped value if within the dead_band.
-        """
-        for track, earliest_latest in self.tracks.iteritems():
-            tckobj = earliest_latest[0]
-            prev = previous_track_source(tckobj,
-                    tckobj.get_timeline_object().get_layer(), start)
-
-            if prev:
-                prev_end = prev.get_start() + prev.get_duration()
-                if abs(start - prev_end) < gst.SECOND:
-                    self.debug("Snaping to edge frontward, diff=%d",
-                            abs(start - prev_end))
-                    return prev_end
-            elif end:
-                tckobj = earliest_latest[1]
-                next = next_track_source(tckobj,
-                        tckobj.get_timeline_object().get_layer(), start,
-                        end - start)
-
-                if next and abs(end - next.get_start()) < gst.SECOND:
-                    self.debug("Snaping to edge backward, diff=%d",
-                            abs(end - next.get_start()))
-                    return next.get_start() - (end - start)
-
-        return start
-
-    def _ensureLayer(self):
-        """
-        Make sure we have a layer in our timeline
-
-        Returns: The number of layer present in self.timeline
-        """
-        layers = self.timeline.get_layers()
-
-        if not layers:
-            layer = ges.TimelineLayer()
-            layer.props.auto_transition = True
-            self.timeline.add_layer(layer)
-            layers = [layer]
-
-        return layers
-
-    def _defaultTo(self, position, priority):
-        if self._snap:
-            position = self.snapToEdge(position,
-                position + self.default_span)
-
-        self.debug("defaulting to %s with priorty %d", position, priority)
-
-        layers = self._ensureLayer()
-        position = max(self.min_position, position)
-
-        # We make sure to work with TimelineObject-s for the drag
-        # and drop
-        if isinstance(self.focus, ges.TrackSource):
-            obj = self.focus.get_timeline_object()
-        else:
-            obj = self.focus
-
-        # FIXME See what we should do in the case we have
-        # have ges.xxxOperation
-
-        self.focus.props.start = long(position)
-
-        for obj, (s_offset, p_offset) in self.offsets.iteritems():
-            obj.props.start = max(0, long(position + s_offset))
-
-            # Move between layers
-            layers = self.timeline.get_layers()
-            priority = min(len(layers), max(0, priority + p_offset))
-            if obj.get_layer().props.priority != priority:
-                if  priority == len(layers):
-                    self.debug("Adding layer")
-                    layer = ges.TimelineLayer()
-                    layer.props.auto_transition = True
-                    layer.props.priority = priority
-                    self.timeline.add_layer(layer)
-                    obj.move_to_layer(layer)
-                else:
-                    obj.move_to_layer(layers[priority])
-
-        #Remove empty layer
-        last_layer = self.timeline.get_layers()[-1]
-        if not last_layer.get_objects():
-            self.debug("Removing layer")
-            self.timeline.remove_layer(last_layer)
-
-        return position, priority
-
-    def _finishRipple(self):
-        self._restoreValues(self.ripple_originals)
-
-    def _rippleTo(self, position, priority):
-        self.debug("Ripple from %s", position)
-        if self._snap:
-            position = self.snapToEdge(position,
-                position + self.default_span)
-
-        priority = max(self.min_priority, priority)
-        left_gap, right_gap = self._getGapsForLayer()
-
-        if left_gap is invalid_gap or right_gap is invalid_gap:
-            if priority == self._last_priority:
-                # abort move
-                return self._last_position, self._last_priority
-
-            # try to do the same time move, using the current priority
-            return self._defaultTo(position, self._last_priority)
-
-        delta = position - self.focus.props.start
-        if delta > 0 and right_gap.duration < delta:
-            position = self.focus.props.start + right_gap.duration
-        elif delta < 0 and left_gap.duration < abs(delta):
-            position = self.focus.props.start - left_gap.duration
-
-        #FIXME GES: What about moving between layers?
-        self.focus.props.start = position
-        for obj, (s_offset, p_offset) in self.offsets.iteritems():
-            obj.props.start = position + s_offset
-
-        for obj, (s_offset, p_offset) in self.ripple_offsets.iteritems():
-            obj.props.start = position + s_offset
-
-        return position, priority
-
-
-class TrimStartContext(EditingContext, Signallable):
-
-    __signals__ = {
-        "clip-trim": ["uri", "position"],
-        "clip-trim-finished": [],
-    }
-
-    def __init__(self, timeline, focus, other):
-        EditingContext.__init__(self, timeline, focus, other)
-        self.tracks = set([])
-
-        if isinstance(self.focus, ges.TrackObject):
-            focus_timeline_object = self.focus.get_timeline_object()
-            self.tracks.add(focus.get_track())
-        else:
-            focus_timeline_object = self.focus
-            tracks = set(track_object.get_track() for track_object in
-                        focus.get_track_objects())
-            self.tracks.update(tracks)
-        self.focus_timeline_object = focus_timeline_object
-        self.default_originals = self._saveValues([focus_timeline_object])
-        #ripple = self.timeline.getObjsBeforeTime(focus.start)
-        #assert not focus.tlobj in ripple or focus.duration == 0
-        #self.ripple_originals = self._saveValues(ripple)
-        #self.ripple_offsets = self._getOffsets(focus.start, focus.priority,
-            #ripple)
-        #if ripple:
-            #self.ripple_min = focus.start - min((obj.start for obj in ripple))
-        #else:
-            #self.ripple_min = 0
-
-    def _rollTo(self, position, priority):
-        earliest = self.focus.start - self.focus.in_point
-        self.focus.trimStart(max(position, earliest))
-        for obj in self.adjacent:
-            duration = max(0, position - obj.start)
-            obj.setDuration(duration, snap=False)
-        return position, priority
-
-    def _finishRoll(self):
-        self._restoreValues(self.adjacent_originals)
-
-    def _rippleTo(self, position, priority):
-        earliest = self.focus.start - self.focus.in_point
-        latest = earliest + self.focus.factory.duration
-
-        if self.snap:
-            position = self.snapToEdge(position)
-
-        position = min(latest, max(position, earliest))
-        self.focus.trimStart(position)
-        r_position = max(position, self.ripple_min)
-        for obj, (s_offset, p_offset) in self.ripple_offsets.iteritems():
-            obj.setStart(r_position + s_offset)
-
-        return position, priority
-
-    def _finishRipple(self):
-        self._restoreValues(self.ripple_originals)
-
-    def _defaultTo(self, position, priority):
-        earliest = max(0, position - self.focus.starting_start)
-        self.focus.props.in_point = earliest
-        self.focus.props.start = position
-        self.focus.props.duration = self.focus.props.max_duration - \
-                self.focus.props.in_point
-        self.emit("clip-trim", self.focus.props.uri, self.focus.props.in_point)
-        return position, priority
-
-    def finish(self):
-        if isinstance(self.focus, ges.TrackObject):
-            obj = self.focus.get_timeline_object()
-        else:
-            obj = self.focus
-
-        initial_position = self.default_originals[self.focus_timeline_object][0]
-        self.focus.starting_start = self.focus.props.start
-        timeline_objects = [self.focus_timeline_object]
-        EditingContext.finish(self)
-
-        left_gap, right_gap = self._getGapsForLayer(timeline_objects)
-
-        if left_gap is invalid_gap:
-            self._defaultTo(initial_position, obj.priority)
-            left_gap, right_gap = Gap.findAroundObject(self.focus_timeline_object)
-            position = initial_position - left_gap.duration
-            self._defaultTo(position, obj)
-
-        self.emit("clip-trim-finished")
-
-
-class TrimEndContext(EditingContext, Signallable):
-
-    __signals__ = {
-        "clip-trim": ["uri", "position"],
-        "clip-trim-finished": [],
-    }
-
-    def __init__(self, timeline, focus, other):
-        EditingContext.__init__(self, timeline, focus, other)
-        self.tracks = set([])
-        if isinstance(self.focus, ges.TrackSource):
-            focus_timeline_object = self.focus
-            self.tracks.add(focus.get_track())
-        else:
-            focus_timeline_object = self.focus
-            tracks = set(track_object.get_track() for track_object in
-                    focus.get_track_objects())
-            self.tracks.update(tracks)
-        self.focus_timeline_object = focus_timeline_object
-        self.default_originals = self._saveValues([focus_timeline_object])
-
-        if isinstance(focus, ges.TrackObject):
-            layer = focus.get_timeline_object().get_layer()
-        else:
-            layer = focus.get_layer()
-        reference = focus.props.start + focus.props.duration
-        ripple = [obj for obj in layer.get_objects() \
-                  if obj.props.start > reference]
-
-        self.ripple_originals = self._saveValues(ripple)
-        self.ripple_offsets = self._getOffsets(reference,
-            self.focus.get_layer().props.priority, ripple)
-
-    def _rollTo(self, position, priority):
-        if self._snap:
-            position = self.snapToEdge(position)
-        duration = max(0, position - self.focus.start)
-        self.focus.setDuration(duration)
-        for obj in self.adjacent:
-            obj.trimStart(position)
-        return position, priority
-
-    def _finishRoll(self):
-        self._restoreValues(self.adjacent_originals)
-
-    def _rippleTo(self, position, priority):
-        earliest = self.focus.start - self.focus.in_point
-        latest = earliest + self.focus.factory.duration
-        if self.snap:
-            position = self.snapToEdge(position)
-        position = min(latest, max(position, earliest))
-        duration = position - self.focus.start
-        self.focus.props.duration = duration
-        for obj, (s_offset, p_offset) in self.ripple_offsets.iteritems():
-            obj.setStart(position + s_offset)
-
-        return position, priority
-
-    def _finishRipple(self):
-        self._restoreValues(self.ripple_originals)
-
-    def _defaultTo(self, position, priority):
-        duration = max(0, position - self.focus.props.start)
-        duration = min(duration, self.focus.max_duration)
-        self.focus.props.duration = duration
-        self.emit("clip-trim", self.focus.props.uri, self.focus.props.duration)
-        return position, priority
-
-    def finish(self):
-        EditingContext.finish(self)
-
-        if isinstance(self.focus, ges.TrackObject):
-            obj = self.focus.get_timeline_object()
-        else:
-            obj = self.focus
-
-        initial_position, initial_duration = \
-                self.default_originals[self.focus_timeline_object][0:2]
-        absolute_initial_duration = initial_position + initial_duration
-
-        timeline_objects = [self.focus_timeline_object]
-
-        left_gap, right_gap = self._getGapsForLayer(timeline_objects)
-
-        if right_gap is invalid_gap:
-            self._defaultTo(absolute_initial_duration, obj.props.priority)
-            left_gap, right_gap = Gap.findAroundObject(self.focus_timeline_object)
-            duration = absolute_initial_duration + right_gap.duration
-            self._defaultTo(duration, obj.props.priority)
-
-        self.emit("clip-trim-finished")
+        res = self.focus.edit(None, priority, self.mode, self.edge, long(position))
+        if res and self.mode == ges.EDIT_MODE_TRIM:
+            uri = self.focus.props.uri
+            if self.edge == ges.EDGE_START:
+                self.emit("clip-trim", uri, self.focus.props.in_point)
+            elif self.edge == ges.EDGE_END:
+                self.emit("clip-trim", uri, self.focus.props.duration)
 
 
 #-------------------------- Interfaces ----------------------------------------#
@@ -973,9 +256,11 @@ ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
 
 class Controller(Loggable):
 
-    """A controller which implements drag-and-drop bahavior on connected view
-    objects in the timeline. Subclasses may override the drag_start, drag_end,
-    pos, and set_pos methods"""
+    """
+        A controller which implements drag-and-drop bahavior on connected view
+        objects in the timeline. Subclasses may override the drag_start, drag_end,
+        pos, and set_pos methods
+    """
 
     # note we SHOULD be using the gtk function for this, but it doesn't appear
     # to be exposed in pygtk
@@ -999,9 +284,10 @@ class Controller(Loggable):
     _handle_mouse_up_down = True
     _handle_motion_notify = True
 
-    def __init__(self, view=None):
+    def __init__(self, instance, view=None):
         object.__init__(self)
         self._view = view
+        self.app = instance
         Loggable.__init__(self)
 
 ## convenience functions
@@ -1181,9 +467,9 @@ class View(object):
 
     Controller = Controller
 
-    def __init__(self):
+    def __init__(self, instance, default_mode=ges.EDIT_MODE_NORMAL):
         object.__init__(self)
-        self._controller = self.Controller(view=self)
+        self._controller = self.Controller(instance, default_mode, view=self)
 
 ## public interface
 



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