/* * glade-named-icon-chooser-widget.c - Named icon chooser widget * * Copyright (C) 2007 Vincent Geddes * * Author: Vincent Geddes * * 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 * MERCHANNAMED_ICON_CHOOSERILITY 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. */ #include #include "glade-private.h" #include "glade-named-icon-chooser-dialog.h" #include "icon-naming-spec.c" #include #include #include #include #define DEFAULT_SETTING_LIST_STANDARD_ONLY TRUE enum { CONTEXTS_ID_COLUMN, CONTEXTS_NAME_COLUMN, CONTEXTS_TITLE_COLUMN, CONTEXTS_N_COLUMS }; enum { ICONS_CONTEXT_COLUMN, ICONS_STANDARD_COLUMN, ICONS_NAME_COLUMN, ICONS_N_COLUMNS }; enum { GLADE_NAMED_ICON }; enum { ICON_ACTIVATED, SELECTION_CHANGED, LAST_SIGNAL }; struct _GladeNamedIconChooserDialogPrivate { GtkWidget *icons_view; GtkTreeModel *filter_model; /* filtering model */ GtkListStore *icons_store; /* data store */ GtkTreeSelection *selection; GtkWidget *contexts_view; GtkListStore *contexts_store; GtkWidget *entry; GtkEntryCompletion *entry_completion; GtkWidget *button; /* list-standard-only checkbutton */ gint context_id; /* current icon name context for icon filtering */ gchar *pending_select_name; /* an icon name for a pending treeview selection. * can only select name after model is loaded * and the widget is mapped */ GtkIconTheme *icon_theme; /* the current icon theme */ guint load_id; /* id of the idle function for loading data into model */ gboolean settings_list_standard; /* whether to list standard icon names only */ GtkWidget *last_focus_widget; gboolean icons_loaded; /* whether the icons have been loaded into the model */ }; static GHashTable *standard_icon_quarks = NULL; static guint dialog_signals[LAST_SIGNAL] = { 0, }; gchar * glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog); void glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog, const gchar *icon_name); gboolean glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog, const gchar *context); gchar * glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog); static gboolean should_respond (GladeNamedIconChooserDialog *dialog); static void filter_icons_model (GladeNamedIconChooserDialog *dialog); static gboolean scan_for_name_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); static gboolean scan_for_context_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); static void settings_load (GladeNamedIconChooserDialog *dialog); static void settings_save (GladeNamedIconChooserDialog *dialog); G_DEFINE_TYPE_WITH_PRIVATE (GladeNamedIconChooserDialog, glade_named_icon_chooser_dialog, GTK_TYPE_DIALOG); static void entry_set_name (GladeNamedIconChooserDialog *dialog, const gchar *name) { /* Must disable completion before setting text, in order to avoid * spurious warnings (possible GTK+ bug). */ gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry), NULL); gtk_entry_set_text (GTK_ENTRY (dialog->priv->entry), name); gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry), dialog->priv->entry_completion); } static GtkIconTheme * get_icon_theme_for_widget (GtkWidget *widget) { if (gtk_widget_has_screen (widget)) return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); return gtk_icon_theme_get_default (); } /* validates name according to the icon naming spec (en_US.US_ASCII [a-z1-9_-.]) */ static gboolean is_well_formed (const gchar * name) { gchar *c = (gchar *) name; for (; *c; c++) { if (g_ascii_isalnum (*c)) { if (g_ascii_isalpha (*c) && !g_ascii_islower (*c)) return FALSE; } else if (*c != '_' && *c != '-' && *c != '.') { return FALSE; } } return TRUE; } static void check_entry_text (GladeNamedIconChooserDialog *dialog, gchar **name_ret, gboolean *is_wellformed_ret, gboolean *is_empty_ret) { if (strlen (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))) == 0) { *name_ret = NULL; *is_wellformed_ret = TRUE; *is_empty_ret = TRUE; return; } *is_empty_ret = FALSE; *is_wellformed_ret = is_well_formed (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))); if (*is_wellformed_ret) *name_ret = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))); else *name_ret = NULL; } static void changed_text_handler (GtkEditable *editable, GladeNamedIconChooserDialog *dialog) { g_signal_emit_by_name (dialog, "selection-changed", NULL); } /* ensure that only valid text can be inserted into entry */ static void insert_text_handler (GtkEditable *editable, const gchar *text, gint length, gint *position, GladeNamedIconChooserDialog *dialog) { if (is_well_formed (text)) { g_signal_handlers_block_by_func (editable, (gpointer) insert_text_handler, dialog); gtk_editable_insert_text (editable, text, length, position); g_signal_handlers_unblock_by_func (editable, (gpointer) insert_text_handler, dialog); } else { gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (dialog))); } g_signal_stop_emission_by_name (editable, "insert-text"); } typedef struct { gchar *name; /* the name of the icon or context */ guint found:1; /* whether an item matching `name' was found */ guint do_select:1; /* select the matched row */ guint do_cursor:1; /* put cursor at the matched row */ guint do_activate:1; /* activate the matched row */ GladeNamedIconChooserDialog *dialog; } ForEachFuncData; void glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog, const gchar *name) { ForEachFuncData *data; gboolean located_in_theme; g_return_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog)); g_return_if_fail (gtk_widget_has_screen (GTK_WIDGET (dialog))); if (name == NULL) { gtk_tree_selection_unselect_all (dialog->priv->selection); entry_set_name (dialog, ""); return; } located_in_theme = gtk_icon_theme_has_icon (get_icon_theme_for_widget (GTK_WIDGET (dialog)), name); if (located_in_theme) { if (dialog->priv->icons_loaded && dialog->priv->filter_model) { data = g_slice_new0 (ForEachFuncData); data->name = g_strdup (name); data->found = FALSE; data->do_activate = FALSE; data->do_select = TRUE; data->do_cursor = TRUE; data->dialog = dialog; gtk_tree_model_foreach (dialog->priv->filter_model, scan_for_name_func, data); g_free (data->name); g_slice_free (ForEachFuncData, data); } else { dialog->priv->pending_select_name = g_strdup (name); } /* selecting a treeview row will set the entry text, * but we must have this here in case the row has been filtered out */ entry_set_name (dialog, name); } else if (is_well_formed (name)) { gtk_tree_selection_unselect_all (dialog->priv->selection); entry_set_name (dialog, name); } else { g_warning ("invalid icon name: '%s' is not well formed", name); } } gboolean glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog, const gchar *name) { ForEachFuncData *data; g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), FALSE); data = g_slice_new0 (ForEachFuncData); if (name) data->name = g_strdup (name); else data->name = g_strdup ("All Contexts"); data->found = FALSE; data->do_select = TRUE; data->do_activate = FALSE; data->do_cursor = FALSE; data->dialog = dialog; gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->priv->contexts_store), (GtkTreeModelForeachFunc) scan_for_context_func, data); g_free (data->name); g_slice_free (ForEachFuncData, data); return TRUE; } gchar * glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog) { GtkTreeSelection *sel; GtkTreeIter iter; gchar *context_name; g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL); sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->contexts_view)); if (gtk_tree_selection_get_selected (sel, NULL, &iter)) { gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->contexts_store), &iter, CONTEXTS_NAME_COLUMN, &context_name, -1); /* if context_name is NULL, then it is the 'all categories' special context */ return context_name; } else { return NULL; } } static gchar * get_icon_name_from_selection (GladeNamedIconChooserDialog *dialog) { GtkTreeIter iter; GtkTreeModel *model; gchar *name; if (!gtk_tree_selection_get_selected (dialog->priv->selection, &model, &iter)) return NULL; gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1); return name; } gchar * glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog) { GtkWidget *current_focus; gchar *name; g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL); current_focus = gtk_window_get_focus (GTK_WINDOW (dialog)); if (current_focus == dialog->priv->icons_view) { view: name = get_icon_name_from_selection (dialog); if (name == NULL) goto entry; } else if (current_focus == dialog->priv->entry) { gboolean is_wellformed, is_empty; entry: check_entry_text (dialog, &name, &is_wellformed, &is_empty); if (!is_wellformed || is_empty) return NULL; } else if (dialog->priv->last_focus_widget == dialog->priv->icons_view) { goto view; } else if (dialog->priv->last_focus_widget == dialog->priv->entry) { goto entry; } else { goto view; } return name; } static void set_busy_cursor (GladeNamedIconChooserDialog *dialog, gboolean busy) { GdkDisplay *display; GdkCursor *cursor; if (!gtk_widget_get_realized (GTK_WIDGET (dialog))) return; display = gtk_widget_get_display (GTK_WIDGET (dialog)); if (busy) cursor = gdk_cursor_new_for_display (display, GDK_WATCH); else cursor = NULL; gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)), cursor); gdk_display_flush (display); if (cursor) g_object_unref (cursor); } static GtkListStore * populate_icon_contexts_model (void) { GtkListStore *store; GtkTreeIter iter; guint i; store = gtk_list_store_new (CONTEXTS_N_COLUMS, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, CONTEXTS_ID_COLUMN, -1, CONTEXTS_NAME_COLUMN, "All Contexts", CONTEXTS_TITLE_COLUMN, _("All Contexts"), -1); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, CONTEXTS_ID_COLUMN, -1, CONTEXTS_NAME_COLUMN, NULL, CONTEXTS_TITLE_COLUMN, NULL, -1); for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, CONTEXTS_ID_COLUMN, i, CONTEXTS_NAME_COLUMN, standard_contexts[i].name, CONTEXTS_TITLE_COLUMN, _(standard_contexts[i].title), -1); } return store; } static void icons_row_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GladeNamedIconChooserDialog *dialog) { g_signal_emit_by_name (dialog, "icon-activated", NULL); } static void icons_selection_changed_cb (GtkTreeSelection * selection, GladeNamedIconChooserDialog * dialog) { GtkTreeModel *model; GtkTreeIter iter; gchar *name; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1); if (name) entry_set_name (dialog, name); g_free (name); } else { /* entry_set_name (dialog, ""); */ } /* we emit "selection-changed" for chooser in insert_text_handler() * to avoid emitting the signal twice */ } static void contexts_row_activated_cb (GtkTreeView *view, GtkTreePath *cpath, GtkTreeViewColumn *column, GladeNamedIconChooserDialog *dialog) { GtkTreeIter iter; GtkTreePath *path; if (gtk_tree_model_get_iter_first (dialog->priv->filter_model, &iter)) { gtk_tree_selection_select_iter (dialog->priv->selection, &iter); path = gtk_tree_model_get_path (dialog->priv->filter_model, &iter); gtk_tree_selection_select_path (dialog->priv->selection, path); gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (dialog->priv->icons_view), -1, 0); gtk_tree_path_free (path); } gtk_widget_grab_focus (dialog->priv->icons_view); } static void contexts_selection_changed_cb (GtkTreeSelection *selection, GladeNamedIconChooserDialog *dialog) { GtkTreeIter iter; GtkTreeModel *model; gboolean retval; gint context_id; retval = gtk_tree_selection_get_selected (selection, &model, &iter); if (retval) { gtk_tree_model_get (model, &iter, CONTEXTS_ID_COLUMN, &context_id, -1); dialog->priv->context_id = context_id; if (!dialog->priv->filter_model) return; filter_icons_model (dialog); } entry_set_name (dialog, ""); } static gboolean row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer unused) { gboolean retval; gchar *name, *title; gtk_tree_model_get (model, iter, CONTEXTS_NAME_COLUMN, &name, CONTEXTS_TITLE_COLUMN, &title, -1); retval = !name && !title; g_free (name); g_free (title); return retval; } static GtkWidget * create_contexts_view (GladeNamedIconChooserDialog *dialog) { GtkTreeView *view; GtkTreeViewColumn *column; GtkTreePath *path; dialog->priv->contexts_store = populate_icon_contexts_model (); view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (dialog->priv->contexts_store))); column = gtk_tree_view_column_new_with_attributes (NULL, gtk_cell_renderer_text_new (), "text", CONTEXTS_TITLE_COLUMN, NULL); gtk_tree_view_append_column (view, column); gtk_tree_view_set_headers_visible (view, FALSE); gtk_tree_view_set_row_separator_func (view, (GtkTreeViewRowSeparatorFunc) row_separator_func, NULL, NULL); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), GTK_SELECTION_BROWSE); path = gtk_tree_path_new_from_indices (0, -1); gtk_tree_selection_select_path (gtk_tree_view_get_selection (view), path); gtk_tree_path_free (path); g_signal_connect (view, "row-activated", G_CALLBACK (contexts_row_activated_cb), dialog); g_signal_connect (gtk_tree_view_get_selection (view), "changed", G_CALLBACK (contexts_selection_changed_cb), dialog); gtk_widget_show (GTK_WIDGET (view)); return GTK_WIDGET (view); } /* filters the icons model based on the current state */ static void filter_icons_model (GladeNamedIconChooserDialog *dialog) { set_busy_cursor (dialog, TRUE); g_object_ref (dialog->priv->filter_model); gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), NULL); gtk_entry_completion_set_model (dialog->priv->entry_completion, NULL); gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (dialog->priv->filter_model)); gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), dialog->priv->filter_model); gtk_entry_completion_set_model (dialog->priv->entry_completion, GTK_TREE_MODEL (dialog->priv->icons_store)); gtk_entry_completion_set_text_column (dialog->priv->entry_completion, ICONS_NAME_COLUMN); g_object_unref (dialog->priv->filter_model); set_busy_cursor (dialog, FALSE); } static gboolean filter_visible_func (GtkTreeModel *model, GtkTreeIter *iter, GladeNamedIconChooserDialog *dialog) { gboolean standard; gint context_id; gtk_tree_model_get (model, iter, ICONS_CONTEXT_COLUMN, &context_id, ICONS_STANDARD_COLUMN, &standard, -1); if (dialog->priv->context_id == -1) return (dialog->priv->settings_list_standard) ? TRUE && standard : TRUE; if (context_id == dialog->priv->context_id) return (dialog->priv->settings_list_standard) ? TRUE && standard : TRUE; else return FALSE; } static gboolean search_equal_func (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, GladeNamedIconChooserDialog *dialog) { gchar *name; gboolean retval; gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1); retval = !g_str_has_prefix (name, key); g_free (name); return retval; } static gboolean scan_for_context_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { ForEachFuncData *data = (ForEachFuncData *) user_data; GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->dialog->priv->contexts_view)); gchar *name = NULL; gtk_tree_model_get (model, iter, CONTEXTS_NAME_COLUMN, &name, -1); if (!name) return FALSE; if (strcmp (name, data->name) == 0) { data->found = TRUE; if (data->do_activate) gtk_tree_view_row_activated (GTK_TREE_VIEW (data->dialog->priv->contexts_view), path, gtk_tree_view_get_column (GTK_TREE_VIEW (data->dialog-> priv-> contexts_view), 0)); if (data->do_select) gtk_tree_selection_select_path (selection, path); else gtk_tree_selection_unselect_path (selection, path); if (data->do_cursor) gtk_tree_view_set_cursor (GTK_TREE_VIEW (data->dialog->priv->contexts_view), path, NULL, FALSE); g_free (name); return TRUE; } g_free (name); return FALSE; } gboolean scan_for_name_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { ForEachFuncData *data = (ForEachFuncData *) user_data; gchar *name = NULL; gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1); if (!name) return FALSE; if (strcmp (name, data->name) == 0) { data->found = TRUE; if (data->do_activate) gtk_tree_view_row_activated (GTK_TREE_VIEW (data->dialog->priv->icons_view), path, gtk_tree_view_get_column (GTK_TREE_VIEW (data->dialog-> priv-> icons_view), 0)); if (data->do_select) gtk_tree_selection_select_path (data->dialog->priv->selection, path); else gtk_tree_selection_unselect_path (data->dialog->priv->selection, path); if (data->do_cursor) gtk_tree_view_set_cursor (GTK_TREE_VIEW (data->dialog->priv->icons_view), path, NULL, FALSE); g_free (name); return TRUE; } g_free (name); return FALSE; } static void centre_selected_row (GladeNamedIconChooserDialog *dialog) { GList *l; g_assert (dialog->priv->icons_store != NULL); g_assert (dialog->priv->selection != NULL); l = gtk_tree_selection_get_selected_rows (dialog->priv->selection, NULL); if (l) { g_assert (gtk_widget_get_mapped (GTK_WIDGET (dialog))); g_assert (gtk_widget_get_visible (GTK_WIDGET (dialog))); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (dialog->priv->icons_view), (GtkTreePath *) l->data, NULL, TRUE, 0.5, 0.0); /* gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->priv->icons_view), (GtkTreePath *) l->data, 0, FALSE); gtk_widget_grab_focus (dialog->priv->icons_view); */ g_list_foreach (l, (GFunc) gtk_tree_path_free, NULL); g_list_free (l); } } static void select_first_row (GladeNamedIconChooserDialog *dialog) { GtkTreePath *path; if (!dialog->priv->filter_model) return; path = gtk_tree_path_new_from_indices (0, -1); gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->priv->icons_view), path, NULL, FALSE); gtk_tree_path_free (path); } static void pending_select_name_process (GladeNamedIconChooserDialog *dialog) { ForEachFuncData *data; g_assert (dialog->priv->icons_store != NULL); g_assert (dialog->priv->selection != NULL); if (dialog->priv->pending_select_name) { data = g_slice_new0 (ForEachFuncData); data->name = dialog->priv->pending_select_name; data->do_select = TRUE; data->do_activate = FALSE; data->dialog = dialog; gtk_tree_model_foreach (dialog->priv->filter_model, scan_for_name_func, data); g_free (dialog->priv->pending_select_name); dialog->priv->pending_select_name = NULL; g_slice_free (ForEachFuncData, data); } else { if (strlen (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))) == 0) { select_first_row (dialog); } } centre_selected_row (dialog); } static gboolean is_standard_icon_name (const gchar *icon_name) { GQuark quark; quark = g_quark_try_string (icon_name); if (quark == 0) return FALSE; return (g_hash_table_lookup (standard_icon_quarks, GUINT_TO_POINTER (quark)) != NULL); } static void cleanup_after_load (GladeNamedIconChooserDialog *dialog) { dialog->priv->load_id = 0; pending_select_name_process (dialog); set_busy_cursor (dialog, FALSE); } static void chooser_set_model (GladeNamedIconChooserDialog *dialog) { /* filter model */ dialog->priv->filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (dialog->priv->icons_store), NULL); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (dialog->priv->filter_model), (GtkTreeModelFilterVisibleFunc) filter_visible_func, dialog, NULL); gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), dialog->priv->filter_model); g_object_unref (dialog->priv->filter_model); gtk_entry_completion_set_model (dialog->priv->entry_completion, GTK_TREE_MODEL (dialog->priv->icons_store)); gtk_entry_completion_set_text_column (dialog->priv->entry_completion, ICONS_NAME_COLUMN); gtk_tree_view_set_search_column (GTK_TREE_VIEW (dialog->priv->icons_view), ICONS_NAME_COLUMN); dialog->priv->icons_loaded = TRUE; } typedef struct { gchar *name; gint context; } IconData; static gint icon_data_compare (IconData *a, IconData *b) { return g_ascii_strcasecmp (a->name, b->name); } static gboolean reload_icons (GladeNamedIconChooserDialog *dialog) { GtkListStore *store = dialog->priv->icons_store; GtkTreeIter iter; guint i; GList *l, *icons = NULL; /* retrieve icon names from each context */ for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++) { GList *icons_in_context = gtk_icon_theme_list_icons (dialog->priv->icon_theme, standard_contexts[i].name); for (l = icons_in_context; l; l = l->next) { IconData *data = g_slice_new (IconData); data->name = (gchar *) l->data; data->context = i; icons = g_list_prepend (icons, data); } g_list_free (icons_in_context); } /* sort icon names */ icons = g_list_sort (icons, (GCompareFunc) icon_data_compare); /* put into to model */ for (l = icons; l; l = l->next) { IconData *data = (IconData *) l->data; gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, ICONS_CONTEXT_COLUMN, data->context, ICONS_STANDARD_COLUMN, is_standard_icon_name (data->name), ICONS_NAME_COLUMN, data->name, -1); g_free (data->name); g_slice_free (IconData, data); } g_list_free (icons); chooser_set_model (dialog); return FALSE; } static void change_icon_theme (GladeNamedIconChooserDialog *dialog) { if (dialog->priv->icon_theme == NULL) dialog->priv->icon_theme = get_icon_theme_for_widget (GTK_WIDGET (dialog)); gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), NULL); gtk_list_store_clear (dialog->priv->icons_store); set_busy_cursor (dialog, TRUE); dialog->priv->load_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 300, (GSourceFunc) reload_icons, dialog, (GDestroyNotify) cleanup_after_load); } static void glade_named_icon_chooser_dialog_screen_changed (GtkWidget *widget, GdkScreen *previous_screen) { GladeNamedIconChooserDialog *dialog; dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); if (GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> screen_changed) GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> screen_changed (widget, previous_screen); if (gtk_widget_get_mapped (widget)) change_icon_theme (dialog); } static GtkWidget * create_icons_view (GladeNamedIconChooserDialog *dialog) { GtkTreeView *view; GtkTreeViewColumn *column; GtkCellRenderer *pixbuf_renderer, *text_renderer; view = GTK_TREE_VIEW (gtk_tree_view_new ()); column = gtk_tree_view_column_new (); gtk_tree_view_column_set_min_width (column, 56); gtk_tree_view_column_set_title (column, NULL); pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, pixbuf_renderer, TRUE); gtk_tree_view_column_set_attributes (column, pixbuf_renderer, "icon-name", ICONS_NAME_COLUMN, NULL); gtk_tree_view_append_column (view, column); g_object_set (pixbuf_renderer, "xpad", 2, "xalign", 1.0, "stock-size", GTK_ICON_SIZE_MENU, NULL); column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, "Name"); text_renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (text_renderer), "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0, NULL); gtk_tree_view_column_pack_start (column, text_renderer, TRUE); gtk_tree_view_column_set_attributes (column, text_renderer, "text", ICONS_NAME_COLUMN, NULL); gtk_tree_view_append_column (view, column); gtk_tree_view_column_set_expand (column, TRUE); gtk_tree_view_column_set_resizable (column, FALSE); gtk_tree_view_set_headers_visible (view, FALSE); gtk_tree_view_set_enable_search (view, TRUE); gtk_tree_view_set_search_equal_func (view, (GtkTreeViewSearchEqualFunc) search_equal_func, dialog, NULL); g_signal_connect (view, "row-activated", G_CALLBACK (icons_row_activated_cb), dialog); g_signal_connect (gtk_tree_view_get_selection (view), "changed", G_CALLBACK (icons_selection_changed_cb), dialog); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), GTK_SELECTION_BROWSE); dialog->priv->selection = gtk_tree_view_get_selection (view); gtk_tree_view_set_rules_hint (view, TRUE); gtk_widget_show (GTK_WIDGET (view)); return GTK_WIDGET (view); } /* sets the 'list-standard' state and refilters the icons model */ static void button_toggled (GtkToggleButton *button, GladeNamedIconChooserDialog *dialog) { dialog->priv->settings_list_standard = gtk_toggle_button_get_active (button); if (dialog->priv->filter_model != NULL) filter_icons_model (dialog); } static GHashTable * create_standard_icon_quarks (void) { GHashTable *table; GQuark quark; guint i; table = g_hash_table_new (NULL, NULL); for (i = 0; i < G_N_ELEMENTS (standard_icon_names); i++) { quark = g_quark_from_static_string (standard_icon_names[i]); g_hash_table_insert (table, GUINT_TO_POINTER (quark), GUINT_TO_POINTER (quark)); } return table; } static void glade_named_icon_chooser_dialog_style_set (GtkWidget *widget, GtkStyle *previous_style) { if (gtk_widget_has_screen (widget) && gtk_widget_get_mapped (widget)) change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget)); } /* override GtkWidget::show_all since we have internal widgets we wish to keep * hidden unless we decide otherwise, like the list-standard-icons-only checkbox. */ static void glade_named_icon_chooser_dialog_show_all (GtkWidget *widget) { gtk_widget_show (widget); } /* Handler for GtkWindow::set-focus; this is where we save the last-focused * widget on our toplevel. See glade_named_icon_chooser_dialog_hierarchy_changed() */ static void glade_named_icon_chooser_dialog_set_focus (GtkWindow *window, GtkWidget *focus) { GTK_WINDOW_CLASS (glade_named_icon_chooser_dialog_parent_class)-> set_focus (window, focus); GLADE_NAMED_ICON_CHOOSER_DIALOG (window)->priv->last_focus_widget = gtk_window_get_focus (window); } static void glade_named_icon_chooser_dialog_finalize (GObject *object) { GladeNamedIconChooserDialog *dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (object); if (dialog->priv->pending_select_name) { g_free (dialog->priv->pending_select_name); dialog->priv->pending_select_name = NULL; } G_OBJECT_CLASS (glade_named_icon_chooser_dialog_parent_class)-> finalize (object); } static void glade_named_icon_chooser_dialog_map (GtkWidget *widget) { GladeNamedIconChooserDialog *dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->map (widget); settings_load (dialog); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->button), dialog->priv->settings_list_standard); gtk_widget_grab_focus (dialog->priv->icons_view); } static void glade_named_icon_chooser_dialog_unmap (GtkWidget *widget) { GladeNamedIconChooserDialog *dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); settings_save (dialog); GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> unmap (widget); } /* we load the icons in expose() because we want the widget * to be fully painted before loading begins */ static gboolean glade_named_icon_chooser_dialog_draw (GtkWidget *widget, cairo_t *cr) { GladeNamedIconChooserDialog *dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); gboolean retval; retval = GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> draw (widget, cr); if (!dialog->priv->icons_loaded) { change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget)); dialog->priv->icons_loaded = TRUE; } return retval; } static void response_cb (GtkDialog *dialog, gint response_id) { /* Act only on response IDs we recognize */ if (!(response_id == GTK_RESPONSE_ACCEPT || response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY)) return; if (!should_respond (GLADE_NAMED_ICON_CHOOSER_DIALOG (dialog))) { g_signal_stop_emission_by_name (dialog, "response"); } } /* we intercept the GladeNamedIconChooser::icon-activated signal and try to * make the dialog emit a valid response signal */ static void icon_activated_cb (GladeNamedIconChooserDialog *dialog) { GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog)))); for (l = children; l; l = l->next) { GtkWidget *widget; gint response_id; widget = GTK_WIDGET (l->data); response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); if (response_id == GTK_RESPONSE_ACCEPT || response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY) { g_list_free (children); gtk_dialog_response (GTK_DIALOG (dialog), response_id); return; } } g_list_free (children); } /* we intercept the GladeNamedIconChooser::selection-changed signal and try to * make the affirmative response button insensitive when the selection is empty */ static void selection_changed_cb (GladeNamedIconChooserDialog *dialog) { GList *children, *l; gchar *icon_name; children = gtk_container_get_children (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog)))); for (l = children; l; l = l->next) { GtkWidget *widget; gint response_id; widget = GTK_WIDGET (l->data); response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); if (response_id == GTK_RESPONSE_ACCEPT || response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY) { icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), response_id, icon_name != NULL); g_free (icon_name); g_list_free (children); return; } } g_list_free (children); } static void glade_named_icon_chooser_dialog_init (GladeNamedIconChooserDialog *dialog) { GtkWidget *contents; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *sw; GtkWidget *label; GtkWidget *hpaned; GtkWidget *content_area; GtkSizeGroup *group; dialog->priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); dialog->priv->filter_model = NULL; dialog->priv->icons_store = NULL; dialog->priv->context_id = -1; dialog->priv->pending_select_name = NULL; dialog->priv->last_focus_widget = NULL; dialog->priv->icons_loaded = FALSE; gtk_window_set_title (GTK_WINDOW (dialog), _("Named Icon Chooser")); gtk_window_set_default_size (GTK_WINDOW (dialog), 610, 480); _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); /* We do a signal connection here rather than overriding the method in * class_init because GtkDialog::response is a RUN_LAST signal. We want *our* * handler to be run *first*, regardless of whether the user installs response * handlers of his own. */ g_signal_connect (dialog, "response", G_CALLBACK (response_cb), NULL); g_signal_connect (dialog, "icon-activated", G_CALLBACK (icon_activated_cb), NULL); g_signal_connect (dialog, "selection-changed", G_CALLBACK (selection_changed_cb), NULL); if (standard_icon_quarks == NULL) standard_icon_quarks = create_standard_icon_quarks (); contents = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_container_set_border_width (GTK_CONTAINER (contents), 5); gtk_widget_show (contents); label = gtk_label_new_with_mnemonic (_("Icon _Name:")); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_show (label); dialog->priv->entry = gtk_entry_new (); gtk_entry_set_activates_default (GTK_ENTRY (dialog->priv->entry), TRUE); gtk_entry_set_width_chars (GTK_ENTRY (dialog->priv->entry), 40); g_object_set (G_OBJECT (dialog->priv->entry), "truncate-multiline", TRUE, NULL); g_signal_connect (G_OBJECT (dialog->priv->entry), "changed", G_CALLBACK (changed_text_handler), dialog); g_signal_connect (G_OBJECT (dialog->priv->entry), "insert-text", G_CALLBACK (insert_text_handler), dialog); gtk_widget_show (dialog->priv->entry); dialog->priv->entry_completion = gtk_entry_completion_new (); gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry), dialog->priv->entry_completion); gtk_entry_completion_set_popup_completion (dialog->priv->entry_completion, FALSE); gtk_entry_completion_set_inline_completion (dialog->priv->entry_completion, TRUE); gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->priv->entry); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), dialog->priv->entry, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (contents), hbox, FALSE, FALSE, 6); hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); gtk_paned_set_position (GTK_PANED (hpaned), 150); gtk_widget_show (hpaned); dialog->priv->contexts_view = create_contexts_view (dialog); dialog->priv->icons_view = create_icons_view (dialog); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_widget_show (vbox); group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); label = gtk_label_new_with_mnemonic (_("C_ontexts:")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->priv->contexts_view); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_size_group_add_widget (group, label); gtk_widget_show (label); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); sw = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); gtk_widget_show (sw); gtk_container_add (GTK_CONTAINER (sw), dialog->priv->contexts_view); gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); gtk_paned_pack1 (GTK_PANED (hpaned), vbox, FALSE, FALSE); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_widget_show (vbox); label = gtk_label_new_with_mnemonic (_("Icon Na_mes:")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->priv->icons_view); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_size_group_add_widget (group, label); gtk_widget_show (label); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); sw = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); gtk_widget_show (sw); gtk_container_add (GTK_CONTAINER (sw), dialog->priv->icons_view); gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); gtk_paned_pack2 (GTK_PANED (hpaned), vbox, TRUE, FALSE); gtk_box_pack_start (GTK_BOX (contents), hpaned, TRUE, TRUE, 0); g_object_unref (G_OBJECT (group)); dialog->priv->button = gtk_check_button_new_with_mnemonic (_("_List standard icons only")); gtk_widget_show (dialog->priv->button); g_signal_connect (dialog->priv->button, "toggled", G_CALLBACK (button_toggled), dialog); gtk_box_pack_start (GTK_BOX (contents), dialog->priv->button, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (content_area), contents, TRUE, TRUE, 0); /* underlying model */ dialog->priv->icons_store = gtk_list_store_new (ICONS_N_COLUMNS, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_STRING); } static void glade_named_icon_chooser_dialog_class_init (GladeNamedIconChooserDialogClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; GtkWindowClass *window_class; object_class = G_OBJECT_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); window_class = GTK_WINDOW_CLASS (klass); object_class->finalize = glade_named_icon_chooser_dialog_finalize; widget_class->map = glade_named_icon_chooser_dialog_map; widget_class->unmap = glade_named_icon_chooser_dialog_unmap; widget_class->draw = glade_named_icon_chooser_dialog_draw; widget_class->show_all = glade_named_icon_chooser_dialog_show_all; widget_class->style_set = glade_named_icon_chooser_dialog_style_set; widget_class->screen_changed = glade_named_icon_chooser_dialog_screen_changed; window_class->set_focus = glade_named_icon_chooser_dialog_set_focus; /** * GladeNamedIconChooserDialog::icon-activated * @chooser: the object which received the signal * * This signal is emitted when the user "activates" an icon * in the named icon chooser. This can happen by double-clicking on an item * in the recently used resources list, or by pressing * Enter. */ dialog_signals[ICON_ACTIVATED] = g_signal_new ("icon-activated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass, icon_activated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GladeNamedIconChooserDialog::selection-changed * @chooser: the object which received the signal * * This signal is emitted when there is a change in the set of * selected icon names. This can happen when a user * modifies the selection with the mouse or the keyboard, or when * explicitely calling functions to change the selection. */ dialog_signals[SELECTION_CHANGED] = g_signal_new ("selection-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass, selection_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static gboolean should_respond (GladeNamedIconChooserDialog *dialog) { gchar *icon_name; /* is there an icon selected? */ icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog); if (!icon_name) return FALSE; g_free (icon_name); return TRUE; } /* get's the name of the configuration file */ static gchar * get_config_filename (void) { return g_build_filename (g_get_user_config_dir (), "gladeui", "config", NULL); } /* get's the name of the directory that contains the config file */ static char * get_config_dirname (void) { return g_build_filename (g_get_user_config_dir (), "gladeui", NULL); } /* loads the configuration settings */ static void settings_load (GladeNamedIconChooserDialog *dialog) { GKeyFile *keyfile; gboolean success, boolean_value; gchar *filename; GError *error = NULL; keyfile = g_key_file_new (); filename = get_config_filename (); success = g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error); g_free (filename); if (!success) { dialog->priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY; g_clear_error (&error); g_key_file_free (keyfile); return; } boolean_value = g_key_file_get_boolean (keyfile, "Named Icon Chooser", "ListStandardOnly", &error); if (error) { dialog->priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY; g_clear_error (&error); } else { dialog->priv->settings_list_standard = boolean_value; } g_key_file_free (keyfile); } /* creates a GKeyFile based on the current settings */ static GKeyFile * settings_to_keyfile (GladeNamedIconChooserDialog *dialog) { GKeyFile *keyfile; gchar *filename; keyfile = g_key_file_new (); filename = get_config_filename (); g_key_file_load_from_file (keyfile, get_config_filename (), G_KEY_FILE_NONE, NULL); g_free (filename); g_key_file_set_boolean (keyfile, "Named Icon Chooser", "ListStandardOnly", dialog->priv->settings_list_standard); return keyfile; } /* serializes the the current configuration to the config file */ static void settings_save (GladeNamedIconChooserDialog *dialog) { GKeyFile *keyfile; gchar *contents; gsize contents_length; gchar *filename = NULL, *dirname = NULL; GError *error = NULL; keyfile = settings_to_keyfile (dialog); contents = g_key_file_to_data (keyfile, &contents_length, &error); if (error) goto out; filename = get_config_filename (); if (!g_file_set_contents (filename, contents, contents_length, NULL)) { gchar *dirname; gint saved_errno; dirname = get_config_dirname (); if (g_mkdir_with_parents (dirname, 0700) != 0) /* 0700 per the XDG basedir spec */ { saved_errno = errno; g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (saved_errno), _("Could not create directory: %s"), dirname); goto out; } if (!g_file_set_contents (filename, contents, contents_length, &error)) { goto out; } } out: g_free (contents); g_free (dirname); g_free (filename); g_clear_error (&error); g_key_file_free (keyfile); } static GtkWidget * glade_named_icon_chooser_dialog_new_valist (const gchar *title, GtkWindow *parent, const gchar *first_button_text, va_list varargs) { GtkWidget *result; const char *button_text = first_button_text; gint response_id; result = g_object_new (GLADE_TYPE_NAMED_ICON_CHOOSER_DIALOG, "title", title, "transient-for", parent, NULL); while (button_text) { response_id = va_arg (varargs, gint); gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id); button_text = va_arg (varargs, const gchar *); } return result; } /** * glade_named_icon_chooser_dialog_new: * @title: Title of the dialog, or %NULL * @parent: Transient parent of the dialog, or %NULL, * @first_button_text: stock ID or text to go in the first button, or %NULL * @Varargs: response ID for the first button, then additional (button, id) * pairs, ending with %NULL * * Creates a new #GladeNamedIconChooserDialog. This function is analogous to * gtk_dialog_new_with_buttons(). * * Return value: a new #GladeNamedIconChooserDialog */ GtkWidget * glade_named_icon_chooser_dialog_new (const gchar *title, GtkWindow *parent, const gchar *first_button_text, ...) { GtkWidget *result; va_list varargs; va_start (varargs, first_button_text); result = glade_named_icon_chooser_dialog_new_valist (title, parent, first_button_text, varargs); va_end (varargs); return result; }