[gnome-builder/wip/chergert/shortcut-editing: 2/2] plugins/shortcutui: start on tweaks for shortcut editing




commit e257335d9c7fd1275c3858fb6fc8fbb45935737a
Author: Christian Hergert <chergert redhat com>
Date:   Mon Oct 3 17:09:24 2022 -0700

    plugins/shortcutui: start on tweaks for shortcut editing

 src/plugins/meson.build                            |   1 +
 .../shortcutui/gbp-shortcutui-action-model.c       | 144 ++++++++++
 .../shortcutui/gbp-shortcutui-action-model.h       |  33 +++
 src/plugins/shortcutui/gbp-shortcutui-action.c     | 303 +++++++++++++++++++++
 src/plugins/shortcutui/gbp-shortcutui-action.h     |  38 +++
 src/plugins/shortcutui/gbp-shortcutui-dialog.c     | 157 +++++++++++
 src/plugins/shortcutui/gbp-shortcutui-dialog.h     |  31 +++
 src/plugins/shortcutui/gbp-shortcutui-dialog.ui    |  89 ++++++
 src/plugins/shortcutui/gbp-shortcutui-row.c        | 186 +++++++++++++
 src/plugins/shortcutui/gbp-shortcutui-row.h        |  34 +++
 src/plugins/shortcutui/gbp-shortcutui-row.ui       |   8 +
 .../shortcutui/gbp-shortcutui-tweaks-addin.c       | 125 +++++++++
 .../shortcutui/gbp-shortcutui-tweaks-addin.h       |  31 +++
 src/plugins/shortcutui/meson.build                 |  16 ++
 src/plugins/shortcutui/shortcutui-plugin.c         |  38 +++
 src/plugins/shortcutui/shortcutui.gresource.xml    |  10 +
 src/plugins/shortcutui/shortcutui.plugin           |   9 +
 src/plugins/shortcutui/style.css                   |   8 +
 src/plugins/shortcutui/tweaks.ui                   |  25 ++
 19 files changed, 1286 insertions(+)
---
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index deb2d81fe..058675856 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -117,6 +117,7 @@ subdir('rust-analyzer')
 subdir('sdkui')
 subdir('sessionui')
 subdir('shellcmd')
+subdir('shortcutui')
 subdir('snippets')
 subdir('spellcheck')
 subdir('sphinx-preview')
