/* * glade-inspector.h * * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2007 Vincent Geddes * * Authors: * Chema Celorio * Tristan Van Berkom * 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 * 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. */ #include /** * SECTION:glade-inspector * @Short_Description: A widget for inspecting objects in a #GladeProject. * * A #GladeInspector is a widget for inspecting the objects that make up a user interface. * * An inspector is created by calling either glade_inspector_new() or glade_inspector_new_with_project(). * The current project been inspected can be changed by calling glade_inspector_set_project(). */ #include "glade.h" #include "glade-widget.h" #include "glade-project.h" #include "glade-widget-adaptor.h" #include "glade-inspector.h" #include "glade-popup.h" #include "glade-app.h" #include "glade-dnd.h" #include #include #if GTK_CHECK_VERSION (2, 21, 8) #include #else #include #endif static void search_entry_text_inserted_cb (GtkEntry *entry, const gchar *text, gint length, gint *position, GladeInspector *inspector); static void search_entry_text_deleted_cb (GtkEditable *editable, gint start_pos, gint end_pos, GladeInspector *inspector); enum { PROP_0, PROP_PROJECT, N_PROPERTIES }; enum { SELECTION_CHANGED, ITEM_ACTIVATED, LAST_SIGNAL }; typedef struct _GladeInspectorPrivate { GtkWidget *view; GtkTreeModel *filter; GladeProject *project; GtkWidget *entry; guint idle_complete; gboolean search_disabled; gchar *completion_text; gchar *completion_text_fold; } GladeInspectorPrivate; static GParamSpec *properties[N_PROPERTIES]; static guint glade_inspector_signals[LAST_SIGNAL] = { 0 }; static void glade_inspector_dispose (GObject *object); static void glade_inspector_finalize (GObject *object); static void add_columns (GtkTreeView *inspector); static void item_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GladeInspector *inspector); static void selection_changed_cb (GtkTreeSelection *selection, GladeInspector *inspector); static gint button_press_cb (GtkWidget *widget, GdkEventButton *event, GladeInspector *inspector); G_DEFINE_TYPE_WITH_PRIVATE (GladeInspector, glade_inspector, GTK_TYPE_BOX) static void glade_inspector_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GladeInspector *inspector = GLADE_INSPECTOR (object); switch (property_id) { case PROP_PROJECT: glade_inspector_set_project (inspector, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void glade_inspector_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GladeInspector *inspector = GLADE_INSPECTOR (object); switch (property_id) { case PROP_PROJECT: g_value_set_object (value, glade_inspector_get_project (inspector)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void glade_inspector_class_init (GladeInspectorClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->dispose = glade_inspector_dispose; object_class->finalize = glade_inspector_finalize; object_class->set_property = glade_inspector_set_property; object_class->get_property = glade_inspector_get_property; /** * GladeInspector::selection-changed: * @inspector: the object which received the signal * * Emitted when the selection changes in the GladeInspector. */ glade_inspector_signals[SELECTION_CHANGED] = g_signal_new ("selection-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeInspectorClass, selection_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GladeInspector::item-activated: * @inspector: the object which received the signal * * Emitted when a item is activated in the GladeInspector. */ glade_inspector_signals[ITEM_ACTIVATED] = g_signal_new ("item-activated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeInspectorClass, item_activated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); properties[PROP_PROJECT] = g_param_spec_object ("project", _("Project"), _("The project being inspected"), GLADE_TYPE_PROJECT, G_PARAM_READABLE | G_PARAM_WRITABLE); /* Install all properties */ g_object_class_install_properties (object_class, N_PROPERTIES, properties); } static gboolean glade_inspector_visible_func (GtkTreeModel *model, GtkTreeIter *parent, gpointer data) { GladeInspector *inspector = data; GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GtkTreeIter iter; gboolean retval = FALSE; if (priv->search_disabled || priv->completion_text == NULL) return TRUE; if (gtk_tree_model_iter_children (model, &iter, parent)) { do { retval = glade_inspector_visible_func (model, &iter, data); } while (gtk_tree_model_iter_next (model, &iter) && !retval); } if (!retval) { gchar *widget_name, *haystack; gtk_tree_model_get (model, parent, GLADE_PROJECT_MODEL_COLUMN_NAME, &widget_name, -1); haystack = g_utf8_casefold (widget_name, -1); retval = strstr (haystack, priv->completion_text_fold) != NULL; g_free (haystack); g_free (widget_name); } return retval; } static void glade_inspector_refilter (GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); if (!priv->search_disabled) { gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter)); gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->view)); } } static void search_entry_changed_cb (GtkEntry *entry, GladeInspector *inspector) { glade_inspector_refilter (inspector); } typedef struct { const gchar *text; gchar *common_text; gchar *first_match; } CommonMatchData; static void reduce_string (gchar *str1, const gchar *str2) { gint str1len = strlen (str1); gint i; for (i = 0; str2[i] != '\0'; i++) { if (str1[i] != str2[i] || i >= str1len) { str1[i] = '\0'; break; } } if (str2[i] == '\0') str1[i] = '\0'; } static gboolean search_common_matches (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, CommonMatchData *data) { g_autoptr(GObject) obj = NULL; GladeWidget *gwidget; const gchar *name; gboolean match; gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj, -1); gwidget = glade_widget_get_from_gobject (obj); if (!glade_widget_has_name (gwidget)) return FALSE; name = glade_widget_get_name (gwidget); match = (strncmp (data->text, name, strlen (data->text)) == 0); if (match) { if (!data->first_match) data->first_match = g_strdup (name); if (data->common_text) reduce_string (data->common_text, name); else data->common_text = g_strdup (name); } return FALSE; } /* Returns the shortest common matching text from all * project widget names. * * If shortest_match is specified, it is given the first * full match for the 'search' text */ static gchar * get_partial_match (GladeInspector *inspector, const gchar *search, gchar **first_match) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GtkTreeModel *model = GTK_TREE_MODEL (priv->project); CommonMatchData data; data.text = search; data.common_text = NULL; data.first_match = NULL; gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)search_common_matches, &data); if (first_match) *first_match = data.first_match; else g_free (data.first_match); return data.common_text; } static void inspector_set_completion_text (GladeInspector *inspector, const gchar *text) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); g_free (priv->completion_text); priv->completion_text = g_strdup (text); priv->completion_text_fold = text ? g_utf8_casefold (text, -1) : NULL; } static gboolean search_complete_idle (GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); gchar *completed; const gchar *str; gsize length; str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); completed = get_partial_match (inspector, str, NULL); inspector_set_completion_text (inspector, str); if (completed) { length = strlen (str); g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); gtk_entry_set_text (GTK_ENTRY (priv->entry), completed); gtk_editable_set_position (GTK_EDITABLE (priv->entry), length); gtk_editable_select_region (GTK_EDITABLE (priv->entry), length, -1); g_free (completed); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); } priv->idle_complete = 0; return FALSE; } static void search_entry_text_inserted_cb (GtkEntry *entry, const gchar *text, gint length, gint *position, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); if (!priv->search_disabled && !priv->idle_complete) { priv->idle_complete = g_idle_add ((GSourceFunc) search_complete_idle, inspector); } } static void search_entry_text_deleted_cb (GtkEditable *editable, gint start_pos, gint end_pos, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); if (!priv->search_disabled) { inspector_set_completion_text (inspector, gtk_entry_get_text (GTK_ENTRY (priv->entry))); glade_inspector_refilter (inspector); } } static gboolean search_entry_key_press_event_cb (GtkEntry *entry, GdkEventKey *event, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); const gchar *str; str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); if (event->keyval == GDK_KEY_Tab) { /* CNTL-Tab: An escape route to move focus */ if (event->state & GDK_CONTROL_MASK) { gtk_widget_grab_focus (priv->view); } else /* Tab: Move cursor forward and refine the filter to include all text */ { inspector_set_completion_text (inspector, str); gtk_editable_set_position (GTK_EDITABLE (entry), -1); gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); glade_inspector_refilter (inspector); } return TRUE; } /* Enter/Return: Find the first complete match, refine filter to the complete match, and select the match */ if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { gchar *name, *full_match = NULL; if (str && (name = get_partial_match (inspector, str, &full_match))) { GladeWidget *widget; inspector_set_completion_text (inspector, full_match); g_free (name); g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); gtk_entry_set_text (GTK_ENTRY (entry), priv->completion_text); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); gtk_editable_set_position (GTK_EDITABLE (entry), -1); gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); glade_inspector_refilter (inspector); widget = glade_project_get_widget_by_name (priv->project, priv->completion_text); if (widget) glade_project_selection_set (priv->project, glade_widget_get_object (widget), TRUE); } return TRUE; } /* Backspace: Attempt to move the cursor backwards and maintain the completed/selected portion */ if (event->keyval == GDK_KEY_BackSpace) { if (!priv->search_disabled && !priv->idle_complete && str && str[0]) { /* Now, set the text to the current completion text -1 char, and recomplete */ if (priv->completion_text && priv->completion_text[0]) { /* If we're not at the position of the length of the completion text, just carry on */ if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry), NULL, NULL)) return FALSE; priv->completion_text[strlen (priv->completion_text) -1] = '\0'; g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); gtk_entry_set_text (GTK_ENTRY (priv->entry), priv->completion_text); gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); priv->idle_complete = g_idle_add ((GSourceFunc) search_complete_idle, inspector); return TRUE; } } } return FALSE; } static gboolean search_entry_focus_in_cb (GtkWidget *entry, GdkEventFocus *event, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); if (priv->search_disabled) priv->search_disabled = FALSE; return FALSE; } static gboolean search_entry_focus_out_cb (GtkWidget *entry, GdkEventFocus *event, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); priv->search_disabled = TRUE; inspector_set_completion_text (inspector, NULL); gtk_entry_set_text (GTK_ENTRY (priv->entry), ""); gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter)); return FALSE; } static void glade_inspector_init (GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GtkWidget *sw; GtkTreeSelection *selection; gtk_orientable_set_orientation (GTK_ORIENTABLE (inspector), GTK_ORIENTATION_VERTICAL); priv->project = NULL; priv->entry = gtk_entry_new (); gtk_entry_set_placeholder_text (GTK_ENTRY (priv->entry), _(" < Search Widgets >")); gtk_widget_show (priv->entry); gtk_box_pack_start (GTK_BOX (inspector), priv->entry, FALSE, FALSE, 2); g_signal_connect (priv->entry, "changed", G_CALLBACK (search_entry_changed_cb), inspector); g_signal_connect (priv->entry, "key-press-event", G_CALLBACK (search_entry_key_press_event_cb), inspector); g_signal_connect_after (priv->entry, "insert-text", G_CALLBACK (search_entry_text_inserted_cb), inspector); g_signal_connect_after (priv->entry, "delete-text", G_CALLBACK (search_entry_text_deleted_cb), inspector); g_signal_connect (priv->entry, "focus-in-event", G_CALLBACK (search_entry_focus_in_cb), inspector); g_signal_connect (priv->entry, "focus-out-event", G_CALLBACK (search_entry_focus_out_cb), inspector); priv->view = gtk_tree_view_new (); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->view), FALSE); gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (priv->view), GTK_SCROLL_MINIMUM); add_columns (GTK_TREE_VIEW (priv->view)); /* Set it as a drag source */ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (priv->view), GDK_BUTTON1_MASK, _glade_dnd_get_target (), 1, GDK_ACTION_MOVE | GDK_ACTION_COPY); g_signal_connect (G_OBJECT (priv->view), "row-activated", G_CALLBACK (item_activated_cb), inspector); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (selection_changed_cb), inspector); /* Expand All */ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, "go-down"); gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, _("Expand all")); g_signal_connect_swapped (priv->entry, "icon-press", G_CALLBACK (gtk_tree_view_expand_all), priv->view); /* popup menu */ g_signal_connect (G_OBJECT (priv->view), "button-press-event", G_CALLBACK (button_press_cb), inspector); sw = gtk_scrolled_window_new (NULL, NULL); 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); gtk_container_add (GTK_CONTAINER (sw), priv->view); gtk_box_pack_start (GTK_BOX (inspector), sw, TRUE, TRUE, 0); gtk_widget_show (priv->view); gtk_widget_show (sw); } static void glade_inspector_dispose (GObject *object) { GladeInspector *inspector = GLADE_INSPECTOR (object); GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); glade_inspector_set_project (inspector, NULL); if (priv->idle_complete) { g_source_remove (priv->idle_complete); priv->idle_complete = 0; } G_OBJECT_CLASS (glade_inspector_parent_class)->dispose (object); } static void glade_inspector_finalize (GObject *object) { GladeInspector *inspector = GLADE_INSPECTOR (object); GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); g_free (priv->completion_text); g_free (priv->completion_text_fold); G_OBJECT_CLASS (glade_inspector_parent_class)->finalize (object); } static void project_selection_changed_cb (GladeProject *project, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GladeWidget *widget; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter *iter; GtkTreePath *path, *ancestor_path; GList *list; g_return_if_fail (GLADE_IS_INSPECTOR (inspector)); g_return_if_fail (GLADE_IS_PROJECT (project)); g_return_if_fail (priv->project == project); g_signal_handlers_block_by_func (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)), G_CALLBACK (selection_changed_cb), inspector); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); g_return_if_fail (selection != NULL); model = priv->filter; gtk_tree_selection_unselect_all (selection); for (list = glade_project_selection_get (project); list && list->data; list = list->next) { if ((widget = glade_widget_get_from_gobject (G_OBJECT (list->data))) != NULL) { if ((iter = glade_util_find_iter_by_widget (model, widget, GLADE_PROJECT_MODEL_COLUMN_OBJECT)) != NULL) { path = gtk_tree_model_get_path (model, iter); ancestor_path = gtk_tree_path_copy (path); /* expand parent node */ if (gtk_tree_path_up (ancestor_path)) gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->view), ancestor_path); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->view), path, NULL, TRUE, 0.5, 0); gtk_tree_selection_select_iter (selection, iter); gtk_tree_iter_free (iter); gtk_tree_path_free (path); gtk_tree_path_free (ancestor_path); } } } g_signal_handlers_unblock_by_func (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)), G_CALLBACK (selection_changed_cb), inspector); } static void selection_foreach_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **selection) { g_autoptr(GObject) object = NULL; gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1); if (object) *selection = g_list_prepend (*selection, object); } static void selection_changed_cb (GtkTreeSelection *selection, GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GList *sel = NULL, *l; gtk_tree_selection_selected_foreach (selection, (GtkTreeSelectionForeachFunc) selection_foreach_func, &sel); /* We dont modify the project selection for a change that * leaves us with no selection. * * This is typically because the user is changing the name * of a widget and the filter is active, the new name causes * the row to go out of the model and the selection to be * cleared, if we clear the selection we remove the editor * that the user is trying to type into. */ if (!sel) return; g_signal_handlers_block_by_func (priv->project, G_CALLBACK (project_selection_changed_cb), inspector); glade_project_selection_clear (priv->project, FALSE); for (l = sel; l; l = l->next) glade_project_selection_add (priv->project, G_OBJECT (l->data), FALSE); glade_project_selection_changed (priv->project); g_list_free (sel); g_signal_handlers_unblock_by_func (priv->project, G_CALLBACK (project_selection_changed_cb), inspector); g_signal_emit (inspector, glade_inspector_signals[SELECTION_CHANGED], 0); } static void item_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GladeInspector *inspector) { g_signal_emit (inspector, glade_inspector_signals[ITEM_ACTIVATED], 0); } static gint button_press_cb (GtkWidget *widget, GdkEventButton *event, GladeInspector *inspector) { GtkTreeView *view = GTK_TREE_VIEW (widget); GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); GtkTreePath *path = NULL; gboolean handled = FALSE; /* Give some kind of access in case of missing right button */ if (event->window == gtk_tree_view_get_bin_window (view) && glade_popup_is_popup_event (event)) { if (gtk_tree_view_get_path_at_pos (view, (gint) event->x, (gint) event->y, &path, NULL, NULL, NULL) && path != NULL) { GtkTreeIter iter; if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->project), &iter, path)) { g_autoptr (GladeWidget) object = NULL; /* now we can obtain the widget from the iter. */ gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1); if (widget != NULL) glade_popup_widget_pop (glade_widget_get_from_gobject (object), event, TRUE); else glade_popup_simple_pop (priv->project, event); handled = TRUE; gtk_tree_path_free (path); } } else { glade_popup_simple_pop (priv->project, event); handled = TRUE; } } return handled; } static void glade_inspector_warning_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gchar *warning = NULL; gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_WARNING, &warning, -1); g_object_set (renderer, "visible", warning != NULL, NULL); g_free (warning); } static void glade_inspector_name_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { g_autoptr(GObject) obj = NULL; GladeWidget *gwidget; gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj, -1); gwidget = glade_widget_get_from_gobject (obj); g_object_set (renderer, "text", (glade_widget_has_name (gwidget)) ? glade_widget_get_display_name (gwidget) : NULL, NULL); } static void glade_inspector_detail_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gchar *type_name = NULL, *detail = NULL; gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME, &type_name, GLADE_PROJECT_MODEL_COLUMN_MISC, &detail, -1); if (detail) { gchar *final = g_strconcat (type_name, " ", detail, NULL); g_object_set (renderer, "text", final, NULL); g_free (final); } else g_object_set (renderer, "text", type_name, NULL); g_free (type_name); g_free (detail); } static void add_columns (GtkTreeView *view) { GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkCellAreaBox *box; /* Use a GtkCellArea box to set the alignments manually */ box = (GtkCellAreaBox *)gtk_cell_area_box_new (); column = gtk_tree_view_column_new_with_area (GTK_CELL_AREA (box)); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (box), 2); gtk_tree_view_set_tooltip_column (view, GLADE_PROJECT_MODEL_COLUMN_WARNING); /* Padding */ renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "width", 4, NULL); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); /* Warning cell */ renderer = gtk_cell_renderer_pixbuf_new (); g_object_set (renderer, "icon-name", "dialog-warning-symbolic", "xpad", 2, NULL); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); gtk_tree_view_column_set_cell_data_func (column, renderer, glade_inspector_warning_cell_data_func, NULL, NULL); /* Class Icon */ renderer = gtk_cell_renderer_pixbuf_new (); g_object_set (renderer, "xpad", 2, NULL); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "icon_name", GLADE_PROJECT_MODEL_COLUMN_ICON_NAME, NULL); /* Widget Name */ renderer = gtk_cell_renderer_text_new (); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "text", GLADE_PROJECT_MODEL_COLUMN_NAME, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, glade_inspector_name_cell_data_func, NULL, NULL); /* Padding */ renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "width", 8, NULL); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); /* Class name & detail */ renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "style", PANGO_STYLE_ITALIC, "foreground", "Gray", "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); gtk_tree_view_column_set_cell_data_func (column, renderer, glade_inspector_detail_cell_data_func, NULL, NULL); gtk_tree_view_append_column (view, column); gtk_tree_view_set_headers_visible (view, FALSE); } static void disconnect_project_signals (GladeInspector *inspector, GladeProject *project) { g_signal_handlers_disconnect_by_func (G_OBJECT (project), G_CALLBACK (project_selection_changed_cb), inspector); } static void connect_project_signals (GladeInspector *inspector, GladeProject *project) { g_signal_connect (G_OBJECT (project), "selection-changed", G_CALLBACK (project_selection_changed_cb), inspector); } /** * glade_inspector_set_project: * @inspector: a #GladeInspector * @project: a #GladeProject * * Sets the current project of @inspector to @project. To unset the current * project, pass %NULL for @project. */ void glade_inspector_set_project (GladeInspector *inspector, GladeProject *project) { g_return_if_fail (GLADE_IS_INSPECTOR (inspector)); g_return_if_fail (GLADE_IS_PROJECT (project) || project == NULL); GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); if (priv->project == project) return; if (priv->project) { disconnect_project_signals (inspector, priv->project); /* Release our filter which releases the project */ gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), NULL); priv->filter = NULL; priv->project = NULL; } if (project) { priv->project = project; /* The filter holds our reference to 'project' */ priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->project), NULL); gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter), (GtkTreeModelFilterVisibleFunc) glade_inspector_visible_func, inspector, NULL); gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), priv->filter); g_object_unref (priv->filter); /* pass ownership of the filter to the model */ connect_project_signals (inspector, project); } g_object_notify_by_pspec (G_OBJECT (inspector), properties[PROP_PROJECT]); } /** * glade_inspector_get_project: * @inspector: a #GladeInspector * * Note that the method does not ref the returned #GladeProject. * * Returns: (transfer none): A #GladeProject */ GladeProject * glade_inspector_get_project (GladeInspector *inspector) { GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); g_return_val_if_fail (GLADE_IS_INSPECTOR (inspector), NULL); return priv->project; } /** * glade_inspector_get_selected_items: * @inspector: a #GladeInspector * * Returns the selected items in the inspector. * * Returns: (transfer container) (element-type GladeWidget): A #GList of #GladeWidget */ GList * glade_inspector_get_selected_items (GladeInspector *inspector) { GtkTreeSelection *selection; GList *items = NULL, *paths; GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); for (paths = gtk_tree_selection_get_selected_rows (selection, NULL); paths != NULL; paths = g_list_next (paths->next)) { GtkTreeIter filter_iter; GtkTreeIter iter; GtkTreePath *path = (GtkTreePath *) paths->data; g_autoptr(GObject) object = NULL; gtk_tree_model_get_iter (priv->filter, &filter_iter, path); gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (priv->filter), &iter, &filter_iter); gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1); items = g_list_prepend (items, glade_widget_get_from_gobject (object)); } g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); return items; } /** * glade_inspector_new: * * Creates a new #GladeInspector * * Returns: (transfer full): a new #GladeInspector */ GtkWidget * glade_inspector_new (void) { return g_object_new (GLADE_TYPE_INSPECTOR, NULL); } /** * glade_inspector_new_with_project: * @project: a #GladeProject * * Creates a new #GladeInspector with @project * * Returns: (transfer full): a new #GladeInspector */ GtkWidget * glade_inspector_new_with_project (GladeProject *project) { GladeInspector *inspector; g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); inspector = g_object_new (GLADE_TYPE_INSPECTOR, "project", project, NULL); /* Make sure we expended to the right path */ project_selection_changed_cb (project, inspector); return GTK_WIDGET (inspector); }