/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2008 Tristan Van Berkom * * 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. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Authors: * Tristan Van Berkom */ #include #include #include #include #include #include "glade-accels.h" #define GLADE_RESPONSE_CLEAR 42 GList * glade_accel_list_copy (GList *accels) { GList *ret = NULL, *list; GladeAccelInfo *info, *dup_info; for (list = accels; list; list = list->next) { info = list->data; dup_info = g_new0 (GladeAccelInfo, 1); dup_info->signal = g_strdup (info->signal); dup_info->key = info->key; dup_info->modifiers = info->modifiers; ret = g_list_prepend (ret, dup_info); } return g_list_reverse (ret); } void glade_accel_list_free (GList *accels) { GList *list; GladeAccelInfo *info; for (list = accels; list; list = list->next) { info = list->data; g_free (info->signal); g_free (info); } g_list_free (accels); } GType glade_accel_glist_get_type (void) { static GType type_id = 0; if (!type_id) type_id = g_boxed_type_register_static ("GladeAccelGList", (GBoxedCopyFunc) glade_accel_list_copy, (GBoxedFreeFunc) glade_accel_list_free); return type_id; } /* This is not used to save in the glade file... and its a one-way conversion. * its only usefull to show the values in the UI. */ gchar * glade_accels_make_string (GList *accels) { GladeAccelInfo *info; GString *string; GList *list; gchar *accel_text; string = g_string_new (""); for (list = accels; list; list = list->next) { info = list->data; accel_text = gtk_accelerator_name (info->key, info->modifiers); g_string_append (string, accel_text); g_free (accel_text); if (list->next) g_string_append (string, ", "); } return g_string_free (string, FALSE); } /************************************************************** * GladeEditorProperty stuff here **************************************************************/ enum { ACCEL_COLUMN_SIGNAL = 0, ACCEL_COLUMN_REAL_SIGNAL, ACCEL_COLUMN_TEXT, ACCEL_COLUMN_WEIGHT, ACCEL_COLUMN_STYLE, ACCEL_COLUMN_FOREGROUND, ACCEL_COLUMN_VISIBLE, ACCEL_COLUMN_KEY_ENTERED, ACCEL_COLUMN_KEYCODE, ACCEL_COLUMN_MODIFIERS, ACCEL_NUM_COLUMNS }; typedef struct { GladeEditorProperty parent_instance; GtkWidget *entry; GList *parent_iters; GtkTreeModel *model; } GladeEPropAccel; typedef struct { GtkTreeIter *iter; gchar *name; /* <-- dont free */ } GladeEpropIterTab; GLADE_MAKE_EPROP (GladeEPropAccel, glade_eprop_accel) #define GLADE_EPROP_ACCEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_EPROP_ACCEL, GladeEPropAccel)) #define GLADE_EPROP_ACCEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_EPROP_ACCEL, GladeEPropAccelClass)) #define GLADE_IS_EPROP_ACCEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_EPROP_ACCEL)) #define GLADE_IS_EPROP_ACCEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_EPROP_ACCEL)) #define GLADE_EPROP_ACCEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GLADE_EPROP_ACCEL, GladeEPropAccelClass)) static void glade_eprop_accel_finalize (GObject *object) { /* Chain up */ GObjectClass *parent_class = g_type_class_peek_parent (G_OBJECT_GET_CLASS (object)); G_OBJECT_CLASS (parent_class)->finalize (object); } static void glade_eprop_accel_load (GladeEditorProperty *eprop, GladeProperty *property) { GladeEditorPropertyClass *parent_class = g_type_class_peek_parent (GLADE_EDITOR_PROPERTY_GET_CLASS (eprop)); GladeEPropAccel *eprop_accel = GLADE_EPROP_ACCEL (eprop); gchar *accels; /* Chain up first */ parent_class->load (eprop, property); if (property == NULL) return; if ((accels = glade_accels_make_string (g_value_get_boxed (property->value))) != NULL) { gtk_entry_set_text (GTK_ENTRY (eprop_accel->entry), accels); g_free (accels); } else gtk_entry_set_text (GTK_ENTRY (eprop_accel->entry), ""); } static gint eprop_find_iter (GladeEpropIterTab *iter_tab, gchar *name) { return strcmp (iter_tab->name, name); } static void iter_tab_free (GladeEpropIterTab *iter_tab) { gtk_tree_iter_free (iter_tab->iter); g_free (iter_tab); } static void glade_eprop_accel_populate_view (GladeEditorProperty *eprop, GtkTreeView *view) { GladeEPropAccel *eprop_accel = GLADE_EPROP_ACCEL (eprop); GladeSignalClass *sclass; GladeWidgetAdaptor *adaptor = glade_widget_adaptor_from_pclass (eprop->klass); GtkTreeStore *model = (GtkTreeStore *)gtk_tree_view_get_model (view); GtkTreeIter iter; GladeEpropIterTab *parent_tab; GladeAccelInfo *info; GList *list, *l, *found, *accelerators; gchar *name, *accel_text; accelerators = g_value_get_boxed (eprop->property->value); /* First make parent iters... */ for (list = adaptor->signals; list; list = list->next) { sclass = list->data; /* Only action signals have accelerators. */ if ((sclass->query.signal_flags & G_SIGNAL_ACTION) == 0) continue; if (g_list_find_custom (eprop_accel->parent_iters, sclass->type, (GCompareFunc)eprop_find_iter) == NULL) { gtk_tree_store_append (model, &iter, NULL); gtk_tree_store_set (model, &iter, ACCEL_COLUMN_SIGNAL, sclass->type, ACCEL_COLUMN_WEIGHT, PANGO_WEIGHT_BOLD, ACCEL_COLUMN_VISIBLE, FALSE, -1); parent_tab = g_new0 (GladeEpropIterTab, 1); parent_tab->name = sclass->type; parent_tab->iter = gtk_tree_iter_copy (&iter); eprop_accel->parent_iters = g_list_prepend (eprop_accel->parent_iters, parent_tab); } } /* Now we populate... */ for (list = adaptor->signals; list; list = list->next) { sclass = list->data; /* Only action signals have accelerators. */ if ((sclass->query.signal_flags & G_SIGNAL_ACTION) == 0) continue; if ((found = g_list_find_custom (eprop_accel->parent_iters, sclass->type, (GCompareFunc)eprop_find_iter)) != NULL) { parent_tab = found->data; name = g_strdup_printf (" %s", sclass->name); /* Populate from accelerator list */ for (l = accelerators; l; l = l->next) { info = l->data; if (strcmp (info->signal, sclass->name)) continue; accel_text = gtk_accelerator_name (info->key, info->modifiers); gtk_tree_store_append (model, &iter, parent_tab->iter); gtk_tree_store_set (model, &iter, ACCEL_COLUMN_SIGNAL, name, ACCEL_COLUMN_REAL_SIGNAL, sclass->name, ACCEL_COLUMN_TEXT, accel_text, ACCEL_COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, ACCEL_COLUMN_STYLE, PANGO_STYLE_NORMAL, ACCEL_COLUMN_FOREGROUND, "Black", ACCEL_COLUMN_VISIBLE, TRUE, ACCEL_COLUMN_KEYCODE, info->key, ACCEL_COLUMN_MODIFIERS, info->modifiers, ACCEL_COLUMN_KEY_ENTERED, TRUE, -1); g_free (accel_text); } /* Append a new empty slot at the end */ gtk_tree_store_append (model, &iter, parent_tab->iter); gtk_tree_store_set (model, &iter, ACCEL_COLUMN_SIGNAL, name, ACCEL_COLUMN_REAL_SIGNAL, sclass->name, ACCEL_COLUMN_TEXT, _(""), ACCEL_COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, ACCEL_COLUMN_STYLE, PANGO_STYLE_ITALIC, ACCEL_COLUMN_FOREGROUND, "Grey", ACCEL_COLUMN_VISIBLE, TRUE, ACCEL_COLUMN_KEYCODE, 0, ACCEL_COLUMN_MODIFIERS, 0, ACCEL_COLUMN_KEY_ENTERED, FALSE, -1); g_free (name); } } } void accel_edited (GtkCellRendererAccel *accel, gchar *path_string, guint accel_key, GdkModifierType accel_mods, guint hardware_keycode, GladeEPropAccel *eprop_accel) { gboolean key_was_set; GtkTreeIter iter, parent_iter, new_iter; gchar *accel_text; if (!gtk_tree_model_get_iter_from_string (eprop_accel->model, &iter, path_string)) return; gtk_tree_model_get (eprop_accel->model, &iter, ACCEL_COLUMN_KEY_ENTERED, &key_was_set, -1); accel_text = gtk_accelerator_name (accel_key, accel_mods); gtk_tree_store_set (GTK_TREE_STORE (eprop_accel->model), &iter, ACCEL_COLUMN_KEY_ENTERED, TRUE, ACCEL_COLUMN_STYLE, PANGO_STYLE_NORMAL, ACCEL_COLUMN_FOREGROUND, "Black", ACCEL_COLUMN_TEXT, accel_text, ACCEL_COLUMN_KEYCODE, accel_key, ACCEL_COLUMN_MODIFIERS, accel_mods, -1); g_free (accel_text); /* Append a new one if needed */ if (key_was_set == FALSE && gtk_tree_model_iter_parent (eprop_accel->model, &parent_iter, &iter)) { gchar *signal, *real_signal; gtk_tree_model_get (eprop_accel->model, &iter, ACCEL_COLUMN_SIGNAL, &signal, ACCEL_COLUMN_REAL_SIGNAL, &real_signal, -1); /* Append a new empty slot at the end */ gtk_tree_store_insert_after (GTK_TREE_STORE (eprop_accel->model), &new_iter, &parent_iter, &iter); gtk_tree_store_set (GTK_TREE_STORE (eprop_accel->model), &new_iter, ACCEL_COLUMN_SIGNAL, signal, ACCEL_COLUMN_REAL_SIGNAL, real_signal, ACCEL_COLUMN_TEXT, _(""), ACCEL_COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, ACCEL_COLUMN_STYLE, PANGO_STYLE_ITALIC, ACCEL_COLUMN_FOREGROUND, "Grey", ACCEL_COLUMN_VISIBLE, TRUE, ACCEL_COLUMN_KEYCODE, 0, ACCEL_COLUMN_MODIFIERS, 0, ACCEL_COLUMN_KEY_ENTERED, FALSE, -1); g_free (signal); g_free (real_signal); } } void accel_cleared (GtkCellRendererAccel *accel, gchar *path_string, GladeEPropAccel *eprop_accel) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_from_string (eprop_accel->model, &iter, path_string)) return; gtk_tree_store_remove (GTK_TREE_STORE (eprop_accel->model), &iter); } static GtkWidget * glade_eprop_accel_view (GladeEditorProperty *eprop) { GladeEPropAccel *eprop_accel = GLADE_EPROP_ACCEL (eprop); GtkWidget *view_widget; GtkCellRenderer *renderer; GtkTreeViewColumn *column; eprop_accel->model = (GtkTreeModel *)gtk_tree_store_new (ACCEL_NUM_COLUMNS, G_TYPE_STRING, /* The GSignal name formatted for display */ G_TYPE_STRING, /* The GSignal name */ G_TYPE_STRING, /* The text to show in the accelerator cell */ G_TYPE_INT, /* PangoWeight attribute for bold headers */ G_TYPE_INT, /* PangoStyle attribute for italic grey unset items */ G_TYPE_STRING, /* Foreground colour for italic grey unset items */ G_TYPE_BOOLEAN, /* Visible attribute to hide items for header entries */ G_TYPE_BOOLEAN, /* Whether the key has been entered for this row */ G_TYPE_UINT, /* Hardware keycode */ G_TYPE_INT); /* GdkModifierType */ view_widget = gtk_tree_view_new_with_model (eprop_accel->model); gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (view_widget), FALSE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view_widget), FALSE); /********************* signal name column *********************/ renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL); column = gtk_tree_view_column_new_with_attributes (_("Signal"), renderer, "text", ACCEL_COLUMN_SIGNAL, "weight", ACCEL_COLUMN_WEIGHT, NULL); gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (view_widget), column); /********************* accel editor column *********************/ renderer = gtk_cell_renderer_accel_new (); g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); g_signal_connect (renderer, "accel-edited", G_CALLBACK (accel_edited), eprop); g_signal_connect (renderer, "accel-cleared", G_CALLBACK (accel_cleared), eprop); column = gtk_tree_view_column_new_with_attributes (_("Accelerator Key"), renderer, "text", ACCEL_COLUMN_TEXT, "foreground", ACCEL_COLUMN_FOREGROUND, "style", ACCEL_COLUMN_STYLE, "visible", ACCEL_COLUMN_VISIBLE, NULL); gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (view_widget), column); return view_widget; } static gboolean glade_eprop_accel_accum_accelerators (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **ret) { GladeAccelInfo *info; gchar *signal; GdkModifierType accel_mods; guint accel_key; gboolean entered = FALSE; gtk_tree_model_get (model, iter, ACCEL_COLUMN_KEY_ENTERED, &entered, -1); if (!entered) return FALSE; gtk_tree_model_get (model, iter, ACCEL_COLUMN_REAL_SIGNAL, &signal, ACCEL_COLUMN_KEYCODE, &accel_key, ACCEL_COLUMN_MODIFIERS, &accel_mods, -1); info = g_new0 (GladeAccelInfo, 1); info->signal = signal; info->key = accel_key; info->modifiers = accel_mods; *ret = g_list_prepend (*ret, info); return FALSE; } static void glade_eprop_accel_show_dialog (GtkWidget *dialog_button, GladeEditorProperty *eprop) { GladeEPropAccel *eprop_accel = GLADE_EPROP_ACCEL (eprop); GtkWidget *dialog, *parent, *vbox, *sw, *tree_view; GladeProject *project; GValue value = { 0, }; GList *accelerators = NULL; gint res; project = glade_widget_get_project (eprop->property->widget); parent = gtk_widget_get_toplevel (GTK_WIDGET (eprop)); dialog = gtk_dialog_new_with_buttons (_("Choose accelerator keys..."), GTK_WINDOW (parent), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLEAR, GLADE_RESPONSE_CLEAR, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox, TRUE, TRUE, 0); sw = gtk_scrolled_window_new (NULL, NULL); gtk_widget_show (sw); gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); gtk_widget_set_size_request (sw, 400, 200); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); tree_view = glade_eprop_accel_view (eprop); glade_eprop_accel_populate_view (eprop, GTK_TREE_VIEW (tree_view)); gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); gtk_widget_show (tree_view); gtk_container_add (GTK_CONTAINER (sw), tree_view); /* Run the dialog */ res = gtk_dialog_run (GTK_DIALOG (dialog)); if (res == GTK_RESPONSE_OK) { gtk_tree_model_foreach (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)), (GtkTreeModelForeachFunc) glade_eprop_accel_accum_accelerators, &accelerators); g_value_init (&value, GLADE_TYPE_ACCEL_GLIST); g_value_take_boxed (&value, accelerators); glade_editor_property_commit (eprop, &value); g_value_unset (&value); } else if (res == GLADE_RESPONSE_CLEAR) { g_value_init (&value, GLADE_TYPE_ACCEL_GLIST); g_value_set_boxed (&value, NULL); glade_editor_property_commit (eprop, &value); g_value_unset (&value); } /* Clean up ... */ gtk_widget_destroy (dialog); g_object_unref (G_OBJECT (eprop_accel->model)); eprop_accel->model = NULL; if (eprop_accel->parent_iters) { g_list_foreach (eprop_accel->parent_iters, (GFunc)iter_tab_free, NULL); g_list_free (eprop_accel->parent_iters); eprop_accel->parent_iters = NULL; } } static GtkWidget * glade_eprop_accel_create_input (GladeEditorProperty *eprop) { GladeEPropAccel *eprop_accel = GLADE_EPROP_ACCEL (eprop); GtkWidget *hbox; GtkWidget *button; hbox = gtk_hbox_new (FALSE, 0); eprop_accel->entry = gtk_entry_new (); gtk_entry_set_editable (GTK_ENTRY (eprop_accel->entry), FALSE); gtk_widget_show (eprop_accel->entry); gtk_box_pack_start (GTK_BOX (hbox), eprop_accel->entry, TRUE, TRUE, 0); button = gtk_button_new_with_label ("..."); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (glade_eprop_accel_show_dialog), eprop); return hbox; }