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 | |
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: &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 | |
135 | static gboolean |
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 |
153 | toggle_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 | |
171 | static gboolean |
172 | has_instance_counts (void) |
173 | { |
174 | return g_type_get_instance_count (GTK_TYPE_LABEL) > 0; |
175 | } |
176 | |
177 | static gboolean |
178 | instance_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 | |
198 | static void |
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 (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 | |
218 | static void |
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 (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 | |
244 | static void |
245 | type_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 | |
255 | static gboolean |
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 (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 | |
288 | static gboolean |
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 (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 | |
305 | static gboolean |
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 (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 | |
324 | static void |
325 | destroy_controller (GtkEventController *controller) |
326 | { |
327 | gtk_widget_remove_controller (widget: gtk_event_controller_get_widget (controller), controller); |
328 | } |
329 | |
330 | static void |
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), 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 | |
349 | static void |
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), key: "statistics-controller" , NULL); |
356 | |
357 | GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->unroot (widget); |
358 | } |
359 | |
360 | static void |
361 | gtk_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 | |
387 | static void |
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 (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 | |
406 | static void |
407 | finalize (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 | |
419 | static void |
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, 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 | |
439 | static void |
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 |
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 (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 | |