[pitivi/ges: 78/287] timeline: rivive the Selection class and use it



commit 55d0afeac5da5c7398a48c9f09f9fc91ade6e4c5
Author: Thibault Saunier <thibault saunier collabora com>
Date:   Wed Dec 7 18:45:49 2011 -0300

    timeline: rivive the Selection class and use it
    
        + Port it to GES.
        + Modify the current code to use it
        + Handle various clip selection using the code from old PiTiVI
        + Some cleanup all around
    
    Also makes triming working again

 pitivi/project.py           |    7 ++-
 pitivi/timeline/timeline.py |  127 ++++++++++++++++++++++++++++++++++++++--
 pitivi/ui/timeline.py       |   41 +++++---------
 pitivi/ui/timelinecanvas.py |    4 +-
 pitivi/ui/trackobject.py    |  135 ++++++++++++++++++++++--------------------
 5 files changed, 214 insertions(+), 100 deletions(-)
---
diff --git a/pitivi/project.py b/pitivi/project.py
index d58c699..92ab35c 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -26,11 +26,12 @@ Project class
 import gst
 import ges
 
+from pitivi.utils import Seeker
 from pitivi.log.loggable import Loggable
 from pitivi.sourcelist import SourceList
 from pitivi.settings import ExportSettings
 from pitivi.signalinterface import Signallable
-from pitivi.utils import Seeker
+from pitivi.timeline.timeline import Selection
 
 
 class ProjectError(Exception):
@@ -86,7 +87,9 @@ class Project(Signallable, Loggable):
         self._dirty = False
 
         self.timeline = ges.timeline_new_audio_video()
-        self.timeline.selected = []
+        # We add a Selection to the timeline as there is currently
+        # no such feature in GES
+        self.timeline.selection = Selection()
         self.layer = ges.TimelineLayer()
         self.layer.set_property("auto-transition", True)
 
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index bbb41a1..b70a912 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -23,6 +23,7 @@
 import ges
 
 from pitivi.utils import infinity
+from pitivi.signalinterface import Signallable
 from pitivi.timeline.gap import Gap, SmallestGapsFinder, invalid_gap
 
 #from pitivi.timeline.align import AutoAligner
@@ -38,6 +39,11 @@ SELECT_BETWEEN = 3
 """Select a range of clips"""
 
 
+class TimelineError(Exception):
+    """Base Exception for errors happening in L{Timeline}s or L{TimelineObject}s"""
+    pass
+
+
 class EditingContext(object):
 
     DEFAULT = 0
@@ -511,8 +517,8 @@ class TrimStartContext(EditingContext):
         timeline_objects = [self.focus_timeline_object]
         EditingContext.finish(self)
 
-        left_gap, right_gap = self._getGapsForLayer(obj,
-                timeline_objects, self.tracks)
+        left_gap, right_gap = self._getGapsForLayer(timeline_objects,
+            self.tracks)
 
         if left_gap is invalid_gap:
             self._defaultTo(initial_position, obj.priority)
@@ -547,7 +553,7 @@ class TrimEndContext(EditingContext):
                   if obj.props.start > reference]
 
         self.ripple_originals = self._saveValues(ripple)
