pitivi r1385 - in trunk: bin pitivi pitivi/ui



Author: edwardrv
Date: Fri Nov 28 17:05:03 2008
New Revision: 1385
URL: http://svn.gnome.org/viewvc/pitivi?rev=1385&view=rev

Log:
checkign in stuff i forgot to commit before, incremental progress towards getting new version of TimelineObject to actually work

Added:
   trunk/pitivi/ui/timelinecanvas.py
   trunk/pitivi/ui/timelineobject.py
   trunk/pitivi/ui/track.py
Modified:
   trunk/bin/pitivi.in
   trunk/pitivi/receiver.py
   trunk/pitivi/ui/controller.py
   trunk/pitivi/ui/point.py
   trunk/pitivi/ui/view.py

Modified: trunk/bin/pitivi.in
==============================================================================
--- trunk/bin/pitivi.in	(original)
+++ trunk/bin/pitivi.in	Fri Nov 28 17:05:03 2008
@@ -110,11 +110,7 @@
 def _run_pitivi():
     import pitivi.pitivi as ptv
 
-    try:
-        sys.exit(ptv.main(sys.argv))
-    except StandardError, e:
-        print "Error: ", e
-        sys.exit(1)
+    sys.exit(ptv.main(sys.argv))
 
 try:
     _add_pitivi_path()

Modified: trunk/pitivi/receiver.py
==============================================================================
--- trunk/pitivi/receiver.py	(original)
+++ trunk/pitivi/receiver.py	Fri Nov 28 17:05:03 2008
@@ -24,7 +24,7 @@
             for id in self.sigids.itervalues():
                 self.sender.disconnect(id)
             self.sender = None
-            self.sigids = None
+            self.sigids = {}
         if value:
             for sig, hdlr in self.handlers.iteritems():
                 value.connect(sig, MethodType(hdlr, instance))

Modified: trunk/pitivi/ui/controller.py
==============================================================================
--- trunk/pitivi/ui/controller.py	(original)
+++ trunk/pitivi/ui/controller.py	Fri Nov 28 17:05:03 2008
@@ -20,11 +20,11 @@
 
     _dragging = None
     _canvas = None
-    _mouse_down = None
     _ptr_within = False
     _last_click = None
 
     def __init__(self, view=None):
+        object.__init__(self)
         self._view = view
 
 ## signal handlers
@@ -47,8 +47,6 @@
         if not self._canvas:
             self._canvas = item.get_canvas()
         self._dragging = target
-        self._mouse_down = self.pos - self.transform(Point.from_event,
-            canvas, event)
         self._drag_start(item, target, event)
         return True
 
@@ -56,8 +54,9 @@
     def motion_notify_event(self, item, target, event):
         if self._dragging:
             self.set_pos(self._dragging, 
-                self.transform(self._mouse_down + Point.from_event(canvas,
-                    event)))
+                self.transform(
+                    Point.from_event(self._canvas, event).from_item_space(
+                        self._canvas, item)))
             return True
         return False
 
@@ -69,7 +68,7 @@
 
 ## internal callbacks
 
-   def _drag_start(self, item, target, event):
+    def _drag_start(self, item, target, event):
         self._view.activate()
         self.drag_start()
 

Modified: trunk/pitivi/ui/point.py
==============================================================================
--- trunk/pitivi/ui/point.py	(original)
+++ trunk/pitivi/ui/point.py	Fri Nov 28 17:05:03 2008
@@ -1,27 +1,23 @@
+from itertools import izip
+
 class Point(tuple):
     
-    def __new__(self, x, y):
-        return Point(x, y)
-
-    def __mul__(p1, p2):
-        """Returns the 2-dvector difference p1 - p2"""
-        p1_x, p1_y = p1
-        p2_x, p2_y = p2
-        return Point(p1_x - p2_x, p1_y - p2_y)
+    def __new__(cls, x, y):
+        return tuple.__new__(cls, (x, y))
 
     def __pow__(p1, scalar):
         """Returns the scalar multiple p1, scalar"""
         return Point(p1[0] * scalar, p1[1] * scalar)
 
-    # needed to support the case where you have <number> * point
     def __rpow__(p2, scalar):
         """Returns the scalar multiple of p2, scalar"""
         return p2 ** scalar
 
+    def __mul__(p1, p2):
+        return Point(*(a * b for a, b in izip(p1, p2)))
+
     def __div__(self, other):
-        p1_x, p1_y = p1
-        p2_x, p2_y = p2
-        return Point(p1_x / p2_x, p1_y / p2_y)
+        return Point(*(a / b for a, b in izip(p1, p2)))
 
     def __floordiv__(p1, scalar):
         """Returns the scalar division of self and scalar"""
