[pitivi] elements: Select MultipleKeyframeCurve keyframes.



commit 1508ea333c3af3b17abc159067336a0b395b6eca
Author: Stefan Popa <stefanpopa2209 gmail com>
Date:   Tue Jul 18 18:24:02 2017 +0300

    elements: Select MultipleKeyframeCurve keyframes.
    
    Added the possibility to select MultipleKeyframeCurve keyframes by clicking
    on them.
    
    Differential Revision: https://phabricator.freedesktop.org/D1784

 pitivi/timeline/elements.py |  145 ++++++++++++++++++++++++++++++++++---------
 pitivi/timeline/timeline.py |   15 ++++-
 2 files changed, 129 insertions(+), 31 deletions(-)
---
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index e06a18f..b2d9227 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -38,6 +38,7 @@ from pitivi.effects import VIDEO_EFFECT
 from pitivi.timeline.previewers import AudioPreviewer
 from pitivi.timeline.previewers import VideoPreviewer
 from pitivi.undo.timeline import CommitTimelineFinalizingAction
+from pitivi.utils import pipeline
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.misc import disconnectAllByFunc
 from pitivi.utils.misc import filename_from_uri
@@ -54,6 +55,8 @@ KEYFRAME_LINE_HEIGHT = 2
 KEYFRAME_LINE_ALPHA = 0.5
 KEYFRAME_LINE_COLOR = "#EDD400"  # "Tango" medium yellow
 KEYFRAME_NODE_COLOR = "#F57900"  # "Tango" medium orange
