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
20 #include "statistics.h"
21
22 #include "graphdata.h"
23
24 #include "gtkcelllayout.h"
25 #include "gtkcellrenderertext.h"
26 #include "gtklabel.h"
27 #include "gtksearchbar.h"
28 #include "gtkstack.h"
29 #include "gtktogglebutton.h"
30 #include "gtktreeselection.h"
31 #include "gtktreeview.h"
32 #include "gtkeventcontrollerkey.h"
33 #include "gtkmain.h"
34 #include "gtkliststore.h"
35
36 #include <glib/gi18n-lib.h>
37
38 enum
39 {
40 PROP_0,
41 PROP_BUTTON
42 };
43
44 struct _GtkInspectorStatisticsPrivate
45 {
46 GtkWidget *stack;
47 GtkWidget *excuse;
48 GtkTreeModel *model;
49 GtkTreeView *view;
50 GtkWidget *button;
51 GHashTable *data;
52 GtkTreeViewColumn *column_self1;
53 GtkCellRenderer *renderer_self1;
54 GtkTreeViewColumn *column_cumulative1;
55 GtkCellRenderer *renderer_cumulative1;
56 GtkTreeViewColumn *column_self2;
57 GtkCellRenderer *renderer_self2;
58 GtkTreeViewColumn *column_cumulative2;
59 GtkCellRenderer *renderer_cumulative2;
60 GHashTable *counts;
61 guint update_source_id;
62 GtkWidget *search_entry;
63 GtkWidget *search_bar;
64 };
65
66 typedef struct {
67 GType type;
68 GtkTreeIter treeiter;
69 GtkGraphData *self;
70 GtkGraphData *cumulative;
71 } TypeData;
72
73 enum
74 {
75 COLUMN_TYPE,
76 COLUMN_TYPE_NAME,
77 COLUMN_SELF1,
78 COLUMN_CUMULATIVE1,
79 COLUMN_SELF2,
80 COLUMN_CUMULATIVE2,
81 COLUMN_SELF_DATA,
82 COLUMN_CUMULATIVE_DATA
83 };
84
G_DEFINE_TYPE_WITH_PRIVATE(GtkInspectorStatistics,gtk_inspector_statistics,GTK_TYPE_BOX)85 G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorStatistics, gtk_inspector_statistics, GTK_TYPE_BOX)
86
87 static int
88 add_type_count (GtkInspectorStatistics *sl, GType type)
89 {
90 int cumulative;
91 int self;
92 GType *children;
93 guint n_children;
94 int i;
95 TypeData *data;
96
97 cumulative = 0;
98
99 children = g_type_children (type, &n_children);
100 for (i = 0; i < n_children; i++)
101 cumulative += add_type_count (sl, children[i]);
102
103 data = g_hash_table_lookup (sl->priv->counts, GSIZE_TO_POINTER (type));
104 if (!data)
105 {
106 data = g_new0 (TypeData, 1);
107 data->type = type;
108 data->self = gtk_graph_data_new (60);
109 data->cumulative = gtk_graph_data_new (60);
110 gtk_list_store_append (GTK_LIST_STORE (sl->priv->model), &data->treeiter);
111 gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter,
112 COLUMN_TYPE, data->type,
113 COLUMN_TYPE_NAME, g_type_name (data->type),
114 COLUMN_SELF_DATA, data->self,
115 COLUMN_CUMULATIVE_DATA, data->cumulative,
116 -1);
117 g_hash_table_insert (sl->priv->counts, GSIZE_TO_POINTER (type), data);
118 }
119
120 self = g_type_get_instance_count (type);
121 cumulative += self;
122
123 gtk_graph_data_prepend_value (data->self, self);
124 gtk_graph_data_prepend_value (data->cumulative, cumulative);
125
126 gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter,
127 COLUMN_SELF1, (int) gtk_graph_data_get_value (data->self, 1),
128 COLUMN_CUMULATIVE1, (int) gtk_graph_data_get_value (data->cumulative, 1),
129 COLUMN_SELF2, (int) gtk_graph_data_get_value (data->self, 0),
130 COLUMN_CUMULATIVE2, (int) gtk_graph_data_get_value (data->cumulative, 0),
131 -1);
132 return cumulative;
133 }
134
135 static gboolean
update_type_counts(gpointer data)136 update_type_counts (gpointer data)
137 {
138 GtkInspectorStatistics *sl = data;
139 GType type;
140
141 for (type = G_TYPE_INTERFACE; type <= G_TYPE_FUNDAMENTAL_MAX; type += (1 << G_TYPE_FUNDAMENTAL_SHIFT))
142 {
143 if (!G_TYPE_IS_INSTANTIATABLE (type))
144 continue;
145
146 add_type_count (sl, type);
147 }
148
149 return TRUE;
150 }
151
152 static void
toggle_record(GtkToggleButton * button,GtkInspectorStatistics * sl)153 toggle_record (GtkToggleButton *button,
154 GtkInspectorStatistics *sl)
155 {
156 if (gtk_toggle_button_get_active (button) == (sl->priv->update_source_id != 0))
157 return;
158
159 if (gtk_toggle_button_get_active (button))
160 {
161 sl->priv->update_source_id = g_timeout_add_seconds (1, update_type_counts, sl);
162 update_type_counts (sl);
163 }
164 else
165 {
166 g_source_remove (sl->priv->update_source_id);
167 sl->priv->update_source_id = 0;
168 }
169 }
170
171 static gboolean
has_instance_counts(void)172 has_instance_counts (void)
173 {
174 return g_type_get_instance_count (GTK_TYPE_LABEL) > 0;
175 }
176
177 static gboolean
instance_counts_enabled(void)178 instance_counts_enabled (void)
179 {
180 const char *string;
181 guint flags = 0;
182
183 string = g_getenv ("GOBJECT_DEBUG");
184 if (string != NULL)
185 {
186 GDebugKey debug_keys[] = {
187 { "objects", 1 },
188 { "instance-count", 2 },
189 { "signals", 4 }
190 };
191
192 flags = g_parse_debug_string (string, debug_keys, G_N_ELEMENTS (debug_keys));
193 }
194
195 return (flags & 2) != 0;
196 }
197
198 static void
cell_data_data(GtkCellLayout * layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)199 cell_data_data (GtkCellLayout *layout,
200 GtkCellRenderer *cell,
201 GtkTreeModel *model,
202 GtkTreeIter *iter,
203 gpointer data)
204 {
205 int column;
206 int count;
207 char *text;
208
209 column = GPOINTER_TO_INT (data);
210
211 gtk_tree_model_get (model, iter, column, &count, -1);
212
213 text = g_strdup_printf ("%d", count);
214 g_object_set (cell, "text", text, NULL);
215 g_free (text);
216 }
217
218 static void
cell_data_delta(GtkCellLayout * layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)219 cell_data_delta (GtkCellLayout *layout,
220 GtkCellRenderer *cell,
221 GtkTreeModel *model,
222 GtkTreeIter *iter,
223 gpointer data)
224 {
225 int column;
226 int count1;
227 int count2;
228 char *text;
229
230 column = GPOINTER_TO_INT (data);
231
232 gtk_tree_model_get (model, iter, column - 2, &count1, column, &count2, -1);
233
234 if (count2 > count1)
235 text = g_strdup_printf ("%d (↗ %d)", count2, count2 - count1);
236 else if (count2 < count1)
237 text = g_strdup_printf ("%d (↘ %d)", count2, count1 - count2);
238 else
239 text = g_strdup_printf ("%d", count2);
240 g_object_set (cell, "text", text, NULL);
241 g_free (text);
242 }
243
244 static void
type_data_free(gpointer data)245 type_data_free (gpointer data)
246 {
247 TypeData *type_data = data;
248
249 g_object_unref (type_data->self);
250 g_object_unref (type_data->cumulative);
251
252 g_free (type_data);
253 }
254
255 static gboolean
key_pressed(GtkEventController * controller,guint keyval,guint keycode,GdkModifierType state,GtkInspectorStatistics * sl)256 key_pressed (GtkEventController *controller,
257 guint keyval,
258 guint keycode,
259 GdkModifierType state,
260 GtkInspectorStatistics *sl)
261 {
262 if (gtk_widget_get_mapped (GTK_WIDGET (sl)))
263 {
264 if (keyval == GDK_KEY_Return ||
265 keyval == GDK_KEY_ISO_Enter ||
266 keyval == GDK_KEY_KP_Enter)
267 {
268 GtkTreeSelection *selection;
269 GtkTreeModel *model;
270 GtkTreeIter iter;
271 GtkTreePath *path;
272
273 selection = gtk_tree_view_get_selection (sl->priv->view);
274 if (gtk_tree_selection_get_selected (selection, &model, &iter))
275 {
276 path = gtk_tree_model_get_path (model, &iter);
277 gtk_tree_view_row_activated (sl->priv->view, path, NULL);
278 gtk_tree_path_free (path);
279
280 return GDK_EVENT_STOP;
281 }
282 }
283 }
284
285 return GDK_EVENT_PROPAGATE;
286 }
287
288 static gboolean
match_string(const char * string,const char * text)289 match_string (const char *string,
290 const char *text)
291 {
292 char *lower;
293 gboolean match = FALSE;
294
295 if (string)
296 {
297 lower = g_ascii_strdown (string, -1);
298 match = g_str_has_prefix (lower, text);
299 g_free (lower);
300 }
301
302 return match;
303 }
304
305 static gboolean
match_row(GtkTreeModel * model,int column,const char * key,GtkTreeIter * iter,gpointer data)306 match_row (GtkTreeModel *model,
307 int column,
308 const char *key,
309 GtkTreeIter *iter,
310 gpointer data)
311 {
312 char *type;
313 gboolean match;
314
315 gtk_tree_model_get (model, iter, column, &type, -1);
316
317 match = match_string (type, key);
318
319 g_free (type);
320
321 return !match;
322 }
323
324 static void
destroy_controller(GtkEventController * controller)325 destroy_controller (GtkEventController *controller)
326 {
327 gtk_widget_remove_controller (gtk_event_controller_get_widget (controller), controller);
328 }
329
330 static void
root(GtkWidget * widget)331 root (GtkWidget *widget)
332 {
333 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (widget);
334 GtkEventController *controller;
335 GtkWidget *toplevel;
336
337 GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->root (widget);
338
339 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
340
341 controller = gtk_event_controller_key_new ();
342 g_object_set_data_full (G_OBJECT (toplevel), "statistics-controller", controller, (GDestroyNotify)destroy_controller);
343 g_signal_connect (controller, "key-pressed", G_CALLBACK (key_pressed), widget);
344 gtk_widget_add_controller (toplevel, controller);
345
346 gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (sl->priv->search_bar), toplevel);
347 }
348
349 static void
unroot(GtkWidget * widget)350 unroot (GtkWidget *widget)
351 {
352 GtkWidget *toplevel;
353
354 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
355 g_object_set_data (G_OBJECT (toplevel), "statistics-controller", NULL);
356
357 GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->unroot (widget);
358 }
359
360 static void
gtk_inspector_statistics_init(GtkInspectorStatistics * sl)361 gtk_inspector_statistics_init (GtkInspectorStatistics *sl)
362 {
363 sl->priv = gtk_inspector_statistics_get_instance_private (sl);
364 gtk_widget_init_template (GTK_WIDGET (sl));
365 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self1),
366 sl->priv->renderer_self1,
367 cell_data_data,
368 GINT_TO_POINTER (COLUMN_SELF1), NULL);
369 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative1),
370 sl->priv->renderer_cumulative1,
371 cell_data_data,
372 GINT_TO_POINTER (COLUMN_CUMULATIVE1), NULL);
373 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self2),
374 sl->priv->renderer_self2,
375 cell_data_delta,
376 GINT_TO_POINTER (COLUMN_SELF2), NULL);
377 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative2),
378 sl->priv->renderer_cumulative2,
379 cell_data_delta,
380 GINT_TO_POINTER (COLUMN_CUMULATIVE2), NULL);
381 sl->priv->counts = g_hash_table_new_full (NULL, NULL, NULL, type_data_free);
382
383 gtk_tree_view_set_search_entry (sl->priv->view, GTK_EDITABLE (sl->priv->search_entry));
384 gtk_tree_view_set_search_equal_func (sl->priv->view, match_row, sl, NULL);
385 }
386
387 static void
constructed(GObject * object)388 constructed (GObject *object)
389 {
390 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
391
392 g_signal_connect (sl->priv->button, "toggled",
393 G_CALLBACK (toggle_record), sl);
394
395 if (has_instance_counts ())
396 update_type_counts (sl);
397 else
398 {
399 if (instance_counts_enabled ())
400 gtk_label_set_text (GTK_LABEL (sl->priv->excuse), _("GLib must be configured with -Dbuildtype=debug"));
401 gtk_stack_set_visible_child_name (GTK_STACK (sl->priv->stack), "excuse");
402 gtk_widget_set_sensitive (sl->priv->button, FALSE);
403 }
404 }
405
406 static void
finalize(GObject * object)407 finalize (GObject *object)
408 {
409 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
410
411 if (sl->priv->update_source_id)
412 g_source_remove (sl->priv->update_source_id);
413
414 g_hash_table_unref (sl->priv->counts);
415
416 G_OBJECT_CLASS (gtk_inspector_statistics_parent_class)->finalize (object);
417 }
418
419 static void
get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)420 get_property (GObject *object,
421 guint param_id,
422 GValue *value,
423 GParamSpec *pspec)
424 {
425 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
426
427 switch (param_id)
428 {
429 case PROP_BUTTON:
430 g_value_take_object (value, sl->priv->button);
431 break;
432
433 default:
434 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
435 break;
436 }
437 }
438
439 static void
set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)440 set_property (GObject *object,
441 guint param_id,
442 const GValue *value,
443 GParamSpec *pspec)
444 {
445 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
446
447 switch (param_id)
448 {
449 case PROP_BUTTON:
450 sl->priv->button = g_value_get_object (value);
451 break;
452
453 default:
454 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
455 break;
456 }
457 }
458
459 static void
gtk_inspector_statistics_class_init(GtkInspectorStatisticsClass * klass)460 gtk_inspector_statistics_class_init (GtkInspectorStatisticsClass *klass)
461 {
462 GObjectClass *object_class = G_OBJECT_CLASS (klass);
463 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
464
465 object_class->get_property = get_property;
466 object_class->set_property = set_property;
467 object_class->constructed = constructed;
468 object_class->finalize = finalize;
469
470 widget_class->root = root;
471 widget_class->unroot = unroot;
472
473 g_object_class_install_property (object_class, PROP_BUTTON,
474 g_param_spec_object ("button", NULL, NULL,
475 GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
476
477 gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/statistics.ui");
478 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, view);
479 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, stack);
480 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, model);
481 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self1);
482 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self1);
483 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative1);
484 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative1);
485 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self2);
486 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self2);
487 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative2);
488 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative2);
489 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_entry);
490 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_bar);
491 gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, excuse);
492
493 }
494
495 // vim: set et sw=2 ts=2:
496