[pitivi] This commit makes addition of keyframes possible.



commit dc9cefee08cd599a3692b7624dc1241cc2caca27
Author: Mathieu Duponchelle <mathieu duponchelle epitech eu>
Date:   Tue Apr 23 03:53:30 2013 +0200

    This commit makes addition of keyframes possible.
    
    Conflicts:
        pitivi/timeline/elements.py
        pitivi/timeline/timeline.py

 pitivi/clipproperties.py    |   18 ---
 pitivi/timeline/elements.py |  244 ++++++++++++++++++++++++++++++++++++++++---
 pitivi/timeline/timeline.py |   15 ++-
 pitivi/utils/ui.py          |    2 +
 pitivi/utils/widgets.py     |   32 ++++++-
 5 files changed, 270 insertions(+), 41 deletions(-)
---
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index d8f7b2a..1cf400c 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -167,12 +167,6 @@ class EffectProperties(Gtk.Expander, Loggable):
         removeEffectButton.set_is_important(True)
         self._toolbar.insert(removeEffectButton, 0)
 
-        showKeyframesButton = Gtk.ToolButton()
-        showKeyframesButton.set_icon_name("document-properties-symbolic")
-        showKeyframesButton.set_label(_("Show keyframes"))
-        showKeyframesButton.set_is_important(True)
-        self._toolbar.insert(showKeyframesButton, 0)
-
         # Treeview to display a list of effects (checkbox, effect type and name)
         self.treeview_scrollwin = Gtk.ScrolledWindow()
         self.treeview_scrollwin.set_policy(Gtk.PolicyType.NEVER,
@@ -244,7 +238,6 @@ class EffectProperties(Gtk.Expander, Loggable):
         self.treeview.connect("query-tooltip", self._treeViewQueryTooltipCb)
         self._vcontent.connect("notify", self._vcontentNotifyCb)
         removeEffectButton.connect("clicked", self._removeEffectClicked)
-        showKeyframesButton.connect("clicked", self._showKeyframesClicked)
         self.app.connect("new-project-loaded", self._newProjectLoadedCb)
         self.connect('notify::expanded', self._expandedCb)
         self.connected = False
@@ -317,17 +310,6 @@ class EffectProperties(Gtk.Expander, Loggable):
                                                COL_TRACK_EFFECT)
             self._removeEffect(effect)
 
