diff options
Diffstat (limited to 'widget/gtk/WidgetStyleCache.cpp')
-rw-r--r-- | widget/gtk/WidgetStyleCache.cpp | 1162 |
1 files changed, 1162 insertions, 0 deletions
diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp new file mode 100644 index 000000000..fd099681f --- /dev/null +++ b/widget/gtk/WidgetStyleCache.cpp @@ -0,0 +1,1162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <dlfcn.h> +#include <gtk/gtk.h> +#include "WidgetStyleCache.h" +#include "gtkdrawing.h" + +#define STATE_FLAG_DIR_LTR (1U << 7) +#define STATE_FLAG_DIR_RTL (1U << 8) +#if GTK_CHECK_VERSION(3,8,0) +static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR && + GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL, + "incorrect direction state flags"); +#endif + +static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT]; +static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT]; + +static bool sStyleContextNeedsRestore; +#ifdef DEBUG +static GtkStyleContext* sCurrentStyleContext; +#endif +static GtkStyleContext* +GetWidgetRootStyle(WidgetNodeType aNodeType); +static GtkStyleContext* +GetCssNodeStyleInternal(WidgetNodeType aNodeType); + +static GtkWidget* +CreateWindowWidget() +{ + GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_name(widget, "MozillaGtkWidget"); + return widget; +} + +static GtkWidget* +CreateWindowContainerWidget() +{ + GtkWidget *widget = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget); + return widget; +} + +static void +AddToWindowContainer(GtkWidget* widget) +{ + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget); +} + +static GtkWidget* +CreateScrollbarWidget(WidgetNodeType aWidgetType, GtkOrientation aOrientation) +{ + GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateCheckboxWidget() +{ + GtkWidget* widget = gtk_check_button_new_with_label("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateRadiobuttonWidget() +{ + GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateMenuBarWidget() +{ + GtkWidget* widget = gtk_menu_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateMenuPopupWidget() +{ + GtkWidget* widget = gtk_menu_new(); + gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW), + nullptr); + return widget; +} + +static GtkWidget* +CreateProgressWidget() +{ + GtkWidget* widget = gtk_progress_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTooltipWidget() +{ + MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr, + "CreateTooltipWidget should be used for Gtk < 3.20 only."); + GtkWidget* widget = CreateWindowWidget(); + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP); + return widget; +} + +static GtkWidget* +CreateExpanderWidget() +{ + GtkWidget* widget = gtk_expander_new("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateFrameWidget() +{ + GtkWidget* widget = gtk_frame_new(nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateGripperWidget() +{ + GtkWidget* widget = gtk_handle_box_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateToolbarWidget() +{ + GtkWidget* widget = gtk_toolbar_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget); + return widget; +} + +static GtkWidget* +CreateToolbarSeparatorWidget() +{ + GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new()); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateInfoBarWidget() +{ + GtkWidget* widget = gtk_info_bar_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateButtonWidget() +{ + GtkWidget* widget = gtk_button_new_with_label("M"); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateToggleButtonWidget() +{ + GtkWidget* widget = gtk_toggle_button_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateButtonArrowWidget() +{ + GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget); + gtk_widget_show(widget); + return widget; +} + +static GtkWidget* +CreateSpinWidget() +{ + GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateEntryWidget() +{ + GtkWidget* widget = gtk_entry_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateComboBoxWidget() +{ + GtkWidget* widget = gtk_combo_box_new(); + AddToWindowContainer(widget); + return widget; +} + +typedef struct +{ + GType type; + GtkWidget** widget; +} GtkInnerWidgetInfo; + +static void +GetInnerWidget(GtkWidget* widget, gpointer client_data) +{ + auto info = static_cast<GtkInnerWidgetInfo*>(client_data); + + if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) { + *info->widget = widget; + } +} + +static GtkWidget* +CreateComboBoxButtonWidget() +{ + GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX); + GtkWidget* comboBoxButton = nullptr; + + /* Get its inner Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON, + &comboBoxButton }; + gtk_container_forall(GTK_CONTAINER(comboBox), + GetInnerWidget, &info); + + if (!comboBoxButton) { + /* Shouldn't be reached with current internal gtk implementation; we + * use a generic toggle button as last resort fallback to avoid + * crashing. */ + comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON); + } else { + /* We need to have pointers to the inner widgets (button, separator, arrow) + * of the ComboBox to get the correct rendering from theme engines which + * special cases their look. Since the inner layout can change, we ask GTK + * to NULL our pointers when they are about to become invalid because the + * corresponding widgets don't exist anymore. It's the role of + * g_object_add_weak_pointer(). + * Note that if we don't find the inner widgets (which shouldn't happen), we + * fallback to use generic "non-inner" widgets, and they don't need that kind + * of weak pointer since they are explicit children of gProtoLayout and as + * such GTK holds a strong reference to them. */ + g_object_add_weak_pointer(G_OBJECT(comboBoxButton), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_BUTTON); + } + + return comboBoxButton; +} + +static GtkWidget* +CreateComboBoxArrowWidget() +{ + GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON); + GtkWidget* comboBoxArrow = nullptr; + + /* Get the widgets inside the Button */ + GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton)); + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_ARROW, + &comboBoxArrow }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } else if (GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + comboBoxArrow = buttonChild; + } + + if (!comboBoxArrow) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxArrow), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ARROW); + } + + return comboBoxArrow; +} + +static GtkWidget* +CreateComboBoxSeparatorWidget() +{ + // Ensure to search for separator only once as it can fail + // TODO - it won't initialize after ResetWidgetCache() call + static bool isMissingSeparator = false; + if (isMissingSeparator) + return nullptr; + + /* Get the widgets inside the Button */ + GtkWidget* comboBoxSeparator = nullptr; + GtkWidget* buttonChild = + gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON))); + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_SEPARATOR, + &comboBoxSeparator }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } + + if (comboBoxSeparator) { + g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_SEPARATOR); + } else { + /* comboBoxSeparator may be NULL + * when "appears-as-list" = TRUE or "cell-view" = FALSE; + * if there is no separator, then we just won't paint it. */ + isMissingSeparator = true; + } + + return comboBoxSeparator; +} + +static GtkWidget* +CreateComboBoxEntryWidget() +{ + GtkWidget* widget = gtk_combo_box_new_with_entry(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateComboBoxEntryTextareaWidget() +{ + GtkWidget* comboBoxTextarea = nullptr; + + /* Get its inner Entry and Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_ENTRY, + &comboBoxTextarea }; + gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)), + GetInnerWidget, &info); + + if (!comboBoxTextarea) { + comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxTextarea), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY); + } + + return comboBoxTextarea; +} + +static GtkWidget* +CreateComboBoxEntryButtonWidget() +{ + GtkWidget* comboBoxButton = nullptr; + + /* Get its inner Entry and Button */ + GtkInnerWidgetInfo info = { GTK_TYPE_TOGGLE_BUTTON, + &comboBoxButton }; + gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)), + GetInnerWidget, &info); + + if (!comboBoxButton) { + comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxButton), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY_BUTTON); + } + + return comboBoxButton; +} + +static GtkWidget* +CreateComboBoxEntryArrowWidget() +{ + GtkWidget* comboBoxArrow = nullptr; + + /* Get the Arrow inside the Button */ + GtkWidget* buttonChild = + gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON))); + + if (GTK_IS_BOX(buttonChild)) { + /* appears-as-list = FALSE, cell-view = TRUE; the button + * contains an hbox. This hbox is there because the ComboBox + * needs to place a cell renderer, a separator, and an arrow in + * the button when appears-as-list is FALSE. */ + GtkInnerWidgetInfo info = { GTK_TYPE_ARROW, + &comboBoxArrow }; + gtk_container_forall(GTK_CONTAINER(buttonChild), + GetInnerWidget, &info); + } else if (GTK_IS_ARROW(buttonChild)) { + /* appears-as-list = TRUE, or cell-view = FALSE; + * the button only contains an arrow */ + comboBoxArrow = buttonChild; + } + + if (!comboBoxArrow) { + /* Shouldn't be reached with current internal gtk implementation; + * we gButtonArrowWidget as last resort fallback to avoid + * crashing. */ + comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW); + } else { + g_object_add_weak_pointer(G_OBJECT(comboBoxArrow), + reinterpret_cast<gpointer *>(sWidgetStorage) + + MOZ_GTK_COMBOBOX_ENTRY_ARROW); + } + + return comboBoxArrow; +} + +static GtkWidget* +CreateScrolledWindowWidget() +{ + GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTextViewWidget() +{ + GtkWidget* widget = gtk_text_view_new(); + gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_SCROLLED_WINDOW)), + widget); + return widget; +} + +static GtkWidget* +CreateMenuSeparatorWidget() +{ + GtkWidget* widget = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), + widget); + return widget; +} + +static GtkWidget* +CreateTreeViewWidget() +{ + GtkWidget* widget = gtk_tree_view_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateTreeHeaderCellWidget() +{ + /* + * Some GTK engines paint the first and last cell + * of a TreeView header with a highlight. + * Since we do not know where our widget will be relative + * to the other buttons in the TreeView header, we must + * paint it as a button that is between two others, + * thus ensuring it is neither the first or last button + * in the header. + * GTK doesn't give us a way to do this explicitly, + * so we must paint with a button that is between two + * others. + */ + GtkTreeViewColumn* firstTreeViewColumn; + GtkTreeViewColumn* middleTreeViewColumn; + GtkTreeViewColumn* lastTreeViewColumn; + + GtkWidget *treeView = GetWidget(MOZ_GTK_TREEVIEW); + + /* Create and append our three columns */ + firstTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(firstTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + firstTreeViewColumn); + + middleTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(middleTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + middleTreeViewColumn); + + lastTreeViewColumn = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(lastTreeViewColumn, "M"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), + lastTreeViewColumn); + + /* Use the middle column's header for our button */ + return gtk_tree_view_column_get_button(middleTreeViewColumn); +} + +static GtkWidget* +CreateTreeHeaderSortArrowWidget() +{ + /* TODO, but it can't be NULL */ + GtkWidget* widget = gtk_button_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateHPanedWidget() +{ + GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateVPanedWidget() +{ + GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateScaleWidget(GtkOrientation aOrientation) +{ + GtkWidget* widget = gtk_scale_new(aOrientation, nullptr); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateNotebookWidget() +{ + GtkWidget* widget = gtk_notebook_new(); + AddToWindowContainer(widget); + return widget; +} + +static GtkWidget* +CreateWidget(WidgetNodeType aWidgetType) +{ + switch (aWidgetType) { + case MOZ_GTK_WINDOW: + return CreateWindowWidget(); + case MOZ_GTK_WINDOW_CONTAINER: + return CreateWindowContainerWidget(); + case MOZ_GTK_CHECKBUTTON_CONTAINER: + return CreateCheckboxWidget(); + case MOZ_GTK_PROGRESSBAR: + return CreateProgressWidget(); + case MOZ_GTK_RADIOBUTTON_CONTAINER: + return CreateRadiobuttonWidget(); + case MOZ_GTK_SCROLLBAR_HORIZONTAL: + return CreateScrollbarWidget(aWidgetType, + GTK_ORIENTATION_HORIZONTAL); + case MOZ_GTK_SCROLLBAR_VERTICAL: + return CreateScrollbarWidget(aWidgetType, + GTK_ORIENTATION_VERTICAL); + case MOZ_GTK_MENUBAR: + return CreateMenuBarWidget(); + case MOZ_GTK_MENUPOPUP: + return CreateMenuPopupWidget(); + case MOZ_GTK_MENUSEPARATOR: + return CreateMenuSeparatorWidget(); + case MOZ_GTK_EXPANDER: + return CreateExpanderWidget(); + case MOZ_GTK_FRAME: + return CreateFrameWidget(); + case MOZ_GTK_GRIPPER: + return CreateGripperWidget(); + case MOZ_GTK_TOOLBAR: + return CreateToolbarWidget(); + case MOZ_GTK_TOOLBAR_SEPARATOR: + return CreateToolbarSeparatorWidget(); + case MOZ_GTK_INFO_BAR: + return CreateInfoBarWidget(); + case MOZ_GTK_SPINBUTTON: + return CreateSpinWidget(); + case MOZ_GTK_BUTTON: + return CreateButtonWidget(); + case MOZ_GTK_TOGGLE_BUTTON: + return CreateToggleButtonWidget(); + case MOZ_GTK_BUTTON_ARROW: + return CreateButtonArrowWidget(); + case MOZ_GTK_ENTRY: + return CreateEntryWidget(); + case MOZ_GTK_SCROLLED_WINDOW: + return CreateScrolledWindowWidget(); + case MOZ_GTK_TEXT_VIEW: + return CreateTextViewWidget(); + case MOZ_GTK_TREEVIEW: + return CreateTreeViewWidget(); + case MOZ_GTK_TREE_HEADER_CELL: + return CreateTreeHeaderCellWidget(); + case MOZ_GTK_TREE_HEADER_SORTARROW: + return CreateTreeHeaderSortArrowWidget(); + case MOZ_GTK_SPLITTER_HORIZONTAL: + return CreateHPanedWidget(); + case MOZ_GTK_SPLITTER_VERTICAL: + return CreateVPanedWidget(); + case MOZ_GTK_SCALE_HORIZONTAL: + return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL); + case MOZ_GTK_SCALE_VERTICAL: + return CreateScaleWidget(GTK_ORIENTATION_VERTICAL); + case MOZ_GTK_NOTEBOOK: + return CreateNotebookWidget(); + case MOZ_GTK_COMBOBOX: + return CreateComboBoxWidget(); + case MOZ_GTK_COMBOBOX_BUTTON: + return CreateComboBoxButtonWidget(); + case MOZ_GTK_COMBOBOX_ARROW: + return CreateComboBoxArrowWidget(); + case MOZ_GTK_COMBOBOX_SEPARATOR: + return CreateComboBoxSeparatorWidget(); + case MOZ_GTK_COMBOBOX_ENTRY: + return CreateComboBoxEntryWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA: + return CreateComboBoxEntryTextareaWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_BUTTON: + return CreateComboBoxEntryButtonWidget(); + case MOZ_GTK_COMBOBOX_ENTRY_ARROW: + return CreateComboBoxEntryArrowWidget(); + default: + /* Not implemented */ + return nullptr; + } +} + +GtkWidget* +GetWidget(WidgetNodeType aWidgetType) +{ + GtkWidget* widget = sWidgetStorage[aWidgetType]; + if (!widget) { + widget = CreateWidget(aWidgetType); + sWidgetStorage[aWidgetType] = widget; + } + return widget; +} + +GtkStyleContext* +CreateStyleForWidget(GtkWidget* aWidget, GtkStyleContext* aParentStyle) +{ + static auto sGtkWidgetClassGetCSSName = + reinterpret_cast<const char* (*)(GtkWidgetClass*)> + (dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name")); + + GtkWidgetClass *widgetClass = GTK_WIDGET_GET_CLASS(aWidget); + const gchar* name = sGtkWidgetClassGetCSSName ? + sGtkWidgetClassGetCSSName(widgetClass) : nullptr; + + GtkStyleContext *context = + CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass)); + + // Classes are stored on the style context instead of the path so that any + // future gtk_style_context_save() will inherit classes on the head CSS + // node, in the same way as happens when called on a style context owned by + // a widget. + // + // Classes can be stored on a GtkCssNodeDeclaration and/or the path. + // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a + // new object to the path, without copying the classes from the old path + // head. The new head picks up classes from the GtkCssNodeDeclaration, but + // not the path. GtkWidgets store their classes on the + // GtkCssNodeDeclaration, so make sure to add classes there. + // + // Picking up classes from the style context also means that + // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop + // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20, + // is not a problem. + GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget); + GList* classes = gtk_style_context_list_classes(widgetStyle); + for (GList* link = classes; link; link = link->next) { + gtk_style_context_add_class(context, static_cast<gchar*>(link->data)); + } + g_list_free(classes); + + // Release any floating reference on aWidget. + g_object_ref_sink(aWidget); + g_object_unref(aWidget); + + return context; +} + +static GtkStyleContext* +CreateStyleForWidget(GtkWidget* aWidget, WidgetNodeType aParentType) +{ + return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType)); +} + +GtkStyleContext* +CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle, GType aType) +{ + static auto sGtkWidgetPathIterSetObjectName = + reinterpret_cast<void (*)(GtkWidgetPath *, gint, const char *)> + (dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name")); + + GtkWidgetPath* path; + if (aParentStyle) { + path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle)); + // Copy classes from the parent style context to its corresponding node in + // the path, because GTK will only match against ancestor classes if they + // are on the path. + GList* classes = gtk_style_context_list_classes(aParentStyle); + for (GList* link = classes; link; link = link->next) { + gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data)); + } + g_list_free(classes); + } else { + path = gtk_widget_path_new(); + } + + gtk_widget_path_append_type(path, aType); + + if (sGtkWidgetPathIterSetObjectName) { + (*sGtkWidgetPathIterSetObjectName)(path, -1, aName); + } + + GtkStyleContext *context = gtk_style_context_new(); + gtk_style_context_set_path(context, path); + gtk_style_context_set_parent(context, aParentStyle); + gtk_widget_path_unref(path); + + return context; +} + +// Return a style context matching that of the root CSS node of a widget. +// This is used by all GTK versions. +static GtkStyleContext* +GetWidgetRootStyle(WidgetNodeType aNodeType) +{ + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + switch (aNodeType) { + case MOZ_GTK_MENUBARITEM: + style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR); + break; + case MOZ_GTK_MENUITEM: + style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_IMAGEMENUITEM: + style = CreateStyleForWidget(gtk_image_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_CHECKMENUITEM_CONTAINER: + style = CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP); + break; + case MOZ_GTK_RADIOMENUITEM_CONTAINER: + style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr), + MOZ_GTK_MENUPOPUP); + break; + default: + GtkWidget* widget = GetWidget(aNodeType); + MOZ_ASSERT(widget); + return gtk_widget_get_style_context(widget); + } + + MOZ_ASSERT(style); + sStyleStorage[aNodeType] = style; + return style; +} + +static GtkStyleContext* +CreateChildCSSNode(const char* aName, WidgetNodeType aParentNodeType) +{ + return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType)); +} + +static GtkStyleContext* +GetWidgetStyleWithClass(WidgetNodeType aWidgetType, const gchar* aStyleClass) +{ + GtkStyleContext* style = GetWidgetRootStyle(aWidgetType); + gtk_style_context_save(style); + MOZ_ASSERT(!sStyleContextNeedsRestore); + sStyleContextNeedsRestore = true; + gtk_style_context_add_class(style, aStyleClass); + return style; +} + +/* GetCssNodeStyleInternal is used by Gtk >= 3.20 */ +static GtkStyleContext* +GetCssNodeStyleInternal(WidgetNodeType aNodeType) +{ + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + switch (aNodeType) { + case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCROLLBAR_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL); + break; + case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCROLLBAR_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL); + break; + case MOZ_GTK_SCROLLBAR_BUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON, + MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL); + break; + case MOZ_GTK_RADIOBUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, + MOZ_GTK_RADIOBUTTON_CONTAINER); + break; + case MOZ_GTK_CHECKBUTTON: + style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, + MOZ_GTK_CHECKBUTTON_CONTAINER); + break; + case MOZ_GTK_RADIOMENUITEM: + style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, + MOZ_GTK_RADIOMENUITEM_CONTAINER); + break; + case MOZ_GTK_CHECKMENUITEM: + style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, + MOZ_GTK_CHECKMENUITEM_CONTAINER); + break; + case MOZ_GTK_PROGRESS_TROUGH: + /* Progress bar background (trough) */ + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_PROGRESSBAR); + break; + case MOZ_GTK_PROGRESS_CHUNK: + style = CreateChildCSSNode("progress", + MOZ_GTK_PROGRESS_TROUGH); + break; + case MOZ_GTK_TOOLTIP: + // We create this from the path because GtkTooltipWindow is not public. + style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND); + break; + case MOZ_GTK_GRIPPER: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER, + GTK_STYLE_CLASS_GRIP); + case MOZ_GTK_INFO_BAR: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR, + GTK_STYLE_CLASS_INFO); + case MOZ_GTK_SPINBUTTON_ENTRY: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON, + GTK_STYLE_CLASS_ENTRY); + case MOZ_GTK_SCROLLED_WINDOW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW, + GTK_STYLE_CLASS_FRAME); + case MOZ_GTK_TEXT_VIEW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_FRAME_BORDER: + style = CreateChildCSSNode("border", MOZ_GTK_FRAME); + break; + case MOZ_GTK_TREEVIEW_VIEW: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_TREEVIEW_EXPANDER: + // TODO - create from CSS node + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_EXPANDER); + case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL: + style = CreateChildCSSNode("separator", + MOZ_GTK_SPLITTER_HORIZONTAL); + break; + case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL: + style = CreateChildCSSNode("separator", + MOZ_GTK_SPLITTER_VERTICAL); + break; + case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCALE_HORIZONTAL); + break; + case MOZ_GTK_SCALE_CONTENTS_VERTICAL: + style = CreateChildCSSNode("contents", + MOZ_GTK_SCALE_VERTICAL); + break; + case MOZ_GTK_SCALE_TROUGH_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCALE_CONTENTS_HORIZONTAL); + break; + case MOZ_GTK_SCALE_TROUGH_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, + MOZ_GTK_SCALE_CONTENTS_VERTICAL); + break; + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCALE_TROUGH_HORIZONTAL); + break; + case MOZ_GTK_SCALE_THUMB_VERTICAL: + style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER, + MOZ_GTK_SCALE_TROUGH_VERTICAL); + break; + case MOZ_GTK_TAB_TOP: + { + // TODO - create from CSS node + style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_TOP); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_TAB_BOTTOM: + { + // TODO - create from CSS node + style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_BOTTOM); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_NOTEBOOK: + case MOZ_GTK_NOTEBOOK_HEADER: + case MOZ_GTK_TABPANELS: + case MOZ_GTK_TAB_SCROLLARROW: + { + // TODO - create from CSS node + GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); + return gtk_widget_get_style_context(widget); + } + default: + return GetWidgetRootStyle(aNodeType); + } + + MOZ_ASSERT(style, "missing style context for node type"); + sStyleStorage[aNodeType] = style; + return style; +} + +/* GetWidgetStyleInternal is used by Gtk < 3.20 */ +static GtkStyleContext* +GetWidgetStyleInternal(WidgetNodeType aNodeType) +{ + switch (aNodeType) { + case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_RADIOBUTTON: + return GetWidgetStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER, + GTK_STYLE_CLASS_RADIO); + case MOZ_GTK_CHECKBUTTON: + return GetWidgetStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER, + GTK_STYLE_CLASS_CHECK); + case MOZ_GTK_RADIOMENUITEM: + return GetWidgetStyleWithClass(MOZ_GTK_RADIOMENUITEM_CONTAINER, + GTK_STYLE_CLASS_RADIO); + case MOZ_GTK_CHECKMENUITEM: + return GetWidgetStyleWithClass(MOZ_GTK_CHECKMENUITEM_CONTAINER, + GTK_STYLE_CLASS_CHECK); + case MOZ_GTK_PROGRESS_TROUGH: + return GetWidgetStyleWithClass(MOZ_GTK_PROGRESSBAR, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_TOOLTIP: { + GtkStyleContext* style = sStyleStorage[aNodeType]; + if (style) + return style; + + // The tooltip style class is added first in CreateTooltipWidget() so + // that gtk_widget_path_append_for_widget() in CreateStyleForWidget() + // will find it. + GtkWidget* tooltipWindow = CreateTooltipWidget(); + style = CreateStyleForWidget(tooltipWindow, nullptr); + gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference. + sStyleStorage[aNodeType] = style; + return style; + } + case MOZ_GTK_GRIPPER: + return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER, + GTK_STYLE_CLASS_GRIP); + case MOZ_GTK_INFO_BAR: + return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR, + GTK_STYLE_CLASS_INFO); + case MOZ_GTK_SPINBUTTON_ENTRY: + return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON, + GTK_STYLE_CLASS_ENTRY); + case MOZ_GTK_SCROLLED_WINDOW: + return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW, + GTK_STYLE_CLASS_FRAME); + case MOZ_GTK_TEXT_VIEW: + return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_FRAME_BORDER: + return GetWidgetRootStyle(MOZ_GTK_FRAME); + case MOZ_GTK_TREEVIEW_VIEW: + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_VIEW); + case MOZ_GTK_TREEVIEW_EXPANDER: + return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW, + GTK_STYLE_CLASS_EXPANDER); + case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL, + GTK_STYLE_CLASS_PANE_SEPARATOR); + case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL, + GTK_STYLE_CLASS_PANE_SEPARATOR); + case MOZ_GTK_SCALE_TROUGH_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCALE_TROUGH_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL, + GTK_STYLE_CLASS_TROUGH); + case MOZ_GTK_SCALE_THUMB_HORIZONTAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_SCALE_THUMB_VERTICAL: + return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL, + GTK_STYLE_CLASS_SLIDER); + case MOZ_GTK_TAB_TOP: + { + GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_TOP); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_TAB_BOTTOM: + { + GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK, + GTK_STYLE_CLASS_BOTTOM); + gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB, + static_cast<GtkRegionFlags>(0)); + return style; + } + case MOZ_GTK_NOTEBOOK: + case MOZ_GTK_NOTEBOOK_HEADER: + case MOZ_GTK_TABPANELS: + case MOZ_GTK_TAB_SCROLLARROW: + { + GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK); + return gtk_widget_get_style_context(widget); + } + default: + return GetWidgetRootStyle(aNodeType); + } +} + +void +ResetWidgetCache(void) +{ + MOZ_ASSERT(!sStyleContextNeedsRestore); +#ifdef DEBUG + MOZ_ASSERT(!sCurrentStyleContext); +#endif + + for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) { + if (sStyleStorage[i]) + g_object_unref(sStyleStorage[i]); + } + mozilla::PodArrayZero(sStyleStorage); + + /* This will destroy all of our widgets */ + if (sWidgetStorage[MOZ_GTK_WINDOW]) + gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]); + + /* Clear already freed arrays */ + mozilla::PodArrayZero(sWidgetStorage); +} + +GtkStyleContext* +ClaimStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection, + GtkStateFlags aStateFlags, StyleFlags aFlags) +{ + MOZ_ASSERT(!sStyleContextNeedsRestore); + GtkStyleContext* style; + if (gtk_check_version(3, 20, 0) != nullptr) { + style = GetWidgetStyleInternal(aNodeType); + } else { + style = GetCssNodeStyleInternal(aNodeType); + } +#ifdef DEBUG + MOZ_ASSERT(!sCurrentStyleContext); + sCurrentStyleContext = style; +#endif + bool stateChanged = false; + bool stateHasDirection = gtk_get_minor_version() >= 8; + GtkStateFlags oldState = gtk_style_context_get_state(style); + MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL))); + unsigned newState = aStateFlags; + if (stateHasDirection) { + switch (aDirection) { + case GTK_TEXT_DIR_LTR: + newState |= STATE_FLAG_DIR_LTR; + break; + case GTK_TEXT_DIR_RTL: + newState |= STATE_FLAG_DIR_RTL; + break; + default: + MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection"); + case GTK_TEXT_DIR_NONE: + // GtkWidget uses a default direction if neither is explicitly + // specified, but here DIR_NONE is interpreted as meaning the + // direction is not important, so don't change the direction + // unnecessarily. + newState |= oldState & (STATE_FLAG_DIR_LTR|STATE_FLAG_DIR_RTL); + } + } else if (aDirection != GTK_TEXT_DIR_NONE) { + GtkTextDirection oldDirection = gtk_style_context_get_direction(style); + if (aDirection != oldDirection) { + gtk_style_context_set_direction(style, aDirection); + stateChanged = true; + } + } + if (oldState != newState) { + gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState)); + stateChanged = true; + } + // This invalidate is necessary for unsaved style contexts from GtkWidgets + // in pre-3.18 GTK, because automatic invalidation of such contexts + // was delayed until a resize event runs. + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7 + // + // Avoid calling invalidate on saved contexts to avoid performing + // build_properties() (in 3.16 stylecontext.c) unnecessarily early. + if (stateChanged && !sStyleContextNeedsRestore) { + gtk_style_context_invalidate(style); + } + return style; +} + +void +ReleaseStyleContext(GtkStyleContext* aStyleContext) +{ + if (sStyleContextNeedsRestore) { + gtk_style_context_restore(aStyleContext); + } + sStyleContextNeedsRestore = false; +#ifdef DEBUG + MOZ_ASSERT(sCurrentStyleContext == aStyleContext); + sCurrentStyleContext = nullptr; +#endif +} |