[pitivi/ges: 181/287] utils/timeline: Move interfaces used in the Timeline to the utils folder



commit d60346f83797b7da8ea4877150f4952a05116ec7
Author: Thibault Saunier <thibault saunier collabora com>
Date:   Tue Jan 10 14:23:55 2012 -0300

    utils/timeline: Move interfaces used in the Timeline to the utils folder
    
    Move:
        + Zoomable
        + View
        + Controller

 pitivi/ui/Makefile.am       |    5 +-
 pitivi/ui/controller.py     |  241 -------------------
 pitivi/ui/curve.py          |    5 +-
 pitivi/ui/mainwindow.py     |    6 +-
 pitivi/ui/preview.py        |    2 +-
 pitivi/ui/previewer.py      |    2 +-
 pitivi/ui/ruler.py          |    4 +-
 pitivi/ui/timeline.py       |   14 +-
 pitivi/ui/timelinecanvas.py |   13 +-
 pitivi/ui/track.py          |    5 +-
 pitivi/ui/trackobject.py    |    8 +-
 pitivi/ui/view.py           |   24 --
 pitivi/ui/zoominterface.py  |  149 ------------
 pitivi/utils/timeline.py    |  548 +++++++++++++++++++++++++++++++++++--------
 tests/test_integration.py   |    2 +-
 15 files changed, 484 insertions(+), 544 deletions(-)
---
diff --git a/pitivi/ui/Makefile.am b/pitivi/ui/Makefile.am
index 4c1acd9..0a403ce 100644
--- a/pitivi/ui/Makefile.am
+++ b/pitivi/ui/Makefile.am
@@ -3,7 +3,6 @@ uidir = $(libdir)/pitivi/python/pitivi/ui
 ui_PYTHON =			\
 	__init__.py		\
 	alignmentprogress.py    \
-	controller.py		\
 	curve.py		\
 	depsmanager.py	\
 	filelisterrordialog.py	\
@@ -22,10 +21,8 @@ ui_PYTHON =			\
 	trackobject.py		\
 	track.py		\
 	viewer.py		\
-	view.py			\
 	clipproperties.py	\
-	filechooserpreview.py	\
-	zoominterface.py
+	filechooserpreview.py
 
 clean-local:
 	rm -rf *.pyc *.pyo
diff --git a/pitivi/ui/curve.py b/pitivi/ui/curve.py
index 7fa4549..d160532 100644
--- a/pitivi/ui/curve.py
+++ b/pitivi/ui/curve.py
@@ -27,10 +27,7 @@ import gobject
 import gtk
 
 from pitivi.utils.receiver import receiver, handler
-from pitivi.ui.zoominterface import Zoomable
-import pitivi.ui.previewer as previewer
-from pitivi.ui.view import View
-from pitivi.ui.controller import Controller
+from pitivi.utils.timeline import View, Controller, Zoomable
 from pitivi.utils.ui import LAYER_HEIGHT_EXPANDED, roundedrec, Point
 from pitivi.utils.misc import between
 
diff --git a/pitivi/ui/mainwindow.py b/pitivi/ui/mainwindow.py
index 5b8d4b4..5d24d70 100644
--- a/pitivi/ui/mainwindow.py
+++ b/pitivi/ui/mainwindow.py
@@ -37,17 +37,17 @@ from gtk import RecentManager
 
 from pitivi.utils.loggable import Loggable
 from pitivi.settings import GlobalSettings
+from pitivi.effects import EffectListWidget
+from pitivi.medialibrary import MediaLibraryWidget, MediaLibraryError
 
 from pitivi.utils.misc import show_user_manual
 from pitivi.utils.ui import SPACING, info_name, FILESOURCE_TUPLE, URI_TUPLE, \
          TYPE_URI_LIST, TYPE_PITIVI_FILESOURCE
+from pitivi.utils.timeline import Zoomable
 
 from pitivi.ui.timeline import Timeline
 from pitivi.ui.basetabs import BaseTabs
 from pitivi.ui.viewer import PitiviViewer