-    def _showKeyframesClicked(self, event):
-        if not self.clips:
-            return
-        effect = self.storemodel.get_value(self.selection.get_selected()[1],
-                                           COL_TRACK_EFFECT)
-        track_type = effect.get_track_type()
-        for track_element in effect.get_parent().get_children():
-            print track_element
-            if hasattr(track_element, "ui_element") and track_type == track_element.get_track_type():
-                track_element.ui_element.showKeyframes()
-
     def _removeEffect(self, effect):
         self.app.action_log.begin("remove effect")
         self._cleanCache(effect)
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 7a3621c..66dd83f 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -26,15 +26,18 @@ Every GES element which name could be mistaken with a UI element
 is prefixed with a little b, example : bTimeline
 """
 
+import cairo
+import math
+
 import os
 import cairo
 
-from gi.repository import Clutter, Cogl, GES, Gdk, GstController
+from gi.repository import Clutter, Cogl, GES, Gdk, Gst, GstController
 from pitivi.utils.timeline import Zoomable, EditingContext, Selection, SELECT, UNSELECT, Selected
 from previewers import VideoPreviewer, BORDER_WIDTH
 
 import pitivi.configure as configure
-from pitivi.utils.ui import EXPANDED_SIZE, SPACING
+from pitivi.utils.ui import EXPANDED_SIZE, SPACING, KEYFRAME_SIZE, CONTROL_WIDTH
 
 
 def get_preview_for_object(bElement, timeline):
@@ -344,6 +347,9 @@ class TimelineElement(Clutter.Actor, Zoomable):
         self.bElement.ui_element = self
         self.track_type = self.bElement.get_track_type()  # This won't change
         self.isDragged = False
+        self.lines = []
+        self.keyframes = []
+        self.source = None
         size = self.bElement.get_duration()
 
         self._createBackground(track)
@@ -399,6 +405,66 @@ class TimelineElement(Clutter.Actor, Zoomable):
                 pass
             self.restore_easing_state()
 
+    def addKeyframe(self, value, timestamp):
+        self.source.set(timestamp, value)
+        self.updateKeyframes()
+
+    def showKeyframes(self, effect, propname):
+        binding = effect.get_control_binding(propname.name)
+        if not binding:
+            source = GstController.InterpolationControlSource()
+            source.props.mode = GstController.InterpolationMode.LINEAR
+            if not (effect.set_control_source(source, propname.name, "direct")):
+                print "There was something like a problem captain"
+                return
+            val = float(propname.default_value) / (propname.maximum - propname.minimum)
+            source.set(self.bElement.props.in_point, val)
+            source.set(self.bElement.props.duration, val)
+            binding = effect.get_control_binding(propname.name)
+        self.binding = binding
+        self.source = self.binding.props.control_source
+        self.prop = propname
+        self.updateKeyframes()
+
+    def setKeyframePosition(self, keyframe, value):
+        x = self.nsToPixel(value.timestamp) - KEYFRAME_SIZE / 2
+        y = EXPANDED_SIZE - (value.value * EXPANDED_SIZE)
+
+        keyframe.set_z_position(2)
+        keyframe.set_position(x, y)
+
+    def drawLines(self):
+        for line in self.lines:
+            self.remove_child(line)
+
+        self.lines = []
+
+        lastKeyframe = None
+        for keyframe in self.keyframes:
+            if lastKeyframe:
+                self._createLine(keyframe.value, lastKeyframe.value)
+            lastKeyframe = keyframe
+
+    def updateKeyframes(self):
+        if not self.source:
+            return
+
+        values = self.source.get_all()
+        lastPoint = None
+
+        for keyframe in self.keyframes:
+            self.remove_child(keyframe)
+
+        self.keyframes = []
+
+        for value in values:
+            self._createKeyframe(value)
+            lastPoint = value
+
+        self.drawLines()
+
+    # private API
+
     def update(self, ease):
         size = self.bElement.get_duration()
         self.set_size(self.nsToPixel(size), EXPANDED_SIZE, ease)
@@ -409,10 +475,34 @@ class TimelineElement(Clutter.Actor, Zoomable):
             brother.isDragged = dragged
         self.isDragged = dragged
 
-    def showKeyframes(self, propname):
-        pass
-
-    # Internal API
+    def _setKeyframePosition(self, keyframe, value):
+        x = self.nsToPixel(value.timestamp) - KEYFRAME_SIZE / 2
+        y = EXPANDED_SIZE - (value.value * EXPANDED_SIZE)
+
+        keyframe.set_z_position(2)
+        keyframe.set_position(x, y)
+
+    def _createKeyframe(self, value):
+        keyframe = Keyframe(self, value)
+
+        self.add_child(keyframe)
+        self.keyframes.append(keyframe)
+        self.setKeyframePosition(keyframe, value)
+
+    def _createLine(self, value, lastPoint):
+        line = Line(self)
+        adj = self.nsToPixel(value.timestamp - lastPoint.timestamp)
+        opp = (lastPoint.value - value.value) * EXPANDED_SIZE
+        hyp = math.sqrt(adj ** 2 + opp ** 2)
+        sinX = opp / hyp
+        line.props.width = hyp
+        line.props.height = 6
+        line.props.rotation_angle_z = math.degrees(math.asin(sinX))
+        line.props.x = self.nsToPixel(lastPoint.timestamp)
+        line.props.y = EXPANDED_SIZE - (EXPANDED_SIZE * lastPoint.value)
+        self.lines.append(line)
+        self.add_child(line)
+        line.canvas.invalidate()
 
     def _createGhostclip(self):
         pass
@@ -470,6 +560,7 @@ class TimelineElement(Clutter.Actor, Zoomable):
 
     def zoomChanged(self):
         self.update(True)
+        self.updateKeyframes()
 
     # Callbacks
 
@@ -515,6 +606,135 @@ class Gradient(Clutter.Actor):
         cr.fill()
 
 
+class Line(Clutter.Actor):
+    def __init__(self, timelineElement):
+        Clutter.Actor.__init__(self)
+
+        self.timelineElement = timelineElement
+
+        self.canvas = Clutter.Canvas()
+        self.canvas.set_size(1000, 4)
+        self.canvas.connect("draw", self._drawCb)
+        self.set_content(self.canvas)
+        self.set_reactive(True)
+
+        self.connect("button-press-event", self._clickedCb)
+        self.connect("motion-event", self._motionEventCb)
+        self.connect("enter-event", self._enterEventCb)
+        self.connect("leave-event", self._leaveEventCb)
+
+    def _drawCb(self, canvas, cr, width, height):
+        cr.set_operator(cairo.OPERATOR_CLEAR)
+        cr.paint()
+        cr.set_operator(cairo.OPERATOR_OVER)
+        cr.set_source_rgb(255, 0, 0)
+        cr.set_line_width(2.)
+        cr.move_to(0, 2)
+        cr.line_to(width, 2)
+        cr.stroke()
+
+    def transposeXY(self, x, y):
+        x -= self.timelineElement.props.x + CONTROL_WIDTH - self.timelineElement.timeline._scroll_point.x
+        y -= self.timelineElement.props.y
+        return x, y
+
+    def _clickedCb(self, actor, event):
+        x, y = self.transposeXY(event.x, event.y)
+        value = 1.0 - (y / EXPANDED_SIZE)
+        value = max(0.0, value)
+        value = min(1.0, value)
+        timestamp = Zoomable.pixelToNs(x)
+        self.timelineElement.addKeyframe(value, timestamp)
+
+    def _enterEventCb(self, actor, event):
+        self.timelineElement.set_reactive(False)
+        
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1))
+
+    def _leaveEventCb(self, actor, event):
+        self.timelineElement.set_reactive(True)
+        
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
+
+    def _motionEventCb(self, actor, event):
+        pass
+
+
+class Keyframe(Clutter.Actor):
+    def __init__(self, timelineElement, value):
+        Clutter.Actor.__init__(self)
+
+        self.value = value
+        self.timelineElement = timelineElement
+
+        self.set_size(KEYFRAME_SIZE, KEYFRAME_SIZE)
+        self.set_background_color(Clutter.Color.new(0, 255, 0, 255))
+
+        self.dragAction = Clutter.DragAction()
+        self.add_action(self.dragAction)
+
+        self.dragAction.connect("drag-begin", self._dragBeginCb)
+        self.dragAction.connect("drag-end", self._dragEndCb)
+        self.dragAction.connect("drag-progress", self._dragProgressCb)
+        self.connect("key-press-event", self._keyPressEventCb)
+        self.connect("enter-event", self._enterEventCb)
+        self.connect("leave-event", self._leaveEventCb)
+        self.set_reactive(True)
+
+    def _keyPressEventCb(self, actor, event):
+        print event, dir(event)
+
+    def _enterEventCb(self, actor, event):
+        self.timelineElement.set_reactive(False)
+        self.set_background_color(Clutter.Color.new(0, 0, 0, 255))
+        
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1))
+
+    def _leaveEventCb(self, actor, event):
+        self.timelineElement.set_reactive(True)
+        self.set_background_color(Clutter.Color.new(0, 255, 0, 255))
+        
self.timelineElement.timeline._container.embed.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.ARROW))
+
+    def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
+        self.dragBeginStartX = event_x
+        self.dragBeginStartY = event_y
+        self.lastTs = self.value.timestamp
+        self.valueStart = self.value.value
+        self.tsStart = self.value.timestamp
+        self.duration = self.timelineElement.bElement.props.duration
+        self.inpoint = self.timelineElement.bElement.props.in_point
+        self.start = self.timelineElement.bElement.props.start
+
+    def _dragProgressCb(self, action, actor, delta_x, delta_y):
+        coords = self.dragAction.get_motion_coords()
+        delta_x = coords[0] - self.dragBeginStartX
+        delta_y = coords[1] - self.dragBeginStartY
+        newTs = self.tsStart + Zoomable.pixelToNs(delta_x)
+        newValue = self.valueStart - (delta_y / EXPANDED_SIZE)
+
+        if newTs < self.inpoint or newTs > self.duration:
+            return False
+
+        if newValue < 0.0 or newValue > 1.0:
+            return False
+
+        self.timelineElement.source.unset(self.lastTs)
+        if (self.timelineElement.source.set(newTs, newValue)):
+            self.value = Gst.TimedValue()
+            self.value.timestamp = newTs
+            self.value.value = newValue
+
+            self.lastTs = newTs
+
+            self.timelineElement.setKeyframePosition(self, self.value)
+            self.timelineElement.keyframes = sorted(self.timelineElement.keyframes, key=lambda keyframe: 
keyframe.value.timestamp)
+            self.timelineElement.drawLines()
+            # This will update the viewer. nifty.
+            self.timelineElement.timeline._container.seekInPosition(newTs + self.start)
+
+        return False
+
+    def _dragEndCb(self, action, actor, event_x, event_y, modifiers):
+        pass
+
+
 class URISourceElement(TimelineElement):
     def __init__(self, bElement, track, timeline):
         TimelineElement.__init__(self, bElement, track, timeline)
@@ -525,17 +745,6 @@ class URISourceElement(TimelineElement):
         self.rightHandle.hide()
         self.leftHandle.hide()
 
-    def showKeyframes(self, effect, propname):
-        binding = self.bElement.get_control_binding(propname.name)
-        if not binding:
-            source = GstController.InterpolationControlSource()
-            if not (effect.set_control_source(source, propname.name, "direct")):
-                print "There was something like a problem captain"
-                return
-            binding = effect.get_control_binding(propname.name)
-        print binding
-        print propname.default_value
-
     # private API
 
     def _createGhostclip(self):
@@ -570,6 +779,7 @@ class URISourceElement(TimelineElement):
     def _clickedCb(self, action, actor):
         #TODO : Let's be more specific, masks etc ..
         self.timeline.selection.setToObj(self.bElement, SELECT)
+        return False
 
     def _dragBeginCb(self, action, actor, event_x, event_y, modifiers):
         mode = self.timeline._container.getEditionMode()
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 1b5f285..c460c72 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -665,6 +665,10 @@ class Timeline(Gtk.VBox, Zoomable):
         else:
             self._scrollToPosition(position)
 
+    def seekInPosition(self, position):
+        self.pressed = True
+        self._seeker.seek(position)
+
     def setTimeline(self, bTimeline):
         self.bTimeline = bTimeline
         self.timeline.selection.connect("selection-changed", self._selectionChangedCb)
@@ -685,6 +689,7 @@ class Timeline(Gtk.VBox, Zoomable):
         self.embed = GtkClutter.Embed()
         self.embed.get_accessible().set_name("timeline canvas")  # for dogtail
         self.stage = self.embed.get_stage()
+        perspective = self.stage.get_perspective()
 
         self.timeline = TimelineStage(self)
         self.controls = ControlContainer(self.timeline)
@@ -692,7 +697,9 @@ class Timeline(Gtk.VBox, Zoomable):
         self.shiftMask = False
         self.controlMask = False
 
-        # TODO: make the bg a gradient from (0, 0, 0, 255) to (50, 50, 50, 255)
+        perspective.fov_y = 90.
+        self.stage.set_perspective(perspective)
+
         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)
@@ -1032,7 +1039,7 @@ class Timeline(Gtk.VBox, Zoomable):
     def _playPause(self, unused_action):
         self.app.current.pipeline.togglePlayback()
 
-    def _transposeXY(self, x, y):
+    def transposeXY(self, x, y):
         height = self.ruler.get_allocation().height
         x += self.timeline.get_scroll_point().x
         return x - CONTROL_WIDTH, y - height
@@ -1236,7 +1243,7 @@ class Timeline(Gtk.VBox, Zoomable):
                 if self.zoomed_fitted:
                     self._setBestZoomRatio()
                 else:
-                    x, y = self._transposeXY(x, y)
+                    x, y = self.transposeXY(x, y)
                     self.scrollToPosition(Zoomable.pixelToNs(x))
             else:
                 actor = self.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y)
@@ -1257,7 +1264,7 @@ class Timeline(Gtk.VBox, Zoomable):
             widget.drag_get_data(context, target, time)
             Gdk.drag_status(context, 0, time)
         else:
-            x, y = self._transposeXY(x, y)
+            x, y = self.transposeXY(x, y)
 
             # dragged from the media library
             if not self.timeline.ghostClips and self.isDraggedClip:
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index f07f532..76e976a 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -64,6 +64,8 @@ CANVAS_SPACING = 21
 
 CONTROL_WIDTH = 250
 
+KEYFRAME_SIZE = 8
+
 # 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 793104e..77a09dc 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -839,6 +839,8 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
         If there are no properties, returns a table containing the label
         "No properties."
         """
