From 7886a8696bb7b7d626782ede74a1b20e0fc2f4ee Mon Sep 17 00:00:00 2001 From: Peter TB Brett Date: Tue, 17 Sep 2013 21:14:01 +0100 Subject: [PATCH] gschem: Revamp hotkeys window. --- gschem/include/Makefile.am | 1 + gschem/include/gschem.h | 1 + gschem/include/gschemhotkeystore.h | 75 ++++++++++++ gschem/include/prototype.h | 1 - gschem/po/POTFILES.in | 1 + gschem/scheme/gschem.scm | 28 +++-- gschem/src/Makefile.am | 1 + gschem/src/g_keys.c | 60 +-------- gschem/src/gschemhotkeystore.c | 242 +++++++++++++++++++++++++++++++++++++ gschem/src/x_dialog.c | 29 +++-- 10 files changed, 362 insertions(+), 77 deletions(-) create mode 100644 gschem/include/gschemhotkeystore.h create mode 100644 gschem/src/gschemhotkeystore.c diff --git a/gschem/include/Makefile.am b/gschem/include/Makefile.am index 4daedf49f..f8ad90f4d 100644 --- a/gschem/include/Makefile.am +++ b/gschem/include/Makefile.am @@ -10,6 +10,7 @@ noinst_HEADERS = \ x_log.h x_multiattrib.h x_pagesel.h x_preview.h \ x_swatchcr.h \ gschem.h \ + gschemhotkeystore.h \ gschem_accel_label.h \ gschem_action.h \ gschem_defines.h \ diff --git a/gschem/include/gschem.h b/gschem/include/gschem.h index 4d787f0df..45bcf36ff 100644 --- a/gschem/include/gschem.h +++ b/gschem/include/gschem.h @@ -12,6 +12,7 @@ #include "gschem_accel_label.h" #include "gschem_action.h" #include "gschem_dialog.h" +#include "gschemhotkeystore.h" #include "i_vars.h" #include "x_preview.h" #include "x_compselect.h" diff --git a/gschem/include/gschemhotkeystore.h b/gschem/include/gschemhotkeystore.h new file mode 100644 index 000000000..e8bda2f51 --- /dev/null +++ b/gschem/include/gschemhotkeystore.h @@ -0,0 +1,75 @@ +/* gEDA - GPL Electronic Design Automation + * gschem - gEDA Schematic Capture + * Copyright (C) 2013 Peter Brett + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02111-1301 USA. + */ + +#ifndef __GSCHEM_HOTKEY_STORE_H__ +#define __GSCHEM_HOTKEY_STORE_H__ + +G_BEGIN_DECLS + +/* ---------------------------------------------------------------- */ + +/*! \class GschemHotkeyStore gschemhotkeystore.h "gschemhotkeystore.h" + * \brief GtkTreeModel that contains keybinding data. + * + * A GtkListStore that contains a list of user editing actions with + * their icons and their current keybindings. The store automatically + * updates when any of the actions' labels, icons or keybindings are + * changed. + */ + +#define GSCHEM_TYPE_HOTKEY_STORE (gschem_hotkey_store_get_type ()) +#define GSCHEM_HOTKEY_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSCHEM_TYPE_HOTKEY_STORE, GschemHotkeyStore)) +#define GSCHEM_HOTKEY_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSCHEM_TYPE_HOTKEY_STORE, GschemHotkeyStoreClass)) +#define GSCHEM_IS_HOTKEY_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSCHEM_TYPE_HOTKEY_STORE)) +#define GSCHEM_IS_HOTKEY_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSCHEM_TYPE_HOTKEY_STORE)) +#define GSCHEM_HOTKEY_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSCHEM_TYPE_HOTKEY_STORE, GschemHotkeyStoreClass)) + +enum { + GSCHEM_HOTKEY_STORE_COLUMN_ICON = 0, + GSCHEM_HOTKEY_STORE_COLUMN_LABEL, + GSCHEM_HOTKEY_STORE_COLUMN_KEYS, + GSCHEM_HOTKEY_STORE_NUM_COLUMNS, +}; + +typedef struct _GschemHotkeyStoreClass GschemHotkeyStoreClass; +typedef struct _GschemHotkeyStore GschemHotkeyStore; + +struct _GschemHotkeyStoreClass +{ + GtkListStoreClass parent_class; +}; + +struct _GschemHotkeyStore +{ + GtkListStore parent_instance; + + /* Protected members */ + EdascmHookProxy *action_hook_proxy; + EdascmHookProxy *keymap_hook_proxy; + guint rebuild_source_id; +}; + +GType gschem_hotkey_store_get_type (void) G_GNUC_CONST; + +GschemHotkeyStore *gschem_hotkey_store_new (void) G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* ! __GSCHEM_HOTKEY_STORE_H__ */ diff --git a/gschem/include/prototype.h b/gschem/include/prototype.h index 3ec623ca5..fb39f22bb 100644 --- a/gschem/include/prototype.h +++ b/gschem/include/prototype.h @@ -53,7 +53,6 @@ EdascmHookProxy *g_hook_new_proxy_by_name (const char *name); /* g_keys.c */ void g_keys_reset (GSCHEM_TOPLEVEL *w_current); int g_keys_execute(GSCHEM_TOPLEVEL *w_current, GdkEventKey *event); -GtkListStore *g_keys_to_list_store (void); SCM g_keys_file_new(SCM rest); SCM g_keys_file_new_window(SCM rest); SCM g_keys_file_open(SCM rest); diff --git a/gschem/po/POTFILES.in b/gschem/po/POTFILES.in index eb7a62e3f..1e10a77cb 100644 --- a/gschem/po/POTFILES.in +++ b/gschem/po/POTFILES.in @@ -11,6 +11,7 @@ gschem/src/g_util.c gschem/src/g_window.c gschem/src/globals.c gschem/src/gschem.c +gschem/src/gschemhotkeystore.c gschem/src/gschem_accel_label.c gschem/src/i_basic.c gschem/src/i_callbacks.c diff --git a/gschem/scheme/gschem.scm b/gschem/scheme/gschem.scm index ca09c406a..038463d43 100644 --- a/gschem/scheme/gschem.scm +++ b/gschem/scheme/gschem.scm @@ -1,7 +1,7 @@ ;;; gEDA - GPL Electronic Design Automation ;;; gschem - gEDA Schematic Capture ;;; Copyright (C) 1998-2010 Ales Hvezda -;;; Copyright (C) 1998-2011 gEDA Contributors (see ChangeLog for details) +;;; Copyright (C) 1998-2013 gEDA Contributors (see ChangeLog for details) ;;; ;;; 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 @@ -92,25 +92,39 @@ (and keys (keys->display-string keys)))) ;; Printing out current key bindings for gEDA (gschem) -(define (dump-global-keymap) +(define (%gschem-hotkey-store/dump-global-keymap) (dump-keymap %global-keymap)) (define (dump-keymap keymap) + ;; Use this to change "Page_Up" to "Page Up" (etc.) + (define (munge-keystring str) + (string-map (lambda (c) (case c ((#\_) #\ ) (else c))) str)) + (define lst '()) (define (binding->entry prefix key binding) - (let ((keys (list->vector (reverse (cons key prefix))))) - (set! lst (cons (cons (symbol->string binding) - (keys->display-string keys)) - lst)))) + (let ((keys (list->vector (reverse (cons key prefix)))) + (action (false-if-exception (eval binding (current-module))))) + + ;; If the binding points to an action, then use its label. + ;; Otherwise, just use the string value of the binding. + (let ((keystr (munge-keystring (keys->display-string keys))) + (cmdstr (or (and (action? action) + (action-property action 'label)) + (symbol->string binding))) + (iconstr (and (action? action) + (action-property action 'icon)))) + (set! lst (cons (list cmdstr keystr iconstr) lst))))) (define (build-dump! km prefix) (keymap-for-each (lambda (key binding) (cond - ((symbol? binding) + + ((or (symbol? binding) (action? binding)) (binding->entry prefix key binding)) + ((keymap? binding) (build-dump! binding (cons key prefix))) (else (error "Invalid action ~S bound to ~S" diff --git a/gschem/src/Makefile.am b/gschem/src/Makefile.am index 256a27bb5..5db1f7318 100644 --- a/gschem/src/Makefile.am +++ b/gschem/src/Makefile.am @@ -24,6 +24,7 @@ gschem_SOURCES = \ g_window.c \ globals.c \ gschem.c \ + gschemhotkeystore.c \ gschem_accel_label.c \ gschem_action.c \ gschem_dialog.c \ diff --git a/gschem/src/g_keys.c b/gschem/src/g_keys.c index e318593f8..4a7b76929 100644 --- a/gschem/src/g_keys.c +++ b/gschem/src/g_keys.c @@ -1,7 +1,7 @@ /* gEDA - GPL Electronic Design Automation * gschem - gEDA Schematic Capture * Copyright (C) 1998-2010 Ales Hvezda - * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details) + * Copyright (C) 1998-2013 gEDA Contributors (see ChangeLog for details) * * 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 @@ -586,64 +586,6 @@ g_keys_execute(GSCHEM_TOPLEVEL *w_current, GdkEventKey *event) return !scm_is_false (s_retval); } -/*! \brief Exports the keymap in Scheme to a GtkListStore - * \par Function Description - * This function converts the list of key sequence/action pairs - * returned by the Scheme function \c dump-global-keymap into a - * GtkListStore with two columns. The first column contains the name - * of the action executed by the keybinding as a string, and the - * second contains the keybinding itself as a string suitable for - * display. - * - * The returned value must be freed by caller. - * - * \return A GtkListStore containing keymap data. - */ -GtkListStore * -g_keys_to_list_store (void) -{ - SCM s_expr; - SCM s_lst; - SCM s_iter; - GtkListStore *list_store; - - /* Call Scheme procedure to dump global keymap into list */ - s_expr = scm_list_1 (scm_from_utf8_symbol ("dump-global-keymap")); - s_lst = g_scm_eval_protected (s_expr, scm_interaction_environment ()); - - g_return_val_if_fail (scm_is_true (scm_list_p (s_lst)), NULL); - - /* Convert to */ - scm_dynwind_begin (0); - list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); - scm_dynwind_unwind_handler (g_object_unref, list_store, 0); - - for (s_iter = s_lst; !scm_is_null (s_iter); s_iter = scm_cdr (s_iter)) { - SCM s_binding = scm_caar (s_iter); - SCM s_keys = scm_cdar (s_iter); - char *binding, *keys; - GtkTreeIter iter; - - scm_dynwind_begin (0); - - binding = scm_to_utf8_string (s_binding); - scm_dynwind_free (binding); - - keys = scm_to_utf8_string (s_keys); - scm_dynwind_free (keys); - - gtk_list_store_insert_with_values (list_store, &iter, -1, - 0, binding, - 1, keys, - -1); - - scm_dynwind_end (); - } - - scm_dynwind_end (); - return list_store; -} - /*! \brief Create the (gschem core keymap) Scheme module * \par Function Description * Defines procedures in the (gschem core keymap) module. The module diff --git a/gschem/src/gschemhotkeystore.c b/gschem/src/gschemhotkeystore.c new file mode 100644 index 000000000..8bd063775 --- /dev/null +++ b/gschem/src/gschemhotkeystore.c @@ -0,0 +1,242 @@ +/* gEDA - GPL Electronic Design Automation + * gschem - gEDA Schematic Capture + * Copyright (C) 2013 Peter Brett + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02111-1301 USA. + */ + +#include +#include +#include "gschem.h" + +enum { + COLUMN_ICON = 0, + COLUMN_LABEL, + COLUMN_KEYS, + NUM_COLUMNS, +}; + +#define HELPER_FUNC_NAME "%gschem-hotkey-store/dump-global-keymap" + +static void gschem_hotkey_store_dispose (GObject *object); +static void gschem_hotkey_store_schedule_rebuild (GschemHotkeyStore *store); +static gboolean gschem_hotkey_store_rebuild (GschemHotkeyStore *store); +static void gschem_hotkey_store_action_property_handler (GschemHotkeyStore *store, + SCM s_args, + EdascmHookProxy *proxy); +static void gschem_hotkey_store_bind_keys_handler (GschemHotkeyStore *store, + SCM s_args, + EdascmHookProxy *proxy); + +G_DEFINE_TYPE (GschemHotkeyStore, gschem_hotkey_store, GTK_TYPE_LIST_STORE); + +/*! Initialise GschemHotkeyStore class */ +static void +gschem_hotkey_store_class_init (GschemHotkeyStoreClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + /* Register functions with base class */ + gobject_class->dispose = gschem_hotkey_store_dispose; +} + +/*! Initialise GschemHotkeyStore instance */ +static void +gschem_hotkey_store_init (GschemHotkeyStore *store) +{ + GType column_types[GSCHEM_HOTKEY_STORE_NUM_COLUMNS] + = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, }; + + /* This list store contains the hotkey data */ + gtk_list_store_set_column_types (GTK_LIST_STORE (store), + GSCHEM_HOTKEY_STORE_NUM_COLUMNS, + column_types); + + /* Make sure we can get notified when actions or keybindings + * change */ + store->action_hook_proxy = + g_hook_new_proxy_by_name ("%action-property-hook"); + g_signal_connect_swapped (store->action_hook_proxy, "run", + G_CALLBACK (gschem_hotkey_store_action_property_handler), + store); + + store->keymap_hook_proxy = + g_hook_new_proxy_by_name ("%bind-keys-hook"); + g_signal_connect_swapped (store->keymap_hook_proxy, "run", + G_CALLBACK (gschem_hotkey_store_bind_keys_handler), + store); + + /* Finally, carry out initial rebuild of the treeview's backing + * store */ + gschem_hotkey_store_schedule_rebuild (store); +} + +/*! Dispose of a GschemHotkeyStore instance. Drop all references to + * other GObjects, but keep the instance otherwise intact. May be run + * multiple times (due to reference loops). + */ +static void +gschem_hotkey_store_dispose (GObject *object) +{ + GschemHotkeyStore *store = GSCHEM_HOTKEY_STORE (object); + + if (store->action_hook_proxy) { + g_object_unref (store->action_hook_proxy); + store->action_hook_proxy = NULL; + } + + if (store->keymap_hook_proxy) { + g_object_unref (store->keymap_hook_proxy); + store->keymap_hook_proxy = NULL; + } + + /* Chain up to the parent class */ + G_OBJECT_CLASS (gschem_hotkey_store_parent_class)->dispose (object); +} + +/*! Schedule a list view rebuild to occur next time the GLib main loop + * is idle. */ +static void +gschem_hotkey_store_schedule_rebuild (GschemHotkeyStore *store) +{ + if (store->rebuild_source_id) return; + + store->rebuild_source_id = + g_idle_add ((GSourceFunc) gschem_hotkey_store_rebuild, + store); +} + +/*! Rebuild the list view. Calls into Scheme to generate a list of + * current keybindings, and uses it to update the GtkListStore that + * backs the list of key bindings. */ +static gboolean +gschem_hotkey_store_rebuild (GschemHotkeyStore *store) +{ + static SCM s_expr = SCM_UNDEFINED; + SCM s_lst, s_iter; + + g_assert (GSCHEM_IS_HOTKEY_STORE (store)); + + /* First run the Scheme function to dump the global keymap */ + if (s_expr == SCM_UNDEFINED) { + s_expr = scm_permanent_object (scm_list_1 (scm_from_utf8_symbol (HELPER_FUNC_NAME))); + } + s_lst = g_scm_eval_protected (s_expr, SCM_UNDEFINED); + + g_return_val_if_fail (scm_is_true (scm_list_p (s_lst)), FALSE); + + /* If it worked, then rebuild the keymap */ + gtk_list_store_clear (GTK_LIST_STORE (store)); + + for (s_iter = s_lst; !scm_is_null (s_iter); s_iter = scm_cdr (s_iter)) { + SCM s_info = scm_car (s_iter); + SCM s_binding = scm_car (s_info); + SCM s_keys = scm_cadr (s_info); + SCM s_icon = scm_caddr (s_info); + char *binding, *keys, *icon = NULL; + GtkTreeIter iter; + + scm_dynwind_begin (0); + + binding = scm_to_utf8_string (s_binding); + scm_dynwind_free (binding); + + keys = scm_to_utf8_string (s_keys); + scm_dynwind_free (keys); + + if (scm_is_true (s_icon)) { + icon = scm_to_utf8_string (s_icon); + scm_dynwind_free (icon); + } + + gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, -1, + GSCHEM_HOTKEY_STORE_COLUMN_LABEL, binding, + GSCHEM_HOTKEY_STORE_COLUMN_KEYS, keys, + GSCHEM_HOTKEY_STORE_COLUMN_ICON, icon, + -1); + + scm_dynwind_end (); + } + + store->rebuild_source_id = 0; + return FALSE; +} + +/*! Scheme-level hook handler for %action-property-hook. Triggers + * rebuilding of the list of hotkeys, but only if the property that + * was changed was the label or icon (since those are the properties + * that are actually used in the hotkey display). + */ +static void +gschem_hotkey_store_action_property_handler (GschemHotkeyStore *store, + SCM s_args, + EdascmHookProxy *proxy) +{ + static SCM label_sym = SCM_UNDEFINED; + static SCM icon_sym = SCM_UNDEFINED; + SCM s_key; + gboolean rebuild = FALSE; + + g_assert (GSCHEM_IS_HOTKEY_STORE (store)); + + /* Unpack Scheme value before making any changes to the store. + * args should be a list of the form (action key value). We only + * want to rebuild the store if the change is to a label or to an + * icon. */ + if (label_sym == SCM_UNDEFINED) { + label_sym = scm_permanent_object (scm_from_utf8_symbol ("label")); + icon_sym = scm_permanent_object (scm_from_utf8_symbol ("icon")); + } + /* Don't even bother checking that there's a well-formed argument + * list; if there isn't, you've got bigger problems! */ + s_key = scm_cadr (s_args); + rebuild = (scm_is_eq (s_key, label_sym) || scm_is_eq (s_key, icon_sym)); + + if (rebuild) gschem_hotkey_store_schedule_rebuild (store); +} + +/*! Scheme-level hook handler for %bind-keys-hook. Triggers + * rebuilding the list of hotkeys, but only if the %global-keymap was + * modified. + */ +static void +gschem_hotkey_store_bind_keys_handler (GschemHotkeyStore *store, + SCM s_args, + EdascmHookProxy *proxy) +{ + static SCM global_keymap_sym = SCM_UNDEFINED; + gboolean rebuild = FALSE; + + g_assert (GSCHEM_IS_HOTKEY_STORE (store)); + + if (global_keymap_sym == SCM_UNDEFINED) { + global_keymap_sym = + scm_permanent_object (scm_from_utf8_symbol ("%global-keymap")); + } + /* Rather brute-force approach to checking whether the affected + * keymap is the global keymap */ + rebuild = (scm_is_eq (scm_car (s_args), + g_scm_eval_protected (global_keymap_sym, + SCM_UNDEFINED))); + + if (rebuild) gschem_hotkey_store_schedule_rebuild (store); +} + +GschemHotkeyStore * +gschem_hotkey_store_new (void) +{ + return g_object_new (GSCHEM_TYPE_HOTKEY_STORE, NULL); +} diff --git a/gschem/src/x_dialog.c b/gschem/src/x_dialog.c index bfc750de9..f44dd6337 100644 --- a/gschem/src/x_dialog.c +++ b/gschem/src/x_dialog.c @@ -1,7 +1,7 @@ /* gEDA - GPL Electronic Design Automation * gschem - gEDA Schematic Capture * Copyright (C) 1998-2010 Ales Hvezda - * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details) + * Copyright (C) 1998-2013 gEDA Contributors (see ChangeLog for details) * * 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 @@ -815,7 +815,7 @@ void x_dialog_hotkeys_response(GtkWidget *w, gint response, void x_dialog_hotkeys (GSCHEM_TOPLEVEL *w_current) { GtkWidget *vbox, *scrolled_win; - GtkListStore *store; + GtkTreeModel *store; GtkWidget *treeview; GtkCellRenderer *renderer; GtkTreeViewColumn *column; @@ -853,25 +853,34 @@ void x_dialog_hotkeys (GSCHEM_TOPLEVEL *w_current) GTK_POLICY_AUTOMATIC); /* the model */ - store = g_keys_to_list_store (); + store = GTK_TREE_MODEL (gschem_hotkey_store_new ()); /* the tree view */ - treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + treeview = gtk_tree_view_new_with_model (store); gtk_container_add(GTK_CONTAINER(scrolled_win), treeview); /* the columns */ - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes (_("Function"), + /* The first column contains the action's icon (if one was set) + * and its label. */ + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new_with_attributes (_("Action"), renderer, - "text", - 0, + "icon-name", + GSCHEM_HOTKEY_STORE_COLUMN_ICON, NULL); - gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", GSCHEM_HOTKEY_STORE_COLUMN_LABEL, + NULL); + + /* The second column contains the action's keybinding */ + gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); column = gtk_tree_view_column_new_with_attributes (_("Keystroke(s)"), renderer, "text", - 1, + GSCHEM_HOTKEY_STORE_COLUMN_KEYS, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), column); -- 2.11.4.GIT