-from pitivi.medialibrary import MediaLibraryWidget, MediaLibraryError
-from pitivi.effects import EffectListWidget
-from pitivi.ui.zoominterface import Zoomable
 from pitivi.ui.clipproperties import ClipProperties
 from pitivi.ui.filechooserpreview import PreviewWidget
 
diff --git a/pitivi/ui/preview.py b/pitivi/ui/preview.py
index 605b613..1822fe4 100644
--- a/pitivi/ui/preview.py
+++ b/pitivi/ui/preview.py
@@ -28,7 +28,7 @@ import goocanvas
 import gobject
 
 from pitivi.utils.receiver import receiver, handler
-from pitivi.ui.zoominterface import Zoomable
+from pitivi.utils.timeline import Zoomable
 import pitivi.ui.previewer as previewer
 
 
diff --git a/pitivi/ui/previewer.py b/pitivi/ui/previewer.py
index 43fa8ec..e8ead1d 100644
--- a/pitivi/ui/previewer.py
+++ b/pitivi/ui/previewer.py
@@ -32,7 +32,7 @@ import pitivi.utils as utils
 from pitivi.configure import get_pixmap_dir
 from pitivi.utils.signal import Signallable
 from pitivi.settings import GlobalSettings
-from pitivi.ui.zoominterface import Zoomable
+from pitivi.utils.timeline import Zoomable
 from pitivi.utils.loggable import Loggable
 from pitivi.thumbnailcache import ThumbnailCache
 from pitivi.ui.prefs import PreferencesDialog
diff --git a/pitivi/ui/ruler.py b/pitivi/ui/ruler.py
index 9c5ab79..1ac12f6 100644
--- a/pitivi/ui/ruler.py
+++ b/pitivi/ui/ruler.py
@@ -28,9 +28,9 @@ import gtk
 import gst
 
 from pitivi.utils.playback import Seeker
-
-from pitivi.ui.zoominterface import Zoomable
+from pitivi.utils.timeline import Zoomable
 from pitivi.utils.loggable import Loggable
+
 from pitivi.utils.ui import time_to_string
 
 
