[pitivi] clipproperties: Consolidated the Title Tab into the Clip Tab



commit 5703200dedc40dcf132f04a1fa66a68d54e72f5b
Author: Thomas Braccia <Bracciata gmail com>
Date:   Tue Apr 21 15:38:14 2020 -0600

    clipproperties: Consolidated the Title Tab into the Clip Tab
    
    The consolidation puts all title clip functionality in the clip tab.
    
    The issue was that we were adding similiar clip types to title in the
    future and there would be too many tabs.
    
    The mockup was approved in issue
    https://gitlab.gnome.org/GNOME/pitivi/issues/2296

 data/ui/titleeditor.ui                             |  68 -------------
 .../{titleeditor.py => clip_properties/title.py}   | 100 +++++--------------
 pitivi/clipproperties.py                           | 108 +++++++++++++++++----
 pitivi/editorperspective.py                        |   6 +-
 pitivi/viewer/overlay.py                           |   4 +-
 tests/test_clipproperties.py                       |  44 +++++++++
 tests/test_editorperspective.py                    |   2 +-
 tests/test_titleeditor.py                          |  67 -------------
 8 files changed, 158 insertions(+), 241 deletions(-)
---
diff --git a/data/ui/titleeditor.ui b/data/ui/titleeditor.ui
index 694f20eb..297685bb 100644
--- a/data/ui/titleeditor.ui
+++ b/data/ui/titleeditor.ui
@@ -20,74 +20,6 @@
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="orientation">vertical</property>
-    <child>
-      <object class="GtkInfoBar" id="infobar">
-        <property name="visible">True</property>
-        <property name="app_paintable">True</property>
-        <property name="can_focus">False</property>
-        <property name="message_type">other</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="infobar-action_area1">
-            <property name="can_focus">False</property>
-            <property name="border_width">5</property>
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkButton" id="create">
-                <property name="label" translatable="yes">Create</property>
-                <property name="use_action_appearance">False</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="clicked" handler="_create_cb" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child internal-child="content_area">
-          <object class="GtkBox" id="infobar-content_area1">
-            <property name="can_focus">False</property>
-            <property name="border_width">8</property>
-            <child>
-              <object class="GtkLabel" id="info_bar_label2">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">Select a title clip to edit or create a new 
one.</property>
-                <property name="wrap">True</property>
-                <property name="width_chars">20</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <action-widgets>
-          <action-widget response="0">create</action-widget>
-        </action-widgets>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">0</property>
-      </packing>
-    </child>
     <child>
       <object class="GtkViewport" id="viewport1">
         <property name="visible">True</property>
diff --git a/pitivi/titleeditor.py b/pitivi/clip_properties/title.py
similarity index 76%
rename from pitivi/titleeditor.py
rename to pitivi/clip_properties/title.py
index 92c6a753..91031b7a 100644
--- a/pitivi/titleeditor.py
+++ b/pitivi/clip_properties/title.py
@@ -14,13 +14,12 @@
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
-import os
+"""Widgets to control title clip properties."""
 import html
+import os
 from gettext import gettext as _
 
 from gi.repository import GES
-from gi.repository import GLib
-from gi.repository import Gst
 from gi.repository import Gtk
 from gi.repository import Pango
 
@@ -28,65 +27,51 @@ from pitivi.configure import get_ui_dir
 from pitivi.dialogs.prefs import PreferencesDialog
 from pitivi.settings import GlobalSettings
 from pitivi.utils.loggable import Loggable
-from pitivi.utils.timeline import SELECT
 from pitivi.utils.ui import argb_to_gdk_rgba
-from pitivi.utils.ui import fix_infobar
 from pitivi.utils.ui import gdk_rgba_to_argb
 from pitivi.utils.widgets import ColorPickerButton
 