@@ -29,15 +25,11 @@
 
     def __add__(p1, p2):
         """Returns the 2d vector sum p1 + p2"""
-        p1_x, p1_y = p1
-        p2_x, p2_y = p2
-        return Point(p1_x + p2_x, p1_y + p2_y)
+        return Point(*(a + b for a, b in izip(p1, p2)))
 
     def __sub__(p1, p2):
         """Returns the 2-dvector difference p1 - p2"""
-        p1_x, p1_y = p1
-        p2_x, p2_y = p2
-        return Point(p1_x - p2_x, p1_y - p2_y)
+        return Point(*(a - b for a, b in izip(p1, p2)))
 
     ## utility functions for working with points
     @classmethod
@@ -45,3 +37,6 @@
         """returns the coordinates of an event"""
         return Point(*canvas.convert_from_pixels(event.x, event.y))
 
+    def from_item_space(self, canvas, item):
+        return Point(*canvas.convert_from_item_space(item, self[0], self[1]))
+

Added: trunk/pitivi/ui/timelinecanvas.py
==============================================================================
--- (empty file)
+++ trunk/pitivi/ui/timelinecanvas.py	Fri Nov 28 17:05:03 2008
@@ -0,0 +1,285 @@
+from util import *
+from track import Track
+from pitivi.utils import closest_item
+import goocanvas
+from complexinterface import Zoomable
+import pitivi.instance as instance
+
+
+RAZOR_LINE = (
+    goocanvas.Rect,
+    {
+        "line_width" : 0,
+        "fill_color" : "orange",
+        "width" : 1,
+    },
+    {}
+)
+
+# the vsiual appearance for the selection marquee
+MARQUEE = (
+    goocanvas.Rect,
+    {
+        "stroke_color_rgba" : 0x33CCFF66,
+        "fill_color_rgba" : 0x33CCFF66,
+    },
+    {}
+)
+
+# cursors to be used for resizing objects
+ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
+# TODO: replace this with custom cursor
+RAZOR_CURSOR = gtk.gdk.Cursor(gtk.gdk.XTERM)
+
+# FIXME: do we want this expressed in pixels or miliseconds?
+# If we express it in miliseconds, then we can have the core handle edge
+# snapping (it's really best implemented in the core). On the other hand, if
+# the dead-band is a constant unit of time, it will be too large at high zoom,
+# and too small at low zoom. So we might want to be able to adjust the
+# deadband from the UI.
+# default number of pixels to use for edge snaping
+DEADBAND = 5
+
+class TimelineCanvas(goocanvas.Canvas, Zoomable):
+    """ Souped-up VBox that contains the timeline's CompositionLayer """
+
+    def __init__(self, layerinfolist):
+        goocanvas.Canvas.__init__(self)
+        self._selected_sources = []
+        self._timeline_position = 0
+
+        self._block_size_request = False
+        self.props.integer_layout = True
+        self.props.automatic_bounds = False
+
+        self.layerInfoList = layerinfolist
+        self.layerInfoList.connect('layer-added', self._layerAddedCb)
+        self.layerInfoList.connect('layer-removed', self._layerRemovedCb)
+
+        self._createUI()
+        self.connect("size_allocate", self._size_allocate)
+       
+    def _createUI(self):
+        self._cursor = ARROW
+
+        self.layers = VList(canvas=self)
+        self.layers.connect("notify::width", self._request_size)
+        self.layers.connect("notify::height", self._request_size)
+
+        root = self.get_root_item()
+        root.add_child(self.layers)
+
+        root.connect("enter_notify_event", self._mouseEnterCb)
+        self._marquee = make_item(MARQUEE)
+        manage_selection(self, self._marquee, True, self._selection_changed_cb)
+
+        self._razor = make_item(RAZOR_LINE)
+        self._razor.props.visibility = goocanvas.ITEM_INVISIBLE
+        root.add_child(self._razor)
+
+## methods for dealing with updating the canvas size
+
+    def block_size_request(self, status):
+        self._block_size_request = status
+
+    def _size_allocate(self, unused_layout, allocation):
+        self._razor.props.height = allocation.height
+
+    def _request_size(self, unused_item, unused_prop):
+        #TODO: figure out why this doesn't work... (wtf?!?)
+        if self._block_size_request:
+            return True
+        # we only update the bounds of the canvas by chunks of 100 pixels
+        # in width, otherwise we would always be redrawing the whole canvas.
+        # Make sure canvas is at least 800 pixels wide, and at least 100 pixels 
+        # wider than it actually needs to be.
+        w = max(800, ((int(self.layers.width + 100) / 100) + 1 ) * 100)
+        h = int(self.layers.height)
+        x1, y1, x2, y2 = self.get_bounds()
+        pw = abs(x2 - x1)
+        ph = abs(y2 - y1)
+        if not (w == pw and h == ph):
+            self.set_bounds(0, 0, w, h)
+        return True
+
+## mouse callbacks
+
+    def _mouseEnterCb(self, unused_item, unused_target, event):
+        event.window.set_cursor(self._cursor)
+        return True
+
+## Editing Operations
+
+    # FIXME: here once again we're doing something that would be better done
+    # in the core. As we add different types of objects in the Core, we'll
+    # have to modify this code here (maybe there are different ways of
+    # deleting different objects: you might delete() a source, but unset() a
+    # keyframe)
+
+    def deleteSelected(self, unused_action):
+        for obj in self._selected_sources:
+            if obj.comp:
+                obj.comp.removeSource(obj.element, remove_linked=True, 
+                    collapse_neighbours=False)
+        set_selection(self, set())
+        return True
+
+
+    # FIXME: the razor is the one toolbar tool that violates the noun-verb
+    # principle. Do I really want to make an exception for this? What about
+    # just double-clicking on the source like jokosher?
+
+    def activateRazor(self, unused_action):
+        self._cursor = RAZOR_CURSOR
+        # we don't want mouse events passing through to the canvas items
+        # underneath, so we connect to the canvas's signals
+        self._razor_sigid = self.connect("button_press_event", 
+            self._razorClickedCb)
+        self._razor_motion_sigid = self.connect("motion_notify_event",
+            self._razorMovedCb)
+        self._razor.props.visibility = goocanvas.ITEM_VISIBLE
+        return True
+
+    def _razorMovedCb(self, canvas, event):
+        x, y = event_coords(self, event)
+        self._razor.props.x = self.nsToPixel(self.pixelToNs(x))
+        return True
+
+    def _razorClickedCb(self, unused_canvas, event):
+        self._cursor = ARROW
+        event.window.set_cursor(ARROW)
+        self.disconnect(self._razor_sigid)
+        self.disconnect(self._razor_motion_sigid)
+        self._razor.props.visibility = goocanvas.ITEM_INVISIBLE
+
+        # Find the topmost source under the mouse. This is tricky because not
+        # all objects in the timeline are TimelineObjects. Some of them
+        # are drag handles, for example. For now, only objects marked as
+        # selectable should be sources
+        x, y = event_coords(self, event)
+        items = self.get_items_at(x, y, True)
+        if not items:
+            return True
+        for item in items:
+            if item.get_data("selectable"):
+                parent = item.get_parent()
+                gst.log("attempting to split source at position %d" %  x)
+                self._splitSource(parent, self.pixelToNs(x))
+        return True
+
+    # FIXME: this DEFINITELY needs to be in the core. Also, do we always want
+    # to split linked sources? Should the user be forced to un-link linked
+    # sources when they only wisth to split one of them? If not, 
+
+    def _splitSource(self, obj, editpoint):
+        comp = obj.comp
+        element = obj.element
+
+        # we want to divide element in elementA, elementB at the
+        # edit point.
+        a_start = element.start
+        a_end = editpoint
+        b_start = editpoint
+        b_end = element.start + element.duration
+
+        # so far so good, but we need this expressed in the form
+        # start/duration.
+        a_dur = a_end - a_start
+        b_dur = b_end - b_start
+        if not (a_dur and b_dur):
+            gst.Log("cannot cut at existing edit point, aborting")
+            return
+
+        # and finally, we need the media-start/duration for both sources.
+        # in this case, media-start = media-duration, but this would not be
+        # true if timestretch were applied to either source. this is why I
+        # really think we should not have to care about media-start /duratoin
+        # here, and have a more abstract method for setting time stretch that
+        # would keep media start/duration in sync for sources that have it.
+        a_media_start = element.media_start
+        b_media_start = a_media_start + a_dur
+
+        # trim source a
+        element.setMediaStartDurationTime(a_media_start, a_dur)
+        element.setStartDurationTime(a_start, a_dur)
+
+        # add source b
+        # TODO: for linked sources, split linked and create brother
+        # TODO: handle other kinds of sources
+        new = TimelineFileSource(factory=element.factory,
+            media_type=comp.media_type)
+        new.setMediaStartDurationTime(b_media_start, b_dur)
+        new.setStartDurationTime(b_start, b_dur)
+        comp.addSource(new, 0, True)
+
+    # FIXME: should be implemented in core, if at all. Another alternative
+    # would be directly suppporting ripple edits in the core, rather than
+    # doing select after + move selection. 
+
+    def selectBeforeCurrent(self, unused_action):
+        pass
+
+    def selectAfterCurrent(self, unused_action):
+        ## helper function
+        #def source_pos(ui_obj):
+        #    return ui_obj.comp.getSimpleSourcePosition(ui_obj.element)
+
+        ## mapping from composition -> (source1, ... sourceN)
+        #comps = dict()
+        #for source in self._selected_sources:
+        #    if not source.comp in comps:
+        #        comps[source.comp] = []
+        #    comps[source.comp].append(source)
+
+        ## find the latest source in each compostion, and all sources which
+        ## occur after it. then select them.
+        #to_select = set()
+        #for comp, sources in comps.items():
+        #    # source positions start at 1, not 0.
+        #    latest = max((source_pos(source) for source in sources)) - 1
+        #    # widget is available in "widget" data member of object.
+        #    # we add the background of the widget, not the widget itself.
+        #    objs = [obj.get_data("widget").bg for obj in comp.condensed[latest:]]
+        #    to_select.update(set(objs))
+        #set_selection(self, to_select)
+        pass
+
+    def _selection_changed_cb(self, selected, deselected):
+        # TODO: filter this list for things other than sources, and put them
+        # into appropriate lists
+        for item in selected:
+            item.props.fill_color_rgba = item.get_data("selected_color")
+            parent = item.get_parent()
+            self._selected_sources.append(parent)
+        for item in deselected:
+            item.props.fill_color_rgba = item.get_data("normal_color")
+            parent = item.get_parent()
+            self._selected_sources.remove(parent)
+
+    def timelinePositionChanged(self, value, unused_frame):
+        self._timeline_position = value
+
+## Zoomable Override
+
+    def zoomChanged(self):
+        instance.PiTiVi.current.timeline.setDeadband(self.pixelToNs(DEADBAND))
+
+    def setChildZoomAdjustment(self, adj):
+        for layer in self.layers:
+            layer.setZoomAdjustment(adj)
+
+## LayerInfoList callbacks
+
+    def _layerAddedCb(self, unused_infolist, layer, position):
+        track = Track()
+        track.setZoomAdjustment(self.getZoomAdjustment())
+        track.set_composition(layer.composition)
+        track.set_canvas(self)
+        self.layers.insert_child(track, position)
+        self.set_bounds(0, 0, self.layers.width, self.layers.height)
+        self.set_size_request(int(self.layers.width), int(self.layers.height))
+
+    def _layerRemovedCb(self, unused_layerInfoList, position):
+        child = self.layers.item_at(position)
+        self.layers.remove_child(child)
+#