diff --git a/pitivi/ui/timeline.py b/pitivi/ui/timeline.py
index e386ddb..9c3ba4b 100644
--- a/pitivi/ui/timeline.py
+++ b/pitivi/ui/timeline.py
@@ -24,25 +24,25 @@ Timeline widgets for the complex view
 """
 
 import gtk
-
-import ruler
 import gst
-import gobject
 import ges
+import ruler
+import gobject
 
 from gettext import gettext as _
 
-from zoominterface import Zoomable
 from pitivi.check import soft_deps
-from pitivi.utils.loggable import Loggable
+from pitivi.effects import AUDIO_EFFECT, VIDEO_EFFECT
+
 from timelinecanvas import TimelineCanvas
 from timelinecontrols import TimelineControls
-from pitivi.effects import AUDIO_EFFECT, VIDEO_EFFECT
-from pitivi.utils.timeline import MoveContext, SELECT
 
 from pitivi.ui.depsmanager import DepsManager
 from pitivi.ui.filelisterrordialog import FileListErrorDialog
 from pitivi.ui.alignmentprogress import AlignmentProgressDialog
+
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.timeline import MoveContext, SELECT, Zoomable
 from pitivi.utils.ui import SPACING, TRACK_SPACING, LAYER_HEIGHT_EXPANDED,\
     LAYER_SPACING, TYPE_PITIVI_FILESOURCE, VIDEO_EFFECT_TUPLE, \
     AUDIO_EFFECT_TUPLE, EFFECT_TUPLE, FILESOURCE_TUPLE, TYPE_PITIVI_EFFECT
diff --git a/pitivi/ui/timelinecanvas.py b/pitivi/ui/timelinecanvas.py
index 3242945..b4b966a 100644
--- a/pitivi/ui/timelinecanvas.py
+++ b/pitivi/ui/timelinecanvas.py
@@ -23,17 +23,18 @@ import gtk
 import goocanvas
 from gettext import gettext as _
 
-from pitivi.utils.loggable import Loggable
-from pitivi.utils.receiver import receiver, handler
+from pitivi.settings import GlobalSettings
+
 from pitivi.ui.track import Track
 from pitivi.ui.trackobject import TrackObject
-from pitivi.ui.zoominterface import Zoomable
-from pitivi.settings import GlobalSettings
 from pitivi.ui.prefs import PreferencesDialog
+from pitivi.ui.curve import KW_LABEL_Y_OVERFLOW
+
+from pitivi.utils.loggable import Loggable
+from pitivi.utils.receiver import receiver, handler
 from pitivi.utils.ui import TRACK_SPACING, unpack_cairo_pattern, \
         LAYER_HEIGHT_EXPANDED, LAYER_SPACING, Point
-from pitivi.ui.controller import Controller
-from pitivi.ui.curve import KW_LABEL_Y_OVERFLOW
+from pitivi.utils.timeline import Controller, Zoomable
 from pitivi.utils.ui import SPACING
 
 # cursors to be used for resizing objects
diff --git a/pitivi/ui/track.py b/pitivi/ui/track.py
index 12f9a6f..b5198e0 100644
--- a/pitivi/ui/track.py
+++ b/pitivi/ui/track.py
@@ -25,12 +25,13 @@ import ges
 import gobject
 
 from pitivi.utils.loggable import Loggable
-from pitivi.ui.zoominterface import Zoomable
+from pitivi.utils.timeline import Zoomable
 from pitivi.utils.receiver import receiver, handler
-from pitivi.ui.trackobject import TrackObject
 from pitivi.utils.ui import LAYER_HEIGHT_EXPANDED,\
         LAYER_HEIGHT_COLLAPSED, LAYER_SPACING
 
+from pitivi.ui.trackobject import TrackObject
+
 
 class Transition(goocanvas.Rect, Zoomable):
 
diff --git a/pitivi/ui/trackobject.py b/pitivi/ui/trackobject.py
index a57b761..034ab32 100644
--- a/pitivi/ui/trackobject.py
+++ b/pitivi/ui/trackobject.py
@@ -5,11 +5,8 @@ import pango
 import cairo
 
 import pitivi.configure as configure
-import controller
 
-from view import View
 from gettext import gettext as _
-from zoominterface import Zoomable
 from pitivi.utils.ui import LAYER_SPACING, unpack_cairo_pattern, \
     unpack_cairo_gradient, LAYER_HEIGHT_EXPANDED, LAYER_HEIGHT_COLLAPSED
 
@@ -20,7 +17,8 @@ from pitivi.utils.receiver import receiver, handler
 from pitivi.ui.prefs import PreferencesDialog
 from pitivi.utils.signal import Signallable
 from pitivi.utils.timeline import SELECT, SELECT_ADD, UNSELECT, \
-    SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext
+    SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext, Controller, \
+    View, Zoomable
 
 
 #--------------------------------------------------------------#
@@ -126,7 +124,7 @@ class Selected (Signallable):
     selected = property(getSelected, setSelected)
 
 
-class TimelineController(controller.Controller):
+class TimelineController(Controller):
 
     _cursor = ARROW
     _context = None
diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py
index 07eed83..6964288 100644
--- a/pitivi/utils/timeline.py
+++ b/pitivi/utils/timeline.py
@@ -21,12 +21,14 @@
 # Boston, MA 02110-1301, USA.
 
 import ges
-
-from gst import SECOND
+import gtk
+import gst
 
 from pitivi.utils.misc import infinity
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.signal import Signallable
+from pitivi.utils.receiver import receiver, handler
+from pitivi.utils.ui import Point
 
 #from pitivi.utils.align import AutoAligner
 
@@ -42,7 +44,12 @@ SELECT_BETWEEN = 3
 
 
 #------------------------------------------------------------------------------#
-#                              Private utililities                             #
+#                          Timeline Object management helper                   #
+class TimelineError(Exception):
+    """Base Exception for errors happening in L{Timeline}s or L{TimelineObject}s"""
+    pass
+
+
 def previous_track_source(focus, layer, start):
     """
     Get the source before @start in @track
