Esempio n. 1
0
    def _createUi(self):
        self.embed = GtkClutter.Embed()
        self.embed.get_accessible().set_name("timeline canvas")  # for dogtail
        self.stage = self.embed.get_stage()

        self.timeline = TimelineStage(self)
        self.controls = ControlContainer(self.timeline)
        self.zoomBox = ZoomBox(self)
        self.shiftMask = False
        self.controlMask = False

        # TODO: make the bg a gradient from (0, 0, 0, 255) to (50, 50, 50, 255)
        self.stage.set_background_color(Clutter.Color.new(31, 30, 33, 255))
        self.timeline.set_position(CONTROL_WIDTH, 0)
        self.controls.set_position(0, 0)
        self.controls.set_z_position(2)

        self.stage.add_child(self.controls)
        self.stage.add_child(self.timeline)

        self.stage.connect("destroy", quit_)
        self.stage.connect("button-press-event", self._clickedCb)
        self.stage.connect("button-release-event", self._releasedCb)
        self.embed.connect("scroll-event", self._scrollEventCb)
        if self.gui:
            self.gui.connect("key-press-event", self._keyPressEventCb)
            self.gui.connect("key-release-event", self._keyReleaseEventCb)

        self.embed.connect("enter-notify-event", self._enterNotifyEventCb)

        self.point = Clutter.Point()
        self.point.x = 0
        self.point.y = 0

        self.scrolled = 0

        self.zoomed_fitted = True
        self.pressed = False

        self._packScrollbars(self)
        self.stage.show()