Added: trunk/pitivi/ui/timelineobject.py
==============================================================================
--- (empty file)
+++ trunk/pitivi/ui/timelineobject.py	Fri Nov 28 17:05:03 2008
@@ -0,0 +1,129 @@
+import goocanvas
+import gobject
+import gtk
+import os.path
+import pango
+import pitivi.instance as instance
+from urllib import unquote
+from pitivi.receiver import receiver, handler
+from view import View
+import controller
+from complexinterface import Zoomable
+
+LEFT_SIDE = gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE)
+RIGHT_SIDE = gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE)
+
+class TrimHandle(goocanvas.Rect):
+
+    """A component of a TimelineObject which manage's the source's edit
+    points"""
+
+    element = receiver()
+
+    def __init__(self, element):
+        self.element = element
+        goocanvas.Rect.__init__(self,
+            width=5,
+            fill_color_rgba=0x00000022,
+            line_width=0
+        )
+
+class StartHandle(TrimHandle):
+
+    """Subclass of TrimHandle wich sets the object's start time"""
+
+    class Controller(controller.Controller):
+
+        def set_pos(self, obj, pos):
+            self._view.element.snapInTime(self.pixelToNs(pos[0]))
+
+class EndHandle(controller.Controller):
+
+    """Subclass of TrimHandle which sets the objects's end time"""
+
+    class Controller(controller.Controller):
+
+        def set_pos(self, obj, pos):
+            self._view.element.snapOutTime(self.pixelToNs(pos[0]))
+
+class TimelineObject(View, goocanvas.Group, Zoomable):
+
+    element = receiver()
+
+    __HEIGHT__ = 50
+    __NORMAL__ = 0x709fb899
+    __SELECTED__ = 0xa6cee3AA
+
+    class Controller(controller.Controller):
+
+        def drag_start(self):
+            item.raise_(None)
+            instance.PiTiVi.current.timeline.disableEdgeUpdates()
+
+        def drag_end(self):
+            instance.PiTiVi.current.timeline.enableEdgeUpdates()
+
+        def set_pos(self, item, pos):
+            self._view.element.snapStartDurationTime(max(
+                self._view.pixelToNs(pos[0]), 0))
+
+        def click(self, pos):
+            self._view.select()
+
+    def __init__(self, element, composition, style):
+        goocanvas.Group.__init__(self)
+        View.__init__(self)
+
+        self.element = element
+        self.comp = composition
+
+        self.bg = goocanvas.Rect(
+            height=self.__HEIGHT__, 
+            line_width=0)
+
+        self.name = goocanvas.Text(
+            x=10,
+            y=10,
+            text=os.path.basename(unquote(element.factory.name)),
+            font="Sans 9",
+            fill_color_rgba=0x000000FF,
+            alignment=pango.ALIGN_LEFT)
+ 
+        #self.start_handle = StartHandle(element)
+        #self.end_handle = EndHandle(element)
+
+        #for thing in (self.bg, self.start_handle, self.end_handle, self.name):
+        for thing in (self.bg, self.name):
+            self.add_child(thing)
+
+        self.normal()
+
+    def select(self):
+        self.bg.props.fill_color_rgba = self.__SELECTED__
+
+    def normal(self):
+        self.bg.props.fill_color_rgba = self.__NORMAL__
+
+    ## only temporary
+    x = gobject.property(type=float)
+    y = gobject.property(type=float)
+    width = gobject.property(type=float)
+    height = gobject.property(type=float, default=__HEIGHT__)
+
+    @handler(element, "start-duration-changed")
+    def _start_duration_cb(self, obj, start, duration):
+        # set our position with set_simple_transform
+        self.x = self.nsToPixel(start)
+        self.set_simple_transform(self.nsToPixel(start), 0, 1, 0)
+
+        # clip text to within object bounds
+        width = self.nsToPixel(duration)
+        self.width = width
+        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (
+            10, 0, width, self.__HEIGHT__, width - 10)
+
+        # size background according to duration
+        self.bg.props.width = width
+
+        # place end handle at appropriate distance
+        #self.end_handle.props.x = width - 10

