From 2a053cc6c3152b7845b54947d7a5853eef08c9f5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 6 Jul 2014 12:18:09 -0400 Subject: [PATCH] GtkInspector: Add a style tab This tab shows css properties of a widget, and their origin. --- gtk/inspector/Makefile.am | 2 + gtk/inspector/init.c | 2 + gtk/inspector/inspector.gresource.xml | 1 + gtk/inspector/style-prop-list.c | 333 ++++++++++++++++++++++++++ gtk/inspector/style-prop-list.h | 60 +++++ gtk/inspector/style-prop-list.ui | 81 +++++++ gtk/inspector/window.c | 3 + gtk/inspector/window.h | 1 + gtk/inspector/window.ui | 14 ++ 9 files changed, 497 insertions(+) create mode 100644 gtk/inspector/style-prop-list.c create mode 100644 gtk/inspector/style-prop-list.h create mode 100644 gtk/inspector/style-prop-list.ui diff --git a/gtk/inspector/Makefile.am b/gtk/inspector/Makefile.am index 0ef0a6ff50..3cfac71dac 100644 --- a/gtk/inspector/Makefile.am +++ b/gtk/inspector/Makefile.am @@ -43,6 +43,8 @@ libgtkinspector_la_SOURCES = \ prop-editor.c \ prop-list.h \ prop-list.c \ + style-prop-list.h \ + style-prop-list.c \ python-hooks.h \ python-hooks.c \ python-shell.h \ diff --git a/gtk/inspector/init.c b/gtk/inspector/init.c index 4fdbbcd7b0..35abb3f876 100644 --- a/gtk/inspector/init.c +++ b/gtk/inspector/init.c @@ -38,6 +38,7 @@ #include "resources.h" #include "signals-list.h" #include "size-groups.h" +#include "style-prop-list.h" #include "visual.h" #include "widget-tree.h" #include "window.h" @@ -64,6 +65,7 @@ gtk_inspector_init (void) g_type_ensure (GTK_TYPE_INSPECTOR_PYTHON_SHELL); g_type_ensure (GTK_TYPE_INSPECTOR_RESOURCE_LIST); g_type_ensure (GTK_TYPE_INSPECTOR_SIGNALS_LIST); + g_type_ensure (GTK_TYPE_INSPECTOR_STYLE_PROP_LIST); g_type_ensure (GTK_TYPE_INSPECTOR_SIZE_GROUPS); g_type_ensure (GTK_TYPE_INSPECTOR_VISUAL); g_type_ensure (GTK_TYPE_INSPECTOR_WIDGET_TREE); diff --git a/gtk/inspector/inspector.gresource.xml b/gtk/inspector/inspector.gresource.xml index 774161ba7d..6f6c3ab680 100644 --- a/gtk/inspector/inspector.gresource.xml +++ b/gtk/inspector/inspector.gresource.xml @@ -10,6 +10,7 @@ menu.ui object-hierarchy.ui prop-list.ui + style-prop-list.ui resource-list.ui signals-list.ui visual.ui diff --git a/gtk/inspector/style-prop-list.c b/gtk/inspector/style-prop-list.c new file mode 100644 index 0000000000..f08395d26b --- /dev/null +++ b/gtk/inspector/style-prop-list.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2014 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "style-prop-list.h" +#include "gtkcssproviderprivate.h" +#include "gtkcssstylepropertyprivate.h" + +enum +{ + COLUMN_NAME, + COLUMN_VALUE, + COLUMN_LOCATION, + COLUMN_URI, + COLUMN_LINE +}; + +struct _GtkInspectorStylePropListPrivate +{ + GHashTable *css_files; + GtkListStore *model; + GtkWidget *widget; + GHashTable *prop_iters; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorStylePropList, gtk_inspector_style_prop_list, GTK_TYPE_BOX) + +static void +gtk_inspector_style_prop_list_init (GtkInspectorStylePropList *pl) +{ + gint i; + + pl->priv = gtk_inspector_style_prop_list_get_instance_private (pl); + gtk_widget_init_template (GTK_WIDGET (pl)); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (pl->priv->model), + COLUMN_NAME, + GTK_SORT_ASCENDING); + + pl->priv->css_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, (GDestroyNotify) g_strfreev); + + pl->priv->prop_iters = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) gtk_tree_iter_free); + + for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++) + { + GtkCssStyleProperty *prop; + GtkTreeIter iter; + const gchar *name; + + prop = _gtk_css_style_property_lookup_by_id (i); + name = _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop)); + + gtk_list_store_append (pl->priv->model, &iter); + gtk_list_store_set (pl->priv->model, &iter, COLUMN_NAME, name, -1); + g_hash_table_insert (pl->priv->prop_iters, (gpointer)name, gtk_tree_iter_copy (&iter)); + } +} + +static void +disconnect_each_other (gpointer still_alive, + GObject *for_science) +{ + if (GTK_INSPECTOR_IS_STYLE_PROP_LIST (still_alive)) + { + GtkInspectorStylePropList *self = GTK_INSPECTOR_STYLE_PROP_LIST (still_alive); + self->priv->widget = NULL; + } + + g_signal_handlers_disconnect_matched (still_alive, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, for_science); + g_object_weak_unref (still_alive, disconnect_each_other, for_science); +} + +static void +finalize (GObject *object) +{ + GtkInspectorStylePropList *pl = GTK_INSPECTOR_STYLE_PROP_LIST (object); + + g_hash_table_unref (pl->priv->css_files); + g_hash_table_unref (pl->priv->prop_iters); + + G_OBJECT_CLASS (gtk_inspector_style_prop_list_parent_class)->finalize (object); +} + +static void +ensure_css_sections (void) +{ + GtkSettings *settings; + gchar *theme_name; + + gtk_css_provider_set_keep_css_sections (); + + settings = gtk_settings_get_default (); + g_object_get (settings, "gtk-theme-name", &theme_name, NULL); + g_object_set (settings, "gtk-theme-name", theme_name, NULL); + g_free (theme_name); +} + +static void +gtk_inspector_style_prop_list_class_init (GtkInspectorStylePropListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + ensure_css_sections (); + + object_class->finalize = finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/inspector/style-prop-list.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorStylePropList, model); +} + +static gchar * +strip_property (const gchar *property) +{ + gchar **split; + gchar *value; + + split = g_strsplit_set (property, ":;", 3); + if (!split[0] || !split[1]) + value = g_strdup (""); + else + value = g_strdup (split[1]); + + g_strfreev (split); + + return value; +} + +static gchar * +get_css_content (GtkInspectorStylePropList *self, + GFile *file, + guint start_line, + guint end_line) +{ + GtkInspectorStylePropListPrivate *priv = self->priv; + guint i; + guint contents_lines; + gchar *value, *property; + gchar **contents; + + contents = g_hash_table_lookup (priv->css_files, file); + if (!contents) + { + gchar *tmp; + + if (g_file_load_contents (file, NULL, &tmp, NULL, NULL, NULL)) + { + contents = g_strsplit_set (tmp, "\n\r", -1); + g_free (tmp); + } + else + { + contents = g_strsplit ("", "", -1); + } + + g_object_ref (file); + g_hash_table_insert (priv->css_files, file, contents); + } + + contents_lines = g_strv_length (contents); + property = g_strdup (""); + for (i = start_line; (i < end_line + 1) && (i < contents_lines); ++i) + { + gchar *s1, *s2; + + s1 = g_strdup (contents[i]); + s1 = g_strstrip (s1); + s2 = g_strconcat (property, s1, NULL); + g_free (property); + g_free (s1); + property = s2; + } + + value = strip_property (property); + g_free (property); + + return value; +} + +static void +populate (GtkInspectorStylePropList *self) +{ + GtkInspectorStylePropListPrivate *priv = self->priv; + GtkStyleContext *context; + gint i; + + context = gtk_widget_get_style_context (priv->widget); + + for (i = 0; i < _gtk_css_style_property_get_n_properties (); i++) + { + GtkCssStyleProperty *prop; + const gchar *name; + GtkTreeIter *iter; + GtkCssSection *section; + gchar *location; + gchar *value; + gchar *uri; + guint start_line, end_line; + + prop = _gtk_css_style_property_lookup_by_id (i); + name = _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop)); + + iter = (GtkTreeIter *)g_hash_table_lookup (priv->prop_iters, name); + + section = gtk_style_context_get_section (context, name); + if (section) + { + GFileInfo *info; + GFile *file; + const gchar *path; + + start_line = gtk_css_section_get_start_line (section); + end_line = gtk_css_section_get_end_line (section); + + file = gtk_css_section_get_file (section); + if (file) + { + info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, NULL); + + if (info) + path = g_file_info_get_display_name (info); + else + path = ""; + + uri = g_file_get_uri (file); + value = get_css_content (self, file, start_line, end_line); + } + else + { + info = NULL; + path = ""; + uri = NULL; + value = NULL; + } + + if (end_line != start_line) + location = g_strdup_printf ("%s:%u-%u", path, start_line + 1, end_line + 1); + else + location = g_strdup_printf ("%s:%u", path, start_line + 1); + if (info) + g_object_unref (info); + } + else + { + location = NULL; + value = NULL; + uri = NULL; + start_line = -1; + } + + gtk_list_store_set (priv->model, + iter, + COLUMN_VALUE, value, + COLUMN_LOCATION, location, + COLUMN_URI, uri, + COLUMN_LINE, start_line + 1, + -1); + + g_free (location); + g_free (value); + g_free (uri); + } +} + +static void +widget_style_updated (GtkWidget *widget, + GtkInspectorStylePropList *self) +{ + populate (self); +} + +static void +widget_state_flags_changed (GtkWidget *widget, + GtkStateFlags flags, + GtkInspectorStylePropList *self) +{ + populate (self); +} + +void +gtk_inspector_style_prop_list_set_object (GtkInspectorStylePropList *self, + GObject *object) +{ + gtk_widget_hide (GTK_WIDGET (self)); + + if (self->priv->widget == (GtkWidget *)object) + return; + + if (self->priv->widget) + { + disconnect_each_other (self->priv->widget, G_OBJECT (self)); + disconnect_each_other (self, G_OBJECT (self->priv->widget)); + self->priv->widget = NULL; + } + + if (!GTK_IS_WIDGET (object)) + return; + + self->priv->widget = (GtkWidget *)object; + g_object_weak_ref (G_OBJECT (self), disconnect_each_other, object); + g_object_weak_ref (G_OBJECT (object), disconnect_each_other, self); + + populate (self); + gtk_widget_show (GTK_WIDGET (self)); + + g_signal_connect (object, "style-updated", + G_CALLBACK (widget_style_updated), self); + g_signal_connect (object, "state-flags-changed", + G_CALLBACK (widget_state_flags_changed), self); +} + +// vim: set et sw=2 ts=2: diff --git a/gtk/inspector/style-prop-list.h b/gtk/inspector/style-prop-list.h new file mode 100644 index 0000000000..fff5454737 --- /dev/null +++ b/gtk/inspector/style-prop-list.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef _GTK_INSPECTOR_STYLE_PROP_LIST_H_ +#define _GTK_INSPECTOR_STYLE_PROP_LIST_H_ + + +#include + +#define GTK_TYPE_INSPECTOR_STYLE_PROP_LIST (gtk_inspector_style_prop_list_get_type()) +#define GTK_INSPECTOR_STYLE_PROP_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_INSPECTOR_STYLE_PROP_LIST, GtkInspectorStylePropList)) +#define GTK_INSPECTOR_STYLE_PROP_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_INSPECTOR_STYLE_PROP_LIST, GtkInspectorStylePropListClass)) +#define GTK_INSPECTOR_IS_STYLE_PROP_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_INSPECTOR_STYLE_PROP_LIST)) +#define GTK_INSPECTOR_IS_STYLE_PROP_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_INSPECTOR_STYLE_PROP_LIST)) +#define GTK_INSPECTOR_STYLE_PROP_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_INSPECTOR_STYLE_PROP_LIST, GtkInspectorStylePropListClass)) + + +typedef struct _GtkInspectorStylePropListPrivate GtkInspectorStylePropListPrivate; + +typedef struct _GtkInspectorStylePropList +{ + GtkBox parent; + GtkInspectorStylePropListPrivate *priv; +} GtkInspectorStylePropList; + +typedef struct _GtkInspectorStylePropListClass +{ + GtkBoxClass parent; +} GtkInspectorStylePropListClass; + + +G_BEGIN_DECLS + +GType gtk_inspector_style_prop_list_get_type (void); +void gtk_inspector_style_prop_list_set_object (GtkInspectorStylePropList *pl, + GObject *object); + +G_END_DECLS + +#endif // _GTK_INSPECTOR_STYLE_PROP_LIST_H_ + +// vim: set et sw=2 ts=2: diff --git a/gtk/inspector/style-prop-list.ui b/gtk/inspector/style-prop-list.ui new file mode 100644 index 0000000000..c0041323f2 --- /dev/null +++ b/gtk/inspector/style-prop-list.ui @@ -0,0 +1,81 @@ + + + + + + + + + + + + + diff --git a/gtk/inspector/window.c b/gtk/inspector/window.c index ef7d0743fe..d97d801000 100644 --- a/gtk/inspector/window.c +++ b/gtk/inspector/window.c @@ -35,6 +35,7 @@ #include "python-shell.h" #include "button-path.h" #include "size-groups.h" +#include "style-prop-list.h" #include "data-list.h" #include "signals-list.h" #include "actions.h" @@ -71,6 +72,7 @@ on_widget_tree_selection_changed (GtkInspectorWidgetTree *wt, return; gtk_inspector_prop_list_set_object (GTK_INSPECTOR_PROP_LIST (iw->child_prop_list), selected); + gtk_inspector_style_prop_list_set_object (GTK_INSPECTOR_STYLE_PROP_LIST (iw->style_prop_list), selected); gtk_inspector_signals_list_set_object (GTK_INSPECTOR_SIGNALS_LIST (iw->signals_list), selected); gtk_inspector_object_hierarchy_set_object (GTK_INSPECTOR_OBJECT_HIERARCHY (iw->object_hierarchy), selected); gtk_inspector_button_path_set_object (GTK_INSPECTOR_BUTTON_PATH (iw->button_path), selected); @@ -195,6 +197,7 @@ gtk_inspector_window_class_init (GtkInspectorWindowClass *klass) gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, signals_list); gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, button_path); gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, classes_list); + gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, style_prop_list); gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, widget_css_editor); gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, object_hierarchy); gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, python_shell); diff --git a/gtk/inspector/window.h b/gtk/inspector/window.h index 6b0e9d012a..42a882861b 100644 --- a/gtk/inspector/window.h +++ b/gtk/inspector/window.h @@ -46,6 +46,7 @@ typedef struct GtkWidget *prop_list; GtkWidget *child_prop_list; GtkWidget *signals_list; + GtkWidget *style_prop_list; GtkWidget *python_shell; GtkWidget *button_path; GtkWidget *classes_list; diff --git a/gtk/inspector/window.ui b/gtk/inspector/window.ui index 85d96528e1..6b37d2d0f1 100644 --- a/gtk/inspector/window.ui +++ b/gtk/inspector/window.ui @@ -182,6 +182,20 @@ CSS Classes + + + + + True + True + + + + + True + Style Properties + + False