Bumping manifests a=b2g-bump
[gecko.git] / widget / gtk / nsNativeThemeGTK.cpp
blob969fb0a762394621a140eedc158eec8ed33ac5eb
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/. */
6 #include "nsNativeThemeGTK.h"
7 #include "nsThemeConstants.h"
8 #include "gtkdrawing.h"
10 #include "nsIObserverService.h"
11 #include "nsIServiceManager.h"
12 #include "nsIFrame.h"
13 #include "nsIPresShell.h"
14 #include "nsIContent.h"
15 #include "nsViewManager.h"
16 #include "nsNameSpaceManager.h"
17 #include "nsGfxCIID.h"
18 #include "nsTransform2D.h"
19 #include "nsMenuFrame.h"
20 #include "prlink.h"
21 #include "nsIDOMHTMLInputElement.h"
22 #include "nsRenderingContext.h"
23 #include "nsGkAtoms.h"
25 #include "mozilla/EventStates.h"
26 #include "mozilla/Services.h"
28 #include <gdk/gdkprivate.h>
29 #include <gtk/gtk.h>
31 #include "gfxContext.h"
32 #include "gfxPlatformGtk.h"
33 #include "gfxGdkNativeRenderer.h"
34 #include <algorithm>
36 using namespace mozilla;
38 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
39 nsIObserver)
41 static int gLastGdkError;
43 nsNativeThemeGTK::nsNativeThemeGTK()
45 if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
46 memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
47 return;
50 // We have to call moz_gtk_shutdown before the event loop stops running.
51 nsCOMPtr<nsIObserverService> obsServ =
52 mozilla::services::GetObserverService();
53 obsServ->AddObserver(this, "xpcom-shutdown", false);
55 memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
56 memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
59 nsNativeThemeGTK::~nsNativeThemeGTK() {
62 NS_IMETHODIMP
63 nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
64 const char16_t *aData)
66 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
67 moz_gtk_shutdown();
68 } else {
69 NS_NOTREACHED("unexpected topic");
70 return NS_ERROR_UNEXPECTED;
73 return NS_OK;
76 void
77 nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
79 nsIPresShell *shell = GetPresShell(aFrame);
80 if (!shell)
81 return;
83 nsViewManager* vm = shell->GetViewManager();
84 if (!vm)
85 return;
87 vm->InvalidateAllViews();
90 static bool IsFrameContentNodeInNamespace(nsIFrame *aFrame, uint32_t aNamespace)
92 nsIContent *content = aFrame ? aFrame->GetContent() : nullptr;
93 if (!content)
94 return false;
95 return content->IsInNamespace(aNamespace);
98 static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
99 return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
102 static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
103 aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
106 static inline uint16_t
107 GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState)
109 return (aWidgetState->active |
110 aWidgetState->focused << 1 |
111 aWidgetState->inHover << 2 |
112 aWidgetState->disabled << 3 |
113 aWidgetState->isDefault << 4 |
114 aWidgetType << 5);
117 static bool IsWidgetStateSafe(uint8_t* aSafeVector,
118 uint8_t aWidgetType,
119 GtkWidgetState *aWidgetState)
121 uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
122 return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
125 static void SetWidgetStateSafe(uint8_t *aSafeVector,
126 uint8_t aWidgetType,
127 GtkWidgetState *aWidgetState)
129 uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
130 aSafeVector[key >> 3] |= (1 << (key & 7));
133 static GtkTextDirection GetTextDirection(nsIFrame* aFrame)
135 if (!aFrame)
136 return GTK_TEXT_DIR_NONE;
138 switch (aFrame->StyleVisibility()->mDirection) {
139 case NS_STYLE_DIRECTION_RTL:
140 return GTK_TEXT_DIR_RTL;
141 case NS_STYLE_DIRECTION_LTR:
142 return GTK_TEXT_DIR_LTR;
145 return GTK_TEXT_DIR_NONE;
148 // Returns positive for negative margins (otherwise 0).
149 gint
150 nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame)
152 nscoord margin =
153 IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
154 : aFrame->GetUsedMargin().bottom;
156 return std::min<gint>(MOZ_GTK_TAB_MARGIN_MASK,
157 std::max(0,
158 aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
161 bool
162 nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
163 GtkThemeWidgetType& aGtkWidgetType,
164 GtkWidgetState* aState,
165 gint* aWidgetFlags)
167 if (aState) {
168 if (!aFrame) {
169 // reset the entire struct to zero
170 memset(aState, 0, sizeof(GtkWidgetState));
171 } else {
173 // For XUL checkboxes and radio buttons, the state of the parent
174 // determines our state.
175 nsIFrame *stateFrame = aFrame;
176 if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
177 aWidgetType == NS_THEME_RADIO)) ||
178 aWidgetType == NS_THEME_CHECKBOX_LABEL ||
179 aWidgetType == NS_THEME_RADIO_LABEL)) {
181 nsIAtom* atom = nullptr;
182 if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
183 if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
184 aWidgetType == NS_THEME_RADIO_LABEL) {
185 // Adjust stateFrame so GetContentState finds the correct state.
186 stateFrame = aFrame = aFrame->GetParent()->GetParent();
187 } else {
188 // GetContentState knows to look one frame up for radio/checkbox
189 // widgets, so don't adjust stateFrame here.
190 aFrame = aFrame->GetParent();
192 if (aWidgetFlags) {
193 if (!atom) {
194 atom = (aWidgetType == NS_THEME_CHECKBOX ||
195 aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked
196 : nsGkAtoms::selected;
198 *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
200 } else {
201 if (aWidgetFlags) {
202 nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(aFrame->GetContent()));
203 *aWidgetFlags = 0;
204 if (inputElt) {
205 bool isHTMLChecked;
206 inputElt->GetChecked(&isHTMLChecked);
207 if (isHTMLChecked)
208 *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
211 if (GetIndeterminate(aFrame))
212 *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
215 } else if (aWidgetType == NS_THEME_TOOLBAR_BUTTON_DROPDOWN ||
216 aWidgetType == NS_THEME_TREEVIEW_HEADER_SORTARROW ||
217 aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS ||
218 aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
219 aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
220 aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) {
221 // The state of an arrow comes from its parent.
222 stateFrame = aFrame = aFrame->GetParent();
225 EventStates eventState = GetContentState(stateFrame, aWidgetType);
227 aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
228 aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
229 aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
230 aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
231 aState->isDefault = IsDefaultButton(aFrame);
232 aState->canDefault = FALSE; // XXX fix me
233 aState->depressed = FALSE;
235 if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
236 aState->disabled = FALSE;
237 aState->active = FALSE;
238 aState->inHover = FALSE;
239 aState->isDefault = FALSE;
240 aState->canDefault = FALSE;
242 aState->focused = TRUE;
243 aState->depressed = TRUE; // see moz_gtk_entry_paint()
246 if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
247 // For these widget types, some element (either a child or parent)
248 // actually has element focus, so we check the focused attribute
249 // to see whether to draw in the focused state.
250 if (aWidgetType == NS_THEME_NUMBER_INPUT ||
251 aWidgetType == NS_THEME_TEXTFIELD ||
252 aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
253 aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
254 aWidgetType == NS_THEME_SPINNER_TEXTFIELD ||
255 aWidgetType == NS_THEME_RADIO_CONTAINER ||
256 aWidgetType == NS_THEME_RADIO_LABEL) {
257 aState->focused = IsFocused(aFrame);
258 } else if (aWidgetType == NS_THEME_RADIO ||
259 aWidgetType == NS_THEME_CHECKBOX) {
260 // In XUL, checkboxes and radios shouldn't have focus rings, their labels do
261 aState->focused = FALSE;
264 if (aWidgetType == NS_THEME_SCROLLBAR_THUMB_VERTICAL ||
265 aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL) {
266 // for scrollbars we need to go up two to go from the thumb to
267 // the slider to the actual scrollbar object
268 nsIFrame *tmpFrame = aFrame->GetParent()->GetParent();
270 aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
271 aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
273 if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
274 aState->active = TRUE;
278 if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_UP ||
279 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_DOWN ||
280 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT ||
281 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT) {
282 // set the state to disabled when the scrollbar is scrolled to
283 // the beginning or the end, depending on the button type.
284 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
285 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
286 if ((curpos == 0 && (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_UP ||
287 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT)) ||
288 (curpos == maxpos &&
289 (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_DOWN ||
290 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)))
291 aState->disabled = true;
293 // In order to simulate native GTK scrollbar click behavior,
294 // we set the active attribute on the element to true if it's
295 // pressed with any mouse button.
296 // This allows us to show that it's active without setting :active
297 else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
298 aState->active = true;
300 if (aWidgetFlags) {
301 *aWidgetFlags = GetScrollbarButtonType(aFrame);
302 if (aWidgetType - NS_THEME_SCROLLBAR_BUTTON_UP < 2)
303 *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
307 // menu item state is determined by the attribute "_moz-menuactive",
308 // and not by the mouse hovering (accessibility). as a special case,
309 // menus which are children of a menu bar are only marked as prelight
310 // if they are open, not on normal hover.
312 if (aWidgetType == NS_THEME_MENUITEM ||
313 aWidgetType == NS_THEME_CHECKMENUITEM ||
314 aWidgetType == NS_THEME_RADIOMENUITEM ||
315 aWidgetType == NS_THEME_MENUSEPARATOR ||
316 aWidgetType == NS_THEME_MENUARROW) {
317 bool isTopLevel = false;
318 nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
319 if (menuFrame) {
320 isTopLevel = menuFrame->IsOnMenuBar();
323 if (isTopLevel) {
324 aState->inHover = menuFrame->IsOpen();
325 *aWidgetFlags |= MOZ_TOPLEVEL_MENU_ITEM;
326 } else {
327 aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
328 *aWidgetFlags &= ~MOZ_TOPLEVEL_MENU_ITEM;
331 aState->active = FALSE;
333 if (aWidgetType == NS_THEME_CHECKMENUITEM ||
334 aWidgetType == NS_THEME_RADIOMENUITEM) {
335 *aWidgetFlags = 0;
336 if (aFrame && aFrame->GetContent()) {
337 *aWidgetFlags = aFrame->GetContent()->
338 AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
339 nsGkAtoms::_true, eIgnoreCase);
344 // A button with drop down menu open or an activated toggle button
345 // should always appear depressed.
346 if (aWidgetType == NS_THEME_BUTTON ||
347 aWidgetType == NS_THEME_TOOLBAR_BUTTON ||
348 aWidgetType == NS_THEME_TOOLBAR_DUAL_BUTTON ||
349 aWidgetType == NS_THEME_TOOLBAR_BUTTON_DROPDOWN ||
350 aWidgetType == NS_THEME_DROPDOWN ||
351 aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
352 bool menuOpen = IsOpenButton(aFrame);
353 aState->depressed = IsCheckedButton(aFrame) || menuOpen;
354 // we must not highlight buttons with open drop down menus on hover.
355 aState->inHover = aState->inHover && !menuOpen;
358 // When the input field of the drop down button has focus, some themes
359 // should draw focus for the drop down button as well.
360 if (aWidgetType == NS_THEME_DROPDOWN_BUTTON && aWidgetFlags) {
361 *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
367 switch (aWidgetType) {
368 case NS_THEME_BUTTON:
369 case NS_THEME_TOOLBAR_BUTTON:
370 case NS_THEME_TOOLBAR_DUAL_BUTTON:
371 if (aWidgetFlags)
372 *aWidgetFlags = (aWidgetType == NS_THEME_BUTTON) ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE;
373 aGtkWidgetType = MOZ_GTK_BUTTON;
374 break;
375 case NS_THEME_FOCUS_OUTLINE:
376 aGtkWidgetType = MOZ_GTK_ENTRY;
377 break;
378 case NS_THEME_CHECKBOX:
379 case NS_THEME_RADIO:
380 aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON;
381 break;
382 case NS_THEME_SCROLLBAR_BUTTON_UP:
383 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
384 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
385 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
386 aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
387 break;
388 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
389 aGtkWidgetType = MOZ_GTK_SCROLLBAR_TRACK_VERTICAL;
390 break;
391 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
392 aGtkWidgetType = MOZ_GTK_SCROLLBAR_TRACK_HORIZONTAL;
393 break;
394 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
395 aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
396 break;
397 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
398 aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
399 break;
400 case NS_THEME_SPINNER:
401 aGtkWidgetType = MOZ_GTK_SPINBUTTON;
402 break;
403 case NS_THEME_SPINNER_UP_BUTTON:
404 aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
405 break;
406 case NS_THEME_SPINNER_DOWN_BUTTON:
407 aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
408 break;
409 case NS_THEME_SPINNER_TEXTFIELD:
410 aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
411 break;
412 case NS_THEME_RANGE:
414 if (IsRangeHorizontal(aFrame)) {
415 if (aWidgetFlags)
416 *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
417 aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
418 } else {
419 if (aWidgetFlags)
420 *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
421 aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
423 break;
425 case NS_THEME_RANGE_THUMB:
427 if (IsRangeHorizontal(aFrame)) {
428 if (aWidgetFlags)
429 *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
430 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
431 } else {
432 if (aWidgetFlags)
433 *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
434 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
436 break;
438 case NS_THEME_SCALE_HORIZONTAL:
439 if (aWidgetFlags)
440 *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
441 aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
442 break;
443 case NS_THEME_SCALE_THUMB_HORIZONTAL:
444 if (aWidgetFlags)
445 *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
446 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
447 break;
448 case NS_THEME_SCALE_VERTICAL:
449 if (aWidgetFlags)
450 *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
451 aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
452 break;
453 case NS_THEME_TOOLBAR_SEPARATOR:
454 aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
455 break;
456 case NS_THEME_SCALE_THUMB_VERTICAL:
457 if (aWidgetFlags)
458 *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
459 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
460 break;
461 case NS_THEME_TOOLBAR_GRIPPER:
462 aGtkWidgetType = MOZ_GTK_GRIPPER;
463 break;
464 case NS_THEME_RESIZER:
465 aGtkWidgetType = MOZ_GTK_RESIZER;
466 break;
467 case NS_THEME_NUMBER_INPUT:
468 case NS_THEME_TEXTFIELD:
469 case NS_THEME_TEXTFIELD_MULTILINE:
470 aGtkWidgetType = MOZ_GTK_ENTRY;
471 break;
472 case NS_THEME_LISTBOX:
473 case NS_THEME_TREEVIEW:
474 aGtkWidgetType = MOZ_GTK_TREEVIEW;
475 break;
476 case NS_THEME_TREEVIEW_HEADER_CELL:
477 if (aWidgetFlags) {
478 // In this case, the flag denotes whether the header is the sorted one or not
479 if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
480 *aWidgetFlags = false;
481 else
482 *aWidgetFlags = true;
484 aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
485 break;
486 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
487 if (aWidgetFlags) {
488 switch (GetTreeSortDirection(aFrame)) {
489 case eTreeSortDirection_Ascending:
490 *aWidgetFlags = GTK_ARROW_DOWN;
491 break;
492 case eTreeSortDirection_Descending:
493 *aWidgetFlags = GTK_ARROW_UP;
494 break;
495 case eTreeSortDirection_Natural:
496 default:
497 /* This prevents the treecolums from getting smaller
498 * and wider when switching sort direction off and on
499 * */
500 *aWidgetFlags = GTK_ARROW_NONE;
501 break;
504 aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
505 break;
506 case NS_THEME_TREEVIEW_TWISTY:
507 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
508 if (aWidgetFlags)
509 *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
510 break;
511 case NS_THEME_TREEVIEW_TWISTY_OPEN:
512 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
513 if (aWidgetFlags)
514 *aWidgetFlags = GTK_EXPANDER_EXPANDED;
515 break;
516 case NS_THEME_DROPDOWN:
517 aGtkWidgetType = MOZ_GTK_DROPDOWN;
518 if (aWidgetFlags)
519 *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
520 break;
521 case NS_THEME_DROPDOWN_TEXT:
522 return false; // nothing to do, but prevents the bg from being drawn
523 case NS_THEME_DROPDOWN_TEXTFIELD:
524 aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
525 break;
526 case NS_THEME_DROPDOWN_BUTTON:
527 aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
528 break;
529 case NS_THEME_TOOLBAR_BUTTON_DROPDOWN:
530 case NS_THEME_BUTTON_ARROW_DOWN:
531 case NS_THEME_BUTTON_ARROW_UP:
532 case NS_THEME_BUTTON_ARROW_NEXT:
533 case NS_THEME_BUTTON_ARROW_PREVIOUS:
534 aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
535 if (aWidgetFlags) {
536 *aWidgetFlags = GTK_ARROW_DOWN;
538 if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
539 *aWidgetFlags = GTK_ARROW_UP;
540 else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
541 *aWidgetFlags = GTK_ARROW_RIGHT;
542 else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
543 *aWidgetFlags = GTK_ARROW_LEFT;
545 break;
546 case NS_THEME_CHECKBOX_CONTAINER:
547 aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
548 break;
549 case NS_THEME_RADIO_CONTAINER:
550 aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
551 break;
552 case NS_THEME_CHECKBOX_LABEL:
553 aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
554 break;
555 case NS_THEME_RADIO_LABEL:
556 aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
557 break;
558 case NS_THEME_TOOLBAR:
559 aGtkWidgetType = MOZ_GTK_TOOLBAR;
560 break;
561 case NS_THEME_TOOLTIP:
562 aGtkWidgetType = MOZ_GTK_TOOLTIP;
563 break;
564 case NS_THEME_STATUSBAR_PANEL:
565 case NS_THEME_STATUSBAR_RESIZER_PANEL:
566 aGtkWidgetType = MOZ_GTK_FRAME;
567 break;
568 case NS_THEME_PROGRESSBAR:
569 case NS_THEME_PROGRESSBAR_VERTICAL:
570 aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
571 break;
572 case NS_THEME_PROGRESSBAR_CHUNK:
573 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
575 nsIFrame* stateFrame = aFrame->GetParent();
576 EventStates eventStates = GetContentState(stateFrame, aWidgetType);
578 aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
579 ? (stateFrame->StyleDisplay()->mOrient == NS_STYLE_ORIENT_VERTICAL)
580 ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
581 : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
582 : MOZ_GTK_PROGRESS_CHUNK;
584 break;
585 case NS_THEME_TAB_SCROLLARROW_BACK:
586 case NS_THEME_TAB_SCROLLARROW_FORWARD:
587 if (aWidgetFlags)
588 *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLLARROW_BACK ?
589 GTK_ARROW_LEFT : GTK_ARROW_RIGHT;
590 aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
591 break;
592 case NS_THEME_TAB_PANELS:
593 aGtkWidgetType = MOZ_GTK_TABPANELS;
594 break;
595 case NS_THEME_TAB:
597 if (aWidgetFlags) {
598 /* First bits will be used to store max(0,-bmargin) where bmargin
599 * is the bottom margin of the tab in pixels (resp. top margin,
600 * for bottom tabs). */
601 if (IsBottomTab(aFrame)) {
602 *aWidgetFlags = MOZ_GTK_TAB_BOTTOM;
603 } else {
604 *aWidgetFlags = 0;
607 *aWidgetFlags |= GetTabMarginPixels(aFrame);
609 if (IsSelectedTab(aFrame))
610 *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
612 if (IsFirstTab(aFrame))
613 *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
616 aGtkWidgetType = MOZ_GTK_TAB;
618 break;
619 case NS_THEME_SPLITTER:
620 if (IsHorizontal(aFrame))
621 aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
622 else
623 aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
624 break;
625 case NS_THEME_MENUBAR:
626 aGtkWidgetType = MOZ_GTK_MENUBAR;
627 break;
628 case NS_THEME_MENUPOPUP:
629 aGtkWidgetType = MOZ_GTK_MENUPOPUP;
630 break;
631 case NS_THEME_MENUITEM:
632 aGtkWidgetType = MOZ_GTK_MENUITEM;
633 break;
634 case NS_THEME_MENUSEPARATOR:
635 aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
636 break;
637 case NS_THEME_MENUARROW:
638 aGtkWidgetType = MOZ_GTK_MENUARROW;
639 break;
640 case NS_THEME_CHECKMENUITEM:
641 aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
642 break;
643 case NS_THEME_RADIOMENUITEM:
644 aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
645 break;
646 case NS_THEME_WINDOW:
647 case NS_THEME_DIALOG:
648 aGtkWidgetType = MOZ_GTK_WINDOW;
649 break;
650 default:
651 return false;
654 return true;
657 #if (MOZ_WIDGET_GTK == 2)
658 class ThemeRenderer : public gfxGdkNativeRenderer {
659 public:
660 ThemeRenderer(GtkWidgetState aState, GtkThemeWidgetType aGTKWidgetType,
661 gint aFlags, GtkTextDirection aDirection,
662 const GdkRectangle& aGDKRect, const GdkRectangle& aGDKClip)
663 : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags),
664 mDirection(aDirection), mGDKRect(aGDKRect), mGDKClip(aGDKClip) {}
665 nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX, gint offsetY,
666 GdkRectangle * clipRects, uint32_t numClipRects);
667 private:
668 GtkWidgetState mState;
669 GtkThemeWidgetType mGTKWidgetType;
670 gint mFlags;
671 GtkTextDirection mDirection;
672 const GdkRectangle& mGDKRect;
673 const GdkRectangle& mGDKClip;
676 nsresult
677 ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX,
678 gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects)
680 GdkRectangle gdk_rect = mGDKRect;
681 gdk_rect.x += offsetX;
682 gdk_rect.y += offsetY;
684 GdkRectangle gdk_clip = mGDKClip;
685 gdk_clip.x += offsetX;
686 gdk_clip.y += offsetY;
688 GdkRectangle surfaceRect;
689 surfaceRect.x = 0;
690 surfaceRect.y = 0;
691 gdk_drawable_get_size(drawable, &surfaceRect.width, &surfaceRect.height);
692 gdk_rectangle_intersect(&gdk_clip, &surfaceRect, &gdk_clip);
694 NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!");
695 moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip,
696 &mState, mFlags, mDirection);
698 return NS_OK;
700 #endif
702 bool
703 nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
704 nsIntMargin* aExtra)
706 *aExtra = nsIntMargin(0,0,0,0);
707 // Allow an extra one pixel above and below the thumb for certain
708 // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
709 // We modify the frame's overflow area. See bug 297508.
710 switch (aWidgetType) {
711 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
712 aExtra->top = aExtra->bottom = 1;
713 return true;
714 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
715 aExtra->left = aExtra->right = 1;
716 return true;
718 // Include the indicator spacing (the padding around the control).
719 case NS_THEME_CHECKBOX:
720 case NS_THEME_RADIO:
722 gint indicator_size, indicator_spacing;
724 if (aWidgetType == NS_THEME_CHECKBOX) {
725 moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
726 } else {
727 moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
730 aExtra->top = indicator_spacing;
731 aExtra->right = indicator_spacing;
732 aExtra->bottom = indicator_spacing;
733 aExtra->left = indicator_spacing;
734 return true;
736 case NS_THEME_BUTTON :
738 if (IsDefaultButton(aFrame)) {
739 // Some themes draw a default indicator outside the widget,
740 // include that in overflow
741 gint top, left, bottom, right;
742 moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
743 aExtra->top = top;
744 aExtra->right = right;
745 aExtra->bottom = bottom;
746 aExtra->left = left;
747 return true;
750 case NS_THEME_FOCUS_OUTLINE:
752 moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
753 aExtra->right = aExtra->left;
754 aExtra->bottom = aExtra->top;
755 return true;
757 case NS_THEME_TAB :
759 if (!IsSelectedTab(aFrame))
760 return false;
762 gint gap_height = moz_gtk_get_tab_thickness();
764 int32_t extra = gap_height - GetTabMarginPixels(aFrame);
765 if (extra <= 0)
766 return false;
768 if (IsBottomTab(aFrame)) {
769 aExtra->top = extra;
770 } else {
771 aExtra->bottom = extra;
774 default:
775 return false;
779 NS_IMETHODIMP
780 nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext,
781 nsIFrame* aFrame,
782 uint8_t aWidgetType,
783 const nsRect& aRect,
784 const nsRect& aDirtyRect)
786 GtkWidgetState state;
787 GtkThemeWidgetType gtkWidgetType;
788 GtkTextDirection direction = GetTextDirection(aFrame);
789 gint flags;
790 if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state,
791 &flags))
792 return NS_OK;
794 gfxContext* ctx = aContext->ThebesContext();
795 nsPresContext *presContext = aFrame->PresContext();
797 gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
798 gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
800 // Align to device pixels where sensible
801 // to provide crisper and faster drawing.
802 // Don't snap if it's a non-unit scale factor. We're going to have to take
803 // slow paths then in any case.
804 bool snapXY = ctx->UserToDevicePixelSnapped(rect);
805 if (snapXY) {
806 // Leave rect in device coords but make dirtyRect consistent.
807 dirtyRect = ctx->UserToDevice(dirtyRect);
810 // Translate the dirty rect so that it is wrt the widget top-left.
811 dirtyRect.MoveBy(-rect.TopLeft());
812 // Round out the dirty rect to gdk pixels to ensure that gtk draws
813 // enough pixels for interpolation to device pixels.
814 dirtyRect.RoundOut();
816 // GTK themes can only draw an integer number of pixels
817 // (even when not snapped).
818 nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
819 nsIntRect overflowRect(widgetRect);
820 nsIntMargin extraSize;
821 if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) {
822 overflowRect.Inflate(extraSize);
825 // This is the rectangle that will actually be drawn, in gdk pixels
826 nsIntRect drawingRect(int32_t(dirtyRect.X()),
827 int32_t(dirtyRect.Y()),
828 int32_t(dirtyRect.Width()),
829 int32_t(dirtyRect.Height()));
830 if (widgetRect.IsEmpty()
831 || !drawingRect.IntersectRect(overflowRect, drawingRect))
832 return NS_OK;
834 // gdk rectangles are wrt the drawing rect.
836 GdkRectangle gdk_rect = {-drawingRect.x, -drawingRect.y,
837 widgetRect.width, widgetRect.height};
839 // translate everything so (0,0) is the top left of the drawingRect
840 gfxContextAutoSaveRestore autoSR(ctx);
841 if (snapXY) {
842 // Rects are in device coords.
843 ctx->IdentityMatrix();
845 ctx->Translate(rect.TopLeft() + gfxPoint(drawingRect.x, drawingRect.y));
847 NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
848 "Trying to render an unsafe widget!");
850 bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
851 if (!safeState) {
852 gLastGdkError = 0;
853 gdk_error_trap_push ();
856 #if (MOZ_WIDGET_GTK == 2)
857 // The gdk_clip is just advisory here, meaning "you don't
858 // need to draw outside this rect if you don't feel like it!"
859 GdkRectangle gdk_clip = {0, 0, drawingRect.width, drawingRect.height};
861 ThemeRenderer renderer(state, gtkWidgetType, flags, direction,
862 gdk_rect, gdk_clip);
864 // Some themes (e.g. Clearlooks) just don't clip properly to any
865 // clip rect we provide, so we cannot advertise support for clipping within
866 // the widget bounds.
867 uint32_t rendererFlags = 0;
868 if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque) {
869 rendererFlags |= gfxGdkNativeRenderer::DRAW_IS_OPAQUE;
872 // GtkStyles (used by the widget drawing backend) are created for a
873 // particular colormap/visual.
874 GdkColormap* colormap = moz_gtk_widget_get_colormap();
876 renderer.Draw(ctx, drawingRect.Size(), rendererFlags, colormap);
877 #else
878 moz_gtk_widget_paint(gtkWidgetType, ctx->GetCairo(), &gdk_rect,
879 &state, flags, direction);
880 #endif
882 if (!safeState) {
883 gdk_flush();
884 gLastGdkError = gdk_error_trap_pop ();
886 if (gLastGdkError) {
887 #ifdef DEBUG
888 printf("GTK theme failed for widget type %d, error was %d, state was "
889 "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
890 aWidgetType, gLastGdkError, state.active, state.focused,
891 state.inHover, state.disabled);
892 #endif
893 NS_WARNING("GTK theme failed; disabling unsafe widget");
894 SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
895 // force refresh of the window, because the widget was not
896 // successfully drawn it must be redrawn using the default look
897 RefreshWidgetWindow(aFrame);
898 } else {
899 SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
903 // Indeterminate progress bar are animated.
904 if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
905 gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
906 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
907 NS_WARNING("unable to animate widget!");
911 return NS_OK;
914 NS_IMETHODIMP
915 nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
916 uint8_t aWidgetType, nsIntMargin* aResult)
918 GtkTextDirection direction = GetTextDirection(aFrame);
919 aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
920 switch (aWidgetType) {
921 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
922 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
924 MozGtkScrollbarMetrics metrics;
925 moz_gtk_get_scrollbar_metrics(&metrics);
926 aResult->top = aResult->left = aResult->right = aResult->bottom = metrics.trough_border;
928 break;
929 case NS_THEME_TOOLBOX:
930 // gtk has no toolbox equivalent. So, although we map toolbox to
931 // gtk's 'toolbar' for purposes of painting the widget background,
932 // we don't use the toolbar border for toolbox.
933 break;
934 case NS_THEME_TOOLBAR_DUAL_BUTTON:
935 // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
936 // around the entire button + dropdown, and also an inner border if you're
937 // over the button part. But, we want the inner button to be right up
938 // against the edge of the outer button so that the borders overlap.
939 // To make this happen, we draw a button border for the outer button,
940 // but don't reserve any space for it.
941 break;
942 case NS_THEME_TAB:
943 // Top tabs have no bottom border, bottom tabs have no top border
944 moz_gtk_get_widget_border(MOZ_GTK_TAB, &aResult->left, &aResult->top,
945 &aResult->right, &aResult->bottom, direction,
946 FALSE);
947 if (IsBottomTab(aFrame))
948 aResult->top = 0;
949 else
950 aResult->bottom = 0;
951 break;
952 case NS_THEME_MENUITEM:
953 case NS_THEME_CHECKMENUITEM:
954 case NS_THEME_RADIOMENUITEM:
955 // For regular menuitems, we will be using GetWidgetPadding instead of
956 // GetWidgetBorder to pad up the widget's internals; other menuitems
957 // will need to fall through and use the default case as before.
958 if (IsRegularMenuItem(aFrame))
959 break;
960 default:
962 GtkThemeWidgetType gtkWidgetType;
963 if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
964 nullptr)) {
965 moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
966 &aResult->right, &aResult->bottom, direction,
967 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
971 return NS_OK;
974 bool
975 nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
976 nsIFrame* aFrame, uint8_t aWidgetType,
977 nsIntMargin* aResult)
979 switch (aWidgetType) {
980 case NS_THEME_BUTTON_FOCUS:
981 case NS_THEME_TOOLBAR_BUTTON:
982 case NS_THEME_TOOLBAR_DUAL_BUTTON:
983 case NS_THEME_TAB_SCROLLARROW_BACK:
984 case NS_THEME_TAB_SCROLLARROW_FORWARD:
985 case NS_THEME_DROPDOWN_BUTTON:
986 case NS_THEME_TOOLBAR_BUTTON_DROPDOWN:
987 case NS_THEME_BUTTON_ARROW_UP:
988 case NS_THEME_BUTTON_ARROW_DOWN:
989 case NS_THEME_BUTTON_ARROW_NEXT:
990 case NS_THEME_BUTTON_ARROW_PREVIOUS:
991 case NS_THEME_RANGE_THUMB:
992 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
993 // and have a meaningful baseline, so they can't have
994 // author-specified padding.
995 case NS_THEME_CHECKBOX:
996 case NS_THEME_RADIO:
997 aResult->SizeTo(0, 0, 0, 0);
998 return true;
999 case NS_THEME_MENUITEM:
1000 case NS_THEME_CHECKMENUITEM:
1001 case NS_THEME_RADIOMENUITEM:
1003 // Menubar and menulist have their padding specified in CSS.
1004 if (!IsRegularMenuItem(aFrame))
1005 return false;
1007 aResult->SizeTo(0, 0, 0, 0);
1008 GtkThemeWidgetType gtkWidgetType;
1009 if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1010 nullptr)) {
1011 moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
1012 &aResult->right, &aResult->bottom, GetTextDirection(aFrame),
1013 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
1016 gint horizontal_padding;
1018 if (aWidgetType == NS_THEME_MENUITEM)
1019 moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
1020 else
1021 moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
1023 aResult->left += horizontal_padding;
1024 aResult->right += horizontal_padding;
1026 return true;
1030 return false;
1033 bool
1034 nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
1035 nsIFrame* aFrame, uint8_t aWidgetType,
1036 nsRect* aOverflowRect)
1038 nsIntMargin extraSize;
1039 if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize))
1040 return false;
1042 int32_t p2a = aContext->AppUnitsPerDevPixel();
1043 nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
1044 NSIntPixelsToAppUnits(extraSize.right, p2a),
1045 NSIntPixelsToAppUnits(extraSize.bottom, p2a),
1046 NSIntPixelsToAppUnits(extraSize.left, p2a));
1048 aOverflowRect->Inflate(m);
1049 return true;
1052 NS_IMETHODIMP
1053 nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
1054 nsIFrame* aFrame, uint8_t aWidgetType,
1055 nsIntSize* aResult, bool* aIsOverridable)
1057 aResult->width = aResult->height = 0;
1058 *aIsOverridable = true;
1060 switch (aWidgetType) {
1061 case NS_THEME_SCROLLBAR_BUTTON_UP:
1062 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
1064 MozGtkScrollbarMetrics metrics;
1065 moz_gtk_get_scrollbar_metrics(&metrics);
1067 aResult->width = metrics.slider_width;
1068 aResult->height = metrics.stepper_size;
1069 *aIsOverridable = false;
1071 break;
1072 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
1073 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
1075 MozGtkScrollbarMetrics metrics;
1076 moz_gtk_get_scrollbar_metrics(&metrics);
1078 aResult->width = metrics.stepper_size;
1079 aResult->height = metrics.slider_width;
1080 *aIsOverridable = false;
1082 break;
1083 case NS_THEME_SPLITTER:
1085 gint metrics;
1086 if (IsHorizontal(aFrame)) {
1087 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
1088 aResult->width = metrics;
1089 aResult->height = 0;
1090 } else {
1091 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
1092 aResult->width = 0;
1093 aResult->height = metrics;
1095 *aIsOverridable = false;
1097 break;
1098 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1099 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1101 /* While we enforce a minimum size for the thumb, this is ignored
1102 * for the some scrollbars if buttons are hidden (bug 513006) because
1103 * the thumb isn't a direct child of the scrollbar, unlike the buttons
1104 * or track. So add a minimum size to the track as well to prevent a
1105 * 0-width scrollbar. */
1106 MozGtkScrollbarMetrics metrics;
1107 moz_gtk_get_scrollbar_metrics(&metrics);
1109 if (aWidgetType == NS_THEME_SCROLLBAR_TRACK_VERTICAL)
1110 aResult->width = metrics.slider_width + 2 * metrics.trough_border;
1111 else
1112 aResult->height = metrics.slider_width + 2 * metrics.trough_border;
1114 *aIsOverridable = false;
1116 break;
1117 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
1118 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
1120 MozGtkScrollbarMetrics metrics;
1121 moz_gtk_get_scrollbar_metrics(&metrics);
1123 nsRect rect = aFrame->GetParent()->GetRect();
1124 int32_t p2a = aFrame->PresContext()->DeviceContext()->
1125 AppUnitsPerDevPixel();
1126 nsMargin margin;
1128 /* Get the available space, if that is smaller then the minimum size,
1129 * adjust the mininum size to fit into it.
1130 * Setting aIsOverridable to true has no effect for thumbs. */
1131 aFrame->GetMargin(margin);
1132 rect.Deflate(margin);
1133 aFrame->GetParent()->GetBorderAndPadding(margin);
1134 rect.Deflate(margin);
1136 if (aWidgetType == NS_THEME_SCROLLBAR_THUMB_VERTICAL) {
1137 aResult->width = metrics.slider_width;
1138 aResult->height = std::min(NSAppUnitsToIntPixels(rect.height, p2a),
1139 metrics.min_slider_size);
1140 } else {
1141 aResult->height = metrics.slider_width;
1142 aResult->width = std::min(NSAppUnitsToIntPixels(rect.width, p2a),
1143 metrics.min_slider_size);
1146 *aIsOverridable = false;
1148 break;
1149 case NS_THEME_RANGE_THUMB:
1151 gint thumb_length, thumb_height;
1153 if (IsRangeHorizontal(aFrame)) {
1154 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
1155 } else {
1156 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height, &thumb_length);
1158 aResult->width = thumb_length;
1159 aResult->height = thumb_height;
1161 *aIsOverridable = false;
1163 break;
1164 case NS_THEME_SCALE_THUMB_HORIZONTAL:
1165 case NS_THEME_SCALE_THUMB_VERTICAL:
1167 gint thumb_length, thumb_height;
1169 if (aWidgetType == NS_THEME_SCALE_THUMB_VERTICAL) {
1170 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height);
1171 aResult->width = thumb_height;
1172 aResult->height = thumb_length;
1173 } else {
1174 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
1175 aResult->width = thumb_length;
1176 aResult->height = thumb_height;
1179 *aIsOverridable = false;
1181 break;
1182 case NS_THEME_TAB_SCROLLARROW_BACK:
1183 case NS_THEME_TAB_SCROLLARROW_FORWARD:
1185 moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
1186 *aIsOverridable = false;
1188 break;
1189 case NS_THEME_DROPDOWN_BUTTON:
1191 moz_gtk_get_combo_box_entry_button_size(&aResult->width,
1192 &aResult->height);
1193 *aIsOverridable = false;
1195 break;
1196 case NS_THEME_MENUSEPARATOR:
1198 gint separator_height;
1200 moz_gtk_get_menu_separator_height(&separator_height);
1201 aResult->height = separator_height;
1203 *aIsOverridable = false;
1205 break;
1206 case NS_THEME_CHECKBOX:
1207 case NS_THEME_RADIO:
1209 gint indicator_size, indicator_spacing;
1211 if (aWidgetType == NS_THEME_CHECKBOX) {
1212 moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
1213 } else {
1214 moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
1217 // Include space for the indicator and the padding around it.
1218 aResult->width = indicator_size;
1219 aResult->height = indicator_size;
1221 break;
1222 case NS_THEME_TOOLBAR_BUTTON_DROPDOWN:
1223 case NS_THEME_BUTTON_ARROW_UP:
1224 case NS_THEME_BUTTON_ARROW_DOWN:
1225 case NS_THEME_BUTTON_ARROW_NEXT:
1226 case NS_THEME_BUTTON_ARROW_PREVIOUS:
1228 moz_gtk_get_arrow_size(&aResult->width, &aResult->height);
1229 *aIsOverridable = false;
1231 break;
1232 case NS_THEME_CHECKBOX_CONTAINER:
1233 case NS_THEME_RADIO_CONTAINER:
1234 case NS_THEME_CHECKBOX_LABEL:
1235 case NS_THEME_RADIO_LABEL:
1236 case NS_THEME_BUTTON:
1237 case NS_THEME_DROPDOWN:
1238 case NS_THEME_TOOLBAR_BUTTON:
1239 case NS_THEME_TREEVIEW_HEADER_CELL:
1241 // Just include our border, and let the box code augment the size.
1242 nsIntMargin border;
1243 nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
1244 aFrame, aWidgetType, &border);
1245 aResult->width = border.left + border.right;
1246 aResult->height = border.top + border.bottom;
1248 break;
1249 case NS_THEME_TOOLBAR_SEPARATOR:
1251 gint separator_width;
1253 moz_gtk_get_toolbar_separator_width(&separator_width);
1255 aResult->width = separator_width;
1257 break;
1258 case NS_THEME_SPINNER:
1259 // hard code these sizes
1260 aResult->width = 14;
1261 aResult->height = 26;
1262 break;
1263 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
1264 case NS_THEME_SPINNER_UP_BUTTON:
1265 case NS_THEME_SPINNER_DOWN_BUTTON:
1266 // hard code these sizes
1267 aResult->width = 14;
1268 aResult->height = 13;
1269 break;
1270 case NS_THEME_RESIZER:
1271 // same as Windows to make our lives easier
1272 aResult->width = aResult->height = 15;
1273 *aIsOverridable = false;
1274 break;
1275 case NS_THEME_TREEVIEW_TWISTY:
1276 case NS_THEME_TREEVIEW_TWISTY_OPEN:
1278 gint expander_size;
1280 moz_gtk_get_treeview_expander_size(&expander_size);
1281 aResult->width = aResult->height = expander_size;
1282 *aIsOverridable = false;
1284 break;
1286 return NS_OK;
1289 NS_IMETHODIMP
1290 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
1291 nsIAtom* aAttribute, bool* aShouldRepaint)
1293 // Some widget types just never change state.
1294 if (aWidgetType == NS_THEME_TOOLBOX ||
1295 aWidgetType == NS_THEME_TOOLBAR ||
1296 aWidgetType == NS_THEME_STATUSBAR ||
1297 aWidgetType == NS_THEME_STATUSBAR_PANEL ||
1298 aWidgetType == NS_THEME_STATUSBAR_RESIZER_PANEL ||
1299 aWidgetType == NS_THEME_PROGRESSBAR_CHUNK ||
1300 aWidgetType == NS_THEME_PROGRESSBAR_CHUNK_VERTICAL ||
1301 aWidgetType == NS_THEME_PROGRESSBAR ||
1302 aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
1303 aWidgetType == NS_THEME_MENUBAR ||
1304 aWidgetType == NS_THEME_MENUPOPUP ||
1305 aWidgetType == NS_THEME_TOOLTIP ||
1306 aWidgetType == NS_THEME_MENUSEPARATOR ||
1307 aWidgetType == NS_THEME_WINDOW ||
1308 aWidgetType == NS_THEME_DIALOG) {
1309 *aShouldRepaint = false;
1310 return NS_OK;
1313 if ((aWidgetType == NS_THEME_SCROLLBAR_THUMB_VERTICAL ||
1314 aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL) &&
1315 aAttribute == nsGkAtoms::active) {
1316 *aShouldRepaint = true;
1317 return NS_OK;
1320 if ((aWidgetType == NS_THEME_SCROLLBAR_BUTTON_UP ||
1321 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_DOWN ||
1322 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT ||
1323 aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT) &&
1324 (aAttribute == nsGkAtoms::curpos ||
1325 aAttribute == nsGkAtoms::maxpos)) {
1326 *aShouldRepaint = true;
1327 return NS_OK;
1330 // XXXdwh Not sure what can really be done here. Can at least guess for
1331 // specific widgets that they're highly unlikely to have certain states.
1332 // For example, a toolbar doesn't care about any states.
1333 if (!aAttribute) {
1334 // Hover/focus/active changed. Always repaint.
1335 *aShouldRepaint = true;
1337 else {
1338 // Check the attribute to see if it's relevant.
1339 // disabled, checked, dlgtype, default, etc.
1340 *aShouldRepaint = false;
1341 if (aAttribute == nsGkAtoms::disabled ||
1342 aAttribute == nsGkAtoms::checked ||
1343 aAttribute == nsGkAtoms::selected ||
1344 aAttribute == nsGkAtoms::focused ||
1345 aAttribute == nsGkAtoms::readonly ||
1346 aAttribute == nsGkAtoms::_default ||
1347 aAttribute == nsGkAtoms::menuactive ||
1348 aAttribute == nsGkAtoms::open ||
1349 aAttribute == nsGkAtoms::parentfocused)
1350 *aShouldRepaint = true;
1353 return NS_OK;
1356 NS_IMETHODIMP
1357 nsNativeThemeGTK::ThemeChanged()
1359 memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
1360 return NS_OK;
1363 NS_IMETHODIMP_(bool)
1364 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
1365 nsIFrame* aFrame,
1366 uint8_t aWidgetType)
1368 if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
1369 return false;
1371 switch (aWidgetType) {
1372 case NS_THEME_BUTTON:
1373 case NS_THEME_BUTTON_FOCUS:
1374 case NS_THEME_RADIO:
1375 case NS_THEME_CHECKBOX:
1376 case NS_THEME_TOOLBOX: // N/A
1377 case NS_THEME_TOOLBAR:
1378 case NS_THEME_TOOLBAR_BUTTON:
1379 case NS_THEME_TOOLBAR_DUAL_BUTTON: // so we can override the border with 0
1380 case NS_THEME_TOOLBAR_BUTTON_DROPDOWN:
1381 case NS_THEME_BUTTON_ARROW_UP:
1382 case NS_THEME_BUTTON_ARROW_DOWN:
1383 case NS_THEME_BUTTON_ARROW_NEXT:
1384 case NS_THEME_BUTTON_ARROW_PREVIOUS:
1385 case NS_THEME_TOOLBAR_SEPARATOR:
1386 case NS_THEME_TOOLBAR_GRIPPER:
1387 case NS_THEME_STATUSBAR:
1388 case NS_THEME_STATUSBAR_PANEL:
1389 case NS_THEME_STATUSBAR_RESIZER_PANEL:
1390 case NS_THEME_RESIZER:
1391 case NS_THEME_LISTBOX:
1392 // case NS_THEME_LISTBOX_LISTITEM:
1393 case NS_THEME_TREEVIEW:
1394 // case NS_THEME_TREEVIEW_TREEITEM:
1395 case NS_THEME_TREEVIEW_TWISTY:
1396 // case NS_THEME_TREEVIEW_LINE:
1397 // case NS_THEME_TREEVIEW_HEADER:
1398 case NS_THEME_TREEVIEW_HEADER_CELL:
1399 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
1400 case NS_THEME_TREEVIEW_TWISTY_OPEN:
1401 case NS_THEME_PROGRESSBAR:
1402 case NS_THEME_PROGRESSBAR_CHUNK:
1403 case NS_THEME_PROGRESSBAR_VERTICAL:
1404 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
1405 case NS_THEME_TAB:
1406 // case NS_THEME_TAB_PANEL:
1407 case NS_THEME_TAB_PANELS:
1408 case NS_THEME_TAB_SCROLLARROW_BACK:
1409 case NS_THEME_TAB_SCROLLARROW_FORWARD:
1410 case NS_THEME_TOOLTIP:
1411 case NS_THEME_SPINNER:
1412 case NS_THEME_SPINNER_UP_BUTTON:
1413 case NS_THEME_SPINNER_DOWN_BUTTON:
1414 case NS_THEME_SPINNER_TEXTFIELD:
1415 // case NS_THEME_SCROLLBAR: (n/a for gtk)
1416 // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
1417 case NS_THEME_SCROLLBAR_BUTTON_UP:
1418 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
1419 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
1420 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
1421 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1422 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1423 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
1424 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
1425 case NS_THEME_NUMBER_INPUT:
1426 case NS_THEME_TEXTFIELD:
1427 case NS_THEME_TEXTFIELD_MULTILINE:
1428 case NS_THEME_DROPDOWN_TEXTFIELD:
1429 case NS_THEME_RANGE:
1430 case NS_THEME_RANGE_THUMB:
1431 case NS_THEME_SCALE_HORIZONTAL:
1432 case NS_THEME_SCALE_THUMB_HORIZONTAL:
1433 case NS_THEME_SCALE_VERTICAL:
1434 case NS_THEME_SCALE_THUMB_VERTICAL:
1435 // case NS_THEME_SCALE_THUMB_START:
1436 // case NS_THEME_SCALE_THUMB_END:
1437 // case NS_THEME_SCALE_TICK:
1438 case NS_THEME_CHECKBOX_CONTAINER:
1439 case NS_THEME_RADIO_CONTAINER:
1440 case NS_THEME_CHECKBOX_LABEL:
1441 case NS_THEME_RADIO_LABEL:
1442 case NS_THEME_MENUBAR:
1443 case NS_THEME_MENUPOPUP:
1444 case NS_THEME_MENUITEM:
1445 case NS_THEME_MENUARROW:
1446 case NS_THEME_MENUSEPARATOR:
1447 case NS_THEME_CHECKMENUITEM:
1448 case NS_THEME_RADIOMENUITEM:
1449 case NS_THEME_SPLITTER:
1450 case NS_THEME_WINDOW:
1451 case NS_THEME_DIALOG:
1452 case NS_THEME_DROPDOWN:
1453 case NS_THEME_DROPDOWN_TEXT:
1454 return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1456 case NS_THEME_DROPDOWN_BUTTON:
1457 // "Native" dropdown buttons cause padding and margin problems, but only
1458 // in HTML so allow them in XUL.
1459 return (!aFrame || IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
1460 !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1462 case NS_THEME_FOCUS_OUTLINE:
1463 return true;
1466 return false;
1469 NS_IMETHODIMP_(bool)
1470 nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType)
1472 // XXXdwh At some point flesh all of this out.
1473 if (aWidgetType == NS_THEME_DROPDOWN_BUTTON ||
1474 aWidgetType == NS_THEME_RADIO ||
1475 aWidgetType == NS_THEME_RANGE_THUMB ||
1476 aWidgetType == NS_THEME_CHECKBOX ||
1477 aWidgetType == NS_THEME_TAB_SCROLLARROW_BACK ||
1478 aWidgetType == NS_THEME_TAB_SCROLLARROW_FORWARD ||
1479 aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
1480 aWidgetType == NS_THEME_BUTTON_ARROW_DOWN ||
1481 aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
1482 aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
1483 return false;
1484 return true;
1487 bool
1488 nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
1490 if (aWidgetType == NS_THEME_DROPDOWN ||
1491 aWidgetType == NS_THEME_BUTTON ||
1492 aWidgetType == NS_THEME_TREEVIEW_HEADER_CELL)
1493 return true;
1495 return false;
1498 bool
1499 nsNativeThemeGTK::ThemeNeedsComboboxDropmarker()
1501 return false;
1504 nsITheme::Transparency
1505 nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
1507 switch (aWidgetType) {
1508 // These widgets always draw a default background.
1509 #if (MOZ_WIDGET_GTK == 2)
1510 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1511 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1512 case NS_THEME_TOOLBAR:
1513 case NS_THEME_MENUBAR:
1514 #endif
1515 case NS_THEME_MENUPOPUP:
1516 case NS_THEME_WINDOW:
1517 case NS_THEME_DIALOG:
1518 // Tooltips use gtk_paint_flat_box().
1519 case NS_THEME_TOOLTIP:
1520 return eOpaque;
1523 return eUnknownTransparency;