/* GTK - The GIMP Toolkit * Copyright (C) 2014 Benjamin Otte * * 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 "gtkcssnodeprivate.h" #include "gtkcssanimatedstyleprivate.h" #include "gtkdebug.h" #include "gtksettingsprivate.h" /* When these change we do a full restyling. Otherwise we try to figure out * if we need to change things. */ #define GTK_CSS_RADICAL_CHANGE (GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_SOURCE) G_DEFINE_TYPE (GtkCssNode, gtk_css_node, G_TYPE_OBJECT) void gtk_css_node_set_invalid (GtkCssNode *node, gboolean invalid) { if (node->invalid == invalid) return; node->invalid = invalid; if (node->parent) { if (invalid && node->visible) gtk_css_node_set_invalid (node->parent, TRUE); } else { if (invalid) GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node); else GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node); } } static void gtk_css_node_dispose (GObject *object) { GtkCssNode *cssnode = GTK_CSS_NODE (object); while (cssnode->first_child) { gtk_css_node_set_parent (cssnode->first_child, NULL); } gtk_css_node_set_invalid (cssnode, FALSE); G_OBJECT_CLASS (gtk_css_node_parent_class)->dispose (object); } static void gtk_css_node_finalize (GObject *object) { GtkCssNode *cssnode = GTK_CSS_NODE (object); if (cssnode->style) g_object_unref (cssnode->style); gtk_css_node_declaration_unref (cssnode->decl); G_OBJECT_CLASS (gtk_css_node_parent_class)->finalize (object); } static gboolean may_use_global_parent_cache (GtkCssNode *node) { GtkCssNode *parent; parent = gtk_css_node_get_parent (node); if (parent == NULL) return FALSE; if (gtk_css_node_get_style_provider (node) != gtk_css_node_get_style_provider (parent)) return FALSE; return TRUE; } static GtkCssStyle * lookup_in_global_parent_cache (GtkCssNode *node, GtkCssStyle *parent, const GtkCssNodeDeclaration *decl) { GHashTable *cache; GtkCssStyle *style; if (parent == NULL || !may_use_global_parent_cache (node)) return NULL; cache = g_object_get_data (G_OBJECT (parent), "gtk-global-cache"); if (cache == NULL) return NULL; style = g_hash_table_lookup (cache, decl); return style; } static gboolean may_be_stored_in_parent_cache (GtkCssStyle *style) { GtkCssChange change; change = gtk_css_static_style_get_change (GTK_CSS_STATIC_STYLE (style)); /* The cache is shared between all children of the parent, so if a * style depends on a sibling it is not independant of the child. */ if (change & GTK_CSS_CHANGE_ANY_SIBLING) return FALSE; /* Again, the cache is shared between all children of the parent. * If the position is relevant, no child has the same style. */ if (change & GTK_CSS_CHANGE_POSITION) return FALSE; return TRUE; } static void store_in_global_parent_cache (GtkCssNode *node, GtkCssStyle *parent, const GtkCssNodeDeclaration *decl, GtkCssStyle *style) { GHashTable *cache; g_assert (GTK_IS_CSS_STATIC_STYLE (style)); if (parent == NULL || !may_use_global_parent_cache (node)) return; if (!may_be_stored_in_parent_cache (style)) return; cache = g_object_get_data (G_OBJECT (parent), "gtk-global-cache"); if (cache == NULL) { cache = g_hash_table_new_full (gtk_css_node_declaration_hash, gtk_css_node_declaration_equal, (GDestroyNotify) gtk_css_node_declaration_unref, g_object_unref); g_object_set_data_full (G_OBJECT (parent), "gtk-global-cache", cache, (GDestroyNotify) g_hash_table_destroy); } g_hash_table_insert (cache, gtk_css_node_declaration_ref ((GtkCssNodeDeclaration *) decl), g_object_ref (style)); } GtkCssStyle * gtk_css_node_update_style (GtkCssNode *cssnode, GtkCssStyle *style, const GtkBitmask *parent_changes) { const GtkCssNodeDeclaration *decl; GtkCssMatcher matcher; GtkCssStyle *parent; GtkCssStyle *result; parent = cssnode->parent ? cssnode->parent->style : NULL; decl = gtk_css_node_get_declaration (cssnode); result = lookup_in_global_parent_cache (cssnode, parent, decl); if (result) return g_object_ref (result); if (!gtk_css_node_init_matcher (cssnode, &matcher)) { g_assert_not_reached (); } result = gtk_css_static_style_new_update (GTK_CSS_STATIC_STYLE (style), parent_changes, gtk_css_node_get_style_provider (cssnode), &matcher, parent); store_in_global_parent_cache (cssnode, parent, decl, style); return result; } GtkCssStyle * gtk_css_node_create_style (GtkCssNode *cssnode) { const GtkCssNodeDeclaration *decl; GtkCssMatcher matcher; GtkCssStyle *parent; GtkCssStyle *style; decl = gtk_css_node_get_declaration (cssnode); parent = cssnode->parent ? cssnode->parent->style : NULL; style = lookup_in_global_parent_cache (cssnode, parent, decl); if (style) return g_object_ref (style); if (gtk_css_node_init_matcher (cssnode, &matcher)) style = gtk_css_static_style_new_compute (gtk_css_node_get_style_provider (cssnode), &matcher, parent); else style = gtk_css_static_style_new_compute (gtk_css_node_get_style_provider (cssnode), NULL, parent); store_in_global_parent_cache (cssnode, parent, decl, style); return style; } static gboolean gtk_css_style_needs_recreation (GtkCssStyle *style, GtkCssChange change) { /* Try to avoid invalidating if we can */ if (change & GTK_CSS_RADICAL_CHANGE) return TRUE; if (GTK_IS_CSS_ANIMATED_STYLE (style)) style = GTK_CSS_ANIMATED_STYLE (style)->style; if (gtk_css_static_style_get_change (GTK_CSS_STATIC_STYLE (style)) & change) return TRUE; else return FALSE; } static GtkCssStyle * gtk_css_node_real_update_style (GtkCssNode *cssnode, GtkCssChange pending_change, GtkCssStyle *old_style) { if (!gtk_css_style_needs_recreation (old_style, pending_change)) return g_object_ref (old_style); return gtk_css_node_create_style (cssnode); } static void gtk_css_node_real_invalidate (GtkCssNode *node) { } static void gtk_css_node_real_queue_validate (GtkCssNode *node) { } static void gtk_css_node_real_dequeue_validate (GtkCssNode *node) { } static GtkBitmask * gtk_css_node_real_validate (GtkCssNode *cssnode, gint64 timestamp, GtkCssChange change, const GtkBitmask *parent_changes) { return _gtk_bitmask_new (); } gboolean gtk_css_node_real_init_matcher (GtkCssNode *cssnode, GtkCssMatcher *matcher) { _gtk_css_matcher_node_init (matcher, cssnode); return TRUE; } static GtkWidgetPath * gtk_css_node_real_create_widget_path (GtkCssNode *cssnode) { return gtk_widget_path_new (); } static const GtkWidgetPath * gtk_css_node_real_get_widget_path (GtkCssNode *cssnode) { return NULL; } static GtkStyleProviderPrivate * gtk_css_node_real_get_style_provider (GtkCssNode *cssnode) { if (cssnode->parent) return gtk_css_node_get_style_provider (cssnode->parent); return GTK_STYLE_PROVIDER_PRIVATE (_gtk_settings_get_style_cascade (gtk_settings_get_default (), 1)); } static void gtk_css_node_class_init (GtkCssNodeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_css_node_dispose; object_class->finalize = gtk_css_node_finalize; klass->update_style = gtk_css_node_real_update_style; klass->invalidate = gtk_css_node_real_invalidate; klass->validate = gtk_css_node_real_validate; klass->queue_validate = gtk_css_node_real_queue_validate; klass->dequeue_validate = gtk_css_node_real_dequeue_validate; klass->init_matcher = gtk_css_node_real_init_matcher; klass->create_widget_path = gtk_css_node_real_create_widget_path; klass->get_widget_path = gtk_css_node_real_get_widget_path; klass->get_style_provider = gtk_css_node_real_get_style_provider; } static void gtk_css_node_init (GtkCssNode *cssnode) { cssnode->decl = gtk_css_node_declaration_new (); cssnode->style = g_object_ref (gtk_css_static_style_get_default ()); cssnode->visible = TRUE; } static void gtk_css_node_parent_was_unset (GtkCssNode *node) { if (node->invalid) GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node); } static void gtk_css_node_parent_will_be_set (GtkCssNode *node) { if (node->invalid) GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node); } static void gtk_css_node_unlink_from_siblings (GtkCssNode *node) { if (node->previous_sibling) node->previous_sibling->next_sibling = node->next_sibling; else node->parent->first_child = node->next_sibling; if (node->next_sibling) node->next_sibling->previous_sibling = node->previous_sibling; else node->parent->last_child = node->previous_sibling; node->previous_sibling = NULL; node->next_sibling = NULL; } static void gtk_css_node_link_to_siblings (GtkCssNode *node, GtkCssNode *new_previous) { if (new_previous) { node->previous_sibling = new_previous; node->next_sibling = new_previous->next_sibling; new_previous->next_sibling = node; } else { node->next_sibling = node->parent->first_child; node->parent->first_child = node; } if (node->next_sibling) node->next_sibling->previous_sibling = node; else node->parent->last_child = node; } static void gtk_css_node_set_children_changed (GtkCssNode *node) { if (node->children_changed) return; node->children_changed = TRUE; gtk_css_node_set_invalid (node, TRUE); } static void gtk_css_node_reposition (GtkCssNode *node, GtkCssNode *parent, GtkCssNode *previous) { g_assert (! (parent == NULL && previous != NULL)); /* Take a reference here so the whole function has a reference */ g_object_ref (node); if (node->parent != NULL) gtk_css_node_unlink_from_siblings (node); if (node->parent != parent) { if (node->parent == NULL) { gtk_css_node_parent_will_be_set (node); } else { g_object_unref (node); if (node->visible) gtk_css_node_set_children_changed (node->parent); } node->parent = parent; if (parent) { if (node->visible) gtk_css_node_set_children_changed (parent); g_object_ref (node); if (node->invalid && node->visible) gtk_css_node_set_invalid (parent, TRUE); } else { gtk_css_node_parent_was_unset (node); } } if (parent) gtk_css_node_link_to_siblings (node, previous); gtk_css_node_invalidate (node, GTK_CSS_CHANGE_ANY_PARENT | GTK_CSS_CHANGE_ANY_SIBLING); g_object_unref (node); } void gtk_css_node_set_parent (GtkCssNode *node, GtkCssNode *parent) { if (node->parent == parent) return; gtk_css_node_reposition (node, parent, parent ? parent->last_child : NULL); } void gtk_css_node_set_after (GtkCssNode *cssnode, GtkCssNode *previous_sibling) { if (cssnode->previous_sibling == previous_sibling) return; gtk_css_node_reposition (cssnode, previous_sibling->parent, previous_sibling); } void gtk_css_node_set_before (GtkCssNode *cssnode, GtkCssNode *next_sibling) { if (cssnode->next_sibling == next_sibling) return; gtk_css_node_reposition (cssnode, next_sibling->parent, next_sibling->previous_sibling); } GtkCssNode * gtk_css_node_get_parent (GtkCssNode *cssnode) { return cssnode->parent; } GtkCssNode * gtk_css_node_get_first_child (GtkCssNode *cssnode) { return cssnode->first_child; } GtkCssNode * gtk_css_node_get_last_child (GtkCssNode *cssnode) { return cssnode->last_child; } GtkCssNode * gtk_css_node_get_previous_sibling (GtkCssNode *cssnode) { return cssnode->previous_sibling; } GtkCssNode * gtk_css_node_get_next_sibling (GtkCssNode *cssnode) { return cssnode->next_sibling; } void gtk_css_node_set_style (GtkCssNode *cssnode, GtkCssStyle *style) { if (cssnode->style == style) return; if (style) g_object_ref (style); if (cssnode->style) g_object_unref (cssnode->style); cssnode->style = style; } GtkCssStyle * gtk_css_node_get_style (GtkCssNode *cssnode) { GtkCssStyle *new_style; if (cssnode->pending_changes) { new_style = GTK_CSS_NODE_GET_CLASS (cssnode)->update_style (cssnode, cssnode->pending_changes, cssnode->style); if (new_style) { gtk_css_node_set_style (cssnode, new_style); g_object_unref (new_style); cssnode->pending_changes = 0; } } return cssnode->style; } void gtk_css_node_set_visible (GtkCssNode *cssnode, gboolean visible) { if (cssnode->visible == visible) return; cssnode->visible = visible; if (cssnode->parent) gtk_css_node_set_children_changed (cssnode->parent); } gboolean gtk_css_node_get_visible (GtkCssNode *cssnode) { return cssnode->visible; } void gtk_css_node_set_widget_type (GtkCssNode *cssnode, GType widget_type) { if (gtk_css_node_declaration_set_type (&cssnode->decl, widget_type)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_NAME); } GType gtk_css_node_get_widget_type (GtkCssNode *cssnode) { return gtk_css_node_declaration_get_type (cssnode->decl); } void gtk_css_node_set_id (GtkCssNode *cssnode, const char *id) { if (gtk_css_node_declaration_set_id (&cssnode->decl, id)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_ID); } const char * gtk_css_node_get_id (GtkCssNode *cssnode) { return gtk_css_node_declaration_get_id (cssnode->decl); } void gtk_css_node_set_state (GtkCssNode *cssnode, GtkStateFlags state_flags) { if (gtk_css_node_declaration_set_state (&cssnode->decl, state_flags)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_STATE); } GtkStateFlags gtk_css_node_get_state (GtkCssNode *cssnode) { return gtk_css_node_declaration_get_state (cssnode->decl); } void gtk_css_node_set_junction_sides (GtkCssNode *cssnode, GtkJunctionSides junction_sides) { gtk_css_node_declaration_set_junction_sides (&cssnode->decl, junction_sides); } GtkJunctionSides gtk_css_node_get_junction_sides (GtkCssNode *cssnode) { return gtk_css_node_declaration_get_junction_sides (cssnode->decl); } void gtk_css_node_add_class (GtkCssNode *cssnode, GQuark style_class) { if (gtk_css_node_declaration_add_class (&cssnode->decl, style_class)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS); } void gtk_css_node_remove_class (GtkCssNode *cssnode, GQuark style_class) { if (gtk_css_node_declaration_remove_class (&cssnode->decl, style_class)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS); } gboolean gtk_css_node_has_class (GtkCssNode *cssnode, GQuark style_class) { return gtk_css_node_declaration_has_class (cssnode->decl, style_class); } GList * gtk_css_node_list_classes (GtkCssNode *cssnode) { return gtk_css_node_declaration_list_classes (cssnode->decl); } void gtk_css_node_add_region (GtkCssNode *cssnode, GQuark region, GtkRegionFlags flags) { if (gtk_css_node_declaration_add_region (&cssnode->decl, region, flags)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_REGION); } void gtk_css_node_remove_region (GtkCssNode *cssnode, GQuark region) { if (gtk_css_node_declaration_remove_region (&cssnode->decl, region)) gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_REGION); } gboolean gtk_css_node_has_region (GtkCssNode *cssnode, GQuark region, GtkRegionFlags *out_flags) { return gtk_css_node_declaration_has_region (cssnode->decl, region, out_flags); } GList * gtk_css_node_list_regions (GtkCssNode *cssnode) { return gtk_css_node_declaration_list_regions (cssnode->decl); } const GtkCssNodeDeclaration * gtk_css_node_get_declaration (GtkCssNode *cssnode) { return cssnode->decl; } void gtk_css_node_invalidate (GtkCssNode *cssnode, GtkCssChange change) { cssnode->pending_changes |= change; GTK_CSS_NODE_GET_CLASS (cssnode)->invalidate (cssnode); gtk_css_node_set_invalid (cssnode, TRUE); } static void gtk_css_node_propagate_pending_changes (GtkCssNode *cssnode) { GtkCssChange change; GtkCssNode *child; if (!cssnode->invalid) return; change = _gtk_css_change_for_child (cssnode->pending_changes); if (cssnode->children_changed) { change |= GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_ANY_SIBLING; cssnode->children_changed = FALSE; } for (child = gtk_css_node_get_first_child (cssnode); child; child = gtk_css_node_get_next_sibling (child)) { gtk_css_node_invalidate (child, change); } } void gtk_css_node_validate (GtkCssNode *cssnode, gint64 timestamp, const GtkBitmask *parent_changes) { GtkCssChange change; GtkCssNode *child; GtkBitmask *changes; /* If you run your application with * GTK_DEBUG=no-css-cache * every invalidation will purge the cache and completely query * everything anew form the cache. This is slow (in particular * when animating), but useful for figuring out bugs. * * We achieve that by pretending that everything that could have * changed has and so we of course totally need to redo everything. * * Note that this also completely revalidates child widgets all * the time. */ if (G_UNLIKELY (gtk_get_debug_flags () & GTK_DEBUG_NO_CSS_CACHE)) change = GTK_CSS_CHANGE_ANY; gtk_css_node_propagate_pending_changes (cssnode); if (!cssnode->invalid && change == 0 && _gtk_bitmask_is_empty (parent_changes)) return; gtk_css_node_set_invalid (cssnode, FALSE); change = cssnode->pending_changes; cssnode->pending_changes = 0; changes = GTK_CSS_NODE_GET_CLASS (cssnode)->validate (cssnode, timestamp, change, parent_changes); for (child = gtk_css_node_get_first_child (cssnode); child; child = gtk_css_node_get_next_sibling (child)) { if (child->visible) gtk_css_node_validate (child, timestamp, changes); } _gtk_bitmask_free (changes); } gboolean gtk_css_node_init_matcher (GtkCssNode *cssnode, GtkCssMatcher *matcher) { return GTK_CSS_NODE_GET_CLASS (cssnode)->init_matcher (cssnode, matcher); } GtkWidgetPath * gtk_css_node_create_widget_path (GtkCssNode *cssnode) { return GTK_CSS_NODE_GET_CLASS (cssnode)->create_widget_path (cssnode); } const GtkWidgetPath * gtk_css_node_get_widget_path (GtkCssNode *cssnode) { return GTK_CSS_NODE_GET_CLASS (cssnode)->get_widget_path (cssnode); } GtkStyleProviderPrivate * gtk_css_node_get_style_provider (GtkCssNode *cssnode) { return GTK_CSS_NODE_GET_CLASS (cssnode)->get_style_provider (cssnode); }