[pitivi] editorperspective: Add UI intro



commit 9ec3e8e157ef3601f326063f2768256ef1410c20
Author: 1-2-ka-4-4-2-ka-1 <pratyushtiwarimj gmail com>
Date:   Sat Jan 4 17:15:24 2020 +0530

    editorperspective: Add UI intro
    
    First version shows an overview of the UI sections.
    
    Fixes #2361

 data/ui/mainmenubutton.ui   |  19 +++-
 pitivi/action_search_bar.py |  11 +-
 pitivi/editorperspective.py |   9 ++
 pitivi/interactiveintro.py  | 260 ++++++++++++++++++++++++++++++++++++++++++++
 pitivi/timeline/timeline.py |   6 +-
 5 files changed, 297 insertions(+), 8 deletions(-)
---
diff --git a/data/ui/mainmenubutton.ui b/data/ui/mainmenubutton.ui
index a4fcee34..73a4a029 100644
--- a/data/ui/mainmenubutton.ui
+++ b/data/ui/mainmenubutton.ui
@@ -185,6 +185,23 @@
             <property name="position">10</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkModelButton" id="menu_interactive_intro">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Start the intro to Pitivi for new 
users</property>
+            <property name="margin_top">1</property>
+            <property name="margin_bottom">1</property>
+            <property name="action_name">editor.interactive-intro</property>
+            <property name="text" translatable="yes">Interactive Intro</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">11</property>
+          </packing>
+        </child>
         <child>
           <object class="GtkModelButton" id="menu_about">
             <property name="visible">True</property>
@@ -198,7 +215,7 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
-            <property name="position">11</property>
+            <property name="position">12</property>
           </packing>
         </child>
       </object>
diff --git a/pitivi/action_search_bar.py b/pitivi/action_search_bar.py
index 1622125d..c81897b3 100644
--- a/pitivi/action_search_bar.py
+++ b/pitivi/action_search_bar.py
@@ -53,7 +53,7 @@ class ActionSearchBar(Gtk.Window):
         self.vbox.pack_start(self.results_window, True, True, 0)
 
     def setup_results_window(self):
-        self.list_model = Gtk.ListStore(str, int, Gdk.ModifierType, Gio.SimpleAction, object, bool)
+        self.list_model = Gtk.ListStore(str, int, Gdk.ModifierType, Gio.SimpleAction, object, bool, bool)
         self.model_filter = self.list_model.filter_new()
         disable_groups = ["medialibrary"]
 
@@ -63,14 +63,17 @@ class ActionSearchBar(Gtk.Window):
         for group in self.app.shortcuts.group_actions:
             if group not in disable_groups:
                 for action, title, action_object in self.app.shortcuts.group_actions[group]:
-                    accelerator_parsed = Gtk.accelerator_parse(self.app.get_accels_for_action(action)[0])
+                    accels = self.app.get_accels_for_action(action)
+                    accel = accels[0] if accels else ""
+                    accelerator_parsed = Gtk.accelerator_parse(accel)
                     disabled = not action_object.props.enabled
                     self.list_model.append([title,
                                             accelerator_parsed.accelerator_key,
                                             accelerator_parsed.accelerator_mods,
                                             action_object,
                                             title.lower().split(" "),
-                                            disabled])
+                                            disabled,
+                                            bool(accels)])
 
         self.model_filter.set_visible_func(self.filter_func)
         self.treeview = Gtk.TreeView.new_with_model(self.model_filter)
@@ -90,7 +93,7 @@ class ActionSearchBar(Gtk.Window):
         accel_renderer.props.accel_mode = Gtk.CellRendererAccelMode.OTHER
         accel_renderer.props.foreground_rgba = color_insensitive
         accel_renderer.props.foreground_set = True
