[pitivi] Code is now clean and consistent, and logically split in different files
- From: Jean-François Fortin Tam <jfft src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] Code is now clean and consistent, and logically split in different files
- Date: Wed, 24 Apr 2013 18:02:06 +0000 (UTC)
commit ddabf48882b6a6dfc1067fb885ef830c35b1f6c6
Author: Mathieu Duponchelle <mathieu duponchelle epitech eu>
Date: Wed Apr 17 03:54:39 2013 +0200
Code is now clean and consistent, and logically split in different files
pitivi/timeline/controls.py | 149 +++++++
pitivi/timeline/elements.py | 114 +++---
pitivi/timeline/previewers.py | 7 +
pitivi/timeline/timeline.py | 853 ++++++++++++++++-------------------------
pitivi/utils/ui.py | 4 +
pitivi/utils/widgets.py | 65 ++++
6 files changed, 623 insertions(+), 569 deletions(-)
---
diff --git a/pitivi/timeline/controls.py b/pitivi/timeline/controls.py
new file mode 100644
index 0000000..33468a6
--- /dev/null
+++ b/pitivi/timeline/controls.py
@@ -0,0 +1,149 @@
+from gi.repository import GtkClutter
+GtkClutter.init([])
+
+from gi.repository import Clutter, GObject
+
+from pitivi.utils.ui import EXPANDED_SIZE, SPACING, CONTROL_WIDTH
+
+from layer import VideoLayerControl, AudioLayerControl
+
+
+class ControlActor(GtkClutter.Actor):
+ def __init__(self, container, widget, layer):
+ GtkClutter.Actor.__init__(self)
+
+ self.layer = layer
+ self._container = container
+ self.widget = widget
+
+ self.get_widget().add(widget)
+ self.set_reactive(True)
+ self._setUpDragAndDrop()
+
+ def _getLayerForY(self, y):
+ if self.isAudio:
+ y -= self.nbrLayers * (EXPANDED_SIZE + SPACING)
+ priority = int(y / (EXPANDED_SIZE + SPACING))
+
+ return priority
+
+ def _setUpDragAndDrop(self):
+ self.dragAction = Clutter.DragAction()
+ self.add_action(self.dragAction)
+
+ self.dragAction.connect("drag-begin", self._dragBeginCb)
+ self.dragAction.connect("drag-progress", self._dragProgressCb)
+ self.dragAction.connect("drag-end", self._dragEndCb)
+
+ def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
+ self.brother = self._container.getBrotherControl(self)
+
+ self.brother.raise_top()
+ self.raise_top()
+
+ self.nbrLayers = len(self._container.timeline.bTimeline.get_layers())
+ self._dragBeginStartX = event_x
+
+ def _dragProgressCb(self, action, actor, delta_x, delta_y):
+ y = self.dragAction.get_motion_coords()[1]
+ priority = self._getLayerForY(y)
+ lowerLimit = 0
+ if self.isAudio:
+ lowerLimit = self.nbrLayers * (EXPANDED_SIZE + SPACING)
+
+ if actor.props.y + delta_y > lowerLimit and priority < self.nbrLayers:
+ actor.move_by(0, delta_y)
+ self.brother.move_by(0, delta_y)
+
+ if self.layer.get_priority() != priority and priority >= 0 and priority < self.nbrLayers:
+ self._container.moveLayer(self, priority)
+ return False
+
+ def _dragEndCb(self, action, actor, event_x, event_y, modifiers):
+ priority = self._getLayerForY(event_y)
+
+ if self.layer.get_priority() != priority and priority >= 0 and priority < self.nbrLayers:
+ self._container.moveLayer(self, priority)
+ self._container._reorderLayerActors()
+
+
+class ControlContainer(Clutter.ScrollActor):
+ __gsignals__ = {
+ "selection-changed": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,),)
+ }
+
+ def __init__(self, timeline):
+ Clutter.ScrollActor.__init__(self)
+
+ self.timeline = timeline
+ self.controlActors = []
+ self.trackControls = []
+
+ def _setTrackControlPosition(self, control):
+ y = control.layer.get_priority() * (EXPANDED_SIZE + SPACING) + SPACING
+ if control.isAudio:
+ y += len(self.timeline.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING)
+
+ control.set_position(0, y)
+
+ def _reorderLayerActors(self):
+ for control in self.controlActors:
+ control.save_easing_state()
+ control.set_easing_mode(Clutter.AnimationMode.EASE_OUT_BACK)
+ self._setTrackControlPosition(control)
+ control.restore_easing_state()
+
+ def getBrotherControl(self, control):
+ for cont in self.controlActors:
+ if cont != control and cont.layer == control.layer:
+ return cont
+
+ def moveLayer(self, control, target):
+ movedLayer = control.layer
+ priority = movedLayer.get_priority()
+
+ self.timeline.bTimeline.enable_update(False)
+
+ movedLayer.props.priority = 999 # Don't put 1000 layers or this breaks !
+
+ if priority > target:
+ for layer in self.timeline.bTimeline.get_layers():
+ prio = layer.get_priority()
+ if target <= prio < priority: # Python idiom, is that bad ?
+ layer.props.priority = prio + 1
+ elif priority < target:
+ for layer in self.timeline.bTimeline.get_layers():
+ prio = layer.get_priority()
+ if priority < prio <= target:
+ layer.props.priority = prio - 1
+
+ movedLayer.props.priority = target
+
+ self._reorderLayerActors()
+ self.timeline.bTimeline.enable_update(True)
+
+ def addTrackControl(self, layer, isAudio):
+ if isAudio:
+ control = AudioLayerControl(self, layer)
+ else:
+ control = VideoLayerControl(self, layer)
+
+ controlActor = ControlActor(self, control, layer)
+ controlActor.isAudio = isAudio
+ controlActor.layer = layer
+ controlActor.set_size(CONTROL_WIDTH, EXPANDED_SIZE + SPACING)
+
+ self.add_child(controlActor)
+ self.trackControls.append(control)
+ self.controlActors.append(controlActor)
+
+ def selectLayerControl(self, layer_control):
+ for control in self.trackControls:
+ control.selected = False
+ layer_control.selected = True
+ self.props.height += (EXPANDED_SIZE + SPACING) * 2 + SPACING
+
+ def addLayerControl(self, layer):
+ self.addTrackControl(layer, False)
+ self.addTrackControl(layer, True)
+ self._reorderLayerActors()
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 63b3fbe..bba56fa 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -6,7 +6,7 @@ is prefixed with a little b, example : bTimeline
import os
-from gi.repository import Clutter, Cogl, GES
+from gi.repository import Clutter, Cogl, GES, Gdk
from pitivi.utils.timeline import Zoomable, EditingContext, Selection, SELECT, UNSELECT, Selected
from previewers import VideoPreviewer, BORDER_WIDTH
@@ -18,6 +18,7 @@ def get_preview_for_object(bElement, timeline):
# Fixme special preview for transitions, titles
if not isinstance(bElement.get_parent(), GES.UriClip):
return Clutter.Actor()
+
track_type = bElement.get_track_type()
if track_type == GES.TrackType.AUDIO:
# FIXME: RandomAccessAudioPreviewer doesn't work yet
@@ -46,8 +47,10 @@ class RoundedRectangle(Clutter.Actor):
Creates a new rounded rectangle
"""
Clutter.Actor.__init__(self)
+
self.props.width = width
self.props.height = height
+
self._arc = arc
self._step = step
self._border_width = border_width
@@ -114,18 +117,17 @@ class RoundedRectangle(Clutter.Actor):
class TrimHandle(Clutter.Texture):
def __init__(self, timelineElement, isLeft):
Clutter.Texture.__init__(self)
- self.set_from_file(os.path.join(configure.get_pixmap_dir(), "trimbar-normal.png"))
- self.isLeft = isLeft
- self.set_size(-1, EXPANDED_SIZE)
- self.hide()
+ self.isLeft = isLeft
self.isSelected = False
-
self.timelineElement = timelineElement
+ self.dragAction = Clutter.DragAction()
+ self.set_from_file(os.path.join(configure.get_pixmap_dir(), "trimbar-normal.png"))
+ self.set_size(-1, EXPANDED_SIZE)
+ self.hide()
self.set_reactive(True)
- self.dragAction = Clutter.DragAction()
self.add_action(self.dragAction)
self.dragAction.connect("drag-begin", self._dragBeginCb)
@@ -134,6 +136,7 @@ class TrimHandle(Clutter.Texture):
self.connect("enter-event", self._enterEventCb)
self.connect("leave-event", self._leaveEventCb)
+
self.timelineElement.connect("enter-event", self._elementEnterEventCb)
self.timelineElement.connect("leave-event", self._elementLeaveEventCb)
self.timelineElement.bElement.selected.connect("selected-changed", self._selectedChangedCb)
@@ -145,6 +148,7 @@ class TrimHandle(Clutter.Texture):
for elem in self.timelineElement.get_children():
elem.set_reactive(False)
self.set_reactive(True)
+
self.set_from_file(os.path.join(configure.get_pixmap_dir(), "trimbar-focused.png"))
if self.isLeft:
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_SIDE))
@@ -170,9 +174,12 @@ class TrimHandle(Clutter.Texture):
self.props.visible = isSelected
def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
- self.timelineElement.setDragged(True)
+ self.dragBeginStartX = event_x
+ self.dragBeginStartY = event_y
elem = self.timelineElement.bElement.get_parent()
+ self.timelineElement.setDragged(True)
+
if self.isLeft:
edge = GES.Edge.EDGE_START
self._dragBeginStart = self.timelineElement.bElement.get_parent().get_start()
@@ -188,14 +195,10 @@ class TrimHandle(Clutter.Texture):
set([]),
None)
- self.dragBeginStartX = event_x
- self.dragBeginStartY = event_y
-
def _dragProgressCb(self, action, actor, delta_x, delta_y):
# We can't use delta_x here because it fluctuates weirdly.
coords = self.dragAction.get_motion_coords()
delta_x = coords[0] - self.dragBeginStartX
-
new_start = self._dragBeginStart + Zoomable.pixelToNs(delta_x)
self._context.editTo(new_start,
self.timelineElement.bElement.get_parent().get_layer().get_priority())
@@ -204,9 +207,11 @@ class TrimHandle(Clutter.Texture):
def _dragEndCb(self, action, actor, event_x, event_y, modifiers):
self.timelineElement.setDragged(False)
self._context.finish()
+
self.timelineElement.set_reactive(True)
for elem in self.timelineElement.get_children():
elem.set_reactive(True)
+
self.set_from_file(os.path.join(configure.get_pixmap_dir(), "trimbar-normal.png"))
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
@@ -222,31 +227,22 @@ class TimelineElement(Clutter.Actor, Zoomable):
Clutter.Actor.__init__(self)
self.timeline = timeline
-
self.bElement = bElement
-
self.bElement.selected = Selected()
- self.bElement.selected.connect("selected-changed", self._selectedChangedCb)
+ self.track_type = self.bElement.get_track_type() # This won't change
+ self.isDragged = False
+ size = self.bElement.get_duration()
self._createBackground(track)
-
self._createPreview()
-
self._createBorder()
-
self._createMarquee()
-
self._createHandles()
-
self._createGhostclip()
- self.track_type = self.bElement.get_track_type() # This won't change
-
- self.isDragged = False
-
- size = self.bElement.get_duration()
- self.set_size(self.nsToPixel(size), EXPANDED_SIZE, False)
+ self.update(True)
self.set_reactive(True)
+
self._connectToEvents()
# Public API
@@ -308,11 +304,12 @@ class TimelineElement(Clutter.Actor, Zoomable):
def _createBorder(self):
self.border = RoundedRectangle(0, 0, 5, 5)
color = Cogl.Color()
+
color.init_from_4ub(100, 100, 100, 255)
self.border.set_border_color(color)
self.border.set_border_width(3)
-
self.border.set_position(0, 0)
+
self.add_child(self.border)
def _createBackground(self, track):
@@ -323,33 +320,36 @@ class TimelineElement(Clutter.Actor, Zoomable):
def _createPreview(self):
self.preview = get_preview_for_object(self.bElement, self.timeline)
+
self.add_child(self.preview)
def _createMarquee(self):
# TODO: difference between Actor.new() and Actor()?
self.marquee = Clutter.Actor()
+
self.marquee.set_background_color(Clutter.Color.new(60, 60, 60, 100))
- self.add_child(self.marquee)
self.marquee.props.visible = False
+ self.add_child(self.marquee)
+
def _connectToEvents(self):
- # Click
+ self.dragAction = Clutter.DragAction()
+
+ self.add_action(self.dragAction)
+
+ self.dragAction.connect("drag-progress", self._dragProgressCb)
+ self.dragAction.connect("drag-begin", self._dragBeginCb)
+ self.dragAction.connect("drag-end", self._dragEndCb)
+ self.bElement.selected.connect("selected-changed", self._selectedChangedCb)
# We gotta go low-level cause Clutter.ClickAction["clicked"]
# gets emitted after Clutter.DragAction["drag-begin"]
self.connect("button-press-event", self._clickedCb)
- # Drag and drop.
- action = Clutter.DragAction()
- self.add_action(action)
- action.connect("drag-progress", self._dragProgressCb)
- action.connect("drag-begin", self._dragBeginCb)
- action.connect("drag-end", self._dragEndCb)
- self.dragAction = action
-
def _getLayerForY(self, y):
if self.bElement.get_track_type() == GES.TrackType.AUDIO:
y -= self.nbrLayers * (EXPANDED_SIZE + SPACING)
priority = int(y / (EXPANDED_SIZE + SPACING))
+
return priority
# Interface (Zoomable)
@@ -420,29 +420,35 @@ class ClipElement(TimelineElement):
def _createGhostclip(self):
self.ghostclip = Clutter.Actor.new()
+
self.ghostclip.set_background_color(Clutter.Color.new(100, 100, 100, 50))
self.ghostclip.props.visible = False
+
self.timeline.add_child(self.ghostclip)
def _createHandles(self):
self.leftHandle = TrimHandle(self, True)
self.rightHandle = TrimHandle(self, False)
+
+ self.leftHandle.set_position(0, 0)
+
self.add_child(self.leftHandle)
self.add_child(self.rightHandle)
- self.leftHandle.set_position(0, 0)
def _createBackground(self, track):
self.background = RoundedRectangle(0, 0, 5, 5)
+
if track.type == GES.TrackType.AUDIO:
color = Cogl.Color()
color.init_from_4ub(70, 79, 118, 255)
else:
color = Cogl.Color()
color.init_from_4ub(225, 232, 238, 255)
+
self.background.set_color(color)
self.background.set_border_width(3)
-
self.background.set_position(0, 0)
+
self.add_child(self.background)
# Callbacks
@@ -451,29 +457,32 @@ class ClipElement(TimelineElement):
self.timeline.selection.setToObj(self.bElement, SELECT)
def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
- self._context = EditingContext(self.bElement, self.timeline.bTimeline, GES.EditMode.EDIT_NORMAL,
GES.Edge.EDGE_NONE, self.timeline.selection.getSelectedTrackElements(), None)
-
+ self._context = EditingContext(self.bElement,
+ self.timeline.bTimeline,
+ GES.EditMode.EDIT_NORMAL,
+ GES.Edge.EDGE_NONE,
+ self.timeline.selection.getSelectedTrackElements(),
+ None)
# This can't change during a drag, so we can safely compute it now for drag events.
self.nbrLayers = len(self.timeline.bTimeline.get_layers())
- # We can also safely find if the object has a brother element
- self.setDragged(True)
self.brother = self.timeline.findBrother(self.bElement)
- if self.brother:
- self.brother.nbrLayers = self.nbrLayers
-
self._dragBeginStart = self.bElement.get_start()
self.dragBeginStartX = event_x
self.dragBeginStartY = event_y
+ # We can also safely find if the object has a brother element
+ self.setDragged(True)
+ if self.brother:
+ self.brother.nbrLayers = self.nbrLayers
+
def _dragProgressCb(self, action, actor, delta_x, delta_y):
# We can't use delta_x here because it fluctuates weirdly.
coords = self.dragAction.get_motion_coords()
delta_x = coords[0] - self.dragBeginStartX
delta_y = coords[1] - self.dragBeginStartY
-
y = coords[1] + self.timeline._container.point.y
-
priority = self._getLayerForY(y)
+ new_start = self._dragBeginStart + self.pixelToNs(delta_x)
self.ghostclip.props.x = self.nsToPixel(self._dragBeginStart) + delta_x
self.updateGhostclip(priority, y, False)
@@ -481,8 +490,6 @@ class ClipElement(TimelineElement):
self.brother.ghostclip.props.x = self.nsToPixel(self._dragBeginStart) + delta_x
self.brother.updateGhostclip(priority, y, True)
- new_start = self._dragBeginStart + self.pixelToNs(delta_x)
-
if not self.ghostclip.props.visible:
self._context.editTo(new_start, self.bElement.get_parent().get_layer().get_priority())
else:
@@ -493,19 +500,17 @@ class ClipElement(TimelineElement):
coords = self.dragAction.get_motion_coords()
delta_x = coords[0] - self.dragBeginStartX
new_start = self._dragBeginStart + self.pixelToNs(delta_x)
-
priority = self._getLayerForY(coords[1] + self.timeline._container.point.y)
priority = min(priority, len(self.timeline.bTimeline.get_layers()))
+ priority = max(0, priority)
self.timeline._snapEndedCb()
-
self.setDragged(False)
self.ghostclip.props.visible = False
if self.brother:
self.brother.ghostclip.props.visible = False
- priority = max(0, priority)
self._context.editTo(new_start, priority)
self._context.finish()
@@ -521,9 +526,10 @@ class TransitionElement(TimelineElement):
def _createBackground(self, track):
self.background = RoundedRectangle(0, 0, 5, 5)
color = Cogl.Color()
+
color.init_from_4ub(100, 100, 100, 125)
self.background.set_color(color)
self.background.set_border_width(3)
-
self.background.set_position(0, 0)
+
self.add_child(self.background)
diff --git a/pitivi/timeline/previewers.py b/pitivi/timeline/previewers.py
index 5c21e48..275bcbb 100644
--- a/pitivi/timeline/previewers.py
+++ b/pitivi/timeline/previewers.py
@@ -11,6 +11,13 @@ from pitivi.utils.ui import EXPANDED_SIZE, SPACING
BORDER_WIDTH = 3 # For the timeline elements
+"""
+Convention throughout this file:
+Every GES element which name could be mistaken with a UI element
+is prefixed with a little b, example : bTimeline
+"""
+
+
class VideoPreviewer(Clutter.ScrollActor, Zoomable):
def __init__(self, bElement, timeline):
"""
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 411425e..bcb8148 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -6,12 +6,14 @@ from gi.repository import Gst, GES, GObject, Clutter, Gtk, GLib, Gdk
from pitivi.utils.timeline import Zoomable, Selection, UNSELECT
from pitivi.settings import GlobalSettings
from pitivi.dialogs.prefs import PreferencesDialog
-from pitivi.utils.ui import EXPANDED_SIZE, SPACING
+from pitivi.utils.ui import EXPANDED_SIZE, SPACING, PLAYHEAD_WIDTH, CONTROL_WIDTH
+from pitivi.utils.widgets import ZoomBox
+
from ruler import ScaleRuler
from gettext import gettext as _
from pitivi.utils.pipeline import Pipeline
-from layer import VideoLayerControl, AudioLayerControl
from elements import ClipElement, TransitionElement
+from controls import ControlContainer
GlobalSettings.addConfigOption('edgeSnapDeadband',
section="user-interface",
@@ -38,10 +40,6 @@ PreferencesDialog.addNumericPreference('imageClipLength',
description=_("Default clip length (in miliseconds) of images when inserting on the timeline."),
lower=1)
-# CONSTANTS
-
-CONTROL_WIDTH = 250
-
# tooltip text for toolbar
DELETE = _("Delete Selected")
SPLIT = _("Split clip at playhead position")
@@ -120,15 +118,18 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
def __init__(self, container):
Clutter.ScrollActor.__init__(self)
Zoomable.__init__(self)
+
self.bTimeline = None
- self.set_background_color(Clutter.Color.new(31, 30, 33, 255))
+ self._container = container
self.elements = []
self.selection = Selection()
+ self._scroll_point = Clutter.Point()
+ self.lastPosition = 0 # Saved for redrawing when paused
+
+ self.set_background_color(Clutter.Color.new(31, 30, 33, 255))
+
self._createPlayhead()
self._createSnapIndicator()
- self._container = container
- self.lastPosition = 0
- self._scroll_point = Clutter.Point()
# Public API
@@ -165,11 +166,8 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
self.zoomChanged()
- #Stage was clicked with nothing under the pointer
+ # Stage was clicked with nothing under the pointer
def emptySelection(self):
- """
- Empty the current selection.
- """
self.selection.setSelection(self.selection.getSelectedTrackElements(), UNSELECT)
def findBrother(self, element):
@@ -179,7 +177,7 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
return elem
return None
- #Internal API
+ # Internal API
def _connectTrack(self, track):
track.connect("track-element-added", self._trackElementAddedCb)
@@ -191,28 +189,33 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
def _positionCb(self, pipeline, position):
self.playhead.props.x = self.nsToPixel(position)
+ # FIXME this currently sucks
self._container._scrollToPlayhead()
self.lastPosition = position
def _updatePlayHead(self):
height = len(self.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING) * 2
- self.playhead.set_size(2, height)
+ self.playhead.set_size(PLAYHEAD_WIDTH, height)
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.add_child(self.playhead)
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(0, 0, 250, 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):
@@ -230,19 +233,20 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
self.elements.append(element)
+ self._setElementX(element)
self._setElementY(element)
self.add_child(element)
- self._setElementX(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)
@@ -254,18 +258,17 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
if ease:
element.restore_easing_state()
- # Crack, change that when we have retractable layers
+ # FIXME, change that when we have retractable layers
def _setElementY(self, element):
- element.save_easing_state()
- y = 0
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()
@@ -274,31 +277,39 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
self.set_easing_duration(0)
self.props.width = self.nsToPixel(self.bTimeline.get_duration()) + 250
self.restore_easing_state()
+
self._container.updateHScrollAdjustments()
def _redraw(self):
self._updateSize()
+
self.save_easing_state()
for element in self.elements:
self._setElementX(element)
self.restore_easing_state()
- self.playhead.props.x = self.nsToPixel(self.lastPosition)
-
- # Interface overrides (Zoomable)
- def zoomChanged(self):
- self._redraw()
+ self.playhead.props.x = self.nsToPixel(self.lastPosition)
def _add_layer(self, layer):
for element in self.elements:
self._setElementY(element)
+
self.save_easing_state()
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.controls.addLayerControl(layer)
self._updatePlayHead()
+ # 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
@@ -321,9 +332,10 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
if position == 0:
self._snapEndedCb()
else:
- self._snap_indicator.props.x = Zoomable.nsToPixel(position)
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):
@@ -333,30 +345,15 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
self._add_layer(layer)
def _layerRemovedCb(self, timeline, layer):
- layer.disconnect_by_func(self._clipAddedCb)
- layer.disconnect_by_func(self._clipRemovedCb)
+ # FIXME : really remove layer ^^
self._updatePlayHead()
- def _clipAddedCb(self, layer, clip):
- clip.connect("child-added", self._elementAddedCb)
- clip.connect("child-removed", self._elementRemovedCb)
-
- def _clipRemovedCb(self, layer, clip):
- clip.disconnect_by_func(self._elementAddedCb)
- clip.disconnect_by_func(self._elementRemovedCb)
-
def _trackAddedCb(self, timeline, track):
self._connectTrack(track)
def _trackRemovedCb(self, timeline, track):
self._disconnectTrack(track)
- def _elementAddedCb(self, clip, bElement):
- pass
-
- def _elementRemovedCb(self):
- pass
-
def _trackElementAddedCb(self, track, bElement):
self._updateSize()
self._addTimelineElement(track, bElement)
@@ -369,6 +366,7 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
def _elementStartChangedCb(self, bElement, start, element):
self._updateSize()
+
if element.isDragged:
self._setElementX(element, ease=False)
else:
@@ -385,6 +383,7 @@ class TimelineStage(Clutter.ScrollActor, Zoomable):
self._redraw()
+# This is for running standalone
def quit_(stage):
Gtk.main_quit()
@@ -393,255 +392,35 @@ def quit2_(*args, **kwargs):
Gtk.main_quit()
-class ZoomBox(Gtk.HBox, Zoomable):
- def __init__(self, timeline):
- """
- This will hold the widgets responsible for zooming.
- """
- Gtk.HBox.__init__(self)
- Zoomable.__init__(self)
-
- self.timeline = timeline
-
- zoom_fit_btn = Gtk.Button()
- zoom_fit_btn.set_relief(Gtk.ReliefStyle.NONE)
- zoom_fit_btn.set_tooltip_text(ZOOM_FIT)
- zoom_fit_icon = Gtk.Image()
- zoom_fit_icon.set_from_stock(Gtk.STOCK_ZOOM_FIT, Gtk.IconSize.BUTTON)
- zoom_fit_btn_hbox = Gtk.HBox()
- zoom_fit_btn_hbox.pack_start(zoom_fit_icon, False, True, 0)
- zoom_fit_btn_hbox.pack_start(Gtk.Label(_("Zoom")), False, True, 0)
- zoom_fit_btn.add(zoom_fit_btn_hbox)
- zoom_fit_btn.connect("clicked", self._zoomFitCb)
- self.pack_start(zoom_fit_btn, False, True, 0)
-
- # 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.Scale.new(Gtk.Orientation.HORIZONTAL, adjustment=self._zoomAdjustment)
- zoomslider.props.draw_value = False
- zoomslider.set_tooltip_text(_("Zoom Timeline"))
- zoomslider.connect("scroll-event", self._zoomSliderScrollCb)
- zoomslider.set_size_request(100, 0) # At least 100px wide for precision
- self.pack_start(zoomslider, True, True, 0)
-
- self.show_all()
-
- self._updateZoomSlider = True
-
- def _zoomAdjustmentChangedCb(self, adjustment):
- # GTK crack
- self._updateZoomSlider = False
- Zoomable.setZoomLevel(int(adjustment.get_value()))
- self.zoomed_fitted = False
- self._updateZoomSlider = True
-
- def _zoomFitCb(self, button):
- self.timeline.zoomFit()
-
- def _zoomSliderScrollCb(self, unused, event):
- value = self._zoomAdjustment.get_value()
- if event.direction in [Gdk.ScrollDirection.UP, Gdk.ScrollDirection.RIGHT]:
- self._zoomAdjustment.set_value(value + 1)
- elif event.direction in [Gdk.ScrollDirection.DOWN, Gdk.ScrollDirection.LEFT]:
- self._zoomAdjustment.set_value(value - 1)
-
- def zoomChanged(self):
- if self._updateZoomSlider:
- self._zoomAdjustment.set_value(self.getCurrentZoomLevel())
-
-
-class ControlActor(GtkClutter.Actor):
- def __init__(self, container, widget, layer):
- GtkClutter.Actor.__init__(self)
- self.get_widget().add(widget)
- self.set_reactive(True)
- self.layer = layer
- self._setUpDragAndDrop()
- self._container = container
- self.widget = widget
-
- def _getLayerForY(self, y):
- if self.isAudio:
- y -= self.nbrLayers * (EXPANDED_SIZE + SPACING)
- priority = int(y / (EXPANDED_SIZE + SPACING))
- return priority
-
- def _setUpDragAndDrop(self):
- self.dragAction = Clutter.DragAction()
- self.add_action(self.dragAction)
- self.dragAction.connect("drag-begin", self._dragBeginCb)
- self.dragAction.connect("drag-progress", self._dragProgressCb)
- self.dragAction.connect("drag-end", self._dragEndCb)
-
- def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
- self.brother = self._container.getBrotherControl(self)
- self.brother.raise_top()
- self.raise_top()
- self.nbrLayers = len(self._container.timeline.bTimeline.get_layers())
- self._dragBeginStartX = event_x
-
- def _dragProgressCb(self, action, actor, delta_x, delta_y):
- y = self.dragAction.get_motion_coords()[1]
- priority = self._getLayerForY(y)
- lowerLimit = 0
- if self.isAudio:
- lowerLimit = self.nbrLayers * (EXPANDED_SIZE + SPACING)
-
- if actor.props.y + delta_y > lowerLimit and priority < self.nbrLayers:
- actor.move_by(0, delta_y)
- self.brother.move_by(0, delta_y)
-
- if self.layer.get_priority() != priority and priority >= 0 and priority < self.nbrLayers:
- self._container.moveLayer(self, priority)
- return False
-
- def _dragEndCb(self, action, actor, event_x, event_y, modifiers):
- priority = self._getLayerForY(event_y)
- if self.layer.get_priority() != priority and priority >= 0 and priority < self.nbrLayers:
- self._container.moveLayer(self, priority)
- self._container._reorderLayerActors()
-
-
-class ControlContainer(Clutter.ScrollActor):
- __gsignals__ = {
- "selection-changed": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,),)
- }
-
- def __init__(self, timeline):
- Clutter.ScrollActor.__init__(self)
- self.controlActors = []
- self.trackControls = []
- self.timeline = timeline
-
- def _setTrackControlPosition(self, control):
- y = control.layer.get_priority() * (EXPANDED_SIZE + SPACING) + SPACING
- if control.isAudio:
- y += len(self.timeline.bTimeline.get_layers()) * (EXPANDED_SIZE + SPACING)
- control.set_position(0, y)
-
- def _reorderLayerActors(self):
- for control in self.controlActors:
- control.save_easing_state()
- control.set_easing_mode(Clutter.AnimationMode.EASE_OUT_BACK)
- self._setTrackControlPosition(control)
- control.restore_easing_state()
-
- def getBrotherControl(self, control):
- for cont in self.controlActors:
- if cont != control and cont.layer == control.layer:
- return cont
-
- def moveLayer(self, control, target):
- movedLayer = control.layer
- priority = movedLayer.get_priority()
- self.timeline.bTimeline.enable_update(False)
- movedLayer.props.priority = 999
-
- if priority > target:
- for layer in self.timeline.bTimeline.get_layers():
- prio = layer.get_priority()
- if target <= prio < priority:
- layer.props.priority = prio + 1
- elif priority < target:
- for layer in self.timeline.bTimeline.get_layers():
- prio = layer.get_priority()
- if priority < prio <= target:
- layer.props.priority = prio - 1
- movedLayer.props.priority = target
-
- self._reorderLayerActors()
- self.timeline.bTimeline.enable_update(True)
-
- def addTrackControl(self, layer, isAudio):
- if isAudio:
- control = AudioLayerControl(self, layer)
- else:
- control = VideoLayerControl(self, layer)
-
- controlActor = ControlActor(self, control, layer)
- controlActor.isAudio = isAudio
- controlActor.layer = layer
- controlActor.set_size(CONTROL_WIDTH, EXPANDED_SIZE + SPACING)
-
- self.add_child(controlActor)
- self.trackControls.append(control)
- self.controlActors.append(controlActor)
-
- def selectLayerControl(self, layer_control):
- for control in self.trackControls:
- control.selected = False
- layer_control.selected = True
- self.props.height += (EXPANDED_SIZE + SPACING) * 2 + SPACING
-
- def addLayerControl(self, layer):
- self.addTrackControl(layer, False)
- self.addTrackControl(layer, True)
- self._reorderLayerActors()
-
-
class Timeline(Gtk.VBox, Zoomable):
def __init__(self, instance, ui_manager):
- gtksettings = Gtk.Settings.get_default()
- gtksettings.set_property("gtk-application-prefer-dark-theme", True)
Zoomable.__init__(self)
Gtk.VBox.__init__(self)
+
GObject.threads_init()
self.ui_manager = ui_manager
self.app = instance
self._settings = self.app.settings
- self.embed = GtkClutter.Embed()
- self.embed.show()
-
- self.point = Clutter.Point()
- self.point.x = 0
- self.point.y = 0
-
- self.zoomBox = ZoomBox(self)
-
- self._packScrollbars(self)
-
- stage = self.embed.get_stage()
- stage.set_background_color(Clutter.Color.new(31, 30, 33, 255))
-
- self.stage = stage
-
- self.embed.connect("scroll-event", self._scrollEventCb)
-
- self.stage.set_throttle_motion_events(True)
-
- stage.show()
-
- widget = TimelineStage(self)
-
- self.controls = ControlContainer(widget)
- stage.add_child(self.controls)
- self.controls.set_position(0, 0)
- self.controls.set_z_position(2)
-
- stage.add_child(widget)
- widget.set_position(CONTROL_WIDTH, 0)
- stage.connect("destroy", quit_)
- stage.connect("button-press-event", self._clickedCb)
- self.timeline = widget
-
- self.scrolled = 0
-
- self._createActions()
-
self._projectmanager = None
self._project = None
+ self._createUi()
+ self._createActions()
+
self._settings.connect("edgeSnapDeadbandChanged",
self._snapDistanceChangedCb)
+ # Standalone
+ if __name__ == "__main__":
+ gtksettings = Gtk.Settings.get_default()
+ gtksettings.set_property("gtk-application-prefer-dark-theme", True)
+
self.show_all()
+ # Public API
+
def insertEnd(self, assets):
"""
Add source at the end of the timeline
@@ -649,11 +428,14 @@ class Timeline(Gtk.VBox, Zoomable):
@param x2: A list of sources to add to the timeline
"""
self.app.action_log.begin("add clip")
+
# FIXME we should find the longets layer instead of adding it to the
# first one
# Handle the case of a blank project
layer = self._ensureLayer()[0]
+
self.bTimeline.enable_update(False)
+
for asset in assets:
if isinstance(asset, GES.TitleClip):
clip_duration = asset.get_duration()
@@ -662,13 +444,13 @@ class Timeline(Gtk.VBox, Zoomable):
else:
clip_duration = asset.get_duration()
- print "added asset"
if not isinstance(asset, GES.TitleClip):
layer.add_asset(asset, self.bTimeline.props.duration,
0, clip_duration, 1.0, asset.get_supported_formats())
else:
asset.set_start(self.bTimeline.props.duration)
layer.add_clip(asset)
+
self.bTimeline.enable_update(True)
def setProjectManager(self, projectmanager):
@@ -676,10 +458,81 @@ class Timeline(Gtk.VBox, Zoomable):
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)
+
+ # Internal API
+
+ def _createUi(self):
+ self.embed = GtkClutter.Embed()
+ self.stage = self.embed.get_stage()
+ self.timeline = TimelineStage(self)
+ self.controls = ControlContainer(self.timeline)
+ self.zoomBox = ZoomBox(self)
+
+ 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.embed.connect("scroll-event", self._scrollEventCb)
+
+ self.point = Clutter.Point()
+ self.point.x = 0
+ self.point.y = 0
+
+ self.scrolled = 0
+
+ self._packScrollbars(self)
+ self.stage.show()
+
def _ensureLayer(self):
"""
Make sure we have a layer in our timeline
@@ -688,7 +541,7 @@ class Timeline(Gtk.VBox, Zoomable):
"""
layers = self.bTimeline.get_layers()
- if (len(layers) == 0):
+ if not layers:
layer = GES.Layer()
layer.props.auto_transition = True
self.bTimeline.add_layer(layer)
@@ -724,28 +577,28 @@ class Timeline(Gtk.VBox, Zoomable):
selection_actions = (
("DeleteObj", Gtk.STOCK_DELETE, None,
- "Delete", DELETE, self.deleteSelected),
+ "Delete", DELETE, self._deleteSelected),
("UngroupObj", "pitivi-ungroup", _("Ungroup"),
- "<Shift><Control>G", UNGROUP, self.ungroupSelected),
+ "<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),
+ "<Control>G", GROUP, self._groupSelected),
("AlignObj", "pitivi-align", _("Align"),
- "<Shift><Control>A", ALIGN, self.alignSelected),
+ "<Shift><Control>A", ALIGN, self._alignSelected),
)
playhead_actions = (
("PlayPause", Gtk.STOCK_MEDIA_PLAY, None,
- "space", _("Start Playback"), self.playPause),
+ "space", _("Start Playback"), self._playPause),
("Split", "pitivi-split", _("Split"),
- "S", SPLIT, self.split),
+ "S", SPLIT, self._split),
("Keyframe", "pitivi-keyframe", _("Add a Keyframe"),
- "K", KEYFRAME, self.keyframe),
+ "K", KEYFRAME, self._keyframe),
("Prevkeyframe", None, _("_Previous Keyframe"),
"comma", PREVKEYFRAME, self._previousKeyframeCb),
@@ -755,14 +608,15 @@ class Timeline(Gtk.VBox, Zoomable):
)
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 = Gtk.ActionGroup("timelineselection")
+ 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 = Gtk.ActionGroup("timelineplayhead")
self.playhead_actions.add_actions(playhead_actions)
self.ui_manager.insert_action_group(self.playhead_actions, -1)
@@ -771,17 +625,18 @@ class Timeline(Gtk.VBox, Zoomable):
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)
- self._vscrollbar = Gtk.VScrollbar(self.vadj)
-
- self._hscrollBar = Gtk.HScrollbar(self.hadj)
vbox.pack_end(self._hscrollBar, False, True, False)
- self.ruler = ScaleRuler(self, self.hadj)
self.ruler.setProjectFrameRate(24.)
-
self.ruler.set_size_request(0, 25)
self.ruler.hide()
@@ -789,7 +644,6 @@ class Timeline(Gtk.VBox, Zoomable):
self.vadj.props.upper = 500
self.vadj.props.page_size = 250
- hbox = Gtk.HBox()
hbox.set_size_request(-1, 500)
hbox.pack_start(self.embed, True, True, True)
hbox.pack_start(self._vscrollbar, False, True, False)
@@ -797,7 +651,9 @@ class Timeline(Gtk.VBox, Zoomable):
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)
@@ -809,48 +665,11 @@ class Timeline(Gtk.VBox, Zoomable):
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 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()
-
- def updateHScrollAdjustments(self):
- """
- Recalculate the horizontal scrollbar depending on the timeline duration.
- """
- timeline_ui_width = self.embed.get_allocation().width
-# controls_width = self.controls.get_allocation().width
-# scrollbar_width = self._vscrollbar.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 = 500 # 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.log("Setting 'zoomed_fitted' to True")
- self.zoomed_fitted = True
-
- def run(self):
- self.testTimeline(self.timeline)
- GLib.io_add_watch(sys.stdin, GLib.IO_IN, quit2_)
- Gtk.main()
-
def _setBestZoomRatio(self):
"""
Set the zoom level so that the entire timeline is in view.
@@ -860,38 +679,20 @@ class Timeline(Gtk.VBox, Zoomable):
# last second of the timeline will be in view.
duration = self.timeline.bTimeline.get_duration()
if duration == 0:
-# self.debug("The timeline duration is 0, impossible to calculate zoom")
return
timeline_duration = duration + Gst.SECOND - 1
timeline_duration_s = int(timeline_duration / Gst.SECOND)
- #self.debug("duration: %s, timeline duration: %s" % (print_ns(duration),
- # print_ns(timeline_duration)))
-
ideal_zoom_ratio = float(ruler_width) / timeline_duration_s
nearest_zoom_level = Zoomable.computeZoomLevel(ideal_zoom_ratio)
- #self.debug("Ideal zoom: %s, nearest_zoom_level %s", ideal_zoom_ratio, nearest_zoom_level)
Zoomable.setZoomLevel(nearest_zoom_level)
- #self.timeline.props.snapping_distance = \
- # Zoomable.pixelToNs(self.app.settings.edgeSnapDeadband)
+ 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.log("Setting 'zoomed_fitted' to True")
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 _scrollLeft(self):
self._hscrollBar.set_value(self._hscrollBar.get_value() -
self.hadj.props.page_size ** (2.0 / 3.0))
@@ -916,177 +717,49 @@ class Timeline(Gtk.VBox, Zoomable):
canvas_size = self.embed.get_allocation().width - CONTROL_WIDTH
new_pos = self.timeline.playhead.props.x
scroll_pos = self.hadj.get_value()
+
self.scrollToPosition(min(new_pos - canvas_size / 2,
self.hadj.props.upper - canvas_size - 1))
- def goToPoint(self, timeline):
- point = Clutter.Point()
- point.x = 1000
- point.y = 0
- timeline.scroll_to_point(point)
- return False
-
- def addClipToLayer(self, layer, asset, start, duration, inpoint):
- layer.add_asset(asset, start * Gst.SECOND, 0, duration * Gst.SECOND, 1.0,
asset.get_supported_formats())
-
- 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
-
- def _clickedCb(self, stage, event):
- actor = self.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, event.x, event.y)
- if actor == stage:
- self.timeline.emptySelection()
-
- def doSeek(self):
- #self.pipeline.simple_seek(3000000000)
- return False
-
- def togglePlayback(self, button):
- self.pipeline.togglePlayback()
-
- 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 _doAssetAddedCb(self, project, asset, layer):
- self.addClipToLayer(layer, asset, 2, 10, 5)
- self.addClipToLayer(layer, asset, 15, 10, 5)
-
- self.pipeline = Pipeline()
- self.pipeline.add_timeline(layer.get_timeline())
-
- self.bus = self.pipeline.get_bus()
- self.bus.add_signal_watch()
- self.bus.connect("message", self.handle_message)
- self.playButton.connect("clicked", self.togglePlayback)
- #self.pipeline.togglePlayback()
- self.pipeline.activatePositionListener(interval=30)
- self.timeline.setPipeline(self.pipeline)
- GObject.timeout_add(1000, self.doSeek)
- Zoomable.setZoomLevel(50)
-
- 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
- """
-# self.debug("Project changed")
-
- if project:
-# self.debug("Project is not None, connecting to its pipeline")
- self._seeker = self._project.seeker
- self._pipeline = self._project.pipeline
-# self._pipeline.connect("position", self.positionChangedCb)
- 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
- """
-# self.debug("Setting project %s", project)
- if self._project:
- self._project.disconnect_by_func(self._renderingSettingsChangedCb)
- #try:
- # self._pipeline.disconnect_by_func(self.positionChangedCb)
- #except TypeError:
- # pass # We were not connected no problem
-
- self._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):
- # This only handles the button callbacks (from the menus),
- # not keyboard shortcuts or the zoom slider!
- Zoomable.zoomIn()
- self.log("Setting 'zoomed_fitted' to False")
- self.zoomed_fitted = False
-
- def _zoomOutCb(self, unused_action):
- # This only handles the button callbacks (from the menus),
- # not keyboard shortcuts or the zoom slider!
- 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 deleteSelected(self, unused_action):
+ 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):
+ def _ungroupSelected(self, unused_action):
if self.timeline:
- self.debug("Ungouping selected clips %s" % self.timeline.selection)
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):
+ def _groupSelected(self, unused_action):
if self.timeline:
- self.debug("Gouping selected clips %s" % self.timeline.selection)
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):
+ 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)
@@ -1097,14 +770,16 @@ class Timeline(Gtk.VBox, Zoomable):
progress_dialog.window.destroy()
pmeter = self.timeline.alignSelection(alignedCb)
+
pmeter.addWatcher(progress_dialog.updatePosition)
- def split(self, action):
+ 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()
@@ -1112,15 +787,17 @@ class Timeline(Gtk.VBox, Zoomable):
if start < position and end > position:
clip = element.get_parent()
clip.split(position)
+
self.bTimeline.enable_update(True)
- def keyframe(self, action):
+ 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()
@@ -1140,6 +817,111 @@ class Timeline(Gtk.VBox, Zoomable):
interpolator.newKeyframe(position_in_obj)
self.app.action_log.commit()
+ def _playPause(self, unused_action):
+ self.app.current.pipeline.togglePlayback()
+
+ # 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 _clickedCb(self, stage, event):
+ actor = self.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, event.x, event.y)
+ if actor == stage:
+ self.timeline.emptySelection()
+
+ 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):
+ # This only handles the button callbacks (from the menus),
+ # not keyboard shortcuts or the zoom slider!
+ Zoomable.zoomIn()
+ self.log("Setting 'zoomed_fitted' to False")
+ self.zoomed_fitted = False
+
+ def _zoomOutCb(self, unused_action):
+ # This only handles the button callbacks (from the menus),
+ # not keyboard shortcuts or the zoom slider!
+ 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)
@@ -1154,27 +936,6 @@ class Timeline(Gtk.VBox, Zoomable):
self._seeker.seek(next_kf)
self.scrollToPlayhead()
- def playPause(self, unused_action):
- self.app.current.pipeline.togglePlayback()
-
- def setTimeline(self, bTimeline):
- self.bTimeline = bTimeline
- self.timeline.selection.connect("selection-changed", self._selectionChangedCb)
- self.timeline.setTimeline(bTimeline)
-
- 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)
-
def _scrollEventCb(self, embed, event):
# FIXME : see https://bugzilla.gnome.org/show_bug.cgi?id=697522
deltas = event.get_scroll_deltas()
@@ -1196,6 +957,34 @@ class Timeline(Gtk.VBox, Zoomable):
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)
+
+ # 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, 1.0,
asset.get_supported_formats())
+
+ def togglePlayback(self, button):
+ self.pipeline.togglePlayback()
+
def testTimeline(self, timeline):
timeline.set_easing_duration(600)
@@ -1218,6 +1007,40 @@ class Timeline(Gtk.VBox, Zoomable):
self.project.connect("asset-added", self._doAssetAddedCb, layer)
self.project.create_asset("file://" + sys.argv[1], 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)
+
+ self.pipeline = Pipeline()
+ self.pipeline.add_timeline(layer.get_timeline())
+
+ self.bus = self.pipeline.get_bus()
+ self.bus.add_signal_watch()
+ self.bus.connect("message", self._handle_message)
+ self.playButton.connect("clicked", self.togglePlayback)
+ #self.pipeline.togglePlayback()
+ self.pipeline.activatePositionListener(interval=30)
+ self.timeline.setPipeline(self.pipeline)
+ GObject.timeout_add(1000, self.doSeek)
+ Zoomable.setZoomLevel(50)
+
if __name__ == "__main__":
# Basic argument handling, no need for getopt here
if len(sys.argv) < 2:
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index 8655051..a9f5f6e 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -58,8 +58,12 @@ PADDING = 6
SPACING = 10
+PLAYHEAD_WIDTH = 2
+
CANVAS_SPACING = 21
+CONTROL_WIDTH = 250
+
# Layer creation blocking time in s
LAYER_CREATION_BLOCK_TIME = 0.2
diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py
index 2fa6a24..9261fc0 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -42,6 +42,9 @@ from pitivi.utils.loggable import Loggable
from pitivi.configure import get_ui_dir
from pitivi.utils.ui import unpack_color, pack_color_32, pack_color_64, \
time_to_string, SPACING
+from pitivi.utils.timeline import Zoomable
+
+ZOOM_FIT = _("Zoom Fit")
class DynamicWidget(object):
@@ -1079,3 +1082,65 @@ class BaseTabs(Gtk.Notebook):
resize=True, shrink=False)
self.app.gui.mainhpaned.pack1(self.app.gui.secondhpaned,
resize=True, shrink=False)
+
+
+class ZoomBox(Gtk.HBox, Zoomable):
+ def __init__(self, timeline):
+ """
+ This will hold the widgets responsible for zooming.
+ """
+ Gtk.HBox.__init__(self)
+ Zoomable.__init__(self)
+
+ self.timeline = timeline
+
+ zoom_fit_btn = Gtk.Button()
+ zoom_fit_btn.set_relief(Gtk.ReliefStyle.NONE)
+ zoom_fit_btn.set_tooltip_text(ZOOM_FIT)
+ zoom_fit_icon = Gtk.Image()
+ zoom_fit_icon.set_from_stock(Gtk.STOCK_ZOOM_FIT, Gtk.IconSize.BUTTON)
+ zoom_fit_btn_hbox = Gtk.HBox()
+ zoom_fit_btn_hbox.pack_start(zoom_fit_icon, False, True, 0)
+ zoom_fit_btn_hbox.pack_start(Gtk.Label(_("Zoom")), False, True, 0)
+ zoom_fit_btn.add(zoom_fit_btn_hbox)
+ zoom_fit_btn.connect("clicked", self._zoomFitCb)
+
+ self.pack_start(zoom_fit_btn, False, True, 0)
+
+ # 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.Scale.new(Gtk.Orientation.HORIZONTAL, adjustment=self._zoomAdjustment)
+ zoomslider.props.draw_value = False
+ zoomslider.set_tooltip_text(_("Zoom Timeline"))
+ zoomslider.connect("scroll-event", self._zoomSliderScrollCb)
+ zoomslider.set_size_request(100, 0) # At least 100px wide for precision
+ self.pack_start(zoomslider, True, True, 0)
+
+ self.show_all()
+
+ self._updateZoomSlider = True
+
+ def _zoomAdjustmentChangedCb(self, adjustment):
+ # GTK crack
+ self._updateZoomSlider = False
+ Zoomable.setZoomLevel(int(adjustment.get_value()))
+ self.zoomed_fitted = False
+ self._updateZoomSlider = True
+
+ def _zoomFitCb(self, button):
+ self.timeline.zoomFit()
+
+ def _zoomSliderScrollCb(self, unused, event):
+ value = self._zoomAdjustment.get_value()
+ if event.direction in [Gdk.ScrollDirection.UP, Gdk.ScrollDirection.RIGHT]:
+ self._zoomAdjustment.set_value(value + 1)
+ elif event.direction in [Gdk.ScrollDirection.DOWN, Gdk.ScrollDirection.LEFT]:
+ self._zoomAdjustment.set_value(value - 1)
+
+ def zoomChanged(self):
+ if self._updateZoomSlider:
+ self._zoomAdjustment.set_value(self.getCurrentZoomLevel())
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]