1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <dlfcn.h>
8 #include <gtk/gtk.h>
9 #include "WidgetStyleCache.h"
10 #include "gtkdrawing.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/PodOperations.h"
13 #include "nsDebug.h"
14 #include "nsPrintfCString.h"
15 #include "nsString.h"
16
17 #define STATE_FLAG_DIR_LTR (1U << 7)
18 #define STATE_FLAG_DIR_RTL (1U << 8)
19 static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
20 GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
21 "incorrect direction state flags");
22
23 static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
24 static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
25
26 static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType);
27 static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType);
28
CreateWindowWidget()29 static GtkWidget* CreateWindowWidget() {
30 GtkWidget* widget = gtk_window_new(GTK_WINDOW_POPUP);
31 MOZ_RELEASE_ASSERT(widget, "We're missing GtkWindow widget!");
32 gtk_widget_set_name(widget, "MozillaGtkWidget");
33 return widget;
34 }
35
CreateWindowContainerWidget()36 static GtkWidget* CreateWindowContainerWidget() {
37 GtkWidget* widget = gtk_fixed_new();
38 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
39 return widget;
40 }
41
AddToWindowContainer(GtkWidget * widget)42 static void AddToWindowContainer(GtkWidget* widget) {
43 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget);
44 }
45
CreateScrollbarWidget(WidgetNodeType aAppearance,GtkOrientation aOrientation)46 static GtkWidget* CreateScrollbarWidget(WidgetNodeType aAppearance,
47 GtkOrientation aOrientation) {
48 GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr);
49 AddToWindowContainer(widget);
50 return widget;
51 }
52
CreateCheckboxWidget()53 static GtkWidget* CreateCheckboxWidget() {
54 GtkWidget* widget = gtk_check_button_new_with_label("M");
55 AddToWindowContainer(widget);
56 return widget;
57 }
58
CreateRadiobuttonWidget()59 static GtkWidget* CreateRadiobuttonWidget() {
60 GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M");
61 AddToWindowContainer(widget);
62 return widget;
63 }
64
CreateMenuBarWidget()65 static GtkWidget* CreateMenuBarWidget() {
66 GtkWidget* widget = gtk_menu_bar_new();
67 AddToWindowContainer(widget);
68 return widget;
69 }
70
CreateMenuPopupWidget()71 static GtkWidget* CreateMenuPopupWidget() {
72 GtkWidget* widget = gtk_menu_new();
73 gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
74 nullptr);
75 return widget;
76 }
77
CreateProgressWidget()78 static GtkWidget* CreateProgressWidget() {
79 GtkWidget* widget = gtk_progress_bar_new();
80 AddToWindowContainer(widget);
81 return widget;
82 }
83
CreateTooltipWidget()84 static GtkWidget* CreateTooltipWidget() {
85 MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr,
86 "CreateTooltipWidget should be used for Gtk < 3.20 only.");
87 GtkWidget* widget = CreateWindowWidget();
88 GtkStyleContext* style = gtk_widget_get_style_context(widget);
89 gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
90 return widget;
91 }
92
CreateExpanderWidget()93 static GtkWidget* CreateExpanderWidget() {
94 GtkWidget* widget = gtk_expander_new("M");
95 AddToWindowContainer(widget);
96 return widget;
97 }
98
CreateFrameWidget()99 static GtkWidget* CreateFrameWidget() {
100 GtkWidget* widget = gtk_frame_new(nullptr);
101 AddToWindowContainer(widget);
102 return widget;
103 }
104
CreateGripperWidget()105 static GtkWidget* CreateGripperWidget() {
106 GtkWidget* widget = gtk_handle_box_new();
107 AddToWindowContainer(widget);
108 return widget;
109 }
110
CreateToolbarWidget()111 static GtkWidget* CreateToolbarWidget() {
112 GtkWidget* widget = gtk_toolbar_new();
113 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
114 return widget;
115 }
116
CreateToolbarSeparatorWidget()117 static GtkWidget* CreateToolbarSeparatorWidget() {
118 GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
119 AddToWindowContainer(widget);
120 return widget;
121 }
122
CreateButtonWidget()123 static GtkWidget* CreateButtonWidget() {
124 GtkWidget* widget = gtk_button_new_with_label("M");
125 AddToWindowContainer(widget);
126 return widget;
127 }
128
CreateToggleButtonWidget()129 static GtkWidget* CreateToggleButtonWidget() {
130 GtkWidget* widget = gtk_toggle_button_new();
131 AddToWindowContainer(widget);
132 return widget;
133 }
134
CreateButtonArrowWidget()135 static GtkWidget* CreateButtonArrowWidget() {
136 GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
137 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
138 gtk_widget_show(widget);
139 return widget;
140 }
141
CreateSpinWidget()142 static GtkWidget* CreateSpinWidget() {
143 GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
144 AddToWindowContainer(widget);
145 return widget;
146 }
147
CreateEntryWidget()148 static GtkWidget* CreateEntryWidget() {
149 GtkWidget* widget = gtk_entry_new();
150 AddToWindowContainer(widget);
151 return widget;
152 }
153
CreateComboBoxWidget()154 static GtkWidget* CreateComboBoxWidget() {
155 GtkWidget* widget = gtk_combo_box_new();
156 AddToWindowContainer(widget);
157 return widget;
158 }
159
160 typedef struct {
161 GType type;
162 GtkWidget** widget;
163 } GtkInnerWidgetInfo;
164
GetInnerWidget(GtkWidget * widget,gpointer client_data)165 static void GetInnerWidget(GtkWidget* widget, gpointer client_data) {
166 auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
167
168 if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
169 *info->widget = widget;
170 }
171 }
172
CreateComboBoxButtonWidget()173 static GtkWidget* CreateComboBoxButtonWidget() {
174 GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
175 GtkWidget* comboBoxButton = nullptr;
176
177 /* Get its inner Button */
178 GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
179 gtk_container_forall(GTK_CONTAINER(comboBox), GetInnerWidget, &info);
180
181 if (!comboBoxButton) {
182 /* Shouldn't be reached with current internal gtk implementation; we
183 * use a generic toggle button as last resort fallback to avoid
184 * crashing. */
185 comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
186 } else {
187 /* We need to have pointers to the inner widgets (button, separator, arrow)
188 * of the ComboBox to get the correct rendering from theme engines which
189 * special cases their look. Since the inner layout can change, we ask GTK
190 * to NULL our pointers when they are about to become invalid because the
191 * corresponding widgets don't exist anymore. It's the role of
192 * g_object_add_weak_pointer().
193 * Note that if we don't find the inner widgets (which shouldn't happen), we
194 * fallback to use generic "non-inner" widgets, and they don't need that
195 * kind of weak pointer since they are explicit children of gProtoLayout and
196 * as such GTK holds a strong reference to them. */
197 g_object_add_weak_pointer(
198 G_OBJECT(comboBoxButton),
199 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_BUTTON);
200 }
201
202 return comboBoxButton;
203 }
204
CreateComboBoxArrowWidget()205 static GtkWidget* CreateComboBoxArrowWidget() {
206 GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
207 GtkWidget* comboBoxArrow = nullptr;
208
209 /* Get the widgets inside the Button */
210 GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
211 if (GTK_IS_BOX(buttonChild)) {
212 /* appears-as-list = FALSE, cell-view = TRUE; the button
213 * contains an hbox. This hbox is there because the ComboBox
214 * needs to place a cell renderer, a separator, and an arrow in
215 * the button when appears-as-list is FALSE. */
216 GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
217 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
218 } else if (GTK_IS_ARROW(buttonChild)) {
219 /* appears-as-list = TRUE, or cell-view = FALSE;
220 * the button only contains an arrow */
221 comboBoxArrow = buttonChild;
222 }
223
224 if (!comboBoxArrow) {
225 /* Shouldn't be reached with current internal gtk implementation;
226 * we gButtonArrowWidget as last resort fallback to avoid
227 * crashing. */
228 comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
229 } else {
230 g_object_add_weak_pointer(
231 G_OBJECT(comboBoxArrow),
232 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ARROW);
233 }
234
235 return comboBoxArrow;
236 }
237
CreateComboBoxSeparatorWidget()238 static GtkWidget* CreateComboBoxSeparatorWidget() {
239 // Ensure to search for separator only once as it can fail
240 // TODO - it won't initialize after ResetWidgetCache() call
241 static bool isMissingSeparator = false;
242 if (isMissingSeparator) return nullptr;
243
244 /* Get the widgets inside the Button */
245 GtkWidget* comboBoxSeparator = nullptr;
246 GtkWidget* buttonChild =
247 gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
248 if (GTK_IS_BOX(buttonChild)) {
249 /* appears-as-list = FALSE, cell-view = TRUE; the button
250 * contains an hbox. This hbox is there because the ComboBox
251 * needs to place a cell renderer, a separator, and an arrow in
252 * the button when appears-as-list is FALSE. */
253 GtkInnerWidgetInfo info = {GTK_TYPE_SEPARATOR, &comboBoxSeparator};
254 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
255 }
256
257 if (comboBoxSeparator) {
258 g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
259 reinterpret_cast<gpointer*>(sWidgetStorage) +
260 MOZ_GTK_COMBOBOX_SEPARATOR);
261 } else {
262 /* comboBoxSeparator may be NULL
263 * when "appears-as-list" = TRUE or "cell-view" = FALSE;
264 * if there is no separator, then we just won't paint it. */
265 isMissingSeparator = true;
266 }
267
268 return comboBoxSeparator;
269 }
270
CreateComboBoxEntryWidget()271 static GtkWidget* CreateComboBoxEntryWidget() {
272 GtkWidget* widget = gtk_combo_box_new_with_entry();
273 AddToWindowContainer(widget);
274 return widget;
275 }
276
CreateComboBoxEntryTextareaWidget()277 static GtkWidget* CreateComboBoxEntryTextareaWidget() {
278 GtkWidget* comboBoxTextarea = nullptr;
279
280 /* Get its inner Entry and Button */
281 GtkInnerWidgetInfo info = {GTK_TYPE_ENTRY, &comboBoxTextarea};
282 gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
283 GetInnerWidget, &info);
284
285 if (!comboBoxTextarea) {
286 comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
287 } else {
288 g_object_add_weak_pointer(
289 G_OBJECT(comboBoxTextarea),
290 reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ENTRY);
291 }
292
293 return comboBoxTextarea;
294 }
295
CreateComboBoxEntryButtonWidget()296 static GtkWidget* CreateComboBoxEntryButtonWidget() {
297 GtkWidget* comboBoxButton = nullptr;
298
299 /* Get its inner Entry and Button */
300 GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
301 gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
302 GetInnerWidget, &info);
303
304 if (!comboBoxButton) {
305 comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
306 } else {
307 g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
308 reinterpret_cast<gpointer*>(sWidgetStorage) +
309 MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
310 }
311
312 return comboBoxButton;
313 }
314
CreateComboBoxEntryArrowWidget()315 static GtkWidget* CreateComboBoxEntryArrowWidget() {
316 GtkWidget* comboBoxArrow = nullptr;
317
318 /* Get the Arrow inside the Button */
319 GtkWidget* buttonChild =
320 gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
321
322 if (GTK_IS_BOX(buttonChild)) {
323 /* appears-as-list = FALSE, cell-view = TRUE; the button
324 * contains an hbox. This hbox is there because the ComboBox
325 * needs to place a cell renderer, a separator, and an arrow in
326 * the button when appears-as-list is FALSE. */
327 GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
328 gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
329 } else if (GTK_IS_ARROW(buttonChild)) {
330 /* appears-as-list = TRUE, or cell-view = FALSE;
331 * the button only contains an arrow */
332 comboBoxArrow = buttonChild;
333 }
334
335 if (!comboBoxArrow) {
336 /* Shouldn't be reached with current internal gtk implementation;
337 * we gButtonArrowWidget as last resort fallback to avoid
338 * crashing. */
339 comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
340 } else {
341 g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
342 reinterpret_cast<gpointer*>(sWidgetStorage) +
343 MOZ_GTK_COMBOBOX_ENTRY_ARROW);
344 }
345
346 return comboBoxArrow;
347 }
348
CreateScrolledWindowWidget()349 static GtkWidget* CreateScrolledWindowWidget() {
350 GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
351 AddToWindowContainer(widget);
352 return widget;
353 }
354
CreateMenuSeparatorWidget()355 static GtkWidget* CreateMenuSeparatorWidget() {
356 GtkWidget* widget = gtk_separator_menu_item_new();
357 gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), widget);
358 return widget;
359 }
360
CreateTreeViewWidget()361 static GtkWidget* CreateTreeViewWidget() {
362 GtkWidget* widget = gtk_tree_view_new();
363 AddToWindowContainer(widget);
364 return widget;
365 }
366
CreateTreeHeaderCellWidget()367 static GtkWidget* CreateTreeHeaderCellWidget() {
368 /*
369 * Some GTK engines paint the first and last cell
370 * of a TreeView header with a highlight.
371 * Since we do not know where our widget will be relative
372 * to the other buttons in the TreeView header, we must
373 * paint it as a button that is between two others,
374 * thus ensuring it is neither the first or last button
375 * in the header.
376 * GTK doesn't give us a way to do this explicitly,
377 * so we must paint with a button that is between two
378 * others.
379 */
380 GtkTreeViewColumn* firstTreeViewColumn;
381 GtkTreeViewColumn* middleTreeViewColumn;
382 GtkTreeViewColumn* lastTreeViewColumn;
383
384 GtkWidget* treeView = GetWidget(MOZ_GTK_TREEVIEW);
385
386 /* Create and append our three columns */
387 firstTreeViewColumn = gtk_tree_view_column_new();
388 gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
389 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), firstTreeViewColumn);
390
391 middleTreeViewColumn = gtk_tree_view_column_new();
392 gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
393 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), middleTreeViewColumn);
394
395 lastTreeViewColumn = gtk_tree_view_column_new();
396 gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
397 gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), lastTreeViewColumn);
398
399 /* Use the middle column's header for our button */
400 return gtk_tree_view_column_get_button(middleTreeViewColumn);
401 }
402
CreateTreeHeaderSortArrowWidget()403 static GtkWidget* CreateTreeHeaderSortArrowWidget() {
404 /* TODO, but it can't be NULL */
405 GtkWidget* widget = gtk_button_new();
406 AddToWindowContainer(widget);
407 return widget;
408 }
409
CreateHPanedWidget()410 static GtkWidget* CreateHPanedWidget() {
411 GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
412 AddToWindowContainer(widget);
413 return widget;
414 }
415
CreateVPanedWidget()416 static GtkWidget* CreateVPanedWidget() {
417 GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
418 AddToWindowContainer(widget);
419 return widget;
420 }
421
CreateScaleWidget(GtkOrientation aOrientation)422 static GtkWidget* CreateScaleWidget(GtkOrientation aOrientation) {
423 GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
424 AddToWindowContainer(widget);
425 return widget;
426 }
427
CreateNotebookWidget()428 static GtkWidget* CreateNotebookWidget() {
429 GtkWidget* widget = gtk_notebook_new();
430 AddToWindowContainer(widget);
431 return widget;
432 }
433
CreateHeaderBarWidget(WidgetNodeType aAppearance,bool aIsSolidCSDStyleUsed)434 static void CreateHeaderBarWidget(WidgetNodeType aAppearance,
435 bool aIsSolidCSDStyleUsed) {
436 sWidgetStorage[aAppearance] = gtk_header_bar_new();
437
438 GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
439 GtkStyleContext* style = gtk_widget_get_style_context(window);
440
441 if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
442 gtk_style_context_add_class(style, "maximized");
443 MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] == nullptr,
444 "Window widget is already created!");
445 sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
446 } else {
447 MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] == nullptr,
448 "Window widget is already created!");
449 sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
450 }
451
452 // Headerbar has to be placed to window with csd or solid-csd style
453 // to properly draw the decorated.
454 gtk_style_context_add_class(style,
455 aIsSolidCSDStyleUsed ? "solid-csd" : "csd");
456
457 GtkWidget* fixed = gtk_fixed_new();
458 gtk_container_add(GTK_CONTAINER(window), fixed);
459 gtk_container_add(GTK_CONTAINER(fixed), sWidgetStorage[aAppearance]);
460
461 // Emulate what create_titlebar() at gtkwindow.c does.
462 style = gtk_widget_get_style_context(sWidgetStorage[aAppearance]);
463 gtk_style_context_add_class(style, "titlebar");
464
465 // TODO: Define default-decoration titlebar style as workaround
466 // to ensure the titlebar buttons does not overflow outside.
467 // Recently the titlebar size is calculated as
468 // tab size + titlebar border/padding (default-decoration has 6px padding
469 // at default Adwaita theme).
470 // We need to fix titlebar size calculation to also include
471 // titlebar button sizes. (Bug 1419442)
472 gtk_style_context_add_class(style, "default-decoration");
473 }
474
475 #define ICON_SCALE_VARIANTS 2
476
LoadWidgetIconPixbuf(GtkWidget * aWidgetIcon)477 static void LoadWidgetIconPixbuf(GtkWidget* aWidgetIcon) {
478 GtkStyleContext* style = gtk_widget_get_style_context(aWidgetIcon);
479
480 const gchar* iconName;
481 GtkIconSize gtkIconSize;
482 gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon), &iconName, >kIconSize);
483
484 gint iconWidth, iconHeight;
485 gtk_icon_size_lookup(gtkIconSize, &iconWidth, &iconHeight);
486
487 /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
488 for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
489 GtkIconInfo* gtkIconInfo = gtk_icon_theme_lookup_icon_for_scale(
490 gtk_icon_theme_get_default(), iconName, iconWidth, scale,
491 (GtkIconLookupFlags)0);
492
493 if (!gtkIconInfo) {
494 // We miss the icon, nothing to do here.
495 return;
496 }
497
498 gboolean unused;
499 GdkPixbuf* iconPixbuf = gtk_icon_info_load_symbolic_for_context(
500 gtkIconInfo, style, &unused, nullptr);
501 g_object_unref(G_OBJECT(gtkIconInfo));
502
503 cairo_surface_t* iconSurface =
504 gdk_cairo_surface_create_from_pixbuf(iconPixbuf, scale, nullptr);
505 g_object_unref(iconPixbuf);
506
507 nsPrintfCString surfaceName("MozillaIconSurface%d", scale);
508 g_object_set_data_full(G_OBJECT(aWidgetIcon), surfaceName.get(),
509 iconSurface, (GDestroyNotify)cairo_surface_destroy);
510 }
511 }
512
GetWidgetIconSurface(GtkWidget * aWidgetIcon,int aScale)513 cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale) {
514 if (aScale > ICON_SCALE_VARIANTS) {
515 aScale = ICON_SCALE_VARIANTS;
516 }
517
518 nsPrintfCString surfaceName("MozillaIconSurface%d", aScale);
519 return (cairo_surface_t*)g_object_get_data(G_OBJECT(aWidgetIcon),
520 surfaceName.get());
521 }
522
CreateHeaderBarButton(GtkWidget * aParentWidget,WidgetNodeType aAppearance)523 static void CreateHeaderBarButton(GtkWidget* aParentWidget,
524 WidgetNodeType aAppearance) {
525 GtkWidget* widget = gtk_button_new();
526
527 // We have to add button to widget hierarchy now to pick
528 // right icon style at LoadWidgetIconPixbuf().
529 if (GTK_IS_BOX(aParentWidget)) {
530 gtk_box_pack_start(GTK_BOX(aParentWidget), widget, FALSE, FALSE, 0);
531 } else {
532 gtk_container_add(GTK_CONTAINER(aParentWidget), widget);
533 }
534
535 // We bypass GetWidget() here because we create all titlebar
536 // buttons at once when a first one is requested.
537 NS_ASSERTION(sWidgetStorage[aAppearance] == nullptr,
538 "Titlebar button is already created!");
539 sWidgetStorage[aAppearance] = widget;
540
541 // We need to show the button widget now as GtkBox does not
542 // place invisible widgets and we'll miss first-child/last-child
543 // css selectors at the buttons otherwise.
544 gtk_widget_show(widget);
545
546 GtkStyleContext* style = gtk_widget_get_style_context(widget);
547 gtk_style_context_add_class(style, "titlebutton");
548
549 GtkWidget* image = nullptr;
550 switch (aAppearance) {
551 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
552 gtk_style_context_add_class(style, "close");
553 image = gtk_image_new_from_icon_name("window-close-symbolic",
554 GTK_ICON_SIZE_MENU);
555 break;
556 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
557 gtk_style_context_add_class(style, "minimize");
558 image = gtk_image_new_from_icon_name("window-minimize-symbolic",
559 GTK_ICON_SIZE_MENU);
560 break;
561
562 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
563 gtk_style_context_add_class(style, "maximize");
564 image = gtk_image_new_from_icon_name("window-maximize-symbolic",
565 GTK_ICON_SIZE_MENU);
566 break;
567
568 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
569 gtk_style_context_add_class(style, "maximize");
570 image = gtk_image_new_from_icon_name("window-restore-symbolic",
571 GTK_ICON_SIZE_MENU);
572 break;
573 default:
574 break;
575 }
576
577 gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
578 g_object_set(image, "use-fallback", TRUE, NULL);
579 gtk_container_add(GTK_CONTAINER(widget), image);
580
581 // We bypass GetWidget() here by explicit sWidgetStorage[] update so
582 // invalidate the style as well as GetWidget() does.
583 style = gtk_widget_get_style_context(image);
584 gtk_style_context_invalidate(style);
585
586 LoadWidgetIconPixbuf(image);
587 }
588
IsToolbarButtonEnabled(ButtonLayout * aButtonLayout,size_t aButtonNums,WidgetNodeType aAppearance)589 static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout,
590 size_t aButtonNums,
591 WidgetNodeType aAppearance) {
592 for (size_t i = 0; i < aButtonNums; i++) {
593 if (aButtonLayout[i].mType == aAppearance) {
594 return true;
595 }
596 }
597 return false;
598 }
599
CreateHeaderBarButtons()600 static void CreateHeaderBarButtons() {
601 GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
602 MOZ_ASSERT(headerBar != nullptr, "We're missing header bar widget!");
603
604 gint buttonSpacing = 6;
605 g_object_get(headerBar, "spacing", &buttonSpacing, nullptr);
606
607 GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
608 gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_HEADER_BAR)), buttonBox);
609 // We support only LTR headerbar layout for now.
610 gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
611 GTK_STYLE_CLASS_LEFT);
612
613 ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
614
615 size_t activeButtons =
616 GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout), nullptr);
617
618 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
619 MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
620 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
621 }
622 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
623 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
624 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
625 // We don't pack "restore" headerbar button to box as it's an icon
626 // placeholder. Pack it only to header bar to get correct style.
627 CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR),
628 MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE);
629 }
630 if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
631 MOZ_GTK_HEADER_BAR_BUTTON_CLOSE)) {
632 CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
633 }
634 }
635
CreateHeaderBar()636 static void CreateHeaderBar() {
637 const bool isSolidCSDStyleUsed = []() {
638 GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
639 gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new());
640 gtk_widget_realize(window);
641 GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
642 bool ret = gtk_style_context_has_class(windowStyle, "solid-csd");
643 gtk_widget_destroy(window);
644 return ret;
645 }();
646
647 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR, isSolidCSDStyleUsed);
648 CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED, isSolidCSDStyleUsed);
649 CreateHeaderBarButtons();
650 }
651
CreateWidget(WidgetNodeType aAppearance)652 static GtkWidget* CreateWidget(WidgetNodeType aAppearance) {
653 switch (aAppearance) {
654 case MOZ_GTK_WINDOW:
655 return CreateWindowWidget();
656 case MOZ_GTK_WINDOW_CONTAINER:
657 return CreateWindowContainerWidget();
658 case MOZ_GTK_CHECKBUTTON_CONTAINER:
659 return CreateCheckboxWidget();
660 case MOZ_GTK_PROGRESSBAR:
661 return CreateProgressWidget();
662 case MOZ_GTK_RADIOBUTTON_CONTAINER:
663 return CreateRadiobuttonWidget();
664 case MOZ_GTK_SCROLLBAR_HORIZONTAL:
665 return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_HORIZONTAL);
666 case MOZ_GTK_SCROLLBAR_VERTICAL:
667 return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
668 case MOZ_GTK_MENUBAR:
669 return CreateMenuBarWidget();
670 case MOZ_GTK_MENUPOPUP:
671 return CreateMenuPopupWidget();
672 case MOZ_GTK_MENUSEPARATOR:
673 return CreateMenuSeparatorWidget();
674 case MOZ_GTK_EXPANDER:
675 return CreateExpanderWidget();
676 case MOZ_GTK_FRAME:
677 return CreateFrameWidget();
678 case MOZ_GTK_GRIPPER:
679 return CreateGripperWidget();
680 case MOZ_GTK_TOOLBAR:
681 return CreateToolbarWidget();
682 case MOZ_GTK_TOOLBAR_SEPARATOR:
683 return CreateToolbarSeparatorWidget();
684 case MOZ_GTK_SPINBUTTON:
685 return CreateSpinWidget();
686 case MOZ_GTK_BUTTON:
687 return CreateButtonWidget();
688 case MOZ_GTK_TOGGLE_BUTTON:
689 return CreateToggleButtonWidget();
690 case MOZ_GTK_BUTTON_ARROW:
691 return CreateButtonArrowWidget();
692 case MOZ_GTK_ENTRY:
693 case MOZ_GTK_DROPDOWN_ENTRY:
694 return CreateEntryWidget();
695 case MOZ_GTK_SCROLLED_WINDOW:
696 return CreateScrolledWindowWidget();
697 case MOZ_GTK_TREEVIEW:
698 return CreateTreeViewWidget();
699 case MOZ_GTK_TREE_HEADER_CELL:
700 return CreateTreeHeaderCellWidget();
701 case MOZ_GTK_TREE_HEADER_SORTARROW:
702 return CreateTreeHeaderSortArrowWidget();
703 case MOZ_GTK_SPLITTER_HORIZONTAL:
704 return CreateHPanedWidget();
705 case MOZ_GTK_SPLITTER_VERTICAL:
706 return CreateVPanedWidget();
707 case MOZ_GTK_SCALE_HORIZONTAL:
708 return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
709 case MOZ_GTK_SCALE_VERTICAL:
710 return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
711 case MOZ_GTK_NOTEBOOK:
712 return CreateNotebookWidget();
713 case MOZ_GTK_COMBOBOX:
714 return CreateComboBoxWidget();
715 case MOZ_GTK_COMBOBOX_BUTTON:
716 return CreateComboBoxButtonWidget();
717 case MOZ_GTK_COMBOBOX_ARROW:
718 return CreateComboBoxArrowWidget();
719 case MOZ_GTK_COMBOBOX_SEPARATOR:
720 return CreateComboBoxSeparatorWidget();
721 case MOZ_GTK_COMBOBOX_ENTRY:
722 return CreateComboBoxEntryWidget();
723 case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
724 return CreateComboBoxEntryTextareaWidget();
725 case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
726 return CreateComboBoxEntryButtonWidget();
727 case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
728 return CreateComboBoxEntryArrowWidget();
729 case MOZ_GTK_HEADERBAR_WINDOW:
730 case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED:
731 case MOZ_GTK_HEADER_BAR:
732 case MOZ_GTK_HEADER_BAR_MAXIMIZED:
733 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
734 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
735 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
736 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
737 /* Create header bar widgets once and fill with child elements as we need
738 the header bar fully configured to get a correct style */
739 CreateHeaderBar();
740 return sWidgetStorage[aAppearance];
741 default:
742 /* Not implemented */
743 return nullptr;
744 }
745 }
746
GetWidget(WidgetNodeType aAppearance)747 GtkWidget* GetWidget(WidgetNodeType aAppearance) {
748 GtkWidget* widget = sWidgetStorage[aAppearance];
749 if (!widget) {
750 widget = CreateWidget(aAppearance);
751 // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
752 // available or implemented.
753 if (!widget) {
754 return nullptr;
755 }
756 // In GTK versions prior to 3.18, automatic invalidation of style contexts
757 // for widgets was delayed until the next resize event. Gecko however,
758 // typically uses the style context before the resize event runs and so an
759 // explicit invalidation may be required. This is necessary if a style
760 // property was retrieved before all changes were made to the style
761 // context. One such situation is where gtk_button_construct_child()
762 // retrieves the style property "image-spacing" during construction of the
763 // GtkButton, before its parent is set to provide inheritance of ancestor
764 // properties. More recent GTK versions do not need this, but do not
765 // re-resolve until required and so invalidation does not trigger
766 // unnecessary resolution in general.
767 GtkStyleContext* style = gtk_widget_get_style_context(widget);
768 gtk_style_context_invalidate(style);
769
770 sWidgetStorage[aAppearance] = widget;
771 }
772 return widget;
773 }
774
AddStyleClassesFromStyle(GtkStyleContext * aDest,GtkStyleContext * aSrc)775 static void AddStyleClassesFromStyle(GtkStyleContext* aDest,
776 GtkStyleContext* aSrc) {
777 GList* classes = gtk_style_context_list_classes(aSrc);
778 for (GList* link = classes; link; link = link->next) {
779 gtk_style_context_add_class(aDest, static_cast<gchar*>(link->data));
780 }
781 g_list_free(classes);
782 }
783
CreateStyleForWidget(GtkWidget * aWidget,GtkStyleContext * aParentStyle)784 GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
785 GtkStyleContext* aParentStyle) {
786 static auto sGtkWidgetClassGetCSSName =
787 reinterpret_cast<const char* (*)(GtkWidgetClass*)>(
788 dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
789
790 GtkWidgetClass* widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
791 const gchar* name = sGtkWidgetClassGetCSSName
792 ? sGtkWidgetClassGetCSSName(widgetClass)
793 : nullptr;
794
795 GtkStyleContext* context =
796 CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
797
798 // Classes are stored on the style context instead of the path so that any
799 // future gtk_style_context_save() will inherit classes on the head CSS
800 // node, in the same way as happens when called on a style context owned by
801 // a widget.
802 //
803 // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
804 // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
805 // new object to the path, without copying the classes from the old path
806 // head. The new head picks up classes from the GtkCssNodeDeclaration, but
807 // not the path. GtkWidgets store their classes on the
808 // GtkCssNodeDeclaration, so make sure to add classes there.
809 //
810 // Picking up classes from the style context also means that
811 // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
812 // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
813 // is not a problem.
814 GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
815 AddStyleClassesFromStyle(context, widgetStyle);
816
817 // Release any floating reference on aWidget.
818 g_object_ref_sink(aWidget);
819 g_object_unref(aWidget);
820
821 return context;
822 }
823
CreateStyleForWidget(GtkWidget * aWidget,WidgetNodeType aParentType)824 static GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
825 WidgetNodeType aParentType) {
826 return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
827 }
828
CreateCSSNode(const char * aName,GtkStyleContext * aParentStyle,GType aType)829 GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
830 GType aType) {
831 static auto sGtkWidgetPathIterSetObjectName =
832 reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
833 dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
834
835 GtkWidgetPath* path;
836 if (aParentStyle) {
837 path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
838 // Copy classes from the parent style context to its corresponding node in
839 // the path, because GTK will only match against ancestor classes if they
840 // are on the path.
841 GList* classes = gtk_style_context_list_classes(aParentStyle);
842 for (GList* link = classes; link; link = link->next) {
843 gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
844 }
845 g_list_free(classes);
846 } else {
847 path = gtk_widget_path_new();
848 }
849
850 gtk_widget_path_append_type(path, aType);
851
852 if (sGtkWidgetPathIterSetObjectName) {
853 (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
854 }
855
856 GtkStyleContext* context = gtk_style_context_new();
857 gtk_style_context_set_path(context, path);
858 gtk_style_context_set_parent(context, aParentStyle);
859 gtk_widget_path_unref(path);
860
861 // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
862 // context without ensuring any style resolution sets it appropriately
863 // in style_data_lookup(). e.g.
864 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
865 //
866 // That can result in incorrect drawing on first draw. To work around this,
867 // force a style look-up to set |theming_engine|. It is sufficient to do
868 // this only on context creation, instead of after every modification to the
869 // context, because themes typically (Ambiance and oxygen-gtk, at least) set
870 // the "engine" property with the '*' selector.
871 if (GTK_MAJOR_VERSION == 3 && gtk_get_minor_version() < 6) {
872 GdkRGBA unused;
873 gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &unused);
874 }
875
876 return context;
877 }
878
879 // Return a style context matching that of the root CSS node of a widget.
880 // This is used by all GTK versions.
GetWidgetRootStyle(WidgetNodeType aNodeType)881 static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType) {
882 GtkStyleContext* style = sStyleStorage[aNodeType];
883 if (style) return style;
884
885 switch (aNodeType) {
886 case MOZ_GTK_MENUBARITEM:
887 style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
888 break;
889 case MOZ_GTK_MENUITEM:
890 style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
891 break;
892 case MOZ_GTK_CHECKMENUITEM:
893 style =
894 CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
895 break;
896 case MOZ_GTK_RADIOMENUITEM:
897 style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
898 MOZ_GTK_MENUPOPUP);
899 break;
900 case MOZ_GTK_TEXT_VIEW:
901 style =
902 CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW);
903 break;
904 case MOZ_GTK_TOOLTIP:
905 if (gtk_check_version(3, 20, 0) != nullptr) {
906 // The tooltip style class is added first in CreateTooltipWidget()
907 // and transfered to style in CreateStyleForWidget().
908 GtkWidget* tooltipWindow = CreateTooltipWidget();
909 style = CreateStyleForWidget(tooltipWindow, nullptr);
910 gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
911 } else {
912 // We create this from the path because GtkTooltipWindow is not public.
913 style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
914 gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
915 }
916 break;
917 case MOZ_GTK_TOOLTIP_BOX:
918 style = CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
919 MOZ_GTK_TOOLTIP);
920 break;
921 case MOZ_GTK_TOOLTIP_BOX_LABEL:
922 style = CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX);
923 break;
924 default:
925 GtkWidget* widget = GetWidget(aNodeType);
926 MOZ_ASSERT(widget);
927 return gtk_widget_get_style_context(widget);
928 }
929
930 MOZ_ASSERT(style);
931 sStyleStorage[aNodeType] = style;
932 return style;
933 }
934
CreateChildCSSNode(const char * aName,WidgetNodeType aParentNodeType)935 static GtkStyleContext* CreateChildCSSNode(const char* aName,
936 WidgetNodeType aParentNodeType) {
937 return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
938 }
939
940 // Create a style context equivalent to a saved root style context of
941 // |aAppearance| with |aStyleClass| as an additional class. This is used to
942 // produce a context equivalent to what GTK versions < 3.20 use for many
943 // internal parts of widgets.
CreateSubStyleWithClass(WidgetNodeType aAppearance,const gchar * aStyleClass)944 static GtkStyleContext* CreateSubStyleWithClass(WidgetNodeType aAppearance,
945 const gchar* aStyleClass) {
946 static auto sGtkWidgetPathIterGetObjectName =
947 reinterpret_cast<const char* (*)(const GtkWidgetPath*, gint)>(
948 dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_get_object_name"));
949
950 GtkStyleContext* parentStyle = GetWidgetRootStyle(aAppearance);
951
952 // Create a new context that behaves like |parentStyle| would after
953 // gtk_style_context_save(parentStyle).
954 //
955 // Avoiding gtk_style_context_save() avoids the need to manage the
956 // restore, and a new context permits caching style resolution.
957 //
958 // gtk_style_context_save(context) changes the node hierarchy of |context|
959 // to add a new GtkCssNodeDeclaration that is a copy of its original node.
960 // The new node is a child of the original node, and so the new heirarchy is
961 // one level deeper. The new node receives the same classes as the
962 // original, but any changes to the classes on |context| will change only
963 // the new node. The new node inherits properties from the original node
964 // (which retains the original heirarchy and classes) and matches CSS rules
965 // with the new heirarchy and any changes to the classes.
966 //
967 // The change in hierarchy can produce some surprises in matching theme CSS
968 // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
969 // is important here to produce the same behavior so that rules match the
970 // same widget parts in Gecko as they do in GTK.
971 //
972 // When using public GTK API to construct style contexts, a widget path is
973 // required. CSS rules are not matched against the style context heirarchy
974 // but according to the heirarchy in the widget path. The path that matches
975 // the same CSS rules as a saved context is like the path of |parentStyle|
976 // but with an extra copy of the head (last) object appended. Setting
977 // |parentStyle| as the parent context provides the same inheritance of
978 // properties from the widget root node.
979 const GtkWidgetPath* parentPath = gtk_style_context_get_path(parentStyle);
980 const gchar* name = sGtkWidgetPathIterGetObjectName
981 ? sGtkWidgetPathIterGetObjectName(parentPath, -1)
982 : nullptr;
983 GType objectType = gtk_widget_path_get_object_type(parentPath);
984
985 GtkStyleContext* style = CreateCSSNode(name, parentStyle, objectType);
986
987 // Start with the same classes on the new node as were on |parentStyle|.
988 // GTK puts no regions or junction_sides on widget root nodes, and so there
989 // is no need to copy these.
990 AddStyleClassesFromStyle(style, parentStyle);
991
992 gtk_style_context_add_class(style, aStyleClass);
993 return style;
994 }
995
996 /* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
GetCssNodeStyleInternal(WidgetNodeType aNodeType)997 static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) {
998 GtkStyleContext* style = sStyleStorage[aNodeType];
999 if (style) return style;
1000
1001 switch (aNodeType) {
1002 case MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL:
1003 style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_HORIZONTAL);
1004 break;
1005 case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
1006 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1007 MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
1008 break;
1009 case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
1010 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1011 MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL);
1012 break;
1013 case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
1014 style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL);
1015 break;
1016 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1017 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1018 MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
1019 break;
1020 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1021 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1022 MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
1023 break;
1024 case MOZ_GTK_SCROLLBAR_BUTTON:
1025 style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON,
1026 MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
1027 break;
1028 case MOZ_GTK_RADIOBUTTON:
1029 style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
1030 MOZ_GTK_RADIOBUTTON_CONTAINER);
1031 break;
1032 case MOZ_GTK_CHECKBUTTON:
1033 style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
1034 MOZ_GTK_CHECKBUTTON_CONTAINER);
1035 break;
1036 case MOZ_GTK_RADIOMENUITEM_INDICATOR:
1037 style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, MOZ_GTK_RADIOMENUITEM);
1038 break;
1039 case MOZ_GTK_CHECKMENUITEM_INDICATOR:
1040 style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, MOZ_GTK_CHECKMENUITEM);
1041 break;
1042 case MOZ_GTK_PROGRESS_TROUGH:
1043 /* Progress bar background (trough) */
1044 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, MOZ_GTK_PROGRESSBAR);
1045 break;
1046 case MOZ_GTK_PROGRESS_CHUNK:
1047 style = CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH);
1048 break;
1049 case MOZ_GTK_GRIPPER:
1050 // TODO - create from CSS node
1051 style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1052 break;
1053 case MOZ_GTK_SPINBUTTON_ENTRY:
1054 // TODO - create from CSS node
1055 style =
1056 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1057 break;
1058 case MOZ_GTK_SCROLLED_WINDOW:
1059 // TODO - create from CSS node
1060 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1061 GTK_STYLE_CLASS_FRAME);
1062 break;
1063 case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION:
1064 style = CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT);
1065 break;
1066 case MOZ_GTK_TEXT_VIEW_TEXT:
1067 case MOZ_GTK_RESIZER:
1068 style = CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW);
1069 if (aNodeType == MOZ_GTK_RESIZER) {
1070 // The "grip" class provides the correct builtin icon from
1071 // gtk_render_handle(). The icon is drawn with shaded variants of
1072 // the background color, and so a transparent background would lead to
1073 // a transparent resizer. gtk_render_handle() also uses the
1074 // background color to draw a background, and so this style otherwise
1075 // matches what is used in GtkTextView to match the background with
1076 // textarea elements.
1077 GdkRGBA color;
1078 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
1079 &color);
1080 if (color.alpha == 0.0) {
1081 g_object_unref(style);
1082 style = CreateStyleForWidget(gtk_text_view_new(),
1083 MOZ_GTK_SCROLLED_WINDOW);
1084 }
1085 gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1086 }
1087 break;
1088 case MOZ_GTK_FRAME_BORDER:
1089 style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
1090 break;
1091 case MOZ_GTK_TREEVIEW_VIEW:
1092 // TODO - create from CSS node
1093 style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1094 break;
1095 case MOZ_GTK_TREEVIEW_EXPANDER:
1096 // TODO - create from CSS node
1097 style =
1098 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1099 break;
1100 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1101 style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL);
1102 break;
1103 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1104 style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL);
1105 break;
1106 case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
1107 style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL);
1108 break;
1109 case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
1110 style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL);
1111 break;
1112 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1113 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1114 MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
1115 break;
1116 case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1117 style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
1118 MOZ_GTK_SCALE_CONTENTS_VERTICAL);
1119 break;
1120 case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1121 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1122 MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
1123 break;
1124 case MOZ_GTK_SCALE_THUMB_VERTICAL:
1125 style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
1126 MOZ_GTK_SCALE_TROUGH_VERTICAL);
1127 break;
1128 case MOZ_GTK_TAB_TOP: {
1129 // TODO - create from CSS node
1130 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1131 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1132 static_cast<GtkRegionFlags>(0));
1133 break;
1134 }
1135 case MOZ_GTK_TAB_BOTTOM: {
1136 // TODO - create from CSS node
1137 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1138 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1139 static_cast<GtkRegionFlags>(0));
1140 break;
1141 }
1142 case MOZ_GTK_NOTEBOOK:
1143 case MOZ_GTK_NOTEBOOK_HEADER:
1144 case MOZ_GTK_TABPANELS:
1145 case MOZ_GTK_TAB_SCROLLARROW: {
1146 // TODO - create from CSS node
1147 GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1148 return gtk_widget_get_style_context(widget);
1149 }
1150 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE: {
1151 NS_ASSERTION(
1152 false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!");
1153 return nullptr;
1154 }
1155 case MOZ_GTK_WINDOW_DECORATION: {
1156 GtkStyleContext* parentStyle =
1157 CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd");
1158 style = CreateCSSNode("decoration", parentStyle);
1159 g_object_unref(parentStyle);
1160 break;
1161 }
1162 case MOZ_GTK_WINDOW_DECORATION_SOLID: {
1163 GtkStyleContext* parentStyle =
1164 CreateSubStyleWithClass(MOZ_GTK_WINDOW, "solid-csd");
1165 style = CreateCSSNode("decoration", parentStyle);
1166 g_object_unref(parentStyle);
1167 break;
1168 }
1169 default:
1170 return GetWidgetRootStyle(aNodeType);
1171 }
1172
1173 MOZ_ASSERT(style, "missing style context for node type");
1174 sStyleStorage[aNodeType] = style;
1175 return style;
1176 }
1177
1178 /* GetWidgetStyleInternal is used by Gtk < 3.20 */
GetWidgetStyleInternal(WidgetNodeType aNodeType)1179 static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) {
1180 GtkStyleContext* style = sStyleStorage[aNodeType];
1181 if (style) return style;
1182
1183 switch (aNodeType) {
1184 case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
1185 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
1186 GTK_STYLE_CLASS_TROUGH);
1187 break;
1188 case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
1189 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
1190 GTK_STYLE_CLASS_SLIDER);
1191 break;
1192 case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
1193 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1194 GTK_STYLE_CLASS_TROUGH);
1195 break;
1196 case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
1197 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
1198 GTK_STYLE_CLASS_SLIDER);
1199 break;
1200 case MOZ_GTK_RADIOBUTTON:
1201 style = CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
1202 GTK_STYLE_CLASS_RADIO);
1203 break;
1204 case MOZ_GTK_CHECKBUTTON:
1205 style = CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
1206 GTK_STYLE_CLASS_CHECK);
1207 break;
1208 case MOZ_GTK_RADIOMENUITEM_INDICATOR:
1209 style =
1210 CreateSubStyleWithClass(MOZ_GTK_RADIOMENUITEM, GTK_STYLE_CLASS_RADIO);
1211 break;
1212 case MOZ_GTK_CHECKMENUITEM_INDICATOR:
1213 style =
1214 CreateSubStyleWithClass(MOZ_GTK_CHECKMENUITEM, GTK_STYLE_CLASS_CHECK);
1215 break;
1216 case MOZ_GTK_PROGRESS_TROUGH:
1217 style =
1218 CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR, GTK_STYLE_CLASS_TROUGH);
1219 break;
1220 case MOZ_GTK_PROGRESS_CHUNK:
1221 style = CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR,
1222 GTK_STYLE_CLASS_PROGRESSBAR);
1223 gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
1224 break;
1225 case MOZ_GTK_GRIPPER:
1226 style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
1227 break;
1228 case MOZ_GTK_SPINBUTTON_ENTRY:
1229 style =
1230 CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
1231 break;
1232 case MOZ_GTK_SCROLLED_WINDOW:
1233 style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
1234 GTK_STYLE_CLASS_FRAME);
1235 break;
1236 case MOZ_GTK_TEXT_VIEW_TEXT:
1237 case MOZ_GTK_RESIZER:
1238 // GTK versions prior to 3.20 do not have the view class on the root
1239 // node, but add this to determine the background for the text window.
1240 style = CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW, GTK_STYLE_CLASS_VIEW);
1241 if (aNodeType == MOZ_GTK_RESIZER) {
1242 // The "grip" class provides the correct builtin icon from
1243 // gtk_render_handle(). The icon is drawn with shaded variants of
1244 // the background color, and so a transparent background would lead to
1245 // a transparent resizer. gtk_render_handle() also uses the
1246 // background color to draw a background, and so this style otherwise
1247 // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
1248 // textarea elements. GtkTextView creates a separate text window and
1249 // so the background should not be transparent.
1250 gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
1251 }
1252 break;
1253 case MOZ_GTK_FRAME_BORDER:
1254 return GetWidgetRootStyle(MOZ_GTK_FRAME);
1255 case MOZ_GTK_TREEVIEW_VIEW:
1256 style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
1257 break;
1258 case MOZ_GTK_TREEVIEW_EXPANDER:
1259 style =
1260 CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
1261 break;
1262 case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
1263 style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
1264 GTK_STYLE_CLASS_PANE_SEPARATOR);
1265 break;
1266 case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
1267 style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
1268 GTK_STYLE_CLASS_PANE_SEPARATOR);
1269 break;
1270 case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
1271 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1272 GTK_STYLE_CLASS_TROUGH);
1273 break;
1274 case MOZ_GTK_SCALE_TROUGH_VERTICAL:
1275 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1276 GTK_STYLE_CLASS_TROUGH);
1277 break;
1278 case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
1279 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
1280 GTK_STYLE_CLASS_SLIDER);
1281 break;
1282 case MOZ_GTK_SCALE_THUMB_VERTICAL:
1283 style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
1284 GTK_STYLE_CLASS_SLIDER);
1285 break;
1286 case MOZ_GTK_TAB_TOP:
1287 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
1288 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1289 static_cast<GtkRegionFlags>(0));
1290 break;
1291 case MOZ_GTK_TAB_BOTTOM:
1292 style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
1293 gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
1294 static_cast<GtkRegionFlags>(0));
1295 break;
1296 case MOZ_GTK_NOTEBOOK:
1297 case MOZ_GTK_NOTEBOOK_HEADER:
1298 case MOZ_GTK_TABPANELS:
1299 case MOZ_GTK_TAB_SCROLLARROW: {
1300 GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
1301 return gtk_widget_get_style_context(widget);
1302 }
1303 default:
1304 return GetWidgetRootStyle(aNodeType);
1305 }
1306
1307 MOZ_ASSERT(style);
1308 sStyleStorage[aNodeType] = style;
1309 return style;
1310 }
1311
ResetWidgetCache(void)1312 void ResetWidgetCache(void) {
1313 for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
1314 if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
1315 }
1316 mozilla::PodArrayZero(sStyleStorage);
1317
1318 /* This will destroy all of our widgets */
1319 if (sWidgetStorage[MOZ_GTK_WINDOW]) {
1320 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
1321 }
1322 if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]) {
1323 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]);
1324 }
1325 if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]) {
1326 gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]);
1327 }
1328
1329 /* Clear already freed arrays */
1330 mozilla::PodArrayZero(sWidgetStorage);
1331 }
1332
GetStyleContext(WidgetNodeType aNodeType,int aScale,GtkTextDirection aDirection,GtkStateFlags aStateFlags)1333 GtkStyleContext* GetStyleContext(WidgetNodeType aNodeType, int aScale,
1334 GtkTextDirection aDirection,
1335 GtkStateFlags aStateFlags) {
1336 GtkStyleContext* style;
1337 if (gtk_check_version(3, 20, 0) != nullptr) {
1338 style = GetWidgetStyleInternal(aNodeType);
1339 } else {
1340 style = GetCssNodeStyleInternal(aNodeType);
1341 StyleContextSetScale(style, aScale);
1342 }
1343 bool stateChanged = false;
1344 bool stateHasDirection = gtk_get_minor_version() >= 8;
1345 GtkStateFlags oldState = gtk_style_context_get_state(style);
1346 MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL)));
1347 unsigned newState = aStateFlags;
1348 if (stateHasDirection) {
1349 switch (aDirection) {
1350 case GTK_TEXT_DIR_LTR:
1351 newState |= STATE_FLAG_DIR_LTR;
1352 break;
1353 case GTK_TEXT_DIR_RTL:
1354 newState |= STATE_FLAG_DIR_RTL;
1355 break;
1356 default:
1357 MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
1358 case GTK_TEXT_DIR_NONE:
1359 // GtkWidget uses a default direction if neither is explicitly
1360 // specified, but here DIR_NONE is interpreted as meaning the
1361 // direction is not important, so don't change the direction
1362 // unnecessarily.
1363 newState |= oldState & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL);
1364 }
1365 } else if (aDirection != GTK_TEXT_DIR_NONE) {
1366 GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
1367 if (aDirection != oldDirection) {
1368 gtk_style_context_set_direction(style, aDirection);
1369 stateChanged = true;
1370 }
1371 }
1372 if (oldState != newState) {
1373 gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
1374 stateChanged = true;
1375 }
1376 // This invalidate is necessary for unsaved style contexts from GtkWidgets
1377 // in pre-3.18 GTK, because automatic invalidation of such contexts
1378 // was delayed until a resize event runs.
1379 //
1380 // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
1381 //
1382 // Avoid calling invalidate on contexts that are not owned and constructed
1383 // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
1384 // unnecessarily early.
1385 if (stateChanged && sWidgetStorage[aNodeType]) {
1386 gtk_style_context_invalidate(style);
1387 }
1388 return style;
1389 }
1390
CreateStyleContextWithStates(WidgetNodeType aNodeType,int aScale,GtkTextDirection aDirection,GtkStateFlags aStateFlags)1391 GtkStyleContext* CreateStyleContextWithStates(WidgetNodeType aNodeType,
1392 int aScale,
1393 GtkTextDirection aDirection,
1394 GtkStateFlags aStateFlags) {
1395 GtkStyleContext* style =
1396 GetStyleContext(aNodeType, aScale, aDirection, aStateFlags);
1397 GtkWidgetPath* path = gtk_widget_path_copy(gtk_style_context_get_path(style));
1398
1399 static auto sGtkWidgetPathIterGetState =
1400 (GtkStateFlags(*)(const GtkWidgetPath*, gint))dlsym(
1401 RTLD_DEFAULT, "gtk_widget_path_iter_get_state");
1402 static auto sGtkWidgetPathIterSetState =
1403 (void (*)(GtkWidgetPath*, gint, GtkStateFlags))dlsym(
1404 RTLD_DEFAULT, "gtk_widget_path_iter_set_state");
1405
1406 int pathLength = gtk_widget_path_length(path);
1407 for (int i = 0; i < pathLength; i++) {
1408 unsigned state = aStateFlags | sGtkWidgetPathIterGetState(path, i);
1409 sGtkWidgetPathIterSetState(path, i, GtkStateFlags(state));
1410 }
1411
1412 style = gtk_style_context_new();
1413 gtk_style_context_set_path(style, path);
1414 gtk_widget_path_unref(path);
1415
1416 return style;
1417 }
1418
StyleContextSetScale(GtkStyleContext * style,gint aScaleFactor)1419 void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor) {
1420 // Support HiDPI styles on Gtk 3.20+
1421 static auto sGtkStyleContextSetScalePtr =
1422 (void (*)(GtkStyleContext*, gint))dlsym(RTLD_DEFAULT,
1423 "gtk_style_context_set_scale");
1424 if (sGtkStyleContextSetScalePtr && style) {
1425 sGtkStyleContextSetScalePtr(style, aScaleFactor);
1426 }
1427 }
1428