def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.props.row_spacing = 2 self.props.column_spacing = 2 self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # zooming slider self._zoomAdjustment = gtk.Adjustment() self._zoomAdjustment.set_value(Zoomable.getCurrentZoomLevel()) self._zoomAdjustment.connect("value-changed", self._zoomAdjustmentChangedCb) self._zoomAdjustment.props.lower = 0 self._zoomAdjustment.props.upper = Zoomable.zoom_steps zoomslider = gtk.HScale(self._zoomAdjustment) zoomslider.props.draw_value = False zoomslider.set_tooltip_text(_("Zoom Timeline")) self.attach(zoomslider, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL) # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.Viewport(None, self.vadj) controlwindow.add(self._controls) controlwindow.set_size_request(-1, 1) controlwindow.set_shadow_type(gtk.SHADOW_OUT) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.app, self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.ruler.connect("size-allocate", self._rulerSizeAllocateCb) rulerframe = gtk.Frame() rulerframe.set_shadow_type(gtk.SHADOW_OUT) rulerframe.add(self.ruler) self.attach(rulerframe, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) self._root_item = self._canvas.get_root_item() self.attach(self._canvas, 1, 2, 1, 2) # scrollbar self._hscrollbar = gtk.HScrollbar(self.hadj) self._vscrollbar = gtk.VScrollbar(self.vadj) self.attach(self._hscrollbar, 1, 2, 2, 3, yoptions=0) self.attach(self._vscrollbar, 2, 3, 1, 2, xoptions=0) self.hadj.connect("value-changed", self._updateScrollPosition) self.vadj.connect("value-changed", self._updateScrollPosition) # error infostub self.infostub = InfoStub() self.attach(self.infostub, 1, 2, 4, 5, yoptions=0) self.show_all() self.infostub.hide() # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, "<Control>plus", ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, "<Control>minus", ZOOM_OUT, self._zoomOutCb), # actions for adding additional accelerators ("ControlEqualAccel", gtk.STOCK_ZOOM_IN, None, "<Control>equal", ZOOM_IN, self._zoomInCb), ("ControlKPAddAccel", gtk.STOCK_ZOOM_IN, None, "<Control>KP_Add", ZOOM_IN, self._zoomInCb), ("ControlKPSubtractAccel", gtk.STOCK_ZOOM_OUT, None, "<Control>KP_Subtract", ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) self.playhead_actions = ( ("Split", "pitivi-split", _("Split"), "S", SPLIT, self.split), ("Keyframe", "pitivi-keyframe", _("Add a keyframe"), "K", KEYFRAME, self.keyframe), ("Prevframe", "pitivi-prevframe", _("_Prevframe"), "E", PREVFRAME, self.prevframe), ("Nextframe", "pitivi-nextframe", _("_Nextframe"), "R", NEXTFRAME, self.nextframe), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) actiongroup.add_actions(self.playhead_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.split_action = actiongroup.get_action("Split") self.keyframe_action = actiongroup.get_action("Keyframe") self.prevframe_action = actiongroup.get_action("Prevframe") self.nextframe_action = actiongroup.get_action("Nextframe") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE, dnd.EFFECT_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb)
class Timeline(gtk.Table, Loggable, Zoomable): # the screen width of the current unit unit_width = 10 # specific levels of zoom, in (multiplier, unit) pairs which # from zoomed out to zoomed in def __init__(self, instance, ui_manager): gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False) Loggable.__init__(self) Zoomable.__init__(self) self.log("Creating Timeline") self._updateZoom = True self.project = None self.ui_manager = ui_manager self.app = instance self._temp_objects = None self._factories = None self._finish_drag = False self._position = 0 self._state = gst.STATE_NULL self._createUI() self._prev_duration = 0 self.shrink = True self.rate = gst.Fraction(1,1) def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.props.row_spacing = 2 self.props.column_spacing = 2 self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # zooming slider self._zoomAdjustment = gtk.Adjustment() self._zoomAdjustment.set_value(Zoomable.getCurrentZoomLevel()) self._zoomAdjustment.connect("value-changed", self._zoomAdjustmentChangedCb) self._zoomAdjustment.props.lower = 0 self._zoomAdjustment.props.upper = Zoomable.zoom_steps zoomslider = gtk.HScale(self._zoomAdjustment) zoomslider.props.draw_value = False zoomslider.set_tooltip_text(_("Zoom Timeline")) self.attach(zoomslider, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL) # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.Viewport(None, self.vadj) controlwindow.add(self._controls) controlwindow.set_size_request(-1, 1) controlwindow.set_shadow_type(gtk.SHADOW_OUT) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.app, self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.ruler.connect("size-allocate", self._rulerSizeAllocateCb) rulerframe = gtk.Frame() rulerframe.set_shadow_type(gtk.SHADOW_OUT) rulerframe.add(self.ruler) self.attach(rulerframe, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) self._root_item = self._canvas.get_root_item() self.attach(self._canvas, 1, 2, 1, 2) # scrollbar self._hscrollbar = gtk.HScrollbar(self.hadj) self._vscrollbar = gtk.VScrollbar(self.vadj) self.attach(self._hscrollbar, 1, 2, 2, 3, yoptions=0) self.attach(self._vscrollbar, 2, 3, 1, 2, xoptions=0) self.hadj.connect("value-changed", self._updateScrollPosition) self.vadj.connect("value-changed", self._updateScrollPosition) # error infostub self.infostub = InfoStub() self.attach(self.infostub, 1, 2, 4, 5, yoptions=0) self.show_all() self.infostub.hide() # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, "<Control>plus", ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, "<Control>minus", ZOOM_OUT, self._zoomOutCb), # actions for adding additional accelerators ("ControlEqualAccel", gtk.STOCK_ZOOM_IN, None, "<Control>equal", ZOOM_IN, self._zoomInCb), ("ControlKPAddAccel", gtk.STOCK_ZOOM_IN, None, "<Control>KP_Add", ZOOM_IN, self._zoomInCb), ("ControlKPSubtractAccel", gtk.STOCK_ZOOM_OUT, None, "<Control>KP_Subtract", ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) self.playhead_actions = ( ("Split", "pitivi-split", _("Split"), "S", SPLIT, self.split), ("Keyframe", "pitivi-keyframe", _("Add a keyframe"), "K", KEYFRAME, self.keyframe), ("Prevframe", "pitivi-prevframe", _("_Prevframe"), "E", PREVFRAME, self.prevframe), ("Nextframe", "pitivi-nextframe", _("_Nextframe"), "R", NEXTFRAME, self.nextframe), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) actiongroup.add_actions(self.playhead_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.split_action = actiongroup.get_action("Split") self.keyframe_action = actiongroup.get_action("Keyframe") self.prevframe_action = actiongroup.get_action("Prevframe") self.nextframe_action = actiongroup.get_action("Nextframe") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE, dnd.EFFECT_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb) ## Event callbacks def _keyPressEventCb(self, unused_widget, event): kv = event.keyval self.debug("kv:%r", kv) if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]: return False mod = event.get_state() try: if mod & gtk.gdk.CONTROL_MASK: now = self.project.pipeline.getPosition() ltime, rtime = self.project.timeline.edges.closest(now) if kv == gtk.keysyms.Left: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(-gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(ltime+1) else: self._seekRelative(-long(self.rate * gst.SECOND)) elif kv == gtk.keysyms.Right: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(rtime+1) else: self._seekRelative(long(self.rate * gst.SECOND)) finally: return True def _seekRelative(self, time): pipeline = self.project.pipeline seekvalue = max(0, min(pipeline.getPosition() + time, pipeline.getDuration())) self._seeker.seek(seekvalue) def _buttonPress(self, window, event): self.shrink = False def _buttonRelease(self, window, event): self.shrink = True self._timelineStartDurationChanged(self.timeline, self.timeline.duration) ## Drag and Drop callbacks def _dragMotionCb(self, unused, context, x, y, timestamp): self.warning("self._factories:%r, self._temp_objects:%r", not not self._factories, not not self._temp_objects) if self._factories is None: if context.targets in DND_EFFECT_LIST: atom = gtk.gdk.atom_intern(dnd.EFFECT_TUPLE[0]) else: atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0]) self.drag_get_data(context, atom, timestamp) self.drag_highlight() else: if context.targets not in DND_EFFECT_LIST: if not self._temp_objects: self.timeline.disableUpdates() self._add_temp_source() focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) return True def _dragLeaveCb(self, unused_layout, context, unused_tstamp): if self._temp_objects: try: for obj in self._temp_objects: self.timeline.removeTimelineObject(obj, deep=True) finally: self._temp_objects = None self.drag_unhighlight() self.timeline.enableUpdates() def _dragDropCb(self, widget, context, x, y, timestamp): if context.targets not in DND_EFFECT_LIST: self.app.action_log.begin("add clip") self.timeline.disableUpdates() self._add_temp_source() self.timeline.selection.setSelection(self._temp_objects, SELECT) focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) self._move_context.finish() self.app.action_log.commit() context.drop_finish(True, timestamp) self._factories = None self._temp_objects = None self.app.current.seeker.seek(self._position) return True elif context.targets in DND_EFFECT_LIST: if not self.timeline.timeline_objects: return False factory = self._factories[0] timeline_objs = self._getTimelineObjectUnderMouse(x, y, factory.getInputStreams()[0]) if timeline_objs: self.app.action_log.begin("add effect") self.timeline.addEffectFactoryOnObject(factory, timeline_objects = timeline_objs) self.app.action_log.commit() self._factories = None self.app.current.seeker.seek(self._position) context.drop_finish(True, timestamp) self.timeline.selection.setSelection(timeline_objs, SELECT) return True return False def _dragDataReceivedCb(self, unused_layout, context, x, y, selection, targetType, timestamp): self.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data)) # FIXME: let's have just one target type, call it # TYPE_PITIVI_OBJECTFACTORY. # TODO: handle uri targets by doign an import-add. This would look # something like this: # tell current project to import the uri # wait for source-added signal, meanwhile ignore dragMotion signals # when ready, add factories to the timeline. if targetType not in [dnd.TYPE_PITIVI_FILESOURCE, dnd.TYPE_PITIVI_EFFECT]: context.finish(False, False, timestamp) return if targetType == dnd.TYPE_PITIVI_FILESOURCE: uris = selection.data.split("\n") self._factories = [self.project.sources.getUri(uri) for uri in uris] else: if not self.timeline.timeline_objects: return False self._factories = [self.app.effects.getFactoryFromName(selection.data)] context.drag_status(gtk.gdk.ACTION_COPY, timestamp) return True def _getTimelineObjectUnderMouse(self, x, y, stream): timeline_objs = [] items_in_area = self._canvas.getItemsInArea(x, y-15, x+1, y-30) tracks = [obj for obj in items_in_area[0]] track_objects = [obj for obj in items_in_area[1]] for track_object in track_objects: if (type(stream) == type(track_object.stream)): timeline_objs.append(track_object.timeline_object) return timeline_objs def _add_temp_source(self): self._temp_objects = [self.timeline.addSourceFactory(factory) for factory in self._factories] def _move_temp_source(self, x, y): x1, y1, x2, y2 = self._controls.get_allocation() offset = 10 + (x2 - x1) x, y = self._canvas.convert_from_pixels(x - offset, y) priority = int((y // (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))) delta = Zoomable.pixelToNs(x) self._move_context.editTo(delta, priority) ## Zooming and Scrolling def _updateScrollPosition(self, adjustment): self._root_item.set_simple_transform( -self.hadj.get_value(), -self.vadj.get_value(), 1.0, 0) def _zoomAdjustmentChangedCb(self, adjustment): # GTK crack self._updateZoom = False Zoomable.setZoomLevel(int(adjustment.get_value())) self._updateZoom = True def zoomChanged(self): self._canvas.props.redraw_when_scrolled = True if self._updateZoom: self._zoomAdjustment.set_value(self.getCurrentZoomLevel()) self.ruler.queue_resize() self.ruler.queue_draw() def timelinePositionChanged(self, position): self._position = position self.ruler.timelinePositionChanged(position) self._canvas.timelinePositionChanged(position) if self._state == gst.STATE_PLAYING: self.scrollToPlayhead() def stateChanged(self, state): self._state = state def scrollToPlayhead(self): """ If the current position is out of the view bouds, then scroll as close to the center of the view as possible or as close as the timeline canvas allows. """ page_size = self.hadj.get_page_size() new_pos = Zoomable.nsToPixel(self._position) scroll_pos = self.hadj.get_value() if (new_pos > scroll_pos + page_size) or (new_pos < scroll_pos): self.scrollToPosition(min(new_pos - page_size / 2, self.hadj.upper - page_size - 1)) return False def scrollToPosition(self, position): if position > self.hadj.upper: # we can't perform the scroll because the canvas needs to be # updated gobject.idle_add(self._scrollToPosition, position) else: self._scrollToPosition(position) def _scrollToPosition(self, position): self.hadj.set_value(position) return False def _rulerSizeAllocateCb(self, ruler, allocation): self._canvas.props.redraw_when_scrolled = False ## Project callbacks def _setProject(self): if self.project: self.timeline = self.project.timeline self._controls.timeline = self.timeline self._canvas.timeline = self.timeline self._canvas.zoomChanged() self.ruler.setProjectFrameRate(self.project.getSettings().videorate) self.ruler.zoomChanged() self._settingsChangedCb(self.project, None, self.project.getSettings()) self._seeker = self.project.seeker project = receiver(_setProject) @handler(project, "settings-changed") def _settingsChangedCb(self, project, old, new): rate = new.videorate self.rate = float(1 / rate) self.ruler.setProjectFrameRate(rate) ## Timeline callbacks def _setTimeline(self): if self.timeline: self._timelineSelectionChanged(self.timeline) self._timelineStartDurationChanged(self.timeline, self.timeline.duration) self._controls.timeline = self.timeline timeline = receiver(_setTimeline) @handler(timeline, "duration-changed") def _timelineStartDurationChanged(self, unused_timeline, duration): if self.shrink: self._prev_duration = duration self.ruler.setMaxDuration(duration + 60 * gst.SECOND) self._canvas.setMaxDuration(duration + 60 * gst.SECOND) self.ruler.setShadedDuration(duration) else: # only resize if new size is larger if duration > self._prev_duration: self._prev_duration = duration self.ruler.setMaxDuration(duration) self._canvas.setMaxDuration(duration) #self.ruler.setShadedDuration(duration) @handler(timeline, "selection-changed") def _timelineSelectionChanged(self, timeline): delete = False link = False unlink = False group = False ungroup = False split = False keyframe = False timeline_objects = {} if timeline.selection: delete = True if len(timeline.selection) > 1: link = True group = True start = None duration = None for obj in self.timeline.selection: if obj.link: link = False unlink = True if len(obj.track_objects) > 1: ungroup = True if start is not None and duration is not None: if obj.start != start or obj.duration != duration: group = False else: start = obj.start duration = obj.duration split = True keyframe = True self.delete_action.set_sensitive(delete) self.link_action.set_sensitive(link) self.unlink_action.set_sensitive(unlink) self.group_action.set_sensitive(group) self.ungroup_action.set_sensitive(ungroup) self.split_action.set_sensitive(split) self.keyframe_action.set_sensitive(keyframe) ## ToolBar callbacks def hide(self): self.actiongroup.set_visible(False) gtk.Vbox.hide(self) def _zoomInCb(self, unused_action): Zoomable.zoomIn() def _zoomOutCb(self, unused_action): Zoomable.zoomOut() def deleteSelected(self, unused_action): if self.timeline: self.app.action_log.begin("delete clip") self.timeline.deleteSelection() self.app.action_log.commit() def unlinkSelected(self, unused_action): if self.timeline: self.timeline.unlinkSelection() def linkSelected(self, unused_action): if self.timeline: self.timeline.linkSelection() def ungroupSelected(self, unused_action): if self.timeline: self.app.action_log.begin("ungroup") self.timeline.ungroupSelection() self.app.action_log.commit() def groupSelected(self, unused_action): if self.timeline: self.timeline.groupSelection() def split(self, action): self.app.action_log.begin("split") self.timeline.disableUpdates() self.timeline.split(self._position) self.timeline.enableUpdates() self.app.action_log.commit() # work-around for 603149 self.project.seeker.seek(self._position) def keyframe(self, action): timeline_position = self._position selected = self.timeline.selection.getSelectedTrackObjs() for obj in selected: keyframe_exists = False position_in_obj = (timeline_position - obj.start) + obj.in_point interpolators = obj.getInterpolators() for value in interpolators: interpolator = obj.getInterpolator(value) keyframes = interpolator.getInteriorKeyframes() for kf in keyframes: if kf.getTime() == position_in_obj: keyframe_exists = True self.app.action_log.begin("remove volume point") interpolator.removeKeyframe(kf) self.app.action_log.commit() if keyframe_exists == False: self.app.action_log.begin("add volume point") interpolator.newKeyframe(position_in_obj) self.app.action_log.commit() def prevframe(self, action): timeline_position = self._position prev_kf = self.timeline.getPrevKeyframe(timeline_position) if prev_kf != None: self._seeker.seek(prev_kf) self.scrollToPlayhead() def nextframe(self, action): timeline_position = self._position next_kf = self.timeline.getNextKeyframe(timeline_position) if next_kf: self._seeker.seek(next_kf) self.scrollToPlayhead()
def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.ScrolledWindow(None, self.vadj) controlwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) controlwindow.add_with_viewport(self._controls) controlwindow.set_size_request(-1, 1) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.attach(self.ruler, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj) timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) timelinewindow.add(self._canvas) #FIXME: remove padding between scrollbar and scrolled window self.attach(timelinewindow, 1, 2, 1, 2) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, None, ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, None, ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) toggle_actions = (("Razor", "pitivi-split", _("Razor"), "<Ctrl>R", RAZOR, self.toggleRazor), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) actiongroup.add_toggle_actions(toggle_actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb)
def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.props.row_spacing = 2 self.props.column_spacing = 2 self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # zooming slider self._zoomAdjustment = gtk.Adjustment() self._zoomAdjustment.set_value(Zoomable.getCurrentZoomLevel()) self._zoomAdjustment.connect("value-changed", self._zoomAdjustmentChangedCb) self._zoomAdjustment.props.lower = 0 self._zoomAdjustment.props.upper = Zoomable.zoom_steps zoomslider = gtk.HScale(self._zoomAdjustment) zoomslider.props.draw_value = False zoomslider.set_tooltip_text(_("Zoom Timeline")) self.attach(zoomslider, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL) # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.Viewport(None, self.vadj) controlwindow.add(self._controls) controlwindow.set_size_request(-1, 1) controlwindow.set_shadow_type(gtk.SHADOW_OUT) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.app, self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.ruler.connect("size-allocate", self._rulerSizeAllocateCb) rulerframe = gtk.Frame() rulerframe.set_shadow_type(gtk.SHADOW_OUT) rulerframe.add(self.ruler) self.attach(rulerframe, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj) timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) timelinewindow.add(self._canvas) timelinewindow.set_shadow_type(gtk.SHADOW_IN) timelinewindow.set_name("timelinewindow") # temp fix for padding between scrollbar and scrolled window #FIXME: should be set at an global position for easy editing? gtk.rc_parse_string(""" style 'timelinewindow' { GtkScrolledWindow::scrollbar-spacing = 0 } widget '*.timelinewindow' style 'timelinewindow' """) self.attach(timelinewindow, 1, 2, 1, 2) # error infostub self.infostub = InfoStub() self.attach(self.infostub, 1, 2, 2, 3, yoptions=0) self.show_all() self.infostub.hide() # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, "<Control>equal", ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, "<Control>minus", ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) playhead_actions = ( ("Split", "pitivi-split", _("Split"), "S", SPLIT, self.split), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) actiongroup.add_actions(playhead_actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb)
class Timeline(gtk.Table, Loggable, Zoomable): # the screen width of the current unit unit_width = 10 # specific levels of zoom, in (multiplier, unit) pairs which # from zoomed out to zoomed in def __init__(self, instance, ui_manager): gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False) Loggable.__init__(self) Zoomable.__init__(self) self.log("Creating Timeline") self.project = None self.ui_manager = ui_manager self.app = instance self._temp_objects = None self._factories = None self._finish_drag = False self._position = 0 self._state = gst.STATE_NULL self._createUI() self._prev_duration = 0 self.shrink = True self.rate = gst.Fraction(1, 1) self._seeker = Seeker(80) self._seeker.connect('seek', self._seekerSeekCb) def _createUI(self): self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self.hadj = gtk.Adjustment() self.vadj = gtk.Adjustment() # controls for tracks and layers self._controls = TimelineControls() controlwindow = gtk.ScrolledWindow(None, self.vadj) controlwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) controlwindow.add_with_viewport(self._controls) controlwindow.set_size_request(-1, 1) self.attach(controlwindow, 0, 1, 1, 2, xoptions=0) # timeline ruler self.ruler = ruler.ScaleRuler(self.hadj) self.ruler.set_size_request(0, 25) self.ruler.set_border_width(2) self.ruler.connect("key-press-event", self._keyPressEventCb) self.attach(self.ruler, 1, 2, 0, 1, yoptions=0) # proportional timeline self._canvas = TimelineCanvas(self.app) timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj) timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) timelinewindow.add(self._canvas) #FIXME: remove padding between scrollbar and scrolled window self.attach(timelinewindow, 1, 2, 1, 2) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) # toolbar actions actions = ( ("ZoomIn", gtk.STOCK_ZOOM_IN, None, None, ZOOM_IN, self._zoomInCb), ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, None, ZOOM_OUT, self._zoomOutCb), ) selection_actions = ( ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE, self.deleteSelected), ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK, self.unlinkSelected), ("LinkObj", "pitivi-link", None, "<Control>L", LINK, self.linkSelected), ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP, self.ungroupSelected), ("GroupObj", "pitivi-group", None, "<Control>G", GROUP, self.groupSelected), ) toggle_actions = (("Razor", "pitivi-split", _("Razor"), "<Ctrl>R", RAZOR, self.toggleRazor), ) actiongroup = gtk.ActionGroup("timelinepermanent") actiongroup.add_actions(actions) actiongroup.add_toggle_actions(toggle_actions) self.ui_manager.insert_action_group(actiongroup, 0) actiongroup = gtk.ActionGroup("timelineselection") actiongroup.add_actions(selection_actions) self.link_action = actiongroup.get_action("LinkObj") self.unlink_action = actiongroup.get_action("UnlinkObj") self.group_action = actiongroup.get_action("GroupObj") self.ungroup_action = actiongroup.get_action("UngroupObj") self.delete_action = actiongroup.get_action("DeleteObj") self.ui_manager.insert_action_group(actiongroup, -1) self.ui_manager.add_ui_from_string(ui) # drag and drop self.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [dnd.FILESOURCE_TUPLE], gtk.gdk.ACTION_COPY) self.connect("drag-data-received", self._dragDataReceivedCb) self.connect("drag-leave", self._dragLeaveCb) self.connect("drag-drop", self._dragDropCb) self.connect("drag-motion", self._dragMotionCb) self._canvas.connect("button-press-event", self._buttonPress) self._canvas.connect("button-release-event", self._buttonRelease) self._canvas.connect("key-press-event", self._keyPressEventCb) ## Event callbacks def _keyPressEventCb(self, unused_widget, event): kv = event.keyval self.debug("kv:%r", kv) if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]: return False mod = event.get_state() try: if mod & gtk.gdk.CONTROL_MASK: now = self.project.pipeline.getPosition() ltime, rtime = self.project.timeline.edges.closest(now) if kv == gtk.keysyms.Left: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(-gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(ltime + 1) else: self._seekRelative(-long(self.rate * gst.SECOND)) elif kv == gtk.keysyms.Right: if mod & gtk.gdk.SHIFT_MASK: self._seekRelative(gst.SECOND) elif mod & gtk.gdk.CONTROL_MASK: self._seeker.seek(rtime + 1) else: self._seekRelative(long(self.rate * gst.SECOND)) finally: return True def _seekRelative(self, time): pipeline = self.project.pipeline seekvalue = max( 0, min(pipeline.getPosition() + time, pipeline.getDuration())) self._seeker.seek(seekvalue) def _seekerSeekCb(self, seeker, position, format): self.project.pipeline.seek(position, format) def _buttonPress(self, window, event): self.shrink = False def _buttonRelease(self, window, event): self.shrink = True self._timelineStartDurationChanged(self.timeline, self.timeline.duration) ## Drag and Drop callbacks def _dragMotionCb(self, unused, context, x, y, timestamp): self.warning("self._factories:%r, self._temp_objects:%r", not not self._factories, not not self._temp_objects) if self._factories is None: atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0]) self.drag_get_data(context, atom, timestamp) self.drag_highlight() else: # actual drag-and-drop if not self._temp_objects: self.timeline.disableUpdates() self._add_temp_source() focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) return True def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp): if self._temp_objects: try: for obj in self._temp_objects: self.timeline.removeTimelineObject(obj, deep=True) finally: self._temp_objects = None self.drag_unhighlight() self.timeline.enableUpdates() def _dragDropCb(self, widget, context, x, y, timestamp): self.app.action_log.begin("add clip") self._add_temp_source() focus = self._temp_objects[0] self._move_context = MoveContext(self.timeline, focus, set(self._temp_objects[1:])) self._move_temp_source(self.hadj.props.value + x, y) self._move_context.finish() self.app.action_log.commit() context.drop_finish(True, timestamp) self._factories = None self._temp_objects = None return True def _dragDataReceivedCb(self, unused_layout, context, x, y, selection, targetType, timestamp): self.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data)) # FIXME: let's have just one target type, call it # TYPE_PITIVI_OBJECTFACTORY. # TODO: handle uri targets by doign an import-add. This would look # something like this: # tell current project to import the uri # wait for source-added signal, meanwhile ignore dragMotion signals # when ready, add factories to the timeline. if targetType == dnd.TYPE_PITIVI_FILESOURCE: uris = selection.data.split("\n") else: context.finish(False, False, timestamp) self._factories = [self.project.sources.getUri(uri) for uri in uris] context.drag_status(gtk.gdk.ACTION_COPY, timestamp) return True def _add_temp_source(self): self._temp_objects = [ self.timeline.addSourceFactory(factory) for factory in self._factories ] def _move_temp_source(self, x, y): x1, y1, x2, y2 = self._controls.get_allocation() offset = 10 + (x2 - x1) x, y = self._canvas.convert_from_pixels(x - offset, y) priority = int((y // (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))) delta = Zoomable.pixelToNs(x) self._move_context.editTo(delta, priority) ## Zooming and Scrolling def zoomChanged(self): # this has to be in a timeout, because the resize hasn't actually # completed yet, and so the canvas can't actually complete the scroll gobject.idle_add(self.scrollToPlayhead) def timelinePositionChanged(self, position): self._position = position self.ruler.timelinePositionChanged(position) self._canvas._position = position if self._state == gst.STATE_PLAYING: self.scrollToPlayhead() def stateChanged(self, state): self._state = state def scrollToPlayhead(self): width = self.get_allocation().width new_pos = Zoomable.nsToPixel(self._position) scroll_pos = self.hadj.get_value() if (new_pos < scroll_pos) or (new_pos > scroll_pos + width): self.scrollToPosition(new_pos - width / 2) return False def scrollToPosition(self, position): if position > self.hadj.upper: # we can't perform the scroll because the canvas needs to be # updated gobject.idle_add(self._scrollToPosition, position) else: self._scrollToPosition(position) def _scrollToPosition(self, position): self.hadj.set_value(position) return False ## Project callbacks def _setProject(self): if self.project: self.timeline = self.project.timeline self._controls.timeline = self.timeline self._canvas.timeline = self.timeline self._canvas.zoomChanged() self.ruler.setProjectFrameRate( self.project.getSettings().videorate) self.ruler.zoomChanged() self._settingsChangedCb(self.project) project = receiver(_setProject) @handler(project, "settings-changed") def _settingsChangedCb(self, project): rate = self.project.getSettings().videorate self.rate = float(1 / rate) self.ruler.setProjectFrameRate(rate) ## Timeline callbacks def _setTimeline(self): if self.timeline: self._timelineSelectionChanged(self.timeline) self._timelineStartDurationChanged(self.timeline, self.timeline.duration) self._controls.timeline = self.timeline timeline = receiver(_setTimeline) @handler(timeline, "duration-changed") def _timelineStartDurationChanged(self, unused_timeline, duration): if self.shrink: self._prev_duration = duration self.ruler.setMaxDuration(duration + 60 * gst.SECOND) self._canvas.setMaxDuration(duration + 60 * gst.SECOND) self.ruler.setShadedDuration(duration) else: # only resize if new size is larger if duration > self._prev_duration: self._prev_duration = duration self.ruler.setMaxDuration(duration) self._canvas.setMaxDuration(duration) #self.ruler.setShadedDuration(duration) @handler(timeline, "selection-changed") def _timelineSelectionChanged(self, timeline): delete = False link = False unlink = False group = False ungroup = False timeline_objects = {} if timeline.selection: delete = True if len(timeline.selection) > 1: link = True group = True start = None duration = None for obj in self.timeline.selection: if obj.link: unlink = True if len(obj.track_objects) > 1: ungroup = True if start is not None and duration is not None: if obj.start != start or obj.duration != duration: group = False else: start = obj.start duration = obj.duration self.delete_action.set_sensitive(delete) self.link_action.set_sensitive(link) self.unlink_action.set_sensitive(unlink) self.group_action.set_sensitive(group) self.ungroup_action.set_sensitive(ungroup) ## ToolBar callbacks def hide(self): self.actiongroup.set_visible(False) gtk.Vbox.hide(self) def _zoomInCb(self, unused_action): Zoomable.zoomIn() def _zoomOutCb(self, unused_action): Zoomable.zoomOut() def deleteSelected(self, unused_action): if self.timeline: self.app.action_log.begin("delete clip") self.timeline.deleteSelection() self.app.action_log.commit() def unlinkSelected(self, unused_action): if self.timeline: self.timeline.unlinkSelection() def linkSelected(self, unused_action): if self.timeline: self.timeline.linkSelection() def ungroupSelected(self, unused_action): if self.timeline: self.timeline.ungroupSelection() def groupSelected(self, unused_action): if self.timeline: self.timeline.groupSelection() def toggleRazor(self, action): if action.props.active: self._canvas.activateRazor(action) else: self._canvas.deactivateRazor()