21
20
# Boston, MA 02111-1307, USA.
23
Timeline widgets for the complex view
28
from pitivi.log.loggable import Loggable
31
34
from gettext import gettext as _
33
import pitivi.instance as instance
35
from timelineobjects import SimpleTimeline
36
from complextimeline import ComplexTimelineWidget
38
class TimelineWidget(gtk.VBox):
39
""" Widget for reprensenting Pitivi's Timeline """
42
gst.log("New Timeline Widget")
43
gtk.VBox.__init__(self)
48
self.hadjustment = gtk.Adjustment()
49
self.vadjustment = gtk.Adjustment()
51
self.simpleview = SimpleTimelineContentWidget(self)
52
self.complexview = ComplexTimelineWidget(self)
54
self.simpleview.connect("scroll-event", self._simpleScrollCb)
55
self.complexview.connect("scroll-event", self._simpleScrollCb)
57
self.hscroll = gtk.HScrollbar(self.hadjustment)
58
self.pack_end(self.hscroll, expand=False)
60
def showSimpleView(self):
61
""" Show the simple timeline """
62
if self.complexview in self.get_children():
63
self.remove(self.complexview)
64
self.complexview.hide()
65
self.pack_start(self.simpleview, expand=True)
66
self.simpleview.show_all()
68
def showComplexView(self):
69
""" Show the advanced timeline """
70
if self.simpleview in self.get_children():
71
self.remove(self.simpleview)
72
self.simpleview.hide()
73
self.pack_start(self.complexview, expand=True)
74
self.complexview.show_all()
76
def _simpleScrollCb(self, unused_simplet, event):
77
gst.debug("state:%s" % event.state)
78
self.hscroll.emit("scroll-event", event)
80
class SimpleTimelineContentWidget(gtk.HBox):
81
""" Widget for Simple Timeline content display """
82
def __init__(self, twidget):
84
self.twidget = twidget
85
gtk.HBox.__init__(self)
91
self.timeline = SimpleTimeline(hadjustment = self.twidget.hadjustment)
93
# real simple timeline
94
self.layoutframe = gtk.Frame()
95
self.layoutframe.add(self.timeline)
97
# Explanatory message label
98
txtbuffer = gtk.TextBuffer()
99
txtbuffer.set_text(_("Add clips to the timeline by dragging them here."))
100
txttag = gtk.TextTag()
101
txttag.props.size = self.style.font_desc.get_size() * 1.5
102
txtbuffer.tag_table.add(txttag)
103
txtbuffer.apply_tag(txttag, txtbuffer.get_start_iter(),
104
txtbuffer.get_end_iter())
105
self.messagewindow = gtk.TextView(txtbuffer)
106
self.messagewindow.set_justification(gtk.JUSTIFY_CENTER)
107
self.messagewindow.set_wrap_mode(gtk.WRAP_WORD)
108
self.messagewindow.set_pixels_above_lines(30)
109
self.messagewindow.set_cursor_visible(False)
110
self.messagewindow.set_editable(False)
111
self.messagewindow.set_left_margin(10)
112
self.messagewindow.set_right_margin(10)
113
self.messagewindow.set_size_request(-1, 120)
115
self.messagewindow.add_events(gtk.gdk.ENTER_NOTIFY_MASK)
117
# we start with showing the hint message
118
self.pack_start(self.messagewindow)
119
self.motionSigId = self.messagewindow.connect("drag-motion", self._dragMotionCb)
120
self.showingTimeline = False
121
self._displayTimeline()
123
def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
126
self.showingTimeline = False
127
gobject.idle_add(self._displayTimeline)
129
def _dragLeaveCb(self, unused_layout, unused_context, unused_timestamp):
131
if len(instance.PiTiVi.current.timeline.videocomp):
133
self.showingTimeline = True
134
gobject.idle_add(self._displayTimeline, False)
136
def _displayTimeline(self, displayed=True):
138
if self.showingTimeline:
140
gst.debug("displaying timeline")
141
self.messagewindow.disconnect(self.motionSigId)
142
self.motionSigId = None
143
self.remove(self.messagewindow)
144
self.messagewindow.hide()
145
self.pack_start(self.layoutframe)
146
self.reorder_child(self.layoutframe, 0)
147
self.layoutframe.show_all()
148
self.dragLeaveSigId = self.timeline.connect("drag-leave", self._dragLeaveCb)
149
self.showingTimeline = True
151
if not self.showingTimeline:
153
# only hide if there's nothing left in the timeline
154
if not len(instance.PiTiVi.current.timeline.videocomp):
155
gst.debug("hiding timeline")
156
self.timeline.disconnect(self.dragLeaveSigId)
157
self.dragLeaveSigId = None
158
self.remove(self.layoutframe)
159
self.layoutframe.hide()
160
self.pack_start(self.messagewindow)
161
self.reorder_child(self.messagewindow, 0)
162
self.messagewindow.show()
163
self.motionSigId = self.messagewindow.connect("drag-motion", self._dragMotionCb)
164
self.showingTimeline = False
35
from timelinecanvas import TimelineCanvas
36
from timelinecontrols import TimelineControls
37
from pitivi.receiver import receiver, handler
38
from zoominterface import Zoomable
40
# tooltip text for toolbar
41
DELETE = _("Delete Selected")
42
RAZOR = _("Cut clip at mouse position")
43
ZOOM_IN = _("Zoom In")
44
ZOOM_OUT = _("Zoom Out")
45
UNLINK = _("Break links between clips")
46
LINK = _("Link together arbitrary clips")
47
UNGROUP = _("Ungroup clips")
48
GROUP = _("Group clips")
49
SELECT_BEFORE = ("Select all sources before selected")
50
SELECT_AFTER = ("Select all after selected")
54
<menubar name="MainMenuBar">
56
<placeholder name="Timeline">
57
<menuitem action="ZoomIn" />
58
<menuitem action="ZoomOut" />
61
<menu action="Timeline">
62
<placeholder name="Timeline">
63
<menuitem action="Razor" />
65
<menuitem action="DeleteObj" />
66
<menuitem action="LinkObj" />
67
<menuitem action="UnlinkObj" />
68
<menuitem action="GroupObj" />
69
<menuitem action="UngroupObj" />
73
<toolbar name="TimelineToolBar">
74
<placeholder name="Timeline">
75
<toolitem action="ZoomOut" />
76
<toolitem action="ZoomIn" />
78
<toolitem action="Razor" />
80
<toolitem action="DeleteObj" />
81
<toolitem action="UnlinkObj" />
82
<toolitem action="LinkObj" />
83
<toolitem action="GroupObj" />
84
<toolitem action="UngroupObj" />
87
<accelerator action="DeleteObj" />
91
# Complex Timeline Design v2 (08 Feb 2006)
94
# Tree of contents (ClassName(ParentClass))
95
# -----------------------------------------
100
# +--ScaleRuler(gtk.Layout)
102
# +--gtk.ScrolledWindow
104
# +--TimelineCanvas(goocanas.Canvas)
106
# | +--Track(SmartGroup)
110
class Timeline(gtk.Table, Loggable, Zoomable):
112
# the screen width of the current unit
114
# specific levels of zoom, in (multiplier, unit) pairs which
115
# from zoomed out to zoomed in
118
def __init__(self, instance, ui_manager):
119
gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False)
120
Loggable.__init__(self)
121
Zoomable.__init__(self)
122
self.log("Creating Timeline")
125
self.ui_manager = ui_manager
127
self._temp_objects = None
128
self._factories = None
129
self._finish_drag = False
132
self._prev_duration = 0
136
self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
137
self.hadj = gtk.Adjustment()
138
self.vadj = gtk.Adjustment()
140
# controls for tracks and layers
141
self._controls = TimelineControls()
142
self._controls.connect('track-expanded',
143
self._timelineControlsTrackExpandedCb)
144
controlwindow = gtk.ScrolledWindow(None, self.vadj)
145
controlwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
146
controlwindow.add_with_viewport(self._controls)
147
self.attach(controlwindow, 0, 1, 1, 2, xoptions=0)
150
self.ruler = ruler.ScaleRuler(self.hadj)
151
self.ruler.set_size_request(0, 35)
152
self.ruler.set_border_width(2)
153
self.ruler.connect("key-press-event", self._keyPressEventCb)
154
self.attach(self.ruler, 1, 2, 0, 1, yoptions=0)
156
# proportional timeline
157
self._canvas = TimelineCanvas(self.app)
158
timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj)
159
timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
160
timelinewindow.add(self._canvas)
161
#FIXME: remove padding between scrollbar and scrolled window
162
self.attach(timelinewindow, 1, 2, 1, 2)
165
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
166
[dnd.FILESOURCE_TUPLE],
169
self.connect("drag-data-received", self._dragDataReceivedCb)
170
self.connect("drag-leave", self._dragLeaveCb)
171
self.connect("drag-drop", self._dragDropCb)
172
self.connect("drag-motion", self._dragMotionCb)
176
("ZoomIn", gtk.STOCK_ZOOM_IN, None, None, ZOOM_IN,
178
("ZoomOut", gtk.STOCK_ZOOM_OUT, None, None, ZOOM_OUT,
182
selection_actions = (
183
("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE,
184
self.deleteSelected),
185
("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK,
186
self.unlinkSelected),
187
("LinkObj", "pitivi-link", None, "<Control>L", LINK,
189
("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP,
190
self.ungroupSelected),
191
("GroupObj", "pitivi-group", None, "<Control>G", GROUP,
196
("Razor", "pitivi-split", _("Razor"), "<Ctrl>R", RAZOR,
200
actiongroup = gtk.ActionGroup("timelinepermanent")
201
actiongroup.add_actions(actions)
202
actiongroup.add_toggle_actions(toggle_actions)
203
self.ui_manager.insert_action_group(actiongroup, 0)
205
actiongroup = gtk.ActionGroup("timelineselection")
206
actiongroup.add_actions(selection_actions)
207
self.link_action = actiongroup.get_action("LinkObj")
208
self.unlink_action = actiongroup.get_action("UnlinkObj")
209
self.group_action = actiongroup.get_action("GroupObj")
210
self.ungroup_action = actiongroup.get_action("UngroupObj")
211
self.delete_action = actiongroup.get_action("DeleteObj")
213
self.ui_manager.insert_action_group(actiongroup, -1)
215
self.ui_manager.add_ui_from_string(ui)
218
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
219
[dnd.FILESOURCE_TUPLE],
222
self.connect("drag-data-received", self._dragDataReceivedCb)
223
self.connect("drag-leave", self._dragLeaveCb)
224
self.connect("drag-drop", self._dragDropCb)
225
self.connect("drag-motion", self._dragMotionCb)
226
self._canvas.connect("button-press-event", self._buttonPress)
227
self._canvas.connect("button-release-event", self._buttonRelease)
228
self._canvas.connect("key-press-event", self._keyPressEventCb)
230
def _timelineControlsTrackExpandedCb(self, timeline_controls,
232
self._canvas.setExpanded(track, expanded)
237
def _keyPressEventCb(self, unused_widget, event):
239
self.debug("kv:%r", kv)
240
if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]:
242
mod = event.get_state()
244
if mod & gtk.gdk.CONTROL_MASK:
245
now = self.project.pipeline.getPosition()
246
ltime, rtime = self.project.timeline.edges.closest(now)
248
if kv == gtk.keysyms.Left:
249
if mod & gtk.gdk.SHIFT_MASK:
250
self.project.pipeline.seekRelative(-gst.SECOND)
251
elif mod & gtk.gdk.CONTROL_MASK:
252
self.project.pipeline.seek(ltime+1)
254
self.project.pipeline.seekRelative(-long(self.rate * gst.SECOND))
255
elif kv == gtk.keysyms.Right:
256
if mod & gtk.gdk.SHIFT_MASK:
257
self.project.pipeline.seekRelative(gst.SECOND)
258
elif mod & gtk.gdk.CONTROL_MASK:
259
self.project.pipeline.seek(rtime+1)
261
self.project.pipeline.seekRelative(long(self.rate * gst.SECOND))
265
def _buttonPress(self, window, event):
268
def _buttonRelease(self, window, event):
270
self._timelineStartDurationChanged(self.timeline,
271
self.timeline.duration)
273
## Drag and Drop callbacks
275
def _dragMotionCb(self, unused, context, x, y, timestamp):
276
self.warning("self._factories:%r, self._temp_objects:%r",
277
not not self._factories,
278
not not self._temp_objects)
279
if not self._factories:
280
atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])
281
self.drag_get_data(context, atom, timestamp)
282
self.drag_highlight()
284
# actual drag-and-drop
285
if not self._temp_objects:
286
self.timeline.disableUpdates()
287
self._add_temp_source()
288
self._move_temp_source(x, y)
291
def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp):
292
if self._temp_objects:
294
for obj in self._temp_objects:
295
self.timeline.removeTimelineObject(obj, deep=True)
297
self._temp_objects = None
298
self.drag_unhighlight()
299
self.timeline.enableUpdates()
301
def _dragDropCb(self, widget, context, x, y, timestamp):
302
self._add_temp_source()
303
self._move_temp_source(x, y)
304
context.drop_finish(True, timestamp)
305
self._factories = None
306
self._temp_objects = None
309
def _dragDataReceivedCb(self, unused_layout, context, x, y,
310
selection, targetType, timestamp):
311
self.log("SimpleTimeline, targetType:%d, selection.data:%s" %
312
(targetType, selection.data))
313
# FIXME: let's have just one target type, call it
314
# TYPE_PITIVI_OBJECTFACTORY.
315
# TODO: handle uri targets by doign an import-add. This would look
316
# something like this:
317
# tell current project to import the uri
318
# wait for source-added signal, meanwhile ignore dragMotion signals
319
# when ready, add factories to the timeline.
320
if targetType == dnd.TYPE_PITIVI_FILESOURCE:
321
uris = selection.data.split("\n")
323
context.finish(False, False, timestamp)
324
self._factories = [self.project.sources[uri] for uri in uris]
325
context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
328
def _add_temp_source(self):
329
self._temp_objects = [self.timeline.addSourceFactory(factory)
330
for factory in self._factories]
332
def _move_temp_source(self, x, y):
333
x1, y1, x2, y2 = self._controls.get_allocation()
334
offset = 10 + (x2 - x1)
335
x, y = self._canvas.convert_from_pixels(x - offset, y)
336
delta = Zoomable.pixelToNs(x)
337
for obj in self._temp_objects:
338
obj.setStart(max(0, delta), snap=True)
339
delta += obj.duration
342
## Zooming and Scrolling
344
def zoomChanged(self):
345
# this has to be in a timeout, because the resize hasn't actually
346
# completed yet, and so the canvas can't actually complete the scroll
347
gobject.idle_add(self.scrollToPlayhead)
349
def timelinePositionChanged(self, position):
350
self._position = position
351
self.ruler.timelinePositionChanged(position)
352
self._canvas._position = position
353
self.scrollToPlayhead()
355
def scrollToPlayhead(self):
356
width = self.get_allocation().width
357
new_pos = Zoomable.nsToPixel(self._position)
358
scroll_pos = self.hadj.get_value()
359
if (new_pos < scroll_pos) or (new_pos > scroll_pos + width):
360
self.scrollToPosition(new_pos - width / 2)
363
def scrollToPosition(self, position):
364
if position > self.hadj.upper:
365
# we can't perform the scroll because the canvas needs to be
367
gobject.idle_add(self._scrollToPosition, position)
369
self._scrollToPosition(position)
371
def _scrollToPosition(self, position):
372
self.hadj.set_value(position)
377
def _setProject(self):
379
self.timeline = self.project.timeline
380
self._controls.timeline = self.timeline
381
self._canvas.timeline = self.timeline
382
self._canvas.zoomChanged()
383
self.ruler.zoomChanged()
384
self._settingsChangedCb(self.project)
386
project = receiver(_setProject)
388
@handler(project, "settings-changed")
389
def _settingsChangedCb(self, project):
390
self.rate = float(1 / self.project.getSettings().videorate)
392
## Timeline callbacks
394
def _setTimeline(self):
396
self._timelineSelectionChanged(self.timeline)
397
self._timelineStartDurationChanged(self.timeline,
398
self.timeline.duration)
400
self._controls.timeline = self.timeline
402
timeline = receiver(_setTimeline)
404
@handler(timeline, "duration-changed")
405
def _timelineStartDurationChanged(self, unused_timeline, duration):
407
self._prev_duration = duration
408
self.ruler.setMaxDuration(duration)
409
self._canvas.setMaxDuration(duration)
410
self.ruler.setShadedDuration(duration)
412
# only resize if new size is larger
413
if duration > self._prev_duration:
414
self._prev_duration = duration
415
self.ruler.setMaxDuration(duration)
416
self._canvas.setMaxDuration(duration)
417
#self.ruler.setShadedDuration(duration)
419
@handler(timeline, "selection-changed")
420
def _timelineSelectionChanged(self, timeline):
426
timeline_objects = {}
427
if timeline.selection:
429
if len(timeline.selection) > 1:
435
for obj in self.timeline.selection:
439
if len(obj.track_objects) > 1:
442
if start is not None and duration is not None:
443
if obj.start != start or obj.duration != duration:
447
duration = obj.duration
449
self.delete_action.set_sensitive(delete)
450
self.link_action.set_sensitive(link)
451
self.unlink_action.set_sensitive(unlink)
452
self.group_action.set_sensitive(group)
453
self.ungroup_action.set_sensitive(ungroup)
458
self.actiongroup.set_visible(False)
461
def _computeZoomRatio(self, index):
462
return self.zoom_levels[index]
464
def _zoomInCb(self, unused_action):
467
def _zoomOutCb(self, unused_action):
470
def deleteSelected(self, unused_action):
472
self.timeline.deleteSelection()
474
def unlinkSelected(self, unused_action):
476
self.timeline.unlinkSelection()
478
def linkSelected(self, unused_action):
480
self.timeline.linkSelection()
482
def ungroupSelected(self, unused_action):
484
self.timeline.ungroupSelection()
486
def groupSelected(self, unused_action):
488
self.timeline.groupSelection()
490
def toggleRazor(self, action):
491
if action.props.active:
492
self._canvas.activateRazor(action)
494
self._canvas.deactivateRazor()