+        self.bindings = {}
+        self.showKeyframesButtons = []
         is_effect = False
         if isinstance(self.element, GES.Effect):
             is_effect = True
@@ -892,6 +894,11 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
                 table.attach(label, 0, 1, y, y + 1, xoptions=Gtk.AttachOptions.FILL, 
yoptions=Gtk.AttachOptions.FILL)
                 table.attach(widget, 1, 2, y, y + 1, yoptions=Gtk.AttachOptions.FILL)
 
+            if not isinstance(widget, ToggleWidget) and not isinstance(widget, ChoiceWidget):
+                button = self._getShowKeyframesButton(prop)
+                self.showKeyframesButtons.append(button)
+                table.attach(button, 3, 4, y, y + 1, xoptions=Gtk.AttachOptions.FILL, 
yoptions=Gtk.AttachOptions.FILL)
+
             if hasattr(prop, 'blurb'):
                 widget.set_tooltip_text(prop.blurb)
 
@@ -899,11 +906,17 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
 
             # The "reset to default" button associated with this property
             if default_btn:
+                widget.propName = prop.name.split("-")[0]
+                name = prop.name
+
+                # If this element is controlled, the value means nothing anymore.
+                binding = self.element.get_control_binding(prop.name)
+                if binding:
+                    widget.set_sensitive(False)
+                    self.bindings[widget] = binding
                 button = self._getResetToDefaultValueButton(prop, widget)
                 table.attach(button, 2, 3, y, y + 1, xoptions=Gtk.AttachOptions.FILL, 
yoptions=Gtk.AttachOptions.FILL)
                 self.buttons[button] = widget