-        self.ripple_offsets = self._getOffsets(reference, self.focus.get_priority(),
+        self.ripple_offsets = self._getOffsets(reference, self.focus.props.priority,
             ripple)
 
     def _rollTo(self, position, priority):
@@ -599,11 +605,122 @@ class TrimEndContext(EditingContext):
 
         timeline_objects = [self.focus_timeline_object]
 
-        left_gap, right_gap = self._getGapsForLayer(obj.priority,
-                timeline_objects, self.tracks)
+        left_gap, right_gap = self._getGapsForLayer(timeline_objects,
+                self.tracks)
 
         if right_gap is invalid_gap:
             self._defaultTo(absolute_initial_duration, obj.priority)
             left_gap, right_gap = Gap.findAroundObject(self.focus_timeline_object)
             duration = absolute_initial_duration + right_gap.duration
             self._defaultTo(duration, obj.priority)
+
+
+class Selection(Signallable):
+    """
+    A collection of L{ges.TimelineObject}.
+
+    Signals:
+     - C{selection-changed} : The contents of the L{ges.Selection} changed.
+
+    @ivar selected: Set of selected L{ges.TrackObject}
+    @type selected: C{list}
+    """
+
+    __signals__ = {
+        "selection-changed": []}
+
+    def __init__(self):
+        self.selected = set([])
+        self.last_single_obj = None
+
+    def setToObj(self, obj, mode):
+        """
+        Convenience method for calling L{setSelection} with a single L{ges.TimelineObject}
+
+        @see: L{setSelection}
+        """
+        self.setSelection(set([obj]), mode)
+
+    def addTimelineObject(self, timeline_object):
+        """
+        Add the given timeline_object to the selection.
+
+        @param timeline_object: The object to add
+        @type timeline_object: L{ges.TimelineObject}
+        @raises TimelineError: If the object is already controlled by this
+        Selection.
+        """
+        if timeline_object in self.timeline_objects:
+            raise TimelineError("TrackObject already in this selection")
+
+    def setSelection(self, objs, mode):
+        """
+        Update the current selection.
+
+        Depending on the value of C{mode}, the selection will be:
+         - L{SELECT} : set to the provided selection.
+         - L{UNSELECT} : the same minus the provided selection.
+         - L{SELECT_ADD} : extended with the provided selection.
+
+        @param selection: The list of timeline objects to update the selection with.
+        @param mode: The type of update to apply. Can be C{SELECT},C{UNSELECT} or C{SELECT_ADD}
+
+        @see: L{setToObj}
+        """
+        # get a list of timeline objects
+        selection = set()
+        for obj in objs:
+            # FIXME GES break, handle the fact that we have unlinked objects in GES
+            if isinstance(obj, ges.TrackObject):
+                selection.add(obj.get_timeline_object())
+            else:
+                selection.add(obj)
+
+        old_selection = self.selected
+        if mode == SELECT_ADD:
+            selection = self.selected | selection
+        elif mode == UNSELECT:
+            selection = self.selected - selection
+        self.selected = selection
+
+        if len(self.selected) == 1:
+            self.last_single_obj = iter(selection).next()
+
+        for obj in self.selected - old_selection:
+            for tckobj in obj.get_track_objects():
+                tckobj.selected.selected = True
+
+        for obj in old_selection - self.selected:
+            for tckobj in obj.get_track_objects():
+                tckobj.selected.selected = False
+
+        # FIXME : shouldn't we ONLY emit this IFF the selection has changed ?
+        self.emit("selection-changed")
+
+    def getSelectedTrackObjs(self):
+        """
+        Returns the list of L{TrackObject} contained in this selection.
+        """
+        objects = []
+        for timeline_object in self.selected:
+            objects.extend(timeline_object.get_track_objects())
+
+        return set(objects)
+
+    def getSelectedTrackEffects(self):
+        """
+        Returns the list of L{TrackEffect} contained in this selection.
+        """
+        track_effects = []
+        for timeline_object in self.selected:
+            for track in timeline_object.track_objects:
+                if isinstance(track, ges.TrackEffect):
+                    track_effects.append(track)
+
+        return track_effects
+
+    def __len__(self):
+        return len(self.selected)
+
+    def __iter__(self):
+        return iter(self.selected)
diff --git a/pitivi/ui/timeline.py b/pitivi/ui/timeline.py
index ed8f0f3..7049e07 100644
--- a/pitivi/ui/timeline.py
+++ b/pitivi/ui/timeline.py
@@ -738,18 +738,11 @@ class Timeline(gtk.Table, Loggable, Zoomable):
     def deleteSelected(self, unused_action):
         if self.timeline:
             self.app.action_log.begin("delete clip")
-            for track_object in self.timeline.selected:
-                obj = track_object.get_timeline_object()
-                obj.release_track_object(track_object)
-                track = track_object.get_track()
-                track.remove_object(track_object)
-                remove = True
-                for tck_obj in obj.get_track_objects():
-                    if isinstance(tck_obj, ges.TrackSource):
-                        remove = False
-                if remove:
-                    lyr = obj.get_layer()
-                    lyr.remove_object(obj)
+            #FIXME GES port: Handle unlocked TrackObject-s
+            for obj in self.timeline.selection:
+                layer = obj.get_layer()
+                layer.remove_object(obj)
+
             self.app.action_log.commit()
 
     def unlinkSelected(self, unused_action):
@@ -763,14 +756,14 @@ class Timeline(gtk.Table, Loggable, Zoomable):
     def ungroupSelected(self, unused_action):
         if self.timeline:
             self.app.action_log.begin("ungroup")
-            for track_object in self.timeline.selected:
-                track_object.set_locked(False)
+            for tlobj in self.timeline.selection:
+                tlobj.objects_set_locked(False)
             self.app.action_log.commit()
 
     def groupSelected(self, unused_action):
         if self.timeline:
-            for track_object in self.timeline.selected:
-                track_object.set_locked(True)
+            for tlobj in self.timeline.selection:
+                tlobj.set_locked(True)
 
     def alignSelected(self, unused_action):
         if "NumPy" in soft_deps:
@@ -792,23 +785,17 @@ class Timeline(gtk.Table, Loggable, Zoomable):
 
     def split(self, action):
         self.timeline.enable_update(False)
-        tl_objs_dict = {}
+
+        #Splitting the objects at the current position
         for track in self.timeline.get_tracks():
             for tck_obj in track.get_objects():
                 start = tck_obj.props.start
                 end = start + tck_obj.props.duration
                 if start < self._position and end > self._position:
                     obj = tck_obj.get_timeline_object()
-                    if obj in tl_objs_dict.keys():
-                        tl_objs_dict[obj] = "both"
-                    elif tck_obj.get_track().get_caps().to_string() == "audio/x-raw-int; audio/x-raw-float":
-                        tl_objs_dict[obj] = "audio"
-                    else:
-                        tl_objs_dict[obj] = "video"
-
-        for src in tl_objs_dict:
-            src.split(self._position)
-            self.timeline.enable_update(True)
+                    obj.split(self._position)
+
+        self.timeline.enable_update(True)
 
     def keyframe(self, action):
         timeline_position = self._position
diff --git a/pitivi/ui/timelinecanvas.py b/pitivi/ui/timelinecanvas.py
index 31a5165..778d13a 100644
--- a/pitivi/ui/timelinecanvas.py
+++ b/pitivi/ui/timelinecanvas.py
@@ -243,7 +243,7 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
         self._selecting = False
         self._marquee.props.visibility = goocanvas.ITEM_INVISIBLE
         if not self._got_motion_notify:
-            #self._timeline.setSelectionTo(set(), 0)
+            self._timeline.selection.setSelection([], 0)
             seeker.seek(Zoomable.pixelToNs(event.x))
         else:
             self._got_motion_notify = False
@@ -254,7 +254,7 @@ class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
                 mode = 2
             selected = self._objectsUnderMarquee()
             self.app.projectManager.current.emit("selected-changed", selected)
-            #self._timeline.setSelectionTo(self._objectsUnderMarquee(), mode)
+            self._timeline.selection.setSelection(self._objectsUnderMarquee(), mode)
         return True
 
     def _objectsUnderMarquee(self):
diff --git a/pitivi/ui/trackobject.py b/pitivi/ui/trackobject.py
index 3bb5780..fad5ac4 100644
--- a/pitivi/ui/trackobject.py
+++ b/pitivi/ui/trackobject.py
@@ -14,12 +14,14 @@ from zoominterface import Zoomable
 from common import LAYER_HEIGHT_EXPANDED, LAYER_HEIGHT_COLLAPSED
 from common import LAYER_SPACING, unpack_cairo_pattern, unpack_cairo_gradient
 
+from pitivi.ui.point import Point
 from pitivi.log.loggable import Loggable
 from pitivi.settings import GlobalSettings
 from pitivi.receiver import receiver, handler
 from pitivi.ui.prefs import PreferencesDialog
-from pitivi.timeline.timeline import MoveContext, TrimStartContext,\
-         TrimEndContext
+from pitivi.signalinterface import Signallable
+from pitivi.timeline.timeline import SELECT, SELECT_ADD, UNSELECT, \
+    SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext
 
 LEFT_SIDE = gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE)
 RIGHT_SIDE = gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE)
