/* * Copyright (c) 2014 Red Hat, Inc. * * 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 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 library. If not, see . */ #include "config.h" #include #include "signals-list.h" #include "gtkcellrenderer.h" #include "gtkliststore.h" #include "gtktextbuffer.h" #include "gtktogglebutton.h" #include "gtktreeviewcolumn.h" #include "gtklabel.h" enum { COLUMN_NAME, COLUMN_CLASS, COLUMN_CONNECTED, COLUMN_COUNT, COLUMN_NO_HOOKS, COLUMN_SIGNAL_ID, COLUMN_HOOK_ID }; enum { PROP_0, PROP_TRACE_BUTTON, PROP_CLEAR_BUTTON }; struct _GtkInspectorSignalsListPrivate { GtkWidget *view; GtkListStore *model; GtkTextBuffer *text; GtkWidget *log_win; GtkWidget *trace_button; GtkWidget *clear_button; GtkTreeViewColumn *count_column; GtkCellRenderer *count_renderer; GObject *object; GHashTable *iters; gboolean tracing; }; G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorSignalsList, gtk_inspector_signals_list, GTK_TYPE_PANED) static GType * get_types (GObject *object, guint *length) { GHashTable *seen; GType *ret; GType type; GType *iface; gint i; seen = g_hash_table_new (g_direct_hash, g_direct_equal); type = ((GTypeInstance*)object)->g_class->g_type; while (type) { g_hash_table_add (seen, GSIZE_TO_POINTER (type)); iface = g_type_interfaces (type, NULL); for (i = 0; iface[i]; i++) g_hash_table_add (seen, GSIZE_TO_POINTER (iface[i])); g_free (iface); type = g_type_parent (type); } ret = (GType *)g_hash_table_get_keys_as_array (seen, length); g_hash_table_unref (seen); return ret; } static void add_signals (GtkInspectorSignalsList *sl, GType type, GObject *object) { guint *ids; guint n_ids; gint i; GSignalQuery query; GtkTreeIter iter; gboolean has_handler; if (!G_TYPE_IS_INSTANTIATABLE (type) && !G_TYPE_IS_INTERFACE (type)) return; ids = g_signal_list_ids (type, &n_ids); for (i = 0; i < n_ids; i++) { g_signal_query (ids[i], &query); has_handler = g_signal_has_handler_pending (object, ids[i], 0, TRUE); gtk_list_store_append (sl->priv->model, &iter); gtk_list_store_set (sl->priv->model, &iter, COLUMN_NAME, query.signal_name, COLUMN_CLASS, g_type_name (type), COLUMN_CONNECTED, has_handler ? _("Yes") : "", COLUMN_COUNT, 0, COLUMN_NO_HOOKS, (query.signal_flags & G_SIGNAL_NO_HOOKS) != 0, COLUMN_SIGNAL_ID, ids[i], COLUMN_HOOK_ID, 0, -1); g_hash_table_insert (sl->priv->iters, GINT_TO_POINTER (ids[i]), gtk_tree_iter_copy (&iter)); } g_free (ids); } static void read_signals_from_object (GtkInspectorSignalsList *sl, GObject *object) { GType *types; guint length; gint i; types = get_types (object, &length); for (i = 0; i < length; i++) add_signals (sl, types[i], object); g_free (types); } static void stop_tracing (GtkInspectorSignalsList *sl); void gtk_inspector_signals_list_set_object (GtkInspectorSignalsList *sl, GObject *object) { if (sl->priv->object == object) return; stop_tracing (sl); gtk_list_store_clear (sl->priv->model); g_hash_table_remove_all (sl->priv->iters); sl->priv->object = object; if (object) read_signals_from_object (sl, object); } static void render_count (GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gint count; gboolean no_hooks; gchar text[100]; gtk_tree_model_get (model, iter, COLUMN_COUNT, &count, COLUMN_NO_HOOKS, &no_hooks, -1); if (no_hooks) { g_object_set (renderer, "markup", "(untraceable)", NULL); } else if (count != 0) { g_snprintf (text, 100, "%d", count); g_object_set (renderer, "text", text, NULL); } else g_object_set (renderer, "text", "", NULL); } static void gtk_inspector_signals_list_init (GtkInspectorSignalsList *sl) { sl->priv = gtk_inspector_signals_list_get_instance_private (sl); gtk_widget_init_template (GTK_WIDGET (sl)); gtk_tree_view_column_set_cell_data_func (sl->priv->count_column, sl->priv->count_renderer, render_count, NULL, NULL); sl->priv->iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_tree_iter_free); } static gboolean trace_hook (GSignalInvocationHint *ihint, guint n_param_values, const GValue *param_values, gpointer data) { GtkInspectorSignalsList *sl = data; GObject *object; object = g_value_get_object (param_values); if (object == sl->priv->object) { gint count; GtkTreeIter *iter; iter = (GtkTreeIter *)g_hash_table_lookup (sl->priv->iters, GINT_TO_POINTER (ihint->signal_id)); gtk_tree_model_get (GTK_TREE_MODEL (sl->priv->model), iter, COLUMN_COUNT, &count, -1); gtk_list_store_set (sl->priv->model, iter, COLUMN_COUNT, count + 1, -1); } return TRUE; } static gboolean start_tracing_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GtkInspectorSignalsList *sl = data; guint signal_id; gulong hook_id; gboolean no_hooks; gtk_tree_model_get (model, iter, COLUMN_SIGNAL_ID, &signal_id, COLUMN_HOOK_ID, &hook_id, COLUMN_NO_HOOKS, &no_hooks, -1); g_assert (signal_id != 0); g_assert (hook_id == 0); if (!no_hooks) { hook_id = g_signal_add_emission_hook (signal_id, 0, trace_hook, sl, NULL); gtk_list_store_set (GTK_LIST_STORE (model), iter, COLUMN_COUNT, 0, COLUMN_HOOK_ID, hook_id, -1); } return FALSE; } static gboolean stop_tracing_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { guint signal_id; gulong hook_id; gtk_tree_model_get (model, iter, COLUMN_SIGNAL_ID, &signal_id, COLUMN_HOOK_ID, &hook_id, -1); g_assert (signal_id != 0); if (hook_id != 0) { g_signal_remove_emission_hook (signal_id, hook_id); gtk_list_store_set (GTK_LIST_STORE (model), iter, COLUMN_HOOK_ID, 0, -1); } return FALSE; } static void start_tracing (GtkInspectorSignalsList *sl) { sl->priv->tracing = TRUE; gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), start_tracing_cb, sl); } static void stop_tracing (GtkInspectorSignalsList *sl) { sl->priv->tracing = FALSE; gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), stop_tracing_cb, sl); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sl->priv->trace_button), FALSE); } static void toggle_tracing (GtkToggleButton *button, GtkInspectorSignalsList *sl) { if (gtk_toggle_button_get_active (button) == sl->priv->tracing) return; //gtk_widget_show (sl->priv->log_win); if (gtk_toggle_button_get_active (button)) start_tracing (sl); else stop_tracing (sl); } static gboolean clear_log_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { gtk_list_store_set (GTK_LIST_STORE (model), iter, COLUMN_COUNT, 0, -1); return FALSE; } static void clear_log (GtkButton *button, GtkInspectorSignalsList *sl) { gtk_text_buffer_set_text (sl->priv->text, "", -1); gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), clear_log_cb, sl); } static void get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object); switch (param_id) { case PROP_TRACE_BUTTON: g_value_take_object (value, sl->priv->trace_button); break; case PROP_CLEAR_BUTTON: g_value_take_object (value, sl->priv->trace_button); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object); switch (param_id) { case PROP_TRACE_BUTTON: sl->priv->trace_button = g_value_get_object (value); break; case PROP_CLEAR_BUTTON: sl->priv->clear_button = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void constructed (GObject *object) { GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object); g_signal_connect (sl->priv->trace_button, "toggled", G_CALLBACK (toggle_tracing), sl); g_signal_connect (sl->priv->clear_button, "clicked", G_CALLBACK (clear_log), sl); } static void gtk_inspector_signals_list_class_init (GtkInspectorSignalsListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = constructed; object_class->get_property = get_property; object_class->set_property = set_property; g_object_class_install_property (object_class, PROP_TRACE_BUTTON, g_param_spec_object ("trace-button", NULL, NULL, GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_CLEAR_BUTTON, g_param_spec_object ("clear-button", NULL, NULL, GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/signals-list.ui"); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, view); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, model); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, text); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, log_win); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, count_column); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, count_renderer); } // vim: set et sw=2 ts=2: