Backout bug 555133 to fix bug 555950.
[mozilla-central.git] / widget / src / cocoa / nsNativeThemeCocoa.mm
blob73ecda169b1f1f005af9668172323dea54feb3f3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Mike Pinkerton (pinkerton@netscape.com).
19  * Portions created by the Initial Developer are Copyright (C) 2001
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *    Vladimir Vukicevic <vladimir@pobox.com> (HITheme rewrite)
24  *    Josh Aas <josh@mozilla.com>
25  *    Colin Barrett <cbarrett@mozilla.com>
26  *    Matthew Gregan <kinetik@flim.org>
27  *    Markus Stange <mstange@themasta.com>
28  *
29  * Alternatively, the contents of this file may be used under the terms of
30  * either of the GNU General Public License Version 2 or later (the "GPL"),
31  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32  * in which case the provisions of the GPL or the LGPL are applicable instead
33  * of those above. If you wish to allow use of your version of this file only
34  * under the terms of either the GPL or the LGPL, and not to allow others to
35  * use your version of this file under the terms of the MPL, indicate your
36  * decision by deleting the provisions above and replace them with the notice
37  * and other provisions required by the GPL or the LGPL. If you do not delete
38  * the provisions above, a recipient may use your version of this file under
39  * the terms of any one of the MPL, the GPL or the LGPL.
40  *
41  * ***** END LICENSE BLOCK ***** */
43 #include "nsNativeThemeCocoa.h"
44 #include "nsObjCExceptions.h"
45 #include "nsIRenderingContext.h"
46 #include "nsRect.h"
47 #include "nsSize.h"
48 #include "nsThemeConstants.h"
49 #include "nsIPresShell.h"
50 #include "nsPresContext.h"
51 #include "nsIContent.h"
52 #include "nsIDocument.h"
53 #include "nsIFrame.h"
54 #include "nsIAtom.h"
55 #include "nsIEventStateManager.h"
56 #include "nsINameSpaceManager.h"
57 #include "nsPresContext.h"
58 #include "nsILookAndFeel.h"
59 #include "nsWidgetAtoms.h"
60 #include "nsToolkit.h"
61 #include "nsCocoaWindow.h"
62 #include "nsNativeThemeColors.h"
64 #include "gfxContext.h"
65 #include "gfxQuartzSurface.h"
66 #include "gfxQuartzNativeDrawing.h"
68 #define DRAW_IN_FRAME_DEBUG 0
69 #define SCROLLBARS_VISUAL_DEBUG 0
71 // private Quartz routines needed here
72 extern "C" {
73   CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
76 // Workaround for NSCell control tint drawing
77 // Without this workaround, NSCells are always drawn with the clear control tint
78 // as long as they're not attached to an NSControl which is a subview of an active window.
79 // XXXmstange Why doesn't Webkit need this?
80 @implementation NSCell (ControlTintWorkaround)
81 - (int)_realControlTint { return [self controlTint]; }
82 @end
84 // The purpose of this class is to provide objects that can be used when drawing
85 // NSCells using drawWithFrame:inView: without causing any harm. The only
86 // messages that will be sent to such an object are "isFlipped" and
87 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
88 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
89 // NSView) will be called when drawing search fields, and we only provide it in
90 // order to prevent "unrecognized selector" exceptions.
91 // There's no need to pass the actual NSView that we're drawing into to
92 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
93 // invalidations as soon as we draw a focusring!
94 @interface CellDrawView : NSView
96 @end;
98 @implementation CellDrawView
100 - (BOOL)isFlipped
102   return YES;
105 - (NSText*)currentEditor
107   return nil;
110 @end
112 // Copied from nsLookAndFeel.h
113 // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
114 static const int kThemeScrollBarArrowsBoth = 2;
116 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
117 #define MAX_FOCUS_RING_WIDTH 4
119 // These enums are for indexing into the margin array.
120 enum {
121   leopardOS
124 enum {
125   miniControlSize,
126   smallControlSize,
127   regularControlSize
130 enum {
131   leftMargin,
132   topMargin,
133   rightMargin,
134   bottomMargin
137 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
138   if (cocoaControlSize == NSMiniControlSize)
139     return miniControlSize;
140   else if (cocoaControlSize == NSSmallControlSize)
141     return smallControlSize;
142   else
143     return regularControlSize;
146 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
148   if (!marginSet)
149     return;
151   static int osIndex = leopardOS;
152   int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
153   const float* buttonMargins = marginSet[osIndex][controlSize];
154   rect->origin.x -= buttonMargins[leftMargin];
155   rect->origin.y -= buttonMargins[bottomMargin];
156   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
157   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
160 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
161                                       nsIWidget** aTopLevelWidget = NULL)
163   if (!aFrame)
164     return nil;  
166   nsIWidget* widget = aFrame->GetWindow();
167   if (!widget)
168     return nil;
170   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
171   if (aTopLevelWidget)
172     *aTopLevelWidget = topLevelWidget;
174   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
177 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
179   nsIWidget* topLevelWidget = NULL;
180   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
181   if (!topLevelWidget || !win)
182     return YES;
184   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
185   // but controls in these windows should still get the active look.
186   nsWindowType windowType;
187   topLevelWidget->GetWindowType(windowType);
188   if (windowType == eWindowType_popup)
189     return YES;
190   if ([win isSheet])
191     return [win isKeyWindow];
192   return [win isMainWindow] && ![win attachedSheet];
195 NS_IMPL_ISUPPORTS1(nsNativeThemeCocoa, nsITheme)
198 nsNativeThemeCocoa::nsNativeThemeCocoa()
200   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
202   mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
203   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
204   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
206   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
207   [mRadioButtonCell setButtonType:NSRadioButton];
209   mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
210   [mCheckboxCell setButtonType:NSSwitchButton];
211   [mCheckboxCell setAllowsMixedState:YES];
213   mSearchFieldCell = [[NSSearchFieldCell alloc] initTextCell:@""];
214   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
215   [mSearchFieldCell setBezeled:YES];
216   [mSearchFieldCell setEditable:YES];
217   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
219   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
221   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
222   [mComboBoxCell setBezeled:YES];
223   [mComboBoxCell setEditable:YES];
224   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
226   mCellDrawView = [[CellDrawView alloc] init];
228   NS_OBJC_END_TRY_ABORT_BLOCK;
231 nsNativeThemeCocoa::~nsNativeThemeCocoa()
233   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
235   [mPushButtonCell release];
236   [mRadioButtonCell release];
237   [mCheckboxCell release];
238   [mSearchFieldCell release];
239   [mDropdownCell release];
240   [mComboBoxCell release];
241   [mCellDrawView release];
243   NS_OBJC_END_TRY_ABORT_BLOCK;
246 // Limit on the area of the target rect (in pixels^2) in
247 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
248 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
249 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
250 // CGContextDrawImage(), and also to avoid very poor drawing performance in
251 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
252 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
253 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
254 // different amounts of RAM.
255 #define BITMAP_MAX_AREA 500000
258  * Draw the given NSCell into the given cgContext.
260  * destRect - the size and position of the resulting control rectangle
261  * controlSize - the NSControlSize which will be given to the NSCell before
262  *  asking it to render
263  * naturalSize - The natural dimensions of this control.
264  *  If the control rect size is not equal to either of these, a scale
265  *  will be applied to the context so that rendering the control at the
266  *  natural size will result in it filling the destRect space.
267  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
268  * minimumSize - The minimum dimensions of this control.
269  *  If the control rect size is less than the minimum for a given axis,
270  *  a scale will be applied to the context so that the minimum is used
271  *  for drawing.  If a control has no minimum dimensions in either/both
272  *  axes, pass 0.0f.
273  * marginSet - an array of margins; a multidimensional array of [2][3][4],
274  *  with the first dimension being the OS version (Tiger or Leopard),
275  *  the second being the control size (mini, small, regular), and the third
276  *  being the 4 margin values (left, top, right, bottom).
277  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
278  *  matter if this is really the right view; it just has to return YES when
279  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
280  * mirrorHorizontal - whether to mirror the cell horizontally
281  */
282 static void DrawCellWithScaling(NSCell *cell,
283                                 CGContextRef cgContext,
284                                 const HIRect& destRect,
285                                 NSControlSize controlSize,
286                                 NSSize naturalSize,
287                                 NSSize minimumSize,
288                                 const float marginSet[][3][4],
289                                 NSView* view,
290                                 BOOL mirrorHorizontal)
292   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
294   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
296   if (naturalSize.width != 0.0f)
297     drawRect.size.width = naturalSize.width;
298   if (naturalSize.height != 0.0f)
299     drawRect.size.height = naturalSize.height;
301   // Keep aspect ratio when scaling if one dimension is free.
302   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
303     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
304   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
305     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
307   // Honor minimum sizes.
308   if (drawRect.size.width < minimumSize.width)
309     drawRect.size.width = minimumSize.width;
310   if (drawRect.size.height < minimumSize.height)
311     drawRect.size.height = minimumSize.height;
313   [NSGraphicsContext saveGraphicsState];
315   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
316   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
317     // Inflate the rect Gecko gave us by the margin for the control.
318     InflateControlRect(&drawRect, controlSize, marginSet);
320     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
321     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
323     [cell drawWithFrame:drawRect inView:view];
325     [NSGraphicsContext setCurrentContext:savedContext];
326   }
327   else {
328     float w = ceil(drawRect.size.width);
329     float h = ceil(drawRect.size.height);
330     NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
332     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
333     InflateControlRect(&tmpRect, controlSize, marginSet);
335     // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
336     w += MAX_FOCUS_RING_WIDTH * 2.0;
337     h += MAX_FOCUS_RING_WIDTH * 2.0;
339     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
340     CGContextRef ctx = CGBitmapContextCreate(NULL,
341                                              (int) w, (int) h,
342                                              8, (int) w * 4,
343                                              rgb, kCGImageAlphaPremultipliedFirst);
344     CGColorSpaceRelease(rgb);
346     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
347     // This is the first flip transform, applied to cgContext.
348     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
349     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
350     if (mirrorHorizontal) {
351       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
352       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
353     }
355     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
356     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
358     // This is the second flip transform, applied to ctx.
359     CGContextScaleCTM(ctx, 1.0f, -1.0f);
360     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
362     [cell drawWithFrame:tmpRect inView:view];
364     [NSGraphicsContext setCurrentContext:savedContext];
366     CGImageRef img = CGBitmapContextCreateImage(ctx);
368     // Drop the image into the original destination rectangle, scaling to fit
369     // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
370     // doesn't extend beyond the overflow rect
371     float xscale = destRect.size.width / drawRect.size.width;
372     float yscale = destRect.size.height / drawRect.size.height;
373     float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
374     float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
375     CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
376                                              destRect.origin.y - scaledFocusRingY,
377                                              destRect.size.width + scaledFocusRingX * 2,
378                                              destRect.size.height + scaledFocusRingY * 2),
379                        img);
381     CGImageRelease(img);
382     CGContextRelease(ctx);
383   }
385   [NSGraphicsContext restoreGraphicsState];
387 #if DRAW_IN_FRAME_DEBUG
388   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
389   CGContextFillRect(cgContext, destRect);
390 #endif
392   NS_OBJC_END_TRY_ABORT_BLOCK;
395 struct CellRenderSettings {
396   // The natural dimensions of the control.
397   // If a control has no natural dimensions in either/both axes, set to 0.0f.
398   NSSize naturalSizes[3];
400   // The minimum dimensions of the control.
401   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
402   NSSize minimumSizes[3];
404   // A multidimensional array of [2][3][4],
405   // with the first dimension being the OS version (Tiger or Leopard),
406   // the second being the control size (mini, small, regular), and the third
407   // being the 4 margin values (left, top, right, bottom).
408   float margins[2][3][4];
412  * Draw the given NSCell into the given cgContext with a nice control size.
414  * This function is similar to DrawCellWithScaling, but it decides what
415  * control size to use based on the destRect's size.
416  * Scaling is only applied when the difference between the destRect's size
417  * and the next smaller natural size is greater than snapTolerance. Otherwise
418  * it snaps to the next smaller control size without scaling because unscaled
419  * controls look nicer.
420  */
421 static void DrawCellWithSnapping(NSCell *cell,
422                                  CGContextRef cgContext,
423                                  const HIRect& destRect,
424                                  const CellRenderSettings settings,
425                                  float verticalAlignFactor,
426                                  NSView* view,
427                                  BOOL mirrorHorizontal,
428                                  float snapTolerance = 2.0f)
430   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
432   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
433   const NSSize *sizes = settings.naturalSizes;
434   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
435   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
436   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
438   NSControlSize controlSizeX = NSRegularControlSize, controlSizeY = NSRegularControlSize;
439   HIRect drawRect = destRect;
441   if (rectWidth <= miniSize.width + snapTolerance && rectWidth < smallSize.width)
442     controlSizeX = NSMiniControlSize;
443   else if(rectWidth <= smallSize.width + snapTolerance && rectWidth < regularSize.width)
444     controlSizeX = NSSmallControlSize;
446   if (rectHeight <= miniSize.height + snapTolerance && rectHeight < smallSize.height)
447     controlSizeY = NSMiniControlSize;
448   else if(rectHeight <= smallSize.height + snapTolerance && rectHeight < regularSize.height)
449     controlSizeY = NSSmallControlSize;
451   NSControlSize controlSize = NSRegularControlSize;
452   int sizeIndex = 0;
454   // At some sizes, don't scale but snap.
455   const NSControlSize smallerControlSize =
456     EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
457     controlSizeX : controlSizeY;
458   const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
459   const NSSize size = sizes[smallerControlSizeIndex];
460   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
461   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
462   if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
463       diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
464     // Snap to the smaller control size.
465     controlSize = smallerControlSize;
466     sizeIndex = smallerControlSizeIndex;
467     // Resize and center the drawRect.
468     if (sizes[sizeIndex].width) {
469       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
470       drawRect.size.width = sizes[sizeIndex].width;
471     }
472     if (sizes[sizeIndex].height) {
473       drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
474       drawRect.size.height = sizes[sizeIndex].height;
475     }
476   } else {
477     // Use the larger control size.
478     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
479                   controlSizeX : controlSizeY;
480     sizeIndex = EnumSizeForCocoaSize(controlSize);
481    }
483   [cell setControlSize:controlSize];
485   NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
486   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
487                       minimumSize, settings.margins, view, mirrorHorizontal);
489   NS_OBJC_END_TRY_ABORT_BLOCK;
492 static float VerticalAlignFactor(nsIFrame *aFrame)
494   if (!aFrame)
495     return 0.5f; // default: center
497   const nsStyleCoord& va = aFrame->GetStyleTextReset()->mVerticalAlign;
498   PRUint8 intval = (va.GetUnit() == eStyleUnit_Enumerated)
499                      ? va.GetIntValue()
500                      : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
501   switch (intval) {
502     case NS_STYLE_VERTICAL_ALIGN_TOP:
503     case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
504       return 0.0f;
506     case NS_STYLE_VERTICAL_ALIGN_SUB:
507     case NS_STYLE_VERTICAL_ALIGN_SUPER:
508     case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
509     case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
510       return 0.5f;
512     case NS_STYLE_VERTICAL_ALIGN_BASELINE:
513     case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
514     case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
515       return 1.0f;
517     default:
518       NS_NOTREACHED("invalid vertical-align");
519       return 0.5f;
520   }
523 // These are the sizes that Gecko needs to request to draw if it wants
524 // to get a standard-sized Aqua radio button drawn. Note that the rects
525 // that draw these are actually a little bigger.
526 static const CellRenderSettings radioSettings = {
527   {
528     NSMakeSize(11, 11), // mini
529     NSMakeSize(13, 13), // small
530     NSMakeSize(16, 16)  // regular
531   },
532   {
533     NSZeroSize, NSZeroSize, NSZeroSize
534   },
535   {
536     { // Tiger
537       {0, 0, 0, 0},     // mini
538       {0, 1, 1, 2},     // small
539       {0, -1, 0, 1}     // regular
540     },
541     { // Leopard
542       {0, 0, 0, 0},     // mini
543       {0, 1, 1, 1},     // small
544       {0, 0, 0, 0}      // regular
545     }
546   }
549 static const CellRenderSettings checkboxSettings = {
550   {
551     NSMakeSize(11, 11), // mini
552     NSMakeSize(13, 13), // small
553     NSMakeSize(16, 16)  // regular
554   },
555   {
556     NSZeroSize, NSZeroSize, NSZeroSize
557   },
558   {
559     { // Tiger
560       {0, 1, 0, 0},     // mini
561       {0, 2, 0, 1},     // small
562       {0, 1, 0, 1}      // regular
563     },
564     { // Leopard
565       {0, 1, 0, 0},     // mini
566       {0, 1, 0, 1},     // small
567       {0, 1, 0, 1}      // regular
568     }
569   }
572 void
573 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, PRBool inCheckbox,
574                                         const HIRect& inBoxRect, PRBool inSelected,
575                                         PRBool inDisabled, PRInt32 inState,
576                                         nsIFrame* aFrame)
578   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
580   NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
581   NSCellStateValue state = inSelected ? NSOnState : NSOffState;
583   // Check if we have an indeterminate checkbox
584   if (inCheckbox && GetIndeterminate(aFrame))
585     state = NSMixedState;
587   [cell setEnabled:!inDisabled];
588   [cell setShowsFirstResponder:(inState & NS_EVENT_STATE_FOCUS)];
589   [cell setState:state];
590   [cell setHighlighted:((inState & NS_EVENT_STATE_ACTIVE) && (inState & NS_EVENT_STATE_HOVER))];
591   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
593   // Ensure that the control is square.
594   float length = PR_MIN(inBoxRect.size.width, inBoxRect.size.height);
595   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
596                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
597                                length, length);
599   DrawCellWithSnapping(cell, cgContext, drawRect,
600                        inCheckbox ? checkboxSettings : radioSettings,
601                        VerticalAlignFactor(aFrame), mCellDrawView, NO);
603   NS_OBJC_END_TRY_ABORT_BLOCK;
606 static const CellRenderSettings searchFieldSettings = {
607   {
608     NSMakeSize(0, 16), // mini
609     NSMakeSize(0, 19), // small
610     NSMakeSize(0, 22)  // regular
611   },
612   {
613     NSMakeSize(32, 0), // mini
614     NSMakeSize(38, 0), // small
615     NSMakeSize(44, 0)  // regular
616   },
617   {
618     { // Tiger
619       {0, 0, 0, 0},     // mini
620       {0, 0, 0, 0},     // small
621       {0, 0, 0, 0}      // regular
622     },
623     { // Leopard
624       {0, 0, 0, 0},     // mini
625       {0, 0, 0, 0},     // small
626       {0, 0, 0, 0}      // regular
627     }
628   }
631 void
632 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
633                                     nsIFrame* aFrame)
635   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
637   NSSearchFieldCell* cell = mSearchFieldCell;
638   [cell setEnabled:!IsDisabled(aFrame)];
639   [cell setShowsFirstResponder:IsFocused(aFrame)];
641   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
642                        VerticalAlignFactor(aFrame), mCellDrawView,
643                        IsFrameRTL(aFrame));
645   NS_OBJC_END_TRY_ABORT_BLOCK;
648 static const CellRenderSettings pushButtonSettings = {
649   {
650     NSMakeSize(0, 16), // mini
651     NSMakeSize(0, 19), // small
652     NSMakeSize(0, 22)  // regular
653   },
654   {
655     NSMakeSize(18, 0), // mini
656     NSMakeSize(26, 0), // small
657     NSMakeSize(30, 0)  // regular
658   },
659   {
660     { // Tiger
661       {1, 1, 1, 1},    // mini
662       {5, 0, 5, 2},    // small
663       {6, 0, 6, 2}     // regular
664     },
665     { // Leopard
666       {0, 0, 0, 0},    // mini
667       {4, 0, 4, 1},    // small
668       {5, 0, 5, 2}     // regular
669     }
670   }
673 // The height at which we start doing square buttons instead of rounded buttons
674 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
675 // we switch over to doing square buttons which looks fine at any size.
676 #define DO_SQUARE_BUTTON_HEIGHT 26
678 void
679 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
680                                    PRBool inDisabled, PRInt32 inState, nsIFrame* aFrame)
682   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
684   BOOL isActive = FrameIsInActiveWindow(aFrame);
686   [mPushButtonCell setEnabled:!inDisabled];
687   [mPushButtonCell setHighlighted:((inState & NS_EVENT_STATE_ACTIVE) &&
688                                    (inState & NS_EVENT_STATE_HOVER) && 
689                                    isActive)];
690   [mPushButtonCell setShowsFirstResponder:(inState & NS_EVENT_STATE_FOCUS) && !inDisabled && isActive];
692   // If the button is tall enough, draw the square button style so that buttons with
693   // non-standard content look good. Otherwise draw normal rounded aqua buttons.
694   if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
695     [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
696     DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSRegularControlSize,
697                         NSZeroSize, NSMakeSize(14, 0), NULL,
698                         mCellDrawView, IsFrameRTL(aFrame));
699   } else {
700     [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
702     DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings,
703                          0.5f, mCellDrawView, IsFrameRTL(aFrame), 1.0f);
704   }
706 #if DRAW_IN_FRAME_DEBUG
707   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
708   CGContextFillRect(cgContext, inBoxRect);
709 #endif
711   NS_OBJC_END_TRY_ABORT_BLOCK;
714 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
716 static void
717 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
718                                 RenderHIThemeControlFunction aFunc, void* aData,
719                                 BOOL mirrorHorizontally = NO)
721   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
722   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
724   PRBool drawDirect;
725   HIRect drawRect = aRect;
726   drawRect.origin = CGPointZero;
728   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
729       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
730     drawDirect = TRUE;
731   } else {
732     drawDirect = FALSE;
733   }
735   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
736   // is too large.
737   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
738     aFunc(aCGContext, drawRect, aData);
739   } else {
740     // Inflate the buffer to capture focus rings.
741     int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
742     int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
744     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
745     CGContextRef bitmapctx = CGBitmapContextCreate(NULL, w, h, 8, w * 4,
746                                                    colorSpace,
747                                                    kCGImageAlphaPremultipliedFirst);
748     CGColorSpaceRelease(colorSpace);
750     CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
752     // HITheme always wants to draw into a flipped context, or things
753     // get confused.
754     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
755     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
757     aFunc(bitmapctx, drawRect, aData);
759     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
761     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
763     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
764     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
765     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
767     if (mirrorHorizontally) {
768       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
769       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
770     }
772     HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
773     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
775     CGContextSetCTM(aCGContext, ctm);
777     CGImageRelease(bitmap);
778     CGContextRelease(bitmapctx);
779   }
781   CGContextSetCTM(aCGContext, savedCTM);
784 static void
785 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
787   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
788   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
791 void
792 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
793                                const HIRect& inBoxRect, PRBool inIsDefault, PRBool inDisabled,
794                                ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
795                                PRInt32 inState, nsIFrame* aFrame)
797   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
799   BOOL isActive = FrameIsInActiveWindow(aFrame);
801   HIThemeButtonDrawInfo bdi;
802   bdi.version = 0;
803   bdi.kind = inKind;
804   bdi.value = inValue;
805   bdi.adornment = inAdornment;
807   if (inDisabled) {
808     bdi.state = kThemeStateUnavailable;
809   }
810   else if ((inState & NS_EVENT_STATE_ACTIVE) && (inState & NS_EVENT_STATE_HOVER)) {
811     bdi.state = kThemeStatePressed;
812   }
813   else {
814     if (inKind == kThemeArrowButton)
815       bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
816     else if (!isActive && inKind == kThemeListHeaderButton)
817       bdi.state = kThemeStateInactive;
818     else
819       bdi.state = kThemeStateActive;
820   }
822   if (inState & NS_EVENT_STATE_FOCUS && isActive)
823     bdi.adornment |= kThemeAdornmentFocus;
825   if (inIsDefault && !inDisabled && isActive && !(inState & NS_EVENT_STATE_ACTIVE)) {
826     bdi.adornment |= kThemeAdornmentDefault;
827     bdi.animation.time.start = 0;
828     bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
829   }
831   HIRect drawFrame = inBoxRect;
833   if (inKind == kThemePushButton) {
834     drawFrame.size.height -= 2;
835     if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
836       bdi.kind = kThemePushButtonMini;
837     }
838     else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
839       bdi.kind = kThemePushButtonSmall;
840       drawFrame.origin.y -= 1;
841       drawFrame.origin.x += 1;
842       drawFrame.size.width -= 2;
843     }
844   }
845   else if (inKind == kThemeListHeaderButton) {
846     CGContextClipToRect(cgContext, inBoxRect);
847     // Always remove the top border.
848     drawFrame.origin.y -= 1;
849     drawFrame.size.height += 1;
850     // Remove the left border in LTR mode and the right border in RTL mode.
851     drawFrame.size.width += 1;
852     PRBool isLast = IsLastTreeHeaderCell(aFrame);
853     if (isLast)
854       drawFrame.size.width += 1; // Also remove the other border.
855     if (!IsFrameRTL(aFrame) || isLast)
856       drawFrame.origin.x -= 1;
857   }
859   RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
860                                   IsFrameRTL(aFrame));
862 #if DRAW_IN_FRAME_DEBUG
863   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
864   CGContextFillRect(cgContext, inBoxRect);
865 #endif
867   NS_OBJC_END_TRY_ABORT_BLOCK;
870 static const CellRenderSettings dropdownSettings = {
871   {
872     NSMakeSize(0, 16), // mini
873     NSMakeSize(0, 19), // small
874     NSMakeSize(0, 22)  // regular
875   },
876   {
877     NSMakeSize(18, 0), // mini
878     NSMakeSize(38, 0), // small
879     NSMakeSize(44, 0)  // regular
880   },
881   {
882     { // Tiger
883       {1, 1, 2, 1},    // mini
884       {3, 0, 3, 1},    // small
885       {3, 0, 3, 0}     // regular
886     },
887     { // Leopard
888       {1, 1, 2, 1},    // mini
889       {3, 0, 3, 1},    // small
890       {3, 0, 3, 0}     // regular
891     }
892   }
895 static const CellRenderSettings editableMenulistSettings = {
896   {
897     NSMakeSize(0, 15), // mini
898     NSMakeSize(0, 18), // small
899     NSMakeSize(0, 21)  // regular
900   },
901   {
902     NSMakeSize(18, 0), // mini
903     NSMakeSize(38, 0), // small
904     NSMakeSize(44, 0)  // regular
905   },
906   {
907     { // Tiger
908       {0, 0, 2, 2},    // mini
909       {0, 0, 3, 2},    // small
910       {0, 1, 3, 3}     // regular
911     },
912     { // Leopard
913       {0, 0, 2, 2},    // mini
914       {0, 0, 3, 2},    // small
915       {0, 1, 3, 3}     // regular
916     }
917   }
920 void
921 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
922                                  PRInt32 inState, PRUint8 aWidgetType, nsIFrame* aFrame)
924   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
926   [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
928   BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
929   NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
931   [cell setEnabled:!IsDisabled(aFrame)];
932   [cell setShowsFirstResponder:(IsFocused(aFrame) || (inState & NS_EVENT_STATE_FOCUS))];
933   [cell setHighlighted:IsOpenButton(aFrame)];
934   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
936   const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
937   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
938                        0.5f, mCellDrawView, IsFrameRTL(aFrame));
940   NS_OBJC_END_TRY_ABORT_BLOCK;
943 void
944 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
945                                     const HIRect& inBoxRect, PRBool inDisabled,
946                                     ThemeDrawState inDrawState,
947                                     ThemeButtonAdornment inAdornment,
948                                     PRInt32 inState, nsIFrame* aFrame)
950   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
952   HIThemeButtonDrawInfo bdi;
953   bdi.version = 0;
954   bdi.kind = inKind;
955   bdi.value = kThemeButtonOff;
956   bdi.adornment = inAdornment;
958   if (inDisabled)
959     bdi.state = kThemeStateUnavailable;
960   else
961     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
963   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
965   NS_OBJC_END_TRY_ABORT_BLOCK;
968 void
969 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
970                               const HIRect& inBoxRect, PRBool inIsDisabled, PRInt32 inState)
972   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
974   HIThemeFrameDrawInfo fdi;
975   fdi.version = 0;
976   fdi.kind = inKind;
978   // We don't ever set an inactive state for this because it doesn't
979   // look right (see other apps).
980   fdi.state = inIsDisabled ? kThemeStateUnavailable : kThemeStateActive;
982   // for some reason focus rings on listboxes draw incorrectly
983   if (inKind == kHIThemeFrameListBox)
984     fdi.isFocused = 0;
985   else
986     fdi.isFocused = (inState & NS_EVENT_STATE_FOCUS) != 0;
988   // HIThemeDrawFrame takes the rect for the content area of the frame, not
989   // the bounding rect for the frame. Here we reduce the size of the rect we
990   // will pass to make it the size of the content.
991   HIRect drawRect = inBoxRect;
992   if (inKind == kHIThemeFrameTextFieldSquare) {
993     SInt32 frameOutset = 0;
994     ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
995     drawRect.origin.x += frameOutset;
996     drawRect.origin.y += frameOutset;
997     drawRect.size.width -= frameOutset * 2;
998     drawRect.size.height -= frameOutset * 2;
999   }
1000   else if (inKind == kHIThemeFrameListBox) {
1001     SInt32 frameOutset = 0;
1002     ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1003     drawRect.origin.x += frameOutset;
1004     drawRect.origin.y += frameOutset;
1005     drawRect.size.width -= frameOutset * 2;
1006     drawRect.size.height -= frameOutset * 2;
1007   }
1009 #if DRAW_IN_FRAME_DEBUG
1010   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1011   CGContextFillRect(cgContext, inBoxRect);
1012 #endif
1014   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1016   NS_OBJC_END_TRY_ABORT_BLOCK;
1019 void
1020 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1021                                  PRBool inIsIndeterminate, PRBool inIsHorizontal,
1022                                  PRInt32 inValue, PRInt32 inMaxValue,
1023                                  nsIFrame* aFrame)
1025   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1027   HIThemeTrackDrawInfo tdi;
1029   PRInt32 stepsPerSecond = inIsIndeterminate ? 60 : 30;
1030   PRInt32 milliSecondsPerStep = 1000 / stepsPerSecond;
1032   tdi.version = 0;
1033   tdi.kind = inIsIndeterminate ? kThemeMediumIndeterminateBar: kThemeMediumProgressBar;
1034   tdi.bounds = inBoxRect;
1035   tdi.min = 0;
1036   tdi.max = inMaxValue;
1037   tdi.value = inValue;
1038   tdi.attributes = inIsHorizontal ? kThemeTrackHorizontal : 0;
1039   tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1040   tdi.trackInfo.progress.phase = PR_IntervalToMilliseconds(PR_IntervalNow()) /
1041                                  milliSecondsPerStep % 16;
1043   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1045   NS_OBJC_END_TRY_ABORT_BLOCK;
1048 void
1049 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1050                                  nsIFrame* aFrame)
1052   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1054   HIThemeTabPaneDrawInfo tpdi;
1056   tpdi.version = 1;
1057   tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1058   tpdi.direction = kThemeTabNorth;
1059   tpdi.size = kHIThemeTabSizeNormal;
1060   tpdi.kind = kHIThemeTabKindNormal;
1062   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1064   NS_OBJC_END_TRY_ABORT_BLOCK;
1067 void
1068 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1069                               PRBool inIsDisabled, PRInt32 inState,
1070                               PRBool inIsVertical, PRBool inIsReverse,
1071                               PRInt32 inCurrentValue, PRInt32 inMinValue,
1072                               PRInt32 inMaxValue, nsIFrame* aFrame)
1074   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1076   HIThemeTrackDrawInfo tdi;
1078   tdi.version = 0;
1079   tdi.kind = kThemeMediumSlider;
1080   tdi.bounds = inBoxRect;
1081   tdi.min = inMinValue;
1082   tdi.max = inMaxValue;
1083   tdi.value = inCurrentValue;
1084   tdi.attributes = kThemeTrackShowThumb;
1085   if (!inIsVertical)
1086     tdi.attributes |= kThemeTrackHorizontal;
1087   if (inIsReverse)
1088     tdi.attributes |= kThemeTrackRightToLeft;
1089   if (inState & NS_EVENT_STATE_FOCUS)
1090     tdi.attributes |= kThemeTrackHasFocus;
1091   if (inIsDisabled)
1092     tdi.enableState = kThemeTrackDisabled;
1093   else
1094     tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1095   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1096   tdi.trackInfo.slider.pressState = 0;
1098   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1100   NS_OBJC_END_TRY_ABORT_BLOCK;
1103 #define NATURAL_MINI_TAB_BUTTON_HEIGHT    17
1104 #define NATURAL_SMALL_TAB_BUTTON_HEIGHT   20
1105 #define NATURAL_REGULAR_TAB_BUTTON_HEIGHT 23
1107 void
1108 nsNativeThemeCocoa::DrawTab(CGContextRef cgContext, HIRect inBoxRect,
1109                             PRInt32 inState, nsIFrame* aFrame)
1111   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1113   HIThemeTabDrawInfo tdi;
1114   tdi.version = 1;
1115   tdi.kind = kHIThemeTabKindNormal;
1117   PRBool isSelected = IsSelectedTab(aFrame);
1118   PRBool isDisabled = IsDisabled(aFrame);
1119   if (isSelected) {
1120     if (isDisabled) 
1121       tdi.style = kThemeTabFrontUnavailable;
1122     else
1123       tdi.style = FrameIsInActiveWindow(aFrame) ? kThemeTabFront : kThemeTabFrontInactive;
1124   } else {
1125     if (isDisabled)
1126       tdi.style = kThemeTabNonFrontUnavailable;
1127     else if ((inState & NS_EVENT_STATE_ACTIVE) && (inState & NS_EVENT_STATE_HOVER))
1128       tdi.style = kThemeTabNonFrontPressed;
1129     else
1130       tdi.style = FrameIsInActiveWindow(aFrame) ? kThemeTabNonFront : kThemeTabNonFrontInactive;
1131   }
1133   tdi.direction = kThemeTabNorth;
1134   tdi.size = kHIThemeTabSizeNormal;
1135   if (inBoxRect.size.height < NATURAL_REGULAR_TAB_BUTTON_HEIGHT)
1136     tdi.size = kHIThemeTabSizeSmall;
1137   if (inBoxRect.size.height < NATURAL_SMALL_TAB_BUTTON_HEIGHT)
1138     tdi.size = kHIThemeTabSizeMini;
1140   PRBool isRTL = IsFrameRTL(aFrame);
1141   PRBool isFirst = isRTL ? IsLastTab(aFrame) : IsFirstTab(aFrame);
1142   PRBool isLast = isRTL ? IsFirstTab(aFrame) : IsLastTab(aFrame);
1144   if (isFirst && isLast)
1145     tdi.position = kHIThemeTabPositionOnly;
1146   else if (isFirst)
1147     tdi.position = kHIThemeTabPositionFirst;
1148   else if (isLast)
1149     tdi.position = kHIThemeTabPositionLast;
1150   else
1151     tdi.position = kHIThemeTabPositionMiddle;
1153   // Tab separator management:
1154   // Normal tabs only draw their left separator, in the leftmost pixel row of
1155   // their frame. Selected tabs additionally draw their right separator, outside
1156   // of their frame. To prevent overlapping, the tab to the right of the
1157   // selected tab shouldn't draw its left separator.
1158   tdi.adornment = kHIThemeTabAdornmentNone;
1159   if (isRTL ? IsBeforeSelectedTab(aFrame) : IsAfterSelectedTab(aFrame)) {
1160     // On Leopard, the tab's left edge must be shifted 1px to the right.
1161     // On Tiger, this happens automatically when no leading separator is drawn.
1162     inBoxRect.origin.x += 1;
1163     inBoxRect.size.width -= 1;
1164   }
1165   else {
1166     tdi.adornment = kHIThemeTabAdornmentLeadingSeparator;
1167   }
1169   if (isSelected && !isLast) {
1170     tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator;
1171     // On Tiger, the right separator is drawn outside of the frame.
1172     // On Leopard, the right edge must be shifted 1px to the right.
1173     inBoxRect.size.width += 1;
1174   }
1175   
1176   if (inState & NS_EVENT_STATE_FOCUS)
1177     tdi.adornment |= kThemeAdornmentFocus;
1179   HIThemeDrawTab(&inBoxRect, &tdi, cgContext, HITHEME_ORIENTATION, NULL);
1181   NS_OBJC_END_TRY_ABORT_BLOCK;
1184 static inline UInt8
1185 ConvertToPressState(PRInt32 aButtonState, UInt8 aPressState)
1187   // If the button is pressed, return the press state passed in. Otherwise, return 0.
1188   return ((aButtonState & NS_EVENT_STATE_ACTIVE) && (aButtonState & NS_EVENT_STATE_HOVER)) ? aPressState : 0;
1191 void 
1192 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame *aFrame, PRInt32 aButtonStates[])
1194   static nsIContent::AttrValuesArray attributeValues[] = {
1195     &nsWidgetAtoms::scrollbarUpTop,
1196     &nsWidgetAtoms::scrollbarDownTop,
1197     &nsWidgetAtoms::scrollbarUpBottom,
1198     &nsWidgetAtoms::scrollbarDownBottom,
1199     nsnull
1200   };
1202   // Get the state of any scrollbar buttons in our child frames
1203   for (nsIFrame *childFrame = aFrame->GetFirstChild(nsnull); 
1204        childFrame;
1205        childFrame = childFrame->GetNextSibling()) {
1207     nsIContent *childContent = childFrame->GetContent();
1208     if (!childContent) continue;
1209     PRInt32 attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsWidgetAtoms::sbattr, 
1210                                                       attributeValues, eCaseMatters);
1211     if (attrIndex < 0) continue;
1213     PRInt32 currentState = GetContentState(childFrame, NS_THEME_BUTTON);
1214     aButtonStates[attrIndex] = currentState;
1215   }
1218 // Both of the following sets of numbers were derived by loading the testcase in
1219 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1220 // These magic numbers are the minimum sizes we can draw a scrollbar and still 
1221 // have room for everything to display, including the thumb
1222 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1223 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1224 // And these are the minimum sizes if we don't draw the thumb
1225 #define MIN_SCROLLBAR_SIZE 56
1226 #define MIN_SMALL_SCROLLBAR_SIZE 46
1228 void
1229 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, 
1230                                          const CGSize& aSize, PRBool aShouldGetButtonStates)
1232   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1234   PRInt32 curpos = CheckIntAttr(aFrame, nsWidgetAtoms::curpos, 0);
1235   PRInt32 minpos = CheckIntAttr(aFrame, nsWidgetAtoms::minpos, 0);
1236   PRInt32 maxpos = CheckIntAttr(aFrame, nsWidgetAtoms::maxpos, 100);
1237   PRInt32 thumbSize = CheckIntAttr(aFrame, nsWidgetAtoms::pageincrement, 10);
1239   PRBool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::orient, 
1240                                                           nsWidgetAtoms::horizontal, eCaseMatters);
1241   PRBool isSmall = aFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1243   aTdi.version = 0;
1244   aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1245   aTdi.bounds.origin = CGPointZero;
1246   aTdi.bounds.size = aSize;
1247   aTdi.min = minpos;
1248   aTdi.max = maxpos;
1249   aTdi.value = curpos;
1250   aTdi.attributes = 0;
1251   aTdi.enableState = kThemeTrackActive;
1252   if (isHorizontal)
1253     aTdi.attributes |= kThemeTrackHorizontal;
1255   aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
1257   // This should be done early on so things like "kThemeTrackNothingToScroll" can
1258   // override the active enable state.
1259   aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1261   /* Only display features if we have enough room for them.
1262    * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
1263    */
1264   PRInt32 longSideLength = (PRInt32)(isHorizontal ? (aSize.width) : (aSize.height));
1265   if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
1266     aTdi.attributes |= kThemeTrackShowThumb;
1267   }
1268   else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
1269     aTdi.enableState = kThemeTrackNothingToScroll;
1270     return;
1271   }
1273   aTdi.trackInfo.scrollbar.pressState = 0;
1275   // Only go get these scrollbar button states if we need it. For example, there's no reaon to look up scrollbar button 
1276   // states when we're only creating a TrackDrawInfo to determine the size of the thumb.
1277   if (aShouldGetButtonStates) {
1278     PRInt32 buttonStates[] = {0, 0, 0, 0};
1279     GetScrollbarPressStates(aFrame, buttonStates);
1280     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
1281     // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
1282     // the up scrollbar button.
1283     if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
1284       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1285                                             ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
1286                                             ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
1287                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1288     } else {
1289       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1290                                             ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
1291                                             ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
1292                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1293     }
1294   }
1296   NS_OBJC_END_TRY_ABORT_BLOCK;
1299 static void
1300 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1302   HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1303   HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
1306 void
1307 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
1309   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1311   HIThemeTrackDrawInfo tdi;
1312   GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, PR_TRUE); // True means we want the press states
1313   RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
1315   NS_OBJC_END_TRY_ABORT_BLOCK;
1318 nsIFrame*
1319 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
1321   // Walk our parents to find a scrollbar frame
1322   nsIFrame *scrollbarFrame = aFrame;
1323   do {
1324     if (scrollbarFrame->GetType() == nsWidgetAtoms::scrollbarFrame) break;
1325   } while ((scrollbarFrame = scrollbarFrame->GetParent()));
1326   
1327   // We return null if we can't find a parent scrollbar frame
1328   return scrollbarFrame;
1331 static BOOL DrawingAtWindowTop(CGContextRef cgContext, float viewHeight, float yPos)
1333   // Ignore all non-trivial transforms.
1334   CGAffineTransform ctm = CGContextGetCTM(cgContext);
1335   if (ctm.a != 1.0f || ctm.b != 0.0f || ctm.c != 0.0f || ctm.d != -1.0f)
1336     return NO;
1338   // ctm.ty contains the vertical offset from the window's bottom edge.
1339   return ctm.ty - yPos >= viewHeight;
1342 static BOOL
1343 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
1345   return [aWindow isKindOfClass:[ToolbarWindow class]] &&
1346     ![(ToolbarWindow*)aWindow drawsContentsIntoWindowFrame] &&
1347     DrawingAtWindowTop(cgContext, [[aWindow contentView] bounds].size.height,
1348                        inBoxRect.origin.y);
1351 void
1352 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
1353                                        NSWindow* aWindow)
1355   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1357   float titlebarHeight = 0;
1359   if (ToolbarCanBeUnified(cgContext, inBoxRect, aWindow)) {
1360     // Consider the titlebar height when calculating the gradient.
1361     titlebarHeight = [(ToolbarWindow*)aWindow titlebarHeight];
1362     // Notify the window about the toolbar's height so that it can draw the
1363     // correct gradient in the titlebar.
1364     [(ToolbarWindow*)aWindow setUnifiedToolbarHeight:inBoxRect.size.height];
1365   }
1366   
1367   BOOL isMain = [aWindow isMainWindow] || ![NSView focusView];
1369   // Draw the gradient
1370   UnifiedGradientInfo info = { titlebarHeight, inBoxRect.size.height, isMain, NO };
1371   struct CGFunctionCallbacks callbacks = { 0, nsCocoaWindow::UnifiedShading, NULL };
1372   CGFunctionRef function = CGFunctionCreate(&info, 1,  NULL, 4, NULL, &callbacks);
1373   float srcY = inBoxRect.origin.y;
1374   float dstY = srcY + inBoxRect.size.height - 1;
1375   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1376   CGShadingRef shading = CGShadingCreateAxial(colorSpace,
1377                                               CGPointMake(0, srcY),
1378                                               CGPointMake(0, dstY), function,
1379                                               NO, NO);
1380   CGColorSpaceRelease(colorSpace);
1381   CGFunctionRelease(function);
1382   CGContextClipToRect(cgContext, inBoxRect);
1383   CGContextDrawShading(cgContext, shading);
1384   CGShadingRelease(shading);
1386   // Draw the border at the bottom of the toolbar.
1387   CGRect borderRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y +
1388                                  inBoxRect.size.height - 1.0f,
1389                                  inBoxRect.size.width, 1.0f);
1390   DrawNativeGreyColorInRect(cgContext, headerBorderGrey, borderRect, isMain);
1392   NS_OBJC_END_TRY_ABORT_BLOCK;
1395 struct GreyGradientInfo {
1396   float startGrey;
1397   float endGrey;
1400 static void GreyGradientCallback(void* aInfo, const CGFloat* aIn, CGFloat* aOut)
1402   GreyGradientInfo* info = static_cast<GreyGradientInfo*>(aInfo);
1403   CGFloat result = (1.0f - *aIn) * info->startGrey + *aIn * info->endGrey;
1404   aOut[0] = result;
1405   aOut[1] = result;
1406   aOut[2] = result;
1407   aOut[3] = 1.0f;
1410 static void DrawGreyGradient(CGContextRef cgContext, const HIRect& rect,
1411                              float startGrey, float endGrey)
1413   if (rect.size.height <= 0.0f)
1414     return;
1416   GreyGradientInfo info = { startGrey, endGrey };
1417   struct CGFunctionCallbacks callbacks = { 0, GreyGradientCallback, NULL };
1418   CGFunctionRef function = CGFunctionCreate(&info, 1,  NULL, 4, NULL, &callbacks);
1419   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1420   CGShadingRef shading = CGShadingCreateAxial(colorSpace,
1421                                               CGPointMake(0, CGRectGetMinY(rect)),
1422                                               CGPointMake(0, CGRectGetMaxY(rect)),
1423                                               function, false, false);
1424   CGColorSpaceRelease(colorSpace);
1425   CGFunctionRelease(function);
1426   CGContextSaveGState(cgContext);
1427   CGContextClipToRect(cgContext, rect);
1428   CGContextDrawShading(cgContext, shading);
1429   CGContextRestoreGState(cgContext);
1430   CGShadingRelease(shading);
1433 void
1434 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
1435                                   nsIFrame *aFrame)
1437   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1439   if (inBoxRect.size.height < 2.0f)
1440     return;
1442   BOOL isMain = [NativeWindowForFrame(aFrame) isMainWindow] || ![NSView focusView];
1444   // Draw the borders at the top of the statusbar.
1445   CGRect rect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y,
1446                            inBoxRect.size.width, 1.0f);
1447   DrawNativeGreyColorInRect(cgContext, statusbarFirstTopBorderGrey, rect, isMain);
1448   rect.origin.y += 1.0f;
1449   DrawNativeGreyColorInRect(cgContext, statusbarSecondTopBorderGrey, rect, isMain);
1451   // Draw the gradient.
1452   DrawGreyGradient(cgContext, CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y + 2.0f,
1453                                          inBoxRect.size.width, inBoxRect.size.height - 2.0f),
1454                    NativeGreyColorAsFloat(statusbarGradientStartGrey, isMain),
1455                    NativeGreyColorAsFloat(statusbarGradientEndGrey, isMain));
1457   NS_OBJC_END_TRY_ABORT_BLOCK;
1460 static void
1461 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1463   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
1464   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
1467 void
1468 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
1469                                 nsIFrame *aFrame)
1471   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1473   HIThemeGrowBoxDrawInfo drawInfo;
1474   drawInfo.version = 0;
1475   drawInfo.state = kThemeStateActive;
1476   drawInfo.kind = kHIThemeGrowBoxKindNormal;
1477   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
1478   drawInfo.size = kHIThemeGrowBoxSizeNormal;
1480   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
1481                                   IsFrameRTL(aFrame));
1483   NS_OBJC_END_TRY_ABORT_BLOCK;
1486 NS_IMETHODIMP
1487 nsNativeThemeCocoa::DrawWidgetBackground(nsIRenderingContext* aContext, nsIFrame* aFrame,
1488                                          PRUint8 aWidgetType, const nsRect& aRect,
1489                                          const nsRect& aDirtyRect)
1491   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1493   // setup to draw into the correct port
1494   nsCOMPtr<nsIDeviceContext> dctx;
1495   aContext->GetDeviceContext(*getter_AddRefs(dctx));
1496   PRInt32 p2a = dctx->AppUnitsPerDevPixel();
1498   gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1499   gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
1500   nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
1501   nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
1502   nativeWidgetRect.Round();
1503   if (nativeWidgetRect.IsEmpty())
1504     return NS_OK; // Don't attempt to draw invisible widgets.
1506   nsRefPtr<gfxContext> thebesCtx = aContext->ThebesContext();
1507   if (!thebesCtx)
1508     return NS_ERROR_FAILURE;
1510   gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect);
1512   CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
1513   if (cgContext == nsnull) {
1514     // The Quartz surface handles 0x0 surfaces by internally
1515     // making all operations no-ops; there's no cgcontext created for them.
1516     // Unfortunately, this means that callers that want to render
1517     // directly to the CGContext need to be aware of this quirk.
1518     return NS_OK;
1519   }
1521 #if 0
1522   if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
1523     fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
1524             aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
1525     fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
1526             mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
1527     fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
1528             mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
1529     CGAffineTransform mm = CGContextGetCTM(cgContext);
1530     fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
1531             mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
1532   }
1533 #endif
1535   CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
1536                               nativeWidgetRect.Width(), nativeWidgetRect.Height());
1538 #if 0
1539   fprintf(stderr, "    --> macRect %f %f %f %f\n",
1540           macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
1541   CGRect bounds = CGContextGetClipBoundingBox(cgContext);
1542   fprintf(stderr, "    --> clip bounds: %f %f %f %f\n",
1543           bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
1545   //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
1546   //CGContextFillRect(cgContext, bounds);
1547 #endif
1549   PRInt32 eventState = GetContentState(aFrame, aWidgetType);
1551   switch (aWidgetType) {
1552     case NS_THEME_DIALOG: {
1553       HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
1554       CGContextFillRect(cgContext, macRect);
1555     }
1556       break;
1558     case NS_THEME_MENUPOPUP: {
1559       HIThemeMenuDrawInfo mdi = {
1560         version: 0,
1561         menuType: IsDisabled(aFrame) ? kThemeMenuTypeInactive : kThemeMenuTypePopUp
1562       };
1564       PRBool isLeftOfParent = PR_FALSE;
1565       if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
1566         mdi.menuType = kThemeMenuTypeHierarchical;
1567       }
1568       
1569       // The rounded corners draw outside the frame.
1570       CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
1571                                        macRect.size.width, macRect.size.height - 8);
1572       HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
1573     }
1574       break;
1576     case NS_THEME_MENUITEM: {
1577       // Clear the background to get correct transparency.
1578       CGContextClearRect(cgContext, macRect);
1580       // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
1581       HIThemeMenuItemDrawInfo drawInfo = {
1582         version: 0,
1583         itemType: kThemeMenuItemPlain,
1584         state: (IsDisabled(aFrame) ? kThemeMenuDisabled :
1585                 CheckBooleanAttr(aFrame, nsWidgetAtoms::mozmenuactive) ? kThemeMenuSelected :
1586                 kThemeMenuActive)
1587       };
1589       // XXX pass in the menu rect instead of always using the item rect
1590       HIRect ignored;
1591       HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
1592     }
1593       break;
1595     case NS_THEME_MENUSEPARATOR: {
1596       ThemeMenuState menuState;
1597       if (IsDisabled(aFrame)) {
1598         menuState = kThemeMenuDisabled;
1599       }
1600       else {
1601         menuState = CheckBooleanAttr(aFrame, nsWidgetAtoms::mozmenuactive) ?
1602                     kThemeMenuSelected : kThemeMenuActive;
1603       }
1605       HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
1606       HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
1607     }
1608       break;
1610     case NS_THEME_TOOLTIP:
1611       CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
1612       CGContextFillRect(cgContext, macRect);
1613       break;
1615     case NS_THEME_CHECKBOX:
1616     case NS_THEME_RADIO: {
1617       PRBool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
1618       DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
1619                           IsDisabled(aFrame), eventState, aFrame);
1620     }
1621       break;
1623     case NS_THEME_BUTTON:
1624       if (IsDefaultButton(aFrame)) {
1625         DrawButton(cgContext, kThemePushButton, macRect, true, IsDisabled(aFrame), 
1626                    kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
1627       } else if (IsButtonTypeMenu(aFrame)) {
1628         DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
1629       } else {
1630         DrawPushButton(cgContext, macRect, IsDisabled(aFrame), eventState, aFrame);
1631       }
1632       break;
1634     case NS_THEME_BUTTON_BEVEL:
1635       DrawButton(cgContext, kThemeMediumBevelButton, macRect,
1636                  IsDefaultButton(aFrame), IsDisabled(aFrame), 
1637                  kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
1638       break;
1640     case NS_THEME_SPINNER: {
1641       ThemeDrawState state = kThemeStateActive;
1642       nsIContent* content = aFrame->GetContent();
1643       if (content->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::state,
1644                                NS_LITERAL_STRING("up"), eCaseMatters)) {
1645         state = kThemeStatePressedUp;
1646       }
1647       else if (content->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::state,
1648                                     NS_LITERAL_STRING("down"), eCaseMatters)) {
1649         state = kThemeStatePressedDown;
1650       }
1652       DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, IsDisabled(aFrame),
1653                       state, kThemeAdornmentNone, eventState, aFrame);
1654     }
1655       break;
1657     case NS_THEME_TOOLBAR_BUTTON:
1658       DrawButton(cgContext, kThemePushButton, macRect,
1659                  IsDefaultButton(aFrame), IsDisabled(aFrame),
1660                  kThemeButtonOn, kThemeAdornmentNone, eventState, aFrame);
1661       break;
1663     case NS_THEME_TOOLBAR_SEPARATOR: {
1664       HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
1665       HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
1666     }
1667       break;
1669     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
1670       DrawUnifiedToolbar(cgContext, macRect, NativeWindowForFrame(aFrame));
1671       break;
1673     case NS_THEME_TOOLBAR: {
1674       NSWindow* win = NativeWindowForFrame(aFrame);
1675       if (ToolbarCanBeUnified(cgContext, macRect, win)) {
1676         DrawUnifiedToolbar(cgContext, macRect, win);
1677         break;
1678       }
1679       BOOL isMain = [win isMainWindow] || ![NSView focusView];
1680       CGRect drawRect = macRect;
1682       // top border
1683       drawRect.size.height = 1.0f;
1684       DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
1686       // background
1687       drawRect.origin.y += drawRect.size.height;
1688       drawRect.size.height = macRect.size.height - 2.0f;
1689       DrawNativeGreyColorInRect(cgContext, headerEndGrey, drawRect, isMain);
1691       // bottom border
1692       drawRect.origin.y += drawRect.size.height;
1693       drawRect.size.height = 1.0f;
1694       DrawNativeGreyColorInRect(cgContext, headerBorderGrey, drawRect, isMain);
1695     }
1696       break;
1698     case NS_THEME_TOOLBOX: {
1699       HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
1700       HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
1701     }
1702       break;
1704     case NS_THEME_STATUSBAR: 
1705       DrawStatusBar(cgContext, macRect, aFrame);
1706       break;
1708     case NS_THEME_DROPDOWN:
1709     case NS_THEME_DROPDOWN_TEXTFIELD:
1710       DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
1711       break;
1713     case NS_THEME_DROPDOWN_BUTTON:
1714       DrawButton(cgContext, kThemeArrowButton, macRect, PR_FALSE,
1715                  IsDisabled(aFrame), kThemeButtonOn,
1716                  kThemeAdornmentArrowDownArrow, eventState, aFrame);
1717       break;
1719     case NS_THEME_GROUPBOX: {
1720       HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
1721       HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
1722       break;
1723     }
1725     case NS_THEME_TEXTFIELD:
1726       // HIThemeSetFill is not available on 10.3
1727       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1728       CGContextFillRect(cgContext, macRect);
1730       // XUL textboxes set the native appearance on the containing box, while
1731       // concrete focus is set on the html:input element within it. We can
1732       // though, check the focused attribute of xul textboxes in this case.
1733       if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
1734         eventState |= NS_EVENT_STATE_FOCUS;
1735       }
1737       DrawFrame(cgContext, kHIThemeFrameTextFieldSquare,
1738                 macRect, (IsDisabled(aFrame) || IsReadOnly(aFrame)), eventState);
1739       break;
1740       
1741     case NS_THEME_SEARCHFIELD:
1742       DrawSearchField(cgContext, macRect, aFrame);
1743       break;
1745     case NS_THEME_PROGRESSBAR:
1746       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame),
1747                    PR_TRUE, GetProgressValue(aFrame),
1748                    GetProgressMaxValue(aFrame), aFrame);
1749       break;
1751     case NS_THEME_PROGRESSBAR_VERTICAL:
1752       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame),
1753                    PR_FALSE, GetProgressValue(aFrame),
1754                    GetProgressMaxValue(aFrame), aFrame);
1755       break;
1757     case NS_THEME_PROGRESSBAR_CHUNK:
1758     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
1759       // do nothing, covered by the progress bar cases above
1760       break;
1762     case NS_THEME_TREEVIEW_TWISTY:
1763       DrawButton(cgContext, kThemeDisclosureButton, macRect, PR_FALSE, IsDisabled(aFrame), 
1764                  kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
1765       break;
1767     case NS_THEME_TREEVIEW_TWISTY_OPEN:
1768       DrawButton(cgContext, kThemeDisclosureButton, macRect, PR_FALSE, IsDisabled(aFrame), 
1769                  kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
1770       break;
1772     case NS_THEME_TREEVIEW_HEADER_CELL: {
1773       TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
1774       DrawButton(cgContext, kThemeListHeaderButton, macRect, PR_FALSE, IsDisabled(aFrame), 
1775                  sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
1776                  sortDirection == eTreeSortDirection_Ascending ?
1777                  kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);      
1778     }
1779       break;
1781     case NS_THEME_TREEVIEW_TREEITEM:
1782     case NS_THEME_TREEVIEW:
1783       // HIThemeSetFill is not available on 10.3
1784       // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
1785       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1786       CGContextFillRect(cgContext, macRect);
1787       break;
1789     case NS_THEME_TREEVIEW_HEADER:
1790       // do nothing, taken care of by individual header cells
1791     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
1792       // do nothing, taken care of by treeview header
1793     case NS_THEME_TREEVIEW_LINE:
1794       // do nothing, these lines don't exist on macos
1795       break;
1797     case NS_THEME_SCALE_HORIZONTAL:
1798     case NS_THEME_SCALE_VERTICAL: {
1799       PRInt32 curpos = CheckIntAttr(aFrame, nsWidgetAtoms::curpos, 0);
1800       PRInt32 minpos = CheckIntAttr(aFrame, nsWidgetAtoms::minpos, 0);
1801       PRInt32 maxpos = CheckIntAttr(aFrame, nsWidgetAtoms::maxpos, 100);
1802       if (!maxpos)
1803         maxpos = 100;
1805       PRBool reverse = aFrame->GetContent()->
1806         AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::dir,
1807                     NS_LITERAL_STRING("reverse"), eCaseMatters);
1808       DrawScale(cgContext, macRect, IsDisabled(aFrame), eventState,
1809                 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
1810                 curpos, minpos, maxpos, aFrame);
1811     }
1812       break;
1814     case NS_THEME_SCALE_THUMB_HORIZONTAL:
1815     case NS_THEME_SCALE_THUMB_VERTICAL:
1816       // do nothing, drawn by scale
1817       break;
1819     case NS_THEME_SCROLLBAR_SMALL:
1820     case NS_THEME_SCROLLBAR: {
1821       DrawScrollbar(cgContext, macRect, aFrame);
1822     }
1823       break;
1824     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
1825     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
1826 #if SCROLLBARS_VISUAL_DEBUG
1827       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 0, 0.6);
1828       CGContextFillRect(cgContext, macRect);
1829     break;
1830 #endif
1831     case NS_THEME_SCROLLBAR_BUTTON_UP:
1832     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
1833 #if SCROLLBARS_VISUAL_DEBUG
1834       CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
1835       CGContextFillRect(cgContext, macRect);
1836     break;
1837 #endif
1838     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
1839     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
1840 #if SCROLLBARS_VISUAL_DEBUG
1841       CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
1842       CGContextFillRect(cgContext, macRect);
1843     break;      
1844 #endif
1845     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1846     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1847       // do nothing, drawn by scrollbar
1848       break;
1850     case NS_THEME_TEXTFIELD_MULTILINE: {
1851       // we have to draw this by hand because there is no HITheme value for it
1852       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1853       
1854       CGContextFillRect(cgContext, macRect);
1856       CGContextSetLineWidth(cgContext, 1.0);
1857       CGContextSetShouldAntialias(cgContext, false);
1859       // stroke everything but the top line of the text area
1860       CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
1861       CGContextBeginPath(cgContext);
1862       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
1863       CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
1864       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
1865       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
1866       CGContextStrokePath(cgContext);
1868       // stroke the line across the top of the text area
1869       CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
1870       CGContextBeginPath(cgContext);
1871       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
1872       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
1873       CGContextStrokePath(cgContext);
1875       // draw a focus ring
1876       if (eventState & NS_EVENT_STATE_FOCUS) {
1877         // We need to bring the rectangle in by 1 pixel on each side.
1878         CGRect cgr = CGRectMake(macRect.origin.x + 1,
1879                                 macRect.origin.y + 1,
1880                                 macRect.size.width - 2,
1881                                 macRect.size.height - 2);
1882         HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
1883       }
1884     }
1885       break;
1887     case NS_THEME_LISTBOX:
1888       // HIThemeSetFill is not available on 10.3
1889       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1890       CGContextFillRect(cgContext, macRect);
1891       DrawFrame(cgContext, kHIThemeFrameListBox, macRect,
1892                 (IsDisabled(aFrame) || IsReadOnly(aFrame)), eventState);
1893       break;
1894     
1895     case NS_THEME_TAB:
1896       DrawTab(cgContext, macRect, eventState, aFrame);
1897       break;
1899     case NS_THEME_TAB_PANELS:
1900       DrawTabPanel(cgContext, macRect, aFrame);
1901       break;
1903     case NS_THEME_RESIZER:
1904       DrawResizer(cgContext, macRect, aFrame);
1905       break;
1906   }
1908   nativeDrawing.EndNativeDrawing();
1910   return NS_OK;
1912   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1915 nsIntMargin
1916 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
1918   if (IsFrameRTL(aFrame))
1919     return nsIntMargin(aMargin.right, aMargin.top, aMargin.left, aMargin.bottom);
1921   return aMargin;
1924 static const nsIntMargin kAquaDropdownBorder(5, 1, 22, 2);
1925 static const nsIntMargin kAquaComboboxBorder(4, 3, 20, 3);
1926 static const nsIntMargin kAquaSearchfieldBorder(19, 3, 5, 2);
1928 NS_IMETHODIMP
1929 nsNativeThemeCocoa::GetWidgetBorder(nsIDeviceContext* aContext, 
1930                                     nsIFrame* aFrame,
1931                                     PRUint8 aWidgetType,
1932                                     nsIntMargin* aResult)
1934   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1936   aResult->SizeTo(0, 0, 0, 0);
1938   switch (aWidgetType) {
1939     case NS_THEME_BUTTON:
1940     {
1941       if (IsButtonTypeMenu(aFrame)) {
1942         *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
1943       } else {
1944         aResult->SizeTo(7, 1, 7, 3);
1945       }
1946       break;
1947     }
1949     case NS_THEME_CHECKBOX:
1950     case NS_THEME_RADIO:
1951     {
1952       // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
1953       // assume a border width of 2px.
1954       aResult->SizeTo(2, 2, 2, 2);
1955       break;
1956     }
1958     case NS_THEME_DROPDOWN:
1959     case NS_THEME_DROPDOWN_BUTTON:
1960       *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
1961       break;
1963     case NS_THEME_DROPDOWN_TEXTFIELD:
1964       *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
1965       break;
1967     case NS_THEME_TEXTFIELD:
1968     {
1969       SInt32 frameOutset = 0;
1970       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1972       SInt32 textPadding = 0;
1973       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
1975       frameOutset += textPadding;
1977       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
1978       break;
1979     }
1981     case NS_THEME_TEXTFIELD_MULTILINE:
1982       aResult->SizeTo(1, 1, 1, 1);
1983       break;
1985     case NS_THEME_SEARCHFIELD:
1986       *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
1987       break;
1989     case NS_THEME_LISTBOX:
1990     {
1991       SInt32 frameOutset = 0;
1992       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1993       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
1994       break;
1995     }
1997     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1998     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1999     {
2000       // There's only an endcap to worry about when both arrows are on the bottom
2001       NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2002       if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2003         PRBool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2005         nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2006         if (!scrollbarFrame) return NS_ERROR_FAILURE;
2007         PRBool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2009         // There isn't a metric for this, so just hardcode a best guess at the value.
2010         // This value is even less exact due to the fact that the endcap is partially concave.
2011         PRInt32 endcapSize = isSmall ? 5 : 6;
2013         if (isHorizontal)
2014           aResult->SizeTo(endcapSize, 0, 0, 0);
2015         else
2016           aResult->SizeTo(0, endcapSize, 0, 0);
2017       }
2018       break;
2019     }
2021     case NS_THEME_STATUSBAR:
2022       aResult->SizeTo(0, 1, 0, 0);
2023       break;
2024   }
2026   return NS_OK;
2028   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2031 // Return PR_FALSE here to indicate that CSS padding values should be used. There is
2032 // no reason to make a distinction between padding and border values, just specify
2033 // whatever values you want in GetWidgetBorder and only use this to return PR_TRUE
2034 // if you want to override CSS padding values.
2035 PRBool
2036 nsNativeThemeCocoa::GetWidgetPadding(nsIDeviceContext* aContext, 
2037                                      nsIFrame* aFrame,
2038                                      PRUint8 aWidgetType,
2039                                      nsIntMargin* aResult)
2041   // We don't want CSS padding being used for certain widgets.
2042   // See bug 381639 for an example of why.
2043   switch (aWidgetType) {
2044     case NS_THEME_BUTTON:
2045     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2046     // and have a meaningful baseline, so they can't have
2047     // author-specified padding.
2048     case NS_THEME_CHECKBOX:
2049     case NS_THEME_RADIO:
2050       aResult->SizeTo(0, 0, 0, 0);
2051       return PR_TRUE;
2052   }
2053   return PR_FALSE;
2056 PRBool
2057 nsNativeThemeCocoa::GetWidgetOverflow(nsIDeviceContext* aContext, nsIFrame* aFrame,
2058                                       PRUint8 aWidgetType, nsRect* aOverflowRect)
2060   switch (aWidgetType) {
2061     case NS_THEME_BUTTON:
2062     case NS_THEME_TEXTFIELD:
2063     case NS_THEME_TEXTFIELD_MULTILINE:
2064     case NS_THEME_SEARCHFIELD:
2065     case NS_THEME_LISTBOX:
2066     case NS_THEME_DROPDOWN:
2067     case NS_THEME_DROPDOWN_BUTTON:
2068     case NS_THEME_DROPDOWN_TEXTFIELD:
2069     case NS_THEME_CHECKBOX:
2070     case NS_THEME_RADIO:
2071     case NS_THEME_TAB:
2072     {
2073       // We assume that the above widgets can draw a focus ring that will be less than
2074       // or equal to 4 pixels thick.
2075       nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
2076       PRInt32 p2a = aContext->AppUnitsPerDevPixel();
2077       nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a),
2078                  NSIntPixelsToAppUnits(extraSize.top, p2a),
2079                  NSIntPixelsToAppUnits(extraSize.right, p2a),
2080                  NSIntPixelsToAppUnits(extraSize.bottom, p2a));
2081       aOverflowRect->Inflate(m);
2082       return PR_TRUE;
2083     }
2084   }
2086   return PR_FALSE;
2089 static const PRInt32 kRegularScrollbarThumbMinSize = 22;
2090 static const PRInt32 kSmallScrollbarThumbMinSize = 19;
2092 NS_IMETHODIMP
2093 nsNativeThemeCocoa::GetMinimumWidgetSize(nsIRenderingContext* aContext,
2094                                          nsIFrame* aFrame,
2095                                          PRUint8 aWidgetType,
2096                                          nsIntSize* aResult,
2097                                          PRBool* aIsOverridable)
2099   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2101   aResult->SizeTo(0,0);
2102   *aIsOverridable = PR_TRUE;
2104   switch (aWidgetType) {
2105     case NS_THEME_BUTTON:
2106     {
2107       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2108                       pushButtonSettings.naturalSizes[miniControlSize].height);
2109       break;
2110     }
2112     case NS_THEME_SPINNER:
2113     {
2114       SInt32 buttonHeight = 0, buttonWidth = 0;
2115       ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2116       ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2117       aResult->SizeTo(buttonWidth, buttonHeight);
2118       *aIsOverridable = PR_FALSE;
2119       break;
2120     }
2122     case NS_THEME_DROPDOWN:
2123     case NS_THEME_DROPDOWN_BUTTON:
2124     {
2125       SInt32 popupHeight = 0;
2126       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2127       aResult->SizeTo(0, popupHeight);
2128       break;
2129     }
2131     case NS_THEME_TEXTFIELD:
2132     case NS_THEME_TEXTFIELD_MULTILINE:
2133     case NS_THEME_SEARCHFIELD:
2134     {
2135       // at minimum, we should be tall enough for 9pt text.
2136       // I'm using hardcoded values here because the appearance manager
2137       // values for the frame size are incorrect.
2138       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2139       break;
2140     }
2141       
2142     case NS_THEME_PROGRESSBAR:
2143     {
2144       SInt32 barHeight = 0;
2145       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2146       aResult->SizeTo(0, barHeight);
2147       break;
2148     }
2150     case NS_THEME_TREEVIEW_TWISTY:
2151     case NS_THEME_TREEVIEW_TWISTY_OPEN:   
2152     {
2153       SInt32 twistyHeight = 0, twistyWidth = 0;
2154       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2155       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2156       aResult->SizeTo(twistyWidth, twistyHeight);
2157       *aIsOverridable = PR_FALSE;
2158       break;
2159     }
2160     
2161     case NS_THEME_TREEVIEW_HEADER:
2162     case NS_THEME_TREEVIEW_HEADER_CELL:
2163     {
2164       SInt32 headerHeight = 0;
2165       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2166       aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
2167       break;
2168     }
2170     case NS_THEME_TAB:
2171     {
2172       aResult->SizeTo(0, NATURAL_MINI_TAB_BUTTON_HEIGHT);
2173       break;
2174     }
2176     case NS_THEME_SCALE_HORIZONTAL:
2177     {
2178       SInt32 scaleHeight = 0;
2179       ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
2180       aResult->SizeTo(scaleHeight, scaleHeight);
2181       *aIsOverridable = PR_FALSE;
2182       break;
2183     }
2185     case NS_THEME_SCALE_VERTICAL:
2186     {
2187       SInt32 scaleWidth = 0;
2188       ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
2189       aResult->SizeTo(scaleWidth, scaleWidth);
2190       *aIsOverridable = PR_FALSE;
2191       break;
2192     }
2193       
2194     case NS_THEME_SCROLLBAR_SMALL:
2195     {
2196       SInt32 scrollbarWidth = 0;
2197       ::GetThemeMetric(kThemeMetricSmallScrollBarWidth, &scrollbarWidth);
2198       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2199       *aIsOverridable = PR_FALSE;
2200       break;
2201     }
2203     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2204     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2205     {
2206       // Find our parent scrollbar frame in order to find out whether we're in
2207       // a small or a large scrollbar.
2208       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2209       if (!scrollbarFrame)
2210         return NS_ERROR_FAILURE;
2212       PRBool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2213       PRBool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2214       PRInt32& minSize = isHorizontal ? aResult->width : aResult->height;
2215       minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
2216       break;
2217     }
2219     case NS_THEME_SCROLLBAR:
2220     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2221     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2222     {
2223       // yeah, i know i'm cheating a little here, but i figure that it
2224       // really doesn't matter if the scrollbar is vertical or horizontal
2225       // and the width metric is a really good metric for every piece
2226       // of the scrollbar.
2228       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2229       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2231       PRInt32 themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2232                             kThemeMetricSmallScrollBarWidth :
2233                             kThemeMetricScrollBarWidth;
2234       SInt32 scrollbarWidth = 0;
2235       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2236       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2237       *aIsOverridable = PR_FALSE;
2238       break;
2239     }
2241     case NS_THEME_SCROLLBAR_BUTTON_UP:
2242     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2243     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2244     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2245     {
2246       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2247       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2249       // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
2250       PRInt32 themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2251                             kThemeMetricSmallScrollBarWidth :
2252                             kThemeMetricScrollBarWidth;
2253       SInt32 scrollbarWidth = 0;
2254       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2256       // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
2257       if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
2258         aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
2259       else
2260         aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
2262       *aIsOverridable = PR_FALSE;
2263       break;
2264     }
2265     case NS_THEME_RESIZER:
2266     {
2267       HIThemeGrowBoxDrawInfo drawInfo;
2268       drawInfo.version = 0;
2269       drawInfo.state = kThemeStateActive;
2270       drawInfo.kind = kHIThemeGrowBoxKindNormal;
2271       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2272       drawInfo.size = kHIThemeGrowBoxSizeNormal;
2273       HIPoint pnt = { 0, 0 };
2274       HIRect bounds;
2275       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
2276       aResult->SizeTo(bounds.size.width, bounds.size.height);
2277     }
2278   }
2280   return NS_OK;
2282   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2285 NS_IMETHODIMP
2286 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, PRUint8 aWidgetType, 
2287                                      nsIAtom* aAttribute, PRBool* aShouldRepaint)
2289   // Some widget types just never change state.
2290   switch (aWidgetType) {
2291     case NS_THEME_TOOLBOX:
2292     case NS_THEME_TOOLBAR:
2293     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2294     case NS_THEME_TOOLBAR_BUTTON:
2295     case NS_THEME_SCROLLBAR_TRACK_VERTICAL: 
2296     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2297     case NS_THEME_STATUSBAR:
2298     case NS_THEME_STATUSBAR_PANEL:
2299     case NS_THEME_STATUSBAR_RESIZER_PANEL:
2300     case NS_THEME_TOOLTIP:
2301     case NS_THEME_TAB_PANELS:
2302     case NS_THEME_TAB_PANEL:
2303     case NS_THEME_DIALOG:
2304     case NS_THEME_MENUPOPUP:
2305     case NS_THEME_GROUPBOX:
2306       *aShouldRepaint = PR_FALSE;
2307       return NS_OK;
2308     case NS_THEME_PROGRESSBAR_CHUNK:
2309     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2310     case NS_THEME_PROGRESSBAR:
2311     case NS_THEME_PROGRESSBAR_VERTICAL:
2312       *aShouldRepaint = (aAttribute == nsWidgetAtoms::step);
2313       return NS_OK;
2314   }
2316   // XXXdwh Not sure what can really be done here.  Can at least guess for
2317   // specific widgets that they're highly unlikely to have certain states.
2318   // For example, a toolbar doesn't care about any states.
2319   if (!aAttribute) {
2320     // Hover/focus/active changed.  Always repaint.
2321     *aShouldRepaint = PR_TRUE;
2322   } else {
2323     // Check the attribute to see if it's relevant.  
2324     // disabled, checked, dlgtype, default, etc.
2325     *aShouldRepaint = PR_FALSE;
2326     if (aAttribute == nsWidgetAtoms::disabled ||
2327         aAttribute == nsWidgetAtoms::checked ||
2328         aAttribute == nsWidgetAtoms::selected ||
2329         aAttribute == nsWidgetAtoms::mozmenuactive ||
2330         aAttribute == nsWidgetAtoms::sortdirection ||
2331         aAttribute == nsWidgetAtoms::focused ||
2332         aAttribute == nsWidgetAtoms::_default ||
2333         aAttribute == nsWidgetAtoms::step ||
2334         aAttribute == nsWidgetAtoms::open)
2335       *aShouldRepaint = PR_TRUE;
2336   }
2338   return NS_OK;
2341 NS_IMETHODIMP
2342 nsNativeThemeCocoa::ThemeChanged()
2344   // This is unimplemented because we don't care if gecko changes its theme
2345   // and Mac OS X doesn't have themes.
2346   return NS_OK;
2349 PRBool 
2350 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
2351                                       PRUint8 aWidgetType)
2353   // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
2354   // render natively even if native theme support is disabled.
2355   if (aWidgetType != NS_THEME_SCROLLBAR &&
2356       aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
2357     return PR_FALSE;
2359   // if this is a dropdown button in a combobox the answer is always no
2360   if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
2361     nsIFrame* parentFrame = aFrame->GetParent();
2362     if (parentFrame && (parentFrame->GetType() == nsWidgetAtoms::comboboxControlFrame))
2363       return PR_FALSE;
2364   }
2366   switch (aWidgetType) {
2367     case NS_THEME_LISTBOX:
2369     case NS_THEME_DIALOG:
2370     case NS_THEME_WINDOW:
2371     case NS_THEME_MENUPOPUP:
2372     case NS_THEME_MENUITEM:
2373     case NS_THEME_MENUSEPARATOR:
2374     case NS_THEME_TOOLTIP:
2375     case NS_THEME_RESIZER:
2376     
2377     case NS_THEME_CHECKBOX:
2378     case NS_THEME_CHECKBOX_CONTAINER:
2379     case NS_THEME_RADIO:
2380     case NS_THEME_RADIO_CONTAINER:
2381     case NS_THEME_GROUPBOX:
2382     case NS_THEME_BUTTON:
2383     case NS_THEME_BUTTON_BEVEL:
2384     case NS_THEME_SPINNER:
2385     case NS_THEME_TOOLBAR:
2386     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2387     case NS_THEME_STATUSBAR:
2388     case NS_THEME_TEXTFIELD:
2389     case NS_THEME_TEXTFIELD_MULTILINE:
2390     case NS_THEME_SEARCHFIELD:
2391     //case NS_THEME_TOOLBOX:
2392     //case NS_THEME_TOOLBAR_BUTTON:
2393     case NS_THEME_PROGRESSBAR:
2394     case NS_THEME_PROGRESSBAR_VERTICAL:
2395     case NS_THEME_PROGRESSBAR_CHUNK:
2396     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2397     case NS_THEME_TOOLBAR_SEPARATOR:
2398     
2399     case NS_THEME_TAB_PANELS:
2400     case NS_THEME_TAB:
2401     
2402     case NS_THEME_TREEVIEW_TWISTY:
2403     case NS_THEME_TREEVIEW_TWISTY_OPEN:
2404     case NS_THEME_TREEVIEW:
2405     case NS_THEME_TREEVIEW_HEADER:
2406     case NS_THEME_TREEVIEW_HEADER_CELL:
2407     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2408     case NS_THEME_TREEVIEW_TREEITEM:
2409     case NS_THEME_TREEVIEW_LINE:
2411     case NS_THEME_SCALE_HORIZONTAL:
2412     case NS_THEME_SCALE_THUMB_HORIZONTAL:
2413     case NS_THEME_SCALE_VERTICAL:
2414     case NS_THEME_SCALE_THUMB_VERTICAL:
2416     case NS_THEME_SCROLLBAR:
2417     case NS_THEME_SCROLLBAR_SMALL:
2418     case NS_THEME_SCROLLBAR_BUTTON_UP:
2419     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2420     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2421     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2422     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2423     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2424     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2425     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2427     case NS_THEME_DROPDOWN:
2428     case NS_THEME_DROPDOWN_BUTTON:
2429     case NS_THEME_DROPDOWN_TEXT:
2430     case NS_THEME_DROPDOWN_TEXTFIELD:
2431       return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
2432       break;
2433   }
2435   return PR_FALSE;
2438 PRBool
2439 nsNativeThemeCocoa::WidgetIsContainer(PRUint8 aWidgetType)
2441   // flesh this out at some point
2442   switch (aWidgetType) {
2443    case NS_THEME_DROPDOWN_BUTTON:
2444    case NS_THEME_RADIO:
2445    case NS_THEME_CHECKBOX:
2446    case NS_THEME_PROGRESSBAR:
2447     return PR_FALSE;
2448     break;
2449   }
2450   return PR_TRUE;
2453 PRBool
2454 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsPresContext* aPresContext, nsIFrame* aFrame, PRUint8 aWidgetType)
2456   if (aWidgetType == NS_THEME_DROPDOWN ||
2457       aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
2458       aWidgetType == NS_THEME_BUTTON ||
2459       aWidgetType == NS_THEME_RADIO ||
2460       aWidgetType == NS_THEME_CHECKBOX)
2461     return PR_TRUE;
2463   return PR_FALSE;
2466 PRBool
2467 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
2469   return PR_FALSE;
2472 nsTransparencyMode
2473 nsNativeThemeCocoa::GetWidgetTransparency(PRUint8 aWidgetType)
2475   if (aWidgetType == NS_THEME_MENUPOPUP ||
2476       aWidgetType == NS_THEME_TOOLTIP)
2477     return eTransparencyTransparent;
2479   return eTransparencyOpaque;