[gnome-builder/wip/gtk4-port: 860/1774] libide/gui: create GtkShortcut from keybindings bundles
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/gtk4-port: 860/1774] libide/gui: create GtkShortcut from keybindings bundles
- Date: Mon, 11 Jul 2022 22:31:26 +0000 (UTC)
commit c82d9874665172ebf52c8264845c416c4299773a
Author: Christian Hergert <chergert redhat com>
Date: Tue May 3 14:00:32 2022 -0700
libide/gui: create GtkShortcut from keybindings bundles
This still needs testing as it has only been compile tested. But the goal
is that we will parse JSON files of keybinding descriptions into a bundle
(which is a GListModel of GtkShortcut). We use the template-glib based
TmplExpr for "when" to determine if we can activate the
GtkShortcutAction from a GtkCallbackAction.
The GtkCallbackAction's user_data contains the phase, which we might want
to use at some point to get filtered GListModel by propagation phase for
the GtkShortcutController.
meson.build | 2 +-
src/libide/gui/ide-shortcut-bundle.c | 245 ++++++++++++++++++++++++++++++----
src/libide/gui/ide-shortcut-private.h | 31 -----
src/libide/gui/ide-shortcut.c | 92 -------------
src/libide/gui/meson.build | 2 -
5 files changed, 221 insertions(+), 151 deletions(-)
---
diff --git a/meson.build b/meson.build
index 4ee62a05a..b678b6967 100644
--- a/meson.build
+++ b/meson.build
@@ -294,7 +294,7 @@ libjsonrpc_glib_dep = dependency('jsonrpc-glib-1.0', version: '>= 3.42.0')
libm_dep = cc.find_library('m', required: false)
libpeas_dep = dependency('libpeas-1.0', version: '>= 1.32.0')
libportal_dep = dependency('libportal-gtk4', required: false)
-libtemplate_glib_dep = dependency('template-glib-1.0', version: '>= 3.28.0')
+libtemplate_glib_dep = dependency('template-glib-1.0', version: '>= 3.35.0')
libvte_dep = dependency('vte-2.91-gtk4', version: '>= 0.69.0')
libxml2_dep = dependency('libxml-2.0', version: '>= 2.9.0')
diff --git a/src/libide/gui/ide-shortcut-bundle.c b/src/libide/gui/ide-shortcut-bundle.c
index 5efd5438a..128f3675e 100644
--- a/src/libide/gui/ide-shortcut-bundle.c
+++ b/src/libide/gui/ide-shortcut-bundle.c
@@ -22,10 +22,14 @@
#include "config.h"
+#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
+#include <tmpl-glib.h>
+#include "ide-gui-global.h"
#include "ide-shortcut-bundle-private.h"
-#include "ide-shortcut-private.h"
+#include "ide-workbench.h"
+#include "ide-workspace.h"
struct _IdeShortcutBundle
{
@@ -35,17 +39,111 @@ struct _IdeShortcutBundle
typedef struct
{
- const char *key;
- const char *when;
- const char *command;
- const char *action;
- const char *phase;
-} Shortcut;
+ TmplExpr *when;
+ GVariant *args;
+ GtkShortcutAction *action;
+ GtkPropagationPhase phase;
+} IdeShortcut;
+
+static IdeShortcut *
+ide_shortcut_new (const char *action,
+ GVariant *args,
+ TmplExpr *when,
+ GtkPropagationPhase phase)
+{
+ IdeShortcut *ret;
+
+ g_assert (action != NULL);
+ g_assert (phase == GTK_PHASE_CAPTURE || phase == GTK_PHASE_BUBBLE);
+
+ ret = g_slice_new0 (IdeShortcut);
+ ret->action = gtk_named_action_new (action);
+ ret->args = args ? g_variant_ref (args) : NULL;
+ ret->when = when ? tmpl_expr_ref (when) : NULL;
+ ret->phase = phase;
+
+ return ret;
+}
+
+static void
+ide_shortcut_free (IdeShortcut *shortcut)
+{
+ g_clear_pointer (&shortcut->when, tmpl_expr_unref);
+ g_clear_pointer (&shortcut->args, g_variant_unref);
+ g_clear_object (&shortcut->action);
+ shortcut->phase = 0;
+ g_slice_free (IdeShortcut, shortcut);
+}
+
+static void
+set_object (TmplScope *scope,
+ const char *name,
+ GType type,
+ gpointer object)
+{
+ g_auto(GValue) value = G_VALUE_INIT;
+
+ g_value_init (&value, type);
+ g_value_set_object (&value, object);
+ tmpl_scope_set_value (scope, name, &value);
+}
+
+static gboolean
+ide_shortcut_activate (GtkWidget *widget,
+ GVariant *args,
+ gpointer user_data)
+{
+ IdeShortcut *shortcut = user_data;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (shortcut != NULL);
+
+ if (shortcut->when != NULL)
+ {
+ g_auto(GValue) enabled = G_VALUE_INIT;
+ g_autoptr(TmplScope) scope = tmpl_scope_new ();
+ IdeWorkspace *workspace = ide_widget_get_workspace (widget);
+ IdeWorkbench *workbench = ide_widget_get_workbench (widget);
+ IdePage *page = workspace ? ide_workspace_get_most_recent_page (workspace) : NULL;
+
+ set_object (scope, "focus", GTK_TYPE_WIDGET, widget);
+ set_object (scope, "workbench", IDE_TYPE_WORKBENCH, workbench);
+ set_object (scope, "workspace", IDE_TYPE_WORKSPACE, workspace);
+ set_object (scope, "page", IDE_TYPE_PAGE, page);
+
+ if (!tmpl_expr_eval (shortcut->when, scope, &enabled, NULL))
+ return FALSE;
+
+ if (!G_VALUE_HOLDS_BOOLEAN (&enabled))
+ {
+ GValue as_bool = G_VALUE_INIT;
+
+ g_value_init (&as_bool, G_TYPE_BOOLEAN);
+ if (!g_value_transform (&enabled, &as_bool))
+ return FALSE;
+
+ g_value_unset (&enabled);
+ enabled = as_bool;
+ }
+
+ g_assert (G_VALUE_HOLDS_BOOLEAN (&enabled));
+
+ if (!g_value_get_boolean (&enabled))
+ return FALSE;
+ }
+
+ return gtk_shortcut_action_activate (shortcut->action,
+ GTK_SHORTCUT_ACTION_EXCLUSIVE,
+ widget,
+ shortcut->args);
+}
static guint
ide_shortcut_bundle_get_n_items (GListModel *model)
{
- return IDE_SHORTCUT_BUNDLE (model)->items->len;
+ IdeShortcutBundle *self = IDE_SHORTCUT_BUNDLE (model);
+
+ return self->items ? self->items->len : 0;
}
static gpointer
@@ -54,16 +152,16 @@ ide_shortcut_bundle_get_item (GListModel *model,
{
IdeShortcutBundle *self = IDE_SHORTCUT_BUNDLE (model);
- if (position >= self->items->len)
+ if (self->items == NULL || position >= self->items->len)
return NULL;
- return NULL;
+ return g_object_ref (g_ptr_array_index (self->items, position));
}
static GType
ide_shortcut_bundle_get_item_type (GListModel *model)
{
- return IDE_TYPE_SHORTCUT;
+ return GTK_TYPE_SHORTCUT;
}
static void
@@ -77,15 +175,13 @@ list_model_iface_init (GListModelInterface *iface)
G_DEFINE_FINAL_TYPE_WITH_CODE (IdeShortcutBundle, ide_shortcut_bundle, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
-static void
-shortcut_free (gpointer data)
-{
- g_slice_free (Shortcut, data);
-}
-
static void
ide_shortcut_bundle_dispose (GObject *object)
{
+ IdeShortcutBundle *self = (IdeShortcutBundle *)object;
+
+ g_clear_pointer (&self->items, g_ptr_array_unref);
+
G_OBJECT_CLASS (ide_shortcut_bundle_parent_class)->dispose (object);
}
@@ -100,7 +196,7 @@ ide_shortcut_bundle_class_init (IdeShortcutBundleClass *klass)
static void
ide_shortcut_bundle_init (IdeShortcutBundle *self)
{
- self->items = g_ptr_array_new_with_free_func (shortcut_free);
+ self->items = g_ptr_array_new_with_free_func (g_object_unref);
}
IdeShortcutBundle *
@@ -152,13 +248,42 @@ get_string_member (JsonObject *obj,
return TRUE;
}
+static gboolean
+parse_phase (const char *str,
+ GtkPropagationPhase *phase)
+{
+ if (str == NULL || str[0] == 0 || strcasecmp ("capture", str) == 0)
+ {
+ *phase = GTK_PHASE_CAPTURE;
+ return TRUE;
+ }
+ else if (strcasecmp ("bubble", str) == 0)
+ {
+ *phase = GTK_PHASE_BUBBLE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static gboolean
populate_from_object (IdeShortcutBundle *self,
JsonNode *node,
GError **error)
{
+ g_autoptr(GtkShortcutTrigger) trigger = NULL;
+ g_autoptr(GtkShortcutAction) callback = NULL;
+ g_autoptr(GtkShortcut) shortcut = NULL;
+ g_autoptr(TmplExpr) when = NULL;
+ g_autoptr(GVariant) args = NULL;
+ const char *trigger_str = NULL;
+ const char *when_str = NULL;
+ const char *args_str = NULL;
+ const char *phase_str = NULL;
+ const char *command = NULL;
+ const char *action = NULL;
+ GtkPropagationPhase phase = 0;
JsonObject *obj;
- Shortcut shortcut = {0};
g_assert (IDE_IS_SHORTCUT_BUNDLE (self));
g_assert (node != NULL);
@@ -166,14 +291,84 @@ populate_from_object (IdeShortcutBundle *self,
obj = json_node_get_object (node);
- if (!get_string_member (obj, "key", &shortcut.key, error) ||
- !get_string_member (obj, "when", &shortcut.when, error) ||
- !get_string_member (obj, "command", &shortcut.command, error) ||
- !get_string_member (obj, "action", &shortcut.action, error) ||
- !get_string_member (obj, "phase", &shortcut.phase, error))
+ /* TODO: We might want to add title/description so that our internal
+ * keybindings can be displayed to the user from global search
+ * with more than just a command name and/or arguments.
+ */
+
+ if (!get_string_member (obj, "trigger", &trigger_str, error) ||
+ !get_string_member (obj, "when", &when_str, error) ||
+ !get_string_member (obj, "args", &args_str, error) ||
+ !get_string_member (obj, "command", &command, error) ||
+ !get_string_member (obj, "action", &action, error) ||
+ !get_string_member (obj, "phase", &phase_str, error))
return FALSE;
- g_ptr_array_add (self->items, g_slice_dup (Shortcut, &shortcut));
+ if (!(trigger = gtk_shortcut_trigger_parse_string (trigger_str)))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Failed to parse shortcut trigger: \"%s\"",
+ trigger_str);
+ return FALSE;
+ }
+
+ if (!ide_str_empty0 (command) && !ide_str_empty0 (action))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Cannot specify both \"command\" and \"action\" (\"%s\" and \"%s\")",
+ command, action);
+ return FALSE;
+ }
+
+ if (!ide_str_empty0 (args_str))
+ {
+ /* Parse args from string into GVariant if any */
+ if (!(args = g_variant_parse (NULL, args_str, NULL, NULL, error)))
+ return FALSE;
+ }
+
+ if (!ide_str_empty0 (command))
+ {
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(smv)"));
+ g_variant_builder_add (&builder, "s", command);
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("mv"));
+ if (args != NULL)
+ g_variant_builder_add_value (&builder, args);
+ g_variant_builder_close (&builder);
+
+ g_clear_pointer (&args, g_variant_unref);
+ args = g_variant_builder_end (&builder);
+ action = "workbench.command";
+ }
+
+ if (!ide_str_empty0 (when_str))
+ {
+ if (!(when = tmpl_expr_from_string (when_str, error)))
+ return FALSE;
+ }
+
+ if (!parse_phase (phase_str, &phase))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Unknown phase \"%s\"",
+ phase_str);
+ return FALSE;
+ }
+
+ callback = gtk_callback_action_new (ide_shortcut_activate,
+ ide_shortcut_new (action, args, when, phase),
+ (GDestroyNotify) ide_shortcut_free);
+ shortcut = gtk_shortcut_new (g_steal_pointer (&trigger),
+ g_steal_pointer (&callback));
+ g_ptr_array_add (self->items, g_steal_pointer (&shortcut));
return TRUE;
}
diff --git a/src/libide/gui/meson.build b/src/libide/gui/meson.build
index 7736bbee2..f96003d95 100644
--- a/src/libide/gui/meson.build
+++ b/src/libide/gui/meson.build
@@ -54,7 +54,6 @@ libide_gui_private_headers = [
'ide-recoloring-private.h',
'ide-search-popover-private.h',
'ide-session-private.h',
- 'ide-shortcut-private.h',
'ide-shortcut-bundle-private.h',
'ide-shortcut-model-private.h',
'ide-shortcut-controller-private.h',
@@ -75,7 +74,6 @@ libide_gui_private_sources = [
'ide-recoloring.c',
'ide-search-popover.c',
'ide-session.c',
- 'ide-shortcut.c',
'ide-shortcut-bundle.c',
'ide-shortcut-model.c',
'ide-shortcut-controller.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]