gecko-dev/widget/gtk/WidgetStyleCache.cpp

413 строки
13 KiB
C++

/* -*- 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/. */
/* TODO:
- implement GtkTextDirection
- implement StyleFlags
*/
#include <dlfcn.h>
#include <gtk/gtk.h>
#include "WidgetStyleCache.h"
#include "gtkdrawing.h"
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*
GetCssNodeStyleInternal(WidgetNodeType aNodeType);
static GtkWidget*
CreateWindowWidget()
{
GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP);
gtk_widget_realize(widget);
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);
gtk_widget_realize(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(NULL, "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*
CreateMenuItemWidget(WidgetNodeType aShellType)
{
GtkWidget* widget = gtk_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(aShellType)), widget);
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*
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_MENUBARITEM:
return CreateMenuItemWidget(MOZ_GTK_MENUBAR);
case MOZ_GTK_MENUITEM:
return CreateMenuItemWidget(MOZ_GTK_MENUPOPUP);
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)
{
GtkWidgetPath* path = aParentStyle ?
gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle)) :
gtk_widget_path_new();
// Work around https://bugzilla.gnome.org/show_bug.cgi?id=767312
// which exists in GTK+ 3.20.
gtk_widget_get_style_context(aWidget);
gtk_widget_path_append_for_widget(path, aWidget);
// Release any floating reference on aWidget.
g_object_ref_sink(aWidget);
g_object_unref(aWidget);
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;
}
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 = aParentStyle ?
gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle)) :
gtk_widget_path_new();
gtk_widget_path_append_type(path, aType);
(*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;
}
static GtkStyleContext*
CreateChildCSSNode(const char* aName, WidgetNodeType aParentNodeType)
{
return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
}
/* 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_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_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;
default:
// TODO - create style from style path
GtkWidget* widget = GetWidget(aNodeType);
return gtk_widget_get_style_context(widget);
}
MOZ_ASSERT(style, "missing style context for node type");
sStyleStorage[aNodeType] = style;
return style;
}
static GtkStyleContext*
GetWidgetStyleWithClass(WidgetNodeType aWidgetType, const gchar* aStyleClass)
{
GtkStyleContext* style = gtk_widget_get_style_context(GetWidget(aWidgetType));
gtk_style_context_save(style);
MOZ_ASSERT(!sStyleContextNeedsRestore);
sStyleContextNeedsRestore = true;
gtk_style_context_add_class(style, aStyleClass);
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_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;
}
default:
GtkWidget* widget = GetWidget(aNodeType);
MOZ_ASSERT(widget);
return gtk_widget_get_style_context(widget);
}
}
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
GtkStateFlags oldState = gtk_style_context_get_state(style);
GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
if (oldState != aStateFlags || oldDirection != aDirection) {
// From GTK 3.8, set_state() will overwrite the direction, so set
// direction after state.
gtk_style_context_set_state(style, aStateFlags);
gtk_style_context_set_direction(style, aDirection);
// 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 (!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
}