1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsNativeThemeGTK.h"
7 #include "nsThemeConstants.h"
8 #include "gtkdrawing.h"
9 #include "ScreenHelperGTK.h"
10
11 #include "gfx2DGlue.h"
12 #include "nsIObserverService.h"
13 #include "nsIServiceManager.h"
14 #include "nsIFrame.h"
15 #include "nsIPresShell.h"
16 #include "nsIContent.h"
17 #include "nsViewManager.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsGfxCIID.h"
20 #include "nsTransform2D.h"
21 #include "nsMenuFrame.h"
22 #include "prlink.h"
23 #include "nsGkAtoms.h"
24 #include "nsAttrValueInlines.h"
25
26 #include "mozilla/dom/HTMLInputElement.h"
27 #include "mozilla/EventStates.h"
28 #include "mozilla/Services.h"
29
30 #include <gdk/gdkprivate.h>
31 #include <gtk/gtk.h>
32 #include <gtk/gtkx.h>
33
34 #include "gfxContext.h"
35 #include "gfxPlatformGtk.h"
36 #include "gfxGdkNativeRenderer.h"
37 #include "mozilla/gfx/BorrowedContext.h"
38 #include "mozilla/gfx/HelpersCairo.h"
39 #include "mozilla/gfx/PathHelpers.h"
40 #include "mozilla/Preferences.h"
41
42 #ifdef MOZ_X11
43 #ifdef CAIRO_HAS_XLIB_SURFACE
44 #include "cairo-xlib.h"
45 #endif
46 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
47 #include "cairo-xlib-xrender.h"
48 #endif
49 #endif
50
51 #include <algorithm>
52 #include <dlfcn.h>
53
54 using namespace mozilla;
55 using namespace mozilla::gfx;
56 using namespace mozilla::widget;
57 using mozilla::dom::HTMLInputElement;
58
59 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
60 nsIObserver)
61
62 static int gLastGdkError;
63
64 // Return scale factor of the monitor where the window is located
65 // by the most part or layout.css.devPixelsPerPx pref if set to > 0.
GetMonitorScaleFactor(nsIFrame * aFrame)66 static inline gint GetMonitorScaleFactor(nsIFrame* aFrame) {
67 // When the layout.css.devPixelsPerPx is set the scale can be < 1,
68 // the real monitor scale cannot go under 1.
69 double scale = nsIWidget::DefaultScaleOverride();
70 if (scale <= 0) {
71 nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
72 if (rootWidget) {
73 // We need to use GetDefaultScale() despite it returns monitor scale
74 // factor multiplied by font scale factor because it is the only scale
75 // updated in nsPuppetWidget.
76 // Since we don't want to apply font scale factor for UI elements
77 // (because GTK does not do so) we need to remove that from returned
78 // value. The computed monitor scale factor needs to be rounded before
79 // casting to integer to avoid rounding errors which would lead to
80 // returning 0.
81 int monitorScale = int(round(rootWidget->GetDefaultScale().scale /
82 gfxPlatformGtk::GetFontScaleFactor()));
83 // Monitor scale can be negative if it has not been initialized in the
84 // puppet widget yet. We also make sure that we return positive value.
85 if (monitorScale < 1) {
86 return 1;
87 }
88 return monitorScale;
89 }
90 }
91 // Use monitor scaling factor where devPixelsPerPx is set
92 return ScreenHelperGTK::GetGTKMonitorScaleFactor();
93 }
94
nsNativeThemeGTK()95 nsNativeThemeGTK::nsNativeThemeGTK() {
96 if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
97 memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
98 return;
99 }
100
101 // We have to call moz_gtk_shutdown before the event loop stops running.
102 nsCOMPtr<nsIObserverService> obsServ =
103 mozilla::services::GetObserverService();
104 obsServ->AddObserver(this, "xpcom-shutdown", false);
105
106 ThemeChanged();
107 }
108
~nsNativeThemeGTK()109 nsNativeThemeGTK::~nsNativeThemeGTK() {}
110
111 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)112 nsNativeThemeGTK::Observe(nsISupports* aSubject, const char* aTopic,
113 const char16_t* aData) {
114 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
115 moz_gtk_shutdown();
116 } else {
117 NS_NOTREACHED("unexpected topic");
118 return NS_ERROR_UNEXPECTED;
119 }
120
121 return NS_OK;
122 }
123
RefreshWidgetWindow(nsIFrame * aFrame)124 void nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) {
125 nsIPresShell* shell = GetPresShell(aFrame);
126 if (!shell) return;
127
128 nsViewManager* vm = shell->GetViewManager();
129 if (!vm) return;
130
131 vm->InvalidateAllViews();
132 }
133
IsFrameContentNodeInNamespace(nsIFrame * aFrame,uint32_t aNamespace)134 static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
135 uint32_t aNamespace) {
136 nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
137 if (!content) return false;
138 return content->IsInNamespace(aNamespace);
139 }
140
IsWidgetTypeDisabled(uint8_t * aDisabledVector,uint8_t aWidgetType)141 static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector,
142 uint8_t aWidgetType) {
143 MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
144 return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
145 }
146
SetWidgetTypeDisabled(uint8_t * aDisabledVector,uint8_t aWidgetType)147 static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
148 uint8_t aWidgetType) {
149 MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
150 aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
151 }
152
GetWidgetStateKey(uint8_t aWidgetType,GtkWidgetState * aWidgetState)153 static inline uint16_t GetWidgetStateKey(uint8_t aWidgetType,
154 GtkWidgetState* aWidgetState) {
155 return (aWidgetState->active | aWidgetState->focused << 1 |
156 aWidgetState->inHover << 2 | aWidgetState->disabled << 3 |
157 aWidgetState->isDefault << 4 | aWidgetType << 5);
158 }
159
IsWidgetStateSafe(uint8_t * aSafeVector,uint8_t aWidgetType,GtkWidgetState * aWidgetState)160 static bool IsWidgetStateSafe(uint8_t* aSafeVector, uint8_t aWidgetType,
161 GtkWidgetState* aWidgetState) {
162 MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
163 uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
164 return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
165 }
166
SetWidgetStateSafe(uint8_t * aSafeVector,uint8_t aWidgetType,GtkWidgetState * aWidgetState)167 static void SetWidgetStateSafe(uint8_t* aSafeVector, uint8_t aWidgetType,
168 GtkWidgetState* aWidgetState) {
169 MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
170 uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
171 aSafeVector[key >> 3] |= (1 << (key & 7));
172 }
173
GetTextDirection(nsIFrame * aFrame)174 /* static */ GtkTextDirection nsNativeThemeGTK::GetTextDirection(
175 nsIFrame* aFrame) {
176 // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
177 // horizontal text with direction=RTL), rather than just considering the
178 // text direction. GtkTextDirection does not have distinct values for
179 // vertical writing modes, but considering the block flow direction is
180 // important for resizers and scrollbar elements, at least.
181 return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
182 }
183
184 // Returns positive for negative margins (otherwise 0).
GetTabMarginPixels(nsIFrame * aFrame)185 gint nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) {
186 nscoord margin = IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
187 : aFrame->GetUsedMargin().bottom;
188
189 return std::min<gint>(
190 MOZ_GTK_TAB_MARGIN_MASK,
191 std::max(0, aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
192 }
193
ShouldScrollbarButtonBeDisabled(int32_t aCurpos,int32_t aMaxpos,uint8_t aWidgetType)194 static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
195 uint8_t aWidgetType) {
196 return (
197 (aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
198 aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT)) ||
199 (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
200 aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)));
201 }
202
GetGtkWidgetAndState(uint8_t aWidgetType,nsIFrame * aFrame,WidgetNodeType & aGtkWidgetType,GtkWidgetState * aState,gint * aWidgetFlags)203 bool nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType,
204 nsIFrame* aFrame,
205 WidgetNodeType& aGtkWidgetType,
206 GtkWidgetState* aState,
207 gint* aWidgetFlags) {
208 if (aState) {
209 // For XUL checkboxes and radio buttons, the state of the parent
210 // determines our state.
211 nsIFrame* stateFrame = aFrame;
212 if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
213 aWidgetType == NS_THEME_RADIO)) ||
214 aWidgetType == NS_THEME_CHECKBOX_LABEL ||
215 aWidgetType == NS_THEME_RADIO_LABEL)) {
216 nsAtom* atom = nullptr;
217 if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
218 if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
219 aWidgetType == NS_THEME_RADIO_LABEL) {
220 // Adjust stateFrame so GetContentState finds the correct state.
221 stateFrame = aFrame = aFrame->GetParent()->GetParent();
222 } else {
223 // GetContentState knows to look one frame up for radio/checkbox
224 // widgets, so don't adjust stateFrame here.
225 aFrame = aFrame->GetParent();
226 }
227 if (aWidgetFlags) {
228 if (!atom) {
229 atom = (aWidgetType == NS_THEME_CHECKBOX ||
230 aWidgetType == NS_THEME_CHECKBOX_LABEL)
231 ? nsGkAtoms::checked
232 : nsGkAtoms::selected;
233 }
234 *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
235 }
236 } else {
237 if (aWidgetFlags) {
238 *aWidgetFlags = 0;
239 HTMLInputElement* inputElt =
240 HTMLInputElement::FromContent(aFrame->GetContent());
241 if (inputElt && inputElt->Checked())
242 *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
243
244 if (GetIndeterminate(aFrame))
245 *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
246 }
247 }
248 } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
249 aWidgetType == NS_THEME_TREEHEADERSORTARROW ||
250 aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS ||
251 aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
252 aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
253 aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) {
254 // The state of an arrow comes from its parent.
255 stateFrame = aFrame = aFrame->GetParent();
256 }
257
258 EventStates eventState = GetContentState(stateFrame, aWidgetType);
259
260 aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
261 aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
262 aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
263 aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
264 aState->isDefault = IsDefaultButton(aFrame);
265 aState->canDefault = FALSE; // XXX fix me
266 aState->depressed = FALSE;
267
268 if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
269 aState->disabled = FALSE;
270 aState->active = FALSE;
271 aState->inHover = FALSE;
272 aState->isDefault = FALSE;
273 aState->canDefault = FALSE;
274
275 aState->focused = TRUE;
276 aState->depressed = TRUE; // see moz_gtk_entry_paint()
277 } else if (aWidgetType == NS_THEME_BUTTON ||
278 aWidgetType == NS_THEME_TOOLBARBUTTON ||
279 aWidgetType == NS_THEME_DUALBUTTON ||
280 aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
281 aWidgetType == NS_THEME_MENULIST ||
282 aWidgetType == NS_THEME_MENULIST_BUTTON) {
283 aState->active &= aState->inHover;
284 }
285
286 if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
287 // For these widget types, some element (either a child or parent)
288 // actually has element focus, so we check the focused attribute
289 // to see whether to draw in the focused state.
290 if (aWidgetType == NS_THEME_NUMBER_INPUT ||
291 aWidgetType == NS_THEME_TEXTFIELD ||
292 aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
293 aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
294 aWidgetType == NS_THEME_SPINNER_TEXTFIELD ||
295 aWidgetType == NS_THEME_RADIO_CONTAINER ||
296 aWidgetType == NS_THEME_RADIO_LABEL) {
297 aState->focused = IsFocused(aFrame);
298 } else if (aWidgetType == NS_THEME_RADIO ||
299 aWidgetType == NS_THEME_CHECKBOX) {
300 // In XUL, checkboxes and radios shouldn't have focus rings, their
301 // labels do
302 aState->focused = FALSE;
303 }
304
305 if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
306 aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) {
307 // for scrollbars we need to go up two to go from the thumb to
308 // the slider to the actual scrollbar object
309 nsIFrame* tmpFrame = aFrame->GetParent()->GetParent();
310
311 aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
312 aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
313
314 if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
315 aState->active = TRUE;
316 // Set hover state to emulate Gtk style of active scrollbar thumb
317 aState->inHover = TRUE;
318 }
319 }
320
321 if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
322 aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
323 aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
324 aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
325 // set the state to disabled when the scrollbar is scrolled to
326 // the beginning or the end, depending on the button type.
327 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
328 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
329 if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType)) {
330 aState->disabled = true;
331 }
332
333 // In order to simulate native GTK scrollbar click behavior,
334 // we set the active attribute on the element to true if it's
335 // pressed with any mouse button.
336 // This allows us to show that it's active without setting :active
337 else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
338 aState->active = true;
339
340 if (aWidgetFlags) {
341 *aWidgetFlags = GetScrollbarButtonType(aFrame);
342 if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2)
343 *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
344 }
345 }
346
347 // menu item state is determined by the attribute "_moz-menuactive",
348 // and not by the mouse hovering (accessibility). as a special case,
349 // menus which are children of a menu bar are only marked as prelight
350 // if they are open, not on normal hover.
351
352 if (aWidgetType == NS_THEME_MENUITEM ||
353 aWidgetType == NS_THEME_CHECKMENUITEM ||
354 aWidgetType == NS_THEME_RADIOMENUITEM ||
355 aWidgetType == NS_THEME_MENUSEPARATOR ||
356 aWidgetType == NS_THEME_MENUARROW) {
357 bool isTopLevel = false;
358 nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
359 if (menuFrame) {
360 isTopLevel = menuFrame->IsOnMenuBar();
361 }
362
363 if (isTopLevel) {
364 aState->inHover = menuFrame->IsOpen();
365 } else {
366 aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
367 }
368
369 aState->active = FALSE;
370
371 if (aWidgetType == NS_THEME_CHECKMENUITEM ||
372 aWidgetType == NS_THEME_RADIOMENUITEM) {
373 *aWidgetFlags = 0;
374 if (aFrame && aFrame->GetContent() &&
375 aFrame->GetContent()->IsElement()) {
376 *aWidgetFlags = aFrame->GetContent()->AsElement()->AttrValueIs(
377 kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true,
378 eIgnoreCase);
379 }
380 }
381 }
382
383 // A button with drop down menu open or an activated toggle button
384 // should always appear depressed.
385 if (aWidgetType == NS_THEME_BUTTON ||
386 aWidgetType == NS_THEME_TOOLBARBUTTON ||
387 aWidgetType == NS_THEME_DUALBUTTON ||
388 aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
389 aWidgetType == NS_THEME_MENULIST ||
390 aWidgetType == NS_THEME_MENULIST_BUTTON) {
391 bool menuOpen = IsOpenButton(aFrame);
392 aState->depressed = IsCheckedButton(aFrame) || menuOpen;
393 // we must not highlight buttons with open drop down menus on hover.
394 aState->inHover = aState->inHover && !menuOpen;
395 }
396
397 // When the input field of the drop down button has focus, some themes
398 // should draw focus for the drop down button as well.
399 if (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) {
400 *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
401 }
402 }
403 }
404
405 switch (aWidgetType) {
406 case NS_THEME_BUTTON:
407 if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL;
408 aGtkWidgetType = MOZ_GTK_BUTTON;
409 break;
410 case NS_THEME_TOOLBARBUTTON:
411 case NS_THEME_DUALBUTTON:
412 if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE;
413 aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
414 break;
415 case NS_THEME_FOCUS_OUTLINE:
416 aGtkWidgetType = MOZ_GTK_ENTRY;
417 break;
418 case NS_THEME_CHECKBOX:
419 case NS_THEME_RADIO:
420 aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON
421 : MOZ_GTK_CHECKBUTTON;
422 break;
423 case NS_THEME_SCROLLBARBUTTON_UP:
424 case NS_THEME_SCROLLBARBUTTON_DOWN:
425 case NS_THEME_SCROLLBARBUTTON_LEFT:
426 case NS_THEME_SCROLLBARBUTTON_RIGHT:
427 aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
428 break;
429 case NS_THEME_SCROLLBAR_VERTICAL:
430 aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
431 if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
432 *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
433 else
434 *aWidgetFlags = 0;
435 break;
436 case NS_THEME_SCROLLBAR_HORIZONTAL:
437 aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
438 if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
439 *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
440 else
441 *aWidgetFlags = 0;
442 break;
443 case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
444 aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
445 break;
446 case NS_THEME_SCROLLBARTRACK_VERTICAL:
447 aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
448 break;
449 case NS_THEME_SCROLLBARTHUMB_VERTICAL:
450 aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
451 break;
452 case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
453 aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
454 break;
455 case NS_THEME_INNER_SPIN_BUTTON:
456 aGtkWidgetType = MOZ_GTK_INNER_SPIN_BUTTON;
457 break;
458 case NS_THEME_SPINNER:
459 aGtkWidgetType = MOZ_GTK_SPINBUTTON;
460 break;
461 case NS_THEME_SPINNER_UPBUTTON:
462 aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
463 break;
464 case NS_THEME_SPINNER_DOWNBUTTON:
465 aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
466 break;
467 case NS_THEME_SPINNER_TEXTFIELD:
468 aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
469 break;
470 case NS_THEME_RANGE: {
471 if (IsRangeHorizontal(aFrame)) {
472 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
473 aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
474 } else {
475 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
476 aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
477 }
478 break;
479 }
480 case NS_THEME_RANGE_THUMB: {
481 if (IsRangeHorizontal(aFrame)) {
482 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
483 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
484 } else {
485 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
486 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
487 }
488 break;
489 }
490 case NS_THEME_SCALE_HORIZONTAL:
491 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
492 aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
493 break;
494 case NS_THEME_SCALETHUMB_HORIZONTAL:
495 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
496 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
497 break;
498 case NS_THEME_SCALE_VERTICAL:
499 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
500 aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
501 break;
502 case NS_THEME_SEPARATOR:
503 aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
504 break;
505 case NS_THEME_SCALETHUMB_VERTICAL:
506 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
507 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
508 break;
509 case NS_THEME_TOOLBARGRIPPER:
510 aGtkWidgetType = MOZ_GTK_GRIPPER;
511 break;
512 case NS_THEME_RESIZER:
513 aGtkWidgetType = MOZ_GTK_RESIZER;
514 break;
515 case NS_THEME_NUMBER_INPUT:
516 case NS_THEME_TEXTFIELD:
517 aGtkWidgetType = MOZ_GTK_ENTRY;
518 break;
519 case NS_THEME_TEXTFIELD_MULTILINE:
520 #ifdef MOZ_WIDGET_GTK
521 aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
522 #else
523 aGtkWidgetType = MOZ_GTK_ENTRY;
524 #endif
525 break;
526 case NS_THEME_LISTBOX:
527 case NS_THEME_TREEVIEW:
528 aGtkWidgetType = MOZ_GTK_TREEVIEW;
529 break;
530 case NS_THEME_TREEHEADERCELL:
531 if (aWidgetFlags) {
532 // In this case, the flag denotes whether the header is the sorted one
533 // or not
534 if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
535 *aWidgetFlags = false;
536 else
537 *aWidgetFlags = true;
538 }
539 aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
540 break;
541 case NS_THEME_TREEHEADERSORTARROW:
542 if (aWidgetFlags) {
543 switch (GetTreeSortDirection(aFrame)) {
544 case eTreeSortDirection_Ascending:
545 *aWidgetFlags = GTK_ARROW_DOWN;
546 break;
547 case eTreeSortDirection_Descending:
548 *aWidgetFlags = GTK_ARROW_UP;
549 break;
550 case eTreeSortDirection_Natural:
551 default:
552 /* This prevents the treecolums from getting smaller
553 * and wider when switching sort direction off and on
554 * */
555 *aWidgetFlags = GTK_ARROW_NONE;
556 break;
557 }
558 }
559 aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
560 break;
561 case NS_THEME_TREETWISTY:
562 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
563 if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
564 break;
565 case NS_THEME_TREETWISTYOPEN:
566 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
567 if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED;
568 break;
569 case NS_THEME_MENULIST:
570 aGtkWidgetType = MOZ_GTK_DROPDOWN;
571 if (aWidgetFlags)
572 *aWidgetFlags =
573 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
574 break;
575 case NS_THEME_MENULIST_TEXT:
576 return false; // nothing to do, but prevents the bg from being drawn
577 case NS_THEME_MENULIST_TEXTFIELD:
578 aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
579 break;
580 case NS_THEME_MENULIST_BUTTON:
581 aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
582 break;
583 case NS_THEME_TOOLBARBUTTON_DROPDOWN:
584 case NS_THEME_BUTTON_ARROW_DOWN:
585 case NS_THEME_BUTTON_ARROW_UP:
586 case NS_THEME_BUTTON_ARROW_NEXT:
587 case NS_THEME_BUTTON_ARROW_PREVIOUS:
588 aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
589 if (aWidgetFlags) {
590 *aWidgetFlags = GTK_ARROW_DOWN;
591
592 if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
593 *aWidgetFlags = GTK_ARROW_UP;
594 else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
595 *aWidgetFlags = GTK_ARROW_RIGHT;
596 else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
597 *aWidgetFlags = GTK_ARROW_LEFT;
598 }
599 break;
600 case NS_THEME_CHECKBOX_CONTAINER:
601 aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
602 break;
603 case NS_THEME_RADIO_CONTAINER:
604 aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
605 break;
606 case NS_THEME_CHECKBOX_LABEL:
607 aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
608 break;
609 case NS_THEME_RADIO_LABEL:
610 aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
611 break;
612 case NS_THEME_TOOLBAR:
613 aGtkWidgetType = MOZ_GTK_TOOLBAR;
614 break;
615 case NS_THEME_TOOLTIP:
616 aGtkWidgetType = MOZ_GTK_TOOLTIP;
617 break;
618 case NS_THEME_STATUSBARPANEL:
619 case NS_THEME_RESIZERPANEL:
620 aGtkWidgetType = MOZ_GTK_FRAME;
621 break;
622 case NS_THEME_PROGRESSBAR:
623 case NS_THEME_PROGRESSBAR_VERTICAL:
624 aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
625 break;
626 case NS_THEME_PROGRESSCHUNK:
627 case NS_THEME_PROGRESSCHUNK_VERTICAL: {
628 nsIFrame* stateFrame = aFrame->GetParent();
629 EventStates eventStates = GetContentState(stateFrame, aWidgetType);
630
631 aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
632 ? IsVerticalProgress(stateFrame)
633 ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
634 : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
635 : MOZ_GTK_PROGRESS_CHUNK;
636 } break;
637 case NS_THEME_TAB_SCROLL_ARROW_BACK:
638 case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
639 if (aWidgetFlags)
640 *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK
641 ? GTK_ARROW_LEFT
642 : GTK_ARROW_RIGHT;
643 aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
644 break;
645 case NS_THEME_TABPANELS:
646 aGtkWidgetType = MOZ_GTK_TABPANELS;
647 break;
648 case NS_THEME_TAB: {
649 if (IsBottomTab(aFrame)) {
650 aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
651 } else {
652 aGtkWidgetType = MOZ_GTK_TAB_TOP;
653 }
654
655 if (aWidgetFlags) {
656 /* First bits will be used to store max(0,-bmargin) where bmargin
657 * is the bottom margin of the tab in pixels (resp. top margin,
658 * for bottom tabs). */
659 *aWidgetFlags = GetTabMarginPixels(aFrame);
660
661 if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
662
663 if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
664 }
665 } break;
666 case NS_THEME_SPLITTER:
667 if (IsHorizontal(aFrame))
668 aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
669 else
670 aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
671 break;
672 case NS_THEME_MENUBAR:
673 aGtkWidgetType = MOZ_GTK_MENUBAR;
674 break;
675 case NS_THEME_MENUPOPUP:
676 aGtkWidgetType = MOZ_GTK_MENUPOPUP;
677 break;
678 case NS_THEME_MENUITEM: {
679 nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
680 if (menuFrame && menuFrame->IsOnMenuBar()) {
681 aGtkWidgetType = MOZ_GTK_MENUBARITEM;
682 break;
683 }
684 }
685 aGtkWidgetType = MOZ_GTK_MENUITEM;
686 break;
687 case NS_THEME_MENUSEPARATOR:
688 aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
689 break;
690 case NS_THEME_MENUARROW:
691 aGtkWidgetType = MOZ_GTK_MENUARROW;
692 break;
693 case NS_THEME_CHECKMENUITEM:
694 aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
695 break;
696 case NS_THEME_RADIOMENUITEM:
697 aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
698 break;
699 case NS_THEME_WINDOW:
700 case NS_THEME_DIALOG:
701 aGtkWidgetType = MOZ_GTK_WINDOW;
702 break;
703 case NS_THEME_GTK_INFO_BAR:
704 aGtkWidgetType = MOZ_GTK_INFO_BAR;
705 break;
706 case NS_THEME_WINDOW_TITLEBAR:
707 aGtkWidgetType = MOZ_GTK_HEADER_BAR;
708 break;
709 case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
710 aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
711 break;
712 case NS_THEME_WINDOW_BUTTON_CLOSE:
713 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
714 break;
715 case NS_THEME_WINDOW_BUTTON_MINIMIZE:
716 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
717 break;
718 case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
719 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
720 break;
721 case NS_THEME_WINDOW_BUTTON_RESTORE:
722 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
723 break;
724 default:
725 return false;
726 }
727
728 return true;
729 }
730
731 class SystemCairoClipper : public ClipExporter {
732 public:
SystemCairoClipper(cairo_t * aContext)733 explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext) {}
734
BeginClip(const Matrix & aTransform)735 void BeginClip(const Matrix& aTransform) override {
736 cairo_matrix_t mat;
737 GfxMatrixToCairoMatrix(aTransform, mat);
738 cairo_set_matrix(mContext, &mat);
739
740 cairo_new_path(mContext);
741 }
742
MoveTo(const Point & aPoint)743 void MoveTo(const Point& aPoint) override {
744 cairo_move_to(mContext, aPoint.x, aPoint.y);
745 mCurrentPoint = aPoint;
746 }
747
LineTo(const Point & aPoint)748 void LineTo(const Point& aPoint) override {
749 cairo_line_to(mContext, aPoint.x, aPoint.y);
750 mCurrentPoint = aPoint;
751 }
752
BezierTo(const Point & aCP1,const Point & aCP2,const Point & aCP3)753 void BezierTo(const Point& aCP1, const Point& aCP2,
754 const Point& aCP3) override {
755 cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
756 mCurrentPoint = aCP3;
757 }
758
QuadraticBezierTo(const Point & aCP1,const Point & aCP2)759 void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override {
760 Point CP0 = CurrentPoint();
761 Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
762 Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
763 Point CP3 = aCP2;
764 cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
765 mCurrentPoint = aCP2;
766 }
767
Arc(const Point & aOrigin,float aRadius,float aStartAngle,float aEndAngle,bool aAntiClockwise)768 void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
769 float aEndAngle, bool aAntiClockwise) override {
770 ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
771 aAntiClockwise);
772 }
773
Close()774 void Close() override { cairo_close_path(mContext); }
775
EndClip()776 void EndClip() override { cairo_clip(mContext); }
777
CurrentPoint() const778 Point CurrentPoint() const override { return mCurrentPoint; }
779
780 private:
781 cairo_t* mContext;
782 Point mCurrentPoint;
783 };
784
DrawThemeWithCairo(gfxContext * aContext,DrawTarget * aDrawTarget,GtkWidgetState aState,WidgetNodeType aGTKWidgetType,gint aFlags,GtkTextDirection aDirection,gint aScaleFactor,bool aSnapped,const Point & aDrawOrigin,const nsIntSize & aDrawSize,GdkRectangle & aGDKRect,nsITheme::Transparency aTransparency)785 static void DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
786 GtkWidgetState aState,
787 WidgetNodeType aGTKWidgetType, gint aFlags,
788 GtkTextDirection aDirection, gint aScaleFactor,
789 bool aSnapped, const Point& aDrawOrigin,
790 const nsIntSize& aDrawSize,
791 GdkRectangle& aGDKRect,
792 nsITheme::Transparency aTransparency) {
793 Point drawOffset;
794 Matrix transform;
795 if (!aSnapped) {
796 // If we are not snapped, we depend on the DT for translation.
797 drawOffset = aDrawOrigin;
798 transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
799 } else {
800 // Otherwise, we only need to take the device offset into account.
801 drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
802 transform = Matrix::Translation(drawOffset);
803 }
804
805 if (aScaleFactor != 1) transform.PreScale(aScaleFactor, aScaleFactor);
806
807 cairo_matrix_t mat;
808 GfxMatrixToCairoMatrix(transform, mat);
809
810 nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
811 (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
812
813 #ifndef MOZ_TREE_CAIRO
814 // Directly use the Cairo draw target to render the widget if using system
815 // Cairo everywhere.
816 BorrowedCairoContext borrowCairo(aDrawTarget);
817 if (borrowCairo.mCairo) {
818 cairo_set_matrix(borrowCairo.mCairo, &mat);
819
820 cairo_new_path(borrowCairo.mCairo);
821 cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height);
822 cairo_clip(borrowCairo.mCairo);
823
824 moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState,
825 aFlags, aDirection);
826
827 borrowCairo.Finish();
828 return;
829 }
830 #endif
831
832 // A direct Cairo draw target is not available, so we need to create a
833 // temporary one.
834 #if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
835 // If using a Cairo xlib surface, then try to reuse it.
836 BorrowedXlibDrawable borrow(aDrawTarget);
837 if (borrow.GetDrawable()) {
838 nsIntSize size = borrow.GetSize();
839 cairo_surface_t* surf = nullptr;
840 // Check if the surface is using XRender.
841 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
842 if (borrow.GetXRenderFormat()) {
843 surf = cairo_xlib_surface_create_with_xrender_format(
844 borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
845 borrow.GetXRenderFormat(), size.width, size.height);
846 } else {
847 #else
848 if (!borrow.GetXRenderFormat()) {
849 #endif
850 surf = cairo_xlib_surface_create(borrow.GetDisplay(),
851 borrow.GetDrawable(), borrow.GetVisual(),
852 size.width, size.height);
853 }
854 if (!NS_WARN_IF(!surf)) {
855 Point offset = borrow.GetOffset();
856 if (offset != Point()) {
857 cairo_surface_set_device_offset(surf, offset.x, offset.y);
858 }
859 cairo_t* cr = cairo_create(surf);
860 if (!NS_WARN_IF(!cr)) {
861 RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
862 aContext->ExportClip(*clipper);
863
864 cairo_set_matrix(cr, &mat);
865
866 cairo_new_path(cr);
867 cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
868 cairo_clip(cr);
869
870 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
871 aDirection);
872
873 cairo_destroy(cr);
874 }
875 cairo_surface_destroy(surf);
876 }
877 borrow.Finish();
878 return;
879 }
880 #endif
881
882 // Check if the widget requires complex masking that must be composited.
883 // Try to directly write to the draw target's pixels if possible.
884 uint8_t* data;
885 nsIntSize size;
886 int32_t stride;
887 SurfaceFormat format;
888 IntPoint origin;
889 if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
890 // Create a Cairo image surface context the device rectangle.
891 cairo_surface_t* surf = cairo_image_surface_create_for_data(
892 data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
893 if (!NS_WARN_IF(!surf)) {
894 if (origin != IntPoint()) {
895 cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
896 }
897 cairo_t* cr = cairo_create(surf);
898 if (!NS_WARN_IF(!cr)) {
899 RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
900 aContext->ExportClip(*clipper);
901
902 cairo_set_matrix(cr, &mat);
903
904 cairo_new_path(cr);
905 cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
906 cairo_clip(cr);
907
908 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
909 aDirection);
910
911 cairo_destroy(cr);
912 }
913 cairo_surface_destroy(surf);
914 }
915 aDrawTarget->ReleaseBits(data);
916 } else {
917 // If the widget has any transparency, make sure to choose an alpha format.
918 format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8
919 : aDrawTarget->GetFormat();
920 // Create a temporary data surface to render the widget into.
921 RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
922 aDrawSize, format, aTransparency != nsITheme::eOpaque);
923 DataSourceSurface::MappedSurface map;
924 if (!NS_WARN_IF(
925 !(dataSurface &&
926 dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
927 // Create a Cairo image surface wrapping the data surface.
928 cairo_surface_t* surf = cairo_image_surface_create_for_data(
929 map.mData, GfxFormatToCairoFormat(format), aDrawSize.width,
930 aDrawSize.height, map.mStride);
931 cairo_t* cr = nullptr;
932 if (!NS_WARN_IF(!surf)) {
933 cr = cairo_create(surf);
934 if (!NS_WARN_IF(!cr)) {
935 if (aScaleFactor != 1) {
936 cairo_scale(cr, aScaleFactor, aScaleFactor);
937 }
938
939 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
940 aDirection);
941 }
942 }
943
944 // Unmap the surface before using it as a source
945 dataSurface->Unmap();
946
947 if (cr) {
948 // The widget either needs to be masked or has transparency, so use the
949 // slower drawing path.
950 aDrawTarget->DrawSurface(
951 dataSurface,
952 Rect(aSnapped
953 ? drawOffset - aDrawTarget->GetTransform().GetTranslation()
954 : drawOffset,
955 Size(aDrawSize)),
956 Rect(0, 0, aDrawSize.width, aDrawSize.height));
957 cairo_destroy(cr);
958 }
959
960 if (surf) {
961 cairo_surface_destroy(surf);
962 }
963 }
964 }
965 }
966
967 bool nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame,
968 uint8_t aWidgetType,
969 nsIntMargin* aExtra) {
970 *aExtra = nsIntMargin(0, 0, 0, 0);
971 // Allow an extra one pixel above and below the thumb for certain
972 // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
973 // We modify the frame's overflow area. See bug 297508.
974 switch (aWidgetType) {
975 case NS_THEME_SCROLLBARTHUMB_VERTICAL:
976 aExtra->top = aExtra->bottom = 1;
977 break;
978 case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
979 aExtra->left = aExtra->right = 1;
980 break;
981
982 case NS_THEME_BUTTON: {
983 if (IsDefaultButton(aFrame)) {
984 // Some themes draw a default indicator outside the widget,
985 // include that in overflow
986 gint top, left, bottom, right;
987 moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
988 aExtra->top = top;
989 aExtra->right = right;
990 aExtra->bottom = bottom;
991 aExtra->left = left;
992 break;
993 }
994 return false;
995 }
996 case NS_THEME_FOCUS_OUTLINE: {
997 moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
998 aExtra->right = aExtra->left;
999 aExtra->bottom = aExtra->top;
1000 break;
1001 }
1002 case NS_THEME_TAB: {
1003 if (!IsSelectedTab(aFrame)) return false;
1004
1005 gint gap_height = moz_gtk_get_tab_thickness(
1006 IsBottomTab(aFrame) ? MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
1007 if (!gap_height) return false;
1008
1009 int32_t extra = gap_height - GetTabMarginPixels(aFrame);
1010 if (extra <= 0) return false;
1011
1012 if (IsBottomTab(aFrame)) {
1013 aExtra->top = extra;
1014 } else {
1015 aExtra->bottom = extra;
1016 }
1017 return false;
1018 }
1019 default:
1020 return false;
1021 }
1022 gint scale = GetMonitorScaleFactor(aFrame);
1023 aExtra->top *= scale;
1024 aExtra->right *= scale;
1025 aExtra->bottom *= scale;
1026 aExtra->left *= scale;
1027 return true;
1028 }
1029
1030 NS_IMETHODIMP
1031 nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1032 uint8_t aWidgetType, const nsRect& aRect,
1033 const nsRect& aDirtyRect) {
1034 GtkWidgetState state;
1035 WidgetNodeType gtkWidgetType;
1036 GtkTextDirection direction = GetTextDirection(aFrame);
1037 gint flags;
1038 if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state, &flags))
1039 return NS_OK;
1040
1041 gfxContext* ctx = aContext;
1042 nsPresContext* presContext = aFrame->PresContext();
1043
1044 gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
1045 gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
1046 gint scaleFactor = GetMonitorScaleFactor(aFrame);
1047
1048 // Align to device pixels where sensible
1049 // to provide crisper and faster drawing.
1050 // Don't snap if it's a non-unit scale factor. We're going to have to take
1051 // slow paths then in any case.
1052 bool snapped = ctx->UserToDevicePixelSnapped(rect);
1053 if (snapped) {
1054 // Leave rect in device coords but make dirtyRect consistent.
1055 dirtyRect = ctx->UserToDevice(dirtyRect);
1056 }
1057
1058 // Translate the dirty rect so that it is wrt the widget top-left.
1059 dirtyRect.MoveBy(-rect.TopLeft());
1060 // Round out the dirty rect to gdk pixels to ensure that gtk draws
1061 // enough pixels for interpolation to device pixels.
1062 dirtyRect.RoundOut();
1063
1064 // GTK themes can only draw an integer number of pixels
1065 // (even when not snapped).
1066 nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
1067 nsIntRect overflowRect(widgetRect);
1068 nsIntMargin extraSize;
1069 if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) {
1070 overflowRect.Inflate(extraSize);
1071 }
1072
1073 // This is the rectangle that will actually be drawn, in gdk pixels
1074 nsIntRect drawingRect(int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
1075 int32_t(dirtyRect.Width()),
1076 int32_t(dirtyRect.Height()));
1077 if (widgetRect.IsEmpty() ||
1078 !drawingRect.IntersectRect(overflowRect, drawingRect))
1079 return NS_OK;
1080
1081 NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
1082 "Trying to render an unsafe widget!");
1083
1084 bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
1085 if (!safeState) {
1086 gLastGdkError = 0;
1087 gdk_error_trap_push();
1088 }
1089
1090 Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType);
1091
1092 // gdk rectangles are wrt the drawing rect.
1093 GdkRectangle gdk_rect = {
1094 -drawingRect.x / scaleFactor, -drawingRect.y / scaleFactor,
1095 widgetRect.width / scaleFactor, widgetRect.height / scaleFactor};
1096
1097 // Save actual widget scale to GtkWidgetState as we don't provide
1098 // nsFrame to gtk3drawing routines.
1099 state.scale = scaleFactor;
1100
1101 // translate everything so (0,0) is the top left of the drawingRect
1102 gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
1103
1104 DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
1105 flags, direction, scaleFactor, snapped, ToPoint(origin),
1106 drawingRect.Size(), gdk_rect, transparency);
1107
1108 if (!safeState) {
1109 // gdk_flush() call from expose event crashes Gtk+ on Wayland
1110 // (Gnome BZ #773307)
1111 if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
1112 gdk_flush();
1113 }
1114 gLastGdkError = gdk_error_trap_pop();
1115
1116 if (gLastGdkError) {
1117 #ifdef DEBUG
1118 printf(
1119 "GTK theme failed for widget type %d, error was %d, state was "
1120 "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
1121 aWidgetType, gLastGdkError, state.active, state.focused,
1122 state.inHover, state.disabled);
1123 #endif
1124 NS_WARNING("GTK theme failed; disabling unsafe widget");
1125 SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
1126 // force refresh of the window, because the widget was not
1127 // successfully drawn it must be redrawn using the default look
1128 RefreshWidgetWindow(aFrame);
1129 } else {
1130 SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
1131 }
1132 }
1133
1134 // Indeterminate progress bar are animated.
1135 if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
1136 gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
1137 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
1138 NS_WARNING("unable to animate widget!");
1139 }
1140 }
1141
1142 return NS_OK;
1143 }
1144
1145 bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
1146 mozilla::wr::DisplayListBuilder& aBuilder,
1147 mozilla::wr::IpcResourceUpdateQueue& aResources,
1148 const mozilla::layers::StackingContextHelper& aSc,
1149 mozilla::layers::WebRenderLayerManager* aManager, nsIFrame* aFrame,
1150 uint8_t aWidgetType, const nsRect& aRect) {
1151 nsPresContext* presContext = aFrame->PresContext();
1152 wr::LayoutRect bounds =
1153 aSc.ToRelativeLayoutRect(LayoutDeviceRect::FromAppUnits(
1154 aRect, presContext->AppUnitsPerDevPixel()));
1155
1156 switch (aWidgetType) {
1157 case NS_THEME_WINDOW:
1158 case NS_THEME_DIALOG:
1159 aBuilder.PushRect(
1160 bounds, bounds, true,
1161 wr::ToColorF(Color::FromABGR(LookAndFeel::GetColor(
1162 LookAndFeel::eColorID_WindowBackground, NS_RGBA(0, 0, 0, 0)))));
1163 return true;
1164
1165 default:
1166 return false;
1167 }
1168 }
1169
1170 WidgetNodeType nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType,
1171 nsIFrame* aFrame) {
1172 WidgetNodeType gtkWidgetType;
1173 gint unusedFlags;
1174
1175 if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1176 &unusedFlags)) {
1177 MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
1178 return MOZ_GTK_WINDOW;
1179 }
1180 return gtkWidgetType;
1181 }
1182
1183 void nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame,
1184 uint8_t aWidgetType,
1185 GtkTextDirection aDirection,
1186 nsIntMargin* aResult) {
1187 aResult->SizeTo(0, 0, 0, 0);
1188
1189 WidgetNodeType gtkWidgetType;
1190 gint unusedFlags;
1191 if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1192 &unusedFlags)) {
1193 MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
1194 uint8_t cacheIndex = gtkWidgetType / 8;
1195 uint8_t cacheBit = 1u << (gtkWidgetType % 8);
1196
1197 if (mBorderCacheValid[cacheIndex] & cacheBit) {
1198 *aResult = mBorderCache[gtkWidgetType];
1199 } else {
1200 moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
1201 &aResult->right, &aResult->bottom, aDirection);
1202 if (aWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection
1203 mBorderCacheValid[cacheIndex] |= cacheBit;
1204 mBorderCache[gtkWidgetType] = *aResult;
1205 }
1206 }
1207 }
1208 }
1209
1210 NS_IMETHODIMP
1211 nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
1212 uint8_t aWidgetType, nsIntMargin* aResult) {
1213 GtkTextDirection direction = GetTextDirection(aFrame);
1214 aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
1215 switch (aWidgetType) {
1216 case NS_THEME_SCROLLBAR_HORIZONTAL:
1217 case NS_THEME_SCROLLBAR_VERTICAL: {
1218 GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
1219 ? GTK_ORIENTATION_HORIZONTAL
1220 : GTK_ORIENTATION_VERTICAL;
1221 const ScrollbarGTKMetrics* metrics =
1222 GetScrollbarMetrics(orientation, true);
1223
1224 const GtkBorder& border = metrics->border.scrollbar;
1225 aResult->top = border.top;
1226 aResult->right = border.right;
1227 aResult->bottom = border.bottom;
1228 aResult->left = border.left;
1229 } break;
1230 case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
1231 case NS_THEME_SCROLLBARTRACK_VERTICAL: {
1232 GtkOrientation orientation =
1233 aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL
1234 ? GTK_ORIENTATION_HORIZONTAL
1235 : GTK_ORIENTATION_VERTICAL;
1236 const ScrollbarGTKMetrics* metrics =
1237 GetScrollbarMetrics(orientation, true);
1238
1239 const GtkBorder& border = metrics->border.track;
1240 aResult->top = border.top;
1241 aResult->right = border.right;
1242 aResult->bottom = border.bottom;
1243 aResult->left = border.left;
1244 } break;
1245 case NS_THEME_TOOLBOX:
1246 // gtk has no toolbox equivalent. So, although we map toolbox to
1247 // gtk's 'toolbar' for purposes of painting the widget background,
1248 // we don't use the toolbar border for toolbox.
1249 break;
1250 case NS_THEME_DUALBUTTON:
1251 // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
1252 // around the entire button + dropdown, and also an inner border if you're
1253 // over the button part. But, we want the inner button to be right up
1254 // against the edge of the outer button so that the borders overlap.
1255 // To make this happen, we draw a button border for the outer button,
1256 // but don't reserve any space for it.
1257 break;
1258 case NS_THEME_TAB: {
1259 WidgetNodeType gtkWidgetType;
1260 gint flags;
1261
1262 if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1263 &flags))
1264 return NS_OK;
1265
1266 moz_gtk_get_tab_border(&aResult->left, &aResult->top, &aResult->right,
1267 &aResult->bottom, direction, (GtkTabFlags)flags,
1268 gtkWidgetType);
1269 } break;
1270 case NS_THEME_MENUITEM:
1271 case NS_THEME_CHECKMENUITEM:
1272 case NS_THEME_RADIOMENUITEM:
1273 // For regular menuitems, we will be using GetWidgetPadding instead of
1274 // GetWidgetBorder to pad up the widget's internals; other menuitems
1275 // will need to fall through and use the default case as before.
1276 if (IsRegularMenuItem(aFrame)) break;
1277 MOZ_FALLTHROUGH;
1278 default: { GetCachedWidgetBorder(aFrame, aWidgetType, direction, aResult); }
1279 }
1280
1281 gint scale = GetMonitorScaleFactor(aFrame);
1282 aResult->top *= scale;
1283 aResult->right *= scale;
1284 aResult->bottom *= scale;
1285 aResult->left *= scale;
1286 return NS_OK;
1287 }
1288
1289 bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
1290 nsIFrame* aFrame, uint8_t aWidgetType,
1291 nsIntMargin* aResult) {
1292 switch (aWidgetType) {
1293 case NS_THEME_BUTTON_FOCUS:
1294 case NS_THEME_TOOLBARBUTTON:
1295 case NS_THEME_WINDOW_BUTTON_CLOSE:
1296 case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1297 case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1298 case NS_THEME_WINDOW_BUTTON_RESTORE:
1299 case NS_THEME_DUALBUTTON:
1300 case NS_THEME_TAB_SCROLL_ARROW_BACK:
1301 case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
1302 case NS_THEME_MENULIST_BUTTON:
1303 case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1304 case NS_THEME_BUTTON_ARROW_UP:
1305 case NS_THEME_BUTTON_ARROW_DOWN:
1306 case NS_THEME_BUTTON_ARROW_NEXT:
1307 case NS_THEME_BUTTON_ARROW_PREVIOUS:
1308 case NS_THEME_RANGE_THUMB:
1309 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1310 // and have a meaningful baseline, so they can't have
1311 // author-specified padding.
1312 case NS_THEME_CHECKBOX:
1313 case NS_THEME_RADIO:
1314 aResult->SizeTo(0, 0, 0, 0);
1315 return true;
1316 case NS_THEME_MENUITEM:
1317 case NS_THEME_CHECKMENUITEM:
1318 case NS_THEME_RADIOMENUITEM: {
1319 // Menubar and menulist have their padding specified in CSS.
1320 if (!IsRegularMenuItem(aFrame)) return false;
1321
1322 GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
1323 aResult);
1324
1325 gint horizontal_padding;
1326
1327 if (aWidgetType == NS_THEME_MENUITEM)
1328 moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
1329 else
1330 moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
1331
1332 aResult->left += horizontal_padding;
1333 aResult->right += horizontal_padding;
1334
1335 gint scale = GetMonitorScaleFactor(aFrame);
1336 aResult->top *= scale;
1337 aResult->right *= scale;
1338 aResult->bottom *= scale;
1339 aResult->left *= scale;
1340
1341 return true;
1342 }
1343 }
1344
1345 return false;
1346 }
1347
1348 bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
1349 nsIFrame* aFrame, uint8_t aWidgetType,
1350 nsRect* aOverflowRect) {
1351 nsIntMargin extraSize;
1352 if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) return false;
1353
1354 int32_t p2a = aContext->AppUnitsPerDevPixel();
1355 nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
1356 NSIntPixelsToAppUnits(extraSize.right, p2a),
1357 NSIntPixelsToAppUnits(extraSize.bottom, p2a),
1358 NSIntPixelsToAppUnits(extraSize.left, p2a));
1359
1360 aOverflowRect->Inflate(m);
1361 return true;
1362 }
1363
1364 NS_IMETHODIMP
1365 nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
1366 nsIFrame* aFrame, uint8_t aWidgetType,
1367 LayoutDeviceIntSize* aResult,
1368 bool* aIsOverridable) {
1369 aResult->width = aResult->height = 0;
1370 *aIsOverridable = true;
1371
1372 switch (aWidgetType) {
1373 case NS_THEME_SCROLLBARBUTTON_UP:
1374 case NS_THEME_SCROLLBARBUTTON_DOWN: {
1375 const ScrollbarGTKMetrics* metrics =
1376 GetScrollbarMetrics(GTK_ORIENTATION_VERTICAL, true);
1377
1378 aResult->width = metrics->size.button.width;
1379 aResult->height = metrics->size.button.height;
1380 *aIsOverridable = false;
1381 } break;
1382 case NS_THEME_SCROLLBARBUTTON_LEFT:
1383 case NS_THEME_SCROLLBARBUTTON_RIGHT: {
1384 const ScrollbarGTKMetrics* metrics =
1385 GetScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL, true);
1386
1387 aResult->width = metrics->size.button.width;
1388 aResult->height = metrics->size.button.height;
1389 *aIsOverridable = false;
1390 } break;
1391 case NS_THEME_SPLITTER: {
1392 gint metrics;
1393 if (IsHorizontal(aFrame)) {
1394 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
1395 aResult->width = metrics;
1396 aResult->height = 0;
1397 } else {
1398 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
1399 aResult->width = 0;
1400 aResult->height = metrics;
1401 }
1402 *aIsOverridable = false;
1403 } break;
1404 case NS_THEME_SCROLLBAR_HORIZONTAL:
1405 case NS_THEME_SCROLLBAR_VERTICAL: {
1406 /* While we enforce a minimum size for the thumb, this is ignored
1407 * for the some scrollbars if buttons are hidden (bug 513006) because
1408 * the thumb isn't a direct child of the scrollbar, unlike the buttons
1409 * or track. So add a minimum size to the track as well to prevent a
1410 * 0-width scrollbar. */
1411 GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
1412 ? GTK_ORIENTATION_HORIZONTAL
1413 : GTK_ORIENTATION_VERTICAL;
1414 const ScrollbarGTKMetrics* metrics =
1415 GetScrollbarMetrics(orientation, true);
1416
1417 aResult->width = metrics->size.scrollbar.width;
1418 aResult->height = metrics->size.scrollbar.height;
1419 } break;
1420 case NS_THEME_SCROLLBARTHUMB_VERTICAL:
1421 case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
1422 GtkOrientation orientation =
1423 aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL
1424 ? GTK_ORIENTATION_HORIZONTAL
1425 : GTK_ORIENTATION_VERTICAL;
1426 const ScrollbarGTKMetrics* metrics =
1427 GetScrollbarMetrics(orientation, true);
1428
1429 aResult->width = metrics->size.thumb.width;
1430 aResult->height = metrics->size.thumb.height;
1431 *aIsOverridable = false;
1432 } break;
1433 case NS_THEME_RANGE_THUMB: {
1434 gint thumb_length, thumb_height;
1435
1436 if (IsRangeHorizontal(aFrame)) {
1437 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
1438 &thumb_length, &thumb_height);
1439 } else {
1440 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
1441 &thumb_length);
1442 }
1443 aResult->width = thumb_length;
1444 aResult->height = thumb_height;
1445
1446 *aIsOverridable = false;
1447 } break;
1448 case NS_THEME_RANGE: {
1449 gint scale_width, scale_height;
1450
1451 moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame)
1452 ? GTK_ORIENTATION_HORIZONTAL
1453 : GTK_ORIENTATION_VERTICAL,
1454 &scale_width, &scale_height);
1455 aResult->width = scale_width;
1456 aResult->height = scale_height;
1457
1458 *aIsOverridable = true;
1459 } break;
1460 case NS_THEME_SCALETHUMB_HORIZONTAL:
1461 case NS_THEME_SCALETHUMB_VERTICAL: {
1462 gint thumb_length, thumb_height;
1463
1464 if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) {
1465 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length,
1466 &thumb_height);
1467 aResult->width = thumb_height;
1468 aResult->height = thumb_length;
1469 } else {
1470 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
1471 &thumb_length, &thumb_height);
1472 aResult->width = thumb_length;
1473 aResult->height = thumb_height;
1474 }
1475
1476 *aIsOverridable = false;
1477 } break;
1478 case NS_THEME_TAB_SCROLL_ARROW_BACK:
1479 case NS_THEME_TAB_SCROLL_ARROW_FORWARD: {
1480 moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
1481 *aIsOverridable = false;
1482 } break;
1483 case NS_THEME_MENULIST_BUTTON: {
1484 moz_gtk_get_combo_box_entry_button_size(&aResult->width,
1485 &aResult->height);
1486 *aIsOverridable = false;
1487 } break;
1488 case NS_THEME_MENUSEPARATOR: {
1489 gint separator_height;
1490
1491 moz_gtk_get_menu_separator_height(&separator_height);
1492 aResult->height = separator_height;
1493
1494 *aIsOverridable = false;
1495 } break;
1496 case NS_THEME_CHECKBOX:
1497 case NS_THEME_RADIO: {
1498 const ToggleGTKMetrics* metrics =
1499 GetToggleMetrics(aWidgetType == NS_THEME_RADIO);
1500 aResult->width = metrics->minSizeWithBorder.width;
1501 aResult->height = metrics->minSizeWithBorder.height;
1502 } break;
1503 case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1504 case NS_THEME_BUTTON_ARROW_UP:
1505 case NS_THEME_BUTTON_ARROW_DOWN:
1506 case NS_THEME_BUTTON_ARROW_NEXT:
1507 case NS_THEME_BUTTON_ARROW_PREVIOUS: {
1508 moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &aResult->width,
1509 &aResult->height);
1510 *aIsOverridable = false;
1511 } break;
1512 case NS_THEME_WINDOW_BUTTON_CLOSE: {
1513 const ToolbarButtonGTKMetrics* metrics =
1514 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
1515 aResult->width = metrics->minSizeWithBorderMargin.width;
1516 aResult->height = metrics->minSizeWithBorderMargin.height;
1517 break;
1518 }
1519 case NS_THEME_WINDOW_BUTTON_MINIMIZE: {
1520 const ToolbarButtonGTKMetrics* metrics =
1521 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
1522 aResult->width = metrics->minSizeWithBorderMargin.width;
1523 aResult->height = metrics->minSizeWithBorderMargin.height;
1524 break;
1525 }
1526 case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1527 case NS_THEME_WINDOW_BUTTON_RESTORE: {
1528 const ToolbarButtonGTKMetrics* metrics =
1529 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
1530 aResult->width = metrics->minSizeWithBorderMargin.width;
1531 aResult->height = metrics->minSizeWithBorderMargin.height;
1532 break;
1533 }
1534 case NS_THEME_CHECKBOX_CONTAINER:
1535 case NS_THEME_RADIO_CONTAINER:
1536 case NS_THEME_CHECKBOX_LABEL:
1537 case NS_THEME_RADIO_LABEL:
1538 case NS_THEME_BUTTON:
1539 case NS_THEME_MENULIST:
1540 case NS_THEME_TOOLBARBUTTON:
1541 case NS_THEME_TREEHEADERCELL: {
1542 if (aWidgetType == NS_THEME_MENULIST) {
1543 // Include the arrow size.
1544 moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &aResult->width,
1545 &aResult->height);
1546 }
1547 // else the minimum size is missing consideration of container
1548 // descendants; the value returned here will not be helpful, but the
1549 // box model may consider border and padding with child minimum sizes.
1550
1551 nsIntMargin border;
1552 GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
1553 &border);
1554 aResult->width += border.left + border.right;
1555 aResult->height += border.top + border.bottom;
1556 } break;
1557 #ifdef MOZ_WIDGET_GTK
1558 case NS_THEME_NUMBER_INPUT:
1559 case NS_THEME_TEXTFIELD: {
1560 moz_gtk_get_entry_min_height(&aResult->height);
1561 } break;
1562 #endif
1563 case NS_THEME_SEPARATOR: {
1564 gint separator_width;
1565
1566 moz_gtk_get_toolbar_separator_width(&separator_width);
1567
1568 aResult->width = separator_width;
1569 } break;
1570 case NS_THEME_INNER_SPIN_BUTTON:
1571 case NS_THEME_SPINNER:
1572 // hard code these sizes
1573 aResult->width = 14;
1574 aResult->height = 26;
1575 break;
1576 case NS_THEME_TREEHEADERSORTARROW:
1577 case NS_THEME_SPINNER_UPBUTTON:
1578 case NS_THEME_SPINNER_DOWNBUTTON:
1579 // hard code these sizes
1580 aResult->width = 14;
1581 aResult->height = 13;
1582 break;
1583 case NS_THEME_RESIZER:
1584 // same as Windows to make our lives easier
1585 aResult->width = aResult->height = 15;
1586 *aIsOverridable = false;
1587 break;
1588 case NS_THEME_TREETWISTY:
1589 case NS_THEME_TREETWISTYOPEN: {
1590 gint expander_size;
1591
1592 moz_gtk_get_treeview_expander_size(&expander_size);
1593 aResult->width = aResult->height = expander_size;
1594 *aIsOverridable = false;
1595 } break;
1596 }
1597
1598 *aResult = *aResult * GetMonitorScaleFactor(aFrame);
1599
1600 return NS_OK;
1601 }
1602
1603 NS_IMETHODIMP
1604 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
1605 nsAtom* aAttribute, bool* aShouldRepaint,
1606 const nsAttrValue* aOldValue) {
1607 // Some widget types just never change state.
1608 if (aWidgetType == NS_THEME_TOOLBOX || aWidgetType == NS_THEME_TOOLBAR ||
1609 aWidgetType == NS_THEME_STATUSBAR ||
1610 aWidgetType == NS_THEME_STATUSBARPANEL ||
1611 aWidgetType == NS_THEME_RESIZERPANEL ||
1612 aWidgetType == NS_THEME_PROGRESSCHUNK ||
1613 aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
1614 aWidgetType == NS_THEME_PROGRESSBAR ||
1615 aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
1616 aWidgetType == NS_THEME_MENUBAR || aWidgetType == NS_THEME_MENUPOPUP ||
1617 aWidgetType == NS_THEME_TOOLTIP ||
1618 aWidgetType == NS_THEME_MENUSEPARATOR || aWidgetType == NS_THEME_WINDOW ||
1619 aWidgetType == NS_THEME_DIALOG) {
1620 *aShouldRepaint = false;
1621 return NS_OK;
1622 }
1623
1624 if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
1625 aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) &&
1626 aAttribute == nsGkAtoms::active) {
1627 *aShouldRepaint = true;
1628 return NS_OK;
1629 }
1630
1631 if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
1632 aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
1633 aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
1634 aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) &&
1635 (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos)) {
1636 // If 'curpos' has changed and we are passed its old value, we can
1637 // determine whether the button's enablement actually needs to change.
1638 if (aAttribute == nsGkAtoms::curpos && aOldValue) {
1639 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1640 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
1641 nsAutoString str;
1642 aOldValue->ToString(str);
1643 nsresult err;
1644 int32_t oldCurpos = str.ToInteger(&err);
1645 if (str.IsEmpty() || NS_FAILED(err)) {
1646 *aShouldRepaint = true;
1647 } else {
1648 bool disabledBefore =
1649 ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aWidgetType);
1650 bool disabledNow =
1651 ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType);
1652 *aShouldRepaint = (disabledBefore != disabledNow);
1653 }
1654 } else {
1655 *aShouldRepaint = true;
1656 }
1657 return NS_OK;
1658 }
1659
1660 // XXXdwh Not sure what can really be done here. Can at least guess for
1661 // specific widgets that they're highly unlikely to have certain states.
1662 // For example, a toolbar doesn't care about any states.
1663 if (!aAttribute) {
1664 // Hover/focus/active changed. Always repaint.
1665 *aShouldRepaint = true;
1666 } else {
1667 // Check the attribute to see if it's relevant.
1668 // disabled, checked, dlgtype, default, etc.
1669 *aShouldRepaint = false;
1670 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1671 aAttribute == nsGkAtoms::selected ||
1672 aAttribute == nsGkAtoms::visuallyselected ||
1673 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::readonly ||
1674 aAttribute == nsGkAtoms::_default ||
1675 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::open ||
1676 aAttribute == nsGkAtoms::parentfocused)
1677 *aShouldRepaint = true;
1678 }
1679
1680 return NS_OK;
1681 }
1682
1683 NS_IMETHODIMP
1684 nsNativeThemeGTK::ThemeChanged() {
1685 memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
1686 memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
1687 memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
1688 return NS_OK;
1689 }
1690
1691 NS_IMETHODIMP_(bool)
1692 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
1693 nsIFrame* aFrame, uint8_t aWidgetType) {
1694 if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType)) return false;
1695
1696 switch (aWidgetType) {
1697 // Combobox dropdowns don't support native theming in vertical mode.
1698 case NS_THEME_MENULIST:
1699 case NS_THEME_MENULIST_TEXT:
1700 case NS_THEME_MENULIST_TEXTFIELD:
1701 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
1702 return false;
1703 }
1704 MOZ_FALLTHROUGH;
1705
1706 case NS_THEME_BUTTON:
1707 case NS_THEME_BUTTON_FOCUS:
1708 case NS_THEME_RADIO:
1709 case NS_THEME_CHECKBOX:
1710 case NS_THEME_TOOLBOX: // N/A
1711 case NS_THEME_TOOLBAR:
1712 case NS_THEME_TOOLBARBUTTON:
1713 case NS_THEME_DUALBUTTON: // so we can override the border with 0
1714 case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1715 case NS_THEME_BUTTON_ARROW_UP:
1716 case NS_THEME_BUTTON_ARROW_DOWN:
1717 case NS_THEME_BUTTON_ARROW_NEXT:
1718 case NS_THEME_BUTTON_ARROW_PREVIOUS:
1719 case NS_THEME_SEPARATOR:
1720 case NS_THEME_TOOLBARGRIPPER:
1721 case NS_THEME_STATUSBAR:
1722 case NS_THEME_STATUSBARPANEL:
1723 case NS_THEME_RESIZERPANEL:
1724 case NS_THEME_RESIZER:
1725 case NS_THEME_LISTBOX:
1726 // case NS_THEME_LISTITEM:
1727 case NS_THEME_TREEVIEW:
1728 // case NS_THEME_TREEITEM:
1729 case NS_THEME_TREETWISTY:
1730 // case NS_THEME_TREELINE:
1731 // case NS_THEME_TREEHEADER:
1732 case NS_THEME_TREEHEADERCELL:
1733 case NS_THEME_TREEHEADERSORTARROW:
1734 case NS_THEME_TREETWISTYOPEN:
1735 case NS_THEME_PROGRESSBAR:
1736 case NS_THEME_PROGRESSCHUNK:
1737 case NS_THEME_PROGRESSBAR_VERTICAL:
1738 case NS_THEME_PROGRESSCHUNK_VERTICAL:
1739 case NS_THEME_TAB:
1740 // case NS_THEME_TABPANEL:
1741 case NS_THEME_TABPANELS:
1742 case NS_THEME_TAB_SCROLL_ARROW_BACK:
1743 case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
1744 case NS_THEME_TOOLTIP:
1745 case NS_THEME_INNER_SPIN_BUTTON:
1746 case NS_THEME_SPINNER:
1747 case NS_THEME_SPINNER_UPBUTTON:
1748 case NS_THEME_SPINNER_DOWNBUTTON:
1749 case NS_THEME_SPINNER_TEXTFIELD:
1750 // case NS_THEME_SCROLLBAR: (n/a for gtk)
1751 // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
1752 case NS_THEME_SCROLLBARBUTTON_UP:
1753 case NS_THEME_SCROLLBARBUTTON_DOWN:
1754 case NS_THEME_SCROLLBARBUTTON_LEFT:
1755 case NS_THEME_SCROLLBARBUTTON_RIGHT:
1756 case NS_THEME_SCROLLBAR_HORIZONTAL:
1757 case NS_THEME_SCROLLBAR_VERTICAL:
1758 case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
1759 case NS_THEME_SCROLLBARTRACK_VERTICAL:
1760 case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
1761 case NS_THEME_SCROLLBARTHUMB_VERTICAL:
1762 case NS_THEME_NUMBER_INPUT:
1763 case NS_THEME_TEXTFIELD:
1764 case NS_THEME_TEXTFIELD_MULTILINE:
1765 case NS_THEME_RANGE:
1766 case NS_THEME_RANGE_THUMB:
1767 case NS_THEME_SCALE_HORIZONTAL:
1768 case NS_THEME_SCALETHUMB_HORIZONTAL:
1769 case NS_THEME_SCALE_VERTICAL:
1770 case NS_THEME_SCALETHUMB_VERTICAL:
1771 // case NS_THEME_SCALETHUMBSTART:
1772 // case NS_THEME_SCALETHUMBEND:
1773 // case NS_THEME_SCALETHUMBTICK:
1774 case NS_THEME_CHECKBOX_CONTAINER:
1775 case NS_THEME_RADIO_CONTAINER:
1776 case NS_THEME_CHECKBOX_LABEL:
1777 case NS_THEME_RADIO_LABEL:
1778 case NS_THEME_MENUBAR:
1779 case NS_THEME_MENUPOPUP:
1780 case NS_THEME_MENUITEM:
1781 case NS_THEME_MENUARROW:
1782 case NS_THEME_MENUSEPARATOR:
1783 case NS_THEME_CHECKMENUITEM:
1784 case NS_THEME_RADIOMENUITEM:
1785 case NS_THEME_SPLITTER:
1786 case NS_THEME_WINDOW:
1787 case NS_THEME_DIALOG:
1788 #ifdef MOZ_WIDGET_GTK
1789 case NS_THEME_GTK_INFO_BAR:
1790 #endif
1791 return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1792
1793 case NS_THEME_WINDOW_BUTTON_CLOSE:
1794 case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1795 case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1796 case NS_THEME_WINDOW_BUTTON_RESTORE:
1797 case NS_THEME_WINDOW_TITLEBAR:
1798 case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
1799 // GtkHeaderBar is available on GTK 3.10+, which is used for styling
1800 // title bars and title buttons.
1801 return gtk_check_version(3, 10, 0) == nullptr &&
1802 !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1803
1804 case NS_THEME_MENULIST_BUTTON:
1805 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
1806 return false;
1807 }
1808 // "Native" dropdown buttons cause padding and margin problems, but only
1809 // in HTML so allow them in XUL.
1810 return (!aFrame ||
1811 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
1812 !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1813
1814 case NS_THEME_FOCUS_OUTLINE:
1815 return true;
1816 }
1817
1818 return false;
1819 }
1820
1821 NS_IMETHODIMP_(bool)
1822 nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType) {
1823 // XXXdwh At some point flesh all of this out.
1824 if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
1825 aWidgetType == NS_THEME_RADIO || aWidgetType == NS_THEME_RANGE_THUMB ||
1826 aWidgetType == NS_THEME_CHECKBOX ||
1827 aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ||
1828 aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD ||
1829 aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
1830 aWidgetType == NS_THEME_BUTTON_ARROW_DOWN ||
1831 aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
1832 aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
1833 return false;
1834 return true;
1835 }
1836
1837 bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType) {
1838 if (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_BUTTON ||
1839 aWidgetType == NS_THEME_TREEHEADERCELL)
1840 return true;
1841
1842 return false;
1843 }
1844
1845 bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
1846
1847 nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
1848 nsIFrame* aFrame, uint8_t aWidgetType) {
1849 switch (aWidgetType) {
1850 // These widgets always draw a default background.
1851 case NS_THEME_MENUPOPUP:
1852 case NS_THEME_WINDOW:
1853 case NS_THEME_DIALOG:
1854 return eOpaque;
1855 case NS_THEME_SCROLLBAR_VERTICAL:
1856 case NS_THEME_SCROLLBAR_HORIZONTAL:
1857 #ifdef MOZ_WIDGET_GTK
1858 // Make scrollbar tracks opaque on the window's scroll frame to prevent
1859 // leaf layers from overlapping. See bug 1179780.
1860 if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
1861 aFrame->PresContext()->IsRootContentDocument() &&
1862 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)))
1863 return eTransparent;
1864 #endif
1865 return eOpaque;
1866 // Tooltips use gtk_paint_flat_box() on Gtk2
1867 // but are shaped on Gtk3
1868 case NS_THEME_TOOLTIP:
1869 return eTransparent;
1870 }
1871
1872 return eUnknownTransparency;
1873 }
1874