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
38enum
39{
40 PROP_0,
41 PROP_BUTTON
42};
43
44struct _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
66typedef struct {
67 GType type;
68 GtkTreeIter treeiter;
69 GtkGraphData *self;
70 GtkGraphData *cumulative;
71} TypeData;
72
73enum
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
85G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorStatistics, gtk_inspector_statistics, GTK_TYPE_BOX)
86
87static int
88add_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: &n_children);
100 for (i = 0; i < n_children; i++)
101 cumulative += add_type_count (sl, type: children[i]);
102
103 data = g_hash_table_lookup (hash_table: 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 (n_values: 60);
109 data->cumulative = gtk_graph_data_new (n_values: 60);
110 gtk_list_store_append (GTK_LIST_STORE (sl->priv->model), iter: &data->treeiter);
111 gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), iter: &data->treeiter,
112 COLUMN_TYPE, data->type,
113 COLUMN_TYPE_NAME, g_type_name (type: data->type),
114 COLUMN_SELF_DATA, data->self,
115 COLUMN_CUMULATIVE_DATA, data->cumulative,
116 -1);
117 g_hash_table_insert (hash_table: sl->priv->counts, GSIZE_TO_POINTER (type), value: data);
118 }
119
120 self = g_type_get_instance_count (type);
121 cumulative += self;
122
123 gtk_graph_data_prepend_value (data: data->self, value: self);
124 gtk_graph_data_prepend_value (data: data->cumulative, value: cumulative);
125
126 gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), iter: &data->treeiter,
127 COLUMN_SELF1, (int) gtk_graph_data_get_value (data: data->self, i: 1),
128 COLUMN_CUMULATIVE1, (int) gtk_graph_data_get_value (data: data->cumulative, i: 1),
129 COLUMN_SELF2, (int) gtk_graph_data_get_value (data: data->self, i: 0),
130 COLUMN_CUMULATIVE2, (int) gtk_graph_data_get_value (data: data->cumulative, i: 0),
131 -1);
132 return cumulative;
133}
134
135static gboolean
136update_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
152static void
153toggle_record (GtkToggleButton *button,
154 GtkInspectorStatistics *sl)
155{
156 if (gtk_toggle_button_get_active (toggle_button: button) == (sl->priv->update_source_id != 0))
157 return;
158
159 if (gtk_toggle_button_get_active (toggle_button: button))
160 {
161 sl->priv->update_source_id = g_timeout_add_seconds (interval: 1, function: update_type_counts, data: sl);
162 update_type_counts (data: sl);
163 }
164 else
165 {
166 g_source_remove (tag: sl->priv->update_source_id);
167 sl->priv->update_source_id = 0;
168 }
169}
170
171static gboolean
172has_instance_counts (void)
173{
174 return g_type_get_instance_count (GTK_TYPE_LABEL) > 0;
175}
176
177static gboolean
178instance_counts_enabled (void)
179{
180 const char *string;
181 guint flags = 0;
182
183 string = g_getenv (variable: "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, keys: debug_keys, G_N_ELEMENTS (debug_keys));
193 }
194
195 return (flags & 2) != 0;
196}
197
198static void
199cell_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 (tree_model: model, iter, column, &count, -1);
212
213 text = g_strdup_printf (format: "%d", count);
214 g_object_set (object: cell, first_property_name: "text", text, NULL);
215 g_free (mem: text);
216}
217
218static void
219cell_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 (tree_model: model, iter, column - 2, &count1, column, &count2, -1);
233
234 if (count2 > count1)
235 text = g_strdup_printf (format: "%d (↗ %d)", count2, count2 - count1);
236 else if (count2 < count1)
237 text = g_strdup_printf (format: "%d (↘ %d)", count2, count1 - count2);
238 else
239 text = g_strdup_printf (format: "%d", count2);
240 g_object_set (object: cell, first_property_name: "text", text, NULL);
241 g_free (mem: text);
242}
243
244static void
245type_data_free (gpointer data)
246{
247 TypeData *type_data = data;
248
249 g_object_unref (object: type_data->self);
250 g_object_unref (object: type_data->cumulative);
251
252 g_free (mem: type_data);
253}
254
255static gboolean
256key_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 (tree_view: sl->priv->view);
274 if (gtk_tree_selection_get_selected (selection, model: &model, iter: &iter))
275 {
276 path = gtk_tree_model_get_path (tree_model: model, iter: &iter);
277 gtk_tree_view_row_activated (tree_view: 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
288static gboolean
289match_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 (str: string, len: -1);
298 match = g_str_has_prefix (str: lower, prefix: text);
299 g_free (mem: lower);
300 }
301
302 return match;
303}
304
305static gboolean
306match_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 (tree_model: model, iter, column, &type, -1);
316
317 match = match_string (string: type, text: key);
318
319 g_free (mem: type);
320
321 return !match;
322}
323
324static void
325destroy_controller (GtkEventController *controller)
326{
327 gtk_widget_remove_controller (widget: gtk_event_controller_get_widget (controller), controller);
328}
329
330static void
331root (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), key: "statistics-controller", data: controller, destroy: (GDestroyNotify)destroy_controller);
343 g_signal_connect (controller, "key-pressed", G_CALLBACK (key_pressed), widget);
344 gtk_widget_add_controller (widget: toplevel, controller);
345
346 gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (sl->priv->search_bar), widget: toplevel);
347}
348
349static void
350unroot (GtkWidget *widget)
351{
352 GtkWidget *toplevel;
353
354 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
355 g_object_set_data (G_OBJECT (toplevel), key: "statistics-controller", NULL);
356
357 GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->unroot (widget);
358}
359
360static void
361gtk_inspector_statistics_init (GtkInspectorStatistics *sl)
362{
363 sl->priv = gtk_inspector_statistics_get_instance_private (self: 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 cell: sl->priv->renderer_self1,
367 func: 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 cell: sl->priv->renderer_cumulative1,
371 func: 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 cell: sl->priv->renderer_self2,
375 func: 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 cell: sl->priv->renderer_cumulative2,
379 func: cell_data_delta,
380 GINT_TO_POINTER (COLUMN_CUMULATIVE2), NULL);
381 sl->priv->counts = g_hash_table_new_full (NULL, NULL, NULL, value_destroy_func: type_data_free);
382
383 gtk_tree_view_set_search_entry (tree_view: sl->priv->view, GTK_EDITABLE (sl->priv->search_entry));
384 gtk_tree_view_set_search_equal_func (tree_view: sl->priv->view, search_equal_func: match_row, search_user_data: sl, NULL);
385}
386
387static void
388constructed (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 (data: 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), name: "excuse");
402 gtk_widget_set_sensitive (widget: sl->priv->button, FALSE);
403 }
404}
405
406static void
407finalize (GObject *object)
408{
409 GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
410
411 if (sl->priv->update_source_id)
412 g_source_remove (tag: sl->priv->update_source_id);
413
414 g_hash_table_unref (hash_table: sl->priv->counts);
415
416 G_OBJECT_CLASS (gtk_inspector_statistics_parent_class)->finalize (object);
417}
418
419static void
420get_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, v_object: sl->priv->button);
431 break;
432
433 default:
434 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
435 break;
436 }
437}
438
439static void
440set_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
459static void
460gtk_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 (oclass: object_class, property_id: PROP_BUTTON,
474 pspec: g_param_spec_object (name: "button", NULL, NULL,
475 GTK_TYPE_WIDGET, flags: G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
476
477 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/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

source code of gtk/gtk/inspector/statistics.c