[pitivi] Allow searching actions



commit 27ea4b08d4d5c1aad1623e55cf3c11f073e114f2
Author: Ayush Mittal <ayush mittal9398 gmail com>
Date:   Mon Dec 2 15:55:28 2019 +0530

    Allow searching actions
    
    Fixes #2315

 pitivi/action_search_bar.py     | 174 ++++++++++++++++++++++++++++++++++++++++
 pitivi/application.py           |   7 +-
 pitivi/dialogs/prefs.py         |   2 +-
 pitivi/editorperspective.py     |   4 +-
 pitivi/greeterperspective.py    |   2 +
 pitivi/mainwindow.py            |   6 +-
 pitivi/medialibrary.py          |   2 +
 pitivi/shortcuts.py             |  11 +--
 pitivi/timeline/timeline.py     |  42 +++++++++-
 plugins/console/console.py      |   2 +-
 tests/test_action_search_bar.py |  62 ++++++++++++++
 tests/test_shortcuts.py         |  32 ++++----
 12 files changed, 314 insertions(+), 32 deletions(-)
---
diff --git a/pitivi/action_search_bar.py b/pitivi/action_search_bar.py
new file mode 100644
index 00000000..1622125d
--- /dev/null
+++ b/pitivi/action_search_bar.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2019, Ayush Mittal <ayush mittal9398 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 Gtk
+
+from pitivi.utils.ui import clear_styles
+from pitivi.utils.ui import gtk_style_context_get_color
+from pitivi.utils.ui import PADDING
+
+
+class ActionSearchBar(Gtk.Window):
+
+    def __init__(self, app):
+        Gtk.Window.__init__(self)
+        self.app = app
+
+        self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+
+        self.set_size_request(600, 50)
+        self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=PADDING)
+        self.set_decorated(False)
+        self.add(self.vbox)
+
+        self.entry = Gtk.SearchEntry()
+        self.results_window = Gtk.ScrolledWindow()
+        self.results_window.props.can_focus = False
+        clear_styles(self.entry)
+        self.setup_results_window()
+        self.entry.props.placeholder_text = _("Search Action")
+
+        self.entry.connect("key-press-event", self._entry_key_press_event_cb)
+        self.entry.connect("changed", self._entry_changed_cb)
+        self.treeview.connect("row-activated", self._treeview_row_activated_cb)
+
+        self.vbox.pack_start(self.entry, True, True, 0)
+        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.model_filter = self.list_model.filter_new()
+        disable_groups = ["medialibrary"]
+
+        style_context = self.entry.get_style_context()
+        color_insensitive = gtk_style_context_get_color(style_context, Gtk.StateFlags.INSENSITIVE)
+
+        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])
+                    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])
+
+        self.model_filter.set_visible_func(self.filter_func)
+        self.treeview = Gtk.TreeView.new_with_model(self.model_filter)
+        self.treeview.props.headers_visible = False
+        self.treeview.props.enable_search = False
+        self.treeview.props.can_focus = False
+
+        text_renderer = Gtk.CellRendererText()
+        text_renderer.props.foreground_rgba = color_insensitive
+        description_column = Gtk.TreeViewColumn("Description", text_renderer, text=0, foreground_set=5)
+        description_column.set_fixed_width(400)
+        self.treeview.append_column(description_column)
+
+        accel_renderer = Gtk.CellRendererAccel()
+        # The default is Gtk.CellRendererAccelMode.GTK, but with that one
+        # accelerator "Left" appears as "Invalid" for some reason.
+        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)
+        self.treeview.append_column(shortcut_column)
+
+        self.__select_row(self.model_filter.get_iter_first())
+
+        self.results_window.add(self.treeview)
+        self.results_window.props.min_content_height = 300
+
+    def filter_func(self, model, row, data):
+        text = self.entry.get_text()
+        if text:
+            self.results_window.show()
+            all_found = True
+            for keyword in text.lower().split(" "):
+                found = False
+                for term in model[row][4]:
+                    if term.startswith(keyword):
+                        found = True
+                        break
+                if not found:
+                    all_found = False
+            return all_found
+        else:
+            return True
+
+    def do_focus_out_event(self, event):
+        self.destroy()
+        return True
+
+    def _treeview_row_activated_cb(self, treeview, path, col):
+        self.__activate_selected_action()
+
+    def __activate_selected_action(self):
+        model, row_iter = self.treeview.get_selection().get_selected()
+        if not row_iter:
+            # No row is selected, possibly because there are no rows.
+            return
+
+        action_object, = model.get(row_iter, 3)
+        action_object.activate()
+        self.destroy()
+
+    def _entry_changed_cb(self, entry):
+        self.model_filter.refilter()
+
+        # Make sure a row is always selected.
+        self.__select_row(self.model_filter.get_iter_first())
+
+    def __select_row(self, row_iter):
+        if not row_iter:
+            return
+
+        self.treeview.get_selection().select_iter(row_iter)
+
+        row_path = self.model_filter.get_path(row_iter)
+        self.treeview.scroll_to_cell(row_path, None, False, 0, 0)
+
+    def _entry_key_press_event_cb(self, entry, event):
+        if event.keyval == Gdk.KEY_Escape:
+            self.destroy()
+            return True
+
+        if event.keyval == Gdk.KEY_Return:
+            self.__activate_selected_action()
+            return True
+
+        if event.keyval == Gdk.KEY_Up:
+            selection = self.treeview.get_selection()
+            model, row_iter = selection.get_selected()
+            if row_iter:
+                self.__select_row(model.iter_previous(row_iter))
+                return True
+
+        if event.keyval == Gdk.KEY_Down:
+            selection = self.treeview.get_selection()
+            model, row_iter = selection.get_selected()
+            if row_iter:
+                self.__select_row(model.iter_next(row_iter))
+                return True
+
+        # Let the default handler process this event.
+        return False
diff --git a/pitivi/application.py b/pitivi/application.py
index 86079e4b..0f251456 100644
--- a/pitivi/application.py
+++ b/pitivi/application.py
@@ -161,25 +161,26 @@ class Pitivi(Gtk.Application, Loggable):
         self.undo_action = Gio.SimpleAction.new("undo", None)
         self.undo_action.connect("activate", self._undo_cb)
         self.add_action(self.undo_action)
