360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| #include <glib/gi18n-lib.h>
 | |
| 
 | |
| #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
 | |
| };
 | |
| 
 | |
| struct _GtkInspectorSignalsListPrivate
 | |
| {
 | |
|   GtkWidget *view;
 | |
|   GtkListStore *model;
 | |
|   GtkTextBuffer *text;
 | |
|   GtkWidget *log_win;
 | |
|   GtkWidget *trace_button;
 | |
|   GtkWidget *clear_button;
 | |
|   GtkWidget *object_title;
 | |
|   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)
 | |
|     {
 | |
|       const gchar *title;
 | |
| 
 | |
|       title = (const gchar *)g_object_get_data (object, "gtk-inspector-object-title");
 | |
|       gtk_label_set_label (GTK_LABEL (sl->priv->object_title), title);
 | |
| 
 | |
|       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", "<i>(untraceable)</i>", 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
 | |
| gtk_inspector_signals_list_class_init (GtkInspectorSignalsListClass *klass)
 | |
| {
 | |
|   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 | |
| 
 | |
|   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);
 | |
|   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, trace_button);
 | |
|   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, clear_button);
 | |
|   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, object_title);
 | |
|   gtk_widget_class_bind_template_callback (widget_class, toggle_tracing);
 | |
|   gtk_widget_class_bind_template_callback (widget_class, clear_log);
 | |
| }
 | |
| 
 | |
| // vim: set et sw=2 ts=2:
 | 
