[pitivi] Add back addStartEnd and removeStartEnd to timeline edges.
- From: Edward Hervey <edwardrv src gnome org>
- To: svn-commits-list gnome org
- Subject: [pitivi] Add back addStartEnd and removeStartEnd to timeline edges.
- Date: Fri, 10 Jul 2009 11:41:34 +0000 (UTC)
commit 4fc0880082b400f0c490175aa7c353dc798add04
Author: Alessandro Decina <alessandro d gmail com>
Date: Wed Jul 8 19:55:34 2009 +0200
Add back addStartEnd and removeStartEnd to timeline edges.
I added addStartEnd and removeStartEnd back. In the process, i fixed
removeStartEnd so that now calling rebuildEdges isn't needed anymore.
Finally i restored the tests removed in b8d13554440b24237d14b9f29de4b8eedf146dfa.
pitivi/timeline/timeline.py | 192 ++++++++++++++++++++++++++++++++++---------
pitivi/ui/trackobject.py | 2 +-
tests/test_timeline.py | 108 ++++++++++++++++---------
3 files changed, 224 insertions(+), 78 deletions(-)
---
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 0f593d9..e3fd65f 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -20,7 +20,7 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
-from bisect import bisect_right
+from bisect import bisect_left
from pitivi.signalinterface import Signallable
from pitivi.log.loggable import Loggable
@@ -157,18 +157,18 @@ class TimelineObject(Signallable, Loggable):
return self.track_objects[0].duration
- def setDuration(self, position, snap=False, set_media_stop=True):
+ def setDuration(self, duration, snap=False, set_media_stop=True):
"""
Sets the duration of the object.
- If snap is L{True}, then L{position} will be modified if it is close
+ If snap is L{True}, then L{duration} will be modified if it is close
to a timeline edge.
If set_media_stop is L{False} then the change will not be propagated
to the C{TrackObject}s this object controls.
- @param position: The duration in nanoseconds.
- @type position: L{long}
+ @param duration: The duration in nanoseconds.
+ @type duration: L{long}
@param snap: Whether to snap to the nearest edge or not.
@type snap: L{bool}
@param set_media_stop: propagate changes to track objects.
@@ -179,17 +179,19 @@ class TimelineObject(Signallable, Loggable):
raise TimelineError()
if snap:
+ position = self.start + duration
position = self.timeline.snapToEdge(position)
+ duration = position - self.start
- position = min(position, self.factory.duration -
+ duration = min(duration, self.factory.duration -
self.track_objects[0].in_point)
for track_object in self.track_objects:
- track_object.setObjectDuration(position)
+ track_object.setObjectDuration(duration)
if set_media_stop:
- track_object.setObjectMediaDuration(position)
+ track_object.setObjectMediaDuration(duration)
- self.emit('duration-changed', position)
+ self.emit('duration-changed', duration)
def _getInPoint(self):
if not self.track_objects:
@@ -361,7 +363,6 @@ class TimelineObject(Signallable, Loggable):
# if self is not yet in a timeline, the caller needs to add "other"
# as well when it adds self
self.timeline.addTimelineObject(other)
- self.timeline.rebuildEdges()
self.emit('duration-changed', self.duration)
@@ -586,7 +587,6 @@ class Link(object):
tracker = self.property_trackers.pop(timeline_object)
tracker.disconnectFromObject(timeline_object)
- tracker.disconnect_by_function(self._startChangedCb)
timeline_object.link = None
@@ -659,6 +659,9 @@ class TimelineEdges(object):
self.by_start = {}
self.by_end = {}
self.by_time = {}
+ self.by_object = {}
+ self.changed_objects = {}
+ self.enable_updates = True
def addTimelineObject(self, timeline_object):
"""
@@ -670,6 +673,8 @@ class TimelineEdges(object):
for obj in timeline_object.track_objects:
self.addTrackObject(obj)
+ self._connectToTimelineObject(timeline_object)
+
def removeTimelineObject(self, timeline_object):
"""
Remove this object's start/stop values from the edges.
@@ -678,46 +683,151 @@ class TimelineEdges(object):
to track.
@type timeline_object: L{TimelineObject}
"""
+ self._disconnectFromTimelineObject(timeline_object)
for obj in timeline_object.track_objects:
self.removeTrackObject(obj)
+ def _connectToTimelineObject(self, timeline_object):
+ timeline_object.connect("track-object-added", self._trackObjectAddedCb)
+ timeline_object.connect("track-object-removed", self._trackObjectRemovedCb)
+
+ def _disconnectFromTimelineObject(self, timeline_object):
+ timeline_object.disconnect_by_func(self._trackObjectAddedCb)
+ timeline_object.disconnect_by_func(self._trackObjectRemovedCb)
+
+ def _trackObjectAddedCb(self, timeline_object, track_object):
+ self.addTrackObject(track_object)
+
+ def _trackObjectRemovedCb(self, timeline_object, track_object):
+ self.removeTrackObject(track_object)
+
def addTrackObject(self, track_object):
+ if track_object in self.by_object:
+ raise TimelineError()
+
start = track_object.start
end = track_object.start + track_object.duration
- self.addStart(start)
- self.addEnd(end)
- self.by_start[start].append(track_object)
- self.by_end[end].append(track_object)
- self.by_time[start].append(track_object)
- self.by_time[end].append(track_object)
+
+ self.addStartEnd(start, end)
+
+ self.by_start.setdefault(start, []).append(track_object)
+ self.by_end.setdefault(end, []).append(track_object)
+ self.by_time.setdefault(start, []).append(track_object)
+ self.by_time.setdefault(end, []).append(track_object)
+ self.by_object[track_object] = (start, end)
+ self._connectToTrackObject(track_object)
def removeTrackObject(self, track_object):
+ try:
+ del self.by_object[track_object]
+ except KeyError:
+ raise TimelineError()
+
start = track_object.start
end = track_object.start + track_object.duration
+ self.removeStartEnd(start, end)
- self.by_start[start].remove(track_object)
- self.by_end[end].remove(track_object)
- if not self.by_start[start]:
- if not self.by_end[end]:
- self.removeStartEnd(start, end)
- else:
- self.removeStartEnd(start)
+ # remove start and end from self.by_start, self.by_end and self.by_time
+ for time, time_dict in ((start, self.by_start), (end, self.by_end),
+ (start, self.by_time), (end, self.by_time)):
+ time_dict[time].remove(track_object)
+ if not time_dict[time]:
+ del time_dict[time]
+
+ self._disconnectFromTrackObject(track_object)
+
+ def _connectToTrackObject(self, track_object):
+ track_object.connect("start-changed", self._trackObjectStartChangedCb)
+ track_object.connect("duration-changed", self._trackObjectDurationChangedCb)
+
+ def _disconnectFromTrackObject(self, track_object):
+ track_object.disconnect_by_func(self._trackObjectStartChangedCb)
+ track_object.disconnect_by_func(self._trackObjectDurationChangedCb)
+
+ def _trackObjectStartChangedCb(self, track_object, start):
+ start = track_object.start
+ end = start + track_object.duration
+
+ self.changed_objects[track_object] = (start, end)
+
+ self._maybeProcessChanges()
+
+ def _trackObjectDurationChangedCb(self, track_object, duration):
+ start = track_object.start
+ end = start + track_object.duration
+
+ self.changed_objects[track_object] = (start, end)
+
+ self._maybeProcessChanges()
+
+ def addStartEnd(self, start, end=None):
+ lo = 0
+ index = bisect_left(self.edges, start, lo=lo)
+ lo = index
+ self.edges.insert(index, start)
+
+ if end is not None:
+ index = bisect_left(self.edges, end, lo=lo)
+ self.edges.insert(index, end)
+
+ def removeStartEnd(self, start, end=None):
+ lo = 0
+ index = bisect_left(self.edges, start, lo=lo)
+ # check if start is a valid edge
+ if index == len(self.edges) or self.edges[index] != start:
+ raise TimelineError()
+
+ del self.edges[index]
+ lo = index
+
+ if end is not None:
+ index = bisect_left(self.edges, end, lo=lo)
+ # check if end is a valid edge
+ if index == len(self.edges) or self.edges[index] != end:
+ raise TimelineError()
+
+ del self.edges[index]
+
+ def enableUpdates(self):
+ self.enable_updates = True
+ self._maybeProcessChanges()
+
+ def _maybeProcessChanges(self):
+ if not self.enable_updates:
+ return
+
+ changed, self.changed_objects = self.changed_objects, {}
+
+ for track_object, (start, end) in changed.iteritems():
+ old_start, old_end = self.by_object[track_object]
+
+ for old_time, time, time_dict in ((old_start, start, self.by_start),
+ (old_end, end, self.by_end), (old_start, start, self.by_time),
+ (old_end, end, self.by_time)):
+ time_dict[old_time].remove(track_object)
+ if not time_dict[old_time]:
+ del time_dict[old_time]
+ time_dict.setdefault(time, []).append(track_object)
- def addStart(self, start):
- self.addEdge(start)
- if start not in self.by_start:
- self.by_start[start] = []
+ old_edges = []
+ new_edges = []
+ if start != old_start:
+ old_edges.append(old_start)
+ new_edges.append(start)
- def addEnd(self, end):
- if end not in self.by_end:
- self.addEdge(end)
- self.by_end[end] = []
+ if end != old_end:
+ old_edges.append(old_end)
+ new_edges.append(end)
- def addEdge(self, edge):
- if edge not in self.by_time:
- index = bisect_right(self.edges, edge)
- self.edges.insert(index, edge)
- self.by_time[edge] = []
+ if old_edges:
+ self.removeStartEnd(*old_edges)
+ if new_edges:
+ self.addStartEnd(*new_edges)
+
+ self.by_object[track_object] = (start, end)
+
+ def disableUpdates(self):
+ self.enable_updates = False
def snapToEdge(self, start, end=None):
"""
@@ -840,7 +950,6 @@ class EditingContext(object):
def finish(self):
"""Clean up timeline for normal editing"""
# TODO: post undo / redo action here
- self.timeline.rebuildEdges()
self.timeline.enableUpdates()
def setMode(self, mode):
@@ -1108,7 +1217,8 @@ class Timeline(Signallable, Loggable):
obj.link.removeTimelineObject(obj)
obj.timeline = None
- self.rebuildEdges()
+
+ self.edges.removeTimelineObject(obj)
self.emit("timeline-object-removed", obj)
@@ -1325,6 +1435,8 @@ class Timeline(Signallable, Loggable):
for track in self.tracks:
track.disableUpdates()
+ self.edges.disableUpdates()
+
self.emit("disable-updates", True)
def enableUpdates(self):
@@ -1334,6 +1446,8 @@ class Timeline(Signallable, Loggable):
for track in self.tracks:
track.enableUpdates()
+ self.edges.enableUpdates()
+
self.emit("disable-updates", False)
def getObjsAfterObj(self, obj):
diff --git a/pitivi/ui/trackobject.py b/pitivi/ui/trackobject.py
index 1771a68..963256b 100644
--- a/pitivi/ui/trackobject.py
+++ b/pitivi/ui/trackobject.py
@@ -100,7 +100,7 @@ class TimelineController(controller.Controller):
self._view.unfocus()
def drag_end(self, item, target, event):
- self._view.timeline.rebuildEdges()
+ pass
def drag_start(self, item, target, event):
if not self._view.element.selected:
diff --git a/tests/test_timeline.py b/tests/test_timeline.py
index 52029e3..9022f6c 100644
--- a/tests/test_timeline.py
+++ b/tests/test_timeline.py
@@ -21,6 +21,7 @@
import gst
+from tests.common import FakeSourceFactory
from pitivi.timeline.timeline import Timeline, TimelineObject, TimelineError, \
Selection, Link, TimelineEdges, MoveContext, TrimStartContext, \
TrimEndContext
@@ -487,32 +488,52 @@ class TestLink(TestCase):
self.failUnlessEqual(timeline_object3.start, 8 * gst.SECOND)
class TestTimelineEdges(TestCase):
-
def setUp(self):
TestCase.setUp(self)
self.timeline_edges = TimelineEdges()
- self.factory = StubFactory()
- self.stream = AudioStream(gst.Caps('audio/x-raw-int'))
- self.factory.addOutputStream(self.stream)
- self.t1 = SourceTrackObject(self.factory, self.stream)
- self.t2 = SourceTrackObject(self.factory, self.stream)
- def tearDown(self):
- del self.t1
- del self.t2
- del self.stream
- del self.factory
- del self.timeline_edges
- TestCase.tearDown(self)
+ def testRemove(self):
+ self.timeline_edges.addStartEnd(0, 2000)
+ self.timeline_edges.removeStartEnd(0, 2000)
+
+ def testRemoveNotExisting(self):
+ self.failUnlessRaises(TimelineError,
+ self.timeline_edges.removeStartEnd, 1, 2000)
+
+ self.timeline_edges.addStartEnd(0, 2000)
+ self.failUnlessRaises(TimelineError,
+ self.timeline_edges.removeStartEnd, 1, 2000)
+ self.failUnlessRaises(TimelineError,
+ self.timeline_edges.removeStartEnd, 0, 2001)
def testNoEdges(self):
- # test that edges object returns (start, 0)
self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (500, 0))
- def testSingleEdge(self):
- self.t1.start = 1000
- self.t1.duration = 1000
- self.timeline_edges.addTrackObject(self.t1)
+ def testSimple(self):
+ self.timeline_edges.addStartEnd(0, 2000)
+ self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (0, 500))
+
+ self.timeline_edges.removeStartEnd(0, 2000)
+ self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (500, 0))
+
+ def testSamePosition(self):
+ self.timeline_edges.addStartEnd(0, 2000)
+ self.timeline_edges.addStartEnd(0, 2000)
+
+ self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (0, 500))
+
+ self.timeline_edges.removeStartEnd(0, 2000)
+
+ self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (0, 500))
+
+ self.timeline_edges.removeStartEnd(0, 2000)
+
+ self.failUnlessEqual(self.timeline_edges.snapToEdge(500, 1000), (500, 0))
+
+ def testSnapStart(self):
+ self.timeline_edges = TimelineEdges()
+
+ self.timeline_edges.addStartEnd(1000, 2000)
# match start-left
self.failUnlessEqual(self.timeline_edges.snapToEdge(900, 1400), (1000, 100))
@@ -535,6 +556,9 @@ class TestTimelineEdges(TestCase):
# match both start and end, start is returned
self.failUnlessEqual(self.timeline_edges.snapToEdge(1000, 2000), (1000, 0))
+ def testSnapDuration(self):
+ self.timeline_edges.addStartEnd(1000, 2000)
+
# match start-left
self.failUnlessEqual(self.timeline_edges.snapToEdge(900), (1000, 100))
@@ -553,26 +577,34 @@ class TestTimelineEdges(TestCase):
# match end-right
self.failUnlessEqual(self.timeline_edges.snapToEdge(3000), (2000, 1000))
- def testSamePosition(self):
- self.t2.start = 1000
- self.t2.duration = 1000
- self.timeline_edges.addTrackObject(self.t2)
- self.testSingleEdge()
-
- def testGetAdjacenctObjs(self):
- self.t1.start = 500
- self.t1.duration = 500
- self.t2.start = 1000
- self.t2.duration = 500
- self.timeline_edges.addTrackObject(self.t1)
- self.timeline_edges.addTrackObject(self.t2)
- self.assertEquals(set(self.timeline_edges.getObjsIncidentOnTime(1000)),
- set([self.t1, self.t2]))
- self.assertEquals(set(self.timeline_edges.getObjsAdjacentToStart(self.t2)),
- set([self.t1]))
- self.assertEquals(set(self.timeline_edges.getObjsAdjacentToEnd(self.t1)),
- set([self.t2]))
-
+ def testAdjacenctObjs(self):
+ factory = FakeSourceFactory()
+ stream = AudioStream(gst.Caps("meh"))
+ track_object1 = SourceTrackObject(factory, stream)
+ track_object2 = SourceTrackObject(factory, stream)
+ track_object1.start = 500
+ track_object1.duration = 500
+ track_object2.start = 1000
+ track_object2.duration = 500
+ self.timeline_edges.addTrackObject(track_object1)
+ self.timeline_edges.addTrackObject(track_object2)
+ self.assertEquals(self.timeline_edges.getObjsIncidentOnTime(1000),
+ [track_object1, track_object2])
+ self.assertEquals(self.timeline_edges.getObjsAdjacentToStart(track_object2),
+ [track_object1])
+ self.assertEquals(self.timeline_edges.getObjsAdjacentToEnd(track_object1),
+ [track_object2])
+
+ self.timeline_edges.removeTrackObject(track_object2)
+ self.assertEquals(self.timeline_edges.getObjsIncidentOnTime(1000),
+ [track_object1])
+
+ self.timeline_edges.removeTrackObject(track_object1)
+ self.assertEquals(self.timeline_edges.getObjsIncidentOnTime(1000), [])
+
+ track_object1.release()
+ track_object2.release()
+ del factory
class TestTimelineAddFactory(TestCase):
def setUp(self):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]