-        shortcut_column = Gtk.TreeViewColumn("Shortcut", accel_renderer, accel_key=1, accel_mods=2)
+        shortcut_column = Gtk.TreeViewColumn("Shortcut", accel_renderer, accel_key=1, accel_mods=2, 
visible=6)
         self.treeview.append_column(shortcut_column)
 
         self.__select_row(self.model_filter.get_iter_first())
diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py
index bfbe0cea..8b921515 100644
--- a/pitivi/editorperspective.py
+++ b/pitivi/editorperspective.py
@@ -29,6 +29,7 @@ from pitivi.configure import APPNAME
 from pitivi.configure import get_ui_dir
 from pitivi.dialogs.missingasset import MissingAssetDialog
 from pitivi.effects import EffectListWidget
+from pitivi.interactiveintro import InteractiveIntro
 from pitivi.mediafilespreviewer import PreviewWidget
 from pitivi.medialibrary import MediaLibraryWidget
 from pitivi.perspective import Perspective
@@ -232,6 +233,9 @@ class EditorPerspective(Perspective, Loggable):
         self.timeline_ui = TimelineContainer(self.app)
         self.toplevel_widget.pack2(self.timeline_ui, resize=True, shrink=False)
 
+        self.intro = InteractiveIntro(self.app)
+        self.headerbar.pack_end(self.intro.intro_button)
+
         # Setup shortcuts for HeaderBar buttons and menu items.
         self._create_actions()
 
@@ -334,6 +338,7 @@ class EditorPerspective(Perspective, Loggable):
             os.path.join(get_ui_dir(), "mainmenubutton.ui"))
 
         self.menu_button = self.builder.get_object("menubutton")
+        self.keyboard_shortcuts_button = self.builder.get_object("menu_shortcuts")
 
         headerbar.pack_end(self.menu_button)
         headerbar.pack_end(self.save_button)
@@ -378,6 +383,10 @@ class EditorPerspective(Perspective, Loggable):
         self.project_settings_action.connect("activate", self.__project_settings_cb)
         group.add_action(self.project_settings_action)
 
+        group.add_action(self.intro.intro_action)
+        self.app.shortcuts.add("editor.interactive-intro", [], self.intro.intro_action,
+                               _("Quick intros to Pitivi"), group="win")
+
         self.import_asset_action = Gio.SimpleAction.new("import-asset", None)
         self.import_asset_action.connect("activate", self.__import_asset_cb)
         group.add_action(self.import_asset_action)