diff --git a/src/plugins/shortcutui/gbp-shortcutui-action-model.c 
b/src/plugins/shortcutui/gbp-shortcutui-action-model.c
new file mode 100644
index 000000000..072fd7816
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-action-model.c
@@ -0,0 +1,144 @@
+/* gbp-shortcutui-action-model.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shortcutui-action-model"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "ide-application-private.h"
+
+#include "gbp-shortcutui-action.h"
+#include "gbp-shortcutui-action-model.h"
+
+struct _GbpShortcutuiActionModel
+{
+  GObject    parent_instance;
+  GPtrArray *items;
+};
+
+static guint
+gbp_shortcutui_action_model_get_n_items (GListModel *model)
+{
+  return GBP_SHORTCUTUI_ACTION_MODEL (model)->items->len;
+}
+
+static GType
+gbp_shortcutui_action_model_get_item_type (GListModel *model)
+{
+  return GBP_TYPE_SHORTCUTUI_ACTION;
+}
+
+static gpointer
+gbp_shortcutui_action_model_get_item (GListModel *model,
+                                      guint       position)
+{
+  GPtrArray *items = GBP_SHORTCUTUI_ACTION_MODEL (model)->items;
+
+  if (position < items->len)
+    return g_object_ref (g_ptr_array_index (items, position));
+
+  return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = gbp_shortcutui_action_model_get_n_items;
+  iface->get_item = gbp_shortcutui_action_model_get_item;
+  iface->get_item_type = gbp_shortcutui_action_model_get_item_type;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpShortcutuiActionModel, gbp_shortcutui_action_model, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static int
+sort_actions (gconstpointer a,
+              gconstpointer b)
+{
+  const GbpShortcutuiAction * const *aptr = a;
+  const GbpShortcutuiAction * const *bptr = b;
+
+  return gbp_shortcutui_action_compare (*aptr, *bptr);
+}
+
+static void
+populate_shortcut_info_cb (const IdeShortcutInfo *info,
+                           gpointer               user_data)
+{
+  GPtrArray *items = user_data;
+
+  g_assert (info != NULL);
+  g_assert (items != NULL);
+
+  g_ptr_array_add (items,
+                   g_object_new (GBP_TYPE_SHORTCUTUI_ACTION,
+                                 "action-name", ide_shortcut_info_get_action_name (info),
+                                 "action-target", ide_shortcut_info_get_action_target (info),
+                                 "title", ide_shortcut_info_get_title (info),
+                                 "subtitle", ide_shortcut_info_get_subtitle (info),
+                                 "page", ide_shortcut_info_get_page (info),
+                                 "group", ide_shortcut_info_get_group (info),
+                                 NULL));
+}
+
+static void
+gbp_shortcutui_action_model_constructed (GObject *object)
+{
+  GbpShortcutuiActionModel *self = (GbpShortcutuiActionModel *)object;
+
+  G_OBJECT_CLASS (gbp_shortcutui_action_model_parent_class)->constructed (object);
+
+  ide_shortcut_info_foreach (populate_shortcut_info_cb, self->items);
+
+  g_ptr_array_sort (self->items, sort_actions);
+}
+
+static void
+gbp_shortcutui_action_model_dispose (GObject *object)
+{
+  GbpShortcutuiActionModel *self = (GbpShortcutuiActionModel *)object;
+
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gbp_shortcutui_action_model_parent_class)->dispose (object);
+}
+
+static void
+gbp_shortcutui_action_model_class_init (GbpShortcutuiActionModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gbp_shortcutui_action_model_constructed;
+  object_class->dispose = gbp_shortcutui_action_model_dispose;
+}
+
+static void
+gbp_shortcutui_action_model_init (GbpShortcutuiActionModel *self)
+{
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+GListModel *
+gbp_shortcutui_action_model_new (void)
+{
+  return g_object_new (GBP_TYPE_SHORTCUTUI_ACTION_MODEL, NULL);
+}
diff --git a/src/plugins/shortcutui/gbp-shortcutui-action-model.h 
b/src/plugins/shortcutui/gbp-shortcutui-action-model.h
new file mode 100644
index 000000000..91f004869
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-action-model.h
@@ -0,0 +1,33 @@
+/* gbp-shortcutui-action-model.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHORTCUTUI_ACTION_MODEL (gbp_shortcutui_action_model_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShortcutuiActionModel, gbp_shortcutui_action_model, GBP, SHORTCUTUI_ACTION_MODEL, 
GObject)
+
+GListModel *gbp_shortcutui_action_model_new (void);
+
+G_END_DECLS
diff --git a/src/plugins/shortcutui/gbp-shortcutui-action.c b/src/plugins/shortcutui/gbp-shortcutui-action.c
new file mode 100644
index 000000000..89d3dfc87
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-action.c
@@ -0,0 +1,303 @@
+/* gbp-shortcutui-action.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shortcutui-action"
+
+#include "config.h"
+
+#include "gbp-shortcutui-action.h"
+
+struct _GbpShortcutuiAction
+{
+  GObject parent_instance;
+
+  char *accelerator;
+  char *action_name;
+  char *group;
+  char *page;
+  char *search_text;
+  char *subtitle;
+  char *title;
+
+  GVariant *action_target;
+};
+
+enum {
+  PROP_0,
+  PROP_ACCELERATOR,
+  PROP_ACTION_NAME,
+  PROP_ACTION_TARGET,
+  PROP_SUBTITLE,
+  PROP_TITLE,
+  PROP_GROUP,
+  PROP_PAGE,
+  PROP_SEARCH_TEXT,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GbpShortcutuiAction, gbp_shortcutui_action, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static const char *
+gbp_shortcutui_action_get_search_text (GbpShortcutuiAction *self)
+{
+  g_assert (GBP_IS_SHORTCUTUI_ACTION (self));
+
+  if (self->search_text == NULL)
+    {
+      GString *str = g_string_new (NULL);
+
+      if (self->page != NULL)
+        g_string_append_printf (str, "%s ", self->page);
+
+      if (self->group != NULL)
+        g_string_append_printf (str, "%s ", self->group);
+
+      if (self->title != NULL)
+        g_string_append_printf (str, "%s ", self->title);
+
+      if (self->subtitle != NULL)
+        g_string_append_printf (str, "%s ", self->subtitle);
+
+      self->search_text = g_string_free (str, FALSE);
+    }
+
+  return self->search_text;
+}
+
+static void
+gbp_shortcutui_action_dispose (GObject *object)
+{
+  GbpShortcutuiAction *self = (GbpShortcutuiAction *)object;
+
+  g_clear_pointer (&self->accelerator, g_free);
+  g_clear_pointer (&self->action_name, g_free);
+  g_clear_pointer (&self->action_target, g_variant_unref);
+  g_clear_pointer (&self->group, g_free);
+  g_clear_pointer (&self->page, g_free);
+  g_clear_pointer (&self->subtitle, g_free);
+  g_clear_pointer (&self->search_text, g_free);
+  g_clear_pointer (&self->title, g_free);
+
+  G_OBJECT_CLASS (gbp_shortcutui_action_parent_class)->dispose (object);
+}
+
+static void
+gbp_shortcutui_action_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GbpShortcutuiAction *self = GBP_SHORTCUTUI_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCELERATOR:
+      g_value_set_string (value, self->accelerator);
+      break;
+
+    case PROP_ACTION_NAME:
+      g_value_set_string (value, self->action_name);
+      break;
+
+    case PROP_ACTION_TARGET:
+      g_value_set_variant (value, self->action_target);
+      break;
+
+    case PROP_SUBTITLE:
+      g_value_set_string (value, self->subtitle);
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, self->title);
+      break;
+
+    case PROP_GROUP:
+      g_value_set_string (value, self->group);
+      break;
+
+    case PROP_PAGE:
+      g_value_set_string (value, self->page);
+      break;
+
+    case PROP_SEARCH_TEXT:
+      g_value_set_string (value, gbp_shortcutui_action_get_search_text (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shortcutui_action_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GbpShortcutuiAction *self = GBP_SHORTCUTUI_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACCELERATOR:
+      self->accelerator = g_value_dup_string (value);
+      break;
+
+    case PROP_ACTION_NAME:
+      self->action_name = g_value_dup_string (value);
+      break;
+
+    case PROP_ACTION_TARGET:
+      self->action_target = g_value_dup_variant (value);
+      break;
+
+    case PROP_SUBTITLE:
+      self->subtitle = g_value_dup_string (value);
+      break;
+
+    case PROP_TITLE:
+      self->title = g_value_dup_string (value);
+      break;
+
+    case PROP_GROUP:
+      self->group = g_value_dup_string (value);
+      break;
+
+    case PROP_PAGE:
+      self->page = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shortcutui_action_class_init (GbpShortcutuiActionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_shortcutui_action_dispose;
+  object_class->get_property = gbp_shortcutui_action_get_property;
+  object_class->set_property = gbp_shortcutui_action_set_property;
+
+  properties [PROP_ACCELERATOR] =
+    g_param_spec_string ("accelerator", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ACTION_NAME] =
+    g_param_spec_string ("action-name", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ACTION_TARGET] =
+    g_param_spec_variant ("action-target", NULL, NULL,
+                          G_VARIANT_TYPE_ANY,
+                          NULL,
+                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SUBTITLE] =
+    g_param_spec_string ("subtitle", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_GROUP] =
+    g_param_spec_string ("group", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PAGE] =
+    g_param_spec_string ("page", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SEARCH_TEXT] =
+    g_param_spec_string ("search-text", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_shortcutui_action_init (GbpShortcutuiAction *self)
+{
+}
+
+static inline int
+utf8_collate0 (const char *a,
+               const char *b)
+{
+  if (a == b)
+    return 0;
+
+  if (a == NULL)
+    return 1;
+
+  if (b == NULL)
+    return -1;
+
+  return g_utf8_collate (a, b);
+}
+
+int
+gbp_shortcutui_action_compare (const GbpShortcutuiAction *a,
+                               const GbpShortcutuiAction *b)
+{
+  int ret;
+
+  if ((ret = utf8_collate0 (a->page, b->page)))
+    return ret;
+
+  if ((ret = utf8_collate0 (a->group, b->group)))
+    return ret;
+
+  if ((ret = utf8_collate0 (a->title, b->title)))
+    return ret;
+
+  return 0;
+}
+
+gboolean
+gbp_shortcutui_action_is_same_group (const GbpShortcutuiAction *a,
+                                     const GbpShortcutuiAction *b)
+{
+  return utf8_collate0 (a->page, b->page) == 0 &&
+         utf8_collate0 (a->group, b->group) == 0;
+}
+
+const char *
+gbp_shortcutui_action_get_page (const GbpShortcutuiAction *self)
+{
+  return self->page;
+}
+
+const char *
+gbp_shortcutui_action_get_group (const GbpShortcutuiAction *self)
+{
+  return self->group;
+}
diff --git a/src/plugins/shortcutui/gbp-shortcutui-action.h b/src/plugins/shortcutui/gbp-shortcutui-action.h
new file mode 100644
index 000000000..9f3e08f8e
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-action.h
@@ -0,0 +1,38 @@
+/* gbp-shortcutui-action.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHORTCUTUI_ACTION (gbp_shortcutui_action_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShortcutuiAction, gbp_shortcutui_action, GBP, SHORTCUTUI_ACTION, GObject)
+
+const char *gbp_shortcutui_action_get_page      (const GbpShortcutuiAction *self);
+const char *gbp_shortcutui_action_get_group     (const GbpShortcutuiAction *self);
+int         gbp_shortcutui_action_compare       (const GbpShortcutuiAction *a,
+                                                 const GbpShortcutuiAction *b);
+gboolean    gbp_shortcutui_action_is_same_group (const GbpShortcutuiAction *a,
+                                                 const GbpShortcutuiAction *b);
+
+G_END_DECLS
diff --git a/src/plugins/shortcutui/gbp-shortcutui-dialog.c b/src/plugins/shortcutui/gbp-shortcutui-dialog.c
new file mode 100644
index 000000000..e3fe162b3
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-dialog.c
@@ -0,0 +1,157 @@
+/* gbp-shortcutui-dialog.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shortcutui-dialog"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "gbp-shortcutui-action.h"
+#include "gbp-shortcutui-action-model.h"
+#include "gbp-shortcutui-dialog.h"
+#include "gbp-shortcutui-row.h"
+
+struct _GbpShortcutuiDialog
+{
+  GtkWindow           parent_instance;
+  GtkSearchEntry     *search;
+  GtkListBox         *results_list_box;
+  AdwPreferencesPage *overview;
+  AdwPreferencesPage *results;
+  AdwPreferencesPage *empty;
+  GtkStringFilter    *string_filter;
+  GtkFilterListModel *filter_model;
+  guint               update_source;
+};
+
+G_DEFINE_FINAL_TYPE (GbpShortcutuiDialog, gbp_shortcutui_dialog, GTK_TYPE_WINDOW)
+
+static void
+gbp_shortcutui_dialog_update_header_cb (GtkListBoxRow *row,
+                                        GtkListBoxRow *before,
+                                        gpointer       user_data)
+{
+  g_assert (GBP_IS_SHORTCUTUI_ROW (row));
+  g_assert (!before || GBP_IS_SHORTCUTUI_ROW (before));
+
+  gbp_shortcutui_row_update_header (GBP_SHORTCUTUI_ROW (row),
+                                    GBP_SHORTCUTUI_ROW (before));
+}
+
+static GtkWidget *
+gbp_shortcutui_dialog_create_row_cb (gpointer item,
+                                     gpointer user_data)
+{
+  GbpShortcutuiAction *action = item;
+
+  g_assert (GBP_IS_SHORTCUTUI_ACTION (action));
+
+  return g_object_new (GBP_TYPE_SHORTCUTUI_ROW,
+                       "action", action,
+                       NULL);
+}
+
+static gboolean
+gbp_shortcutui_dialog_update_visible (gpointer user_data)
+{
+  GbpShortcutuiDialog *self = user_data;
+  const char *text;
+  guint n_items;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_SHORTCUTUI_DIALOG (self));
+
+  self->update_source = 0;
+
+  text = gtk_editable_get_text (GTK_EDITABLE (self->search));
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->filter_model));
+
+  if (ide_str_empty0 (text))
+    {
+      gtk_widget_show (GTK_WIDGET (self->overview));
+      gtk_widget_hide (GTK_WIDGET (self->results));
+      gtk_widget_hide (GTK_WIDGET (self->empty));
+    }
+  else
+    {
+      gboolean has_results = n_items > 0;
+
+      gtk_widget_hide (GTK_WIDGET (self->overview));
+      gtk_widget_set_visible (GTK_WIDGET (self->results), has_results);
+      gtk_widget_set_visible (GTK_WIDGET (self->empty), !has_results);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gbp_shortcutui_dialog_queue_update (GbpShortcutuiDialog *self)
+{
+  g_assert (GBP_IS_SHORTCUTUI_DIALOG (self));
+
+  if (self->update_source == 0)
+    self->update_source = g_timeout_add (250, gbp_shortcutui_dialog_update_visible, self);
+}
+
+static void
+gbp_shortcutui_dialog_dispose (GObject *object)
+{
+  GbpShortcutuiDialog *self = (GbpShortcutuiDialog *)object;
+
+  g_clear_handle_id (&self->update_source, g_source_remove);
+
+  G_OBJECT_CLASS (gbp_shortcutui_dialog_parent_class)->dispose (object);
+}
+
+static void
+gbp_shortcutui_dialog_class_init (GbpShortcutuiDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_shortcutui_dialog_dispose;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/shortcutui/gbp-shortcutui-dialog.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, empty);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, filter_model);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, overview);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, results);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, results_list_box);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, search);
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiDialog, string_filter);
+  gtk_widget_class_bind_template_callback (widget_class, gbp_shortcutui_dialog_queue_update);
+
+  g_type_ensure (GBP_TYPE_SHORTCUTUI_ACTION_MODEL);
+}
+
+static void
+gbp_shortcutui_dialog_init (GbpShortcutuiDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_list_box_set_header_func (self->results_list_box,
+                                gbp_shortcutui_dialog_update_header_cb,
+                                NULL, NULL);
+  gtk_list_box_bind_model (self->results_list_box,
+                           G_LIST_MODEL (self->filter_model),
+                           gbp_shortcutui_dialog_create_row_cb,
+                           NULL, NULL);
+}
diff --git a/src/plugins/shortcutui/gbp-shortcutui-dialog.h b/src/plugins/shortcutui/gbp-shortcutui-dialog.h
new file mode 100644
index 000000000..ff5864b52
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-dialog.h
@@ -0,0 +1,31 @@
+/* gbp-shortcutui-dialog.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHORTCUTUI_DIALOG (gbp_shortcutui_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShortcutuiDialog, gbp_shortcutui_dialog, GBP, SHORTCUTUI_DIALOG, GtkWindow)
+
+G_END_DECLS
diff --git a/src/plugins/shortcutui/gbp-shortcutui-dialog.ui b/src/plugins/shortcutui/gbp-shortcutui-dialog.ui
new file mode 100644
index 000000000..d5c4dec19
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-dialog.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpShortcutuiDialog" parent="GtkWindow">
+    <style>
+      <class name="shortcutui"/>
+    </style>
+    <child type="titlebar">
+      <object class="AdwHeaderBar">
+        <child type="start">
+          <object class="GtkButton">
+            <property name="label" translatable="yes">Reset All…</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack">
+        <child>
+          <object class="GtkStackPage">
+            <property name="name">primary</property>
+            <property name="child">
+              <object class="AdwPreferencesPage">
+                <child>
+                  <object class="AdwPreferencesGroup">
+                    <child>
+                      <object class="GtkSearchEntry" id="search">
+                        <property name="halign">center</property>
+                        <property name="width-chars">30</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwPreferencesGroup" id="overview">
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwPreferencesGroup" id="results">
+                    <property name="visible">false</property>
+                    <child>
+                      <object class="GtkListBox" id="results_list_box">
+                        <style>
+                          <class name="boxed-list"/>
+                          <class name="search"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwPreferencesGroup" id="empty">
+                    <property name="visible">false</property>
+                    <child>
+                      <object class="AdwStatusPage">
+                        <property name="icon-name">edit-find-symbolic</property>
+                        <property name="title" translatable="yes">No keyboard shortcut found</property>
+                        <property name="description" translatable="yes">Try a different search</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkFilterListModel" id="filter_model">
+    <signal name="notify::n-items" handler="gbp_shortcutui_dialog_queue_update" swapped="true" 
object="GbpShortcutuiDialog"/>
+    <property name="incremental">false</property>
+    <property name="model">
+      <object class="GbpShortcutuiActionModel" id="action_model"/>
+    </property>
+    <property name="filter">
+      <object class="GtkStringFilter" id="string_filter">
+        <signal name="notify::search" handler="gbp_shortcutui_dialog_queue_update" swapped="true" 
object="GbpShortcutuiDialog"/>
+        <binding name="search">
+          <lookup name="text">search</lookup>
+        </binding>
+        <property name="ignore-case">true</property>
+        <property name="match-mode">substring</property>
+        <property name="expression">
+          <lookup name="search-text" type="GbpShortcutuiAction"/>
+        </property>
+      </object>
+    </property>
+  </object>
+</interface>
diff --git a/src/plugins/shortcutui/gbp-shortcutui-row.c b/src/plugins/shortcutui/gbp-shortcutui-row.c
new file mode 100644
index 000000000..5f9905b87
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-row.c
@@ -0,0 +1,186 @@
+/* gbp-shortcutui-row.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shortcutui-row"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "gbp-shortcutui-action.h"
+#include "gbp-shortcutui-row.h"
+
+struct _GbpShortcutuiRow
+{
+  AdwActionRow parent_instance;
+  GbpShortcutuiAction *action;
+  GtkWidget *shortcut;
+};
+
+G_DEFINE_FINAL_TYPE (GbpShortcutuiRow, gbp_shortcutui_row, ADW_TYPE_ACTION_ROW)
+
+enum {
+  PROP_0,
+  PROP_ACTION,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_shortcutui_row_set_action (GbpShortcutuiRow    *self,
+                               GbpShortcutuiAction *action)
+{
+  g_auto(GValue) title = G_VALUE_INIT;
+  g_auto(GValue) subtitle = G_VALUE_INIT;
+  g_auto(GValue) accelerator = G_VALUE_INIT;
+
+  g_assert (GBP_IS_SHORTCUTUI_ROW (self));
+  g_assert (GBP_IS_SHORTCUTUI_ACTION (action));
+
+  g_set_object (&self->action, action);
+
+  g_value_init (&title, G_TYPE_STRING);
+  g_value_init (&subtitle, G_TYPE_STRING);
+  g_value_init (&accelerator, G_TYPE_STRING);
+
+  g_object_get_property (G_OBJECT (action), "title", &title);
+  g_object_get_property (G_OBJECT (action), "subtitle", &subtitle);
+  g_object_get_property (G_OBJECT (action), "accelerator", &accelerator);
+
+  g_object_set_property (G_OBJECT (self), "title", &title);
+  g_object_set_property (G_OBJECT (self), "subtitle", &subtitle);
+  g_object_get_property (G_OBJECT (self->shortcut), "accelerator", &accelerator);
+}
+
+static void
+gbp_shortcutui_row_dispose (GObject *object)
+{
+  GbpShortcutuiRow *self = (GbpShortcutuiRow *)object;
+
+  g_clear_object (&self->action);
+
+  G_OBJECT_CLASS (gbp_shortcutui_row_parent_class)->dispose (object);
+}
+
+static void
+gbp_shortcutui_row_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GbpShortcutuiRow *self = GBP_SHORTCUTUI_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACTION:
+      g_value_set_object (value, self->action);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shortcutui_row_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GbpShortcutuiRow *self = GBP_SHORTCUTUI_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACTION:
+      gbp_shortcutui_row_set_action (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shortcutui_row_class_init (GbpShortcutuiRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_shortcutui_row_dispose;
+  object_class->get_property = gbp_shortcutui_row_get_property;
+  object_class->set_property = gbp_shortcutui_row_set_property;
+
+  properties [PROP_ACTION] =
+    g_param_spec_object ("action", NULL, NULL,
+                         GBP_TYPE_SHORTCUTUI_ACTION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/shortcutui/gbp-shortcutui-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpShortcutuiRow, shortcut);
+}
+
+static void
+gbp_shortcutui_row_init (GbpShortcutuiRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+gbp_shortcutui_row_update_header (GbpShortcutuiRow *self,
+                                  GbpShortcutuiRow *before)
+{
+  GtkWidget *header = NULL;
+
+  g_return_if_fail (GBP_IS_SHORTCUTUI_ROW (self));
+  g_return_if_fail (!before || GBP_IS_SHORTCUTUI_ROW (before));
+
+  if (self->action == NULL)
+    return;
+
+  if (before == NULL ||
+      !gbp_shortcutui_action_is_same_group (self->action, before->action))
+    {
+      const char *page = gbp_shortcutui_action_get_page (self->action);
+      const char *group = gbp_shortcutui_action_get_group (self->action);
+
+      if (page != NULL && group != NULL)
+        {
+          g_autofree char *title = g_strdup_printf ("%s / %s", page, group);
+
+          header = g_object_new (GTK_TYPE_LABEL,
+                                 "css-classes", IDE_STRV_INIT ("heading"),
+                                 "halign", GTK_ALIGN_START,
+                                 "hexpand", TRUE,
+                                 "label", title,
+                                 "use-markup", TRUE,
+                                 NULL);
+        }
+    }
+
+  if (header)
+    gtk_widget_add_css_class (GTK_WIDGET (self), "has-header");
+  else
+    gtk_widget_remove_css_class (GTK_WIDGET (self), "has-header");
+
+  gtk_list_box_row_set_header (GTK_LIST_BOX_ROW (self), header);
+}
diff --git a/src/plugins/shortcutui/gbp-shortcutui-row.h b/src/plugins/shortcutui/gbp-shortcutui-row.h
new file mode 100644
index 000000000..5b58ce548
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-row.h
@@ -0,0 +1,34 @@
+/* gbp-shortcutui-row.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHORTCUTUI_ROW (gbp_shortcutui_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShortcutuiRow, gbp_shortcutui_row, GBP, SHORTCUTUI_ROW, AdwActionRow)
+
+void gbp_shortcutui_row_update_header (GbpShortcutuiRow *self,
+                                       GbpShortcutuiRow *before);
+
+G_END_DECLS
diff --git a/src/plugins/shortcutui/gbp-shortcutui-row.ui b/src/plugins/shortcutui/gbp-shortcutui-row.ui
new file mode 100644
index 000000000..eab654768
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-row.ui
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpShortcutuiRow" parent="AdwActionRow">
+    <child type="suffix">
+      <object class="GtkShortcutLabel" id="shortcut"/>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.c 
b/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.c
new file mode 100644
index 000000000..d451b3c40
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.c
@@ -0,0 +1,125 @@
+/* gbp-shortcutui-tweaks-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shortcutui-tweaks-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+
+#include "gbp-shortcutui-dialog.h"
+#include "gbp-shortcutui-tweaks-addin.h"
+
+struct _GbpShortcutuiTweaksAddin
+{
+  IdeTweaksAddin parent_instance;
+};
+
+G_DEFINE_FINAL_TYPE (GbpShortcutuiTweaksAddin, gbp_shortcutui_tweaks_addin, IDE_TYPE_TWEAKS_ADDIN)
+
+static void
+gbp_shortcutui_tweaks_addin_row_activated_cb (GbpShortcutuiTweaksAddin *self,
+                                              AdwActionRow             *row)
+{
+  GbpShortcutuiDialog *dialog;
+  GtkRoot *root;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_SHORTCUTUI_TWEAKS_ADDIN (self));
+  g_assert (ADW_IS_ACTION_ROW (row));
+
+  root = gtk_widget_get_root (GTK_WIDGET (row));
+  dialog = g_object_new (GBP_TYPE_SHORTCUTUI_DIALOG,
+                         "default-width", 700,
+                         "default-height", 500,
+                         "title", _("Keyboard Shortcuts"),
+                         "transient-for", root,
+                         "modal", TRUE,
+                         NULL);
+
+  gtk_window_present (GTK_WINDOW (dialog));
+
+  IDE_EXIT;
+}
+
+static GtkWidget *
+shortcutui_create_shortcuts_cb (GbpShortcutuiTweaksAddin *self,
+                                IdeTweaksItem            *instance,
+                                IdeTweaksItem            *original)
+{
+  AdwActionRow *row;
+  GtkImage *image;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_SHORTCUTUI_TWEAKS_ADDIN (self));
+  g_assert (IDE_IS_TWEAKS_ITEM (instance));
+  g_assert (IDE_IS_TWEAKS_ITEM (original));
+
+  row = g_object_new (ADW_TYPE_ACTION_ROW,
+                      "activatable", TRUE,
+                      "title", _("View and Customize Shortcuts…"),
+                      NULL);
+  image = g_object_new (GTK_TYPE_IMAGE,
+                        "icon-name", "go-next-symbolic",
+                        NULL);
+  adw_action_row_add_suffix (row, GTK_WIDGET (image));
+
+  g_signal_connect_object (row,
+                           "activated",
+                           G_CALLBACK (gbp_shortcutui_tweaks_addin_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  return GTK_WIDGET (row);
+}
+
+static void
+gbp_shortcutui_tweaks_addin_load (IdeTweaksAddin *addin,
+                                  IdeTweaks      *tweaks)
+{
+  GbpShortcutuiTweaksAddin *self = (GbpShortcutuiTweaksAddin *)addin;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_SHORTCUTUI_TWEAKS_ADDIN (self));
+  g_assert (IDE_IS_TWEAKS (tweaks));
+
+  ide_tweaks_addin_set_resource_paths (IDE_TWEAKS_ADDIN (self),
+                                       IDE_STRV_INIT ("/plugins/shortcutui/tweaks.ui"));
+  ide_tweaks_addin_bind_callback (IDE_TWEAKS_ADDIN (self), shortcutui_create_shortcuts_cb);
+
+  IDE_TWEAKS_ADDIN_CLASS (gbp_shortcutui_tweaks_addin_parent_class)->load (addin, tweaks);
+}
+
+static void
+gbp_shortcutui_tweaks_addin_class_init (GbpShortcutuiTweaksAddinClass *klass)
+{
+  IdeTweaksAddinClass *tweaks_addin_class = IDE_TWEAKS_ADDIN_CLASS (klass);
+
+  tweaks_addin_class->load = gbp_shortcutui_tweaks_addin_load;
+}
+
+static void
+gbp_shortcutui_tweaks_addin_init (GbpShortcutuiTweaksAddin *self)
+{
+}
diff --git a/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.h 
b/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.h
new file mode 100644
index 000000000..628145f5f
--- /dev/null
+++ b/src/plugins/shortcutui/gbp-shortcutui-tweaks-addin.h
@@ -0,0 +1,31 @@
+/* gbp-shortcutui-tweaks-addin.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-tweaks.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHORTCUTUI_TWEAKS_ADDIN (gbp_shortcutui_tweaks_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShortcutuiTweaksAddin, gbp_shortcutui_tweaks_addin, GBP, SHORTCUTUI_TWEAKS_ADDIN, 
IdeTweaksAddin)
+
+G_END_DECLS
diff --git a/src/plugins/shortcutui/meson.build b/src/plugins/shortcutui/meson.build
new file mode 100644
index 000000000..6aa30d82f
--- /dev/null
+++ b/src/plugins/shortcutui/meson.build
@@ -0,0 +1,16 @@
+plugins_sources += files([
+  'shortcutui-plugin.c',
+  'gbp-shortcutui-action.c',
+  'gbp-shortcutui-action-model.c',
+  'gbp-shortcutui-dialog.c',
+  'gbp-shortcutui-row.c',
+  'gbp-shortcutui-tweaks-addin.c',
+])
+
+plugin_shortcutui_resources = gnome.compile_resources(
+  'shortcutui-resources',
+  'shortcutui.gresource.xml',
+  c_name: 'gbp_shortcutui',
+)
+
+plugins_sources += plugin_shortcutui_resources
diff --git a/src/plugins/shortcutui/shortcutui-plugin.c b/src/plugins/shortcutui/shortcutui-plugin.c
new file mode 100644
index 000000000..ced514ea9
--- /dev/null
+++ b/src/plugins/shortcutui/shortcutui-plugin.c
@@ -0,0 +1,38 @@
+/* shortcutui-plugin.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "shortcutui-plugin"
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+
+#include <libide-gui.h>
+#include <libide-tweaks.h>
+
+#include "gbp-shortcutui-tweaks-addin.h"
+
+_IDE_EXTERN void
+_gbp_shortcutui_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_TWEAKS_ADDIN,
+                                              GBP_TYPE_SHORTCUTUI_TWEAKS_ADDIN);
+}
diff --git a/src/plugins/shortcutui/shortcutui.gresource.xml b/src/plugins/shortcutui/shortcutui.gresource.xml
new file mode 100644
index 000000000..0268b37b4
--- /dev/null
+++ b/src/plugins/shortcutui/shortcutui.gresource.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/shortcutui">
+    <file>shortcutui.plugin</file>
+    <file>style.css</file>
+    <file preprocess="xml-stripblanks">tweaks.ui</file>
+    <file preprocess="xml-stripblanks">gbp-shortcutui-dialog.ui</file>
+    <file preprocess="xml-stripblanks">gbp-shortcutui-row.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/shortcutui/shortcutui.plugin b/src/plugins/shortcutui/shortcutui.plugin
new file mode 100644
index 000000000..889db20d6
--- /dev/null
+++ b/src/plugins/shortcutui/shortcutui.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2022 Christian Hergert
+Depends=platformui;
+Embedded=_gbp_shortcutui_register_types
+Hidden=true
+Module=shortcutui
+Name=Shortcut Integration
diff --git a/src/plugins/shortcutui/style.css b/src/plugins/shortcutui/style.css
new file mode 100644
index 000000000..5d0892b5a
--- /dev/null
+++ b/src/plugins/shortcutui/style.css
@@ -0,0 +1,8 @@
+window.shortcutui list.search row.has-header {
+  border-top: 1px solid @borders;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+window.shortcutui list.search label.heading {
+  padding: 10px 12px 12px 16px;
+}
diff --git a/src/plugins/shortcutui/tweaks.ui b/src/plugins/shortcutui/tweaks.ui
new file mode 100644
index 000000000..27950a4b4
--- /dev/null
+++ b/src/plugins/shortcutui/tweaks.ui
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="IdeTweaks">
+    <child internal-child="visual_section">
+      <object class="IdeTweaksSection">
+        <child>
+          <object class="IdeTweaksPage" id="shortcuts_page">
+            <property name="title" translatable="yes">Keyboard Shortcuts</property>
+            <property name="icon-name">preferences-desktop-keyboard-shortcuts-symbolic</property>
+            <child>
+              <object class="IdeTweaksGroup">
+                <property name="title" translatable="yes">Keyboard Shortcuts</property>
+                <child>
+                  <object class="IdeTweaksWidget">
+                    <signal name="create-for-item" handler="shortcutui_create_shortcuts_cb" 
object="GbpShortcutuiTweaksAddin" swapped="true"/>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>


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