Esempio n. 2
0
class Timeline(Gtk.VBox, Zoomable):
    """
    This is the main timeline widget, which will contain the timeline stage
    and the layer controls, the scrollbars and the ruler.
    """
    def __init__(self, gui, instance, ui_manager):
        Zoomable.__init__(self)
        Gtk.VBox.__init__(self)

        GObject.threads_init()

        self.gui = gui
        self.ui_manager = ui_manager
        self.app = instance
        self._settings = None
        if self.app:
            self._settings = self.app.settings

        self._projectmanager = None
        self._project = None
        self.pipeline = None

        self._createUi()
        self._createActions()

        self._setUpDragAndDrop()

        if self._settings:
            self._settings.connect("edgeSnapDeadbandChanged",
                                   self._snapDistanceChangedCb)

        # Standalone
        if not self._settings:
            gtksettings = Gtk.Settings.get_default()
            gtksettings.set_property("gtk-application-prefer-dark-theme", True)

        self.show_all()

    # Public API

    def insertEnd(self, assets):
        """
        Allows to add any asset at the end of the current timeline.
        """
        self.app.action_log.begin("add clip")

        # FIXME we should find the longest layer instead of adding it to the
        # first one
        # Handle the case of a blank project
        layer = self._ensureLayer()[0]

        for asset in assets:
            if isinstance(asset, GES.TitleClip):
                clip_duration = asset.get_duration()
            elif asset.is_image():
                clip_duration = long(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
            else:
                clip_duration = asset.get_duration()

            if not isinstance(asset, GES.TitleClip):
                layer.add_asset(asset, self.bTimeline.props.duration,
                                0, clip_duration, asset.get_supported_formats())
            else:
                asset.set_start(self.bTimeline.props.duration)
                layer.add_clip(asset)

        if self.zoomed_fitted:
            self._setBestZoomRatio()
        else:
            self.scrollToPosition(self.bTimeline.props.duration)

    def setProjectManager(self, projectmanager):
        if self._projectmanager is not None:
            self._projectmanager.disconnect_by_func(self._projectChangedCb)

        self._projectmanager = projectmanager

        if projectmanager is not None:
            projectmanager.connect("new-project-created", self._projectCreatedCb)
            projectmanager.connect("new-project-loaded", self._projectChangedCb)

    def updateHScrollAdjustments(self):
        """
        Recalculate the horizontal scrollbar depending on the timeline duration.
        """
        timeline_ui_width = self.embed.get_allocation().width
        controls_width = 0
        scrollbar_width = 0
        contents_size = Zoomable.nsToPixel(self.bTimeline.props.duration)

        widgets_width = controls_width + scrollbar_width
        end_padding = CONTROL_WIDTH + 250  # Provide some space for clip insertion at the end

        self.hadj.props.lower = 0
        self.hadj.props.upper = contents_size + widgets_width + end_padding
        self.hadj.props.page_size = timeline_ui_width
        self.hadj.props.page_increment = contents_size * 0.9
        self.hadj.props.step_increment = contents_size * 0.1

        if contents_size + widgets_width <= timeline_ui_width:
            # We're zoomed out completely, re-enable automatic zoom fitting
            # when adding new clips.
            self.zoomed_fitted = True

    def zoomFit(self):
        self._hscrollBar.set_value(0)
        self._setBestZoomRatio()

    def scrollToPosition(self, position):
        if position > self.hadj.props.upper:
            # we can't perform the scroll because the canvas needs to be
            # updated
            GLib.idle_add(self._scrollToPosition, position)
        else:
            self._scrollToPosition(position)

    def setTimeline(self, bTimeline):
        self.bTimeline = bTimeline
        self.timeline.selection.connect("selection-changed", self._selectionChangedCb)
        self.timeline.setTimeline(bTimeline)

    def getEditionMode(self, isAHandle=False):
        if self.shiftMask or (self.gui and self.gui._autoripple_active):
            return GES.EditMode.EDIT_RIPPLE
        if isAHandle and self.controlMask:
            return GES.EditMode.EDIT_ROLL
        elif isAHandle:
            return GES.EditMode.EDIT_TRIM
        return GES.EditMode.EDIT_NORMAL

    # Internal API

    def _createUi(self):
        self.embed = GtkClutter.Embed()
        self.embed.get_accessible().set_name("timeline canvas")  # for dogtail
        self.stage = self.embed.get_stage()

        self.timeline = TimelineStage(self)
        self.controls = ControlContainer(self.timeline)
        self.zoomBox = ZoomBox(self)
        self.shiftMask = False
        self.controlMask = False

        # TODO: make the bg a gradient from (0, 0, 0, 255) to (50, 50, 50, 255)
        self.stage.set_background_color(Clutter.Color.new(31, 30, 33, 255))
        self.timeline.set_position(CONTROL_WIDTH, 0)
        self.controls.set_position(0, 0)
        self.controls.set_z_position(2)

        self.stage.add_child(self.controls)
        self.stage.add_child(self.timeline)

        self.stage.connect("destroy", quit_)
        self.stage.connect("button-press-event", self._clickedCb)
        self.stage.connect("button-release-event", self._releasedCb)
        self.embed.connect("scroll-event", self._scrollEventCb)
        if self.gui:
            self.gui.connect("key-press-event", self._keyPressEventCb)
            self.gui.connect("key-release-event", self._keyReleaseEventCb)

        self.embed.connect("enter-notify-event", self._enterNotifyEventCb)

        self.point = Clutter.Point()
        self.point.x = 0
        self.point.y = 0

        self.scrolled = 0

        self.zoomed_fitted = True
        self.pressed = False

        self._packScrollbars(self)
        self.stage.show()

    def _setUpDragAndDrop(self):
        self.dropHighlight = False
        self.dropOccured = False
        self.dropDataReady = False
        self.dropData = None
        dnd_list = [Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags.OTHER_APP, TARGET_TYPE_URI_LIST)]

        self.drag_dest_set(0, dnd_list, Gdk.DragAction.COPY)
        self.drag_dest_add_uri_targets()

        self.connect('drag-motion', self._dragMotionCb)
        self.connect('drag-data-received', self._dragDataReceivedCb)
        self.connect('drag-drop', self._dragDropCb)
        self.connect('drag-leave', self._dragLeaveCb)

    def _ensureLayer(self):
        """
        Make sure we have a layer in our timeline

        Returns: The number of layer present in self.timeline
        """
        layers = self.bTimeline.get_layers()

        if not layers:
            layer = GES.Layer()
            layer.props.auto_transition = True
            self.bTimeline.add_layer(layer)
            layers = [layer]

        return layers

    def _createActions(self):
        if not self.gui:
            return
        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),

            ("ZoomFit", Gtk.STOCK_ZOOM_FIT, None,
            "<Control>0", ZOOM_FIT, self._zoomFitCb),

            ("Screenshot", None, _("Export current frame..."),
            None, _("Export the frame at the current playhead "
                    "position as an image file."), self._screenshotCb),

            # Alternate keyboard shortcuts to the actions above
            ("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),

            ("UngroupObj", "pitivi-ungroup", _("Ungroup"),
            "<Shift><Control>G", UNGROUP, self._ungroupSelected),

            # Translators: This is an action, the title of a button
            ("GroupObj", "pitivi-group", _("Group"),
            "<Control>G", GROUP, self._groupSelected),

            ("AlignObj", "pitivi-align", _("Align"),
            "<Shift><Control>A", ALIGN, self._alignSelected),
        )

        playhead_actions = (
            ("PlayPause", Gtk.STOCK_MEDIA_PLAY, None,
            "space", _("Start Playback"), self._playPause),

            ("Split", "pitivi-split", _("Split"),
            "S", SPLIT, self._split),

            ("Keyframe", "pitivi-keyframe", _("Add a Keyframe"),
            "K", KEYFRAME, self._keyframe),

            ("Prevkeyframe", None, _("_Previous Keyframe"),
            "comma", PREVKEYFRAME, self._previousKeyframeCb),

            ("Nextkeyframe", None, _("_Next Keyframe"),
            "period", NEXTKEYFRAME, self._nextKeyframeCb),
        )

        actiongroup = Gtk.ActionGroup("timelinepermanent")
        self.selection_actions = Gtk.ActionGroup("timelineselection")
        self.playhead_actions = Gtk.ActionGroup("timelineplayhead")

        actiongroup.add_actions(actions)

        self.ui_manager.insert_action_group(actiongroup, 0)
        self.selection_actions.add_actions(selection_actions)
        self.selection_actions.set_sensitive(False)
        self.ui_manager.insert_action_group(self.selection_actions, -1)
        self.playhead_actions.add_actions(playhead_actions)
        self.ui_manager.insert_action_group(self.playhead_actions, -1)

        self.ui_manager.add_ui_from_string(ui)

    def _packScrollbars(self, vbox):
        self.hadj = Gtk.Adjustment()
        self.vadj = Gtk.Adjustment()
        self._vscrollbar = Gtk.VScrollbar(self.vadj)
        self._hscrollBar = Gtk.HScrollbar(self.hadj)
        self.ruler = ScaleRuler(self, self.hadj)

        hbox = Gtk.HBox()

        self.hadj.connect("value-changed", self._updateScrollPosition)
        self.vadj.connect("value-changed", self._updateScrollPosition)

        vbox.pack_end(self._hscrollBar, False, True, False)

        self.ruler.setProjectFrameRate(24.)
        self.ruler.set_size_request(0, 25)
        self.ruler.hide()

        self.vadj.props.lower = 0
        self.vadj.props.page_size = 250

        hbox.pack_start(self.embed, True, True, True)
        hbox.pack_start(self._vscrollbar, False, True, False)

        vbox.pack_end(hbox, True, True, True)

        hbox = Gtk.HBox()

        self.zoomBox.set_size_request(CONTROL_WIDTH, -1)

        hbox.pack_start(self.zoomBox, False, True, False)
        hbox.pack_start(self.ruler, True, True, True)

        vbox.pack_end(hbox, False, True, False)

    def _updateScrollPosition(self, adjustment):
        self._scroll_pos_ns = Zoomable.pixelToNs(self.hadj.get_value())
        point = Clutter.Point()
        point.x = self.hadj.get_value()
        point.y = self.vadj.get_value()
        self.point = point

        self.timeline.scroll_to_point(point)
        point.x = 0
        self.controls.scroll_to_point(point)

    def _setBestZoomRatio(self):
        """
        Set the zoom level so that the entire timeline is in view.
        """
        ruler_width = self.ruler.get_allocation().width
        # Add Gst.SECOND - 1 to the timeline duration to make sure the
        # last second of the timeline will be in view.
        duration = self.timeline.bTimeline.get_duration()
        if duration == 0:
            return

        timeline_duration = duration + Gst.SECOND - 1
        timeline_duration_s = int(timeline_duration / Gst.SECOND)

        ideal_zoom_ratio = float(ruler_width) / timeline_duration_s
        nearest_zoom_level = Zoomable.computeZoomLevel(ideal_zoom_ratio)
        Zoomable.setZoomLevel(nearest_zoom_level)
        self.timeline.bTimeline.props.snapping_distance = \
            Zoomable.pixelToNs(self.app.settings.edgeSnapDeadband)

        # Only do this at the very end, after updating the other widgets.
        self.zoomed_fitted = True

    def _scrollLeft(self):
        self._hscrollBar.set_value(self._hscrollBar.get_value() -
            self.hadj.props.page_size ** (2.0 / 3.0))

    def _scrollRight(self):
        self._hscrollBar.set_value(self._hscrollBar.get_value() +
            self.hadj.props.page_size ** (2.0 / 3.0))

    def _scrollUp(self):
        self._vscrollbar.set_value(self._vscrollbar.get_value() -
            self.vadj.props.page_size ** (2.0 / 3.0))

    def _scrollDown(self):
        self._vscrollbar.set_value(self._vscrollbar.get_value() +
            self.vadj.props.page_size ** (2.0 / 3.0))

    def _scrollToPosition(self, position):
        if self.pipeline and self.pipeline.get_state() != Gst.State.PLAYING:
            self.timeline.save_easing_state()
            self.timeline.set_easing_duration(600)
        self._hscrollBar.set_value(position)
        if self.pipeline and self.pipeline.get_state() != Gst.State.PLAYING:
            self.timeline.restore_easing_state()
        return False

    def _scrollToPlayhead(self):
        #self.ruler._maybeUpdate()
        if self.ruler.pressed or self.pressed:
            self.pressed = False
            return
        canvas_size = self.embed.get_allocation().width - CONTROL_WIDTH
        try:
            new_pos = Zoomable.nsToPixel(self.app.current.pipeline.getPosition())
        except PipelineError:
            return
        except AttributeError:  # Standalone, no pipeline.
            return
        scroll_pos = self.hadj.get_value()
        self.scrollToPosition(min(new_pos - canvas_size / 2,
                                  self.hadj.props.upper - canvas_size - 1))

    def _deleteSelected(self, unused_action):
        if self.timeline:
            self.app.action_log.begin("delete clip")

            #FIXME GES port: Handle unlocked TrackElement-s
            for clip in self.timeline.selection:
                layer = clip.get_layer()
                layer.remove_clip(clip)

            self.app.action_log.commit()

    def _ungroupSelected(self, unused_action):
        if self.timeline:
            self.timeline.enable_update(False)
            self.app.action_log.begin("ungroup")

            for clip in self.timeline.selection:
                clip.ungroup(False)

            self.timeline.enable_update(True)
            self.app.action_log.commit()

    def _groupSelected(self, unused_action):
        if self.timeline:
            self.timeline.enable_update(False)
            self.app.action_log.begin("group")

            GES.Container.group(self.timeline.selection)

            self.app.action_log.commit()
            self.timeline.enable_update(True)

    def _alignSelected(self, unused_action):
        if "NumPy" in missing_soft_deps:
            DepsManager(self.app)

        elif self.timeline:
            progress_dialog = AlignmentProgressDialog(self.app)

            progress_dialog.window.show()
            self.app.action_log.begin("align")
            self.timeline.enable_update(False)

            def alignedCb():  # Called when alignment is complete
                self.timeline.enable_update(True)
                self.app.action_log.commit()
                progress_dialog.window.destroy()

            pmeter = self.timeline.alignSelection(alignedCb)

            pmeter.addWatcher(progress_dialog.updatePosition)

    def _split(self, action):
        """
        Split clips at the current playhead position, regardless of selections.
        """
        self.bTimeline.enable_update(False)
        position = self.app.current.pipeline.getPosition()

        for track in self.bTimeline.get_tracks():
            for element in track.get_elements():
                start = element.get_start()
                end = start + element.get_duration()
                if start < position and end > position:
                    clip = element.get_parent()
                    clip.split(position)

        self.bTimeline.enable_update(True)

    def _keyframe(self, action):
        """
        Add or remove a keyframe at the current position of the selected clip.

        FIXME GES: this method is currently not used anywhere
        """
        selected = self.timeline.selection.getSelectedTrackElements()

        for obj in selected:
            keyframe_exists = False
            position = self.app.current.pipeline.getPosition()
            position_in_obj = (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 is False:
                    self.app.action_log.begin("add volume point")
                    interpolator.newKeyframe(position_in_obj)
                    self.app.action_log.commit()

    def _playPause(self, unused_action):
        self.app.current.pipeline.togglePlayback()

    def _transposeXY(self, x, y):
        height = self.ruler.get_allocation().height
        x += self.timeline.get_scroll_point().x
        return x - CONTROL_WIDTH, y - height

    # Interface

    # Zoomable

    def zoomChanged(self):
        if self._settings and self.bTimeline:
            # zoomChanged might be called various times before the UI is ready
            self.bTimeline.props.snapping_distance = \
                Zoomable.pixelToNs(self._settings.edgeSnapDeadband)

        self.updateHScrollAdjustments()

    # Callbacks

    def _enterNotifyEventCb(self, widget, event):
        if self.gui:
            self.gui.setActionsSensitive(True)

    def _keyPressEventCb(self, widget, event):
        if event.keyval == Gdk.KEY_Shift_L:
            self.shiftMask = True
        elif event.keyval == Gdk.KEY_Control_L:
            self.controlMask = True

    def _keyReleaseEventCb(self, widget, event):
        if event.keyval == Gdk.KEY_Shift_L:
            self.shiftMask = False
        elif event.keyval == Gdk.KEY_Control_L:
            self.controlMask = False

    def _clickedCb(self, stage, event):
        self.pressed = True
        position = self.pixelToNs(event.x - CONTROL_WIDTH + self.timeline._scroll_point.x)
        if self.app:
            self._seeker.seek(position)
        actor = self.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, event.x, event.y)
        if actor == stage:
            self.timeline.emptySelection()

    def _releasedCb(self, stage, event):
        self.timeline._snapEndedCb()

    def _renderingSettingsChangedCb(self, project, item, value):
        """
        Called when any Project metadata changes, we filter out the one
        we are interested in.

        if @item is None, it mean we called it ourself, and want to force
        getting the project videorate value
        """
        if item == "videorate" or item is None:
            if value is None:
                value = project.videorate
            self._framerate = value

            self.ruler.setProjectFrameRate(self._framerate)

    def _snapDistanceChangedCb(self, settings):
        if self.bTimeline:
            self.bTimeline.props.snapping_distance = \
                Zoomable.pixelToNs(settings.edgeSnapDeadband)

    def _projectChangedCb(self, app, project, unused_fully_loaded):
        """
        When a project is loaded, we connect to its pipeline
        """

        if project:
            self._seeker = self._project.seeker
            self.timeline.setPipeline(self._project.pipeline)

            self.ruler.setProjectFrameRate(self._project.videorate)
            self.ruler.zoomChanged()

            self._renderingSettingsChangedCb(self._project, None, None)
            self._setBestZoomRatio()

    def _projectCreatedCb(self, app, project):
        """
        When a project is created, we connect to it timeline
        """
        if self._project:
            self._project.disconnect_by_func(self._renderingSettingsChangedCb)
            try:
                self.timeline.pipeline.disconnect_by_func(self.timeline.positionCb)
            except AttributeError:
                pass
            except TypeError:
                pass  # We were not connected no problem

            self.timeline.pipeline = None
            self._seeker = None

        self._project = project
        if self._project:
            self._project.connect("rendering-settings-changed",
                                  self._renderingSettingsChangedCb)
            self.setTimeline(project.timeline)

    def _zoomInCb(self, unused_action):
        Zoomable.zoomIn()
        self.log("Setting 'zoomed_fitted' to False")
        self.zoomed_fitted = False

    def _zoomOutCb(self, unused_action):
        Zoomable.zoomOut()
        self.log("Setting 'zoomed_fitted' to False")
        self.zoomed_fitted = False

    def _zoomFitCb(self, unused, unsued2=None):
        self._setBestZoomRatio()

    def _screenshotCb(self, unused_action):
        """
        Export a snapshot of the current frame as an image file.
        """
        foo = self._showSaveScreenshotDialog()
        if foo:
            path, mime = foo[0], foo[1]
            self._project.pipeline.save_thumbnail(-1, -1, mime, path)

    def _previousKeyframeCb(self, action):
        position = self.app.current.pipeline.getPosition()
        prev_kf = self.timeline.getPrevKeyframe(position)
        if prev_kf:
            self._seeker.seek(prev_kf)
            self.scrollToPlayhead()

    def _nextKeyframeCb(self, action):
        position = self.app.current.pipeline.getPosition()
        next_kf = self.timeline.getNextKeyframe(position)
        if next_kf:
            self._seeker.seek(next_kf)
            self.scrollToPlayhead()

    def _scrollEventCb(self, embed, event):
        # FIXME : see https://bugzilla.gnome.org/show_bug.cgi?id=697522
        deltas = event.get_scroll_deltas()
        if event.state & Gdk.ModifierType.CONTROL_MASK:
            if deltas[2] < 0:
                Zoomable.zoomIn()
            elif deltas[2] > 0:
                Zoomable.zoomOut()
            self.zoomed_fitted = False
            self._scrollToPlayhead()
        elif event.state & Gdk.ModifierType.SHIFT_MASK:
            if deltas[2] > 0:
                self._scrollDown()
            elif deltas[2] < 0:
                self._scrollUp()
        else:
            if deltas[2] > 0:
                self._scrollRight()
            elif deltas[2] < 0:
                self._scrollLeft()
        self.scrolled += 1

    def _selectionChangedCb(self, selection):
        """
        The selected clips on the timeline canvas have changed with the
        "selection-changed" signal.

        This is where you apply global UI changes, unlike individual
        track elements' "selected-changed" signal from the Selected class.
        """
        if selection:
            self.selection_actions.set_sensitive(True)
        else:
            self.selection_actions.set_sensitive(False)

    # drag and drop

    def _dragDataReceivedCb(self, widget, context, x, y, data, info, time):
        if not self.dropDataReady:
            if data.get_length() > 0:
                if not self.dropOccured:
                    self.timeline.resetGhostClips()
                self.dropData = data.get_uris()
                self.dropDataReady = True

        if self.dropOccured:
            self.dropOccured = False
            Gtk.drag_finish(context, True, False, time)
            self._dragLeaveCb(widget, context, time)
        else:
            self.isDraggedClip = True

    def _dragDropCb(self, widget, context, x, y, time):
        target = widget.drag_dest_find_target(context, None)
        y -= self.ruler.get_allocation().height
        if target.name() == "text/uri-list":
            self.dropOccured = True
            widget.drag_get_data(context, target, time)
            if self.isDraggedClip:
                self.timeline.convertGhostClips()
                self.timeline.resetGhostClips()
                if self.zoomed_fitted:
                    self._setBestZoomRatio()
                else:
                    x, y = self._transposeXY(x, y)
                    self.scrollToPosition(Zoomable.pixelToNs(x))
            else:
                actor = self.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y)
                try:
                    bElement = actor.bElement
                    self.app.gui.clipconfig.effect_expander.addEffectToClip(bElement.get_parent(), self.dropData[0])
                except AttributeError:
                    return False
            return True
        else:
            return False

    def _dragMotionCb(self, widget, context, x, y, time):
        target = widget.drag_dest_find_target(context, None)
        if target.name() not in ["text/uri-list", "pitivi/effect"]:
            return False
        if not self.dropDataReady:
            widget.drag_get_data(context, target, time)
            Gdk.drag_status(context, 0, time)
        else:
            x, y = self._transposeXY(x, y)

            # dragged from the media library
            if not self.timeline.ghostClips and self.isDraggedClip:
                for uri in self.dropData:
                    asset = self.app.gui.medialibrary.getAssetForUri(uri)
                    if asset is None:
                        self.isDraggedClip = False
                        break
                    self.timeline.addGhostClip(asset, x, y)

            if self.isDraggedClip:
                self.timeline.updateGhostClips(x, y)

            Gdk.drag_status(context, Gdk.DragAction.COPY, time)
            if not self.dropHighlight:
                widget.drag_highlight()
                self.dropHighlight = True
        return True

    def _dragLeaveCb(self, widget, context, time):
        if self.dropDataReady:
            self.dropDataReady = False
        if self.dropHighlight:
            widget.drag_unhighlight()
            self.dropHighlight = False

        self.timeline.removeGhostClips()

    # Standalone

    # Standalone public API

    def run(self):
        self.testTimeline(self.timeline)
        GLib.io_add_watch(sys.stdin, GLib.IO_IN, quit2_)
        Gtk.main()

    def addClipToLayer(self, layer, asset, start, duration, inpoint):
        layer.add_asset(asset, start * Gst.SECOND, 0, duration * Gst.SECOND, asset.get_supported_formats())

    def togglePlayback(self, button):
        self.pipeline.togglePlayback()

    def testTimeline(self, timeline):
        timeline.set_easing_duration(600)

        Gst.init([])
        GES.init()

        self.project = GES.Project(uri=None, extractable_type=GES.Timeline)

        bTimeline = GES.Timeline()
        bTimeline.add_track(GES.Track.audio_raw_new())
        bTimeline.add_track(GES.Track.video_raw_new())

        self.bTimeline = bTimeline
        timeline.setTimeline(bTimeline)

        layer = GES.Layer()
        bTimeline.add_layer(layer)

        self.bTimeline = bTimeline

        self.project.connect("asset-added", self._doAssetAddedCb, layer)
        self.project.create_asset("file://" + sys.argv[2], GES.UriClip)

    # Standalone internal API

    def _handle_message(self, bus, message):
        if message.type == Gst.MessageType.ELEMENT:
            if message.has_name('prepare-window-handle'):
                Gdk.threads_enter()
                self.sink = message.src
                self.sink.set_window_handle(self.viewer.window_xid)
                self.sink.expose()
                Gdk.threads_leave()
            elif message.type == Gst.MessageType.STATE_CHANGED:
                prev, new, pending = message.parse_state_changed()

        return True

    # Standalone callbacks

    def _doAssetAddedCb(self, project, asset, layer):
        self.addClipToLayer(layer, asset, 2, 10, 5)
        self.addClipToLayer(layer, asset, 15, 10, 5)

        Zoomable.setZoomLevel(50)