timeline: Make get_groups public
authorAlexandru Băluț <alexandru.balut@gmail.com>
Tue, 6 Sep 2016 12:27:38 +0000 (14:27 +0200)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Tue, 13 Sep 2016 19:47:24 +0000 (16:47 -0300)
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 <thibault.saunier@collabora.com>
Differential Revision: https://phabricator.freedesktop.org/D1302

ges/ges-base-xml-formatter.c
ges/ges-group.c
ges/ges-internal.h
ges/ges-timeline.c
ges/ges-timeline.h
ges/ges-xml-formatter.c
tests/check/python/common.py [new file with mode: 0644]
tests/check/python/test_group.py
tests/check/python/test_timeline.py [new file with mode: 0644]

index 106aab1..63284ec 100644 (file)
@@ -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);
     }
   }
index b707eb0..a23d257 100644 (file)
@@ -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;
index 47eaffe..b635365 100644 (file)
@@ -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);
index 319e1e7..1efde96 100644 (file)
@@ -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)
@@ -2918,6 +2923,22 @@ ges_timeline_save_to_uri (GESTimeline * timeline, const gchar * uri,
 }
 
 /**
+ * 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
  *
index 5e0b68c..f7136db 100644 (file)
@@ -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);
 
index 2ac6c6a..0fb4335 100644 (file)
@@ -1293,7 +1293,7 @@ _save_groups (GESXmlFormatter * self, GString * str, GESTimeline * timeline)
   GList *seen_groups = NULL;
 
   g_string_append (str, "      <groups>\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, "      </groups>\n");
diff --git a/tests/check/python/common.py b/tests/check/python/common.py
new file mode 100644 (file)
index 0000000..e0e9d70
--- /dev/null
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Alexandru Băluț <alexandru.balut@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 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
+
index ad27444..87c097c 100644 (file)
@@ -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 (file)
index 0000000..9aad8d6
--- /dev/null
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2016 Alexandru Băluț <alexandru.balut@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.
+
+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()