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