@@ -109,6 +111,31 @@ def  get_next_track_source(track, tckobj):
     return None
 
 
+class Selected (Signallable):
+    """
+        A simple class that let us emit a selected-changed signal
+        when need
+    """
+
+    __signals__ = {
+        "selected-changed": []}
+
+    def __init__(self):
+        self._selected = False
+
+    def __nonzero__(self):
+        return self._selected
+
+    def getSelected(self):
+        return self._selected
+
+    def setSelected(self, selected):
+        self._selected = selected
+        self.emit("selected-changed", selected)
+
+    selected = property(getSelected, setSelected)
+
+
 class TimelineController(controller.Controller):
 
     _cursor = ARROW
@@ -125,8 +152,9 @@ class TimelineController(controller.Controller):
         self._view.unfocus()
 
     def drag_start(self, item, target, event):
-        #if not self._view.element.selected:
-            #self._view.timeline.selection.setToObj(self._view.element, SELECT)
+        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
@@ -136,16 +164,17 @@ class TimelineController(controller.Controller):
         # store y offset for later priority calculation
         self._y_offset = tx[5]
         # zero y component of mousdown coordiante
-        #self._mousedown = Point(self._mousedown[0], 0)
+        self._mousedown = Point(self._mousedown[0], 0)
 
     def drag_end(self, item, target, event):