diff --git a/pitivi/interactiveintro.py b/pitivi/interactiveintro.py
new file mode 100644
index 00000000..ddb266b6
--- /dev/null
+++ b/pitivi/interactiveintro.py
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2020, Pratyush Tiwari <pratyushtiwarimj 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, see <http://www.gnu.org/licenses/>.
+from gettext import gettext as _
+
+from gi.repository import Gdk
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gtk
+
+
+INTERACTIVE_INTRO_CSS = """
+@keyframes intro-highlight {
+  from { box-shadow: 0px 0px 10px @theme_selected_bg_color, inset 0px 0px 4px  @theme_selected_bg_color; }
+  to   { box-shadow: 0px 0px 4px  @theme_selected_bg_color, inset 0px 0px 10px @theme_selected_bg_color; }
+}
+
+.intro-highlight {
+  animation: intro-highlight 1.5s infinite alternate;
+}
+
+
+#control-popover > box > button {
+  margin: 10px;
+}
+
+
+#intro-popover {
+  background-color: @theme_selected_bg_color;
+}
+
+#intro-popover > box {
+  margin: 10px;
+}
+
+#intro-popover > box > label {
+  font-size: 18pt;
+  font-weight: bold;
+  margin: 10px;
+  color: @theme_fg_color;
+}
+"""
+
+
+class InteractiveIntro(GObject.Object):
+    """Interactive GUI intro for newcomers.
+
+    Attributes:
+        intro_action (Gio.SimpleAction): The action for showing the control
+            popover.
+        intro_button (Gtk.Button): The button to be displayed in the headerbar
+            for showing the control popover.
+        widget (Gtk.Widget): The current widget being highlighted.
+    """
+
+    def __init__(self, app):
+        GObject.Object.__init__(self)
+        self.app = app
+        self.widget = None
+        self.parent_widget = None
+        self.tips = []
+        self._advance_handler_id = 0
+        self._hiding_popover_when_advancing = False
+
+        self.__setup_css()
+
+        self.intro_action = Gio.SimpleAction.new("interactive-intro", None)
+        self.intro_action.connect("activate", self._control_intro_cb)
+
+        self.intro_button = self.__create_intro_button()
+
+    def __setup_css(self):
+        css_provider = Gtk.CssProvider()
+        css_provider.load_from_data(INTERACTIVE_INTRO_CSS.encode("UTF-8"))
+        screen = Gdk.Screen.get_default()
+        style_context = self.app.gui.get_style_context()
+        style_context.add_provider_for_screen(screen, css_provider,
+                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+    def __create_intro_button(self):
+        """Creates a button for controlling the intro."""
+        button = Gtk.Button.new_from_icon_name(
+            "org.pitivi.Pitivi-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
+        button.set_always_show_image(True)
+        button.props.no_show_all = True
+        button.props.relief = Gtk.ReliefStyle.NONE
+        # We could set_action_name, but we don't know here the group
+        # in which the action will be added, so it's simpler this way.
+        button.connect("clicked", self._intro_button_clicked_cb)
+        return button
+
+    def __create_popover(self, text):
+        popover = Gtk.Popover()
+        popover.set_position(Gtk.PositionType.BOTTOM)
+        popover.set_modal(True)
+        popover.connect("closed", self._intro_popover_closed_cb)
+        popover.set_name("intro-popover")
+
+        label = Gtk.Label()
+        label.set_markup(text)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(24)
+
+        img = Gtk.Image.new_from_icon_name("dialog-information-symbolic", Gtk.IconSize.DIALOG)
+
+        vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+        vbox.pack_start(img, False, False, 5)
+        vbox.pack_end(label, False, False, 5)
+        vbox.show_all()
+
+        popover.add(vbox)
+
+        return popover
+
+    def _intro_popover_closed_cb(self, unused_widget):
+        if self._hiding_popover_when_advancing:
+            if self.parent_widget:
+                self.parent_widget.hide()
+                self.parent_widget = None
+            return
+
+        self.stop_tour()
+
+    def _control_intro_cb(self, unused_action, unused_param):
+        """Handles the activation of the interactive intro action."""
+        if self.running:
+            self.stop_tour()
+        else:
+            self.show_control_popover()
+
+    def _intro_button_clicked_cb(self, button):
+        self.intro_action.activate()
+
+    @property
+    def running(self):
+        return self._advance_handler_id != 0
+
+    def show_control_popover(self):
+        # pylint: disable=attribute-defined-outside-init
+        overview_button = Gtk.Button.new_with_label(_("Start Overview"))
+        overview_button.connect("clicked", self._overview_button_clicked_cb)
+
+        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        vbox.pack_start(overview_button, False, True, 10)
+
+        self.intro_button.show()
+        self.intro_button.get_style_context().add_class("intro-highlight")
+
+        self.control_popover = Gtk.Popover()
+        self.control_popover.set_name("control-popover")
+        self.control_popover.add(vbox)
+        self.control_popover.set_relative_to(self.intro_button)
+        self.control_popover.show_all()
+        self.control_popover.popup()
+        self.control_popover.connect("closed", self._control_popover_closed_cb)
+
+    def _control_popover_closed_cb(self, unused_widget):
+        self.intro_button.get_style_context().remove_class("intro-highlight")
+
+    def set_current_widget(self, widget):
+        """Switches the focus to a new widget, highlighting it."""
+        if self.widget:
+            self.widget.get_style_context().remove_class("intro-highlight")
+            self.widget = None
+
+        if widget:
+            self.widget = widget
+            self.widget.get_style_context().add_class("intro-highlight")
+
+            if isinstance(self.widget, Gtk.ModelButton):
+                self.parent_widget = self._find_parent(self.widget, Gtk.Popover)
+                if self.parent_widget:
+                    self.parent_widget.popup()
+
+    def _find_parent(self, widget, parent_class):
+        while widget:
+            if isinstance(widget, parent_class):
+                return widget
+
+            widget = widget.props.parent
+
+        return None
+
+    def stop_tour(self):
+        if self._advance_handler_id:
+            GLib.source_remove(self._advance_handler_id)
+            self._advance_handler_id = 0
+
+        self.set_current_widget(None)
+
+        self.intro_button.hide()
+
+    def _overview_button_clicked_cb(self, unused_widget):
+        """Starts the UI overview."""
+        self.control_popover.popdown()
+        editor = self.app.gui.editor
+        self.tips = [
+            (self.intro_button, _("Welcome to Pitivi!"),
+             Gtk.PositionType.BOTTOM, False, 4000),
+            (editor.medialibrary.iconview_scrollwin, _("Drag files here to import them"),
+             Gtk.PositionType.RIGHT, True, 5000),
+            (editor.timeline_ui.timeline, _("Drag clips in the timeline to arrange them"),
+             Gtk.PositionType.TOP, True, 7000),
+            (editor.timeline_ui.ruler, _("Right-click anywhere to move the playhead"),
+             Gtk.PositionType.TOP, True, 6000),
+            (editor.viewer.playpause_button, _("Preview with the play button"),
+             Gtk.PositionType.TOP, False, 5000),
+            (editor.render_button, _("Export your project to share it"),
+             Gtk.PositionType.BOTTOM, False, 5000),
+            (editor.keyboard_shortcuts_button, _("See all the keyboard shortcuts"),
+             Gtk.PositionType.LEFT, False, 6000)]
+
+        self.advance_popover_func(previous_popover=None)
+
+    def advance_popover_func(self, previous_popover):
+        """Moves the popover to the next widget."""
+        if previous_popover:
+            self._hiding_popover_when_advancing = True
+            try:
+                previous_popover.popdown()
+            finally:
+                self._hiding_popover_when_advancing = False
+
+        if not self.tips:
+            self.stop_tour()
+            return False
+
+        widget, text, position_type, center, timeout = self.tips.pop(0)
+        self.set_current_widget(widget)
+
+        popover = self.__create_popover(text)
+        popover.set_relative_to(widget)
+
+        if center:
+            rect = Gdk.Rectangle()
+            rect.x = widget.get_allocated_width() / 2
+            rect.y = widget.get_allocated_height() / 2
+            rect.width = 4
+            rect.height = 4
+            popover.set_pointing_to(rect)
+
+        popover.set_position(position_type)
+        popover.popup()
+
+        self._advance_handler_id = GLib.timeout_add(timeout, self.advance_popover_func, popover)
+        return False
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index 9c4cb512..f561dca9 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -1577,8 +1577,8 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
 
     def _create_ui(self):
         left_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
-        zoom_box = ZoomBox(self)
-        left_size_group.add_widget(zoom_box)
+        self.zoom_box = ZoomBox(self)
+        left_size_group.add_widget(self.zoom_box)
 
         self.timeline = Timeline(self.app, left_size_group)
 
@@ -1607,7 +1607,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         self.markers = MarkersBox(self.app, hadj=self.timeline.hadj)
 
         self.attach(self.markers, 1, 0, 1, 1)
-        self.attach(zoom_box, 0, 1, 1, 1)
+        self.attach(self.zoom_box, 0, 1, 1, 1)
         self.attach(self.ruler, 1, 1, 1, 1)
         self.attach(self.timeline, 0, 2, 2, 1)
         self.attach(self.vscrollbar, 2, 2, 1, 1)


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