@@ -213,13 +220,121 @@ class SmallestGapsFinder(object):
         return gap_objects.issubset(self.internal_objects)
 
 
-#-----------------------------------------------------------------------------#
-#                           Public Classes                                    #
-class TimelineError(Exception):
-    """Base Exception for errors happening in L{Timeline}s or L{TimelineObject}s"""
-    pass
+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():
+                if not isinstance(tckobj, ges.TrackEffect):
+                    tckobj.selected.selected = True
+
+        for obj in old_selection - self.selected:
+            for tckobj in obj.get_track_objects():
+                if not isinstance(tckobj, ges.TrackEffect):
+                    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.get_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)
+
+
+#-----------------------------------------------------------------------------#
+#                           Timeline edition modes helpers                    #
 class EditingContext(object):
 
     DEFAULT = 0
@@ -551,7 +666,7 @@ class MoveContext(EditingContext, Loggable):
 
             if prev:
                 prev_end = prev.get_start() + prev.get_duration()
-                if abs(start - prev_end) < SECOND:
+                if abs(start - prev_end) < gst.SECOND:
                     self.debug("Snaping to edge frontward, diff=%d",
                             abs(start - prev_end))
                     return prev_end
@@ -561,7 +676,7 @@ class MoveContext(EditingContext, Loggable):
                         tckobj.get_timeline_object().get_layer(), start,
                         end - start)
 
-                if next and abs(end - next.get_start()) < SECOND:
+                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)
@@ -837,114 +952,359 @@ class TrimEndContext(EditingContext):
             self._defaultTo(duration, obj.props.priority)
 
 
-class Selection(Signallable):
-    """
-    A collection of L{ges.TimelineObject}.
+#-------------------------- Interfaces ----------------------------------------#
 
-    Signals:
-     - C{selection-changed} : The contents of the L{ges.Selection} changed.
+ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
 
-    @ivar selected: Set of selected L{ges.TrackObject}
-    @type selected: C{list}
-    """
 