+        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.get_property("start")
+        self._view.element.starting_start = self._view.element.props.start
         obj = self._view.element.get_timeline_object()
-        obj.starting_start = obj.get_property("start")
+        obj.starting_start = obj.props.start
         self.previous_x = self.next_previous_x
 
     def set_pos(self, item, pos):
@@ -153,10 +182,11 @@ class TimelineController(controller.Controller):
         x = x + self._hadj.get_value()
         priority = int((y - self._y_offset + self._vadj.get_value()) //
             (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))
-        #self._context.setMode(self._getMode())
+        self._context.setMode(self._getMode())
         track = self._view.element.get_track()
         start = self._view.element.get_start()
         duration = self._view.element.get_duration()
+
         if self.previous_x:
             position = self._view.element.get_start() + Zoomable.pixelToNs(x - self.previous_x)
         else:
@@ -251,14 +281,14 @@ class StartHandle(TrimHandle):
         _cursor = LEFT_SIDE
 
         def drag_start(self, item, target, event):
+            self.debug("Trim start %s" % target)
             TimelineController.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 = TrimStartContext(self._view.timeline,
-                elem,
-                set([]))
+                elem, set([]))
             self._view.app.action_log.begin("trim object")
 
 
@@ -271,14 +301,14 @@ class EndHandle(TrimHandle):
         _cursor = RIGHT_SIDE
 
         def drag_start(self, item, target, event):
+            self.debug("Trim end %s" % target)
             TimelineController.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([]))
+                elem, set([]))
             self._view.app.action_log.begin("trim object")
 
 
@@ -303,27 +333,21 @@ class TrackObject(View, goocanvas.Group, Zoomable):
             return self._context.DEFAULT
 
         def click(self, pos):
-            #timeline = self._view.timeline
+            timeline = self._view.timeline
             element = self._view.element
-            if element.is_locked():
-                elem = set(element.get_timeline_object().get_track_objects())
+            if self._last_event.get_state() & gtk.gdk.SHIFT_MASK:
+                timeline.selection.setToObj(element, SELECT_BETWEEN)
+            elif self._last_event.get_state() & gtk.gdk.CONTROL_MASK:
+                if element.selected:
+                    mode = UNSELECT
+                else:
+                    mode = SELECT_ADD
+                timeline.selection.setToObj(element, mode)
             else:
