1 /*
2  * Copyright (c) 2014 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 #include <glib/gi18n-lib.h>
20 
21 #include "signals-list.h"
22 
23 #include "gtkcellrenderer.h"
24 #include "gtkliststore.h"
25 #include "gtktextbuffer.h"
26 #include "gtktogglebutton.h"
27 #include "gtktreeviewcolumn.h"
28 #include "gtklabel.h"
29 
30 enum
31 {
32   COLUMN_NAME,
33   COLUMN_CLASS,
34   COLUMN_CONNECTED,
35   COLUMN_COUNT,
36   COLUMN_NO_HOOKS,
37   COLUMN_SIGNAL_ID,
38   COLUMN_HOOK_ID
39 };
40 
41 enum
42 {
43   PROP_0,
44   PROP_TRACE_BUTTON,
45   PROP_CLEAR_BUTTON
46 };
47 
48 struct _GtkInspectorSignalsListPrivate
49 {
50   GtkWidget *view;
51   GtkListStore *model;
52   GtkTextBuffer *text;
53   GtkWidget *log_win;
54   GtkWidget *trace_button;
55   GtkWidget *clear_button;
56   GtkTreeViewColumn *count_column;
57   GtkCellRenderer *count_renderer;
58   GObject *object;
59   GHashTable *iters;
60   gboolean tracing;
61 };
62 
G_DEFINE_TYPE_WITH_PRIVATE(GtkInspectorSignalsList,gtk_inspector_signals_list,GTK_TYPE_PANED)63 G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorSignalsList, gtk_inspector_signals_list, GTK_TYPE_PANED)
64 
65 static GType *
66 get_types (GObject *object, guint *length)
67 {
68   GHashTable *seen;
69   GType *ret;
70   GType type;
71   GType *iface;
72   gint i;
73 
74   seen = g_hash_table_new (g_direct_hash, g_direct_equal);
75 
76   type = ((GTypeInstance*)object)->g_class->g_type;
77   while (type)
78     {
79       g_hash_table_add (seen, GSIZE_TO_POINTER (type));
80       iface = g_type_interfaces (type, NULL);
81       for (i = 0; iface[i]; i++)
82         g_hash_table_add (seen, GSIZE_TO_POINTER (iface[i]));
83       g_free (iface);
84       type = g_type_parent (type);
85     }
86 
87   ret = (GType *)g_hash_table_get_keys_as_array (seen, length);
88   g_hash_table_unref (seen);
89 
90   return ret;
91 }
92 
93 static void
add_signals(GtkInspectorSignalsList * sl,GType type,GObject * object)94 add_signals (GtkInspectorSignalsList *sl,
95              GType                    type,
96              GObject                 *object)
97 {
98   guint *ids;
99   guint n_ids;
100   gint i;
101   GSignalQuery query;
102   GtkTreeIter iter;
103   gboolean has_handler;
104 
105   if (!G_TYPE_IS_INSTANTIATABLE (type) && !G_TYPE_IS_INTERFACE (type))
106     return;
107 
108   ids = g_signal_list_ids (type, &n_ids);
109   for (i = 0; i < n_ids; i++)
110     {
111       g_signal_query (ids[i], &query);
112       has_handler = g_signal_has_handler_pending (object, ids[i], 0, TRUE);
113       gtk_list_store_append (sl->priv->model, &iter);
114       gtk_list_store_set (sl->priv->model, &iter,
115                           COLUMN_NAME, query.signal_name,
116                           COLUMN_CLASS, g_type_name (type),
117                           COLUMN_CONNECTED, has_handler ? _("Yes") : "",
118                           COLUMN_COUNT, 0,
119                           COLUMN_NO_HOOKS, (query.signal_flags & G_SIGNAL_NO_HOOKS) != 0,
120                           COLUMN_SIGNAL_ID, ids[i],
121                           COLUMN_HOOK_ID, 0,
122                           -1);
123       g_hash_table_insert (sl->priv->iters,
124                            GINT_TO_POINTER (ids[i]), gtk_tree_iter_copy (&iter));
125     }
126   g_free (ids);
127 }
128 
129 static void
read_signals_from_object(GtkInspectorSignalsList * sl,GObject * object)130 read_signals_from_object (GtkInspectorSignalsList *sl,
131                           GObject                 *object)
132 {
133   GType *types;
134   guint length;
135   gint i;
136 
137   types = get_types (object, &length);
138   for (i = 0; i < length; i++)
139     add_signals (sl, types[i], object);
140   g_free (types);
141 }
142 
143 static void stop_tracing (GtkInspectorSignalsList *sl);
144 
145 void
gtk_inspector_signals_list_set_object(GtkInspectorSignalsList * sl,GObject * object)146 gtk_inspector_signals_list_set_object (GtkInspectorSignalsList *sl,
147                                        GObject                 *object)
148 {
149   if (sl->priv->object == object)
150     return;
151 
152   stop_tracing (sl);
153   gtk_list_store_clear (sl->priv->model);
154   g_hash_table_remove_all (sl->priv->iters);
155 
156   sl->priv->object = object;
157 
158   if (object)
159     read_signals_from_object (sl, object);
160 }
161 
162 static void
render_count(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)163 render_count (GtkTreeViewColumn *column,
164               GtkCellRenderer   *renderer,
165               GtkTreeModel      *model,
166               GtkTreeIter       *iter,
167               gpointer           data)
168 {
169   gint count;
170   gboolean no_hooks;
171   gchar text[100];
172 
173   gtk_tree_model_get (model, iter,
174                       COLUMN_COUNT, &count,
175                       COLUMN_NO_HOOKS, &no_hooks,
176                       -1);
177   if (no_hooks)
178     {
179       g_object_set (renderer, "markup", "<i>(untraceable)</i>", NULL);
180     }
181   else if (count != 0)
182     {
183       g_snprintf (text, 100, "%d", count);
184       g_object_set (renderer, "text", text, NULL);
185     }
186   else
187     g_object_set (renderer, "text", "", NULL);
188 }
189 
190 static void
gtk_inspector_signals_list_init(GtkInspectorSignalsList * sl)191 gtk_inspector_signals_list_init (GtkInspectorSignalsList *sl)
192 {
193   sl->priv = gtk_inspector_signals_list_get_instance_private (sl);
194   gtk_widget_init_template (GTK_WIDGET (sl));
195 
196   gtk_tree_view_column_set_cell_data_func (sl->priv->count_column,
197                                            sl->priv->count_renderer,
198                                            render_count,
199                                            NULL, NULL);
200 
201   sl->priv->iters = g_hash_table_new_full (g_direct_hash,
202                                            g_direct_equal,
203                                            NULL,
204                                            (GDestroyNotify) gtk_tree_iter_free);
205 }
206 
207 static gboolean
trace_hook(GSignalInvocationHint * ihint,guint n_param_values,const GValue * param_values,gpointer data)208 trace_hook (GSignalInvocationHint *ihint,
209             guint                  n_param_values,
210             const GValue          *param_values,
211             gpointer               data)
212 {
213   GtkInspectorSignalsList *sl = data;
214   GObject *object;
215 
216   object = g_value_get_object (param_values);
217 
218   if (object == sl->priv->object)
219     {
220       gint count;
221       GtkTreeIter *iter;
222 
223       iter = (GtkTreeIter *)g_hash_table_lookup (sl->priv->iters, GINT_TO_POINTER (ihint->signal_id));
224 
225       gtk_tree_model_get (GTK_TREE_MODEL (sl->priv->model), iter, COLUMN_COUNT, &count, -1);
226       gtk_list_store_set (sl->priv->model, iter, COLUMN_COUNT, count + 1, -1);
227     }
228 
229   return TRUE;
230 }
231 
232 static gboolean
start_tracing_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)233 start_tracing_cb (GtkTreeModel *model,
234                   GtkTreePath  *path,
235                   GtkTreeIter  *iter,
236                   gpointer      data)
237 {
238   GtkInspectorSignalsList *sl = data;
239   guint signal_id;
240   gulong hook_id;
241   gboolean no_hooks;
242 
243   gtk_tree_model_get (model, iter,
244                       COLUMN_SIGNAL_ID, &signal_id,
245                       COLUMN_HOOK_ID, &hook_id,
246                       COLUMN_NO_HOOKS, &no_hooks,
247                       -1);
248 
249   g_assert (signal_id != 0);
250   g_assert (hook_id == 0);
251 
252   if (!no_hooks)
253     {
254       hook_id = g_signal_add_emission_hook (signal_id, 0, trace_hook, sl, NULL);
255 
256       gtk_list_store_set (GTK_LIST_STORE (model), iter,
257                           COLUMN_COUNT, 0,
258                           COLUMN_HOOK_ID, hook_id,
259                           -1);
260     }
261 
262   return FALSE;
263 }
264 
265 static gboolean
stop_tracing_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)266 stop_tracing_cb (GtkTreeModel *model,
267                  GtkTreePath  *path,
268                  GtkTreeIter  *iter,
269                  gpointer      data)
270 {
271   guint signal_id;
272   gulong hook_id;
273 
274   gtk_tree_model_get (model, iter,
275                       COLUMN_SIGNAL_ID, &signal_id,
276                       COLUMN_HOOK_ID, &hook_id,
277                       -1);
278 
279   g_assert (signal_id != 0);
280 
281   if (hook_id != 0)
282     {
283       g_signal_remove_emission_hook (signal_id, hook_id);
284       gtk_list_store_set (GTK_LIST_STORE (model), iter,
285                           COLUMN_HOOK_ID, 0,
286                           -1);
287     }
288 
289   return FALSE;
290 }
291 
292 static void
start_tracing(GtkInspectorSignalsList * sl)293 start_tracing (GtkInspectorSignalsList *sl)
294 {
295   sl->priv->tracing = TRUE;
296   gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), start_tracing_cb, sl);
297 }
298 
299 static void
stop_tracing(GtkInspectorSignalsList * sl)300 stop_tracing (GtkInspectorSignalsList *sl)
301 {
302   sl->priv->tracing = FALSE;
303   gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), stop_tracing_cb, sl);
304   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sl->priv->trace_button), FALSE);
305 }
306 
307 static void
toggle_tracing(GtkToggleButton * button,GtkInspectorSignalsList * sl)308 toggle_tracing (GtkToggleButton *button, GtkInspectorSignalsList *sl)
309 {
310   if (gtk_toggle_button_get_active (button) == sl->priv->tracing)
311     return;
312 
313   //gtk_widget_show (sl->priv->log_win);
314 
315   if (gtk_toggle_button_get_active (button))
316     start_tracing (sl);
317   else
318     stop_tracing (sl);
319 }
320 
321 static gboolean
clear_log_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)322 clear_log_cb (GtkTreeModel *model,
323               GtkTreePath  *path,
324               GtkTreeIter  *iter,
325               gpointer      data)
326 {
327   gtk_list_store_set (GTK_LIST_STORE (model), iter, COLUMN_COUNT, 0, -1);
328   return FALSE;
329 }
330 
331 static void
clear_log(GtkButton * button,GtkInspectorSignalsList * sl)332 clear_log (GtkButton *button, GtkInspectorSignalsList *sl)
333 {
334   gtk_text_buffer_set_text (sl->priv->text, "", -1);
335 
336   gtk_tree_model_foreach (GTK_TREE_MODEL (sl->priv->model), clear_log_cb, sl);
337 }
338 
339 static void
get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)340 get_property (GObject    *object,
341               guint       param_id,
342               GValue     *value,
343               GParamSpec *pspec)
344 {
345   GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object);
346 
347   switch (param_id)
348     {
349     case PROP_TRACE_BUTTON:
350       g_value_take_object (value, sl->priv->trace_button);
351       break;
352 
353     case PROP_CLEAR_BUTTON:
354       g_value_take_object (value, sl->priv->trace_button);
355       break;
356 
357     default:
358       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
359       break;
360     }
361 }
362 
363 static void
set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)364 set_property (GObject      *object,
365               guint         param_id,
366               const GValue *value,
367               GParamSpec   *pspec)
368 {
369   GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object);
370 
371   switch (param_id)
372     {
373     case PROP_TRACE_BUTTON:
374       sl->priv->trace_button = g_value_get_object (value);
375       break;
376 
377     case PROP_CLEAR_BUTTON:
378       sl->priv->clear_button = g_value_get_object (value);
379       break;
380 
381     default:
382       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
383       break;
384     }
385 }
386 
387 static void
constructed(GObject * object)388 constructed (GObject *object)
389 {
390   GtkInspectorSignalsList *sl = GTK_INSPECTOR_SIGNALS_LIST (object);
391 
392   g_signal_connect (sl->priv->trace_button, "toggled",
393                     G_CALLBACK (toggle_tracing), sl);
394   g_signal_connect (sl->priv->clear_button, "clicked",
395                     G_CALLBACK (clear_log), sl);
396 }
397 
398 static void
gtk_inspector_signals_list_class_init(GtkInspectorSignalsListClass * klass)399 gtk_inspector_signals_list_class_init (GtkInspectorSignalsListClass *klass)
400 {
401   GObjectClass *object_class = G_OBJECT_CLASS (klass);
402   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
403 
404   object_class->constructed = constructed;
405   object_class->get_property = get_property;
406   object_class->set_property = set_property;
407 
408   g_object_class_install_property (object_class, PROP_TRACE_BUTTON,
409       g_param_spec_object ("trace-button", NULL, NULL,
410                            GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
411 
412   g_object_class_install_property (object_class, PROP_CLEAR_BUTTON,
413       g_param_spec_object ("clear-button", NULL, NULL,
414                            GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
415 
416   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/signals-list.ui");
417   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, view);
418   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, model);
419   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, text);
420   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, log_win);
421   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, count_column);
422   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorSignalsList, count_renderer);
423 }
424 
425 // vim: set et sw=2 ts=2:
426