-            button = self._getShowKeyframesButton(prop)
-            table.attach(button, 3, 4, y, y + 1, xoptions=Gtk.AttachOptions.FILL, 
yoptions=Gtk.AttachOptions.FILL)
 
             self.element.connect('notify::' + prop.name, self._propertyChangedCb, widget)
 
@@ -936,6 +949,11 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
         return button
 
     def _showKeyframesClickedCb(self, button, prop):
+        for but in self.showKeyframesButtons:
+            but.set_relief(Gtk.ReliefStyle.NONE)
+
+        button.set_relief(Gtk.ReliefStyle.HALF)
+
         effect = self.element
         track_type = effect.get_track_type()
         for track_element in effect.get_parent().get_children():
@@ -943,6 +961,16 @@ class GstElementSettingsWidget(Gtk.VBox, Loggable):
                 track_element.ui_element.showKeyframes(effect, prop)
 
     def _defaultBtnClickedCb(self, button, widget):
+        binding = self.bindings[widget]
+        if binding:
+            effect = self.element
+            track_type = effect.get_track_type()
+            for track_element in effect.get_parent().get_children():
+                if hasattr(track_element, "ui_element") and track_type == track_element.get_track_type():
+                    binding.props.control_source.unset_all()
+                    track_element.ui_element.updateKeyframes()
+
+        widget.set_sensitive(True)
         widget.setWidgetToDefault()
 
     def getSettings(self, with_default=False):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]