-                elem = element
-            self._view.app.projectManager.current.emit("selected-changed", elem)
-            element_end = element.get_property("start") + element.get_property("duration")
-            #if self._last_event.get_state() & gtk.gdk.SHIFT_MASK:
-                #timeline.setSelectionToObj(element, SELECT_BETWEEN)
-            #elif self._last_event.get_state() & gtk.gdk.CONTROL_MASK:
-                #if element.selected:
-                    #mode = UNSELECT
-                #else:
-                    #mode = SELECT_ADD
-                #timeline.setSelectionToObj(element, mode)
-            #else:
-            x, y = pos
-            x += self._hadj.get_value()
-            #self._view.app.current.seeker.seek(Zoomable.pixelToNs(x))
-                #timeline.setSelectionToObj(element, SELECT)
+                x, y = pos
+                x += self._hadj.get_value()
+                self._view.app.current.seeker.seek(Zoomable.pixelToNs(x))
+                timeline.selection.setToObj(element, SELECT)
 
     def __init__(self, instance, element, track, timeline, uTrack, is_transition=False):
         goocanvas.Group.__init__(self)
@@ -337,7 +361,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
         self.namewidth = 0
         self.nameheight = 0
         self.is_transition = is_transition
-        self.app.projectManager.current.connect("selected-changed", self.selected_changed)
+
         self.snapped_before = False
         self.snapped_after = False
 
@@ -362,13 +386,13 @@ class TrackObject(View, goocanvas.Group, Zoomable):
         self.end_handle = EndHandle(self.app, element, timeline,
             height=self.height)
 
-        self.selection_indicator = goocanvas.Rect(
+        self._selec_indic = goocanvas.Rect(
             visibility=goocanvas.ITEM_INVISIBLE,
             line_width=0.0,
             height=self.height)
 
         if not self.is_transition:
-            for thing in (self.bg, self.selection_indicator,
+            for thing in (self.bg, self._selec_indic,
                 self.start_handle, self.end_handle, self.namebg, self.name):
                 self.add_child(thing)
         else:
@@ -376,12 +400,15 @@ class TrackObject(View, goocanvas.Group, Zoomable):
                 self.add_child(thing)
 
         self.element = element
-        self.element.get_timeline_object().max_duration = self.element.get_timeline_object().get_property("duration")
-        self.element.max_duration = self.element.get_property("duration")
-        self.element.selected = False
-        self.element.starting_start = self.element.get_property("start")
+        element.max_duration = element.props.duration
+        element.starting_start = element.props.start
+        element.selected = Selected()
+        element.selected.connect("selected-changed", self.selectedChangedCb)
+
         obj = self.element.get_timeline_object()
         obj.starting_start = obj.get_property("start")
+        obj.max_duration = obj.props.duration
+
         self.settings = instance.settings
         self.unfocus()
 
@@ -463,7 +490,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
 
         self.namebg.props.fill_pattern = pattern
 
-        self.selection_indicator.props.fill_pattern = unpack_cairo_pattern(
+        self._selec_indic.props.fill_pattern = unpack_cairo_pattern(
             self.settings.selectedColor)
 
         self.name.props.font = self.settings.clipFontDesc
@@ -492,31 +519,11 @@ class TrackObject(View, goocanvas.Group, Zoomable):
     def startChangedCb(self, track_object, start):
         self._update()
 
-    def selected_changed(self, unused_project, element):
-        self.timeline.selected = []
-        if isinstance(element, set):
-            for elem in element:
-                elem.selected = True
-                self.timeline.selected.append(elem)
-            for elem in element:
-                if elem == self.element:
-                    self.selection_indicator.props.visibility = goocanvas.ITEM_VISIBLE
-                    elem.selected = True
-                elif self.element.selected == False:
-                    self.selection_indicator.props.visibility = \
-                        goocanvas.ITEM_INVISIBLE
-            for elem in element:
-                elem.selected = False
-            return
-
-        else:
-            self.timeline.selected.append(element)
-
-        if element == self.element:
-            self.selection_indicator.props.visibility = goocanvas.ITEM_VISIBLE
+    def selectedChangedCb(self, element, selected):
+        if element.selected:
+            self._selec_indic.props.visibility = goocanvas.ITEM_VISIBLE
         else:
-            self.selection_indicator.props.visibility = \
-                goocanvas.ITEM_INVISIBLE
+            self._selec_indic.props.visibility = goocanvas.ITEM_INVISIBLE
 
     def _update(self):
         try:
@@ -536,7 +543,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
         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.selection_indicator.props.width = width
+        self._selec_indic.props.width = width
         self.end_handle.props.x = w
         if self.expanded:
             if w - NAME_HOFFSET > 0:



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