+SELECTED_KEYFRAME_NODE_COLOR = "#204A87" # "Tango" dark sky blue
+HOVERED_KEYFRAME_NODE_COLOR = "#3465A4" # "Tango" medium sky blue
 
 CURSORS = {
     GES.Edge.EDGE_START: Gdk.Cursor.new(Gdk.CursorType.LEFT_SIDE),
@@ -130,11 +133,11 @@ class KeyframeCurve(FigureCanvas, Loggable):
 
         # The PathCollection object holding the keyframes dots.
         sizes = [50]
-        self.__keyframes = self._ax.scatter([], [], marker='D', s=sizes,
-                                            c=KEYFRAME_NODE_COLOR, zorder=2)
+        self._keyframes = self._ax.scatter([], [], marker='D', s=sizes,
+                                           c=KEYFRAME_NODE_COLOR, zorder=2)
 
         # matplotlib weirdness, simply here to avoid a warning ..
-        self.__keyframes.set_picker(True)
+        self._keyframes.set_picker(True)
 
         # The Line2D object holding the lines between keyframes.
         self.__line = self._ax.plot([], [],
@@ -145,9 +148,9 @@ class KeyframeCurve(FigureCanvas, Loggable):
 
         # Drag and drop logic
         # Whether the clicked keyframe or line has been dragged.
-        self.__dragged = False
+        self._dragged = False
         # The inpoint of the clicked keyframe.
-        self.__offset = None
+        self._offset = None
         # The (offset, value) of both keyframes of the clicked keyframe line.
         self.__clicked_line = ()
         # Whether the mouse events go to the keyframes logic.
@@ -193,7 +196,7 @@ class KeyframeCurve(FigureCanvas, Loggable):
 
         arr = numpy.array((self._line_xs, self._line_ys))
         arr = arr.transpose()
-        self.__keyframes.set_offsets(arr)
+        self._keyframes.set_offsets(arr)
         self.__line.set_xdata(self._line_xs)
         self.__line.set_ydata(self._line_ys)
         self.queue_draw()
@@ -214,7 +217,7 @@ class KeyframeCurve(FigureCanvas, Loggable):
 
     def __maybeCreateKeyframe(self, event):
         line_contains = self.__line.contains(event)[0]
-        keyframe_existed = self.__keyframes.contains(event)[0]
+        keyframe_existed = self._keyframes.contains(event)[0]
         if line_contains and not keyframe_existed:
             self._create_keyframe(event.xdata)
 
@@ -277,11 +280,11 @@ class KeyframeCurve(FigureCanvas, Loggable):
         if event.button != 1:
             return
 
-        result = self.__keyframes.contains(event)
+        result = self._keyframes.contains(event)
         if result[0]:
             # A keyframe has been clicked.
             keyframe_index = result[1]['ind'][0]
-            offsets = self.__keyframes.get_offsets()
+            offsets = self._keyframes.get_offsets()
             offset = offsets[keyframe_index][0]
 
             if event.guiEvent.type == Gdk.EventType._2BUTTON_PRESS:
@@ -294,7 +297,7 @@ class KeyframeCurve(FigureCanvas, Loggable):
                 # This is needed because a double-click also triggers a
                 # BUTTON_PRESS event which starts a "Move keyframe" operation
                 self._timeline.app.action_log.try_rollback("Move keyframe")
-                self.__offset = None
+                self._offset = None
 
                 # A keyframe has been double-clicked, remove it.
                 self._remove_keyframe(offset)
@@ -302,7 +305,7 @@ class KeyframeCurve(FigureCanvas, Loggable):
                 # Remember the clicked frame for drag&drop.
                 self._timeline.app.action_log.begin("Move keyframe",
                                                     toplevel=True)
-                self.__offset = offset
+                self._offset = offset
                 self.handling_motion = True
             return
 
@@ -313,7 +316,7 @@ class KeyframeCurve(FigureCanvas, Loggable):
             self._timeline.app.action_log.begin("Move keyframe curve segment",
                                                 toplevel=True)
             x = event.xdata
-            offsets = self.__keyframes.get_offsets()
+            offsets = self._keyframes.get_offsets()
             keyframes = offsets[:, 0]
             right = numpy.searchsorted(keyframes, x)
             # Remember the clicked line for drag&drop.
@@ -324,17 +327,17 @@ class KeyframeCurve(FigureCanvas, Loggable):
     def _mpl_motion_event_cb(self, event):
         if event.ydata is not None and event.xdata is not None:
             # The mouse event is in the figure boundaries.
-            if self.__offset is not None:
-                self.__dragged = True
+            if self._offset is not None:
+                self._dragged = True
                 keyframe_ts = self.__computeKeyframeNewTimestamp(event)
                 ydata = max(self.__ylim_min, min(event.ydata, self.__ylim_max))
 
-                self._move_keyframe(int(self.__offset), keyframe_ts, ydata)
-                self.__offset = keyframe_ts
+                self._move_keyframe(int(self._offset), keyframe_ts, ydata)
+                self._offset = keyframe_ts
                 self._update_tooltip(event)
                 hovering = True
             elif self.__clicked_line:
-                self.__dragged = True
+                self._dragged = True
                 ydata = max(self.__ylim_min, min(event.ydata, self.__ylim_max))
                 self._move_keyframe_line(self.__clicked_line, ydata, self.__ydata_drag_start)
                 hovering = True
@@ -371,30 +374,30 @@ class KeyframeCurve(FigureCanvas, Loggable):
         ges_clip = self._timeline.selection.getSingleClip(GES.Clip)
         event.xdata = Zoomable.pixelToNs(x) - ges_clip.props.start + ges_clip.props.in_point
 
-        if self.__offset is not None:
+        if self._offset is not None:
             # If dragging a keyframe, make sure the keyframe ends up exactly
             # where the mouse was released. Otherwise, the playhead will not
             # seek exactly on the keyframe.
-            if self.__dragged:
+            if self._dragged:
                 if event.ydata is not None:
                     keyframe_ts = self.__computeKeyframeNewTimestamp(event)
                     ydata = max(self.__ylim_min, min(event.ydata, self.__ylim_max))
-                    self._move_keyframe(int(self.__offset), keyframe_ts, ydata)
+                    self._move_keyframe(int(self._offset), keyframe_ts, ydata)
             self.debug("Keyframe released")
             self._timeline.app.action_log.commit("Move keyframe")
         elif self.__clicked_line:
             self.debug("Line released")
             self._timeline.app.action_log.commit("Move keyframe curve segment")
 
-            if not self.__dragged:
+            if not self._dragged:
                 # The keyframe line was clicked, but not dragged
                 assert event.guiEvent.type == Gdk.EventType.BUTTON_RELEASE
                 self.__maybeCreateKeyframe(event)
 
         self.handling_motion = False
-        self.__offset = None
+        self._offset = None
         self.__clicked_line = ()
-        self.__dragged = False
+        self._dragged = False
 
     def _update_tooltip(self, event):
         """Sets or clears the tooltip showing info about the hovered line."""
@@ -402,8 +405,8 @@ class KeyframeCurve(FigureCanvas, Loggable):
         if event:
             if not event.xdata:
                 return
-            if self.__offset is not None:
-                xdata = self.__offset
+            if self._offset is not None:
+                xdata = self._offset
             else:
                 xdata = max(self._line_xs[0], min(event.xdata, self._line_xs[-1]))
             res, value = self.__source.control_source_get_value(xdata)
@@ -424,12 +427,12 @@ class KeyframeCurve(FigureCanvas, Loggable):
         # The user can not change the timestamp of the first
         # and last keyframes.
         values = self.__source.get_all()
-        if self.__offset in (values[0].timestamp, values[-1].timestamp):
-            return self.__offset
+        if self._offset in (values[0].timestamp, values[-1].timestamp):
+            return self._offset
 
-        if event.xdata != self.__offset:
+        if event.xdata != self._offset:
             try:
-                kf = next(kf for kf in values if kf.timestamp == int(self.__offset))
+                kf = next(kf for kf in values if kf.timestamp == int(self._offset))
             except StopIteration:
                 return event.xdata
 
@@ -449,9 +452,24 @@ class MultipleKeyframeCurve(KeyframeCurve):
 
     def __init__(self, timeline, bindings):
         self.__bindings = bindings
-
         super().__init__(timeline, bindings[0])
 
+        self._timeline = timeline
+        self._project = timeline.app.project_manager.current_project
+        self._project.pipeline.connect("position", self._position_cb)
+
+        sizes = [80]
+        self.__selected_keyframe = self._ax.scatter([0], [0.5], marker='D', s=sizes,
+                                                    c=SELECTED_KEYFRAME_NODE_COLOR, zorder=3)
+        self.__hovered_keyframe = self._ax.scatter([0], [0.5], marker='D', s=sizes,
+                                                    c=HOVERED_KEYFRAME_NODE_COLOR, zorder=3)
+        self.__update_selected_keyframe()
+        self.__hovered_keyframe.set_visible(False)
+
+    def release(self):
+        super().release()
+        self._project.pipeline.disconnect_by_func(self._position_cb)
+
     def _connect_sources(self):
         for binding in self.__bindings:
             source = binding.props.control_source
@@ -502,6 +520,73 @@ class MultipleKeyframeCurve(KeyframeCurve):
     def _move_keyframe_line(self, line, y_dest_value, y_start_value):
         pass
 
+    def _mpl_button_release_event_cb(self, event):
+        if event.button == 1:
+            if self._offset is not None and not self._dragged:
+                # A keyframe was clicked but not dragged, so we
+                # should select it by seeking to its position.
+                source = self._timeline.selection.getSingleClip()
+                assert source
+                position = int(self._offset) - source.props.in_point + source.props.start
+
+                if self._timeline.app.settings.leftClickAlsoSeeks:
+                    self._timeline.set_next_seek_position(position)
+                else:
+                    self._project.pipeline.simple_seek(position)
+
+        super()._mpl_button_release_event_cb(event)
+
+    def _mpl_motion_event_cb(self, event):
+        super()._mpl_motion_event_cb(event)
+
+        result = self._keyframes.contains(event)
+        if result[0]:
+            # A keyframe is hovered
+            keyframe_index = result[1]['ind'][0]
+            offset = self._keyframes.get_offsets()[keyframe_index][0]
+            self.__show_special_keyframe(self.__hovered_keyframe, offset)
+        else:
+            self.__hide_special_keyframe(self.__hovered_keyframe)
+
+    def __show_special_keyframe(self, keyframe, offset):
+        offsets = numpy.array([[offset, 0.5]])
+        keyframe.set_offsets(offsets)
+        keyframe.set_visible(True)
+        self.queue_draw()
+
+    def __hide_special_keyframe(self, keyframe):
+        keyframe.set_visible(False)
+        self.queue_draw()
+
+    def _controlSourceChangedCb(self, control_source, timed_value):
+        super()._controlSourceChangedCb(control_source, timed_value)
+        self.__update_selected_keyframe()
+        self.__hide_special_keyframe(self.__hovered_keyframe)
+
+    def _position_cb(self, unused_pipeline, unused_position):
+        self.__update_selected_keyframe()
+
+    def __update_selected_keyframe(self):
+        try:
+            position = self._project.pipeline.getPosition()
+        except pipeline.PipelineError:
+            self.warning("Could not get pipeline position")
+            return
+
+        source = self._timeline.selection.getSingleClip()
+        if source is None:
+            return
+        source_position = position - source.props.start + source.props.in_point
+
+        offsets = self._keyframes.get_offsets()
+        keyframes = offsets[:, 0]
+
+        index = numpy.searchsorted(keyframes, source_position)
+        if 0 <= index < len(keyframes) and keyframes[index] == source_position:
+            self.__show_special_keyframe(self.__selected_keyframe, source_position)
+        else:
+            self.__hide_special_keyframe(self.__selected_keyframe)
+
     def _update_tooltip(self, event):
         markup = None
         if event:
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 67077dc..fafc069 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -375,6 +375,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self.__last_position = 0
         self._scrubbing = False
         self._scrolling = False
+        self.__next_seek_position = None
 
         # Clip selection.
         self.selection = Selection()
@@ -670,6 +671,14 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         sources = self.get_sources_at_position(self.__last_position)
         self.app.gui.viewer.overlay_stack.set_current_sources(sources)
 
+    def set_next_seek_position(self, next_seek_position):
+        """Sets the position the playhead seeks to on the next button-release.
+
+        Args:
+            next_seek_position (int): the position to seek to
+        """
+        self.__next_seek_position = next_seek_position
+
     def _button_press_event_cb(self, unused_widget, event):
         self.debug("PRESSED %s", event)
         self.app.gui.focusTimeline()
@@ -727,7 +736,11 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         self._scrolling = False
 
         if allow_seek and res and (button == 1 and self.app.settings.leftClickAlsoSeeks):
-            self._seek(event)
+            if self.__next_seek_position is not None:
+                self._project.pipeline.simple_seek(self.__next_seek_position)
+                self.__next_seek_position = None
+            else:
+                self._seek(event)
 
         self._snapEndedCb()
         self.update_visible_overlays()


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