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