-    __signals__ = {
-        "selection-changed": []}
+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"""
+
+    # note we SHOULD be using the gtk function for this, but it doesn't appear
+    # to be exposed in pygtk
+    __DRAG_THRESHOLD__ = Point(0, 0)
+
+    _view = receiver()
+
+    _dragging = None
+    _canvas = None
+    _cursor = None
+    _ptr_within = False
+    _last_click = None
+    _initial = None
+    _mousedown = None
+    _last_event = None
+    _pending_drag_start = None
+    _pending_drag_end = False
+    _shift_down = False
+    _control_down = False
+    _handle_enter_leave = True
+    _handle_mouse_up_down = True
+    _handle_motion_notify = True
+
+    def __init__(self, view=None):
+        object.__init__(self)
+        self._view = view
+        Loggable.__init__(self)
+
+## convenience functions
+
+    def from_event(self, event):
+        """returns the coordinates of an event"""
+        return Point(*self._canvas.convert_from_pixels(event.x, event.y))
+
+    def from_item_event(self, item, event):
+        return Point(*self._canvas.convert_from_item_space(item,
+            *self.from_event(event)))
+
+    def to_item_space(self, item, point):
+        return Point(*self._canvas.convert_to_item_space(item, *point))
+
+    def pos(self, item):
+        bounds = item.get_bounds()
+        return Point(bounds.x1, bounds.y1)
+
+## signal handlers
+
+    @handler(_view, "enter_notify_event")
+    def enter_notify_event(self, item, target, event):
+        self._event_common(item, target, event)
+        self._canvas.grab_focus(item)
+        if self._cursor and item is target:
+            event.window.set_cursor(self._cursor)
+        if not self._dragging:
+            self.enter(item, target)
+        self._ptr_within = True
+        return self._handle_enter_leave or self._dragging
+
+    @handler(_view, "leave_notify_event")
+    def leave_notify_event(self, item, target, event):
+        self._event_common(item, target, event)
+        self._canvas.keyboard_ungrab(item, event.time)
+        self._ptr_within = False
+        if not self._dragging:
+            self.leave(item, target)
+            event.window.set_cursor(ARROW)
+        return self._handle_enter_leave or self._dragging
+
+    @handler(_view, "button_press_event")
+    def button_press_event(self, item, target, event):
+        self._event_common(item, target, event)
+        if not self._canvas:
+            self._canvas = item.get_canvas()
+        self._mousedown = self.pos(item) - self.transform(self.from_item_event(
+            item, event))
+        self._dragging = target
+        self._initial = self.pos(target)
+        self._pending_drag_start = (item, target, event)
+        return self._handle_mouse_up_down
+
+    @handler(_view, "motion_notify_event")
+    def motion_notify_event(self, item, target, event):
+        self._event_common(item, target, event)
+        if self._dragging:
+            if self._pending_drag_start is not None:
+                pending_drag_start, self._pending_drag_start = \
+                        self._pending_drag_start, None
+                self._pending_drag_end = True
+                self._drag_start(*pending_drag_start)
+
+            self.set_pos(self._dragging,
+                self.transform(self._mousedown + self.from_item_event(item,
+                    event)))
+            return self._handle_motion_notify
+        else:
+            self.hover(item, target, event)
+        return False
+
+    @handler(_view, "button_release_event")
+    def button_release_event(self, item, target, event):
+        self._event_common(item, target, event)
+        self._drag_end(item, self._dragging, event)
+        self._dragging = None
+        return self._handle_mouse_up_down
+
+    @handler(_view, "key_press_event")
+    def key_press_event(self, item, target, event):
+        self._event_common(item, target, event)
+        kv = event.keyval
+        if kv in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R):
+            self._shift_down = True
+        elif kv in (gtk.keysyms.Control_L, gtk.keysyms.Control_R):
+            self._control_down = True
+        return self.key_press(kv)
+
+    @handler(_view, "key_release_event")
+    def key_release_event(self, item, target, event):
+        self._event_common(item, target, event)
+        kv = event.keyval
+        if kv in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R):
+            self._shift_down = False
+        elif kv in (gtk.keysyms.Control_L, gtk.keysyms.Control_R):
+            self._control_down = False
+        return self.key_release(kv)
+
+## internal callbacks
+
+    def _event_common(self, item, target, event):
+        if not self._canvas:
+            self._canvas = item.get_canvas()
+            # might there be a better way to do this?
+            self._hadj = self._canvas.app.gui.timeline.hadj
+            self._vadj = self._canvas.app.gui.timeline.vadj
+        self._last_event = event
+        s = event.get_state()
+        self._shift_down = s & gtk.gdk.SHIFT_MASK
+        self._control_down = s & gtk.gdk.CONTROL_MASK
+
+    def _drag_start(self, item, target, event):
+        self.drag_start(item, target, event)
+
+    def _drag_end(self, item, target, event):
+        self._pending_drag_start = None
+        pending_drag_end, self._pending_drag_end = self._pending_drag_end, False
+        if pending_drag_end:
+            self.drag_end(item, target, event)
+
+        if self._ptr_within and self._drag_threshold():
+            point = self.from_item_event(item, event)
+            if self._last_click and (event.time - self._last_click < 400):
+                self.double_click(point)
+            else:
+                self.click(point)
+            self._last_click = event.time
+            event.window.set_cursor(self._cursor)
+        else:
+            event.window.set_cursor(ARROW)
+
+    def _drag_threshold(self):
+        last = self.pos(self._dragging)
+        difference = abs(self._initial - last)
+        if abs(self._initial - last) > self.__DRAG_THRESHOLD__:
+            return False
+        return True
+
+## protected interface for subclasses
+
+    def click(self, pos):
+        pass
+
+    def double_click(self, pos):
+        pass
+
+    def drag_start(self, item, target, event):
+        pass
+
+    def drag_end(self, item, target, event):
+        pass
+
+    def set_pos(self, obj, pos):
+        obj.props.x, obj.props.y = pos
+
+    def transform(self, pos):
+        return pos
+
+    def enter(self, item, target):
+        pass
+
+    def leave(self, item, target):
+        pass
+
+    def key_press(self, keyval):
+        pass
+
+    def key_release(self, keyval):
+        pass
+
+    def hover(self, item, target, event):
+        pass
+
+
+class View(object):
+
+    Controller = Controller
 
     def __init__(self):
-        self.selected = set([])
-        self.last_single_obj = None
+        object.__init__(self)
+        self._controller = self.Controller(view=self)
 
-    def setToObj(self, obj, mode):
-        """
-        Convenience method for calling L{setSelection} with a single L{ges.TimelineObject}
+## public interface
 
