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