1278 lines
35 KiB
C
1278 lines
35 KiB
C
/*
|
|
* mooui/mooaccel.c
|
|
*
|
|
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* 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 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#include "mooutils/mooaccel.h"
|
|
#include "mooutils/mooaction.h"
|
|
#include "mooutils/mooaccelprefs-glade.h"
|
|
#include "mooutils/mooprefs.h"
|
|
#include "mooutils/mooprefsdialog.h"
|
|
#include "mooutils/moocompat.h"
|
|
#include "mooutils/mooaccelbutton.h"
|
|
#include "mooutils/moostock.h"
|
|
#include "mooutils/mooglade.h"
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <string.h>
|
|
|
|
#define MOO_ACCEL_PREFS_KEY "Shortcuts"
|
|
|
|
static GHashTable *moo_accel_map = NULL; /* char* -> char* */
|
|
static GHashTable *moo_default_accel_map = NULL; /* char* -> char* */
|
|
|
|
|
|
static void watch_gtk_accel_map (void);
|
|
static void block_watch_gtk_accel_map (void);
|
|
static void unblock_watch_gtk_accel_map (void);
|
|
|
|
|
|
static void
|
|
init_accel_map (void)
|
|
{
|
|
static gboolean done = FALSE;
|
|
|
|
if (!done)
|
|
{
|
|
done = TRUE;
|
|
|
|
moo_accel_map =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
(GDestroyNotify) g_free,
|
|
(GDestroyNotify) g_free);
|
|
moo_default_accel_map =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
(GDestroyNotify) g_free,
|
|
(GDestroyNotify) g_free);
|
|
|
|
watch_gtk_accel_map ();
|
|
}
|
|
}
|
|
|
|
|
|
static char *
|
|
accel_path_to_prefs_key (const char *accel_path)
|
|
{
|
|
if (accel_path && accel_path[0] == '<')
|
|
{
|
|
accel_path = strchr (accel_path, '/');
|
|
if (accel_path)
|
|
accel_path++;
|
|
}
|
|
|
|
if (!accel_path || !accel_path[0])
|
|
return NULL;
|
|
|
|
return g_strdup_printf (MOO_ACCEL_PREFS_KEY "/%s", accel_path);
|
|
}
|
|
|
|
|
|
void
|
|
_moo_prefs_set_accel (const char *accel_path,
|
|
const char *accel)
|
|
{
|
|
char *key = accel_path_to_prefs_key (accel_path);
|
|
g_return_if_fail (key != NULL);
|
|
moo_prefs_set_string (key, accel);
|
|
g_free (key);
|
|
}
|
|
|
|
|
|
const char *
|
|
_moo_prefs_get_accel (const char *accel_path)
|
|
{
|
|
const char *accel;
|
|
char *key = accel_path_to_prefs_key (accel_path);
|
|
g_return_val_if_fail (key != NULL, NULL);
|
|
|
|
moo_prefs_new_key_string (key, NULL);
|
|
accel = moo_prefs_get_string (key);
|
|
|
|
g_free (key);
|
|
return accel;
|
|
}
|
|
|
|
|
|
void
|
|
_moo_set_accel (const char *accel_path,
|
|
const char *accel)
|
|
{
|
|
guint accel_key = 0;
|
|
GdkModifierType accel_mods = 0;
|
|
GtkAccelKey old;
|
|
|
|
g_return_if_fail (accel_path != NULL && accel != NULL);
|
|
|
|
init_accel_map ();
|
|
|
|
if (*accel)
|
|
{
|
|
gtk_accelerator_parse (accel, &accel_key, &accel_mods);
|
|
|
|
if (accel_key || accel_mods) {
|
|
g_hash_table_insert (moo_accel_map,
|
|
g_strdup (accel_path),
|
|
gtk_accelerator_name (accel_key, accel_mods));
|
|
}
|
|
else {
|
|
g_warning ("could not parse accelerator '%s'", accel);
|
|
g_hash_table_insert (moo_accel_map,
|
|
g_strdup (accel_path),
|
|
g_strdup (""));
|
|
}
|
|
}
|
|
else {
|
|
g_hash_table_insert (moo_accel_map,
|
|
g_strdup (accel_path),
|
|
g_strdup (""));
|
|
}
|
|
|
|
block_watch_gtk_accel_map ();
|
|
|
|
if (gtk_accel_map_lookup_entry (accel_path, &old))
|
|
{
|
|
if (accel_key != old.accel_key || accel_mods != old.accel_mods)
|
|
{
|
|
if (accel_key || accel_mods)
|
|
{
|
|
if (!gtk_accel_map_change_entry (accel_path, accel_key,
|
|
accel_mods, TRUE))
|
|
g_warning ("could not set accel '%s' for accel_path '%s'",
|
|
accel, accel_path);
|
|
}
|
|
else
|
|
{
|
|
gtk_accel_map_change_entry (accel_path, 0, 0, TRUE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (accel_key || accel_mods)
|
|
{
|
|
gtk_accel_map_add_entry (accel_path,
|
|
accel_key,
|
|
accel_mods);
|
|
}
|
|
}
|
|
|
|
unblock_watch_gtk_accel_map ();
|
|
}
|
|
|
|
|
|
void
|
|
_moo_set_default_accel (const char *accel_path,
|
|
const char *accel)
|
|
{
|
|
const char *old_accel;
|
|
|
|
g_return_if_fail (accel_path != NULL && accel != NULL);
|
|
|
|
init_accel_map ();
|
|
|
|
old_accel = _moo_get_default_accel (accel_path);
|
|
|
|
if (old_accel && !strcmp (old_accel, accel))
|
|
return;
|
|
|
|
if (*accel)
|
|
{
|
|
guint accel_key = 0;
|
|
GdkModifierType accel_mods = 0;
|
|
|
|
gtk_accelerator_parse (accel, &accel_key, &accel_mods);
|
|
|
|
if (accel_key || accel_mods)
|
|
{
|
|
g_hash_table_insert (moo_default_accel_map,
|
|
g_strdup (accel_path),
|
|
gtk_accelerator_name (accel_key, accel_mods));
|
|
}
|
|
else
|
|
{
|
|
g_warning ("could not parse accelerator '%s'", accel);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_hash_table_insert (moo_default_accel_map,
|
|
g_strdup (accel_path),
|
|
g_strdup (""));
|
|
}
|
|
}
|
|
|
|
|
|
const char *
|
|
_moo_get_accel (const char *accel_path)
|
|
{
|
|
g_return_val_if_fail (accel_path != NULL, NULL);
|
|
init_accel_map ();
|
|
return g_hash_table_lookup (moo_accel_map, accel_path);
|
|
}
|
|
|
|
|
|
const char *
|
|
_moo_get_default_accel (const char *accel_path)
|
|
{
|
|
g_return_val_if_fail (accel_path != NULL, NULL);
|
|
init_accel_map ();
|
|
return g_hash_table_lookup (moo_default_accel_map, accel_path);
|
|
}
|
|
|
|
|
|
#if GTK_CHECK_VERSION(2,4,0)
|
|
static void
|
|
sync_accel_prefs (const char *accel_path)
|
|
{
|
|
const char *default_accel, *accel;
|
|
|
|
g_return_if_fail (accel_path != NULL);
|
|
|
|
init_accel_map ();
|
|
|
|
accel = _moo_get_accel (accel_path);
|
|
default_accel = _moo_get_default_accel (accel_path);
|
|
if (!accel) accel = "";
|
|
if (!default_accel) default_accel = "";
|
|
|
|
if (strcmp (accel, default_accel))
|
|
_moo_prefs_set_accel (accel_path, accel);
|
|
else
|
|
_moo_prefs_set_accel (accel_path, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
accel_map_changed (G_GNUC_UNUSED GtkAccelMap *object,
|
|
gchar *accel_path,
|
|
guint accel_key,
|
|
GdkModifierType accel_mods)
|
|
{
|
|
char *accel;
|
|
const char *old_accel;
|
|
const char *default_accel;
|
|
|
|
init_accel_map ();
|
|
|
|
old_accel = _moo_get_accel (accel_path);
|
|
default_accel = _moo_get_default_accel (accel_path);
|
|
|
|
if (!old_accel)
|
|
return;
|
|
|
|
if (accel_key)
|
|
accel = gtk_accelerator_name (accel_key, accel_mods);
|
|
else
|
|
accel = g_strdup ("");
|
|
|
|
g_return_if_fail (accel != NULL);
|
|
|
|
if (strcmp (accel, old_accel))
|
|
g_hash_table_insert (moo_accel_map,
|
|
g_strdup (accel_path),
|
|
g_strdup (accel));
|
|
|
|
sync_accel_prefs (accel_path);
|
|
|
|
g_free (accel);
|
|
}
|
|
|
|
|
|
static void
|
|
watch_gtk_accel_map (void)
|
|
{
|
|
GtkAccelMap *accel_map = gtk_accel_map_get ();
|
|
g_return_if_fail (accel_map != NULL);
|
|
g_signal_connect (accel_map, "changed",
|
|
G_CALLBACK (accel_map_changed), NULL);
|
|
}
|
|
|
|
static void
|
|
block_watch_gtk_accel_map (void)
|
|
{
|
|
GtkAccelMap *accel_map = gtk_accel_map_get ();
|
|
g_return_if_fail (accel_map != NULL);
|
|
g_signal_handlers_block_by_func (accel_map,
|
|
(gpointer) accel_map_changed,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
unblock_watch_gtk_accel_map (void)
|
|
{
|
|
GtkAccelMap *accel_map = gtk_accel_map_get ();
|
|
g_return_if_fail (accel_map != NULL);
|
|
g_signal_handlers_unblock_by_func (accel_map,
|
|
(gpointer) accel_map_changed,
|
|
NULL);
|
|
}
|
|
#else /* !GTK_CHECK_VERSION(2,4,0) */
|
|
static void
|
|
watch_gtk_accel_map (void)
|
|
{
|
|
}
|
|
|
|
static void
|
|
block_watch_gtk_accel_map (void)
|
|
{
|
|
}
|
|
|
|
static void
|
|
unblock_watch_gtk_accel_map (void)
|
|
{
|
|
}
|
|
#endif /* !GTK_CHECK_VERSION(2,4,0) */
|
|
|
|
|
|
char*
|
|
_moo_get_accel_label (const char *accel)
|
|
{
|
|
guint key;
|
|
GdkModifierType mods;
|
|
|
|
g_return_val_if_fail (accel != NULL, g_strdup (""));
|
|
|
|
init_accel_map ();
|
|
|
|
if (*accel)
|
|
{
|
|
gtk_accelerator_parse (accel, &key, &mods);
|
|
return gtk_accelerator_get_label (key, mods);
|
|
}
|
|
else
|
|
{
|
|
return g_strdup ("");
|
|
}
|
|
}
|
|
|
|
|
|
char*
|
|
_moo_get_accel_label_by_path (const char *accel_path)
|
|
{
|
|
g_return_val_if_fail (accel_path != NULL, g_strdup (""));
|
|
return _moo_get_accel_label (_moo_get_accel (accel_path));
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Parsing accelerator strings
|
|
*/
|
|
|
|
/* TODO: multibyte symbols? */
|
|
inline static guint
|
|
keyval_from_symbol (char sym)
|
|
{
|
|
switch (sym)
|
|
{
|
|
case ' ': return GDK_space;
|
|
case '!': return GDK_exclam;
|
|
case '"': return GDK_quotedbl;
|
|
case '#': return GDK_numbersign;
|
|
case '$': return GDK_dollar;
|
|
case '%': return GDK_percent;
|
|
case '&': return GDK_ampersand;
|
|
case '\'': return GDK_apostrophe;
|
|
// case '\'': return GDK_quoteright;
|
|
case '(': return GDK_parenleft;
|
|
case ')': return GDK_parenright;
|
|
case '*': return GDK_asterisk;
|
|
case '+': return GDK_plus;
|
|
case ',': return GDK_comma;
|
|
case '-': return GDK_minus;
|
|
case '.': return GDK_period;
|
|
case '/': return GDK_slash;
|
|
case ':': return GDK_colon;
|
|
case ';': return GDK_semicolon;
|
|
case '<': return GDK_less;
|
|
case '=': return GDK_equal;
|
|
case '>': return GDK_greater;
|
|
case '?': return GDK_question;
|
|
case '@': return GDK_at;
|
|
case '[': return GDK_bracketleft;
|
|
case '\\': return GDK_backslash;
|
|
case ']': return GDK_bracketright;
|
|
// #define GDK_asciicircum 0x05e
|
|
// #define GDK_grave 0x060
|
|
case '_': return GDK_underscore;
|
|
case '`': return GDK_quoteleft;
|
|
case '{': return GDK_braceleft;
|
|
case '|': return GDK_bar;
|
|
case '}': return GDK_braceright;
|
|
case '~': return GDK_asciitilde;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
inline static guint
|
|
parse_key (const char *string)
|
|
{
|
|
char *stripped = g_strstrip (g_strdup (string));
|
|
guint key = gdk_keyval_from_name (stripped);
|
|
|
|
if (!key)
|
|
key = keyval_from_symbol (stripped[0]);
|
|
|
|
g_free (stripped);
|
|
return key;
|
|
}
|
|
|
|
|
|
inline static GdkModifierType
|
|
parse_mod (const char *string)
|
|
{
|
|
GdkModifierType mod = 0;
|
|
char *stripped;
|
|
|
|
stripped = g_strstrip (g_ascii_strdown (string, -1));
|
|
|
|
if (!strcmp (stripped, "alt"))
|
|
mod = GDK_MOD1_MASK;
|
|
else if (!strcmp (stripped, "ctl") ||
|
|
!strcmp (stripped, "ctrl") ||
|
|
!strcmp (stripped, "control"))
|
|
mod = GDK_CONTROL_MASK;
|
|
else if (!strncmp (stripped, "mod", 3) &&
|
|
1 <= stripped[3] && stripped[3] <= 5 && !stripped[4])
|
|
{
|
|
switch (stripped[3])
|
|
{
|
|
case '1': mod = GDK_MOD1_MASK; break;
|
|
case '2': mod = GDK_MOD2_MASK; break;
|
|
case '3': mod = GDK_MOD3_MASK; break;
|
|
case '4': mod = GDK_MOD4_MASK; break;
|
|
case '5': mod = GDK_MOD5_MASK; break;
|
|
}
|
|
}
|
|
else if (!strcmp (stripped, "shift") ||
|
|
!strcmp (stripped, "shft"))
|
|
mod = GDK_SHIFT_MASK;
|
|
else if (!strcmp (stripped, "release"))
|
|
mod = GDK_RELEASE_MASK;
|
|
|
|
g_free (stripped);
|
|
return mod;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
accel_parse_sep (const char *accel,
|
|
const char *sep,
|
|
guint *keyval,
|
|
GdkModifierType *modifiers)
|
|
{
|
|
char **pieces;
|
|
guint n_pieces, i;
|
|
GdkModifierType mod = 0;
|
|
guint key = 0;
|
|
|
|
pieces = g_strsplit (accel, sep, 0);
|
|
n_pieces = g_strv_length (pieces);
|
|
g_return_val_if_fail (n_pieces > 1, FALSE);
|
|
|
|
for (i = 0; i < n_pieces - 1; ++i)
|
|
{
|
|
GdkModifierType m = parse_mod (pieces[i]);
|
|
|
|
if (!m)
|
|
goto out;
|
|
|
|
mod |= m;
|
|
}
|
|
|
|
key = parse_key (pieces[n_pieces-1]);
|
|
|
|
out:
|
|
g_strfreev (pieces);
|
|
|
|
if (!key)
|
|
mod = 0;
|
|
|
|
if (keyval)
|
|
*keyval = key;
|
|
if (modifiers)
|
|
*modifiers = mod;
|
|
return key != 0;
|
|
}
|
|
|
|
|
|
gboolean
|
|
_moo_accel_parse (const char *accel,
|
|
guint *keyval,
|
|
GdkModifierType *modifiers)
|
|
{
|
|
guint key = 0;
|
|
guint len;
|
|
GdkModifierType mods = 0;
|
|
char *p;
|
|
|
|
g_return_val_if_fail (accel && accel[0], FALSE);
|
|
|
|
len = strlen (accel);
|
|
|
|
if (len == 1)
|
|
{
|
|
key = parse_key (accel);
|
|
goto out;
|
|
}
|
|
|
|
if (accel[0] == '<')
|
|
{
|
|
gtk_accelerator_parse (accel, &key, &mods);
|
|
goto out;
|
|
}
|
|
|
|
if ((p = strchr (accel, '+')) && p != accel + len - 1)
|
|
return accel_parse_sep (accel, "+", keyval, modifiers);
|
|
else if ((p = strchr (accel, '-')) && p != accel + len - 1)
|
|
return accel_parse_sep (accel, "-", keyval, modifiers);
|
|
|
|
key = parse_key (accel);
|
|
|
|
out:
|
|
if (keyval)
|
|
*keyval = gdk_keyval_to_lower (key);
|
|
if (modifiers)
|
|
*modifiers = mods;
|
|
return key != 0;
|
|
}
|
|
|
|
|
|
char *
|
|
_moo_accel_normalize (const char *accel)
|
|
{
|
|
guint key;
|
|
GdkModifierType mods;
|
|
|
|
if (!accel || !accel[0])
|
|
return NULL;
|
|
|
|
if (_moo_accel_parse (accel, &key, &mods))
|
|
{
|
|
return gtk_accelerator_name (key, mods);
|
|
}
|
|
else
|
|
{
|
|
g_warning ("could not parse accelerator '%s'", accel);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Prefs
|
|
*/
|
|
|
|
typedef enum {
|
|
NONE,
|
|
DEFAULT,
|
|
CUSTOM
|
|
} ChoiceType;
|
|
|
|
typedef struct {
|
|
char *accel;
|
|
ChoiceType choice;
|
|
} Shortcut;
|
|
|
|
static Shortcut *shortcut_new (ChoiceType choice,
|
|
const char *accel)
|
|
{
|
|
Shortcut *s = g_new (Shortcut, 1);
|
|
s->choice = choice;
|
|
s->accel = g_strdup (accel);
|
|
return s;
|
|
}
|
|
|
|
static void shortcut_free (Shortcut *s)
|
|
{
|
|
g_free (s->accel);
|
|
g_free (s);
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
GtkAction *current_action;
|
|
GtkTreeRowReference *current_row;
|
|
|
|
GtkWidget *shortcut_frame;
|
|
GtkTreeView *treeview;
|
|
GtkTreeSelection *selection;
|
|
GtkTreeStore *store;
|
|
GtkToggleButton *shortcut_default;
|
|
GtkToggleButton *shortcut_none;
|
|
GtkToggleButton *shortcut_custom;
|
|
MooAccelButton *shortcut;
|
|
GtkLabel *default_label;
|
|
|
|
GHashTable *changed; /* Gtkction* -> Shortcut* */
|
|
GPtrArray *actions; /* GtkActionGroup* */
|
|
GHashTable *groups; /* char* -> GtkTreeRowReference* */
|
|
} Stuff;
|
|
|
|
static Stuff *
|
|
stuff_new (void)
|
|
{
|
|
Stuff *s = g_new0 (Stuff, 1);
|
|
|
|
s->changed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
NULL,
|
|
(GDestroyNotify) shortcut_free);
|
|
s->actions = g_ptr_array_new ();
|
|
s->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
(GDestroyNotify) g_free,
|
|
(GDestroyNotify) gtk_tree_row_reference_free);
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
stuff_free (Stuff *s)
|
|
{
|
|
g_hash_table_destroy (s->changed);
|
|
g_hash_table_destroy (s->groups);
|
|
g_ptr_array_free (s->actions, TRUE);
|
|
g_free (s);
|
|
}
|
|
|
|
|
|
enum {
|
|
COLUMN_ACTION_NAME,
|
|
COLUMN_ACTION,
|
|
COLUMN_ACCEL,
|
|
N_COLUMNS
|
|
};
|
|
|
|
|
|
static void init (Stuff *stuff);
|
|
static void apply (Stuff *stuff);
|
|
static void tree_selection_changed (Stuff *stuff);
|
|
static void accel_set (Stuff *stuff);
|
|
static void shortcut_none_toggled (Stuff *stuff);
|
|
static void shortcut_default_toggled (Stuff *stuff);
|
|
static void shortcut_custom_toggled (Stuff *stuff);
|
|
|
|
static void
|
|
row_activated (Stuff *stuff)
|
|
{
|
|
if (GTK_WIDGET_IS_SENSITIVE (stuff->shortcut))
|
|
gtk_button_clicked (GTK_BUTTON (stuff->shortcut));
|
|
}
|
|
|
|
|
|
inline static void block_accel_set (Stuff *stuff)
|
|
{
|
|
g_signal_handlers_block_matched (stuff->shortcut, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)accel_set, 0);
|
|
}
|
|
inline static void unblock_accel_set (Stuff *stuff)
|
|
{
|
|
g_signal_handlers_unblock_matched (stuff->shortcut, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)accel_set, 0);
|
|
}
|
|
|
|
inline static void block_radio (Stuff *stuff)
|
|
{
|
|
g_signal_handlers_block_matched (stuff->shortcut_none, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_none_toggled, 0);
|
|
g_signal_handlers_block_matched (stuff->shortcut_default, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_default_toggled, 0);
|
|
g_signal_handlers_block_matched (stuff->shortcut_custom, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_custom_toggled, 0);
|
|
}
|
|
inline static void unblock_radio (Stuff *stuff)
|
|
{
|
|
g_signal_handlers_unblock_matched (stuff->shortcut_none, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_none_toggled, 0);
|
|
g_signal_handlers_unblock_matched (stuff->shortcut_default, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_default_toggled, 0);
|
|
g_signal_handlers_unblock_matched (stuff->shortcut_custom, G_SIGNAL_MATCH_FUNC,
|
|
0, 0, 0, (gpointer)shortcut_custom_toggled, 0);
|
|
}
|
|
|
|
|
|
GtkWidget*
|
|
_moo_accel_prefs_page_new (GtkActionGroup *actions)
|
|
{
|
|
GtkWidget *page, *page_content;
|
|
Stuff *stuff;
|
|
GtkCellRenderer *renderer;
|
|
GtkTreeViewColumn *column;
|
|
MooGladeXML *xml;
|
|
|
|
xml = moo_glade_xml_new_empty ();
|
|
moo_glade_xml_map_class (xml, "GtkButton", MOO_TYPE_ACCEL_BUTTON);
|
|
moo_glade_xml_parse_memory (xml, MOO_SHORTCUTS_PREFS_GLADE_UI, -1, "page");
|
|
|
|
page = moo_prefs_dialog_page_new ("Shortcuts", MOO_STOCK_KEYBOARD);
|
|
|
|
page_content = moo_glade_xml_get_widget (xml, "page");
|
|
gtk_box_pack_start (GTK_BOX (page), page_content, TRUE, TRUE, 0);
|
|
|
|
stuff = stuff_new ();
|
|
if (actions)
|
|
g_ptr_array_add (stuff->actions, actions);
|
|
|
|
g_object_set_data_full (G_OBJECT (page), "ShortcutsPrefsStuff",
|
|
stuff, (GDestroyNotify) stuff_free);
|
|
|
|
g_signal_connect_swapped (page, "apply",
|
|
G_CALLBACK (apply),
|
|
stuff);
|
|
g_signal_connect_swapped (page, "init",
|
|
G_CALLBACK (init),
|
|
stuff);
|
|
|
|
stuff->treeview = moo_glade_xml_get_widget (xml, "treeview");
|
|
gtk_tree_view_set_headers_clickable (stuff->treeview, TRUE);
|
|
gtk_tree_view_set_search_column (stuff->treeview, 0);
|
|
|
|
g_signal_connect_swapped (stuff->treeview, "row-activated",
|
|
G_CALLBACK (row_activated),
|
|
stuff);
|
|
|
|
stuff->shortcut_frame = moo_glade_xml_get_widget (xml, "shortcut_frame");
|
|
stuff->shortcut_default = moo_glade_xml_get_widget (xml, "shortcut_default");
|
|
stuff->shortcut_none = moo_glade_xml_get_widget (xml, "shortcut_none");
|
|
stuff->shortcut_custom = moo_glade_xml_get_widget (xml, "shortcut_custom");
|
|
stuff->shortcut = moo_glade_xml_get_widget (xml, "shortcut");
|
|
stuff->default_label = moo_glade_xml_get_widget (xml, "default_label");
|
|
|
|
g_object_unref (xml);
|
|
|
|
stuff->store = gtk_tree_store_new (N_COLUMNS,
|
|
G_TYPE_STRING,
|
|
GTK_TYPE_ACTION,
|
|
G_TYPE_STRING);
|
|
gtk_tree_view_set_model (stuff->treeview,
|
|
GTK_TREE_MODEL (stuff->store));
|
|
g_object_unref (G_OBJECT (stuff->store));
|
|
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
column = gtk_tree_view_column_new_with_attributes ("Action", renderer,
|
|
"text", COLUMN_ACTION_NAME,
|
|
NULL);
|
|
gtk_tree_view_append_column (stuff->treeview, column);
|
|
gtk_tree_view_column_set_sort_column_id (column, COLUMN_ACTION_NAME);
|
|
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
column = gtk_tree_view_column_new_with_attributes ("Shortcut", renderer,
|
|
"text", COLUMN_ACCEL,
|
|
NULL);
|
|
gtk_tree_view_append_column (stuff->treeview, column);
|
|
gtk_tree_view_column_set_sort_column_id (column, COLUMN_ACCEL);
|
|
|
|
stuff->selection = gtk_tree_view_get_selection (stuff->treeview);
|
|
gtk_tree_selection_set_mode (stuff->selection, GTK_SELECTION_SINGLE);
|
|
|
|
gtk_tree_view_set_headers_clickable (stuff->treeview, TRUE);
|
|
|
|
g_signal_connect_swapped (stuff->selection, "changed",
|
|
G_CALLBACK (tree_selection_changed),
|
|
stuff);
|
|
|
|
g_signal_connect_swapped (stuff->shortcut, "accel-set",
|
|
G_CALLBACK (accel_set),
|
|
stuff);
|
|
g_signal_connect_swapped (stuff->shortcut_none, "toggled",
|
|
G_CALLBACK (shortcut_none_toggled),
|
|
stuff);
|
|
g_signal_connect_swapped (stuff->shortcut_default, "toggled",
|
|
G_CALLBACK (shortcut_default_toggled),
|
|
stuff);
|
|
g_signal_connect_swapped (stuff->shortcut_custom, "toggled",
|
|
G_CALLBACK (shortcut_custom_toggled),
|
|
stuff);
|
|
|
|
return page;
|
|
}
|
|
|
|
|
|
static void apply_one (GtkAction *action,
|
|
Shortcut *shortcut,
|
|
G_GNUC_UNUSED Stuff *stuff)
|
|
{
|
|
const char *accel_path = _moo_action_get_accel_path (action);
|
|
const char *accel = _moo_get_accel (accel_path);
|
|
const char *default_accel = _moo_get_default_accel (accel_path);
|
|
|
|
switch (shortcut->choice)
|
|
{
|
|
case NONE:
|
|
if (accel[0])
|
|
_moo_set_accel (accel_path, "");
|
|
_moo_prefs_set_accel (accel_path, "");
|
|
break;
|
|
|
|
case CUSTOM:
|
|
if (strcmp (accel, shortcut->accel))
|
|
_moo_set_accel (accel_path, shortcut->accel);
|
|
_moo_prefs_set_accel (accel_path, shortcut->accel);
|
|
break;
|
|
|
|
case DEFAULT:
|
|
if (strcmp (accel, default_accel))
|
|
_moo_set_accel (accel_path, default_accel);
|
|
_moo_prefs_set_accel (accel_path, NULL);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void apply (Stuff *stuff)
|
|
{
|
|
g_hash_table_foreach (stuff->changed,
|
|
(GHFunc) apply_one,
|
|
stuff);
|
|
g_hash_table_foreach_remove (stuff->changed,
|
|
(GHRFunc) gtk_true,
|
|
NULL);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
add_row (GtkActionGroup *group,
|
|
GtkAction *action,
|
|
Stuff *stuff)
|
|
{
|
|
const char *group_name;
|
|
char *accel;
|
|
const char *name;
|
|
GtkTreeIter iter;
|
|
GtkTreeRowReference *row;
|
|
|
|
if (moo_action_get_no_accel (action))
|
|
return FALSE;
|
|
|
|
group_name = gtk_action_group_get_name (group);
|
|
|
|
if (!group_name)
|
|
group_name = "";
|
|
|
|
row = g_hash_table_lookup (stuff->groups, group_name);
|
|
|
|
if (row)
|
|
{
|
|
GtkTreeIter parent;
|
|
GtkTreePath *path = gtk_tree_row_reference_get_path (row);
|
|
if (!path || !gtk_tree_model_get_iter (GTK_TREE_MODEL (stuff->store), &parent, path))
|
|
{
|
|
g_critical ("%s: got invalid path for group %s",
|
|
G_STRLOC, group_name);
|
|
gtk_tree_path_free (path);
|
|
return FALSE;
|
|
}
|
|
gtk_tree_path_free (path);
|
|
|
|
gtk_tree_store_append (stuff->store, &iter, &parent);
|
|
}
|
|
else
|
|
{
|
|
GtkTreeIter group;
|
|
GtkTreePath *path;
|
|
|
|
gtk_tree_store_append (stuff->store, &group, NULL);
|
|
gtk_tree_store_set (stuff->store, &group,
|
|
COLUMN_ACTION_NAME, group_name,
|
|
COLUMN_ACTION, NULL,
|
|
COLUMN_ACCEL, NULL,
|
|
-1);
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (stuff->store), &group);
|
|
g_hash_table_insert (stuff->groups,
|
|
g_strdup (group_name),
|
|
gtk_tree_row_reference_new (GTK_TREE_MODEL (stuff->store), path));
|
|
gtk_tree_path_free (path);
|
|
|
|
gtk_tree_store_append (stuff->store, &iter, &group);
|
|
}
|
|
|
|
accel = _moo_get_accel_label_by_path (_moo_action_get_accel_path (action));
|
|
name = moo_action_get_display_name (action);
|
|
|
|
gtk_tree_store_set (stuff->store, &iter,
|
|
COLUMN_ACTION_NAME, name,
|
|
COLUMN_ACTION, action,
|
|
COLUMN_ACCEL, accel,
|
|
-1);
|
|
g_free (accel);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
init (Stuff *stuff)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < stuff->actions->len; ++i)
|
|
{
|
|
GtkActionGroup *group = g_ptr_array_index (stuff->actions, i);
|
|
GList *list = gtk_action_group_list_actions (group);
|
|
|
|
while (list)
|
|
{
|
|
add_row (group, list->data, stuff);
|
|
list = g_list_delete_link (list, list);
|
|
}
|
|
}
|
|
|
|
gtk_tree_view_expand_all (stuff->treeview);
|
|
tree_selection_changed (stuff);
|
|
}
|
|
|
|
|
|
static void
|
|
tree_selection_changed (Stuff *stuff)
|
|
{
|
|
gboolean selected_action = FALSE;
|
|
GtkTreeIter iter;
|
|
GtkAction *action = NULL;
|
|
GtkTreePath *path;
|
|
char *default_label;
|
|
const char *default_accel;
|
|
Shortcut *shortcut;
|
|
|
|
if (gtk_tree_selection_get_selected (stuff->selection, NULL, &iter))
|
|
{
|
|
gtk_tree_model_get (GTK_TREE_MODEL (stuff->store), &iter,
|
|
COLUMN_ACTION, &action, -1);
|
|
if (action)
|
|
selected_action = TRUE;
|
|
}
|
|
|
|
stuff->current_action = action;
|
|
gtk_tree_row_reference_free (stuff->current_row);
|
|
stuff->current_row = NULL;
|
|
|
|
if (!selected_action)
|
|
{
|
|
gtk_label_set_text (stuff->default_label, "");
|
|
gtk_widget_set_sensitive (stuff->shortcut_frame, FALSE);
|
|
return;
|
|
}
|
|
|
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (stuff->store), &iter);
|
|
stuff->current_row = gtk_tree_row_reference_new (GTK_TREE_MODEL (stuff->store), path);
|
|
gtk_tree_path_free (path);
|
|
|
|
gtk_widget_set_sensitive (stuff->shortcut_frame, TRUE);
|
|
|
|
default_accel = moo_action_get_default_accel (action);
|
|
default_label = _moo_get_accel_label (default_accel);
|
|
|
|
if (!default_label || !default_label[0])
|
|
gtk_label_set_text (stuff->default_label, "None");
|
|
else
|
|
gtk_label_set_text (stuff->default_label, default_label);
|
|
|
|
block_radio (stuff);
|
|
block_accel_set (stuff);
|
|
|
|
shortcut = g_hash_table_lookup (stuff->changed, action);
|
|
|
|
if (shortcut)
|
|
{
|
|
switch (shortcut->choice)
|
|
{
|
|
case NONE:
|
|
gtk_toggle_button_set_active (stuff->shortcut_none, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, NULL);
|
|
break;
|
|
|
|
case DEFAULT:
|
|
gtk_toggle_button_set_active (stuff->shortcut_default, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, default_accel);
|
|
break;
|
|
|
|
case CUSTOM:
|
|
gtk_toggle_button_set_active (stuff->shortcut_custom, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, shortcut->accel);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *accel = _moo_action_get_accel (action);
|
|
|
|
if (!accel[0])
|
|
{
|
|
gtk_toggle_button_set_active (stuff->shortcut_none, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, NULL);
|
|
}
|
|
else if (!strcmp (accel, default_accel))
|
|
{
|
|
gtk_toggle_button_set_active (stuff->shortcut_default, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, default_accel);
|
|
}
|
|
else
|
|
{
|
|
gtk_toggle_button_set_active (stuff->shortcut_custom, TRUE);
|
|
moo_accel_button_set_accel (stuff->shortcut, accel);
|
|
}
|
|
}
|
|
|
|
unblock_radio (stuff);
|
|
unblock_accel_set (stuff);
|
|
|
|
g_free (default_label);
|
|
g_object_unref (action);
|
|
}
|
|
|
|
|
|
static void accel_set (Stuff *stuff)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
const char *accel;
|
|
const char *label;
|
|
|
|
g_return_if_fail (stuff->current_row != NULL && stuff->current_action != NULL);
|
|
|
|
block_radio (stuff);
|
|
gtk_toggle_button_set_active (stuff->shortcut_custom, TRUE);
|
|
unblock_radio (stuff);
|
|
|
|
path = gtk_tree_row_reference_get_path (stuff->current_row);
|
|
if (!path || !gtk_tree_model_get_iter (GTK_TREE_MODEL (stuff->store), &iter, path))
|
|
{
|
|
g_critical ("%s: got invalid path", G_STRLOC);
|
|
gtk_tree_path_free (path);
|
|
return;
|
|
}
|
|
gtk_tree_path_free (path);
|
|
|
|
accel = moo_accel_button_get_accel (stuff->shortcut);
|
|
label = gtk_button_get_label (GTK_BUTTON (stuff->shortcut));
|
|
gtk_tree_store_set (stuff->store, &iter, COLUMN_ACCEL, label, -1);
|
|
|
|
if (accel && accel[0])
|
|
g_hash_table_insert (stuff->changed,
|
|
stuff->current_action,
|
|
shortcut_new (CUSTOM, accel));
|
|
else
|
|
g_hash_table_insert (stuff->changed,
|
|
stuff->current_action,
|
|
shortcut_new (NONE, ""));
|
|
}
|
|
|
|
|
|
static void shortcut_none_toggled (Stuff *stuff)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
|
|
g_return_if_fail (stuff->current_row != NULL && stuff->current_action != NULL);
|
|
|
|
if (!gtk_toggle_button_get_active (stuff->shortcut_none))
|
|
return;
|
|
|
|
path = gtk_tree_row_reference_get_path (stuff->current_row);
|
|
if (!path || !gtk_tree_model_get_iter (GTK_TREE_MODEL (stuff->store), &iter, path))
|
|
{
|
|
g_critical ("%s: got invalid path", G_STRLOC);
|
|
gtk_tree_path_free (path);
|
|
return;
|
|
}
|
|
gtk_tree_path_free (path);
|
|
|
|
gtk_tree_store_set (stuff->store, &iter, COLUMN_ACCEL, NULL, -1);
|
|
g_hash_table_insert (stuff->changed,
|
|
stuff->current_action,
|
|
shortcut_new (NONE, ""));
|
|
block_accel_set (stuff);
|
|
moo_accel_button_set_accel (stuff->shortcut, "");
|
|
unblock_accel_set (stuff);
|
|
}
|
|
|
|
|
|
static void shortcut_default_toggled (Stuff *stuff)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
Shortcut *current_shortcut;
|
|
const char *default_accel;
|
|
|
|
g_return_if_fail (stuff->current_row != NULL && stuff->current_action != NULL);
|
|
|
|
if (!gtk_toggle_button_get_active (stuff->shortcut_default))
|
|
return;
|
|
|
|
path = gtk_tree_row_reference_get_path (stuff->current_row);
|
|
|
|
if (!path || !gtk_tree_model_get_iter (GTK_TREE_MODEL (stuff->store), &iter, path))
|
|
{
|
|
g_critical ("%s: got invalid path", G_STRLOC);
|
|
gtk_tree_path_free (path);
|
|
return;
|
|
}
|
|
|
|
gtk_tree_path_free (path);
|
|
|
|
current_shortcut = g_hash_table_lookup (stuff->changed,
|
|
stuff->current_action);
|
|
if (!current_shortcut)
|
|
{
|
|
current_shortcut = shortcut_new (NONE, "");
|
|
g_hash_table_insert (stuff->changed,
|
|
stuff->current_action,
|
|
current_shortcut);
|
|
}
|
|
|
|
default_accel = moo_action_get_default_accel (stuff->current_action);
|
|
|
|
if (default_accel[0])
|
|
{
|
|
char *label = _moo_get_accel_label (default_accel);
|
|
gtk_tree_store_set (stuff->store, &iter, COLUMN_ACCEL, label, -1);
|
|
g_free (label);
|
|
}
|
|
else
|
|
{
|
|
gtk_tree_store_set (stuff->store, &iter, COLUMN_ACCEL, NULL, -1);
|
|
}
|
|
|
|
current_shortcut->choice = DEFAULT;
|
|
g_free (current_shortcut->accel);
|
|
current_shortcut->accel = g_strdup (default_accel);
|
|
block_accel_set (stuff);
|
|
moo_accel_button_set_accel (stuff->shortcut, default_accel);
|
|
unblock_accel_set (stuff);
|
|
}
|
|
|
|
|
|
static void shortcut_custom_toggled (Stuff *stuff)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
Shortcut *shortcut;
|
|
|
|
g_return_if_fail (stuff->current_row != NULL && stuff->current_action != NULL);
|
|
|
|
if (!gtk_toggle_button_get_active (stuff->shortcut_custom))
|
|
return;
|
|
|
|
path = gtk_tree_row_reference_get_path (stuff->current_row);
|
|
|
|
if (!path || !gtk_tree_model_get_iter (GTK_TREE_MODEL (stuff->store), &iter, path))
|
|
{
|
|
g_critical ("%s: got invalid path", G_STRLOC);
|
|
gtk_tree_path_free (path);
|
|
return;
|
|
}
|
|
|
|
gtk_tree_path_free (path);
|
|
|
|
shortcut = g_hash_table_lookup (stuff->changed, stuff->current_action);
|
|
|
|
if (shortcut)
|
|
{
|
|
block_accel_set (stuff);
|
|
moo_accel_button_set_accel (stuff->shortcut, shortcut->accel);
|
|
unblock_accel_set (stuff);
|
|
gtk_tree_store_set (stuff->store, &iter, COLUMN_ACCEL,
|
|
gtk_button_get_label (GTK_BUTTON (stuff->shortcut)),
|
|
-1);
|
|
shortcut->choice = CUSTOM;
|
|
}
|
|
else
|
|
{
|
|
const char *accel = _moo_action_get_accel (stuff->current_action);
|
|
g_hash_table_insert (stuff->changed,
|
|
stuff->current_action,
|
|
shortcut_new (CUSTOM, accel));
|
|
block_accel_set (stuff);
|
|
moo_accel_button_set_accel (stuff->shortcut, accel);
|
|
unblock_accel_set (stuff);
|
|
}
|
|
}
|
|
|
|
|
|
static void dialog_response (GObject *page,
|
|
int response)
|
|
{
|
|
switch (response)
|
|
{
|
|
case GTK_RESPONSE_OK:
|
|
g_signal_emit_by_name (page, "apply", NULL);
|
|
break;
|
|
|
|
case GTK_RESPONSE_REJECT:
|
|
g_signal_emit_by_name (page, "set_defaults", NULL);
|
|
break;
|
|
};
|
|
}
|
|
|
|
|
|
GtkWidget*
|
|
_moo_accel_prefs_dialog_new (GtkActionGroup *group)
|
|
{
|
|
GtkWidget *page, *dialog, *page_holder;
|
|
MooGladeXML *xml;
|
|
|
|
xml = moo_glade_xml_new_from_buf (MOO_SHORTCUTS_PREFS_GLADE_UI, -1, "dialog");
|
|
g_return_val_if_fail (xml != NULL, NULL);
|
|
|
|
dialog = moo_glade_xml_get_widget (xml, "dialog");
|
|
|
|
page = _moo_accel_prefs_page_new (group);
|
|
gtk_widget_show (page);
|
|
page_holder = moo_glade_xml_get_widget (xml, "page_holder");
|
|
gtk_container_add (GTK_CONTAINER (page_holder), page);
|
|
|
|
g_object_unref (xml);
|
|
|
|
#if GTK_MINOR_VERSION >= 6
|
|
gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
|
|
GTK_RESPONSE_OK,
|
|
GTK_RESPONSE_CANCEL,
|
|
GTK_RESPONSE_REJECT,
|
|
-1);
|
|
#endif /* GTK_MINOR_VERSION >= 6 */
|
|
|
|
g_signal_connect_swapped (dialog, "response",
|
|
G_CALLBACK (dialog_response), page);
|
|
g_signal_emit_by_name (page, "init", NULL);
|
|
|
|
return dialog;
|
|
}
|
|
|
|
|
|
void
|
|
_moo_accel_prefs_dialog_run (GtkActionGroup *group,
|
|
GtkWidget *parent)
|
|
{
|
|
GtkWidget *dialog = _moo_accel_prefs_dialog_new (group);
|
|
GtkWindow *parent_window = GTK_WINDOW (gtk_widget_get_toplevel (parent));
|
|
|
|
gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
|
|
|
|
while (TRUE)
|
|
{
|
|
int response = gtk_dialog_run (GTK_DIALOG (dialog));
|
|
if (response != GTK_RESPONSE_REJECT)
|
|
break;
|
|
}
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
#ifdef __WIN32__ /* TODO */
|
|
gtk_window_present (parent_window);
|
|
#endif /* __WIN32__ */
|
|
|
|
return;
|
|
}
|