-        @see: L{setSelection}
-        """
-        self.setSelection(set([obj]), mode)
+    def focus(self):
+        pass
 
-    def addTimelineObject(self, timeline_object):
-        """
-        Add the given timeline_object to the selection.
+    def select(self):
+        pass
 
-        @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 activate(self):
+        pass
 
-    def setSelection(self, objs, mode):
-        """
-        Update the current selection.
+    def normal(self):
+        pass
 
-        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}
+class Zoomable(object):
+    """
+    Interface for managing tranformation between timeline timestamps and UI
+    pixels.
+
+    Complex Timeline interfaces v2 (01 Jul 2008)
+
+    Zoomable
+    -----------------------
+    Interface for the Complex Timeline widgets for setting, getting,
+    distributing and modifying the zoom ratio and the size of the widget.
+
+    A zoomratio is the number of pixels per second
+    ex : 10.0 = 10 pixels for a second
+    ex : 0.1 = 1 pixel for 10 seconds
+    ex : 1.0 = 1 pixel for a second
+     Class Methods
+    . pixelToNs(pixels)
+    . nsToPixels(time)
+    . setZoomRatio
+    Instance Methods
+    . zoomChanged()
+    """
 
-        @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)
+    sigid = None
+    _instances = []
+    max_zoom = 1000.0
+    min_zoom = 0.25
+    zoom_steps = 100
+    zoom_range = max_zoom - min_zoom
+    _cur_zoom = 2
+    zoomratio = None
 
-        old_selection = self.selected
-        if mode == SELECT_ADD:
-            selection = self.selected | selection
-        elif mode == UNSELECT:
-            selection = self.selected - selection
-        self.selected = selection
+    def __init__(self):
+        # FIXME: ideally we should deprecate this
+        Zoomable.addInstance(self)
+        if Zoomable.zoomratio is None:
+            Zoomable.zoomratio = self.computeZoomRatio(self._cur_zoom)
 
-        if len(self.selected) == 1:
-            self.last_single_obj = iter(selection).next()
+    def __del__(self):
+        if self in Zoomable._instances:
+            # FIXME: ideally we should deprecate this and spit a warning here
+            self._instances.remove(self)
 
-        for obj in self.selected - old_selection:
-            for tckobj in obj.get_track_objects():
-                if not isinstance(tckobj, ges.TrackEffect):
-                    tckobj.selected.selected = True
+    @classmethod
+    def addInstance(cls, instance):
+        cls._instances.append(instance)
 
-        for obj in old_selection - self.selected:
-            for tckobj in obj.get_track_objects():
-                if not isinstance(tckobj, ges.TrackEffect):
-                    tckobj.selected.selected = False
+    @classmethod
+    def removeInstance(cls, instance):
+        cls._instances.remove(instance)
 
-        # FIXME : shouldn't we ONLY emit this IFF the selection has changed ?
-        self.emit("selection-changed")
+    @classmethod
+    def setZoomRatio(cls, ratio):
+        if cls.zoomratio != ratio:
+            cls.zoomratio = min(cls.max_zoom, max(cls.min_zoom, ratio))
+            cls._zoomChanged()
 
-    def getSelectedTrackObjs(self):
+    @classmethod
+    def setZoomLevel(cls, level):
+        level = min(cls.zoom_steps, max(0, level))
+        if level != cls._cur_zoom:
+            cls._cur_zoom = level
+            cls.setZoomRatio(cls.computeZoomRatio(level))
+
+    @classmethod
+    def getCurrentZoomLevel(cls):
+        return cls._cur_zoom
+
+    @classmethod
+    def zoomIn(cls):
+        cls.setZoomLevel(cls._cur_zoom + 1)
+
+    @classmethod
+    def zoomOut(cls):
+        cls.setZoomLevel(cls._cur_zoom - 1)
+
+    @classmethod
+    def computeZoomRatio(cls, x):
+        return ((((float(x) / cls.zoom_steps) ** 3) * cls.zoom_range) +
+            cls.min_zoom)
+
+    @classmethod
+    def computeZoomLevel(cls, ratio):
+        return int((
+            (max(0, ratio - cls.min_zoom) /
+                cls.zoom_range) ** (1.0 / 3.0)) *
+                    cls.zoom_steps)
+
+    @classmethod
+    def pixelToNs(cls, pixel):
         """
