widget/gtk/nsNativeThemeGTK.cpp

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

mercurial