-        self.shortcuts.add("app.undo", ["<Primary>z"],
+        self.shortcuts.add("app.undo", ["<Primary>z"], self.undo_action,
                            _("Undo the most recent action"))
 
         self.redo_action = Gio.SimpleAction.new("redo", None)
         self.redo_action.connect("activate", self._redo_cb)
         self.add_action(self.redo_action)
-        self.shortcuts.add("app.redo", ["<Primary><Shift>z"],
+        self.shortcuts.add("app.redo", ["<Primary><Shift>z"], self.redo_action,
                            _("Redo the most recent action"))
 
         self.quit_action = Gio.SimpleAction.new("quit", None)
         self.quit_action.connect("activate", self._quit_cb)
         self.add_action(self.quit_action)
-        self.shortcuts.add("app.quit", ["<Primary>q"], _("Quit"))
+        self.shortcuts.add("app.quit", ["<Primary>q"], self.quit_action, _("Quit"))
 
         self.show_shortcuts_action = Gio.SimpleAction.new("shortcuts_window", None)
         self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb)
         self.add_action(self.show_shortcuts_action)
         self.shortcuts.add("app.shortcuts_window",
                            ["<Primary>F1", "<Primary>question"],
+                           self.show_shortcuts_action,
                            _("Show the Shortcuts Window"))
 
     def do_activate(self):
diff --git a/pitivi/dialogs/prefs.py b/pitivi/dialogs/prefs.py
index 16f33621..d8e0c21a 100644
--- a/pitivi/dialogs/prefs.py
+++ b/pitivi/dialogs/prefs.py
@@ -393,7 +393,7 @@ class PreferencesDialog(Loggable):
         index = 0
         for group in shortcuts_manager.groups:
             actions = shortcuts_manager.group_actions[group]
-            for action, title in actions:
+            for action, title, _ in actions:
                 item = ModelItem(self.app, action, title, group)
                 self.list_store.append(item)
                 self.action_ids[action] = index
diff --git a/pitivi/editorperspective.py b/pitivi/editorperspective.py
index ac7e208f..bfbe0cea 100644
--- a/pitivi/editorperspective.py
+++ b/pitivi/editorperspective.py
@@ -351,7 +351,7 @@ class EditorPerspective(Perspective, Loggable):
         self.save_action = Gio.SimpleAction.new("save", None)
         self.save_action.connect("activate", self.__save_project_cb)
         group.add_action(self.save_action)
-        self.app.shortcuts.add("editor.save", ["<Primary>s"],
+        self.app.shortcuts.add("editor.save", ["<Primary>s"], self.save_action,
                                _("Save the current project"), group="win")
         self.save_button.set_action_name("editor.save")
 
@@ -359,6 +359,7 @@ class EditorPerspective(Perspective, Loggable):
         self.save_as_action.connect("activate", self.__save_project_as_cb)
         group.add_action(self.save_as_action)
         self.app.shortcuts.add("editor.save-as", ["<Primary><Shift>s"],
+                               self.save_as_action,
                                _("Save the current project as"), group="win")
 
         self.revert_to_saved_action = Gio.SimpleAction.new("revert-to-saved", None)
@@ -381,6 +382,7 @@ class EditorPerspective(Perspective, Loggable):
         self.import_asset_action.connect("activate", self.__import_asset_cb)
         group.add_action(self.import_asset_action)
         self.app.shortcuts.add("editor.import-asset", ["<Primary>i"],
+                               self.import_asset_action,
                                _("Add media files to your project"), group="win")
 
     def __import_asset_cb(self, unused_action, unused_param):
diff --git a/pitivi/greeterperspective.py b/pitivi/greeterperspective.py
index 2182446d..d7180ea9 100644
--- a/pitivi/greeterperspective.py
+++ b/pitivi/greeterperspective.py
@@ -275,12 +275,14 @@ class GreeterPerspective(Perspective):
         self.new_project_action.connect("activate", self.__new_project_cb)
         group.add_action(self.new_project_action)
         self.app.shortcuts.add("greeter.new-project", ["<Primary>n"],
+                               self.new_project_action,
                                _("Create a new project"), group="win")
 
         self.open_project_action = Gio.SimpleAction.new("open-project", None)
         self.open_project_action.connect("activate", self.__open_project_cb)
         group.add_action(self.open_project_action)
         self.app.shortcuts.add("greeter.open-project", ["<Primary>o"],
+                               self.open_project_action,
                                _("Open a project"), group="win")
 
     @staticmethod
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index 00ed0aaa..a18ca504 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -191,24 +191,28 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
         self.help_action = Gio.SimpleAction.new("help", None)
         self.help_action.connect("activate", self.__user_manual_cb)
         self.add_action(self.help_action)
-        self.app.shortcuts.add("win.help", ["F1"], _("Help"), group="app")
+        self.app.shortcuts.add("win.help", ["F1"], self.help_action,
+                               _("Help"), group="app")
 
         self.about_action = Gio.SimpleAction.new("about", None)
         self.about_action.connect("activate", self.__about_cb)
         self.add_action(self.about_action)
         self.app.shortcuts.add("win.about", ["<Primary><Shift>a"],
+                               self.about_action,
                                _("About"), group="app")
 
         self.main_menu_action = Gio.SimpleAction.new("menu-button", None)
         self.main_menu_action.connect("activate", self.__menu_cb)
         self.add_action(self.main_menu_action)
         self.app.shortcuts.add("win.menu-button", ["F10"],
+                               self.main_menu_action,
                                _("Show the menu button content"), group="app")
 
         self.preferences_action = Gio.SimpleAction.new("preferences", None)
         self.preferences_action.connect("activate", self.__preferences_cb)
         self.add_action(self.preferences_action)
         self.app.shortcuts.add("win.preferences", ["<Primary>comma"],
+                               self.preferences_action,
                                _("Preferences"), group="app")
 
     @staticmethod
diff --git a/pitivi/medialibrary.py b/pitivi/medialibrary.py
index 14034afd..6ff0081d 100644
--- a/pitivi/medialibrary.py
+++ b/pitivi/medialibrary.py
@@ -661,12 +661,14 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
         self.remove_assets_action.connect("activate", self._remove_assets_cb)
         actions_group.add_action(self.remove_assets_action)
         self.app.shortcuts.add("medialibrary.remove-assets", ["<Primary>Delete"],
+                               self.remove_assets_action,
                                _("Remove the selected assets"))
 
         self.insert_at_end_action = Gio.SimpleAction.new("insert-assets-at-end", None)
         self.insert_at_end_action.connect("activate", self._insert_end_cb)
         actions_group.add_action(self.insert_at_end_action)
         self.app.shortcuts.add("medialibrary.insert-assets-at-end", ["Insert"],
+                               self.insert_at_end_action,
                                _("Insert selected assets at the end of the timeline"))
 
         self._update_actions()
diff --git a/pitivi/shortcuts.py b/pitivi/shortcuts.py
index 34482e91..79a77c3b 100644
--- a/pitivi/shortcuts.py
+++ b/pitivi/shortcuts.py
@@ -69,11 +69,11 @@ class ShortcutsManager(GObject.Object):
         """
         with open(self.config_path, "w") as conf_file:
             for unused_group_id, actions in self.group_actions.items():
-                for action, unused_title in actions:
+                for action, unused_title, unused_action_object in actions:
                     accels = ",".join(self.app.get_accels_for_action(action))
                     conf_file.write(action + ":" + accels + "\n")
 
-    def add(self, action, accelerators, title, group=None):
+    def add(self, action, accelerators, action_object, title, group=None):
         """Adds an action to be displayed.
 
         Args:
@@ -83,6 +83,7 @@ class ShortcutsManager(GObject.Object):
                 the action. They are set as the accelerators of the action
                 only if no accelerators have been loaded from the config file
                 initially, when the current manager instance has been created.
+            action_object (Gio.SimpleAction): The object of the action.
             title (str): The title of the action.
             group (Optional[str]): The group id registered with `register_group`
                 to be used instead of that extracted from `action`.
@@ -93,7 +94,7 @@ class ShortcutsManager(GObject.Object):
             self.app.set_accels_for_action(action, accelerators)
 
         action_prefix = group or action.split(".")[0]
-        self.group_actions[action_prefix].append((action, title))
+        self.group_actions[action_prefix].append((action, title, action_object))
 
     def set(self, action, accelerators):
         """Sets accelerators for a shortcut.
@@ -136,7 +137,7 @@ class ShortcutsManager(GObject.Object):
         """
         group_name = action.split(".")[0]
         for group in {group_name, "app", "win"}:
-            for neighbor_action, unused_title in self.group_actions[group]:
+            for neighbor_action, unused_title, unused_action_object in self.group_actions[group]:
                 if neighbor_action == action:
                     continue
                 for accel in self.app.get_accels_for_action(neighbor_action):
@@ -202,7 +203,7 @@ class ShortcutsWindow(Gtk.ShortcutsWindow):
 
             group = Gtk.ShortcutsGroup(title=self.app.shortcuts.group_titles[group_id])
             group.show()
-            for action, title in actions:
+            for action, title, _ in actions:
                 # Show only the first accelerator which is the main one.
                 # Don't bother with the others, to keep the dialog pretty.
                 try:
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index d6e22b6b..9c4cb512 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -24,6 +24,7 @@ from gi.repository import GLib
 from gi.repository import Gst
 from gi.repository import Gtk
 
+from pitivi.action_search_bar import ActionSearchBar
 from pitivi.autoaligner import AlignmentProgressDialog
 from pitivi.autoaligner import AutoAligner
 from pitivi.configure import get_ui_dir
@@ -1649,59 +1650,75 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         self.toolbar.insert_action_group("timeline", group)
         self.app.shortcuts.register_group("timeline", _("Timeline"), position=30)
 
+        # Action Search Bar.
+        self.action_search = Gio.SimpleAction.new("action-search", None)
+        self.action_search.connect("activate", self.__action_search_cb)
+        group.add_action(self.action_search)
+        self.app.shortcuts.add("timeline.action-search", ["slash"],
+                               self.action_search, _("Action Search"))
+
         # Clips actions.
         self.delete_action = Gio.SimpleAction.new("delete-selected-clips", None)
         self.delete_action.connect("activate", self._delete_selected)
         group.add_action(self.delete_action)
         self.app.shortcuts.add("timeline.delete-selected-clips", ["Delete"],
+                               self.delete_action,
                                _("Delete selected clips"))
 
         self.delete_and_shift_action = Gio.SimpleAction.new("delete-selected-clips-and-shift", None)
         self.delete_and_shift_action.connect("activate", self._delete_selected_and_shift)
         group.add_action(self.delete_and_shift_action)
         self.app.shortcuts.add("timeline.delete-selected-clips-and-shift", ["<Shift>Delete"],
+                               self.delete_and_shift_action,
                                _("Delete selected clips and shift following ones"))
 
         self.group_action = Gio.SimpleAction.new("group-selected-clips", None)
         self.group_action.connect("activate", self._group_selected_cb)
         group.add_action(self.group_action)
         self.app.shortcuts.add("timeline.group-selected-clips", ["<Primary>g"],
+                               self.group_action,
                                _("Group selected clips together"))
 
         self.ungroup_action = Gio.SimpleAction.new("ungroup-selected-clips", None)
         self.ungroup_action.connect("activate", self._ungroup_selected_cb)
         group.add_action(self.ungroup_action)
         self.app.shortcuts.add("timeline.ungroup-selected-clips", ["<Primary><Shift>g"],
+                               self.ungroup_action,
                                _("Ungroup 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)
         self.app.shortcuts.add("timeline.copy-selected-clips", ["<Primary>c"],
+                               self.copy_action,
                                _("Copy selected clips"))
 
         self.paste_action = Gio.SimpleAction.new("paste-clips", None)
         self.paste_action.connect("activate", self.__paste_clips_cb)
         group.add_action(self.paste_action)
         self.app.shortcuts.add("timeline.paste-clips", ["<Primary>v"],
+                               self.paste_action,
                                _("Paste selected clips"))
 
         self.add_layer_action = Gio.SimpleAction.new("add-layer", None)
         self.add_layer_action.connect("activate", self.__add_layer_cb)
         group.add_action(self.add_layer_action)
         self.app.shortcuts.add("timeline.add-layer", ["<Primary>n"],
+                               self.add_layer_action,
                                _("Add layer"))
 
         self.seek_forward_clip_action = Gio.SimpleAction.new("seek-forward-clip", None)
         self.seek_forward_clip_action.connect("activate", self._seek_forward_clip_cb)
         group.add_action(self.seek_forward_clip_action)
         self.app.shortcuts.add("timeline.seek-forward-clip", ["<Primary>Right"],
+                               self.seek_forward_clip_action,
                                _("Seeks to the first clip edge after the playhead."))
 
         self.seek_backward_clip_action = Gio.SimpleAction.new("seek-backward-clip", None)
         self.seek_backward_clip_action.connect("activate", self._seek_backward_clip_cb)
         group.add_action(self.seek_backward_clip_action)
         self.app.shortcuts.add("timeline.seek-backward-clip", ["<Primary>Left"],
+                               self.seek_backward_clip_action,
                                _("Seeks to the first clip edge before the playhead."))
 
         if in_devel():
@@ -1714,13 +1731,14 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         self.split_action.connect("activate", self._split_cb)
         group.add_action(self.split_action)
         self.split_action.set_enabled(True)
-        self.app.shortcuts.add("timeline.split-clips", ["s"],
+        self.app.shortcuts.add("timeline.split-clips", ["s"], self.split_action,
                                _("Split the clip at the position"))
 
         self.keyframe_action = Gio.SimpleAction.new("keyframe-selected-clips", None)
         self.keyframe_action.connect("activate", self._keyframe_cb)
         group.add_action(self.keyframe_action)
         self.app.shortcuts.add("timeline.keyframe-selected-clips", ["k"],
+                               self.keyframe_action,
                                _("Add keyframe to the keyframe curve of selected clip"))
 
         navigation_group = Gio.SimpleActionGroup()
@@ -1733,35 +1751,43 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         navigation_group.add_action(self.zoom_in_action)
         self.app.shortcuts.add("navigation.zoom-in",
                                ["<Primary>plus", "<Primary>KP_Add", "<Primary>equal"],
+                               self.zoom_in_action,
                                _("Zoom in"))
 
         self.zoom_out_action = Gio.SimpleAction.new("zoom-out", None)
         self.zoom_out_action.connect("activate", self._zoom_out_cb)
         navigation_group.add_action(self.zoom_out_action)
-        self.app.shortcuts.add("navigation.zoom-out", ["<Primary>minus", "<Primary>KP_Subtract"],
+        self.app.shortcuts.add("navigation.zoom-out",
+                               ["<Primary>minus", "<Primary>KP_Subtract"],
+                               self.zoom_out_action,
                                _("Zoom out"))
 
         self.zoom_fit_action = Gio.SimpleAction.new("zoom-fit", None)
         self.zoom_fit_action.connect("activate", self._zoom_fit_cb)
         navigation_group.add_action(self.zoom_fit_action)
-        self.app.shortcuts.add("navigation.zoom-fit", ["<Primary>0", "<Primary>KP_0"],
+        self.app.shortcuts.add("navigation.zoom-fit",
+                               ["<Primary>0", "<Primary>KP_0"],
+                               self.zoom_fit_action,
                                _("Adjust zoom to fit the project to the window"))
 
         self.play_action = Gio.SimpleAction.new("play", None)
         self.play_action.connect("activate", self._play_pause_cb)
         navigation_group.add_action(self.play_action)
-        self.app.shortcuts.add("navigation.play", ["space"], _("Play"))
+        self.app.shortcuts.add("navigation.play", ["space"],
+                               self.play_action, _("Play"))
 
         self.backward_one_frame_action = Gio.SimpleAction.new("backward_one_frame", None)
         self.backward_one_frame_action.connect("activate", self._seek_backward_one_frame_cb)
         navigation_group.add_action(self.backward_one_frame_action)
         self.app.shortcuts.add("navigation.backward_one_frame", ["Left"],
+                               self.backward_one_frame_action,
                                _("Seek backward one frame"))
 
         self.forward_one_frame_action = Gio.SimpleAction.new("forward_one_frame", None)
         self.forward_one_frame_action.connect("activate", self._seek_forward_one_frame_cb)
         navigation_group.add_action(self.forward_one_frame_action)
         self.app.shortcuts.add("navigation.forward_one_frame", ["Right"],
+                               self.forward_one_frame_action,
                                _("Seek forward one frame"))
 
         self.backward_one_second_action = Gio.SimpleAction.new("backward_one_second", None)
@@ -1769,6 +1795,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         navigation_group.add_action(self.backward_one_second_action)
         self.app.shortcuts.add("navigation.backward_one_second",
                                ["<Shift>Left"],
+                               self.backward_one_second_action,
                                _("Seek backward one second"))
 
         self.forward_one_second_action = Gio.SimpleAction.new("forward_one_second", None)
@@ -1776,6 +1803,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
         navigation_group.add_action(self.forward_one_second_action)
         self.app.shortcuts.add("navigation.forward_one_second",
                                ["<Shift>Right"],
+                               self.forward_one_second_action,
                                _("Seek forward one second"))
 
         self.update_actions()
@@ -2114,3 +2142,9 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
     def _gaplessmode_toggled_cb(self, unused_action, unused_parameter):
         self._settings.timelineAutoRipple = self.gapless_button.get_active()
         self.info("Automatic ripple: %s", self._settings.timelineAutoRipple)
+
+    def __action_search_cb(self, unused_action, unused_param):
+        win = ActionSearchBar(self.app)
+        win.set_transient_for(self.app.gui)
+
+        win.show_all()
diff --git a/plugins/console/console.py b/plugins/console/console.py
index 5a175b7e..4f897bc3 100644
--- a/plugins/console/console.py
+++ b/plugins/console/console.py
@@ -143,7 +143,7 @@ class Console(GObject.GObject, Peas.Activatable):
         open_action = Gio.SimpleAction.new("open_console", None)
         open_action.connect("activate", self.__menu_item_activate_cb)
         self.app.add_action(open_action)
-        self.app.shortcuts.add("app.open_console", ["<Primary>d"], _("Developer Console"))
+        self.app.shortcuts.add("app.open_console", ["<Primary>d"], open_action, _("Developer Console"))
 
         self._setup_dialog()
         self.add_menu_item()
diff --git a/tests/test_action_search_bar.py b/tests/test_action_search_bar.py
new file mode 100644
index 00000000..f215d6cc
--- /dev/null
+++ b/tests/test_action_search_bar.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# Pitivi video editor
+# Copyright (c) 2019, Ayush Mittal <ayush mittal9398 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/>.
+"""Test for Action Search Bar."""
+from gi.repository import Gio
+
+from pitivi.action_search_bar import ActionSearchBar
+from pitivi.shortcuts import ShortcutsManager
+from tests import common
+
+
+class TestActionSearchBar(common.TestCase):
+    """Tests for ActionSearchBar."""
+
+    def test_action_search(self):
+        app = common.create_pitivi()
+        shortcut_manager = ShortcutsManager(app)
+
+        shortcut_manager.register_group("alpha_group", "One", position=10)
+        shortcut_manager.register_group("beta_group", "Two", position=20)
+
+        action = Gio.SimpleAction.new("remove-effect", None)
+        shortcut_manager.add("alpha_group.first", ["<Primary>A"], action, "First action")
+        shortcut_manager.add("alpha_group.second", ["<Primary>B"], action, "Second action")
+        shortcut_manager.add("beta_group.first", ["<Primary>C"], action, "First beta action")
+
+        app.shortcuts = shortcut_manager
+        action_search_bar = ActionSearchBar(app)
+
+        # When entry is empty initially.
+        all_vals = ["First action", "Second action", "First beta action"]
+        self.assertEqual(self.result_vals(action_search_bar), all_vals)
+
+        # When entry is "First".
+        self.assertEqual(self.result_vals(action_search_bar, "First action"), ["First action", "First beta 
action"])
+
+        # When entry is "Second".
+        self.assertEqual(self.result_vals(action_search_bar, "Second"), ["Second action"])
+
+    def result_vals(self, action_search_bar, entry=''):
+        action_search_bar.entry.set_text(entry)
+        result_actions = []
+        row_iter = action_search_bar.model_filter.get_iter_first()
+
+        while row_iter is not None:
+            result_actions.append(action_search_bar.model_filter.get_value(row_iter, 0))
+            row_iter = action_search_bar.model_filter.iter_next(row_iter)
+
+        return result_actions
diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py
index 4bfdbed9..158ced96 100644
--- a/tests/test_shortcuts.py
+++ b/tests/test_shortcuts.py
@@ -38,22 +38,22 @@ class TestShortcutsManager(common.TestCase):
         self.assertEqual(manager.groups, ["beta_group", "alpha_group"])
 
         # Test grouping using the stripping away group name from action name
-        manager.add("alpha_group.first", ["<Primary>A"], "First action")
-        self.assertIn(("alpha_group.first", "First action"),
+        manager.add("alpha_group.first", ["<Primary>A"], None, "First action")
+        self.assertIn(("alpha_group.first", "First action", None),
                       manager.group_actions["alpha_group"])
-        manager.add("alpha_group.second", ["<Primary>B"], "Second action")
-        self.assertIn(("alpha_group.second", "Second action"),
+        manager.add("alpha_group.second", ["<Primary>B"], None, "Second action")
+        self.assertIn(("alpha_group.second", "Second action", None),
                       manager.group_actions["alpha_group"])
-        manager.add("beta_group.first", ["<Primary>C"], "First beta action")
-        self.assertIn(("beta_group.first", "First beta action"),
+        manager.add("beta_group.first", ["<Primary>C"], None, "First beta action")
+        self.assertIn(("beta_group.first", "First beta action", None),
                       manager.group_actions["beta_group"])
 
         # Test grouping using the group optional argument
         # if group parameter is set, the action prefix can be anything,
         # it should be disregarded in favour of the group value.
-        manager.add("anything.third_action", ["<Primary>D"], "Third action",
+        manager.add("anything.third_action", ["<Primary>D"], None, "Third action",
                     group="beta_group")
-        self.assertIn(("anything.third_action", "Third action"),
+        self.assertIn(("anything.third_action", "Third action", None),
                       manager.group_actions["beta_group"])
 
     def test_add_shortcut(self):
@@ -66,7 +66,7 @@ class TestShortcutsManager(common.TestCase):
             # Test the add is calling set_accels_for_action(),
             # since there is no shortcuts.conf in the directory.
             manager.register_group("prefix", "General group", position=0)
-            manager.add("prefix.action1", ["<Primary>P"], "Action one")
+            manager.add("prefix.action1", ["<Primary>P"], None, "Action one")
             self.assertEqual(app.set_accels_for_action.call_count, 1)
             # Save the shortcut to the config file.
             manager.save()
@@ -79,11 +79,11 @@ class TestShortcutsManager(common.TestCase):
             manager2.register_group("prefix", "General group", position=0)
             manager2.register_group("other", "Other group", position=0)
             app.set_accels_for_action.reset_mock()
-            manager2.add("prefix.action1", ["<Primary>P"], "Action one")
+            manager2.add("prefix.action1", ["<Primary>P"], None, "Action one")
             # Above shortcut has been already loaded from the config file
             # and hence 'set_accels_for_action' is not called.
             self.assertEqual(app.set_accels_for_action.call_count, 0)
-            manager2.add("prefix.action2", ["<Primary>W"], "Action two")
+            manager2.add("prefix.action2", ["<Primary>W"], None, "Action two")
             self.assertEqual(app.set_accels_for_action.call_count, 1)
 
     def test_load_save(self):
@@ -98,9 +98,9 @@ class TestShortcutsManager(common.TestCase):
 
             # Set default shortcuts
             manager.register_group("group", "Test group", position=0)
-            manager.add("group.action1", ["<Primary>i"], "Action 1")
-            manager.add("group.action2", ["<Shift>p", "<Primary>m"], "Action 2")
-            manager.add("group.action3", ["<Primary><Shift>a", "a"], "Action 3")
+            manager.add("group.action1", ["<Primary>i"], None, "Action 1")
+            manager.add("group.action2", ["<Shift>p", "<Primary>m"], None, "Action 2")
+            manager.add("group.action3", ["<Primary><Shift>a", "a"], None, "Action 3")
 
             # After saving the shortcuts, the accels should be set when
             # initializing a ShortcutsManger.
@@ -127,8 +127,8 @@ class TestShortcutsManager(common.TestCase):
 
             # Set default shortcuts - they will be stored in self.defaults_accelerators.
             manager.register_group("group", "Test group", position=0)
-            manager.add("group.action1", ["<Primary>i"], "Action 1")
-            manager.add("group.action2", ["<Shift>p"], "Action 2")
+            manager.add("group.action1", ["<Primary>i"], None, "Action 1")
+            manager.add("group.action2", ["<Shift>p"], None, "Action 2")
 
             # Test reset of a single action. The shortcuts are saved and no file removed.
             # Only one call to set_accels_for_action() should be made.


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