/* * Copyright (c) 2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include "statistics.h" #include "graphdata.h" #include "gtkcelllayout.h" #include "gtkcellrenderertext.h" #include "gtklabel.h" #include "gtksearchbar.h" #include "gtkstack.h" #include "gtktogglebutton.h" #include "gtktreeselection.h" #include "gtktreeview.h" #include "gtkeventcontrollerkey.h" #include "gtkmain.h" #include enum { PROP_0, PROP_BUTTON }; struct _GtkInspectorStatisticsPrivate { GtkWidget *stack; GtkWidget *excuse; GtkTreeModel *model; GtkTreeView *view; GtkWidget *button; GHashTable *data; GtkTreeViewColumn *column_self1; GtkCellRenderer *renderer_self1; GtkTreeViewColumn *column_cumulative1; GtkCellRenderer *renderer_cumulative1; GtkTreeViewColumn *column_self2; GtkCellRenderer *renderer_self2; GtkTreeViewColumn *column_cumulative2; GtkCellRenderer *renderer_cumulative2; GHashTable *counts; guint update_source_id; GtkWidget *search_entry; GtkWidget *search_bar; }; typedef struct { GType type; GtkTreeIter treeiter; GtkGraphData *self; GtkGraphData *cumulative; } TypeData; enum { COLUMN_TYPE, COLUMN_TYPE_NAME, COLUMN_SELF1, COLUMN_CUMULATIVE1, COLUMN_SELF2, COLUMN_CUMULATIVE2, COLUMN_SELF_DATA, COLUMN_CUMULATIVE_DATA }; G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorStatistics, gtk_inspector_statistics, GTK_TYPE_BOX) static gint add_type_count (GtkInspectorStatistics *sl, GType type) { gint cumulative; gint self; GType *children; guint n_children; gint i; TypeData *data; cumulative = 0; children = g_type_children (type, &n_children); for (i = 0; i < n_children; i++) cumulative += add_type_count (sl, children[i]); data = g_hash_table_lookup (sl->priv->counts, GSIZE_TO_POINTER (type)); if (!data) { data = g_new0 (TypeData, 1); data->type = type; data->self = gtk_graph_data_new (60); data->cumulative = gtk_graph_data_new (60); gtk_list_store_append (GTK_LIST_STORE (sl->priv->model), &data->treeiter); gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter, COLUMN_TYPE, data->type, COLUMN_TYPE_NAME, g_type_name (data->type), COLUMN_SELF_DATA, data->self, COLUMN_CUMULATIVE_DATA, data->cumulative, -1); g_hash_table_insert (sl->priv->counts, GSIZE_TO_POINTER (type), data); } self = g_type_get_instance_count (type); cumulative += self; gtk_graph_data_prepend_value (data->self, self); gtk_graph_data_prepend_value (data->cumulative, cumulative); gtk_list_store_set (GTK_LIST_STORE (sl->priv->model), &data->treeiter, COLUMN_SELF1, (int) gtk_graph_data_get_value (data->self, 1), COLUMN_CUMULATIVE1, (int) gtk_graph_data_get_value (data->cumulative, 1), COLUMN_SELF2, (int) gtk_graph_data_get_value (data->self, 0), COLUMN_CUMULATIVE2, (int) gtk_graph_data_get_value (data->cumulative, 0), -1); return cumulative; } static gboolean update_type_counts (gpointer data) { GtkInspectorStatistics *sl = data; GType type; for (type = G_TYPE_INTERFACE; type <= G_TYPE_FUNDAMENTAL_MAX; type += (1 << G_TYPE_FUNDAMENTAL_SHIFT)) { if (!G_TYPE_IS_INSTANTIATABLE (type)) continue; add_type_count (sl, type); } return TRUE; } static void toggle_record (GtkToggleButton *button, GtkInspectorStatistics *sl) { if (gtk_toggle_button_get_active (button) == (sl->priv->update_source_id != 0)) return; if (gtk_toggle_button_get_active (button)) { sl->priv->update_source_id = g_timeout_add_seconds (1, update_type_counts, sl); update_type_counts (sl); } else { g_source_remove (sl->priv->update_source_id); sl->priv->update_source_id = 0; } } static gboolean has_instance_counts (void) { return g_type_get_instance_count (GTK_TYPE_LABEL) > 0; } static gboolean instance_counts_enabled (void) { const gchar *string; guint flags = 0; string = g_getenv ("GOBJECT_DEBUG"); if (string != NULL) { GDebugKey debug_keys[] = { { "objects", 1 }, { "instance-count", 2 }, { "signals", 4 } }; flags = g_parse_debug_string (string, debug_keys, G_N_ELEMENTS (debug_keys)); } return (flags & 2) != 0; } static void cell_data_data (GtkCellLayout *layout, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gint column; gint count; gchar *text; column = GPOINTER_TO_INT (data); gtk_tree_model_get (model, iter, column, &count, -1); text = g_strdup_printf ("%d", count); g_object_set (cell, "text", text, NULL); g_free (text); } static void cell_data_delta (GtkCellLayout *layout, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gint column; gint count1; gint count2; gchar *text; column = GPOINTER_TO_INT (data); gtk_tree_model_get (model, iter, column - 2, &count1, column, &count2, -1); if (count2 > count1) text = g_strdup_printf ("%d (↗ %d)", count2, count2 - count1); else if (count2 < count1) text = g_strdup_printf ("%d (↘ %d)", count2, count1 - count2); else text = g_strdup_printf ("%d", count2); g_object_set (cell, "text", text, NULL); g_free (text); } static void type_data_free (gpointer data) { TypeData *type_data = data; g_object_unref (type_data->self); g_object_unref (type_data->cumulative); g_free (type_data); } static gboolean key_pressed (GtkEventController *controller, guint keyval, guint keycode, GdkModifierType state, GtkInspectorStatistics *sl) { if (gtk_widget_get_mapped (GTK_WIDGET (sl))) { if (keyval == GDK_KEY_Return || keyval == GDK_KEY_ISO_Enter || keyval == GDK_KEY_KP_Enter) { GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; GtkTreePath *path; selection = gtk_tree_view_get_selection (sl->priv->view); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_row_activated (sl->priv->view, path, NULL); gtk_tree_path_free (path); return GDK_EVENT_STOP; } } } return GDK_EVENT_PROPAGATE; } static gboolean match_string (const gchar *string, const gchar *text) { gchar *lower; gboolean match = FALSE; if (string) { lower = g_ascii_strdown (string, -1); match = g_str_has_prefix (lower, text); g_free (lower); } return match; } static gboolean match_row (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data) { gchar *type; gboolean match; gtk_tree_model_get (model, iter, column, &type, -1); match = match_string (type, key); g_free (type); return !match; } static void destroy_controller (GtkEventController *controller) { gtk_widget_remove_controller (gtk_event_controller_get_widget (controller), controller); } static void root (GtkWidget *widget) { GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (widget); GtkEventController *controller; GtkWidget *toplevel; GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->root (widget); toplevel = gtk_widget_get_toplevel (widget); controller = gtk_event_controller_key_new (); g_object_set_data_full (G_OBJECT (toplevel), "statistics-controller", controller, (GDestroyNotify)destroy_controller); g_signal_connect (controller, "key-pressed", G_CALLBACK (key_pressed), widget); gtk_widget_add_controller (toplevel, controller); gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (sl->priv->search_bar), toplevel); } static void unroot (GtkWidget *widget) { GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (widget); g_object_set_data (G_OBJECT (toplevel), "statistics-controller", NULL); GTK_WIDGET_CLASS (gtk_inspector_statistics_parent_class)->unroot (widget); } static void gtk_inspector_statistics_init (GtkInspectorStatistics *sl) { sl->priv = gtk_inspector_statistics_get_instance_private (sl); gtk_widget_init_template (GTK_WIDGET (sl)); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self1), sl->priv->renderer_self1, cell_data_data, GINT_TO_POINTER (COLUMN_SELF1), NULL); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative1), sl->priv->renderer_cumulative1, cell_data_data, GINT_TO_POINTER (COLUMN_CUMULATIVE1), NULL); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_self2), sl->priv->renderer_self2, cell_data_delta, GINT_TO_POINTER (COLUMN_SELF2), NULL); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (sl->priv->column_cumulative2), sl->priv->renderer_cumulative2, cell_data_delta, GINT_TO_POINTER (COLUMN_CUMULATIVE2), NULL); sl->priv->counts = g_hash_table_new_full (NULL, NULL, NULL, type_data_free); gtk_tree_view_set_search_entry (sl->priv->view, GTK_EDITABLE (sl->priv->search_entry)); gtk_tree_view_set_search_equal_func (sl->priv->view, match_row, sl, NULL); } static void constructed (GObject *object) { GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object); g_signal_connect (sl->priv->button, "toggled", G_CALLBACK (toggle_record), sl); if (has_instance_counts ()) update_type_counts (sl); else { if (instance_counts_enabled ()) gtk_label_set_text (GTK_LABEL (sl->priv->excuse), _("GLib must be configured with --enable-debug")); gtk_stack_set_visible_child_name (GTK_STACK (sl->priv->stack), "excuse"); gtk_widget_set_sensitive (sl->priv->button, FALSE); } } static void finalize (GObject *object) { GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object); if (sl->priv->update_source_id) g_source_remove (sl->priv->update_source_id); g_hash_table_unref (sl->priv->counts); G_OBJECT_CLASS (gtk_inspector_statistics_parent_class)->finalize (object); } static void get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object); switch (param_id) { case PROP_BUTTON: g_value_take_object (value, sl->priv->button); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { GtkInspectorStatistics *sl = GTK_INSPECTOR_STATISTICS (object); switch (param_id) { case PROP_BUTTON: sl->priv->button = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void gtk_inspector_statistics_class_init (GtkInspectorStatisticsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = get_property; object_class->set_property = set_property; object_class->constructed = constructed; object_class->finalize = finalize; widget_class->root = root; widget_class->unroot = unroot; g_object_class_install_property (object_class, PROP_BUTTON, g_param_spec_object ("button", NULL, NULL, GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/statistics.ui"); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, view); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, stack); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, model); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self1); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self1); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative1); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative1); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_self2); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_self2); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, column_cumulative2); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, renderer_cumulative2); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_entry); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, search_bar); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStatistics, excuse); } // vim: set et sw=2 ts=2: