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})
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()
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)
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)
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)
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))
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))
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()
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))