Ejemplo n.º 1
0
    def test_toplevels(self):
        timeline_container = common.create_timeline_container()
        timeline = timeline_container.timeline
        clip1, clip2, clip3, clip4 = self.addClipsSimple(timeline, 4)

        selection = Selection()

        selection.setSelection([clip1, clip2, clip3, clip4], SELECT)
        self.assertSetEqual(selection.toplevels, {clip1, clip2, clip3, clip4})

        group1 = GES.Container.group([clip1, clip2])
        group1.props.serialize = True
        self.assertSetEqual(selection.toplevels, {group1, clip3, clip4})

        group2 = GES.Container.group([group1, clip3])
        group2.props.serialize = True
        self.assertSetEqual(selection.toplevels, {group2, clip4})

        group1.props.serialize = True
        group1.props.serialize = False
        self.assertSetEqual(selection.toplevels, {group2, clip4})

        group1.props.serialize = False
        group2.props.serialize = False
        self.assertSetEqual(selection.toplevels, {clip1, clip2, clip3, clip4})

        group1.props.serialize = True
        group2.props.serialize = False
        self.assertSetEqual(selection.toplevels, {group1, clip3, clip4})
Ejemplo n.º 2
0
 def __init__(self, container):
     Clutter.ScrollActor.__init__(self)
     Zoomable.__init__(self)
     self.bTimeline = None
     self._container = container
     self.elements = []
     self.ghostClips = []
     self.selection = Selection()
     self._scroll_point = Clutter.Point()
     self.lastPosition = 0  # Saved for redrawing when paused
     self._createPlayhead()
     self._createSnapIndicator()
Ejemplo n.º 3
0
 def testBoolEvaluation(self):
     clip1 = mock.MagicMock()
     selection = Selection()
     self.assertFalse(selection)
     selection.setSelection([clip1], SELECT)
     self.assertTrue(selection)
     selection.setSelection([clip1], SELECT_ADD)
     self.assertTrue(selection)
     selection.setSelection([clip1], UNSELECT)
     self.assertFalse(selection)
Ejemplo n.º 4
0
    def test_can_group_ungroup(self):
        clip1 = common.create_test_clip(GES.UriClip)
        clip2 = common.create_test_clip(GES.UriClip)
        selection = Selection()
        self.assertFalse(selection)

        selection.setSelection([clip1], SELECT)
        self.assertFalse(selection.can_ungroup)
        self.assertFalse(selection.can_group)

        selection.setSelection([clip2], SELECT_ADD)
        self.assertTrue(selection.can_group)
        self.assertFalse(selection.can_ungroup)

        selection.setSelection([], SELECT)
        self.assertFalse(selection.can_group)
        self.assertFalse(selection.can_ungroup)
Ejemplo n.º 5
0
    def test_can_group_ungroup(self):
        timeline_container = common.create_timeline_container()
        timeline = timeline_container.timeline
        clip1, clip2 = self.addClipsSimple(timeline, 2)

        selection = Selection()
        self.assertFalse(selection.can_group)
        self.assertFalse(selection.can_ungroup)

        selection.setSelection([clip1], SELECT)
        self.assertFalse(selection.can_group)
        self.assertTrue(selection.can_ungroup)

        selection.setSelection([clip2], SELECT_ADD)
        self.assertTrue(selection.can_group)
        self.assertFalse(selection.can_ungroup)

        selection.setSelection([], SELECT)
        self.assertFalse(selection.can_group)
        self.assertFalse(selection.can_ungroup)
Ejemplo n.º 6
0
    def testGetSingleClip(self):
        selection = Selection()
        clip1 = common.create_test_clip(GES.UriClip)
        clip2 = common.create_test_clip(GES.TitleClip)

        # Selection empty.
        self.assertIsNone(selection.getSingleClip())
        self.assertIsNone(selection.getSingleClip(GES.UriClip))
        self.assertIsNone(selection.getSingleClip(GES.TitleClip))

        selection.setSelection([clip1], SELECT)
        self.assertEqual(selection.getSingleClip(), clip1)
        self.assertEqual(selection.getSingleClip(GES.UriClip), clip1)
        self.assertIsNone(selection.getSingleClip(GES.TitleClip))

        selection.setSelection([clip2], SELECT)
        self.assertEqual(selection.getSingleClip(), clip2)
        self.assertIsNone(selection.getSingleClip(GES.UriClip))
        self.assertEqual(selection.getSingleClip(GES.TitleClip), clip2)

        selection.setSelection([clip1, clip2], SELECT)
        self.assertIsNone(selection.getSingleClip())
        self.assertIsNone(selection.getSingleClip(GES.UriClip))
        self.assertIsNone(selection.getSingleClip(GES.TitleClip))
Ejemplo n.º 7
0
    def testGetSingleClip(self):
        selection = Selection()
        clip1 = common.createTestClip(GES.UriClip)
        clip2 = common.createTestClip(GES.TitleClip)

        # Selection empty.
        self.assertFalse(selection.getSingleClip(GES.TitleClip))

        # Selection contains only a non-requested-type clip.
        selection.setSelection([clip1], SELECT)
        self.assertFalse(selection.getSingleClip(GES.TitleClip))

        # Selection contains only requested-type clip.
        selection.setSelection([clip2], SELECT)
        self.assertEqual(clip2, selection.getSingleClip(GES.TitleClip))

        # Selection contains more than one clip.
        selection.setSelection([clip1, clip2], SELECT)
        self.assertFalse(selection.getSingleClip(GES.UriClip))
Ejemplo n.º 8
0
class TimelineStage(Clutter.ScrollActor, Zoomable):
    __gsignals__ = {
        'scrolled': (GObject.SIGNAL_RUN_FIRST, None, ())
    }

    def __init__(self, container):
        Clutter.ScrollActor.__init__(self)
        Zoomable.__init__(self)
        self.bTimeline = None
        self._container = container
        self.elements = []
        self.ghostClips = []
        self.selection = Selection()
        self._scroll_point = Clutter.Point()
        self.lastPosition = 0  # Saved for redrawing when paused
        self._createPlayhead()
        self._createSnapIndicator()

    # Public API

    def setPipeline(self, pipeline):
        pipeline.connect('position', self._positionCb)

    def setTimeline(self, bTimeline):
        """
        @param bTimeline : the backend GES.Timeline which we interface.
        Does all the necessary connections.
        """

        if self.bTimeline is not None:
            self.bTimeline.disconnect_by_func(self._trackAddedCb)
            self.bTimeline.disconnect_by_func(self._trackRemovedCb)
            self.bTimeline.disconnect_by_func(self._layerAddedCb)
            self.bTimeline.disconnect_by_func(self._layerRemovedCb)
            self.bTimeline.disconnect_by_func(self._snapCb)
            self.bTimeline.disconnect_by_func(self._snapEndedCb)

        self.bTimeline = bTimeline

        for track in bTimeline.get_tracks():
            self._connectTrack(track)
        for layer in bTimeline.get_layers():
            self._add_layer(layer)

        self.bTimeline.connect("track-added", self._trackAddedCb)
        self.bTimeline.connect("track-removed", self._trackRemovedCb)
        self.bTimeline.connect("layer-added", self._layerAddedCb)
        self.bTimeline.connect("layer-removed", self._layerRemovedCb)
        self.bTimeline.connect("snapping-started", self._snapCb)
        self.bTimeline.connect("snapping-ended", self._snapEndedCb)

        self.zoomChanged()

    # Stage was clicked with nothing under the pointer
    def emptySelection(self):
        self.selection.setSelection(self.selection.getSelectedTrackElements(), UNSELECT)

    """
    @param element: the ui_element for which we want to find the sibling.
    Will iterate over ui_elements to get the possible uri source with the same parent clip.
    """
    def findBrother(self, element):
        father = element.get_parent()
        for elem in self.elements:
            if elem.bElement.get_parent() == father and elem.bElement != element:
                return elem
        return None

    """
    @param ghostclip: the ghostclip that was dropped, needing a new layer.
    Will move subsequent layers down, if any.
    """
    def insertLayer(self, ghostclip):
        layer = None
        if ghostclip.priority < len(self.bTimeline.get_layers()):
            self.bTimeline.enable_update(False)
            for layer in self.bTimeline.get_layers():
                if layer.get_priority() >= ghostclip.priority:
                    layer.props.priority += 1

            layer = self.bTimeline.append_layer()
            layer.props.priority = ghostclip.priority
            self.bTimeline.enable_update(True)
            self._container.controls._reorderLayerActors()
        return layer

    # drag and drop from the medialibrary

    """
    Drag and drop is handled with ghostclips. We build a list of ghostclips when
    drag data is received, and reset it after conversion only, avoiding the drag-leave bug.
    """

    def resetGhostClips(self):
        for ghostCouple in self.ghostClips:
            for ghostclip in ghostCouple:
                del ghostclip
        self.ghostClips = []

    def addGhostClip(self, asset, x, y):
        ghostAudio = ghostVideo = None

        if asset.get_supported_formats() & GES.TrackType.VIDEO:
            ghostVideo = self._createGhostclip(GES.TrackType.VIDEO, asset)
        if asset.get_supported_formats() & GES.TrackType.AUDIO:
            ghostAudio = self._createGhostclip(GES.TrackType.AUDIO, asset)

        self.ghostClips.append([ghostVideo, ghostAudio])

    """
    This is called for each drag-motion.
    """
    def updateGhostClips(self, x, y):
        for ghostCouple in self.ghostClips:
            for ghostclip in ghostCouple:
                if ghostclip is not None:
                    priority = int(y / (EXPANDED_SIZE + SPACING))
                    ghostclip.update(priority, y, False)
                    if x >= 0:
                        ghostclip.props.x = x
                        self._updateSize(ghostclip)

    """
    This is called at drag-drop
    """
    def convertGhostClips(self):
        for ghostCouple in self.ghostClips:
            ghostclip = ghostCouple[0]
            if not ghostclip:
                ghostclip = ghostCouple[1]

            layer = None
            target = None

            if ghostclip.shouldCreateLayer:
                layer = self.insertLayer(ghostclip)
                target = layer
            else:
                for layer in self.bTimeline.get_layers():
                    if layer.get_priority() == ghostclip.priority:
                        target = layer
                        break

            if target is None:
                layer = self.bTimeline.append_layer()

            layer.add_asset(ghostclip.asset,
                            Zoomable.pixelToNs(ghostclip.props.x),
                            0,
                            ghostclip.asset.get_duration(),
                            ghostclip.asset.get_supported_formats())

    """
    This is called at drag-leave. We don't empty the list on purpose.
    """
    def removeGhostClips(self):
        for ghostCouple in self.ghostClips:
            for ghostclip in ghostCouple:
                if ghostclip is not None and ghostclip.get_parent():
                    self.remove_child(ghostclip)

    # Internal API

    def _createGhostclip(self, trackType, asset):
        ghostclip = Ghostclip(trackType)
        ghostclip.asset = asset
        ghostclip.setNbrLayers(len(self.bTimeline.get_layers()))
        ghostclip.setWidth(Zoomable.nsToPixel(asset.get_duration()))
        self.add_child(ghostclip)
        return ghostclip

    def _connectTrack(self, track):
        track.connect("track-element-added", self._trackElementAddedCb)
        track.connect("track-element-removed", self._trackElementRemovedCb)

    def _disconnectTrack(self, track):
        track.disconnect_by_func(self._trackElementAddedCb)
        track.disconnect_by_func(self._trackElementRemovedCb)

    def _positionCb(self, pipeline, position):
        self.playhead.props.x = self.nsToPixel(position)
        self._container._scrollToPlayhead()
        self.lastPosition = position

    def _updatePlayHead(self):
        if self._container.pipeline and self._container.pipeline.get_state() != Gst.State.PLAYING:
            self.playhead.save_easing_state()
            self.playhead.set_easing_duration(600)
        height = len(self.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING) * 2
        self.playhead.set_size(PLAYHEAD_WIDTH, height)
        self.playhead.props.x = self.nsToPixel(self.lastPosition)
        if self._container.pipeline and self._container.pipeline.get_state() != Gst.State.PLAYING:
            self.playhead.restore_easing_state()

    def _createPlayhead(self):
        self.playhead = Clutter.Actor()
        self.playhead.set_background_color(Clutter.Color.new(200, 0, 0, 255))
        self.playhead.set_size(0, 0)
        self.playhead.set_position(0, 0)
        self.playhead.set_easing_duration(0)
        self.playhead.set_z_position(1)
        self.add_child(self.playhead)

    def _createSnapIndicator(self):
        self._snap_indicator = Clutter.Actor()
        self._snap_indicator.set_background_color(Clutter.Color.new(50, 150, 200, 200))
        self._snap_indicator.props.visible = False
        self._snap_indicator.props.width = 3
        self._snap_indicator.props.y = 0
        self.add_child(self._snap_indicator)

    def _addTimelineElement(self, track, bElement):
        if isinstance(bElement, GES.Effect):
            return
        if isinstance(bElement.get_parent(), GES.TransitionClip):
            element = TransitionElement(bElement, track, self)
            element.set_z_position(0)
        else:
            element = URISourceElement(bElement, track, self)
            element.set_z_position(-1)

        bElement.connect("notify::start", self._elementStartChangedCb, element)
        bElement.connect("notify::duration", self._elementDurationChangedCb, element)
        bElement.connect("notify::in-point", self._elementInPointChangedCb, element)
        bElement.connect("notify::priority", self._elementPriorityChangedCb, element)

        self.elements.append(element)

        self._setElementX(element)
        self._setElementY(element)

        self.add_child(element)

    def _removeTimelineElement(self, track, bElement):
        bElement.disconnect_by_func(self._elementStartChangedCb)
        bElement.disconnect_by_func(self._elementDurationChangedCb)
        bElement.disconnect_by_func(self._elementInPointChangedCb)

        for element in self.elements:
            if element.bElement == bElement:
                break

        self.elements.remove(element)
        self.remove_child(element)

    def _setElementX(self, element, ease=True):
        if ease:
            element.save_easing_state()
            element.set_easing_duration(600)
        element.props.x = self.nsToPixel(element.bElement.get_start())
        if ease:
            element.restore_easing_state()

    # FIXME, change that when we have retractable layers
    def _setElementY(self, element):
        bElement = element.bElement
        track_type = bElement.get_track_type()

        y = 0
        if (track_type == GES.TrackType.AUDIO):
            y = len(self.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING)
        y += bElement.get_parent().get_layer().get_priority() * (EXPANDED_SIZE + SPACING) + SPACING

        element.save_easing_state()
        element.props.y = y
        element.restore_easing_state()

    def _updateSize(self, ghostclip=None):
        self.save_easing_state()
        self.set_easing_duration(0)
        self.props.width = self.nsToPixel(self.bTimeline.get_duration()) + 250
        if ghostclip is not None:
            ghostEnd = ghostclip.props.x + ghostclip.props.width + 250
            self.props.width = max(ghostEnd, self.props.width)
        self.props.height = (len(self.bTimeline.get_layers()) + 1) * (EXPANDED_SIZE + SPACING) * 2 + SPACING
        self.restore_easing_state()
        self._container.vadj.props.upper = self.props.height
        self._container.updateHScrollAdjustments()

    def _redraw(self):
        self._updateSize()

        self.save_easing_state()
        for element in self.elements:
            self._setElementX(element)
            self._setElementY(element)
        self.restore_easing_state()

        self._updatePlayHead()

    def _remove_layer(self, layer):
        self._container.controls.removeLayerControl(layer)
        self._redraw()

    def _add_layer(self, layer):
        self._redraw()
        self._container.controls.addLayerControl(layer)

    # Interface overrides

    # Zoomable Override

    def zoomChanged(self):
        self._redraw()

    # Clutter Override

    # TODO: remove self._scroll_point and get_scroll_point as soon as the Clutter API
    # offers a way to query a ScrollActor for its current scroll point
    def scroll_to_point(self, point):
        Clutter.ScrollActor.scroll_to_point(self, point)
        self._scroll_point = point.copy()
        self.emit("scrolled")

    def get_scroll_point(self):
        return self._scroll_point

    # Callbacks

    # snapping indicator
    def _snapCb(self, unused_timeline, obj1, obj2, position):
        """
        Display or hide a snapping indicator line
        """
        if position == 0:
            self._snapEndedCb()
        else:
            height = len(self.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING) * 2

            self._snap_indicator.props.height = height
            self._snap_indicator.props.x = Zoomable.nsToPixel(position)
            self._snap_indicator.props.visible = True

    def _snapEndedCb(self, *args):
        self._snap_indicator.props.visible = False

    def _layerAddedCb(self, timeline, layer):
        self._add_layer(layer)

    def _layerRemovedCb(self, timeline, layer):
        # FIXME : really remove layer ^^
        for lyr in self.bTimeline.get_layers():
            if lyr.props.priority > layer.props.priority:
                lyr.props.priority -= 1
        self._remove_layer(layer)
        self._updatePlayHead()

    def _trackAddedCb(self, timeline, track):
        self._connectTrack(track)

    def _trackRemovedCb(self, timeline, track):
        self._disconnectTrack(track)

    def _trackElementAddedCb(self, track, bElement):
        self._updateSize()
        self._addTimelineElement(track, bElement)

    def _trackElementRemovedCb(self, track, bElement):
        self._removeTimelineElement(track, bElement)

    def _elementPriorityChangedCb(self, bElement, priority, element):
        self._setElementY(element)

    def _elementStartChangedCb(self, bElement, start, element):
        self._updateSize()

        if element.isDragged:
            self._setElementX(element, ease=False)
        else:
            self._setElementX(element)

    def _elementDurationChangedCb(self, bElement, duration, element):
        self._updateSize()
        element.update(False)

    def _elementInPointChangedCb(self, bElement, inpoint, element):
        self._setElementX(element, ease=False)

    def _layerPriorityChangedCb(self, layer, priority):
        self._redraw()
Ejemplo n.º 9
0
    def testGetSingleClip(self):
        selection = Selection()
        clip1 = common.create_test_clip(GES.UriClip)
        clip2 = common.create_test_clip(GES.TitleClip)

        # Selection empty.
        self.assertFalse(selection.getSingleClip(GES.TitleClip))

        # Selection contains only a non-requested-type clip.
        selection.setSelection([clip1], SELECT)
        self.assertFalse(selection.getSingleClip(GES.TitleClip))

        # Selection contains only requested-type clip.
        selection.setSelection([clip2], SELECT)
        self.assertEqual(clip2, selection.getSingleClip(GES.TitleClip))

        # Selection contains more than one clip.
        selection.setSelection([clip1, clip2], SELECT)
        self.assertFalse(selection.getSingleClip(GES.UriClip))