[pitivi] timeline: Shortcut K to toggle a keyframe
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] timeline: Shortcut K to toggle a keyframe
- Date: Mon, 30 May 2016 18:22:37 +0000 (UTC)
commit 7b082cacf063648070cfa2a0c4e47ec31ef7c189
Author: Jakub Brindza <jakub brindza gmail com>
Date: Thu Mar 24 23:39:41 2016 +0000
timeline: Shortcut K to toggle a keyframe
Differential Revision: https://phabricator.freedesktop.org/D898
pitivi/timeline/elements.py | 61 ++++++++++++++++---------
pitivi/timeline/timeline.py | 36 ++++++---------
pitivi/utils/timeline.py | 3 +
pre-commit.hook | 1 +
tests/Makefile.am | 1 +
tests/test_timeline_elements.py | 94 +++++++++++++++++++++++++++++++++++++++
tests/test_timeline_timeline.py | 6 +--
7 files changed, 153 insertions(+), 49 deletions(-)
---
diff --git a/pitivi/timeline/elements.py b/pitivi/timeline/elements.py
index 4e39767..a5f3935 100644
--- a/pitivi/timeline/elements.py
+++ b/pitivi/timeline/elements.py
@@ -213,6 +213,21 @@ class KeyframeCurve(FigureCanvas, Loggable):
with self.__timeline.app.action_log.started("Keyframe added"):
self.__source.set(event.xdata, value)
+ def toggle_keyframe(self, offset):
+ """
+ Set or unset the keyframe at the given offset.
+ """
+ items = self.__source.get_all()
+ if offset in (items[0].timestamp, items[-1].timestamp):
+ return
+
+ if offset in [item.timestamp for item in items]:
+ self.__source.unset(offset)
+ else:
+ res, value = self.__source.control_source_get_value(offset)
+ assert res
+ self.__source.set(offset, value)
+
# Callbacks
def __controlSourceChangedCb(self, unused_control_source, unused_timed_value):
self.__updatePlots()
@@ -423,7 +438,7 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
if self.__background:
self.add(self.__background)
- self.__keyframeCurve = None
+ self.keyframe_curve = None
self.show_all()
# We set up the default mixing property right here, if a binding was
@@ -446,8 +461,8 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
if self.__background:
self.__background.set_size_request(width, height)
- if self.__keyframeCurve:
- self.__keyframeCurve.set_size_request(width, height)
+ if self.keyframe_curve:
+ self.keyframe_curve.set_size_request(width, height)
self.__width = width
self.__height = height
@@ -471,18 +486,18 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
self.emit("curve-leave")
def __removeKeyframes(self):
- if not self.__keyframeCurve:
+ if not self.keyframe_curve:
# Nothing to remove.
return
- self.__keyframeCurve.disconnect_by_func(
+ self.keyframe_curve.disconnect_by_func(
self.__keyframePlotChangedCb)
- self.__keyframeCurve.disconnect_by_func(self.__curveEnterCb)
- self.__keyframeCurve.disconnect_by_func(self.__curveLeaveCb)
- self.remove(self.__keyframeCurve)
+ self.keyframe_curve.disconnect_by_func(self.__curveEnterCb)
+ self.keyframe_curve.disconnect_by_func(self.__curveLeaveCb)
+ self.remove(self.keyframe_curve)
- self.__keyframeCurve.release()
- self.__keyframeCurve = None
+ self.keyframe_curve.release()
+ self.keyframe_curve = None
# Private methods
def __createKeyframeCurve(self, binding):
@@ -500,14 +515,14 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
val)
self.__removeKeyframes()
- self.__keyframeCurve = KeyframeCurve(self.timeline, binding)
- self.__keyframeCurve.connect("plot-changed",
+ self.keyframe_curve = KeyframeCurve(self.timeline, binding)
+ self.keyframe_curve.connect("plot-changed",
self.__keyframePlotChangedCb)
- self.__keyframeCurve.connect("enter", self.__curveEnterCb)
- self.__keyframeCurve.connect("leave", self.__curveLeaveCb)
- self.add(self.__keyframeCurve)
- self.__keyframeCurve.set_size_request(self.__width, self.__height)
- self.__keyframeCurve.props.visible = bool(self._ges_elem.selected)
+ self.keyframe_curve.connect("enter", self.__curveEnterCb)
+ self.keyframe_curve.connect("leave", self.__curveLeaveCb)
+ self.add(self.keyframe_curve)
+ self.keyframe_curve.set_size_request(self.__width, self.__height)
+ self.keyframe_curve.props.visible = bool(self._ges_elem.selected)
self.queue_draw()
def __createControlBinding(self, element):
@@ -535,11 +550,11 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
if self.timeline.app.project_manager.current_project.pipeline.getState() == Gst.State.PLAYING:
return False
- if not self.__keyframeCurve:
+ if not self.keyframe_curve:
return False
# We do not show keyframes while a clip is being moved on the timeline
- if self.timeline.draggingElement and not self.__keyframeCurve.handling_motion:
+ if self.timeline.draggingElement and not self.keyframe_curve.handling_motion:
return False
# We do not show keyframes when there are several clips selected
@@ -555,19 +570,19 @@ class TimelineElement(Gtk.Layout, timelineUtils.Zoomable, Loggable):
self.propagate_draw(self.__previewer, cr)
if self.__showKeyframes():
- self.propagate_draw(self.__keyframeCurve, cr)
+ self.propagate_draw(self.keyframe_curve, cr)
def do_show_all(self):
for child in self.get_children():
- if bool(self._ges_elem.selected) or child != self.__keyframeCurve:
+ if bool(self._ges_elem.selected) or child != self.keyframe_curve:
child.show_all()
self.show()
# Callbacks
def __selectedChangedCb(self, unused_ges_elem, selected):
- if self.__keyframeCurve:
- self.__keyframeCurve.props.visible = selected
+ if self.keyframe_curve:
+ self.keyframe_curve.props.visible = selected
if self.__previewer:
self.__previewer.setSelected(selected)
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index f3b0311..d0d51c7 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -1494,7 +1494,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.split_action.set_enabled(True)
self.keyframe_action = Gio.SimpleAction.new("keyframe_selected_clips", None)
- self.keyframe_action.connect("activate", self._keyframeCb)
+ self.keyframe_action.connect("activate", self._keyframe_cb)
group.add_action(self.keyframe_action)
self.app.add_accelerator("K", "timeline.keyframe_selected_clips", None)
@@ -1698,30 +1698,24 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
if not splitted and splitting_selection:
self._splitElements()
- def _keyframeCb(self, unused_action, unused_parameter):
+ def _keyframe_cb(self, unused_action, unused_parameter):
"""
Add or remove a keyframe at the current position of the selected clip.
"""
- selected = self.timeline.selection.getSelectedTrackElements()
+ ges_clip = self.timeline.selection.getSingleClip(GES.Clip)
+ if ges_clip is None:
+ return
- for obj in selected:
- keyframe_exists = False
- position = self._project.pipeline.getPosition()
- position_in_obj = (position - obj.props.start) + obj.props.in_point
- interpolators = obj.getInterpolators()
- for value in interpolators:
- interpolator = obj.getInterpolator(value)
- keyframes = interpolator.getInteriorKeyframes()
- for kf in keyframes:
- if kf.getTime() == position_in_obj:
- keyframe_exists = True
- self.app.action_log.begin("remove volume point")
- interpolator.removeKeyframe(kf)
- self.app.action_log.commit()
- if keyframe_exists is False:
- self.app.action_log.begin("add volume point")
- interpolator.newKeyframe(position_in_obj)
- self.app.action_log.commit()
+ ges_track_elements = ges_clip.find_track_elements(None, GES.TrackType.VIDEO, GES.Source)
+ ges_track_elements += ges_clip.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+
+ offset = self._project.pipeline.getPosition() - ges_clip.props.start
+ if offset <= 0 or offset >= ges_clip.props.duration:
+ return
+
+ for ges_track_element in ges_track_elements:
+ keyframe_curve = ges_track_element.ui.keyframe_curve
+ keyframe_curve.toggle_keyframe(offset)
def _playPauseCb(self, unused_action, unused_parameter):
self._project.pipeline.togglePlayback()
diff --git a/pitivi/utils/timeline.py b/pitivi/utils/timeline.py
index 316166d..9f02c76 100644
--- a/pitivi/utils/timeline.py
+++ b/pitivi/utils/timeline.py
@@ -152,6 +152,9 @@ class Selection(GObject.Object, Loggable):
element.selected.selected = True
self.emit("selection-changed")
+ def select(self, objs):
+ self.setSelection(objs, SELECT)
+
def unselect(self, objs):
self.setSelection(objs, UNSELECT)
diff --git a/pre-commit.hook b/pre-commit.hook
index 83e4243..bc49404 100755
--- a/pre-commit.hook
+++ b/pre-commit.hook
@@ -23,6 +23,7 @@ tests/test_misc.py \
tests/test_widgets.py \
tests/test_common.py \
tests/common.py \
+tests/test_timeline_elements.py \
tests/test_timeline_timeline.py \
tests/test_pipeline.py \
tests/test_application.py \
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4fd1946..8f576e8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -17,6 +17,7 @@ tests = \
test_previewers.py \
test_project.py \
test_system.py \
+ test_timeline_elements.py \
test_timeline_layer.py \
test_timeline_timeline.py \
test_undo.py \
diff --git a/tests/test_timeline_elements.py b/tests/test_timeline_elements.py
new file mode 100644
index 0000000..079a87a
--- /dev/null
+++ b/tests/test_timeline_elements.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+#
+# tests/test_timeline_elements.py
+#
+# Copyright (c) 2016, Jakub Brindza <jakub brindza gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+from unittest import mock
+
+from gi.repository import GES
+
+from tests import common
+from tests import test_timeline_timeline
+
+
+class TestKeyframeCurve(test_timeline_timeline.BaseTestTimeline):
+
+ def test_keyframe_toggle(self):
+ timeline = self.createTimeline()
+ pipeline = timeline._project.pipeline
+ self.addClipsSimple(timeline, 2)
+ ges_layer = timeline.ges_timeline.get_layers()[0]
+ # For variety, add TitleClip to the list of clips.
+ ges_clip = common.create_test_clip(GES.TitleClip)
+ ges_clip.props.duration = 4.5
+ ges_layer.add_clip(ges_clip)
+
+ for ges_clip in ges_layer.get_clips():
+ start = ges_clip.props.start
+ offsets = list(range(1, int(ges_clip.props.duration)))
+ timeline.selection.select([ges_clip])
+
+ ges_video_source = None
+ for child in ges_clip.get_children(recursive=False):
+ if isinstance(child, GES.VideoSource):
+ ges_video_source = child
+ binding = ges_video_source.get_control_binding("alpha")
+ control_source = binding.props.control_source
+
+ # Test adding of keyframes.
+ for offset in offsets:
+ position = start + offset
+ pipeline.getPosition = mock.Mock(return_value=position)
+ timeline.parent._keyframe_cb(None, None)
+ values = [item.timestamp for item in control_source.get_all()]
+ self.assertIn(offset, values)
+
+ # Test removing of keyframes.
+ for offset in offsets:
+ position = start + offset
+ pipeline.getPosition = mock.Mock(return_value=position)
+ timeline.parent._keyframe_cb(None, None)
+ values = [item.timestamp for item in control_source.get_all()]
+ self.assertNotIn(offset, values)
+
+ # Make sure the keyframes at the start and end of the clip
+ # cannot be toggled.
+ for offset in [0, ges_clip.props.duration]:
+ position = start + offset
+ pipeline.getPosition = mock.Mock(return_value=position)
+ values = [item.timestamp for item in control_source.get_all()]
+ self.assertIn(offset, values)
+ timeline.parent._keyframe_cb(None, None)
+ values = [item.timestamp for item in control_source.get_all()]
+ self.assertIn(offset, values)
+
+ # Test out of clip range.
+ for offset in [-1, ges_clip.props.duration + 1]:
+ position = min(max(0, start + offset),
+ timeline.ges_timeline.props.duration)
+ pipeline.getPosition = mock.Mock(return_value=position)
+ timeline.parent._keyframe_cb(None, None)
+ values = [item.timestamp for item in control_source.get_all()]
+ self.assertEqual(values, [0, ges_clip.props.duration])
+
+ def test_no_clip_selected(self):
+ # When no clip is selected, pressing key should yield no action.
+ # Make sure this does not raise any exception
+ timeline = self.createTimeline()
+ timeline.parent._keyframe_cb(None, None)
diff --git a/tests/test_timeline_timeline.py b/tests/test_timeline_timeline.py
index c1519a7..c6f1661 100644
--- a/tests/test_timeline_timeline.py
+++ b/tests/test_timeline_timeline.py
@@ -53,15 +53,11 @@ class BaseTestTimeline(common.TestCase):
def addClipsSimple(self, timeline, num_clips):
layer = timeline.ges_timeline.append_layer()
-
asset = GES.UriClipAsset.request_sync(
common.get_sample_uri("tears_of_steel.webm"))
-
clips = [layer.add_asset(asset, i * 10, 0, 10, GES.TrackType.UNKNOWN)
- for i in range(num_clips)]
-
+ for i in range(num_clips)]
self.assertEqual(len(clips), num_clips)
-
return clips
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]