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