/* * glade-adaptor-chooser.c * * Copyright (C) 2014 Juan Pablo Ugarte * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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: * Juan Pablo Ugarte */ #include "glade-app.h" #include "gladeui-enum-types.h" #include "glade-adaptor-chooser.h" #include enum { COLUMN_ADAPTOR = 0, COLUMN_NORMALIZED_NAME, COLUMN_NORMALIZED_NAME_LEN, N_COLUMN }; struct _GladeAdaptorChooserPrivate { GtkListStore *store; GtkTreeModelFilter *treemodelfilter; GtkSearchEntry *searchentry; GtkEntryCompletion *entrycompletion; /* Needed for gtk_tree_view_column_set_cell_data_func() */ GtkTreeViewColumn *column_icon; GtkCellRenderer *icon_cell; GtkTreeViewColumn *column_adaptor; GtkCellRenderer *adaptor_cell; /* Properties */ _GladeAdaptorChooserFlags flags; GladeProject *project; }; enum { PROP_0, PROP_SHOW_FLAGS, PROP_PROJECT }; enum { ADAPTOR_SELECTED, LAST_SIGNAL }; static guint adaptor_chooser_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (_GladeAdaptorChooser, _glade_adaptor_chooser, GTK_TYPE_BOX); static void _glade_adaptor_chooser_init (_GladeAdaptorChooser *chooser) { chooser->priv = _glade_adaptor_chooser_get_instance_private (chooser); chooser->priv->flags = GLADE_ADAPTOR_CHOOSER_WIDGET; gtk_widget_init_template (GTK_WIDGET (chooser)); } static void _glade_adaptor_chooser_finalize (GObject *object) { G_OBJECT_CLASS (_glade_adaptor_chooser_parent_class)->finalize (object); } static void _glade_adaptor_chooser_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { _GladeAdaptorChooserPrivate *priv; g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER (object)); priv = GLADE_ADAPTOR_CHOOSER (object)->priv; switch (prop_id) { case PROP_SHOW_FLAGS: priv->flags = g_value_get_flags (value); break; case PROP_PROJECT: priv->project = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _glade_adaptor_chooser_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { _GladeAdaptorChooserPrivate *priv; g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER (object)); priv = GLADE_ADAPTOR_CHOOSER (object)->priv; switch (prop_id) { case PROP_SHOW_FLAGS: g_value_set_flags (value, priv->flags); break; case PROP_PROJECT: g_value_set_object (value, priv->project); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static inline gchar * normalize_name (const gchar *name) { gchar *normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_DEFAULT); gchar *casefold_name = g_utf8_casefold (normalized_name, -1); g_free (normalized_name); return casefold_name; } static inline void store_append_adaptor (GtkListStore *store, GladeWidgetAdaptor *adaptor) { gchar *normalized_name = normalize_name (glade_widget_adaptor_get_name (adaptor)); gtk_list_store_insert_with_values (store, NULL, -1, COLUMN_ADAPTOR, adaptor, COLUMN_NORMALIZED_NAME, normalized_name, COLUMN_NORMALIZED_NAME_LEN, strlen (normalized_name), -1); g_free (normalized_name); } static inline void store_populate (GtkListStore *store, GladeProject *project, _GladeAdaptorChooserFlags flags) { const gchar *catalog = NULL; gint major, minor; GList *l; for (l = glade_app_get_catalogs (); l; l = g_list_next (l)) { GList *groups = glade_catalog_get_widget_groups (GLADE_CATALOG (l->data)); for (; groups; groups = g_list_next (groups)) { GladeWidgetGroup *group = GLADE_WIDGET_GROUP (groups->data); const GList *adaptors; for (adaptors = glade_widget_group_get_adaptors (group); adaptors; adaptors = g_list_next (adaptors)) { GladeWidgetAdaptor *adaptor = adaptors->data; GType type = glade_widget_adaptor_get_object_type (adaptor); /* Skip deprecated adaptors and according to flags */ if (GWA_DEPRECATED (adaptor) || (flags & GLADE_ADAPTOR_CHOOSER_SKIP_TOPLEVEL && GWA_IS_TOPLEVEL (adaptor)) || !((flags & GLADE_ADAPTOR_CHOOSER_WIDGET && g_type_is_a (type, GTK_TYPE_WIDGET)) || (flags & GLADE_ADAPTOR_CHOOSER_TOPLEVEL && GWA_IS_TOPLEVEL (adaptor)))) continue; /* Skip classes not available in project target version */ if (project) { const gchar *new_catalog = glade_widget_adaptor_get_catalog (adaptor); if (g_strcmp0 (catalog, new_catalog)) { catalog = new_catalog; glade_project_get_target_version (project, catalog, &major, &minor); } if (!GWA_VERSION_CHECK (adaptor, major, minor)) continue; } store_append_adaptor (store, adaptor); } } } } static void on_treeview_row_activated (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, _GladeAdaptorChooser *chooser) { GtkTreeModel *model = gtk_tree_view_get_model (tree_view); GtkTreeIter iter; if (gtk_tree_model_get_iter (model, &iter, path)) { GladeWidgetAdaptor *adaptor; gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); /* Emit selected signal */ g_signal_emit (chooser, adaptor_chooser_signals[ADAPTOR_SELECTED], 0, adaptor); g_object_unref (adaptor); } } static void on_searchentry_activate (GtkEntry *entry, _GladeAdaptorChooser *chooser) { const gchar *text = gtk_entry_get_text (entry); GladeWidgetAdaptor *adaptor; /* try to find an adaptor by name */ if (!(adaptor = glade_widget_adaptor_get_by_name (text))) { GtkTreeModel *model = GTK_TREE_MODEL (chooser->priv->treemodelfilter); gchar *normalized_name = normalize_name (text); GtkTreeIter iter; gboolean valid; gint count = 0; valid = gtk_tree_model_get_iter_first (model, &iter); /* we could not find it check if we can find it by normalized name */ while (valid) { gchar *name; gtk_tree_model_get (model, &iter, COLUMN_NORMALIZED_NAME, &name, -1); if (g_strcmp0 (name, normalized_name) == 0) { gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); g_free (name); break; } valid = gtk_tree_model_iter_next (model, &iter); g_free (name); count++; } /* if not, and there is only one row, then we select that one */ if (!adaptor && count == 1 && gtk_tree_model_get_iter_first (model, &iter)) gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); g_free (normalized_name); } if (adaptor) g_signal_emit (chooser, adaptor_chooser_signals[ADAPTOR_SELECTED], 0, adaptor); } static gboolean chooser_match_func (_GladeAdaptorChooser *chooser, GtkTreeModel *model, const gchar *key, GtkTreeIter *iter) { gboolean visible; gint name_len; gchar *name; if (!key || *key == '\0') return TRUE; gtk_tree_model_get (model, iter, COLUMN_NORMALIZED_NAME, &name, COLUMN_NORMALIZED_NAME_LEN, &name_len, -1); visible = (g_strstr_len (name, name_len, key) != NULL); g_free (name); return visible; } static gboolean treemodelfilter_visible_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { _GladeAdaptorChooserPrivate *priv = GLADE_ADAPTOR_CHOOSER (data)->priv; gchar *key = normalize_name (gtk_entry_get_text (GTK_ENTRY (priv->searchentry))); gboolean visible = chooser_match_func (data, model, key, iter); g_free (key); return visible; } static gboolean entrycompletion_match_func (GtkEntryCompletion *entry, const gchar *key, GtkTreeIter *iter, gpointer data) { return chooser_match_func (data, gtk_entry_completion_get_model (entry), key, iter); } static void adaptor_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { GladeWidgetAdaptor *adaptor; gtk_tree_model_get (tree_model, iter, COLUMN_ADAPTOR, &adaptor, -1); if (GPOINTER_TO_SIZE (data)) g_object_set (cell, "icon-name", glade_widget_adaptor_get_icon_name (adaptor), NULL); else g_object_set (cell, "text", glade_widget_adaptor_get_name (adaptor), NULL); g_object_unref (adaptor); } static void _glade_adaptor_chooser_constructed (GObject *object) { _GladeAdaptorChooser *chooser = GLADE_ADAPTOR_CHOOSER (object); _GladeAdaptorChooserPrivate *priv = chooser->priv; store_populate (priv->store, priv->project, priv->flags); /* Set cell data function: this save us from alocating name and icon name for each adaptor. */ gtk_tree_view_column_set_cell_data_func (priv->column_icon, priv->icon_cell, adaptor_cell_data_func, GSIZE_TO_POINTER (TRUE), NULL); gtk_tree_view_column_set_cell_data_func (priv->column_adaptor, priv->adaptor_cell, adaptor_cell_data_func, GSIZE_TO_POINTER (FALSE), NULL); /* Set tree model filter function */ gtk_tree_model_filter_set_visible_func (priv->treemodelfilter, treemodelfilter_visible_func, chooser, NULL); /* Set completion match function */ gtk_entry_completion_set_match_func (priv->entrycompletion, entrycompletion_match_func, chooser, NULL); } static GType _glade_adaptor_chooser_flags_get_type (void) { static GType etype = 0; if (G_UNLIKELY(etype == 0)) { static const GFlagsValue values[] = { { GLADE_ADAPTOR_CHOOSER_WIDGET, "GLADE_ADAPTOR_CHOOSER_WIDGET", "widget" }, { GLADE_ADAPTOR_CHOOSER_TOPLEVEL, "GLADE_ADAPTOR_CHOOSER_TOPLEVEL", "toplevel" }, { GLADE_ADAPTOR_CHOOSER_SKIP_TOPLEVEL, "GLADE_ADAPTOR_CHOOSER_SKIP_TOPLEVEL", "skip-toplevel" }, { 0, NULL, NULL } }; etype = g_flags_register_static (g_intern_static_string ("_GladeAdaptorChooserFlag"), values); } return etype; } static void _glade_adaptor_chooser_class_init (_GladeAdaptorChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = _glade_adaptor_chooser_finalize; object_class->set_property = _glade_adaptor_chooser_set_property; object_class->get_property = _glade_adaptor_chooser_get_property; object_class->constructed = _glade_adaptor_chooser_constructed; g_object_class_install_property (object_class, PROP_SHOW_FLAGS, g_param_spec_flags ("show-flags", "Show flags", "Widget adaptors show flags", _glade_adaptor_chooser_flags_get_type (), GLADE_ADAPTOR_CHOOSER_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_PROJECT, g_param_spec_object ("project", "Glade Project", "If set, use project target version to skip unsupported classes", GLADE_TYPE_PROJECT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); adaptor_chooser_signals[ADAPTOR_SELECTED] = g_signal_new ("adaptor-selected", G_OBJECT_CLASS_TYPE (klass), 0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GLADE_TYPE_WIDGET_ADAPTOR); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-adaptor-chooser.ui"); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, store); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, treemodelfilter); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, searchentry); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, entrycompletion); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, column_icon); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, icon_cell); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, column_adaptor); gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooser, adaptor_cell); gtk_widget_class_bind_template_callback (widget_class, on_treeview_row_activated); gtk_widget_class_bind_template_callback (widget_class, on_searchentry_activate); } GtkWidget * _glade_adaptor_chooser_new (_GladeAdaptorChooserFlags flags, GladeProject *project) { return GTK_WIDGET (g_object_new (GLADE_TYPE_ADAPTOR_CHOOSER, "show-flags", flags, "project", project, NULL)); }