[pitivi] timeline: Add clips cutting functionality
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] timeline: Add clips cutting functionality
- Date: Sat, 2 Apr 2022 18:55:41 +0000 (UTC)
commit 383c60e155ce66e2ef9b2ae53a7fa264177acb87
Author: Jan Kolarik <kotlajz gmail com>
Date: Fri Apr 1 23:03:31 2022 +0200
timeline: Add clips cutting functionality
Adds timeline action that cuts clips to paste / move them at a different
position.
Fixes #2570
data/ui/timelinetoolbar.ui | 15 +++
pitivi/timeline/timeline.py | 12 ++
tests/test_clipproperties.py | 2 +-
tests/test_medialibrary.py | 2 +-
tests/test_render.py | 2 +-
tests/test_timeline_timeline.py | 248 ++++++++++++++++++++++++++--------------
tests/test_undo_timeline.py | 6 +-
7 files changed, 194 insertions(+), 93 deletions(-)
---
diff --git a/data/ui/timelinetoolbar.ui b/data/ui/timelinetoolbar.ui
index 317f3bfb6..c887dc2a4 100644
--- a/data/ui/timelinetoolbar.ui
+++ b/data/ui/timelinetoolbar.ui
@@ -69,6 +69,21 @@
<property name="homogeneous">True</property>
</packing>
</child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Cut clips</property>
+ <property name="action_name">timeline.cut-selected-clips</property>
+ <property name="label" translatable="yes">Cut</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">edit-cut-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
<child>
<object class="GtkToolButton" id="toolbutton5">
<property name="visible">True</property>
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 1361b903a..1cfef877c 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -1656,6 +1656,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.delete_and_shift_action.set_enabled(selection_non_empty)
self.group_action.set_enabled(selection.can_group)
self.ungroup_action.set_enabled(selection.can_ungroup)
+ self.cut_action.set_enabled(selection_non_empty)
self.copy_action.set_enabled(selection_non_empty)
can_paste = bool(self.__copied_group)
self.paste_action.set_enabled(can_paste)
@@ -1779,6 +1780,13 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.ungroup_action,
_("Ungroup selected clips"))
+ self.cut_action = Gio.SimpleAction.new("cut-selected-clips", None)
+ self.cut_action.connect("activate", self.__cut_clips_cb)
+ group.add_action(self.cut_action)
+ self.app.shortcuts.add("timeline.cut-selected-clips", ["<Primary>x"],
+ self.cut_action,
+ _("Cut selected clips"))
+
self.copy_action = Gio.SimpleAction.new("copy-selected-clips", None)
self.copy_action.connect("activate", self.__copy_clips_cb)
group.add_action(self.copy_action)
@@ -2068,6 +2076,10 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
clips = self.timeline.selection.get_clips_of([container])
self.timeline.selection.set_selection(clips, SELECT)
+ def __cut_clips_cb(self, unused_action, unused_parameter):
+ self.copy_action.activate(None)
+ self.delete_action.activate(None)
+
def __copy_clips_cb(self, unused_action, unused_parameter):
group = self.timeline.selection.group()
try:
diff --git a/tests/test_clipproperties.py b/tests/test_clipproperties.py
index 3608a046f..3f9a0b632 100644
--- a/tests/test_clipproperties.py
+++ b/tests/test_clipproperties.py
@@ -620,7 +620,7 @@ class SpeedPropertiesTest(common.TestCase):
self.timeline_container.timeline.selection.select([clip])
total_duration = clip.props.duration
self.project.pipeline.get_position = mock.Mock(return_value=duration)
- self.timeline_container.split_action.emit("activate", None)
+ self.timeline_container.split_action.activate()
clip1, clip2 = self.layer.get_clips()
self.assertEqual(clip1.props.start, 0)
diff --git a/tests/test_medialibrary.py b/tests/test_medialibrary.py
index a5a46d96f..9110abf1e 100644
--- a/tests/test_medialibrary.py
+++ b/tests/test_medialibrary.py
@@ -239,7 +239,7 @@ class TestMediaLibrary(BaseTestMediaLibrary):
self.assertEqual(set(project.list_assets(GES.Extractable)), set([target, asset]))
# Remove the asset
- self.medialibrary.remove_assets_action.emit("activate", None)
+ self.medialibrary.remove_assets_action.activate()
# Make sure that the project has not assets anymore
self.assertEqual(project.list_assets(GES.Extractable), [])
diff --git a/tests/test_render.py b/tests/test_render.py
index 55ae710d0..49538de2b 100644
--- a/tests/test_render.py
+++ b/tests/test_render.py
@@ -542,7 +542,7 @@ class TestRender(BaseTestMediaLibrary):
caps = dialog.dialog.get_caps()
self.assert_caps_equal(caps, "video/x-h264,profile=baseline")
- dialog.dialog.ok_btn.emit("clicked")
+ dialog.dialog.ok_btn.clicked()
self.assert_caps_equal(project.video_profile.get_format(), "video/x-h264,profile=baseline")
dialog._video_settings_button_clicked_cb(None)
diff --git a/tests/test_timeline_timeline.py b/tests/test_timeline_timeline.py
index d59dee1fa..9a7c1587c 100644
--- a/tests/test_timeline_timeline.py
+++ b/tests/test_timeline_timeline.py
@@ -252,7 +252,7 @@ class TestGrouping(common.TestCase):
# An audio-video clip is selected.
self.__check_can_group_ungroup(timeline_container, False, True)
- timeline_container.ungroup_action.emit("activate", None)
+ timeline_container.ungroup_action.activate()
# The resulting audio clip and video clip should both be selected.
self.__check_can_group_ungroup(timeline_container, True, False)
@@ -267,7 +267,7 @@ class TestGrouping(common.TestCase):
# Both clip are selected.
self.__check_can_group_ungroup(timeline_container, True, False)
- timeline_container.group_action.emit("activate", None)
+ timeline_container.group_action.activate()
# The resulting audio-video clip should be selected.
self.__check_can_group_ungroup(timeline_container, False, True)
@@ -280,7 +280,7 @@ class TestGrouping(common.TestCase):
self.assertIsNone(clip.get_parent())
self.click_clip(clip, expect_selected=True, ctrl_key=True)
- timeline_container.group_action.emit("activate", None)
+ timeline_container.group_action.activate()
for clip in clips:
# Check that we created a new group
@@ -333,7 +333,7 @@ class TestGrouping(common.TestCase):
# Selecting a clip selects all the clips in its group.
self.click_clip(clips[0], expect_selected=True)
- timeline_container.ungroup_action.emit("activate", None)
+ timeline_container.ungroup_action.activate()
layer = timeline.ges_timeline.get_layers()[0]
clips = layer.get_clips()
self.assertEqual(len(clips), num_clips)
@@ -352,7 +352,7 @@ class TestGrouping(common.TestCase):
# Split the clip
position = timeline.ges_timeline.get_frame_time(2)
timeline.ges_timeline.get_asset().pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.split_action.emit("activate", None)
+ timeline_container.split_action.activate()
layer = timeline.ges_timeline.get_layers()[0]
clips = layer.get_clips()
self.assertEqual(len(clips), 2)
@@ -375,7 +375,7 @@ class TestGrouping(common.TestCase):
self.assert_clip_selected(clips[1], expect_selected=True)
# Group the two selected clips
- timeline_container.group_action.emit("activate", None)
+ timeline_container.group_action.activate()
# Deselect the first clip, notice both clips have been deselected
self.click_clip(clips[0], expect_selected=False, ctrl_key=True)
@@ -400,7 +400,7 @@ class TestGrouping(common.TestCase):
self.click_clip(ges_clip, expect_selected=True)
- timeline_container.ungroup_action.emit("activate", None)
+ timeline_container.ungroup_action.activate()
layer = timeline.ges_timeline.get_layers()[0]
ges_clip0, ges_clip1 = layer.get_clips()
@@ -466,111 +466,185 @@ class TestGrouping(common.TestCase):
"No new layer should have been created")
-class TestCopyPaste(common.TestCase):
+class TestCutCopyPaste(common.TestCase):
- def copy_clips(self, num_clips):
- timeline_container = common.create_timeline_container()
- timeline = timeline_container.timeline
+ @common.setup_project_with_clips(assets_names=["mp3_sample.mp3", "tears_of_steel.webm"])
+ def test_cut_paste(self):
+ # Cut the clips.
+ clips = self.layer.get_clips()
+ for clip in clips:
+ self.click_clip(clip, expect_selected=True, ctrl_key=True)
+ self.timeline_container.cut_action.activate()
- clips = self.add_clips_simple(timeline, num_clips)
+ # Check no clip is present.
+ clips = self.layer.get_clips()
+ self.assertEqual(len(clips), 0)
- # Press <ctrl> so selecting in ADD mode
- event = mock.Mock()
- event.keyval = Gdk.KEY_Control_L
- timeline_container.do_key_press_event(event)
- timeline.get_clicked_layer_and_pos = mock.Mock()
- timeline.get_clicked_layer_and_pos.return_value = (None, None)
+ # Paste the clips.
+ position = 0
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
- # Select the 2 clips
- for clip in clips:
- self.click_clip(clip, expect_selected=True)
+ # Check the clips are pasted at the given position and are selected.
+ clips = self.layer.get_clips()
+ self.assertEqual(len(clips), 2)
+ self.assert_clip_selected(clips[0], expect_selected=True)
+ self.assert_clip_selected(clips[1], expect_selected=True)
- self.assertTrue(timeline_container.copy_action.props.enabled)
- self.assertFalse(timeline_container.paste_action.props.enabled)
- timeline_container.copy_action.emit("activate", None)
- self.assertTrue(timeline_container.paste_action.props.enabled)
+ first_clip_start = clips[0].props.start
+ second_clip_start = clips[1].props.start
+ self.assertEqual(first_clip_start, position)
+ self.assertEqual(second_clip_start, position + clips[0].props.duration)
- return timeline_container
+ # Cut the first clip.
+ self.click_clip(clips[1], expect_selected=False, ctrl_key=True)
+ # TODO: Remove this line when https://gitlab.gnome.org/GNOME/pitivi/-/issues/2433 is fixed.
+ self.click_clip(clips[0], expect_selected=True)
+ self.timeline_container.cut_action.activate()
- def test_copy_paste(self):
- timeline_container = self.copy_clips(2)
- timeline = timeline_container.timeline
- layer = timeline.ges_timeline.get_layers()[0]
- project = timeline.ges_timeline.get_asset()
+ # Check only the second clip is present.
+ clips = self.layer.get_clips()
+ self.assertEqual(len(clips), 1)
+ self.assert_clip_selected(clips[0], expect_selected=False)
+ self.assertEqual(clips[0].props.start, second_clip_start)
- clips = layer.get_clips()
+ # Paste the clip again.
+ self.project.pipeline.get_position = mock.Mock(return_value=first_clip_start)
+ self.timeline_container.paste_action.activate()
+
+ # Check both clips are present.
+ clips = self.layer.get_clips()
self.assertEqual(len(clips), 2)
+ self.assert_clip_selected(clips[0], expect_selected=True)
+ self.assert_clip_selected(clips[1], expect_selected=False)
+ self.assertEqual(clips[0].props.start, first_clip_start)
+ self.assertEqual(clips[1].props.start, second_clip_start)
+
+ @common.setup_project_with_clips(assets_names=["mp3_sample.mp3", "tears_of_steel.webm"])
+ def test_copy_paste(self):
+ # Copy the clips.
+ clips = self.layer.get_clips()
+ for clip in clips:
+ self.click_clip(clip, expect_selected=True, ctrl_key=True)
+ self.timeline_container.copy_action.activate()
- # Pasting clips for the first time.
- position = 20
- project.pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.paste_action.emit("activate", None)
+ # Paste the clips for the first time.
+ position = self.project.pipeline.get_duration()
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
- n_clips = layer.get_clips()
- self.assertEqual(len(n_clips), 4)
+ new_clips = self.layer.get_clips()
+ self.assertEqual(len(new_clips), 4)
- copied_clips = [clip for clip in n_clips if clip not in clips]
+ copied_clips = [clip for clip in new_clips if clip not in clips]
self.assertEqual(len(copied_clips), 2)
self.assertEqual(copied_clips[0].props.start, position)
- self.assertEqual(copied_clips[1].props.start, position + 10)
+ self.assertEqual(copied_clips[1].props.start, position + copied_clips[0].props.duration)
- # Pasting same clips second time.
- position = 40
- project.pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.paste_action.emit("activate", None)
+ # Paste the same clips for the second time.
+ position = self.project.pipeline.get_duration()
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
- n_clips = layer.get_clips()
- self.assertEqual(len(n_clips), 6)
+ new_clips = self.layer.get_clips()
+ self.assertEqual(len(new_clips), 6)
- copied_clips = [clip for clip in n_clips if clip not in clips]
+ copied_clips = [clip for clip in new_clips if clip not in clips]
self.assertEqual(len(copied_clips), 4)
self.assertEqual(copied_clips[2].props.start, position)
- self.assertEqual(copied_clips[3].props.start, position + 10)
+ self.assertEqual(copied_clips[3].props.start, position + copied_clips[2].props.duration)
+ @common.setup_project_with_clips(assets_names=["tears_of_steel.webm"])
def test_paste_not_possible(self):
- timeline_container = self.copy_clips(1)
- timeline = timeline_container.timeline
- layer = timeline.ges_timeline.get_layers()[0]
- project = timeline.ges_timeline.get_asset()
- self.assertEqual(len(layer.get_clips()), 1)
+ # Copy the clip.
+ clip, = self.layer.get_clips()
+ self.click_clip(clip, expect_selected=True)
+ self.timeline_container.copy_action.activate()
+ # Try to paste the clip at the same position.
position = 0
- project.pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.paste_action.emit("activate", None)
- self.assertEqual(len(layer.get_clips()), 1)
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
+
+ clips = self.layer.get_clips()
+ self.assertEqual(len(clips), 1)
+ @common.setup_project_with_clips(assets_names=["tears_of_steel.webm"])
def test_paste_selection(self):
- timeline_container = self.copy_clips(1)
- timeline = timeline_container.timeline
- layer = timeline.ges_timeline.get_layers()[0]
- project = timeline.ges_timeline.get_asset()
+ # Copy the clip.
+ clip, = self.layer.get_clips()
+ self.click_clip(clip, expect_selected=True)
+ self.timeline_container.copy_action.activate()
- # Paste a single clip.
- clips = layer.get_clips()
- self.assertEqual(len(clips), 1)
- position = 10
- project.pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.paste_action.emit("activate", None)
+ # Paste the clip.
+ position = self.project.pipeline.get_duration()
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
- clips = layer.get_clips()
+ clips = self.layer.get_clips()
self.assert_clip_selected(clips[0], expect_selected=False)
self.assert_clip_selected(clips[1], expect_selected=True)
- # The copy_clips() above simulates CTRL button press, so this will add 1st clip to the selection.
- self.click_clip(clips[0], expect_selected=True)
- timeline_container.copy_action.emit("activate", None)
+ # Add the first clip to the selection.
+ self.click_clip(clips[0], expect_selected=True, ctrl_key=True)
+ self.timeline_container.copy_action.activate()
- # Paste two clips.
- position = 20
- project.pipeline.get_position = mock.Mock(return_value=position)
- timeline_container.paste_action.emit("activate", None)
+ # Paste both clips.
+ position = self.project.pipeline.get_duration()
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
- clips = layer.get_clips()
+ clips = self.layer.get_clips()
self.assert_clip_selected(clips[0], expect_selected=False)
self.assert_clip_selected(clips[1], expect_selected=False)
self.assert_clip_selected(clips[2], expect_selected=True)
self.assert_clip_selected(clips[3], expect_selected=True)
+ @common.setup_project_with_clips(assets_names=["tears_of_steel.webm"])
+ def test_actions_enabled_status(self):
+ # Check status after the initialization.
+ self.assertFalse(self.timeline_container.cut_action.props.enabled)
+ self.assertFalse(self.timeline_container.copy_action.props.enabled)
+ self.assertFalse(self.timeline_container.paste_action.props.enabled)
+
+ # Check status after selecting the clip.
+ clip, = self.layer.get_clips()
+ self.click_clip(clip, expect_selected=True)
+
+ self.assertTrue(self.timeline_container.cut_action.props.enabled)
+ self.assertTrue(self.timeline_container.copy_action.props.enabled)
+ self.assertFalse(self.timeline_container.paste_action.props.enabled)
+
+ # Check status after copying the clip.
+ self.timeline_container.copy_action.activate()
+
+ self.assertTrue(self.timeline_container.cut_action.props.enabled)
+ self.assertTrue(self.timeline_container.copy_action.props.enabled)
+ self.assertTrue(self.timeline_container.paste_action.props.enabled)
+
+ # Check status after cutting the clip.
+ self.timeline_container.cut_action.activate()
+
+ self.assertFalse(self.timeline_container.cut_action.props.enabled)
+ self.assertFalse(self.timeline_container.copy_action.props.enabled)
+ self.assertTrue(self.timeline_container.paste_action.props.enabled)
+
+ # Check status after pasting the clip.
+ position = 0
+ self.project.pipeline.get_position = mock.Mock(return_value=position)
+ self.timeline_container.paste_action.activate()
+
+ self.assertTrue(self.timeline_container.cut_action.props.enabled)
+ self.assertTrue(self.timeline_container.copy_action.props.enabled)
+ self.assertTrue(self.timeline_container.paste_action.props.enabled)
+
+ # Check status after deleting the clip.
+ self.timeline_container.delete_action.activate()
+
+ self.assertFalse(self.timeline_container.cut_action.props.enabled)
+ self.assertFalse(self.timeline_container.copy_action.props.enabled)
+ self.assertTrue(self.timeline_container.paste_action.props.enabled)
+
class TestEditing(common.TestCase):
@@ -906,10 +980,10 @@ class TestKeyboardShiftClips(common.TestCase):
self.click_clip(clip, expect_selected=True)
self.timeline_container.do_key_release_event(event)
- self.timeline_container.shift_forward_action.emit("activate", None)
+ self.timeline_container.shift_forward_action.activate()
self.assertListEqual([clip.start - delta for clip in ges_clips], clip_original_starts)
- self.timeline_container.shift_backward_action.emit("activate", None)
+ self.timeline_container.shift_backward_action.activate()
self.assertListEqual([clip.start for clip in ges_clips], clip_original_starts)
@common.setup_project(["tears_of_steel.webm"])
@@ -947,14 +1021,14 @@ class TestKeyboardShiftClips(common.TestCase):
self.click_clip(ges_clip3, expect_selected=True, ctrl_key=True)
self.click_clip(ges_clip4, expect_selected=True, ctrl_key=True)
- self.timeline_container.shift_forward_action.emit("activate", None)
+ self.timeline_container.shift_forward_action.activate()
self.assertEqual(5 * Gst.SECOND, ges_clip1.start)
self.assertEqual(10 * Gst.SECOND, ges_clip2.start)
self.assertEqual(13 * Gst.SECOND, ges_clip3.start)
self.assertEqual(15 * Gst.SECOND, ges_clip4.start)
- self.timeline_container.shift_backward_action.emit("activate", None)
+ self.timeline_container.shift_backward_action.activate()
self.assertEqual(5 * Gst.SECOND, ges_clip1.start)
self.assertEqual(10 * Gst.SECOND, ges_clip2.start)
@@ -972,10 +1046,10 @@ class TestSnapClips(common.TestCase):
self.click_clip(ges_clip1, expect_selected=True)
- self.timeline_container.snap_clips_forward_action.emit("activate", None)
+ self.timeline_container.snap_clips_forward_action.activate()
self.assertEqual(ges_clip1.start, ges_clip2.start - ges_clip1.duration)
- self.timeline_container.snap_clips_backward_action.emit("activate", None)
+ self.timeline_container.snap_clips_backward_action.activate()
self.assertEqual(ges_clip1.start, 0)
@common.setup_timeline
@@ -988,11 +1062,11 @@ class TestSnapClips(common.TestCase):
self.click_clip(ges_clip1, expect_selected=True, ctrl_key=True)
self.click_clip(ges_clip2, expect_selected=True, ctrl_key=True)
- self.timeline_container.snap_clips_forward_action.emit("activate", None)
+ self.timeline_container.snap_clips_forward_action.activate()
self.assertEqual(ges_clip2.start, ges_clip3.start - ges_clip2.duration)
self.assertEqual(ges_clip1.start, ges_clip2.start - ges_clip1.duration)
- self.timeline_container.snap_clips_backward_action.emit("activate", None)
+ self.timeline_container.snap_clips_backward_action.activate()
self.assertEqual(ges_clip1.start, 0)
self.assertEqual(ges_clip2.start, ges_clip1.start + ges_clip1.duration)
@@ -1008,10 +1082,10 @@ class TestSnapClips(common.TestCase):
self.click_clip(clip, expect_selected=True)
- self.timeline_container.snap_clips_forward_action.emit("activate", None)
+ self.timeline_container.snap_clips_forward_action.activate()
self.assertEqual(clip.start, end_clip.start - clip.duration)
- self.timeline_container.snap_clips_backward_action.emit("activate", None)
+ self.timeline_container.snap_clips_backward_action.activate()
self.assertEqual(clip.start, 0)
@common.setup_timeline
@@ -1022,7 +1096,7 @@ class TestSnapClips(common.TestCase):
self.click_clip(ges_clip, expect_selected=True)
- self.timeline_container.snap_clips_forward_action.emit("activate", None)
+ self.timeline_container.snap_clips_forward_action.activate()
self.assertEqual(ges_clip.start, clip_start)
@common.setup_timeline
@@ -1034,5 +1108,5 @@ class TestSnapClips(common.TestCase):
self.click_clip(ges_clip, expect_selected=True)
- self.timeline_container.snap_clips_forward_action.emit("activate", None)
+ self.timeline_container.snap_clips_forward_action.activate()
self.assertEqual(ges_clip.start, self.timeline.get_duration() - ges_clip.duration)
diff --git a/tests/test_undo_timeline.py b/tests/test_undo_timeline.py
index 5a68ead5e..4c3546a2d 100644
--- a/tests/test_undo_timeline.py
+++ b/tests/test_undo_timeline.py
@@ -379,7 +379,7 @@ class TestLayerObserver(common.TestCase):
clip = GES.TitleClip()
self.layer.add_clip(clip)
- self.timeline_container.add_layer_action.emit("activate", None)
+ self.timeline_container.add_layer_action.activate()
layers = self.timeline.get_layers()
self.assertEqual(len(layers), 2)
@@ -694,11 +694,11 @@ class TestLayerObserver(common.TestCase):
clip.ui.timeline._button_press_event_cb(None, event)
clip.ui._button_release_event_cb(None, event)
- self.timeline_container.copy_action.emit("activate", None)
+ self.timeline_container.copy_action.activate()
position = 10
project.pipeline.get_position = mock.Mock(return_value=position)
- self.timeline_container.paste_action.emit("activate", None)
+ self.timeline_container.paste_action.activate()
self.assertEqual(len(self.layer.get_clips()), 2)
self.action_log.undo()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]