From a63c754222a68958c8e90a8b0f6d0005f948275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20B=C4=83lu=C8=9B?= Date: Tue, 6 Sep 2016 14:27:38 +0200 Subject: [PATCH] timeline: Make get_groups public Had to separate timeline_emit_group_added from timeline_add_group to avoid emitting group-added when the project is being loaded. Reviewed-by: Thibault Saunier Differential Revision: https://phabricator.freedesktop.org/D1302 --- ges/ges-base-xml-formatter.c | 4 +- ges/ges-group.c | 7 ++- ges/ges-internal.h | 15 +++---- ges/ges-timeline.c | 37 ++++++++++++---- ges/ges-timeline.h | 2 + ges/ges-xml-formatter.c | 2 +- tests/check/python/common.py | 69 +++++++++++++++++++++++++++++ tests/check/python/test_group.py | 32 +++++++++++++ tests/check/python/test_timeline.py | 62 ++++++++++++++++++++++++++ 9 files changed, 210 insertions(+), 20 deletions(-) create mode 100644 tests/check/python/common.py create mode 100644 tests/check/python/test_timeline.py diff --git a/ges/ges-base-xml-formatter.c b/ges/ges-base-xml-formatter.c index 106aab1c58..63284ec058 100644 --- a/ges/ges-base-xml-formatter.c +++ b/ges/ges-base-xml-formatter.c @@ -451,6 +451,8 @@ _add_all_groups (GESFormatter * self) GList *lchild; PendingGroup *pgroup = tmp->data; + timeline_add_group (self->timeline, pgroup->group); + for (lchild = ((PendingGroup *) tmp->data)->pending_children; lchild; lchild = lchild->next) { child = g_hash_table_lookup (priv->containers, lchild->data); @@ -458,8 +460,6 @@ _add_all_groups (GESFormatter * self) GST_DEBUG_OBJECT (tmp->data, "Adding %s child %" GST_PTR_FORMAT " %s", (const gchar *) lchild->data, child, GES_TIMELINE_ELEMENT_NAME (child)); - ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (pgroup->group), - self->timeline); ges_container_add (GES_CONTAINER (pgroup->group), child); } } diff --git a/ges/ges-group.c b/ges/ges-group.c index b707eb06c0..a23d257c5a 100644 --- a/ges/ges-group.c +++ b/ges/ges-group.c @@ -442,6 +442,8 @@ _child_added (GESContainer * group, GESTimelineElement * child) if (!GES_TIMELINE_ELEMENT_TIMELINE (group)) { timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (child), GES_GROUP (group)); + timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE (child), + GES_GROUP (group)); } children = GES_CONTAINER_CHILDREN (group); @@ -618,9 +620,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref, paste_position); if (ngroup) { - if (GES_CONTAINER_CHILDREN (ngroup)) + if (GES_CONTAINER_CHILDREN (ngroup)) { timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN (ngroup)->data), GES_GROUP (element)); + timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE + (GES_CONTAINER_CHILDREN (ngroup)->data), GES_GROUP (element)); + } } return ngroup; diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 47eaffe469..b6353657c2 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -100,10 +100,15 @@ timeline_context_to_layer (GESTimeline *timeline, gint offset); G_GNUC_INTERNAL void timeline_add_group (GESTimeline *timeline, GESGroup *group); -G_GNUC_INTERNAL -void +G_GNUC_INTERNAL void timeline_remove_group (GESTimeline *timeline, GESGroup *group); +G_GNUC_INTERNAL void +timeline_emit_group_added (GESTimeline *timeline, + GESGroup *group); +G_GNUC_INTERNAL void +timeline_emit_group_removed (GESTimeline * timeline, + GESGroup * group, GPtrArray * array); G_GNUC_INTERNAL gboolean @@ -118,12 +123,6 @@ G_GNUC_INTERNAL void timeline_fill_gaps (GESTimeline *timeline); -G_GNUC_INTERNAL GList * -timeline_get_groups (GESTimeline * timeline); - -G_GNUC_INTERNAL void -timeline_emit_group_removed (GESTimeline * timeline, - GESGroup * group, GPtrArray * array); G_GNUC_INTERNAL void track_resort_and_fill_gaps (GESTrack *track); diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 319e1e7d6c..1efde965cf 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -550,7 +550,7 @@ ges_timeline_class_init (GESTimelineClass * klass) * @timeline: the #GESTimeline * @layer: the #GESLayer that was added to the timeline * - * Will be emitted after the layer was added to the timeline. + * Will be emitted after a new layer is added to the timeline. */ ges_timeline_signals[LAYER_ADDED] = g_signal_new ("layer-added", G_TYPE_FROM_CLASS (klass), @@ -574,7 +574,7 @@ ges_timeline_class_init (GESTimelineClass * klass) * @timeline: the #GESTimeline * @group: the #GESGroup * - * Will be emitted after a group has been added to to the timeline. + * Will be emitted after a new group is added to to the timeline. */ ges_timeline_signals[GROUP_ADDED] = g_signal_new ("group-added", G_TYPE_FROM_CLASS (klass), @@ -2185,7 +2185,18 @@ timeline_add_group (GESTimeline * timeline, GESGroup * group) gst_object_ref_sink (group)); ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline); +} +/** + * timeline_emit_group_added: + * @timeline: a #GESTimeline + * @group: group that was added + * + * Emit group-added signal. + */ +void +timeline_emit_group_added (GESTimeline * timeline, GESGroup * group) +{ g_signal_emit (timeline, ges_timeline_signals[GROUP_ADDED], 0, group); } @@ -2217,12 +2228,6 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group) gst_object_unref (group); } -GList * -timeline_get_groups (GESTimeline * timeline) -{ - return timeline->priv->groups; -} - static GPtrArray * select_tracks_for_object_default (GESTimeline * timeline, GESClip * clip, GESTrackElement * tr_object, gpointer user_data) @@ -2917,6 +2922,22 @@ ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri, return ret; } +/** + * ges_timeline_get_groups: + * @timeline: a #GESTimeline + * + * Get the list of #GESGroup present in the Timeline. + * + * Returns: (transfer none) (element-type GESGroup): the list of + * #GESGroup that contain clips present in the timeline's layers. + * Must not be changed. + */ +GList * +ges_timeline_get_groups (GESTimeline * timeline) +{ + return timeline->priv->groups; +} + /** * ges_timeline_append_layer: * @timeline: a #GESTimeline diff --git a/ges/ges-timeline.h b/ges/ges-timeline.h index 5e0b68c9ec..f7136db9b7 100644 --- a/ges/ges-timeline.h +++ b/ges/ges-timeline.h @@ -120,6 +120,8 @@ GESTrack * ges_timeline_get_track_for_pad (GESTimeline *timeline, GstPad *pad); GstPad * ges_timeline_get_pad_for_track (GESTimeline * timeline, GESTrack *track); GList *ges_timeline_get_tracks (GESTimeline *timeline); +GList* ges_timeline_get_groups (GESTimeline * timeline); + gboolean ges_timeline_commit (GESTimeline * timeline); gboolean ges_timeline_commit_sync (GESTimeline * timeline); diff --git a/ges/ges-xml-formatter.c b/ges/ges-xml-formatter.c index 2ac6c6a720..0fb4335d27 100644 --- a/ges/ges-xml-formatter.c +++ b/ges/ges-xml-formatter.c @@ -1293,7 +1293,7 @@ _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline) GList *seen_groups = NULL; g_string_append (str, " \n"); - for (tmp = timeline_get_groups (timeline); tmp; tmp = tmp->next) { + for (tmp = ges_timeline_get_groups (timeline); tmp; tmp = tmp->next) { _save_group (self, str, &seen_groups, tmp->data); } g_string_append (str, " \n"); diff --git a/tests/check/python/common.py b/tests/check/python/common.py new file mode 100644 index 0000000000..e0e9d70205 --- /dev/null +++ b/tests/check/python/common.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Alexandru Băluț +# +# 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 gi.repository import GES +from gi.repository import GLib +import tempfile + + +def create_main_loop(): + """Creates a MainLoop with a timeout.""" + mainloop = GLib.MainLoop() + timed_out = False + + def quit_cb(unused): + nonlocal timed_out + timed_out = True + mainloop.quit() + + def run(timeout_seconds=5): + source = GLib.timeout_source_new_seconds(timeout_seconds) + source.set_callback(quit_cb) + source.attach() + GLib.MainLoop.run(mainloop) + source.destroy() + if timed_out: + raise Exception("Timed out after %s seconds" % timeout_seconds) + + mainloop.run = run + return mainloop + +def create_project(with_group=False, saved=False): + """Creates a project with two clips in a group.""" + project = GES.Project() + timeline = project.extract() + layer = timeline.append_layer() + + if with_group: + clip1 = GES.TitleClip() + clip1.set_start(0) + clip1.set_duration(10) + layer.add_clip(clip1) + clip2 = GES.TitleClip() + clip2.set_start(100) + clip2.set_duration(10) + layer.add_clip(clip2) + group = GES.Container.group([clip1, clip2]) + + if saved: + uri = "file://%s" % tempfile.NamedTemporaryFile(suffix=".xges").name + project.save(timeline, uri, None, overwrite=True) + + return timeline + diff --git a/tests/check/python/test_group.py b/tests/check/python/test_group.py index ad27444dfb..87c097c717 100644 --- a/tests/check/python/test_group.py +++ b/tests/check/python/test_group.py @@ -27,6 +27,8 @@ from gi.repository import GES # noqa import unittest # noqa from unittest import mock +import common + Gst.init(None) GES.init() @@ -167,3 +169,33 @@ class TestGroup(unittest.TestCase): child_removed_cb.reset_mock() group.ungroup(recursive=False) child_removed_cb.assert_called_once_with(group, clip1) + + def test_loaded_project_has_groups(self): + mainloop = common.create_main_loop() + timeline = common.create_project(with_group=True, saved=True) + layer, = timeline.get_layers() + group, = timeline.get_groups() + self.assertEqual(len(layer.get_clips()), 2) + for clip in layer.get_clips(): + self.assertEqual(clip.get_parent(), group) + + # Reload the project, check the group. + project = GES.Project.new(uri=timeline.get_asset().props.uri) + + loaded_called = False + def loaded(unused_project, unused_timeline): + nonlocal loaded_called + loaded_called = True + mainloop.quit() + project.connect("loaded", loaded) + + timeline = project.extract() + + mainloop.run() + self.assertTrue(loaded_called) + + layer, = timeline.get_layers() + group, = timeline.get_groups() + self.assertEqual(len(layer.get_clips()), 2) + for clip in layer.get_clips(): + self.assertEqual(clip.get_parent(), group) diff --git a/tests/check/python/test_timeline.py b/tests/check/python/test_timeline.py new file mode 100644 index 0000000000..9aad8d608b --- /dev/null +++ b/tests/check/python/test_timeline.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 Alexandru Băluț +# +# 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. + +import gi + +gi.require_version("Gst", "1.0") +gi.require_version("GES", "1.0") + +from gi.repository import Gst # noqa +from gi.repository import GES # noqa +import unittest # noqa +from unittest import mock + +import common + +Gst.init(None) +GES.init() + + +class TestTimeline(unittest.TestCase): + + def test_signals_not_emitted_when_loading(self): + mainloop = common.create_main_loop() + timeline = common.create_project(with_group=True, saved=True) + + # Reload the project, check the group. + project = GES.Project.new(uri=timeline.get_asset().props.uri) + + loaded_called = False + def loaded(unused_project, unused_timeline): + nonlocal loaded_called + loaded_called = True + mainloop.quit() + project.connect("loaded", loaded) + + timeline = project.extract() + + signals = ["layer-added", "group-added", "track-added"] + called = [] + handle = mock.Mock() + for signal in signals: + timeline.connect(signal, handle) + + mainloop.run() + self.assertTrue(loaded_called) + handle.assert_not_called()