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 "statistics.h"
22 
23 #include "graphdata.h"
24 #include "gtkstack.h"
25 #include "gtktreeview.h"
26 #include "gtkcellrenderertext.h"
27 #include "gtkcelllayout.h"
28 #include "gtksearchbar.h"
29 #include "gtklabel.h"
30 
31 enum
32 {
33   PROP_0,
34   PROP_BUTTON
35 };
36 
37 struct _GtkInspectorStatisticsPrivate
38 {
39   GtkWidget *stack;
40   GtkWidget *excuse;
41   GtkTreeModel *model;
42   GtkTreeView  *view;
43   GtkWidget *button;
44   GHashTable *data;
45   GtkTreeViewColumn *column_self1;
46   GtkCellRenderer *renderer_self1;
47   GtkTreeViewColumn *column_cumulative1;
48   GtkCellRenderer *renderer_cumulative1;
49   GtkTreeViewColumn *column_self2;
50   GtkCellRenderer *renderer_self2;
51   GtkTreeViewColumn *column_cumulative2;
52   GtkCellRenderer *renderer_cumulative2;
53   GHashTable *counts;
54   guint update_source_id;
55   GtkWidget *search_entry;
56   GtkWidget *search_bar;
57 };
58 
59 typedef struct {
60   GType type;
61   GtkTreeIter treeiter;
62   GtkGraphData *self;
63   GtkGraphData *cumulative;
64 } TypeData;
65 
66 enum
67 {
68   COLUMN_TYPE,
69   COLUMN_TYPE_NAME,
70   COLUMN_SELF1,
71   COLUMN_CUMULATIVE1,
72   COLUMN_SELF2,
73   COLUMN_CUMULATIVE2,
74   COLUMN_SELF_DATA,
75   COLUMN_CUMULATIVE_DATA
76 };
77 
G_DEFINE_TYPE_WITH_PRIVATE(GtkInspectorStatistics,gtk_inspector_statistics,GTK_TYPE_BOX)78 G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorStatistics, gtk_inspector_statistics, GTK_TYPE_BOX)
79 
80 static gint
81 add_type_count (GtkInspectorStatistics *sl, GType type)
82 {
83   gint cumulative;
84   gint self;
85   GType *children;
86   guint n_children;
87   gint i;
88   TypeData *data;
89 
90   cumulative = 0;
91 
92   children = g_type_children (type, &n_children);
93   for (i = 0; i < n_children; i++)
94     cumulative += add_type_count (sl, children[i]);
95 
96   data = g_hash_table_lookup (sl->priv->counts, GSIZE_TO_POINTER (type));
97   if (!data)
98     {
99       data = g_new0 (TypeData, 1);
100       data->type = type;
101       data->self = gtk_graph_data_new (60);
102       data->cumulative = gtk_graph_data_new (60);
103       gtk_list_store_append (GTK_LIST_STORE (sl->priv->model), &data->treeiter);
104       gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter,
105                           COLUMN_TYPE, data->type,
106                           COLUMN_TYPE_NAME, g_type_name (data->type),
107                           COLUMN_SELF_DATA, data->self,
108                           COLUMN_CUMULATIVE_DATA, data->cumulative,
109                           -1);
110       g_hash_table_insert (sl->priv->counts, GSIZE_TO_POINTER (type), data);
111     }
112 
113   self = g_type_get_instance_count (type);
114   cumulative += self;
115 
116   gtk_graph_data_prepend_value (data->self, self);
117   gtk_graph_data_prepend_value (data->cumulative, cumulative);
118 
119   gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter,
120                       COLUMN_SELF1, (int) gtk_graph_data_get_value (data->self, 1),
121                       COLUMN_CUMULATIVE1, (int) gtk_graph_data_get_value (data->cumulative, 1),
122                       COLUMN_SELF2, (int) gtk_graph_data_get_value (data->self, 0),
123                       COLUMN_CUMULATIVE2, (int) gtk_graph_data_get_value (data->cumulative, 0),
124                       -1);
125   return cumulative;
126 }
127 
128 static gboolean
update_type_counts(gpointer data)129 update_type_counts (gpointer data)
130 {
131   GtkInspectorStatistics *sl = data;
132   GType type;
133   gpointer class;
134 
135   for (type = G_TYPE_INTERFACE; type <= G_TYPE_FUNDAMENTAL_MAX; type += (1 << G_TYPE_FUNDAMENTAL_SHIFT))
136     {
137       class = g_type_class_peek (type);
138       if (class == NULL)
139         continue;
140 
141       if (!G_TYPE_IS_INSTANTIATABLE (type))
142         continue;
143 
144       add_type_count (sl, type);
145     }
146 
147   return TRUE;
148 }
149 
150 static void
toggle_record(GtkToggleButton * button,GtkInspectorStatistics * sl)151 toggle_record (GtkToggleButton        *button,
152                GtkInspectorStatistics *sl)
153 {
154   if (gtk_toggle_button_get_active (button) == (sl->priv->update_source_id != 0))
155     return;
156 
157   if (gtk_toggle_button_get_active (button))
158     {
159       sl->priv->update_source_id = gdk_threads_add_timeout_seconds (1,
160                                                                     update_type_counts,
161                                                                     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 gchar *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   gint column;
206   gint count;
207   gchar *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   gint column;
226   gint count1;
227   gint count2;
228   gchar *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_press_event(GtkWidget * window,GdkEvent * event,GtkInspectorStatistics * sl)256 key_press_event (GtkWidget              *window,
257                  GdkEvent               *event,
258                  GtkInspectorStatistics *sl)
259 {
260   if (gtk_widget_get_mapped (GTK_WIDGET (sl)))
261     {
262       if (event->key.keyval == GDK_KEY_Return ||
263           event->key.keyval == GDK_KEY_ISO_Enter ||
264           event->key.keyval == GDK_KEY_KP_Enter)
265         {
266           GtkTreeSelection *selection;
267           GtkTreeModel *model;
268           GtkTreeIter iter;
269           GtkTreePath *path;
270 
271           selection = gtk_tree_view_get_selection (sl->priv->view);
272           if (gtk_tree_selection_get_selected (selection, &model, &iter))
273             {
274               path = gtk_tree_model_get_path (model, &iter);
275               gtk_tree_view_row_activated (sl->priv->view, path, NULL);
276               gtk_tree_path_free (path);
277 
278               return GDK_EVENT_STOP;
279             }
280           else
281             return GDK_EVENT_PROPAGATE;
282         }
283 
284       return gtk_search_bar_handle_event (GTK_SEARCH_BAR (sl->priv->search_bar), event);
285     }
286   else
287     return GDK_EVENT_PROPAGATE;
288 }
289 
290 static gboolean
match_string(const gchar * string,const gchar * text)291 match_string (const gchar *string,
292               const gchar *text)
293 {
294   gchar *lower;
295   gboolean match = FALSE;
296 
297   if (string)
298     {
299       lower = g_ascii_strdown (string, -1);
300       match = g_str_has_prefix (lower, text);
301       g_free (lower);
302     }
303 
304   return match;
305 }
306 
307 static gboolean
match_row(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer data)308 match_row (GtkTreeModel *model,
309            gint          column,
310            const gchar  *key,
311            GtkTreeIter  *iter,
312            gpointer      data)
313 {
314   gchar *type;
315   gboolean match;
316 
317   gtk_tree_model_get (model, iter, column, &type, -1);
318 
319   match = match_string (type, key);
320 
321   g_free (type);
322 
323   return !match;
324 }
325 
326 static void
hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel)327 hierarchy_changed (GtkWidget *widget,
328                    GtkWidget *previous_toplevel)
329 {
330   if (previous_toplevel)
331     g_signal_handlers_disconnect_by_func (previous_toplevel, key_press_event, widget);
332   g_signal_connect (gtk_widget_get_toplevel (widget), "key-press-event",
333                     G_CALLBACK (key_press_event), widget);
334 }
335 
336 static void
gtk_inspector_statistics_init(GtkInspectorStatistics * sl)337 gtk_inspector_statistics_init (GtkInspectorStatistics *sl)
338 {
339   sl->priv = gtk_inspector_statistics_get_instance_private (sl);
340   gtk_widget_init_template (GTK_WIDGET (sl));
341   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self1),
342                                       sl->priv->renderer_self1,
343                                       cell_data_data,
344                                       GINT_TO_POINTER (COLUMN_SELF1), NULL);
345   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative1),
346                                       sl->priv->renderer_cumulative1,
347                                       cell_data_data,
348                                       GINT_TO_POINTER (COLUMN_CUMULATIVE1), NULL);
349   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self2),
350                                       sl->priv->renderer_self2,
351                                       cell_data_delta,
352                                       GINT_TO_POINTER (COLUMN_SELF2), NULL);
353   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative2),
354                                       sl->priv->renderer_cumulative2,
355                                       cell_data_delta,
356                                       GINT_TO_POINTER (COLUMN_CUMULATIVE2), NULL);
357   sl->priv->counts = g_hash_table_new_full (NULL, NULL, NULL, type_data_free);
358 
359   gtk_tree_view_set_search_entry (sl->priv->view, GTK_ENTRY (sl->priv->search_entry));
360   gtk_tree_view_set_search_equal_func (sl->priv->view, match_row, sl, NULL);
361   g_signal_connect (sl, "hierarchy-changed", G_CALLBACK (hierarchy_changed), NULL);
362 }
363 
364 static void
constructed(GObject * object)365 constructed (GObject *object)
366 {
367   GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
368 
369   g_signal_connect (sl->priv->button, "toggled",
370                     G_CALLBACK (toggle_record), sl);
371 
372   if (has_instance_counts ())
373     update_type_counts (sl);
374   else
375     {
376       if (instance_counts_enabled ())
377         gtk_label_set_text (GTK_LABEL (sl->priv->excuse), _("GLib must be configured with --enable-debug"));
378       gtk_stack_set_visible_child_name (GTK_STACK (sl->priv->stack), "excuse");
379       gtk_widget_set_sensitive (sl->priv->button, FALSE);
380     }
381 }
382 
383 static void
finalize(GObject * object)384 finalize (GObject *object)
385 {
386   GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
387 
388   if (sl->priv->update_source_id)
389     g_source_remove (sl->priv->update_source_id);
390 
391   g_hash_table_unref (sl->priv->counts);
392 
393   G_OBJECT_CLASS (gtk_inspector_statistics_parent_class)->finalize (object);
394 }
395 
396 static void
get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)397 get_property (GObject    *object,
398               guint       param_id,
399               GValue     *value,
400               GParamSpec *pspec)
401 {
402   GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
403 
404   switch (param_id)
405     {
406     case PROP_BUTTON:
407       g_value_take_object (value, sl->priv->button);
408       break;
409 
410     default:
411       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
412       break;
413     }
414 }
415 
416 static void
set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)417 set_property (GObject      *object,
418               guint         param_id,
419               const GValue *value,
420               GParamSpec   *pspec)
421 {
422   GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object);
423 
424   switch (param_id)
425     {
426     case PROP_BUTTON:
427       sl->priv->button = g_value_get_object (value);
428       break;
429 
430     default:
431       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
432       break;
433     }
434 }
435 
436 static void
gtk_inspector_statistics_class_init(GtkInspectorStatisticsClass * klass)437 gtk_inspector_statistics_class_init (GtkInspectorStatisticsClass *klass)
438 {
439   GObjectClass *object_class = G_OBJECT_CLASS (klass);
440   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
441 
442   object_class->get_property = get_property;
443   object_class->set_property = set_property;
444   object_class->constructed = constructed;
445   object_class->finalize = finalize;
446 
447   g_object_class_install_property (object_class, PROP_BUTTON,
448       g_param_spec_object ("button", NULL, NULL,
449                            GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
450 
451   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/statistics.ui");
452   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, view);
453   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, stack);
454   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, model);
455   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self1);
456   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self1);
457   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative1);
458   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative1);
459   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self2);
460   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self2);
461   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative2);
462   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative2);
463   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_entry);
464   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_bar);
465   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, excuse);
466 
467 }
468 
469 // vim: set et sw=2 ts=2:
470