[pitivi/ges: 88/287] ui: Fix the way we handle object movements in the timeline
- From: Jean-FranÃois Fortin Tam <jfft src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi/ges: 88/287] ui: Fix the way we handle object movements in the timeline
- Date: Thu, 15 Mar 2012 16:33:13 +0000 (UTC)
commit fb960d808a5e25683dc05fb39e35997fb819f220
Author: Thibault Saunier <thibault saunier collabora com>
Date: Tue Dec 13 20:48:44 2011 -0300
ui: Fix the way we handle object movements in the timeline
+ Reimplement ripple
+ Handle various clips adding to the timeline
- We still have issues with unlocked TrackObject-s, this need to be fixed
pitivi/timeline/gap.py | 26 +++--
pitivi/timeline/timeline.py | 234 ++++++++++++++++++++++++++++---------------
pitivi/ui/trackobject.py | 82 ++-------------
3 files changed, 181 insertions(+), 161 deletions(-)
---
diff --git a/pitivi/timeline/gap.py b/pitivi/timeline/gap.py
index fbd9c8b..215c888 100644
--- a/pitivi/timeline/gap.py
+++ b/pitivi/timeline/gap.py
@@ -44,33 +44,35 @@ class Gap(object):
try:
prev = [obj for obj in tlobjs[:index - 1]\
- if isinstance(obj, ges.TimelineSource)].pop()
+ 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
- else:
- left_object = prev
- right_object = timeline_object
- start = prev.props.start + prev.props.duration
- duration = timeline_object.props.start - start
left_gap = Gap(left_object, right_object, start, duration)
try:
next = [obj for obj in tlobjs[index + 1:]\
- if isinstance(obj, ges.TimelineSource)][0]
+ 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
- else:
- left_object = timeline_object
- right_object = next
- start = timeline_object.props.start + timeline_object.props.duration
- duration = next.props.start - start
right_gap = Gap(left_object, right_object, start, duration)
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 8f07cac..ab5ff0c 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -22,6 +22,8 @@
import ges
+from gst import SECOND
+
from pitivi.utils import infinity
from pitivi.log.loggable import Loggable
from pitivi.signalinterface import Signallable
@@ -40,6 +42,49 @@ SELECT_BETWEEN = 3
"""Select a range of clips"""
+#------------------------------------------------------------------------------#
+# Private Functions #
+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
+
+
+#-----------------------------------------------------------------------------#
+# Public Classes #
class TimelineError(Exception):
"""Base Exception for errors happening in L{Timeline}s or L{TimelineObject}s"""
pass
@@ -92,7 +137,7 @@ class EditingContext(object):
offsets = {}
for tlobj in timeline_objects:
offsets[tlobj] = (tlobj.props.start - start_offset,
- tlobj.props.priority - priority_offset)
+ tlobj.get_layer().props.priority - priority_offset)
return offsets
@@ -169,6 +214,7 @@ class EditingContext(object):
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
@@ -185,7 +231,7 @@ class EditingContext(object):
return position, priority
- def _getGapsForLayer(self, timeline_objects, tracks=None):
+ def _getGapsForLayer(self, timeline_objects):
gaps = SmallestGapsFinder(timeline_objects)
for tlobj in timeline_objects:
@@ -197,9 +243,15 @@ class EditingContext(object):
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."""
+ """
+ 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)
@@ -209,53 +261,67 @@ class MoveContext(EditingContext, Loggable):
latest = 0
self.default_originals = {}
self.timeline_objects = set([])
- self.tracks = set([])
+ self.tracks = {}
+ self.tckobjs = set([])
all_objects = set(other)
all_objects.add(focus)
- self.layer_lst = []
+
for obj in all_objects:
if isinstance(obj, ges.TrackObject):
tlobj = obj.get_timeline_object()
- self.tracks.add(obj.get_track())
+ tckobjs = [obj]
else:
tlobj = obj
- timeline_object_tracks = \
- set(track_object.get_track() for track_object
- in tlobj.get_track_objects())
- self.tracks.update(timeline_object_tracks)
+ tckobjs = tlobj.get_track_objects()
self.timeline_objects.add(tlobj)
-
self.default_originals[tlobj] = \
self._getTimelineObjectValues(tlobj)
- earliest = min(earliest, tlobj.props.start)
- latest = max(latest, tlobj.props.start + tlobj.props.duration)
- min_priority = min(min_priority, tlobj.props.priority)
+ # 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]
- self.offsets = self._getOffsets(self.focus.props.start,
- self.focus.props.priority, self.timeline_objects)
+ 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]]
- self.min_priority = focus.props.priority - min_priority
- self.min_position = focus.props.start - earliest
+ 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]
- # get the span over all clips for edge snapping
- self.default_span = latest - earliest
+ 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()
else:
layer = focus.get_layer()
- ripple = [obj for obj in layer.get_objects() \
- if obj.props.start > latest]
- self.ripple_offsets = self._getOffsets(self.focus.props.start,
- self.focus.props.priority, ripple)
+ 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)
+ latest = max(latest, tlobj.props.start + tlobj.props.duration)
self.ripple_span = latest - earliest
# save default values
@@ -271,7 +337,7 @@ class MoveContext(EditingContext, Loggable):
timeline_objects = self.timeline_objects
return EditingContext._getGapsForLayer(self,
- timeline_objects, self.tracks)
+ timeline_objects)
def setMode(self, mode):
if mode == self.ROLL:
@@ -329,7 +395,7 @@ class MoveContext(EditingContext, Loggable):
self._defaultTo(initial_position, priority)
delta = final_position - initial_position
- left_gap, right_gap = self._getGapsForLayer(priority)
+ left_gap, right_gap = self._getGapsForLayer()
if delta > 0 and right_gap.duration < delta:
final_position = initial_position + right_gap.duration
@@ -348,11 +414,27 @@ class MoveContext(EditingContext, Loggable):
@param end: The stop position to snap.
@returns: The snapped value if within the dead_band.
"""
- #FIXME GES port, handle properly the snap to edge function
- #edge, diff = self.edges.snapToEdge(start, end)
-
- #if self.dead_band != -1 and diff <= self.dead_band:
- #return edge
+ 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) < 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()) < SECOND:
+ self.debug("Snaping to edge backward, diff=%d",
+ abs(end - next.get_start()))
+ return next.get_start() - (end - start)
return start
@@ -362,62 +444,56 @@ class MoveContext(EditingContext, Loggable):
Returns: The number of layer present in self.timeline
"""
- num_layers = len(self.timeline.get_layers())
+ layers = self.timeline.get_layers()
- if (num_layers == 0):
+ if not layers:
layer = ges.TimelineLayer()
layer.props.auto_transition = True
self.timeline.add_layer(layer)
- num_layers = 1
+ layers = [layer]
- return num_layers
+ return layers
def _defaultTo(self, position, priority):
if self._snap:
position = self.snapToEdge(position,
position + self.default_span)
- num_layers = self._ensureLayer()
+ self.debug("defaulting to %s with priorty %d", position, priority)
- #Make sure the priority is between 0 and 1 not skipping
- #any layer
- priority = max(0, priority)
- priority = min(num_layers + 1, priority)
+ layers = self._ensureLayer()
+ position = max(self.min_position, position)
- # We make sure to work with sources for the drag
+ # We make sure to work with TimelineObject-s for the drag
# and drop
- obj = self.focus
if isinstance(self.focus, ges.TrackSource):
obj = self.focus.get_timeline_object()
- elif isinstance(self.focus, ges.TrackOperation):
- return
-
- if obj.get_layer().props.priority != priority:
- origin_layer = obj.get_layer()
- moved = False
- for layer in self.timeline.get_layers():
- if layer.props.priority == priority:
- obj.move_to_layer(layer)
- moved = True
-
- if not moved:
- 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)
- self.layer_lst.append(layer)
+ else:
+ obj = self.focus
- if position < 0:
- position = 0
+ # 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 = long(position + s_offset)
+ 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 layers
+ #Remove empty layer
last_layer = self.timeline.get_layers()[-1]
if not last_layer.get_objects():
self.debug("Removing layer")
@@ -429,12 +505,13 @@ class MoveContext(EditingContext, Loggable):
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.ripple_span)
+ position + self.default_span)
priority = max(self.min_priority, priority)
- left_gap, right_gap = self._getGapsForLayer(priority)
+ left_gap, right_gap = self._getGapsForLayer()
if left_gap is invalid_gap or right_gap is invalid_gap:
if priority == self._last_priority:
@@ -444,20 +521,19 @@ class MoveContext(EditingContext, Loggable):
# try to do the same time move, using the current priority
return self._defaultTo(position, self._last_priority)
- delta = position - self.focus.start
+ delta = position - self.focus.props.start
if delta > 0 and right_gap.duration < delta:
- position = self.focus.start + right_gap.duration
+ position = self.focus.props.start + right_gap.duration
elif delta < 0 and left_gap.duration < abs(delta):
- position = self.focus.start - left_gap.duration
+ position = self.focus.props.start - left_gap.duration
- self.focus.setStart(position)
- self.focus.priority = priority
+ #FIXME GES: What about moving between layers?
+ self.focus.props.start = position
for obj, (s_offset, p_offset) in self.offsets.iteritems():
- obj.setStart(position + s_offset)
- obj.priority = priority + p_offset
+ obj.props.start = position + s_offset
+
for obj, (s_offset, p_offset) in self.ripple_offsets.iteritems():
- obj.setStart(position + s_offset)
- obj.priority = priority + p_offset
+ obj.props.start = position + s_offset
return position, priority
diff --git a/pitivi/ui/trackobject.py b/pitivi/ui/trackobject.py
index 9731cdf..10f8f91 100644
--- a/pitivi/ui/trackobject.py
+++ b/pitivi/ui/trackobject.py
@@ -3,7 +3,6 @@ import gtk
import os.path
import pango
import cairo
-import ges
import pitivi.configure as configure
import controller
@@ -23,6 +22,9 @@ from pitivi.signalinterface import Signallable
from pitivi.timeline.timeline import SELECT, SELECT_ADD, UNSELECT, \
SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext
+
+#--------------------------------------------------------------#
+# Private stuff #
LEFT_SIDE = gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE)
RIGHT_SIDE = gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE)
ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
@@ -92,25 +94,8 @@ def text_size(text):
return x2 - x1, y2 - y1
-def get_previous_track_source(track, tckobj):
- tckobjs = track.get_objects()
- i = tckobjs.index(tckobj) - 1
- while (i > 0):
- if(isinstance(tckobjs[i], ges.TrackSource)):
- return tckobjs[i]
- i -= 1
-
-
-def get_next_track_source(track, tckobj):
- tckobjs = track.get_objects()
- i = tckobjs.index(tckobj) + 1
- while(i < len(tckobjs)):
- if(isinstance(tckobjs[i], ges.TrackSource)):
- return tckobjs[i]
- i += 1
- return None
-
-
+#--------------------------------------------------------------#
+# Main Classes #
class Selected (Signallable):
"""
A simple class that let us emit a selected-changed signal
@@ -185,54 +170,13 @@ class TimelineController(controller.Controller):
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))
- self._context.setMode(self._getMode())
- track = self._view.element.get_track()
- start = self._view.element.get_start()
- duration = self._view.element.get_duration()
-
- if self.previous_x:
- position = self._view.element.get_start() + Zoomable.pixelToNs(x - self.previous_x)
- else:
- position = Zoomable.pixelToNs(x)
- self.previous_x = x
-
- if isinstance(self._view, EndHandle) or isinstance(self._view, StartHandle):
- position = Zoomable.pixelToNs(x)
- self._context.editTo(position, priority)
- self._view.get_canvas().regroupTracks()
- return
-
- prev = get_previous_track_source(track, self._view.element)
-
- if prev != None:
- prev_end = prev.get_start() + prev.get_duration()
- offset = Zoomable.nsToPixel(prev_end)
- offset = (x + self._hadj.get_value()) - offset
- if offset < 15 and offset >= 0:
- self._view.element.set_start(prev.get_start() + prev.get_duration())
- self._view.snapped_before = True
- return
- elif self._view.snapped_before:
- self._view.snapped_before = False
-
- next = get_next_track_source(track, self._view.element)
- if next != None:
- offset = Zoomable.nsToPixel(next.get_start())
- dur_offset = Zoomable.nsToPixel(duration)
- dur_offset = (dur_offset + x + self._hadj.get_value())
- offset = offset - dur_offset
- if offset < 15 and offset >= 0:
- self._view.element.set_start(next.get_start() - duration)
- self._view.snapped_after = True
- return
- elif self._view.snapped_after:
- self._view.snapped_after = False
+ self._context.setMode(self._getMode())
+ self.debug("Setting position")
self._context.editTo(position, priority)
- self._view.get_canvas().regroupTracks()
- self.next_previous_x = Zoomable.nsToPixel(self._view.element.get_start())
def _getMode(self):
if self._shift_down:
@@ -325,11 +269,9 @@ class TrackObject(View, goocanvas.Group, Zoomable):
def drag_start(self, item, target, event):
point = self.from_item_event(item, event)
- self.click(point)
TimelineController.drag_start(self, item, target, event)
self._context = MoveContext(self._view.timeline,
- self._view.element,
- set([]))
+ self._view.element, self._view.timeline.selection.getSelectedTrackObjs())
self._view.app.action_log.begin("move object")
def _getMode(self):
@@ -354,14 +296,14 @@ class TrackObject(View, goocanvas.Group, Zoomable):
self._view.app.current.seeker.seek(Zoomable.pixelToNs(x))
timeline.selection.setToObj(element, SELECT)
- def __init__(self, instance, element, track, timeline, uTrack, is_transition=False):
+ def __init__(self, instance, element, track, timeline, utrack, is_transition=False):
goocanvas.Group.__init__(self)
View.__init__(self)
Zoomable.__init__(self)
self.ref = Zoomable.nsToPixel(10000000000)
self.app = instance
self.track = track
- self.uTrack = uTrack
+ self.utrack = utrack
self.timeline = timeline
self.namewidth = 0
self.nameheight = 0
@@ -461,7 +403,7 @@ class TrackObject(View, goocanvas.Group, Zoomable):
self.start_handle.props.visibility = goocanvas.ITEM_VISIBLE
self.end_handle.props.visibility = goocanvas.ITEM_VISIBLE
self.raise_(None)
- for transition in self.uTrack.transitions:
+ for transition in self.utrack.transitions:
transition.raise_(None)
def unfocus(self):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]