pitivi r1385 - in trunk: bin pitivi pitivi/ui
- From: edwardrv svn gnome org
- To: svn-commits-list gnome org
- Subject: pitivi r1385 - in trunk: bin pitivi pitivi/ui
- Date: Fri, 28 Nov 2008 17:05:03 +0000 (UTC)
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]