37
37
from timelinecontrols import TimelineControls
38
38
from pitivi.receiver import receiver, handler
39
39
from zoominterface import Zoomable
40
from pitivi.ui.common import LAYER_HEIGHT_EXPANDED, LAYER_SPACING
41
from pitivi.timeline.timeline import MoveContext
40
from pitivi.ui.common import LAYER_HEIGHT_EXPANDED, LAYER_SPACING, TRACK_SPACING
41
from pitivi.timeline.timeline import MoveContext, SELECT
42
42
from pitivi.utils import Seeker
43
43
from pitivi.ui.filelisterrordialog import FileListErrorDialog
44
44
from pitivi.ui.curve import Curve
45
from pitivi.ui.common import SPACING
47
from pitivi.factories.operation import EffectFactory
49
DND_EFFECT_LIST = [[dnd.VIDEO_EFFECT_TUPLE[0], dnd.EFFECT_TUPLE[0]],\
50
[dnd.AUDIO_EFFECT_TUPLE[0], dnd.EFFECT_TUPLE[0]]]
51
VIDEO_EFFECT_LIST = [dnd.VIDEO_EFFECT_TUPLE[0], dnd.EFFECT_TUPLE[0]],
52
AUDIO_EFFECT_LIST = [dnd.AUDIO_EFFECT_TUPLE[0], dnd.EFFECT_TUPLE[0]],
46
54
# tooltip text for toolbar
47
55
DELETE = _("Delete Selected")
48
56
SPLIT = _("Split clip at playhead position")
49
KEYFRAME = _("Create a keyframe")
57
KEYFRAME = _("Add a keyframe")
58
PREVFRAME = _("Move to the previous keyframe")
59
NEXTFRAME = _("Move to the next keyframe")
50
60
ZOOM_IN = _("Zoom In")
51
61
ZOOM_OUT = _("Zoom Out")
62
ZOOM_FIT = _("Zoom Fit")
52
63
UNLINK = _("Break links between clips")
53
64
LINK = _("Link together arbitrary clips")
54
65
UNGROUP = _("Ungroup clips")
229
251
zoomslider = gtk.HScale(self._zoomAdjustment)
230
252
zoomslider.props.draw_value = False
231
253
zoomslider.set_tooltip_text(_("Zoom Timeline"))
232
self.attach(zoomslider, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL)
254
zoomslider.connect("scroll-event", self._zoomSliderScrollCb)
255
zoomslider.set_size_request(100, 0) # At least 100px wide for precision
256
zoom_controls_hbox.pack_start(zoomslider)
257
self.attach(zoom_controls_hbox, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL)
234
259
# controls for tracks and layers
235
260
self._controls = TimelineControls()
237
262
controlwindow.add(self._controls)
238
263
controlwindow.set_size_request(-1, 1)
239
264
controlwindow.set_shadow_type(gtk.SHADOW_OUT)
240
self.attach(controlwindow, 0, 1, 1, 2, xoptions=0)
265
self.attach(controlwindow, 0, 1, 1, 2, xoptions=gtk.FILL)
243
268
self.ruler = ruler.ScaleRuler(self.app, self.hadj)
244
269
self.ruler.set_size_request(0, 25)
245
self.ruler.set_border_width(2)
270
#self.ruler.set_border_width(2)
246
271
self.ruler.connect("key-press-event", self._keyPressEventCb)
247
272
self.ruler.connect("size-allocate", self._rulerSizeAllocateCb)
248
273
rulerframe = gtk.Frame()
299
327
self.groupSelected),
330
self.playhead_actions = (
303
331
("Split", "pitivi-split", _("Split"), "S", SPLIT,
305
("Keyframe", "pitivi-keyframe", _("Keyframe"), "K", KEYFRAME,
333
("Keyframe", "pitivi-keyframe", _("Add a keyframe"), "K", KEYFRAME,
335
("Prevframe", "pitivi-prevframe", _("_Previous keyframe"), "E", PREVFRAME,
337
("Nextframe", "pitivi-nextframe", _("_Next keyframe"), "R", NEXTFRAME,
309
341
actiongroup = gtk.ActionGroup("timelinepermanent")
320
352
self.delete_action = actiongroup.get_action("DeleteObj")
321
353
self.split_action = actiongroup.get_action("Split")
322
354
self.keyframe_action = actiongroup.get_action("Keyframe")
355
self.prevframe_action = actiongroup.get_action("Prevframe")
356
self.nextframe_action = actiongroup.get_action("Nextframe")
324
358
self.ui_manager.insert_action_group(actiongroup, -1)
326
360
self.ui_manager.add_ui_from_string(ui)
329
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
330
[dnd.FILESOURCE_TUPLE],
363
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
364
[dnd.FILESOURCE_TUPLE, dnd.EFFECT_TUPLE],
331
365
gtk.gdk.ACTION_COPY)
333
367
self.connect("drag-data-received", self._dragDataReceivedCb)
334
368
self.connect("drag-leave", self._dragLeaveCb)
335
369
self.connect("drag-drop", self._dragDropCb)
336
370
self.connect("drag-motion", self._dragMotionCb)
337
self._canvas.connect("button-press-event", self._buttonPress)
338
self._canvas.connect("button-release-event", self._buttonRelease)
339
371
self._canvas.connect("key-press-event", self._keyPressEventCb)
372
self._canvas.connect("scroll-event", self._scrollEventCb)
342
375
## Event callbacks
375
408
pipeline.getDuration()))
376
409
self._seeker.seek(seekvalue)
378
def _buttonPress(self, window, event):
381
def _buttonRelease(self, window, event):
383
self._timelineStartDurationChanged(self.timeline,
384
self.timeline.duration)
386
411
## Drag and Drop callbacks
388
413
def _dragMotionCb(self, unused, context, x, y, timestamp):
389
414
self.warning("self._factories:%r, self._temp_objects:%r",
390
415
not not self._factories,
391
416
not not self._temp_objects)
392
418
if self._factories is None:
393
atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])
419
if context.targets in DND_EFFECT_LIST:
420
atom = gtk.gdk.atom_intern(dnd.EFFECT_TUPLE[0])
422
atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])
394
424
self.drag_get_data(context, atom, timestamp)
395
425
self.drag_highlight()
397
# actual drag-and-drop
398
if not self._temp_objects:
399
self.timeline.disableUpdates()
400
self._add_temp_source()
401
focus = self._temp_objects[0]
402
self._move_context = MoveContext(self.timeline,
403
focus, set(self._temp_objects[1:]))
404
self._move_temp_source(self.hadj.props.value + x, y)
427
if context.targets not in DND_EFFECT_LIST:
428
if not self._temp_objects:
429
self.timeline.disableUpdates()
430
self._add_temp_source()
431
focus = self._temp_objects[0]
432
self._move_context = MoveContext(self.timeline,
433
focus, set(self._temp_objects[1:]))
434
self._move_temp_source(self.hadj.props.value + x, y)
407
def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp):
437
def _dragLeaveCb(self, unused_layout, context, unused_tstamp):
408
438
if self._temp_objects:
410
440
for obj in self._temp_objects:
411
441
self.timeline.removeTimelineObject(obj, deep=True)
413
443
self._temp_objects = None
414
445
self.drag_unhighlight()
415
446
self.timeline.enableUpdates()
417
448
def _dragDropCb(self, widget, context, x, y, timestamp):
418
self.app.action_log.begin("add clip")
419
self.timeline.disableUpdates()
420
self._add_temp_source()
421
focus = self._temp_objects[0]
422
self._move_context = MoveContext(self.timeline,
423
focus, set(self._temp_objects[1:]))
424
self._move_temp_source(self.hadj.props.value + x, y)
425
self._move_context.finish()
426
self.timeline.enableUpdates()
427
self.app.action_log.commit()
428
context.drop_finish(True, timestamp)
429
self._factories = None
430
self._temp_objects = None
431
self.app.current.seeker.seek(self._position)
449
if context.targets not in DND_EFFECT_LIST:
450
self.app.action_log.begin("add clip")
451
self.timeline.disableUpdates()
453
self._add_temp_source()
454
self.timeline.selection.setSelection(self._temp_objects, SELECT)
455
focus = self._temp_objects[0]
456
self._move_context = MoveContext(self.timeline,
457
focus, set(self._temp_objects[1:]))
458
self._move_temp_source(self.hadj.props.value + x, y)
459
self._move_context.finish()
460
self.app.action_log.commit()
461
context.drop_finish(True, timestamp)
462
self._factories = None
463
self._temp_objects = None
464
self.app.current.seeker.seek(self._position)
467
elif context.targets in DND_EFFECT_LIST:
468
if not self.timeline.timeline_objects:
470
factory = self._factories[0]
471
timeline_objs = self._getTimelineObjectUnderMouse(x, y, factory.getInputStreams()[0])
473
self.app.action_log.begin("add effect")
474
self.timeline.addEffectFactoryOnObject(factory,
475
timeline_objects = timeline_objs)
476
self.app.action_log.commit()
477
self._factories = None
478
self.app.current.seeker.seek(self._position)
479
context.drop_finish(True, timestamp)
481
self.timeline.selection.setSelection(timeline_objs, SELECT)
434
487
def _dragDataReceivedCb(self, unused_layout, context, x, y,
435
488
selection, targetType, timestamp):
442
495
# tell current project to import the uri
443
496
# wait for source-added signal, meanwhile ignore dragMotion signals
444
497
# when ready, add factories to the timeline.
445
if targetType != dnd.TYPE_PITIVI_FILESOURCE:
499
if targetType not in [dnd.TYPE_PITIVI_FILESOURCE, dnd.TYPE_PITIVI_EFFECT]:
446
500
context.finish(False, False, timestamp)
449
uris = selection.data.split("\n")
450
self._factories = [self.project.sources.getUri(uri) for uri in uris]
503
if targetType == dnd.TYPE_PITIVI_FILESOURCE:
504
uris = selection.data.split("\n")
505
self._factories = [self.project.sources.getUri(uri) for uri in uris]
507
if not self.timeline.timeline_objects:
509
self._factories = [self.app.effects.getFactoryFromName(selection.data)]
451
511
context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
514
def _getTimelineObjectUnderMouse(self, x, y, stream):
516
items_in_area = self._canvas.getItemsInArea(x, y-15, x+1, y-30)
517
tracks = [obj for obj in items_in_area[0]]
518
track_objects = [obj for obj in items_in_area[1]]
519
for track_object in track_objects:
520
if (type(stream) == type(track_object.stream)):
521
timeline_objs.append(track_object.timeline_object)
454
525
def _add_temp_source(self):
455
526
self._temp_objects = [self.timeline.addSourceFactory(factory)
456
527
for factory in self._factories]
466
537
## Zooming and Scrolling
539
def _scrollEventCb(self, canvas, event):
540
if event.state & gtk.gdk.SHIFT_MASK:
541
# shift + scroll => vertical (up/down) scroll
542
if event.direction == gtk.gdk.SCROLL_UP:
544
elif event.direction == gtk.gdk.SCROLL_DOWN:
546
event.state &= ~gtk.gdk.SHIFT_MASK
547
elif event.state & gtk.gdk.CONTROL_MASK:
548
# zoom + scroll => zooming (up: zoom in)
549
if event.direction == gtk.gdk.SCROLL_UP:
552
elif event.direction == gtk.gdk.SCROLL_DOWN:
557
if event.direction == gtk.gdk.SCROLL_UP:
559
elif event.direction == gtk.gdk.SCROLL_DOWN:
563
def scroll_left(self):
564
self._hscrollbar.set_value (self._hscrollbar.get_value() -
565
self.hadj.props.page_size ** (2.0 / 3.0))
567
def scroll_right(self):
568
self._hscrollbar.set_value (self._hscrollbar.get_value() +
569
self.hadj.props.page_size ** (2.0 / 3.0))
572
self._vscrollbar.set_value (self._vscrollbar.get_value() -
573
self.vadj.props.page_size ** (2.0 / 3.0))
575
def scroll_down(self):
576
self._vscrollbar.set_value (self._vscrollbar.get_value() +
577
self.vadj.props.page_size ** (2.0 / 3.0))
468
579
def _updateScrollPosition(self, adjustment):
469
self._root_item.set_simple_transform( -self.hadj.get_value(),
580
self._scroll_pos_ns = Zoomable.pixelToNs(self.hadj.get_value())
581
self._root_item.set_simple_transform( -self.hadj.get_value(),
470
582
-self.vadj.get_value(), 1.0, 0)
472
584
def _zoomAdjustmentChangedCb(self, adjustment):
475
587
Zoomable.setZoomLevel(int(adjustment.get_value()))
476
588
self._updateZoom = True
590
def _unsureVadjHeightCb(self, adj):
591
# GTK crack, without that, at loading a project, the vadj upper
592
# property is reset to be equal as the lower, right after the
593
# trackobjects are added to the timeline (bug: #648714)
594
if self.vadj.props.upper < self._canvas.height:
595
self.vadj.props.upper = self._canvas.height
599
def _zoomSliderScrollCb(self, unused_widget, event):
600
value = self._zoomAdjustment.get_value()
601
if event.direction in [gtk.gdk.SCROLL_UP, gtk.gdk.SCROLL_RIGHT]:
602
self._zoomAdjustment.set_value(value + 1)
603
elif event.direction in [gtk.gdk.SCROLL_DOWN, gtk.gdk.SCROLL_LEFT]:
604
self._zoomAdjustment.set_value(value - 1)
478
606
def zoomChanged(self):
479
self._canvas.props.redraw_when_scrolled = True
480
607
if self._updateZoom:
481
608
self._zoomAdjustment.set_value(self.getCurrentZoomLevel())
610
# the new scroll position should preserve the current horizontal
611
# position of the playhead in the window
612
cur_playhead_offset = self._canvas._playhead.props.x -\
613
self.hadj.props.value
614
new_pos = Zoomable.nsToPixel(self._position) - cur_playhead_offset
617
self._updateScrollAdjustments()
618
self._scrollToPosition(new_pos)
482
619
self.ruler.queue_resize()
483
620
self.ruler.queue_draw()
556
694
@handler(timeline, "duration-changed")
557
695
def _timelineStartDurationChanged(self, unused_timeline, duration):
559
self._prev_duration = duration
560
self.ruler.setMaxDuration(duration + 60 * gst.SECOND)
561
self._canvas.setMaxDuration(duration + 60 * gst.SECOND)
562
self.ruler.setShadedDuration(duration)
564
# only resize if new size is larger
565
if duration > self._prev_duration:
566
self._prev_duration = duration
567
self.ruler.setMaxDuration(duration)
568
self._canvas.setMaxDuration(duration)
569
#self.ruler.setShadedDuration(duration)
696
self._prev_duration = duration
697
self.ruler.setMaxDuration(duration + 60 * gst.SECOND)
698
self._canvas.setMaxDuration(duration + 60 * gst.SECOND)
699
self.ruler.setShadedDuration(duration)
700
self._updateScrollAdjustments()
702
def _updateScrollAdjustments(self):
703
a = self.get_allocation()
704
size = Zoomable.nsToPixel(self.timeline.duration)
705
self.hadj.props.lower = 0
706
self.hadj.props.upper = size + 200 # why is this necessary???
707
self.hadj.props.page_size = a.width
708
self.hadj.props.page_increment = size * 0.9
709
self.hadj.props.step_increment = size * 0.1
571
712
@handler(timeline, "selection-changed")
572
713
def _timelineSelectionChanged(self, timeline):