-        Returns the list of L{TrackObject} contained in this selection.
+        Returns the pixel equivalent in nanoseconds according to the zoomratio
         """
-        objects = []
-        for timeline_object in self.selected:
-            objects.extend(timeline_object.get_track_objects())
+        return long(pixel * gst.SECOND / cls.zoomratio)
 
-        return set(objects)
-
-    def getSelectedTrackEffects(self):
+    @classmethod
+    def pixelToNsAt(cls, pixel, ratio):
         """
-        Returns the list of L{TrackEffect} contained in this selection.
+        Returns the pixel equivalent in nanoseconds according to the zoomratio
         """
-        track_effects = []
-        for timeline_object in self.selected:
-            for track in timeline_object.get_track_objects():
-                if isinstance(track, ges.TrackEffect):
-                    track_effects.append(track)
+        return long(pixel * gst.SECOND / ratio)
 
-        return track_effects
+    @classmethod
+    def nsToPixel(cls, duration):
+        """
+        Returns the pixel equivalent of the given duration, according to the
+        set zoom ratio
+        """
+        ## DIE YOU CUNTMUNCH CLOCK_TIME_NONE UBER STUPIDITY OF CRACK BINDINGS !!!!!!
+        if duration == 18446744073709551615 or \
+               long(duration) == long(gst.CLOCK_TIME_NONE):
+            return 0
+        return int((float(duration) / gst.SECOND) * cls.zoomratio)
 
-    def __len__(self):
-        return len(self.selected)
+    @classmethod
+    def _zoomChanged(cls):
+        for inst in cls._instances:
+            inst.zoomChanged()
 
-    def __iter__(self):
-        return iter(self.selected)
+    def zoomChanged(self):
+        pass
diff --git a/tests/test_integration.py b/tests/test_integration.py
index b6476ce..0cbe029 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -307,7 +307,7 @@ class InstanceRunner(Signallable):
         if self.no_ui:
             self.instance.run(["--no-ui"])
         else:
-            from pitivi.ui.zoominterface import Zoomable
+            from pitivi.utils.timeline import Zoomable
             # set a common zoom ratio so that things like edge snapping values
             # are consistent
             Zoomable.setZoomLevel((3 * Zoomable.zoom_steps) / 4)



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