Added: trunk/pitivi/ui/track.py
==============================================================================
--- (empty file)
+++ trunk/pitivi/ui/track.py	Fri Nov 28 17:05:03 2008
@@ -0,0 +1,46 @@
+from util import *
+from complexinterface import Zoomable
+from timelineobject import TimelineObject
+from pitivi.timeline.objects import MEDIA_TYPE_VIDEO
+import pitivi.instance as instance
+
+class Track(SmartGroup, Zoomable):
+    __gtype_name__ = 'Track'
+
+    def __init__(self, *args, **kwargs):
+        SmartGroup.__init__(self, *args, **kwargs)
+        # FIXME: all of these should be private
+        self.widgets = {}
+        self.elements = {}
+        self.sig_ids = None
+        self.comp = None
+        self.object_style = None
+
+    # FIXME: this should be set_model(), overriding BaseView
+    def set_composition(self, comp):
+        if self.sig_ids:
+            for sig in self.sig_ids:
+                comp.disconnect(sig)
+        self.comp = comp
+        if comp:
+            added = comp.connect("source-added", self._objectAdded)
+            removed = comp.connect("source-removed", self._objectRemoved)
+            self.sig_ids = (added, removed)
+
+    def _objectAdded(self, unused_timeline, element):
+        w = TimelineObject(element, self.comp, self.object_style)
+        w.setZoomAdjustment(self.getZoomAdjustment())
+        self.widgets[element] = w
+        self.elements[w] = element
+        self.add_child(w)
+
+    def _objectRemoved(self, unused_timeline, element):
+        w = self.widgets[element]
+        self.remove_child(w)
+        w.comp = None
+        del self.widgets[element]
+        del self.elements[w]
+
+    def setChildZoomAdjustment(self, adj):
+        for widget in self.elements:
+            widget.setZoomAdjustment(adj)

Modified: trunk/pitivi/ui/view.py
==============================================================================
--- trunk/pitivi/ui/view.py	(original)
+++ trunk/pitivi/ui/view.py	Fri Nov 28 17:05:03 2008
@@ -1,4 +1,4 @@
-from receiver import receiver, handler
+from pitivi.receiver import receiver, handler
 import controller
 
 class View(object):
@@ -6,6 +6,7 @@
     Controller = controller.Controller
     
     def __init__(self):
+        object.__init__(self)
         self._controller = self.Controller(view=self)
 
 ## public interface



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