/* * Copyright (C) 2001 Ximian, Inc. * * 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: * Shane Butler * Joaquin Cuenca Abela */ #ifdef HAVE_CONFIG_H #include #endif /** * SECTION:glade-signal-editor * @Title: GladeSignalEditor * @Short_Description: An interface to edit signals for a #GladeWidget. */ #include #include #include "glade.h" #include "glade-widget.h" #include "glade-widget-adaptor.h" #include "glade-signal.h" #include "glade-signal-editor.h" #include "glade-signal-model.h" #include "glade-cell-renderer-icon.h" #include "glade-editor.h" #include "glade-command.h" #include "glade-marshallers.h" #include "glade-accumulators.h" #include "glade-project.h" #include "glade-cell-renderer-icon.h" struct _GladeSignalEditorPrivate { GtkTreeModel *model; GladeWidget *widget; GladeWidgetAdaptor *adaptor; GtkWidget *signal_tree; GtkTreeViewColumn *column_name; GtkTreeViewColumn *column_detail; GtkTreeViewColumn *column_handler; GtkTreeViewColumn *column_userdata; GtkTreeViewColumn *column_swap; GtkTreeViewColumn *column_after; GtkCellRenderer *renderer_userdata; GtkListStore *detail_store; GtkListStore *handler_store; GtkTreePath *target_focus_path; guint focus_id; }; enum { SIGNAL_ACTIVATED, CALLBACK_SUGGESTIONS, DETAIL_SUGGESTIONS, LAST_SIGNAL }; enum { PROP_0, PROP_GLADE_WIDGET }; static guint glade_signal_editor_signals[LAST_SIGNAL] = { 0 }; static gboolean tree_path_focus_idle (gpointer data); G_DEFINE_TYPE_WITH_PRIVATE (GladeSignalEditor, glade_signal_editor, GTK_TYPE_BOX) /* Utils */ static inline gboolean glade_signal_is_dummy (GladeSignal *signal) { return glade_signal_get_handler (signal) == NULL; } static void glade_signal_editor_take_target_focus_path (GladeSignalEditor *editor, GtkTreePath *path) { GladeSignalEditorPrivate *priv = editor->priv; if (priv->target_focus_path != path) { /* Set the target path */ gtk_tree_path_free (priv->target_focus_path); priv->target_focus_path = path; } if (priv->target_focus_path) { /* ensure there is an idle callback registred */ if (priv->focus_id == 0) priv->focus_id = g_idle_add (tree_path_focus_idle, editor); } else /* ensure there is no idle callback registred */ if (priv->focus_id > 0) { g_source_remove (priv->focus_id); priv->focus_id = 0; } } /* Signal handlers */ static gboolean tree_path_focus_idle (gpointer data) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (data); GtkTreeSelection *selection; GtkTreeIter iter; GladeSignal *signal; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->signal_tree)); if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) return FALSE; gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (glade_signal_is_dummy (signal)) gtk_tree_view_set_cursor (GTK_TREE_VIEW (self->priv->signal_tree), self->priv->target_focus_path, NULL, FALSE); g_object_unref (signal); glade_signal_editor_take_target_focus_path (self, NULL); return FALSE; } static void on_handler_edited (GtkCellRendererText *renderer, gchar *path, gchar *handler, gpointer user_data) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (user_data); GtkTreePath *tree_path = gtk_tree_path_new_from_string (path); GtkTreeIter iter; gchar *old_handler; GladeSignal *signal; gboolean dummy; g_return_if_fail (self->priv->widget != NULL); gtk_tree_model_get_iter (self->priv->model, &iter, tree_path); gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_HANDLER, &old_handler, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); dummy = glade_signal_is_dummy (signal); /* False alarm ? */ if (handler && !g_str_equal (old_handler, handler)) { if (!dummy) { if (strlen (handler)) { /* change an existing signal handler */ GladeSignal *old_signal; GladeSignal *new_signal; gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &old_signal, -1); new_signal = glade_signal_clone (old_signal); /* Change the new signal handler */ glade_signal_set_handler (new_signal, handler); glade_command_change_signal (self->priv->widget, old_signal, new_signal); g_object_unref (old_signal); g_object_unref (new_signal); } else { GladeSignal *deleted_signal; gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &deleted_signal, -1); /* Delete signal */ glade_command_remove_signal (self->priv->widget, deleted_signal); } } else if (strlen (handler)) { GladeSignal *new_signal; /* Get the signal name */ gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); /* Add a new signal handler */ new_signal = glade_signal_new (glade_signal_get_class (signal), handler, NULL, FALSE, FALSE); glade_signal_set_detail (new_signal, glade_signal_get_detail (signal)); glade_command_add_signal (self->priv->widget, new_signal); glade_signal_set_detail (signal, NULL); g_object_unref (new_signal); glade_signal_editor_take_target_focus_path (self, tree_path); /* make sure we do not free the path here as * glade_signal_editor_take_target_focus_path() takes ownership **/ tree_path = NULL; } } g_object_unref (signal); g_free (old_handler); gtk_tree_path_free (tree_path); } static gchar ** glade_signal_editor_callback_suggestions (GladeSignalEditor *editor, GladeSignal *signal) { GladeWidget *widget = glade_signal_editor_get_widget (editor); gchar *signal_name, **suggestions = g_new (gchar *, 10); const gchar *name, *detail; if ((detail = glade_signal_get_detail (signal))) signal_name = g_strdup_printf ("%s_%s", detail, glade_signal_get_name (signal)); else signal_name = g_strdup (glade_signal_get_name (signal)); glade_util_replace (signal_name, '-', '_'); name = glade_widget_get_name (widget); suggestions[0] = g_strdup_printf ("on_%s_%s", name, signal_name); suggestions[1] = g_strdup_printf ("%s_%s_cb", name, signal_name); suggestions[2] = g_strdup ("gtk_widget_show"); suggestions[3] = g_strdup ("gtk_widget_hide"); suggestions[4] = g_strdup ("gtk_widget_grab_focus"); suggestions[5] = g_strdup ("gtk_widget_destroy"); suggestions[6] = g_strdup ("gtk_true"); suggestions[7] = g_strdup ("gtk_false"); suggestions[8] = g_strdup ("gtk_main_quit"); suggestions[9] = NULL; return suggestions; } static gchar ** glade_signal_editor_detail_suggestions (GladeSignalEditor *editor, GladeSignal *signal) { /* We only support suggestions for notify signal */ if (!g_strcmp0 (glade_signal_get_name (signal), "notify")) { GladeSignalEditorPrivate *priv = editor->priv; const GList *l, *properties = glade_widget_adaptor_get_properties (priv->adaptor); gchar **suggestions = g_new (gchar *, g_list_length ((GList *)properties) + 1); gint i; for (i = 0, l = properties; l; l = g_list_next (l)) { GladePropertyClass *prop = l->data; if (!glade_property_class_is_visible (prop) || glade_property_class_get_virtual (prop)) continue; suggestions[i++] = g_strdup (glade_property_class_id (prop)); } suggestions[i] = NULL; return suggestions; } return NULL; } static void gse_entry_completion_ensure_model (GtkEntry *entry, GtkTreeModel *model) { GtkEntryCompletion *completion = gtk_entry_completion_new (); gtk_entry_completion_set_text_column (completion, 0); gtk_entry_completion_set_minimum_key_length (completion, 0); gtk_entry_completion_set_inline_completion (completion, FALSE); gtk_entry_completion_set_inline_selection (completion, TRUE); gtk_entry_completion_set_popup_completion (completion, TRUE); gtk_entry_completion_set_model (completion, model); gtk_entry_set_completion (entry, completion); } static void on_detail_editing_started (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, gpointer user_data) { /* Check if editable is still an entry */ if (GTK_IS_ENTRY (editable)) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (user_data); GladeSignalEditorPrivate *priv = self->priv; GtkEntry *entry = GTK_ENTRY (editable); GtkTreePath *tree_path; GtkTreeIter iter; GladeSignal *signal; gchar **suggestions; tree_path = gtk_tree_path_new_from_string (path); gtk_tree_model_get_iter (priv->model, &iter, tree_path); gtk_tree_path_free (tree_path); gtk_tree_model_get (priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (glade_signal_get_detail (signal) == NULL) gtk_entry_set_text (entry, ""); g_object_unref (signal); gtk_entry_set_completion (entry, NULL); gtk_list_store_clear (priv->detail_store); g_signal_emit (self, glade_signal_editor_signals [DETAIL_SUGGESTIONS], 0, signal, &suggestions); if (suggestions) { register GtkListStore *store = priv->detail_store; gint i; for (i = 0; suggestions[i]; i++) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, suggestions[i], -1); } gse_entry_completion_ensure_model (entry, GTK_TREE_MODEL (store)); g_strfreev (suggestions); } } } static void on_detail_edited (GtkCellRendererText *renderer, gchar *path, gchar *detail, gpointer user_data) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR(user_data); GtkTreePath *tree_path = gtk_tree_path_new_from_string (path); GtkTreeIter iter; gchar *old_detail; g_return_if_fail (self->priv->widget != NULL); gtk_tree_model_get_iter (self->priv->model, &iter, tree_path); gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_DETAIL, &old_detail, -1); if (detail && strlen (detail) && g_strcmp0 (old_detail, detail)) { /* change an existing signal detail */ GladeSignal *old_signal; gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &old_signal, -1); if (glade_signal_is_dummy (old_signal)) { glade_signal_set_detail (old_signal, detail); } else { GladeSignal *new_signal = glade_signal_clone (old_signal); glade_signal_set_detail (new_signal, detail); glade_command_change_signal (self->priv->widget, old_signal, new_signal); g_object_unref (new_signal); } g_object_unref (old_signal); } g_free (old_detail); gtk_tree_path_free (tree_path); } static void on_handler_editing_started (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, gpointer user_data) { /* Check if editable is still an entry */ if (GTK_IS_ENTRY (editable)) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (user_data); GladeSignalEditorPrivate *priv = self->priv; GtkEntry *entry = GTK_ENTRY (editable); GtkTreePath *tree_path; GtkTreeIter iter; GladeSignal *signal; gchar **suggestions; gint i; tree_path = gtk_tree_path_new_from_string (path); gtk_tree_model_get_iter (priv->model, &iter, tree_path); gtk_tree_path_free (tree_path); gtk_tree_model_get (priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (glade_signal_is_dummy (signal)) gtk_entry_set_text (entry, ""); g_signal_emit (self, glade_signal_editor_signals [CALLBACK_SUGGESTIONS], 0, signal, &suggestions); g_object_unref (signal); gtk_entry_set_completion (entry, NULL); gtk_list_store_clear (priv->handler_store); if (suggestions) { register GtkListStore *store = priv->handler_store; for (i = 0; suggestions[i]; i++) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, suggestions[i], -1); } gse_entry_completion_ensure_model (entry, GTK_TREE_MODEL (store)); g_strfreev (suggestions); } } } static void glade_signal_editor_user_data_activate (GtkCellRenderer *icon_renderer, const gchar *path_str, GladeSignalEditor *editor) { GladeSignalEditorPrivate *priv = editor->priv; GtkTreePath *path = gtk_tree_path_new_from_string (path_str); GtkTreeModel *model = priv->model; GtkTreeIter iter; GladeWidget *project_object = NULL; GladeProject *project; GladeSignal *signal; GList *selected = NULL; GList *exception = NULL; gboolean dummy; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); dummy = glade_signal_is_dummy (signal); if (!dummy) { project = glade_widget_get_project (priv->widget); if (glade_signal_get_userdata (signal)) { project_object = glade_project_get_widget_by_name (project, glade_signal_get_userdata (signal)); selected = g_list_prepend (selected, project_object); } exception = g_list_prepend (exception, priv->widget); if (glade_editor_property_show_object_dialog (project, _("Select an object to pass to the handler"), gtk_widget_get_toplevel (GTK_WIDGET (editor)), G_TYPE_OBJECT, priv->widget, &project_object)) { GladeSignal *old_signal = signal; GladeSignal *new_signal = glade_signal_clone (signal); glade_signal_set_userdata (new_signal, project_object ? glade_widget_get_name (project_object) : NULL); glade_command_change_signal (priv->widget, old_signal, new_signal); g_object_unref (new_signal); } } g_object_unref (signal); gtk_tree_path_free (path); } static void on_swap_toggled (GtkCellRendererToggle *renderer, gchar *path, gpointer user_data) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR(user_data); GtkTreePath *tree_path = gtk_tree_path_new_from_string (path); GtkTreeIter iter; GladeSignal *old_signal; GladeSignal *new_signal; g_return_if_fail (self->priv->widget != NULL); gtk_tree_model_get_iter (self->priv->model, &iter, tree_path); gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &old_signal, -1); new_signal = glade_signal_clone (old_signal); /* Change the new signal handler */ glade_signal_set_swapped (new_signal, !gtk_cell_renderer_toggle_get_active (renderer)); glade_command_change_signal (self->priv->widget, old_signal, new_signal); g_object_unref (new_signal); g_object_unref (old_signal); gtk_tree_path_free (tree_path); } static void on_after_toggled (GtkCellRendererToggle *renderer, gchar *path, gpointer user_data) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR(user_data); GtkTreePath *tree_path = gtk_tree_path_new_from_string (path); GtkTreeIter iter; GladeSignal *old_signal; GladeSignal *new_signal; g_return_if_fail (self->priv->widget != NULL); gtk_tree_model_get_iter (self->priv->model, &iter, tree_path); gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &old_signal, -1); new_signal = glade_signal_clone (old_signal); /* Change the new signal handler */ glade_signal_set_after (new_signal, !gtk_cell_renderer_toggle_get_active (renderer)); glade_command_change_signal (self->priv->widget, old_signal, new_signal); g_object_unref (new_signal); g_object_unref (old_signal); gtk_tree_path_free (tree_path); } static void glade_signal_editor_devhelp (GtkCellRenderer *cell, const gchar *path_str, GladeSignalEditor *editor) { GladeSignalEditorPrivate *priv = editor->priv; GtkTreePath *path = gtk_tree_path_new_from_string (path_str); GtkTreeModel *model = priv->model; GtkTreeIter iter; GladeWidgetAdaptor *adaptor; const GladeSignalClass *signal_class; GladeSignal *signal; gchar *book; gchar *search; g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path)); gtk_tree_path_free (path); gtk_tree_model_get (model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); signal_class = glade_signal_get_class (signal); adaptor = glade_signal_class_get_adaptor (signal_class); g_object_get (adaptor, "book", &book, NULL); search = g_strdup_printf ("The %s signal", glade_signal_get_name (signal)); glade_app_search_docs (book, glade_widget_adaptor_get_name (adaptor), search); g_free (search); g_free (book); g_object_unref (signal); } static void glade_signal_editor_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (object); GladeSignalEditorPrivate *priv = self->priv; switch (prop_id) { case PROP_GLADE_WIDGET: g_value_set_object (value, priv->widget); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void glade_signal_editor_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (object); switch (prop_id) { case PROP_GLADE_WIDGET: glade_signal_editor_load_widget (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * glade_signal_editor_new: * * Returns: a new #GladeSignalEditor */ GladeSignalEditor * glade_signal_editor_new () { GladeSignalEditor *signal_editor; signal_editor = GLADE_SIGNAL_EDITOR (g_object_new (GLADE_TYPE_SIGNAL_EDITOR, NULL, NULL)); return signal_editor; } static gint find_adaptor_by_name (GladeWidgetAdaptor *adaptor, const gchar *name) { return g_strcmp0 (glade_widget_adaptor_get_name (adaptor), name); } /** * glade_signal_editor_load_widget: * @editor: a #GladeSignalEditor * @widget: a #GladeWidget or NULL * * Load a widget in the signal editor. This will show all signals of the widget * an it's accessors in the signal editor where the user can edit them. */ void glade_signal_editor_load_widget (GladeSignalEditor *editor, GladeWidget *widget) { GladeSignalEditorPrivate *priv = editor->priv; GList *signals, *l, *adaptors = NULL; GtkTreePath *path; GtkTreeIter iter; gboolean valid; if (priv->widget != widget) { priv->widget = widget; priv->adaptor = widget ? glade_widget_get_adaptor (widget) : NULL; if (priv->widget) { g_object_ref (priv->widget); } } gtk_tree_view_set_model (GTK_TREE_VIEW (priv->signal_tree), NULL); priv->model = NULL; if (!widget) return; priv->model = glade_widget_get_signal_model (widget); gtk_tree_view_set_model (GTK_TREE_VIEW (priv->signal_tree), priv->model); if (gtk_tree_model_iter_children (priv->model, &iter, NULL)) { path = gtk_tree_model_get_path (priv->model, &iter); gtk_tree_view_expand_row (GTK_TREE_VIEW (priv->signal_tree), path, FALSE); gtk_tree_path_free (path); } /* Collect a list of adaptors which actually have used signals */ signals = glade_widget_get_signal_list (widget); for (l = signals; l; l = l->next) { GladeSignal *signal = l->data; const GladeSignalClass *signal_class; GladeWidgetAdaptor *adaptor; signal_class = glade_signal_get_class (signal); adaptor = glade_signal_class_get_adaptor (signal_class); if (!g_list_find (adaptors, adaptor)) adaptors = g_list_prepend (adaptors, adaptor); } g_list_free (signals); /* Expand any rows which actually contain used signals */ valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); while (valid) { gchar *name = NULL; gtk_tree_model_get (priv->model, &iter, GLADE_SIGNAL_COLUMN_NAME, &name, -1); if (g_list_find_custom (adaptors, name, (GCompareFunc)find_adaptor_by_name)) { path = gtk_tree_model_get_path (priv->model, &iter); gtk_tree_view_expand_row (GTK_TREE_VIEW (priv->signal_tree), path, FALSE); gtk_tree_path_free (path); } g_free (name); valid = gtk_tree_model_iter_next (priv->model, &iter); } g_list_free (adaptors); } /** * glade_signal_editor_enable_dnd: * @editor: a #GladeSignalEditor * @enabled: whether the drag and drop support should be enabled * * If drag and drop support is enabled, the user will be able to drag signal handler * from the tree to some editor. The type of the dnd data will be "application/x-glade-signal" * and it will be in the form of "widget:signal:handler" so for example * "GtkToggleButton:toggled:on_toggle_button_toggled". */ void glade_signal_editor_enable_dnd (GladeSignalEditor *editor, gboolean enabled) { GladeSignalEditorPrivate *priv = editor->priv; if (enabled) { const GtkTargetEntry entry = { "application/x-glade-signal", GTK_TARGET_OTHER_WIDGET, 1 }; /* DND */ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW(priv->signal_tree), GDK_BUTTON1_MASK, &entry, 1, GDK_ACTION_COPY); } else { gtk_tree_view_unset_rows_drag_source (GTK_TREE_VIEW (priv->signal_tree)); } } static void glade_signal_editor_dispose (GObject *object) { GladeSignalEditorPrivate *priv = GLADE_SIGNAL_EDITOR (object)->priv; g_clear_object (&priv->detail_store); g_clear_object (&priv->handler_store); G_OBJECT_CLASS (glade_signal_editor_parent_class)->dispose (object); } #define BORDER 10 static cairo_surface_t* create_rich_drag_surface (GtkWidget *widget, const gchar *text) { GtkStyleContext *context = gtk_widget_get_style_context (widget); GtkStateFlags state = gtk_widget_get_state_flags (widget); PangoLayout *layout = pango_layout_new (gtk_widget_get_pango_context (widget)); cairo_t *cr; cairo_surface_t *s; gint width, height; GdkRGBA rgba; pango_layout_set_text (layout, text, -1); pango_layout_get_size (layout, &width, &height); width = PANGO_PIXELS(width) + BORDER; height = PANGO_PIXELS(height) + BORDER; s = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR, width, height); cr = cairo_create (s); gtk_style_context_get_background_color (context, state, &rgba); gdk_cairo_set_source_rgba (cr, &rgba); cairo_paint (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_move_to (cr, BORDER/2, BORDER/2); pango_cairo_show_layout (cr, layout); cairo_rectangle (cr, 0, 0, width, height); cairo_stroke (cr); cairo_destroy (cr); g_object_unref (layout); return s; } static void glade_signal_editor_drag_begin (GtkWidget *widget, GdkDragContext *context, gpointer user_data) { cairo_surface_t *s = NULL; GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gchar* handler; gchar* text; gtk_tree_model_get (model, &iter, GLADE_SIGNAL_COLUMN_HANDLER, &handler, -1); text = g_strdup_printf ("%s ()", handler); g_free (handler); s = create_rich_drag_surface (widget, text); g_free (text); } if (s) { gtk_drag_set_icon_surface (context, s); cairo_surface_destroy (s); } else { gtk_drag_set_icon_default (context); } } static void glade_signal_editor_name_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignal *signal; gboolean show_name; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, GLADE_SIGNAL_COLUMN_SHOW_NAME, &show_name, -1); if (signal) { gboolean dummy; dummy = glade_signal_is_dummy (signal); if (!dummy && show_name) { g_object_set (renderer, "weight", PANGO_WEIGHT_BOLD, NULL); } else { g_object_set (renderer, "weight", PANGO_WEIGHT_NORMAL, NULL); } g_object_unref (signal); } else g_object_set (renderer, "weight", PANGO_WEIGHT_NORMAL, NULL); g_object_set (renderer, "visible", show_name, NULL); } static void glade_signal_editor_handler_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignalEditor* editor = GLADE_SIGNAL_EDITOR (data); GladeSignal* signal; GdkRGBA color; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (signal) { gboolean dummy; GtkStyleContext* context = gtk_widget_get_style_context (editor->priv->signal_tree); dummy = glade_signal_is_dummy (signal); if (dummy) { gtk_style_context_save (context); gtk_style_context_set_state (context, gtk_style_context_get_state (context) | GTK_STATE_FLAG_INSENSITIVE); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_ITALIC, "foreground-rgba", &color, NULL); gtk_style_context_restore (context); } else { gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_NORMAL, "foreground-rgba", &color, NULL); } g_object_set (renderer, "visible", TRUE, "sensitive", TRUE, "editable", TRUE, NULL); g_object_unref (signal); } else { g_object_set (renderer, "visible", FALSE, "editable", FALSE, NULL); } } static void glade_signal_editor_detail_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignalEditor *editor = GLADE_SIGNAL_EDITOR (data); GladeSignal *signal; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (signal && (glade_signal_class_get_flags (glade_signal_get_class (signal)) & G_SIGNAL_DETAILED)) { GdkRGBA color; gboolean dummy; GtkStyleContext* context = gtk_widget_get_style_context (editor->priv->signal_tree); dummy = glade_signal_is_dummy (signal); if (dummy || !glade_signal_get_detail (signal)) { gtk_style_context_save (context); gtk_style_context_set_state (context, gtk_style_context_get_state (context) | GTK_STATE_FLAG_INSENSITIVE); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_ITALIC, "foreground-rgba", &color, NULL); gtk_style_context_restore (context); } else { gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_NORMAL, "foreground-rgba", &color, NULL); } g_object_set (renderer, "sensitive", TRUE, "visible", TRUE, "editable", TRUE, NULL); g_object_unref (signal); } else { g_object_set (renderer, "editable", FALSE, "sensitive", FALSE, "visible", FALSE, NULL); } } static void glade_signal_editor_data_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignalEditor *editor = GLADE_SIGNAL_EDITOR (data); GladeSignal *signal; GdkRGBA color; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (signal) { gboolean dummy; dummy = glade_signal_is_dummy (signal); g_object_set (renderer, "sensitive", !dummy, "visible", TRUE, NULL); if (GTK_IS_CELL_RENDERER_TEXT (renderer)) { GtkStyleContext* context = gtk_widget_get_style_context (editor->priv->signal_tree); if (dummy || !glade_signal_get_userdata (signal)) { gtk_style_context_save (context); gtk_style_context_set_state (context, gtk_style_context_get_state (context) | GTK_STATE_FLAG_INSENSITIVE); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_ITALIC, "foreground-rgba", &color, NULL); gtk_style_context_restore (context); } else { gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color); g_object_set (renderer, "style", PANGO_STYLE_NORMAL, "foreground-rgba", &color, NULL); } } g_object_unref (signal); } else { g_object_set (renderer, "sensitive", FALSE, "visible", FALSE, NULL); } } static void glade_signal_editor_warning_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignal *signal; gboolean visible = FALSE; gboolean show_name; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, GLADE_SIGNAL_COLUMN_SHOW_NAME, &show_name, -1); if (signal) { const gchar* warning = glade_signal_get_support_warning (signal); visible = warning && strlen (warning); g_object_unref (signal); } g_object_set (renderer, "visible", (visible && show_name), NULL); } static void glade_signal_editor_devhelp_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GladeSignal *signal; gtk_tree_model_get (model, iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if (signal) { const GladeSignalClass *class = glade_signal_get_class (signal); GladeWidgetAdaptor *adaptor = glade_signal_class_get_adaptor (class); gchar *book; g_object_get (adaptor, "book", &book, NULL); g_object_set (renderer, "visible", book != NULL, NULL); g_free (book); g_object_unref (signal); } else { g_object_set (renderer, "visible", FALSE, NULL); } } GladeWidget* glade_signal_editor_get_widget (GladeSignalEditor *editor) { g_return_val_if_fail (GLADE_IS_SIGNAL_EDITOR(editor), NULL); return editor->priv->widget; } static void glade_signal_editor_signal_activate (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, GladeSignalEditor *editor) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (editor); if (self->priv->widget == NULL || column != self->priv->column_name) return; GladeSignal *signal = NULL; GtkTreeIter iter; gtk_tree_model_get_iter (self->priv->model, &iter, path); gtk_tree_model_get (self->priv->model, &iter, GLADE_SIGNAL_COLUMN_SIGNAL, &signal, -1); if(glade_signal_is_dummy (signal)) return; g_signal_emit (editor, glade_signal_editor_signals[SIGNAL_ACTIVATED], 0, signal, NULL); g_object_unref (signal); return; } static void glade_signal_editor_finalize (GObject *object) { GladeSignalEditor *self = GLADE_SIGNAL_EDITOR (object); /* unregister any idle callback if there is any */ glade_signal_editor_take_target_focus_path (self, NULL); } static void glade_signal_editor_init (GladeSignalEditor *self) { GtkWidget *scroll; GtkCellRenderer *renderer; GtkCellArea *cell_area; GladeSignalEditorPrivate *priv; self->priv = glade_signal_editor_get_instance_private (self); priv = self->priv; /* Create signal tree */ priv->signal_tree = gtk_tree_view_new (); g_signal_connect (G_OBJECT (priv->signal_tree), "row-activated", G_CALLBACK (glade_signal_editor_signal_activate), self); /* Create columns */ /* Signal name */ priv->column_name = gtk_tree_view_column_new (); /* version warning */ renderer = gtk_cell_renderer_pixbuf_new (); g_object_set (G_OBJECT (renderer), "icon-name", "dialog-warning", "xalign", 0.0, NULL); gtk_tree_view_column_set_cell_data_func (priv->column_name, renderer, glade_signal_editor_warning_cell_data_func, self, NULL); gtk_tree_view_column_pack_start (priv->column_name, renderer, FALSE); /* signal name */ renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 20, NULL); gtk_tree_view_column_pack_end (priv->column_name, renderer, TRUE); gtk_tree_view_column_set_attributes (priv->column_name, renderer, "text", GLADE_SIGNAL_COLUMN_NAME, NULL); gtk_tree_view_column_set_cell_data_func (priv->column_name, renderer, glade_signal_editor_name_cell_data_func, self, NULL); gtk_tree_view_column_set_resizable (priv->column_name, TRUE); gtk_tree_view_column_set_expand (priv->column_name, TRUE); gtk_tree_view_column_set_title (priv->column_name, _("Signal")); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_name); /* Signal detail */ renderer = gtk_cell_renderer_text_new (); g_signal_connect (renderer, "edited", G_CALLBACK(on_detail_edited), self); g_signal_connect (renderer, "editing-started", G_CALLBACK (on_detail_editing_started), self); priv->column_detail = gtk_tree_view_column_new_with_attributes (_("Detail"), renderer, "text", GLADE_SIGNAL_COLUMN_DETAIL, NULL); gtk_tree_view_column_set_cell_data_func (priv->column_detail, renderer, glade_signal_editor_detail_cell_data_func, self, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_detail); /* Signal handler */ renderer = gtk_cell_renderer_text_new (); g_object_set (renderer, "editable", FALSE, NULL); g_signal_connect (renderer, "edited", G_CALLBACK(on_handler_edited), self); g_signal_connect (renderer, "editing-started", G_CALLBACK (on_handler_editing_started), self); priv->column_handler = gtk_tree_view_column_new_with_attributes (_("Handler"), renderer, "text", GLADE_SIGNAL_COLUMN_HANDLER, NULL); gtk_tree_view_column_set_cell_data_func (priv->column_handler, renderer, glade_signal_editor_handler_cell_data_func, self, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_handler); /* Signal user_data */ priv->renderer_userdata = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (priv->renderer_userdata), "editable", FALSE, "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 10, NULL); cell_area = gtk_cell_area_box_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cell_area), priv->renderer_userdata, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (cell_area), priv->renderer_userdata, "text", GLADE_SIGNAL_COLUMN_OBJECT, NULL); renderer = glade_cell_renderer_icon_new (); g_object_set (G_OBJECT (renderer), "icon-name", "gtk-edit", NULL); g_signal_connect (G_OBJECT (renderer), "activate", G_CALLBACK (glade_signal_editor_user_data_activate), self); gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (cell_area), renderer, FALSE); gtk_cell_area_add_focus_sibling (cell_area, renderer, priv->renderer_userdata); priv->column_userdata = gtk_tree_view_column_new_with_area (cell_area); gtk_tree_view_column_set_title (priv->column_userdata, _("User data")); gtk_tree_view_column_set_cell_data_func (priv->column_userdata, priv->renderer_userdata, glade_signal_editor_data_cell_data_func, self, NULL); gtk_tree_view_column_set_cell_data_func (priv->column_userdata, renderer, glade_signal_editor_data_cell_data_func, self, NULL); gtk_tree_view_column_set_resizable (priv->column_userdata, TRUE); gtk_tree_view_column_set_expand (priv->column_userdata, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_userdata); /* Swap signal */ renderer = gtk_cell_renderer_toggle_new (); g_signal_connect (renderer, "toggled", G_CALLBACK (on_swap_toggled), self); priv->column_swap = gtk_tree_view_column_new_with_attributes (_("Swap"), renderer, "active", GLADE_SIGNAL_COLUMN_SWAP, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_swap); gtk_tree_view_column_set_cell_data_func (priv->column_swap, renderer, glade_signal_editor_data_cell_data_func, self, NULL); /* After */ cell_area = gtk_cell_area_box_new (); renderer = gtk_cell_renderer_toggle_new (); g_object_set (G_OBJECT (renderer), "xalign", 0.0, NULL); g_signal_connect (renderer, "toggled", G_CALLBACK (on_after_toggled), self); priv->column_after = gtk_tree_view_column_new_with_area (cell_area); gtk_tree_view_column_set_title (priv->column_after, _("After")); gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->signal_tree), priv->column_after); gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_area), renderer, FALSE, TRUE, FALSE); gtk_cell_area_attribute_connect (cell_area, renderer, "active", GLADE_SIGNAL_COLUMN_AFTER); gtk_tree_view_column_set_cell_data_func (priv->column_after, renderer, glade_signal_editor_data_cell_data_func, self, NULL); /* Devhelp */ if (glade_util_have_devhelp ()) { renderer = glade_cell_renderer_icon_new (); g_object_set (G_OBJECT (renderer), "activatable", TRUE, NULL); if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), GLADE_DEVHELP_ICON_NAME)) g_object_set (G_OBJECT (renderer), "icon-name", GLADE_DEVHELP_ICON_NAME, NULL); else g_object_set (G_OBJECT (renderer), "icon-name", "dialog-information", NULL); g_signal_connect (G_OBJECT (renderer), "activate", G_CALLBACK (glade_signal_editor_devhelp), self); gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_area), renderer, FALSE, TRUE, FALSE); gtk_tree_view_column_set_cell_data_func (priv->column_after, renderer, glade_signal_editor_devhelp_cell_data_func, self, NULL); } /* Tooltips */ gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (self->priv->signal_tree), GLADE_SIGNAL_COLUMN_TOOLTIP); /* Create scrolled window */ scroll = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (scroll), self->priv->signal_tree); gtk_box_pack_start (GTK_BOX (self), scroll, TRUE, TRUE, 0); /* Dnd */ g_signal_connect_after (self->priv->signal_tree, "drag-begin", G_CALLBACK(glade_signal_editor_drag_begin), self); /* Detail completion */ priv->detail_store = gtk_list_store_new (1, G_TYPE_STRING); /* Handler completion */ priv->handler_store = gtk_list_store_new (1, G_TYPE_STRING); /* Emit created signal */ g_signal_emit_by_name (glade_app_get(), "signal-editor-created", self); gtk_widget_show_all (GTK_WIDGET(self)); } static void glade_signal_editor_class_init (GladeSignalEditorClass *klass) { GObjectClass *object_class; glade_signal_editor_parent_class = g_type_class_peek_parent (klass); object_class = G_OBJECT_CLASS (klass); object_class->get_property = glade_signal_editor_get_property; object_class->set_property = glade_signal_editor_set_property; object_class->dispose = glade_signal_editor_dispose; object_class->finalize = glade_signal_editor_finalize; klass->callback_suggestions = glade_signal_editor_callback_suggestions; klass->detail_suggestions = glade_signal_editor_detail_suggestions; /** * GladeSignalEditor::signal-activated: * @signal_editor: the object which received the signal * * Emitted when a item is activated in the GladeInspector. */ glade_signal_editor_signals[SIGNAL_ACTIVATED] = g_signal_new ("signal-activated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GLADE_TYPE_SIGNAL /* Signal data formatted string */ ); /** * GladeSignalEditor::callback-suggestions: * @editor: the object which received the signal * @signal: the #GladeSignal that needs callbacks suggestions * @suggestions: Return * * Emitted when the editor needs to show a list of callbacks suggestions to the user. * * Returns wheter or not the event was handled. */ glade_signal_editor_signals[CALLBACK_SUGGESTIONS] = g_signal_new ("callback-suggestions", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeSignalEditorClass, callback_suggestions), _glade_strv_handled_accumulator, NULL, _glade_marshal_BOXED__OBJECT, G_TYPE_STRV, 1, GLADE_TYPE_SIGNAL); /** * GladeSignalEditor::detail-suggestions: * @editor: the object which received the signal * @signal: the #GladeSignal that needs callbacks suggestions * @suggestions: Return * * Emitted when the editor needs to show a list of detail suggestions to the user. * * Returns wheter or not the event was handled. */ glade_signal_editor_signals[DETAIL_SUGGESTIONS] = g_signal_new ("detail-suggestions", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GladeSignalEditorClass, detail_suggestions), _glade_strv_handled_accumulator, NULL, _glade_marshal_BOXED__OBJECT, G_TYPE_STRV, 1, GLADE_TYPE_SIGNAL); g_object_class_install_property (object_class, PROP_GLADE_WIDGET, g_param_spec_object ("glade-widget", _("Glade Widget"), _("The glade widget to edit signals"), GTK_TYPE_TREE_MODEL, G_PARAM_READWRITE)); }