-GlobalSettings.add_config_option('titleClipLength',
+
+GlobalSettings.add_config_option("titleClipLength",
                                  section="user-interface",
                                  key="title-clip-length",
                                  default=5000,
                                  notify=True)
 
-PreferencesDialog.add_numeric_preference('titleClipLength',
+PreferencesDialog.add_numeric_preference("titleClipLength",
                                          section="timeline",
                                          label=_("Title clip duration"),
                                          description=_(
                                              "Default clip length (in milliseconds) of titles when inserting 
on the timeline."),
                                          lower=1)
 
-FOREGROUND_DEFAULT_COLOR = 0xFFFFFFFF  # White
-BACKGROUND_DEFAULT_COLOR = 0x00000000  # Transparent
-DEFAULT_FONT_DESCRIPTION = "Sans 36"
-DEFAULT_VALIGNMENT = "absolute"
-DEFAULT_HALIGNMENT = "absolute"
-
 
-class TitleEditor(Loggable):
+class TitleProperties(Gtk.Expander, Loggable):
     """Widget for configuring a title.
 
     Attributes:
         app (Pitivi): The app.
-        _project (Project): The project.
     """
 
     def __init__(self, app):
         Loggable.__init__(self)
+        Gtk.Expander.__init__(self)
+        self.set_label(_("Title"))
+        self.set_expanded(True)
         self.app = app
         self.settings = {}
         self.source = None
-        self._project = None
-        self._selection = None
-
         self._setting_props = False
         self._children_props_handler = None
 
         self._create_ui()
-        # Updates the UI.
-        self.set_source(None)
-
-        self.app.project_manager.connect_after(
-            "new-project-loaded", self._new_project_loaded_cb)
 
     def _create_ui(self):
         builder = Gtk.Builder()
         builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui"))
         builder.connect_signals(self)
-        self.widget = builder.get_object("box1")  # To be used by tabsmanager
-        self.infobar = builder.get_object("infobar")
-        fix_infobar(self.infobar)
+        # Create UI
+        self.add(builder.get_object("box1"))
         self.editing_box = builder.get_object("base_table")
 
         self.textarea = builder.get_object("textview")
@@ -126,6 +111,9 @@ class TitleEditor(Loggable):
                                ("right", _("Right"))):
             self.settings["halignment"].append(value_id, text)
 
+        self.show_all()
+        self.hide()
+
     def _set_child_property(self, name, value):
         with self.app.action_log.started("Title change property",
                                          toplevel=True):
@@ -166,11 +154,11 @@ class TitleEditor(Loggable):
 
     def _update_from_source(self, source):
         self.textbuffer.props.text = html.unescape(source.get_child_property("text")[1] or "")
-        self.settings['x-absolute'].set_value(source.get_child_property("x-absolute")[1])
-        self.settings['y-absolute'].set_value(source.get_child_property("y-absolute")[1])
-        self.settings['valignment'].set_active_id(
+        self.settings["x-absolute"].set_value(source.get_child_property("x-absolute")[1])
+        self.settings["y-absolute"].set_value(source.get_child_property("y-absolute")[1])
+        self.settings["valignment"].set_active_id(
             source.get_child_property("valignment")[1].value_name)
-        self.settings['halignment'].set_active_id(
+        self.settings["halignment"].set_active_id(
             source.get_child_property("halignment")[1].value_name)
         self._update_widgets_visibility()
 
@@ -233,38 +221,14 @@ class TitleEditor(Loggable):
             assert isinstance(source, (GES.TextOverlay, GES.TitleSource))
             self._update_from_source(source)
             self.source = source
-            self.infobar.hide()
-            self.editing_box.show()
-            self._children_props_handler = self.source.connect('deep-notify',
+            self._children_props_handler = self.source.connect("deep-notify",
                                                                self._source_deep_notify_cb)
-        else:
-            self.infobar.show()
-            self.editing_box.hide()
-
-    def _create_cb(self, unused_button):
-        title_clip = GES.TitleClip()
-        duration = self.app.settings.titleClipLength * Gst.MSECOND
-        title_clip.set_duration(duration)
-        with self.app.action_log.started("add title clip", toplevel=True):
-            self.app.gui.editor.timeline_ui.insert_clips_on_first_layer([title_clip])
-            # Now that the clip is inserted in the timeline, it has a source which
-            # can be used to set its properties.
-            source = title_clip.get_children(False)[0]
-            properties = {"text": "",
-                          "foreground-color": BACKGROUND_DEFAULT_COLOR,
-                          "color": FOREGROUND_DEFAULT_COLOR,
-                          "font-desc": DEFAULT_FONT_DESCRIPTION,
-                          "valignment": DEFAULT_VALIGNMENT,
-                          "halignment": DEFAULT_HALIGNMENT}
-            for prop, value in properties.items():
-                res = source.set_child_property(prop, value)
-                assert res, prop
-        self._selection.set_selection([title_clip], SELECT)
+        self.set_visible(bool(self.source))
 
     def _source_deep_notify_cb(self, source, unused_gstelement, pspec):
         """Handles updates in the TitleSource backing the current TitleClip."""
         if self._setting_props:
-            self._project.pipeline.commit_timeline()
+            self.app.project_manager.current_project.pipeline.commit_timeline()
             return
 
         control_binding = self.source.get_control_binding(pspec.name)
@@ -315,24 +279,4 @@ class TitleEditor(Loggable):
                 return
             self.background_color_button.set_rgba(color)
 
-        self._project.pipeline.commit_timeline()
-
-    def _new_project_loaded_cb(self, unused_project_manager, project):
-        if self._selection is not None:
-            self._selection.disconnect_by_func(self._selection_changed_cb)
-            self._selection = None
-        if project:
-            self._selection = project.ges_timeline.ui.selection
-            self._selection.connect('selection-changed', self._selection_changed_cb)
-        self._project = project
-
-    def _selection_changed_cb(self, selection):
-        selected_clip = selection.get_single_clip(GES.TitleClip)
-        source = None
-        if selected_clip:
-            for child in selected_clip.get_children(False):
-                if isinstance(child, GES.TitleSource):
-                    source = child
-                    break
-
-        self.set_source(source)
+        self.app.project_manager.current_project.pipeline.commit_timeline()
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index 38e0cbc6..d4452840 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -22,10 +22,12 @@ from gettext import gettext as _
 from gi.repository import Gdk
 from gi.repository import GES
 from gi.repository import Gio
+from gi.repository import Gst
 from gi.repository import GstController
 from gi.repository import Gtk
 from gi.repository import Pango
 
+from pitivi.clip_properties.title import TitleProperties
 from pitivi.configure import get_ui_dir
 from pitivi.effects import EffectsPropertiesManager
 from pitivi.effects import HIDDEN_EFFECTS
@@ -34,6 +36,7 @@ from pitivi.utils.custom_effect_widgets import setup_custom_effect_widgets
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.misc import disconnect_all_by_func
 from pitivi.utils.pipeline import PipelineError
+from pitivi.utils.timeline import SELECT
 from pitivi.utils.ui import disable_scroll
 from pitivi.utils.ui import EFFECT_TARGET_ENTRY
 from pitivi.utils.ui import fix_infobar
@@ -47,6 +50,12 @@ from pitivi.utils.ui import SPACING
  COL_DESC_TEXT,
  COL_TRACK_EFFECT) = list(range(6))
 
+FOREGROUND_DEFAULT_COLOR = 0xFFFFFFFF  # White
+BACKGROUND_DEFAULT_COLOR = 0x00000000  # Transparent
+DEFAULT_FONT_DESCRIPTION = "Sans 36"
+DEFAULT_VALIGNMENT = "absolute"
+DEFAULT_HALIGNMENT = "absolute"
+
 
 class ClipProperties(Gtk.ScrolledWindow, Loggable):
     """Widget for configuring the selected clip.
@@ -71,29 +80,96 @@ class ClipProperties(Gtk.ScrolledWindow, Loggable):
         vbox.show()
         viewport.add(vbox)
 
-        self.infobar_box = Gtk.Box()
-        self.infobar_box.set_orientation(Gtk.Orientation.VERTICAL)
-        self.infobar_box.show()
-        vbox.pack_start(self.infobar_box, False, False, 0)
+        self.clips_box = Gtk.Box()
+        self.clips_box.set_orientation(Gtk.Orientation.VERTICAL)
+        self.clips_box.show()
+        vbox.pack_start(self.clips_box, False, False, 0)
 
         transformation_expander = TransformationProperties(app)
         transformation_expander.set_vexpand(False)
         vbox.pack_start(transformation_expander, False, False, 0)
 
+        self.title_expander = TitleProperties(app)
+        self.title_expander.set_vexpand(False)
+        vbox.pack_start(self.title_expander, False, False, 0)
+
         self.effect_expander = EffectProperties(app, self)
         self.effect_expander.set_vexpand(False)
         vbox.pack_start(self.effect_expander, False, False, 0)
 
-    def create_info_bar(self, text):
-        """Creates an infobar to be displayed at the top."""
-        label = Gtk.Label(label=text)
+        self.helper_box = self.create_helper_box()
+        self.clips_box.pack_start(self.helper_box, False, False, 0)
+
+        self._project = None
+        self._selection = None
+        self.app.project_manager.connect_after(
+            "new-project-loaded", self.new_project_loaded_cb)
+        self.app.project_manager.connect_after(
+            "project-closed", self.__project_closed_cb)
+
+    def create_helper_box(self):
+        """Creates the widgets to display when no clip is selected."""
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        box.props.margin = PADDING
+
+        label = Gtk.Label(label=_("Select a clip on the timeline to configure its properties and effects or 
create a new clip:"))
         label.set_line_wrap(True)
-        infobar = Gtk.InfoBar()
-        fix_infobar(infobar)
-        infobar.props.message_type = Gtk.MessageType.OTHER
-        infobar.get_content_area().add(label)
-        self.infobar_box.pack_start(infobar, False, False, 0)
-        return infobar
+        label.set_xalign(0)
+        box.pack_start(label, False, False, SPACING)
+
+        title_button = Gtk.Button()
+        title_button.set_label(_("Create a title clip"))
+        title_button.connect("clicked", self.create_cb)
+        box.pack_start(title_button, False, False, SPACING)
+
+        box.show_all()
+        return box
+
+    def create_cb(self, unused_button):
+        title_clip = GES.TitleClip()
+        duration = self.app.settings.titleClipLength * Gst.MSECOND
+        title_clip.set_duration(duration)
+        with self.app.action_log.started("add title clip", toplevel=True):
+            self.app.gui.editor.timeline_ui.insert_clips_on_first_layer([
+                title_clip])
+            # Now that the clip is inserted in the timeline, it has a source which
+            # can be used to set its properties.
+            source = title_clip.get_children(False)[0]
+            properties = {"text": "",
+                          "foreground-color": BACKGROUND_DEFAULT_COLOR,
+                          "color": FOREGROUND_DEFAULT_COLOR,
+                          "font-desc": DEFAULT_FONT_DESCRIPTION,
+                          "valignment": DEFAULT_VALIGNMENT,
+                          "halignment": DEFAULT_HALIGNMENT}
+            for prop, value in properties.items():
+                res = source.set_child_property(prop, value)
+                assert res, prop
+        self._selection.set_selection([title_clip], SELECT)
+
+    def new_project_loaded_cb(self, unused_project_manager, project):
+        if self._selection is not None:
+            self._selection.disconnect_by_func(self._selection_changed_cb)
+            self._selection = None
+        if project:
+            self._selection = project.ges_timeline.ui.selection
+            self._selection.connect('selection-changed', self._selection_changed_cb)
+        self._project = project
+
+    def _selection_changed_cb(self, selection):
+        selected_clips = selection.selected
+        single_clip_selected = len(selected_clips) == 1
+        self.helper_box.set_visible(not single_clip_selected)
+
+        title_source = None
+        if single_clip_selected:
+            for child in list(selected_clips)[0].get_children(False):
+                if isinstance(child, GES.TitleSource):
+                    title_source = child
+                    break
+        self.title_expander.set_source(title_source)
+
+    def __project_closed_cb(self, unused_project_manager, unused_project):
+        self._project = None
 
 
 class EffectProperties(Gtk.Expander, Loggable):
@@ -215,10 +291,6 @@ class EffectProperties(Gtk.Expander, Loggable):
         self.treeview_selection = self.treeview.get_selection()
         self.treeview_selection.set_mode(Gtk.SelectionMode.SINGLE)
 
-        self._infobar = clip_properties.create_info_bar(
-            _("Select a clip on the timeline to configure its associated effects"))
-        self._infobar.show_all()
-
         # Prepare the main container widgets and lay out everything
         self._expander_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
         self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -480,7 +552,6 @@ class EffectProperties(Gtk.Expander, Loggable):
     def __update_all(self, path=None):
         if self.clip:
             self.show()
-            self._infobar.hide()
             self._update_treeview()
             if path:
                 self.treeview_selection.select_path(path)
@@ -488,7 +559,6 @@ class EffectProperties(Gtk.Expander, Loggable):
             self.hide()
             self.__remove_configuration_widget()
             self.storemodel.clear()
-            self._infobar.show()
 
     def _update_treeview(self):
         self.storemodel.clear()
diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py
index 8b921515..881b4867 100644
--- a/pitivi/editorperspective.py
+++ b/pitivi/editorperspective.py
@@ -38,7 +38,6 @@ from pitivi.settings import GlobalSettings
 from pitivi.tabsmanager import BaseTabs
 from pitivi.timeline.previewers import ThumbnailCache
 from pitivi.timeline.timeline import TimelineContainer
-from pitivi.titleeditor import TitleEditor
 from pitivi.transitions import TransitionsListWidget
 from pitivi.utils.loggable import Loggable
 from pitivi.utils.misc import path_from_uri
@@ -208,13 +207,10 @@ class EditorPerspective(Perspective, Loggable):
         self.context_tabs = BaseTabs(self.app)
         self.clipconfig = ClipProperties(self.app)
         self.trans_list = TransitionsListWidget(self.app)
-        self.title_editor = TitleEditor(self.app)
         self.context_tabs.append_page("Clip",
                                       self.clipconfig, Gtk.Label(label=_("Clip")))
         self.context_tabs.append_page("Transition",
                                       self.trans_list, Gtk.Label(label=_("Transition")))
-        self.context_tabs.append_page("Title",
-                                      self.title_editor.widget, Gtk.Label(label=_("Title")))
         # Show by default the Title tab, as the Clip and Transition tabs
         # are useful only when a clip or transition is selected, but
         # the Title tab allows adding titles.
@@ -282,7 +278,7 @@ class EditorPerspective(Perspective, Loggable):
             ges_clip (GES.SourceClip): The clip which has been focused.
         """
         if isinstance(ges_clip, GES.TitleClip):
-            page = 2
+            page = 0
         elif isinstance(ges_clip, GES.SourceClip):
             page = 0
         elif isinstance(ges_clip, GES.TransitionClip):
diff --git a/pitivi/viewer/overlay.py b/pitivi/viewer/overlay.py
index 1dd7142e..fbb79e6a 100644
--- a/pitivi/viewer/overlay.py
+++ b/pitivi/viewer/overlay.py
@@ -54,9 +54,7 @@ class Overlay(Gtk.DrawingArea, Loggable):
     def _select(self):
         self.stack.selected_overlay = self
         self.stack.app.gui.editor.timeline_ui.timeline.selection.set_selection([self._source], SELECT)
-        if isinstance(self._source, GES.TitleSource):
-            page = 2
-        elif isinstance(self._source, GES.VideoUriSource):
+        if isinstance(self._source, (GES.TitleSource, GES.VideoUriSource)):
             page = 0
         else:
             self.warning("Unknown clip type: %s", self._source)
diff --git a/tests/test_clipproperties.py b/tests/test_clipproperties.py
index 278ba785..203a1397 100644
--- a/tests/test_clipproperties.py
+++ b/tests/test_clipproperties.py
@@ -18,12 +18,15 @@
 # pylint: disable=protected-access,no-self-use
 from unittest import mock
 
+from gi.repository import GES
 from gi.repository import Gtk
 
+from pitivi.clipproperties import ClipProperties
 from pitivi.clipproperties import EffectProperties
 from pitivi.clipproperties import TransformationProperties
 from tests import common
 from tests.test_timeline_timeline import BaseTestTimeline
+from tests.test_undo_timeline import BaseTestUndoTimeline
 
 
 class EffectPropertiesTest(common.TestCase):
@@ -323,3 +326,44 @@ class TransformationPropertiesTest(BaseTestTimeline):
             ret, value = source.get_child_property(prop)
             self.assertTrue(ret)
             self.assertEqual(value, source.ui.default_position[prop])
+
+
+class ClipPropertiesTest(BaseTestUndoTimeline):
+    """Tests for the TitleProperties class."""
+
+    def _get_title_source_child_props(self):
+        clips = self.layer.get_clips()
+        self.assertEqual(len(clips), 1, clips)
+        self.assertIsInstance(clips[0], GES.TitleClip)
+        source, = clips[0].get_children(False)
+        return [source.get_child_property(p) for p in ("text",
+                                                       "x-absolute", "y-absolute",
+                                                       "valignment", "halignment",
+                                                       "font-desc",
+                                                       "color",
+                                                       "foreground-color")]
+
+    def test_create_title(self):
+        """Exercise creating a title clip."""
+        # Wait until the project creates a layer in the timeline.
+        common.create_main_loop().run(until_empty=True)
+
+        from pitivi.timeline.timeline import TimelineContainer
+        timeline_container = TimelineContainer(self.app)
+        timeline_container.set_project(self.project)
+        self.app.gui.editor.timeline_ui = timeline_container
+
+        clipproperties = ClipProperties(self.app)
+        clipproperties.new_project_loaded_cb(None, self.project)
+        self.project.pipeline.get_position = mock.Mock(return_value=0)
+
+        clipproperties.create_cb(None)
+        ps1 = self._get_title_source_child_props()
+
+        self.action_log.undo()
+        clips = self.layer.get_clips()
+        self.assertEqual(len(clips), 0, clips)
+
+        self.action_log.redo()
+        ps2 = self._get_title_source_child_props()
+        self.assertListEqual(ps1, ps2)
diff --git a/tests/test_editorperspective.py b/tests/test_editorperspective.py
index b33ad2e4..65a8a599 100644
--- a/tests/test_editorperspective.py
+++ b/tests/test_editorperspective.py
@@ -36,7 +36,7 @@ class TestEditorPerspective(common.TestCase):
         editorperspective = EditorPerspective(app)
         editorperspective.setup_ui()
         for expected_tab, b_element in [
-                (2, GES.TitleClip()),
+                (0, GES.TitleClip()),
                 (0, GES.SourceClip()),
                 (1, GES.TransitionClip())]:
             editorperspective.switch_context_tab(b_element)


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