CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / widget / src / cocoa / nsNativeThemeCocoa.mm
blob2d19c32d5292720ef2c97052d609652ec409b528
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"
63 #include "nsIScrollableFrame.h"
65 #include "gfxContext.h"
66 #include "gfxQuartzSurface.h"
67 #include "gfxQuartzNativeDrawing.h"
69 #define DRAW_IN_FRAME_DEBUG 0
70 #define SCROLLBARS_VISUAL_DEBUG 0
72 // private Quartz routines needed here
73 extern "C" {
74   CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
77 // Workaround for NSCell control tint drawing
78 // Without this workaround, NSCells are always drawn with the clear control tint
79 // as long as they're not attached to an NSControl which is a subview of an active window.
80 // XXXmstange Why doesn't Webkit need this?
81 @implementation NSCell (ControlTintWorkaround)
82 - (int)_realControlTint { return [self controlTint]; }
83 @end
85 // The purpose of this class is to provide objects that can be used when drawing
86 // NSCells using drawWithFrame:inView: without causing any harm. The only
87 // messages that will be sent to such an object are "isFlipped" and
88 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
89 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
90 // NSView) will be called when drawing search fields, and we only provide it in
91 // order to prevent "unrecognized selector" exceptions.
92 // There's no need to pass the actual NSView that we're drawing into to
93 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
94 // invalidations as soon as we draw a focusring!
95 @interface CellDrawView : NSView
97 @end;
99 @implementation CellDrawView
101 - (BOOL)isFlipped
103   return YES;
106 - (NSText*)currentEditor
108   return nil;
111 @end
113 // Workaround for Bug 542048
114 // On 64-bit, NSSearchFieldCells don't draw focus rings.
115 #if defined(__x86_64__)
117 static void DrawFocusRing(NSRect rect, float radius)
119   NSSetFocusRingStyle(NSFocusRingOnly);
120   NSBezierPath* path = [NSBezierPath bezierPath];
121   rect = NSInsetRect(rect, radius, radius);
122   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
123   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
124   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle:  0.0 endAngle: 90.0];
125   [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0];
126   [path closePath];
127   [path fill];
130 @interface SearchFieldCellWithFocusRing : NSSearchFieldCell {} @end
132 @implementation SearchFieldCellWithFocusRing
134 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
136   [super drawWithFrame:rect inView:controlView];
137   if ([self showsFirstResponder]) {
138     DrawFocusRing(rect, NSHeight(rect) / 2);
139   }
142 @end
143   
144 #endif
146 // Copied from nsLookAndFeel.h
147 // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
148 static const int kThemeScrollBarArrowsBoth = 2;
150 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
151 #define MAX_FOCUS_RING_WIDTH 4
153 // These enums are for indexing into the margin array.
154 enum {
155   leopardOS
158 enum {
159   miniControlSize,
160   smallControlSize,
161   regularControlSize
164 enum {
165   leftMargin,
166   topMargin,
167   rightMargin,
168   bottomMargin
171 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
172   if (cocoaControlSize == NSMiniControlSize)
173     return miniControlSize;
174   else if (cocoaControlSize == NSSmallControlSize)
175     return smallControlSize;
176   else
177     return regularControlSize;
180 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
182   if (!marginSet)
183     return;
185   static int osIndex = leopardOS;
186   int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
187   const float* buttonMargins = marginSet[osIndex][controlSize];
188   rect->origin.x -= buttonMargins[leftMargin];
189   rect->origin.y -= buttonMargins[bottomMargin];
190   rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
191   rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
194 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
195                                       nsIWidget** aTopLevelWidget = NULL)
197   if (!aFrame)
198     return nil;  
200   nsIWidget* widget = aFrame->GetNearestWidget();
201   if (!widget)
202     return nil;
204   nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
205   if (aTopLevelWidget)
206     *aTopLevelWidget = topLevelWidget;
208   return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
211 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
213   nsIWidget* topLevelWidget = NULL;
214   NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
215   if (!topLevelWidget || !win)
216     return YES;
218   // XUL popups, e.g. the toolbar customization popup, can't become key windows,
219   // but controls in these windows should still get the active look.
220   nsWindowType windowType;
221   topLevelWidget->GetWindowType(windowType);
222   if (windowType == eWindowType_popup)
223     return YES;
224   if ([win isSheet])
225     return [win isKeyWindow];
226   return [win isMainWindow] && ![win attachedSheet];
229 NS_IMPL_ISUPPORTS_INHERITED1(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
232 nsNativeThemeCocoa::nsNativeThemeCocoa()
234   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
236   mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
237   [mPushButtonCell setButtonType:NSMomentaryPushInButton];
238   [mPushButtonCell setHighlightsBy:NSPushInCellMask];
240   mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
241   [mRadioButtonCell setButtonType:NSRadioButton];
243   mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
244   [mCheckboxCell setButtonType:NSSwitchButton];
245   [mCheckboxCell setAllowsMixedState:YES];
247 #if defined(__x86_64__)
248   mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
249 #else
250   mSearchFieldCell = [[NSSearchFieldCell alloc] initTextCell:@""];
251 #endif
252   [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
253   [mSearchFieldCell setBezeled:YES];
254   [mSearchFieldCell setEditable:YES];
255   [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
257   mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
259   mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
260   [mComboBoxCell setBezeled:YES];
261   [mComboBoxCell setEditable:YES];
262   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
264   mCellDrawView = [[CellDrawView alloc] init];
266   NS_OBJC_END_TRY_ABORT_BLOCK;
269 nsNativeThemeCocoa::~nsNativeThemeCocoa()
271   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
273   [mPushButtonCell release];
274   [mRadioButtonCell release];
275   [mCheckboxCell release];
276   [mSearchFieldCell release];
277   [mDropdownCell release];
278   [mComboBoxCell release];
279   [mCellDrawView release];
281   NS_OBJC_END_TRY_ABORT_BLOCK;
284 // Limit on the area of the target rect (in pixels^2) in
285 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
286 // don't draw the object into a bitmap buffer.  This is to avoid crashes in
287 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
288 // CGContextDrawImage(), and also to avoid very poor drawing performance in
289 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
290 // yscale is less than but near 1 -- e.g. 0.9).  This value was determined
291 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
292 // different amounts of RAM.
293 #define BITMAP_MAX_AREA 500000
296  * Draw the given NSCell into the given cgContext.
298  * destRect - the size and position of the resulting control rectangle
299  * controlSize - the NSControlSize which will be given to the NSCell before
300  *  asking it to render
301  * naturalSize - The natural dimensions of this control.
302  *  If the control rect size is not equal to either of these, a scale
303  *  will be applied to the context so that rendering the control at the
304  *  natural size will result in it filling the destRect space.
305  *  If a control has no natural dimensions in either/both axes, pass 0.0f.
306  * minimumSize - The minimum dimensions of this control.
307  *  If the control rect size is less than the minimum for a given axis,
308  *  a scale will be applied to the context so that the minimum is used
309  *  for drawing.  If a control has no minimum dimensions in either/both
310  *  axes, pass 0.0f.
311  * marginSet - an array of margins; a multidimensional array of [2][3][4],
312  *  with the first dimension being the OS version (Tiger or Leopard),
313  *  the second being the control size (mini, small, regular), and the third
314  *  being the 4 margin values (left, top, right, bottom).
315  * view - The NSView that we're drawing into. As far as I can tell, it doesn't
316  *  matter if this is really the right view; it just has to return YES when
317  *  asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
318  * mirrorHorizontal - whether to mirror the cell horizontally
319  */
320 static void DrawCellWithScaling(NSCell *cell,
321                                 CGContextRef cgContext,
322                                 const HIRect& destRect,
323                                 NSControlSize controlSize,
324                                 NSSize naturalSize,
325                                 NSSize minimumSize,
326                                 const float marginSet[][3][4],
327                                 NSView* view,
328                                 BOOL mirrorHorizontal)
330   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
332   NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
334   if (naturalSize.width != 0.0f)
335     drawRect.size.width = naturalSize.width;
336   if (naturalSize.height != 0.0f)
337     drawRect.size.height = naturalSize.height;
339   // Keep aspect ratio when scaling if one dimension is free.
340   if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
341     drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
342   if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
343     drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
345   // Honor minimum sizes.
346   if (drawRect.size.width < minimumSize.width)
347     drawRect.size.width = minimumSize.width;
348   if (drawRect.size.height < minimumSize.height)
349     drawRect.size.height = minimumSize.height;
351   [NSGraphicsContext saveGraphicsState];
353   // Only skip the buffer if the area of our cell (in pixels^2) is too large.
354   if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
355     // Inflate the rect Gecko gave us by the margin for the control.
356     InflateControlRect(&drawRect, controlSize, marginSet);
358     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
359     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
361     [cell drawWithFrame:drawRect inView:view];
363     [NSGraphicsContext setCurrentContext:savedContext];
364   }
365   else {
366     float w = ceil(drawRect.size.width);
367     float h = ceil(drawRect.size.height);
368     NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
370     // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
371     InflateControlRect(&tmpRect, controlSize, marginSet);
373     // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
374     w += MAX_FOCUS_RING_WIDTH * 2.0;
375     h += MAX_FOCUS_RING_WIDTH * 2.0;
377     CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
378     CGContextRef ctx = CGBitmapContextCreate(NULL,
379                                              (int) w, (int) h,
380                                              8, (int) w * 4,
381                                              rgb, kCGImageAlphaPremultipliedFirst);
382     CGColorSpaceRelease(rgb);
384     // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
385     // This is the first flip transform, applied to cgContext.
386     CGContextScaleCTM(cgContext, 1.0f, -1.0f);
387     CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
388     if (mirrorHorizontal) {
389       CGContextScaleCTM(cgContext, -1.0f, 1.0f);
390       CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
391     }
393     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
394     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
396     // This is the second flip transform, applied to ctx.
397     CGContextScaleCTM(ctx, 1.0f, -1.0f);
398     CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
400     [cell drawWithFrame:tmpRect inView:view];
402     [NSGraphicsContext setCurrentContext:savedContext];
404     CGImageRef img = CGBitmapContextCreateImage(ctx);
406     // Drop the image into the original destination rectangle, scaling to fit
407     // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
408     // doesn't extend beyond the overflow rect
409     float xscale = destRect.size.width / drawRect.size.width;
410     float yscale = destRect.size.height / drawRect.size.height;
411     float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
412     float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
413     CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
414                                              destRect.origin.y - scaledFocusRingY,
415                                              destRect.size.width + scaledFocusRingX * 2,
416                                              destRect.size.height + scaledFocusRingY * 2),
417                        img);
419     CGImageRelease(img);
420     CGContextRelease(ctx);
421   }
423   [NSGraphicsContext restoreGraphicsState];
425 #if DRAW_IN_FRAME_DEBUG
426   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
427   CGContextFillRect(cgContext, destRect);
428 #endif
430   NS_OBJC_END_TRY_ABORT_BLOCK;
433 struct CellRenderSettings {
434   // The natural dimensions of the control.
435   // If a control has no natural dimensions in either/both axes, set to 0.0f.
436   NSSize naturalSizes[3];
438   // The minimum dimensions of the control.
439   // If a control has no minimum dimensions in either/both axes, set to 0.0f.
440   NSSize minimumSizes[3];
442   // A multidimensional array of [2][3][4],
443   // with the first dimension being the OS version (Tiger or Leopard),
444   // the second being the control size (mini, small, regular), and the third
445   // being the 4 margin values (left, top, right, bottom).
446   float margins[2][3][4];
450  * Draw the given NSCell into the given cgContext with a nice control size.
452  * This function is similar to DrawCellWithScaling, but it decides what
453  * control size to use based on the destRect's size.
454  * Scaling is only applied when the difference between the destRect's size
455  * and the next smaller natural size is greater than snapTolerance. Otherwise
456  * it snaps to the next smaller control size without scaling because unscaled
457  * controls look nicer.
458  */
459 static void DrawCellWithSnapping(NSCell *cell,
460                                  CGContextRef cgContext,
461                                  const HIRect& destRect,
462                                  const CellRenderSettings settings,
463                                  float verticalAlignFactor,
464                                  NSView* view,
465                                  BOOL mirrorHorizontal,
466                                  float snapTolerance = 2.0f)
468   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
470   const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
471   const NSSize *sizes = settings.naturalSizes;
472   const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
473   const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
474   const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
476   NSControlSize controlSizeX = NSRegularControlSize, controlSizeY = NSRegularControlSize;
477   HIRect drawRect = destRect;
479   if (rectWidth <= miniSize.width + snapTolerance && rectWidth < smallSize.width)
480     controlSizeX = NSMiniControlSize;
481   else if(rectWidth <= smallSize.width + snapTolerance && rectWidth < regularSize.width)
482     controlSizeX = NSSmallControlSize;
484   if (rectHeight <= miniSize.height + snapTolerance && rectHeight < smallSize.height)
485     controlSizeY = NSMiniControlSize;
486   else if(rectHeight <= smallSize.height + snapTolerance && rectHeight < regularSize.height)
487     controlSizeY = NSSmallControlSize;
489   NSControlSize controlSize = NSRegularControlSize;
490   int sizeIndex = 0;
492   // At some sizes, don't scale but snap.
493   const NSControlSize smallerControlSize =
494     EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
495     controlSizeX : controlSizeY;
496   const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
497   const NSSize size = sizes[smallerControlSizeIndex];
498   float diffWidth = size.width ? rectWidth - size.width : 0.0f;
499   float diffHeight = size.height ? rectHeight - size.height : 0.0f;
500   if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
501       diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
502     // Snap to the smaller control size.
503     controlSize = smallerControlSize;
504     sizeIndex = smallerControlSizeIndex;
505     // Resize and center the drawRect.
506     if (sizes[sizeIndex].width) {
507       drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
508       drawRect.size.width = sizes[sizeIndex].width;
509     }
510     if (sizes[sizeIndex].height) {
511       drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
512       drawRect.size.height = sizes[sizeIndex].height;
513     }
514   } else {
515     // Use the larger control size.
516     controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
517                   controlSizeX : controlSizeY;
518     sizeIndex = EnumSizeForCocoaSize(controlSize);
519    }
521   [cell setControlSize:controlSize];
523   NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
524   DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
525                       minimumSize, settings.margins, view, mirrorHorizontal);
527   NS_OBJC_END_TRY_ABORT_BLOCK;
530 static float VerticalAlignFactor(nsIFrame *aFrame)
532   if (!aFrame)
533     return 0.5f; // default: center
535   const nsStyleCoord& va = aFrame->GetStyleTextReset()->mVerticalAlign;
536   PRUint8 intval = (va.GetUnit() == eStyleUnit_Enumerated)
537                      ? va.GetIntValue()
538                      : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
539   switch (intval) {
540     case NS_STYLE_VERTICAL_ALIGN_TOP:
541     case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
542       return 0.0f;
544     case NS_STYLE_VERTICAL_ALIGN_SUB:
545     case NS_STYLE_VERTICAL_ALIGN_SUPER:
546     case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
547     case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
548       return 0.5f;
550     case NS_STYLE_VERTICAL_ALIGN_BASELINE:
551     case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
552     case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
553       return 1.0f;
555     default:
556       NS_NOTREACHED("invalid vertical-align");
557       return 0.5f;
558   }
561 // These are the sizes that Gecko needs to request to draw if it wants
562 // to get a standard-sized Aqua radio button drawn. Note that the rects
563 // that draw these are actually a little bigger.
564 static const CellRenderSettings radioSettings = {
565   {
566     NSMakeSize(11, 11), // mini
567     NSMakeSize(13, 13), // small
568     NSMakeSize(16, 16)  // regular
569   },
570   {
571     NSZeroSize, NSZeroSize, NSZeroSize
572   },
573   {
574     { // Tiger
575       {0, 0, 0, 0},     // mini
576       {0, 1, 1, 2},     // small
577       {0, -1, 0, 1}     // regular
578     },
579     { // Leopard
580       {0, 0, 0, 0},     // mini
581       {0, 1, 1, 1},     // small
582       {0, 0, 0, 0}      // regular
583     }
584   }
587 static const CellRenderSettings checkboxSettings = {
588   {
589     NSMakeSize(11, 11), // mini
590     NSMakeSize(13, 13), // small
591     NSMakeSize(16, 16)  // regular
592   },
593   {
594     NSZeroSize, NSZeroSize, NSZeroSize
595   },
596   {
597     { // Tiger
598       {0, 1, 0, 0},     // mini
599       {0, 2, 0, 1},     // small
600       {0, 1, 0, 1}      // regular
601     },
602     { // Leopard
603       {0, 1, 0, 0},     // mini
604       {0, 1, 0, 1},     // small
605       {0, 1, 0, 1}      // regular
606     }
607   }
610 void
611 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, PRBool inCheckbox,
612                                         const HIRect& inBoxRect, PRBool inSelected,
613                                         nsEventStates inState, nsIFrame* aFrame)
615   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
617   NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
618   NSCellStateValue state = inSelected ? NSOnState : NSOffState;
620   // Check if we have an indeterminate checkbox
621   if (inCheckbox && GetIndeterminate(aFrame))
622     state = NSMixedState;
624   [cell setEnabled:!IsDisabled(aFrame, inState)];
625   [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
626   [cell setState:state];
627   [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
628   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
630   // Ensure that the control is square.
631   float length = PR_MIN(inBoxRect.size.width, inBoxRect.size.height);
632   HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
633                                inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
634                                length, length);
636   DrawCellWithSnapping(cell, cgContext, drawRect,
637                        inCheckbox ? checkboxSettings : radioSettings,
638                        VerticalAlignFactor(aFrame), mCellDrawView, NO);
640   NS_OBJC_END_TRY_ABORT_BLOCK;
643 static const CellRenderSettings searchFieldSettings = {
644   {
645     NSMakeSize(0, 16), // mini
646     NSMakeSize(0, 19), // small
647     NSMakeSize(0, 22)  // regular
648   },
649   {
650     NSMakeSize(32, 0), // mini
651     NSMakeSize(38, 0), // small
652     NSMakeSize(44, 0)  // regular
653   },
654   {
655     { // Tiger
656       {0, 0, 0, 0},     // mini
657       {0, 0, 0, 0},     // small
658       {0, 0, 0, 0}      // regular
659     },
660     { // Leopard
661       {0, 0, 0, 0},     // mini
662       {0, 0, 0, 0},     // small
663       {0, 0, 0, 0}      // regular
664     }
665   }
668 void
669 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
670                                     nsIFrame* aFrame, nsEventStates inState)
672   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
674   NSSearchFieldCell* cell = mSearchFieldCell;
675   [cell setEnabled:!IsDisabled(aFrame, inState)];
676   // NOTE: this could probably use inState
677   [cell setShowsFirstResponder:IsFocused(aFrame)];
679   DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
680                        VerticalAlignFactor(aFrame), mCellDrawView,
681                        IsFrameRTL(aFrame));
683   NS_OBJC_END_TRY_ABORT_BLOCK;
686 static const CellRenderSettings pushButtonSettings = {
687   {
688     NSMakeSize(0, 16), // mini
689     NSMakeSize(0, 19), // small
690     NSMakeSize(0, 22)  // regular
691   },
692   {
693     NSMakeSize(18, 0), // mini
694     NSMakeSize(26, 0), // small
695     NSMakeSize(30, 0)  // regular
696   },
697   {
698     { // Tiger
699       {1, 1, 1, 1},    // mini
700       {5, 0, 5, 2},    // small
701       {6, 0, 6, 2}     // regular
702     },
703     { // Leopard
704       {0, 0, 0, 0},    // mini
705       {4, 0, 4, 1},    // small
706       {5, 0, 5, 2}     // regular
707     }
708   }
711 // The height at which we start doing square buttons instead of rounded buttons
712 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
713 // we switch over to doing square buttons which looks fine at any size.
714 #define DO_SQUARE_BUTTON_HEIGHT 26
716 void
717 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
718                                    nsEventStates inState, nsIFrame* aFrame)
720   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
722   BOOL isActive = FrameIsInActiveWindow(aFrame);
723   BOOL isDisabled = IsDisabled(aFrame, inState);
725   [mPushButtonCell setEnabled:!isDisabled];
726   [mPushButtonCell setHighlighted:isActive &&
727                                   inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
728   [mPushButtonCell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
730   // If the button is tall enough, draw the square button style so that buttons with
731   // non-standard content look good. Otherwise draw normal rounded aqua buttons.
732   if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
733     [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
734     DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSRegularControlSize,
735                         NSZeroSize, NSMakeSize(14, 0), NULL,
736                         mCellDrawView, IsFrameRTL(aFrame));
737   } else {
738     [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
740     DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings,
741                          0.5f, mCellDrawView, IsFrameRTL(aFrame), 1.0f);
742   }
744 #if DRAW_IN_FRAME_DEBUG
745   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
746   CGContextFillRect(cgContext, inBoxRect);
747 #endif
749   NS_OBJC_END_TRY_ABORT_BLOCK;
752 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
754 static void
755 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
756                                 RenderHIThemeControlFunction aFunc, void* aData,
757                                 BOOL mirrorHorizontally = NO)
759   CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
760   CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
762   PRBool drawDirect;
763   HIRect drawRect = aRect;
764   drawRect.origin = CGPointZero;
766   if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
767       savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
768     drawDirect = TRUE;
769   } else {
770     drawDirect = FALSE;
771   }
773   // Fall back to no bitmap buffer if the area of our control (in pixels^2)
774   // is too large.
775   if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
776     aFunc(aCGContext, drawRect, aData);
777   } else {
778     // Inflate the buffer to capture focus rings.
779     int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
780     int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
782     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
783     CGContextRef bitmapctx = CGBitmapContextCreate(NULL, w, h, 8, w * 4,
784                                                    colorSpace,
785                                                    kCGImageAlphaPremultipliedFirst);
786     CGColorSpaceRelease(colorSpace);
788     CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
790     // HITheme always wants to draw into a flipped context, or things
791     // get confused.
792     CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
793     CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
795     aFunc(bitmapctx, drawRect, aData);
797     CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
799     CGAffineTransform ctm = CGContextGetCTM(aCGContext);
801     // We need to unflip, so that we can do a DrawImage without getting a flipped image.
802     CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
803     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
805     if (mirrorHorizontally) {
806       CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
807       CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
808     }
810     HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
811     CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
813     CGContextSetCTM(aCGContext, ctm);
815     CGImageRelease(bitmap);
816     CGContextRelease(bitmapctx);
817   }
819   CGContextSetCTM(aCGContext, savedCTM);
822 static void
823 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
825   HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
826   HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
829 void
830 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
831                                const HIRect& inBoxRect, PRBool inIsDefault,
832                                ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
833                                nsEventStates inState, nsIFrame* aFrame)
835   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
837   BOOL isActive = FrameIsInActiveWindow(aFrame);
838   BOOL isDisabled = IsDisabled(aFrame, inState);
840   HIThemeButtonDrawInfo bdi;
841   bdi.version = 0;
842   bdi.kind = inKind;
843   bdi.value = inValue;
844   bdi.adornment = inAdornment;
846   if (isDisabled) {
847     bdi.state = kThemeStateUnavailable;
848   }
849   else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
850     bdi.state = kThemeStatePressed;
851   }
852   else {
853     if (inKind == kThemeArrowButton)
854       bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
855     else if (!isActive && inKind == kThemeListHeaderButton)
856       bdi.state = kThemeStateInactive;
857     else
858       bdi.state = kThemeStateActive;
859   }
861   if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
862     bdi.adornment |= kThemeAdornmentFocus;
864   if (inIsDefault && !isDisabled && isActive &&
865       !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
866     bdi.adornment |= kThemeAdornmentDefault;
867     bdi.animation.time.start = 0;
868     bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
869   }
871   HIRect drawFrame = inBoxRect;
873   if (inKind == kThemePushButton) {
874     drawFrame.size.height -= 2;
875     if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
876       bdi.kind = kThemePushButtonMini;
877     }
878     else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
879       bdi.kind = kThemePushButtonSmall;
880       drawFrame.origin.y -= 1;
881       drawFrame.origin.x += 1;
882       drawFrame.size.width -= 2;
883     }
884   }
885   else if (inKind == kThemeListHeaderButton) {
886     CGContextClipToRect(cgContext, inBoxRect);
887     // Always remove the top border.
888     drawFrame.origin.y -= 1;
889     drawFrame.size.height += 1;
890     // Remove the left border in LTR mode and the right border in RTL mode.
891     drawFrame.size.width += 1;
892     PRBool isLast = IsLastTreeHeaderCell(aFrame);
893     if (isLast)
894       drawFrame.size.width += 1; // Also remove the other border.
895     if (!IsFrameRTL(aFrame) || isLast)
896       drawFrame.origin.x -= 1;
897   }
899   RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
900                                   IsFrameRTL(aFrame));
902 #if DRAW_IN_FRAME_DEBUG
903   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
904   CGContextFillRect(cgContext, inBoxRect);
905 #endif
907   NS_OBJC_END_TRY_ABORT_BLOCK;
910 static const CellRenderSettings dropdownSettings = {
911   {
912     NSMakeSize(0, 16), // mini
913     NSMakeSize(0, 19), // small
914     NSMakeSize(0, 22)  // regular
915   },
916   {
917     NSMakeSize(18, 0), // mini
918     NSMakeSize(38, 0), // small
919     NSMakeSize(44, 0)  // regular
920   },
921   {
922     { // Tiger
923       {1, 1, 2, 1},    // mini
924       {3, 0, 3, 1},    // small
925       {3, 0, 3, 0}     // regular
926     },
927     { // Leopard
928       {1, 1, 2, 1},    // mini
929       {3, 0, 3, 1},    // small
930       {3, 0, 3, 0}     // regular
931     }
932   }
935 static const CellRenderSettings editableMenulistSettings = {
936   {
937     NSMakeSize(0, 15), // mini
938     NSMakeSize(0, 18), // small
939     NSMakeSize(0, 21)  // regular
940   },
941   {
942     NSMakeSize(18, 0), // mini
943     NSMakeSize(38, 0), // small
944     NSMakeSize(44, 0)  // regular
945   },
946   {
947     { // Tiger
948       {0, 0, 2, 2},    // mini
949       {0, 0, 3, 2},    // small
950       {0, 1, 3, 3}     // regular
951     },
952     { // Leopard
953       {0, 0, 2, 2},    // mini
954       {0, 0, 3, 2},    // small
955       {0, 1, 3, 3}     // regular
956     }
957   }
960 void
961 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
962                                  nsEventStates inState, PRUint8 aWidgetType,
963                                  nsIFrame* aFrame)
965   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
967   [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
969   BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
970   NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
972   [cell setEnabled:!IsDisabled(aFrame, inState)];
973   [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
974   [cell setHighlighted:IsOpenButton(aFrame)];
975   [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
977   const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
978   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
979                        0.5f, mCellDrawView, IsFrameRTL(aFrame));
981   NS_OBJC_END_TRY_ABORT_BLOCK;
984 void
985 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
986                                     const HIRect& inBoxRect, ThemeDrawState inDrawState,
987                                     ThemeButtonAdornment inAdornment,
988                                     nsEventStates inState, nsIFrame* aFrame)
990   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
992   HIThemeButtonDrawInfo bdi;
993   bdi.version = 0;
994   bdi.kind = inKind;
995   bdi.value = kThemeButtonOff;
996   bdi.adornment = inAdornment;
998   if (IsDisabled(aFrame, inState))
999     bdi.state = kThemeStateUnavailable;
1000   else
1001     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1003   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1005   NS_OBJC_END_TRY_ABORT_BLOCK;
1008 void
1009 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
1010                               const HIRect& inBoxRect, PRBool inDisabled,
1011                               nsEventStates inState)
1013   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1015   HIThemeFrameDrawInfo fdi;
1016   fdi.version = 0;
1017   fdi.kind = inKind;
1019   // We don't ever set an inactive state for this because it doesn't
1020   // look right (see other apps).
1021   fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
1023   // for some reason focus rings on listboxes draw incorrectly
1024   if (inKind == kHIThemeFrameListBox)
1025     fdi.isFocused = 0;
1026   else
1027     fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1029   // HIThemeDrawFrame takes the rect for the content area of the frame, not
1030   // the bounding rect for the frame. Here we reduce the size of the rect we
1031   // will pass to make it the size of the content.
1032   HIRect drawRect = inBoxRect;
1033   if (inKind == kHIThemeFrameTextFieldSquare) {
1034     SInt32 frameOutset = 0;
1035     ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1036     drawRect.origin.x += frameOutset;
1037     drawRect.origin.y += frameOutset;
1038     drawRect.size.width -= frameOutset * 2;
1039     drawRect.size.height -= frameOutset * 2;
1040   }
1041   else if (inKind == kHIThemeFrameListBox) {
1042     SInt32 frameOutset = 0;
1043     ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1044     drawRect.origin.x += frameOutset;
1045     drawRect.origin.y += frameOutset;
1046     drawRect.size.width -= frameOutset * 2;
1047     drawRect.size.height -= frameOutset * 2;
1048   }
1050 #if DRAW_IN_FRAME_DEBUG
1051   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1052   CGContextFillRect(cgContext, inBoxRect);
1053 #endif
1055   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1057   NS_OBJC_END_TRY_ABORT_BLOCK;
1060 static void
1061 RenderProgress(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1063   HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1064   tdi->bounds = aRenderRect;
1065   HIThemeDrawTrack(tdi, NULL, cgContext, kHIThemeOrientationNormal);
1068 void
1069 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1070                                  PRBool inIsIndeterminate, PRBool inIsHorizontal,
1071                                  PRInt32 inValue, PRInt32 inMaxValue,
1072                                  nsIFrame* aFrame)
1074   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1076   HIThemeTrackDrawInfo tdi;
1078   PRInt32 stepsPerSecond = inIsIndeterminate ? 60 : 30;
1079   PRInt32 milliSecondsPerStep = 1000 / stepsPerSecond;
1081   tdi.version = 0;
1082   tdi.kind = inIsIndeterminate ? kThemeMediumIndeterminateBar: kThemeMediumProgressBar;
1083   tdi.bounds = inBoxRect;
1084   tdi.min = 0;
1085   tdi.max = inMaxValue;
1086   tdi.value = inValue;
1087   tdi.attributes = inIsHorizontal ? kThemeTrackHorizontal : 0;
1088   tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1089   tdi.trackInfo.progress.phase = PR_IntervalToMilliseconds(PR_IntervalNow()) /
1090                                  milliSecondsPerStep % 16;
1092   RenderTransformedHIThemeControl(cgContext, inBoxRect, RenderProgress, &tdi,
1093                                   IsFrameRTL(aFrame));
1095   NS_OBJC_END_TRY_ABORT_BLOCK;
1098 void
1099 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1100                                  nsIFrame* aFrame)
1102   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1104   HIThemeTabPaneDrawInfo tpdi;
1106   tpdi.version = 1;
1107   tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1108   tpdi.direction = kThemeTabNorth;
1109   tpdi.size = kHIThemeTabSizeNormal;
1110   tpdi.kind = kHIThemeTabKindNormal;
1112   HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1114   NS_OBJC_END_TRY_ABORT_BLOCK;
1117 void
1118 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1119                               nsEventStates inState, PRBool inIsVertical,
1120                               PRBool inIsReverse, PRInt32 inCurrentValue,
1121                               PRInt32 inMinValue, PRInt32 inMaxValue,
1122                               nsIFrame* aFrame)
1124   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1126   HIThemeTrackDrawInfo tdi;
1128   tdi.version = 0;
1129   tdi.kind = kThemeMediumSlider;
1130   tdi.bounds = inBoxRect;
1131   tdi.min = inMinValue;
1132   tdi.max = inMaxValue;
1133   tdi.value = inCurrentValue;
1134   tdi.attributes = kThemeTrackShowThumb;
1135   if (!inIsVertical)
1136     tdi.attributes |= kThemeTrackHorizontal;
1137   if (inIsReverse)
1138     tdi.attributes |= kThemeTrackRightToLeft;
1139   if (inState.HasState(NS_EVENT_STATE_FOCUS))
1140     tdi.attributes |= kThemeTrackHasFocus;
1141   if (IsDisabled(aFrame, inState))
1142     tdi.enableState = kThemeTrackDisabled;
1143   else
1144     tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1145   tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1146   tdi.trackInfo.slider.pressState = 0;
1148   HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1150   NS_OBJC_END_TRY_ABORT_BLOCK;
1153 #define NATURAL_MINI_TAB_BUTTON_HEIGHT    17
1154 #define NATURAL_SMALL_TAB_BUTTON_HEIGHT   20
1155 #define NATURAL_REGULAR_TAB_BUTTON_HEIGHT 23
1157 void
1158 nsNativeThemeCocoa::DrawTab(CGContextRef cgContext, HIRect inBoxRect,
1159                             nsEventStates inState, nsIFrame* aFrame)
1161   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1163   HIThemeTabDrawInfo tdi;
1164   tdi.version = 1;
1165   tdi.kind = kHIThemeTabKindNormal;
1167   PRBool isSelected = IsSelectedTab(aFrame);
1168   PRBool isDisabled = IsDisabled(aFrame, inState);
1170   if (isSelected) {
1171     if (isDisabled)
1172       tdi.style = kThemeTabFrontUnavailable;
1173     else
1174       tdi.style = FrameIsInActiveWindow(aFrame) ? kThemeTabFront : kThemeTabFrontInactive;
1175   } else {
1176     if (isDisabled)
1177       tdi.style = kThemeTabNonFrontUnavailable;
1178     else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER))
1179       tdi.style = kThemeTabNonFrontPressed;
1180     else
1181       tdi.style = FrameIsInActiveWindow(aFrame) ? kThemeTabNonFront : kThemeTabNonFrontInactive;
1182   }
1184   tdi.direction = kThemeTabNorth;
1185   tdi.size = kHIThemeTabSizeNormal;
1186   if (inBoxRect.size.height < NATURAL_REGULAR_TAB_BUTTON_HEIGHT)
1187     tdi.size = kHIThemeTabSizeSmall;
1188   if (inBoxRect.size.height < NATURAL_SMALL_TAB_BUTTON_HEIGHT)
1189     tdi.size = kHIThemeTabSizeMini;
1191   PRBool isRTL = IsFrameRTL(aFrame);
1192   PRBool isFirst = isRTL ? IsLastTab(aFrame) : IsFirstTab(aFrame);
1193   PRBool isLast = isRTL ? IsFirstTab(aFrame) : IsLastTab(aFrame);
1195   if (isFirst && isLast)
1196     tdi.position = kHIThemeTabPositionOnly;
1197   else if (isFirst)
1198     tdi.position = kHIThemeTabPositionFirst;
1199   else if (isLast)
1200     tdi.position = kHIThemeTabPositionLast;
1201   else
1202     tdi.position = kHIThemeTabPositionMiddle;
1204   // Tab separator management:
1205   // Normal tabs only draw their left separator, in the leftmost pixel row of
1206   // their frame. Selected tabs additionally draw their right separator, outside
1207   // of their frame. To prevent overlapping, the tab to the right of the
1208   // selected tab shouldn't draw its left separator.
1209   tdi.adornment = kHIThemeTabAdornmentNone;
1210   if (isRTL ? IsBeforeSelectedTab(aFrame) : IsAfterSelectedTab(aFrame)) {
1211     // On Leopard, the tab's left edge must be shifted 1px to the right.
1212     // On Tiger, this happens automatically when no leading separator is drawn.
1213     inBoxRect.origin.x += 1;
1214     inBoxRect.size.width -= 1;
1215   }
1216   else {
1217     tdi.adornment = kHIThemeTabAdornmentLeadingSeparator;
1218   }
1220   if (isSelected && !isLast) {
1221     tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator;
1222     // On Tiger, the right separator is drawn outside of the frame.
1223     // On Leopard, the right edge must be shifted 1px to the right.
1224     inBoxRect.size.width += 1;
1225   }
1226   
1227   if (inState.HasState(NS_EVENT_STATE_FOCUS))
1228     tdi.adornment |= kThemeAdornmentFocus;
1230   HIThemeDrawTab(&inBoxRect, &tdi, cgContext, HITHEME_ORIENTATION, NULL);
1232   NS_OBJC_END_TRY_ABORT_BLOCK;
1235 static inline UInt8
1236 ConvertToPressState(nsEventStates aButtonState, UInt8 aPressState)
1238   // If the button is pressed, return the press state passed in. Otherwise, return 0.
1239   return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0;
1242 void 
1243 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame *aFrame, nsEventStates aButtonStates[])
1245   static nsIContent::AttrValuesArray attributeValues[] = {
1246     &nsWidgetAtoms::scrollbarUpTop,
1247     &nsWidgetAtoms::scrollbarDownTop,
1248     &nsWidgetAtoms::scrollbarUpBottom,
1249     &nsWidgetAtoms::scrollbarDownBottom,
1250     nsnull
1251   };
1253   // Get the state of any scrollbar buttons in our child frames
1254   for (nsIFrame *childFrame = aFrame->GetFirstChild(nsnull); 
1255        childFrame;
1256        childFrame = childFrame->GetNextSibling()) {
1258     nsIContent *childContent = childFrame->GetContent();
1259     if (!childContent) continue;
1260     PRInt32 attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsWidgetAtoms::sbattr, 
1261                                                       attributeValues, eCaseMatters);
1262     if (attrIndex < 0) continue;
1264     aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
1265   }
1268 // Both of the following sets of numbers were derived by loading the testcase in
1269 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1270 // These magic numbers are the minimum sizes we can draw a scrollbar and still 
1271 // have room for everything to display, including the thumb
1272 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1273 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1274 // And these are the minimum sizes if we don't draw the thumb
1275 #define MIN_SCROLLBAR_SIZE 56
1276 #define MIN_SMALL_SCROLLBAR_SIZE 46
1278 void
1279 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, 
1280                                          const CGSize& aSize, PRBool aShouldGetButtonStates)
1282   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1284   PRInt32 curpos = CheckIntAttr(aFrame, nsWidgetAtoms::curpos, 0);
1285   PRInt32 minpos = CheckIntAttr(aFrame, nsWidgetAtoms::minpos, 0);
1286   PRInt32 maxpos = CheckIntAttr(aFrame, nsWidgetAtoms::maxpos, 100);
1287   PRInt32 thumbSize = CheckIntAttr(aFrame, nsWidgetAtoms::pageincrement, 10);
1289   PRBool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::orient, 
1290                                                           nsWidgetAtoms::horizontal, eCaseMatters);
1291   PRBool isSmall = aFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1293   aTdi.version = 0;
1294   aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1295   aTdi.bounds.origin = CGPointZero;
1296   aTdi.bounds.size = aSize;
1297   aTdi.min = minpos;
1298   aTdi.max = maxpos;
1299   aTdi.value = curpos;
1300   aTdi.attributes = 0;
1301   aTdi.enableState = kThemeTrackActive;
1302   if (isHorizontal)
1303     aTdi.attributes |= kThemeTrackHorizontal;
1305   aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
1307   // This should be done early on so things like "kThemeTrackNothingToScroll" can
1308   // override the active enable state.
1309   aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1311   /* Only display features if we have enough room for them.
1312    * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
1313    */
1314   PRInt32 longSideLength = (PRInt32)(isHorizontal ? (aSize.width) : (aSize.height));
1315   if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
1316     aTdi.attributes |= kThemeTrackShowThumb;
1317   }
1318   else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
1319     aTdi.enableState = kThemeTrackNothingToScroll;
1320     return;
1321   }
1323   aTdi.trackInfo.scrollbar.pressState = 0;
1325   // Only go get these scrollbar button states if we need it. For example, there's no reaon to look up scrollbar button 
1326   // states when we're only creating a TrackDrawInfo to determine the size of the thumb.
1327   if (aShouldGetButtonStates) {
1328     nsEventStates buttonStates[4];
1329     GetScrollbarPressStates(aFrame, buttonStates);
1330     NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
1331     // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
1332     // the up scrollbar button.
1333     if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
1334       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1335                                             ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
1336                                             ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
1337                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1338     } else {
1339       aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1340                                             ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
1341                                             ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
1342                                             ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1343     }
1344   }
1346   NS_OBJC_END_TRY_ABORT_BLOCK;
1349 static void
1350 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1352   HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1353   HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
1356 void
1357 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
1359   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1361   HIThemeTrackDrawInfo tdi;
1362   GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, PR_TRUE); // True means we want the press states
1363   RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
1365   NS_OBJC_END_TRY_ABORT_BLOCK;
1368 nsIFrame*
1369 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
1371   // Walk our parents to find a scrollbar frame
1372   nsIFrame *scrollbarFrame = aFrame;
1373   do {
1374     if (scrollbarFrame->GetType() == nsWidgetAtoms::scrollbarFrame) break;
1375   } while ((scrollbarFrame = scrollbarFrame->GetParent()));
1376   
1377   // We return null if we can't find a parent scrollbar frame
1378   return scrollbarFrame;
1381 static PRBool
1382 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
1384   if (![aWindow isKindOfClass:[ToolbarWindow class]] ||
1385       [(ToolbarWindow*)aWindow drawsContentsIntoWindowFrame])
1386     return PR_FALSE;
1388   float unifiedToolbarHeight = [(ToolbarWindow*)aWindow unifiedToolbarHeight];
1389   return inBoxRect.origin.x == 0 &&
1390          inBoxRect.size.width == [aWindow frame].size.width &&
1391          inBoxRect.origin.y <= 0.0 &&
1392          inBoxRect.origin.y + inBoxRect.size.height <= unifiedToolbarHeight;
1395 void
1396 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
1397                                        NSWindow* aWindow)
1399   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1401   float titlebarHeight = [(ToolbarWindow*)aWindow titlebarHeight];
1403   BOOL isMain = [aWindow isMainWindow] || ![NSView focusView];
1405   // Draw the gradient
1406   UnifiedGradientInfo info = { titlebarHeight, inBoxRect.size.height, isMain, NO };
1407   struct CGFunctionCallbacks callbacks = { 0, nsCocoaWindow::UnifiedShading, NULL };
1408   CGFunctionRef function = CGFunctionCreate(&info, 1,  NULL, 4, NULL, &callbacks);
1409   float srcY = inBoxRect.origin.y;
1410   float dstY = srcY + inBoxRect.size.height - 1;
1411   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1412   CGShadingRef shading = CGShadingCreateAxial(colorSpace,
1413                                               CGPointMake(0, srcY),
1414                                               CGPointMake(0, dstY), function,
1415                                               NO, NO);
1416   CGColorSpaceRelease(colorSpace);
1417   CGFunctionRelease(function);
1418   CGContextClipToRect(cgContext, inBoxRect);
1419   CGContextDrawShading(cgContext, shading);
1420   CGShadingRelease(shading);
1422   // Draw the border at the bottom of the toolbar.
1423   CGRect borderRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y +
1424                                  inBoxRect.size.height - 1.0f,
1425                                  inBoxRect.size.width, 1.0f);
1426   DrawNativeGreyColorInRect(cgContext, headerBorderGrey, borderRect, isMain);
1428   NS_OBJC_END_TRY_ABORT_BLOCK;
1431 struct GreyGradientInfo {
1432   float startGrey;
1433   float endGrey;
1436 static void GreyGradientCallback(void* aInfo, const CGFloat* aIn, CGFloat* aOut)
1438   GreyGradientInfo* info = static_cast<GreyGradientInfo*>(aInfo);
1439   CGFloat result = (1.0f - *aIn) * info->startGrey + *aIn * info->endGrey;
1440   aOut[0] = result;
1441   aOut[1] = result;
1442   aOut[2] = result;
1443   aOut[3] = 1.0f;
1446 static void DrawGreyGradient(CGContextRef cgContext, const HIRect& rect,
1447                              float startGrey, float endGrey)
1449   if (rect.size.height <= 0.0f)
1450     return;
1452   GreyGradientInfo info = { startGrey, endGrey };
1453   struct CGFunctionCallbacks callbacks = { 0, GreyGradientCallback, NULL };
1454   CGFunctionRef function = CGFunctionCreate(&info, 1,  NULL, 4, NULL, &callbacks);
1455   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1456   CGShadingRef shading = CGShadingCreateAxial(colorSpace,
1457                                               CGPointMake(0, CGRectGetMinY(rect)),
1458                                               CGPointMake(0, CGRectGetMaxY(rect)),
1459                                               function, false, false);
1460   CGColorSpaceRelease(colorSpace);
1461   CGFunctionRelease(function);
1462   CGContextSaveGState(cgContext);
1463   CGContextClipToRect(cgContext, rect);
1464   CGContextDrawShading(cgContext, shading);
1465   CGContextRestoreGState(cgContext);
1466   CGShadingRelease(shading);
1469 void
1470 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
1471                                   nsIFrame *aFrame)
1473   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1475   if (inBoxRect.size.height < 2.0f)
1476     return;
1478   BOOL isMain = [NativeWindowForFrame(aFrame) isMainWindow] || ![NSView focusView];
1480   // Draw the borders at the top of the statusbar.
1481   CGRect rect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y,
1482                            inBoxRect.size.width, 1.0f);
1483   DrawNativeGreyColorInRect(cgContext, statusbarFirstTopBorderGrey, rect, isMain);
1484   rect.origin.y += 1.0f;
1485   DrawNativeGreyColorInRect(cgContext, statusbarSecondTopBorderGrey, rect, isMain);
1487   // Draw the gradient.
1488   DrawGreyGradient(cgContext, CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y + 2.0f,
1489                                          inBoxRect.size.width, inBoxRect.size.height - 2.0f),
1490                    NativeGreyColorAsFloat(statusbarGradientStartGrey, isMain),
1491                    NativeGreyColorAsFloat(statusbarGradientEndGrey, isMain));
1493   NS_OBJC_END_TRY_ABORT_BLOCK;
1496 static void
1497 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1499   HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
1500   HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
1503 void
1504 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
1505                                 nsIFrame *aFrame)
1507   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1509   HIThemeGrowBoxDrawInfo drawInfo;
1510   drawInfo.version = 0;
1511   drawInfo.state = kThemeStateActive;
1512   drawInfo.kind = kHIThemeGrowBoxKindNormal;
1513   drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
1514   drawInfo.size = kHIThemeGrowBoxSizeNormal;
1516   RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
1517                                   IsFrameRTL(aFrame));
1519   NS_OBJC_END_TRY_ABORT_BLOCK;
1522 NS_IMETHODIMP
1523 nsNativeThemeCocoa::DrawWidgetBackground(nsIRenderingContext* aContext, nsIFrame* aFrame,
1524                                          PRUint8 aWidgetType, const nsRect& aRect,
1525                                          const nsRect& aDirtyRect)
1527   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1529   // setup to draw into the correct port
1530   nsCOMPtr<nsIDeviceContext> dctx;
1531   aContext->GetDeviceContext(*getter_AddRefs(dctx));
1532   PRInt32 p2a = dctx->AppUnitsPerDevPixel();
1534   gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1535   gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
1536   nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
1537   nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
1538   nativeWidgetRect.Round();
1539   if (nativeWidgetRect.IsEmpty())
1540     return NS_OK; // Don't attempt to draw invisible widgets.
1542   nsRefPtr<gfxContext> thebesCtx = aContext->ThebesContext();
1543   if (!thebesCtx)
1544     return NS_ERROR_FAILURE;
1546   gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect);
1548   CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
1549   if (cgContext == nsnull) {
1550     // The Quartz surface handles 0x0 surfaces by internally
1551     // making all operations no-ops; there's no cgcontext created for them.
1552     // Unfortunately, this means that callers that want to render
1553     // directly to the CGContext need to be aware of this quirk.
1554     return NS_OK;
1555   }
1557 #if 0
1558   if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
1559     fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
1560             aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
1561     fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
1562             mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
1563     fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
1564             mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
1565     CGAffineTransform mm = CGContextGetCTM(cgContext);
1566     fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
1567             mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
1568   }
1569 #endif
1571   CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
1572                               nativeWidgetRect.Width(), nativeWidgetRect.Height());
1574 #if 0
1575   fprintf(stderr, "    --> macRect %f %f %f %f\n",
1576           macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
1577   CGRect bounds = CGContextGetClipBoundingBox(cgContext);
1578   fprintf(stderr, "    --> clip bounds: %f %f %f %f\n",
1579           bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
1581   //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
1582   //CGContextFillRect(cgContext, bounds);
1583 #endif
1585   nsEventStates eventState = GetContentState(aFrame, aWidgetType);
1587   switch (aWidgetType) {
1588     case NS_THEME_DIALOG: {
1589       HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
1590       CGContextFillRect(cgContext, macRect);
1591     }
1592       break;
1594     case NS_THEME_MENUPOPUP: {
1595       HIThemeMenuDrawInfo mdi = {
1596         version: 0,
1597         menuType: IsDisabled(aFrame, eventState) ? kThemeMenuTypeInactive
1598                                                  : kThemeMenuTypePopUp
1599       };
1601       PRBool isLeftOfParent = PR_FALSE;
1602       if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
1603         mdi.menuType = kThemeMenuTypeHierarchical;
1604       }
1605       
1606       // The rounded corners draw outside the frame.
1607       CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
1608                                        macRect.size.width, macRect.size.height - 8);
1609       HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
1610     }
1611       break;
1613     case NS_THEME_MENUITEM: {
1614       // Clear the background to get correct transparency.
1615       CGContextClearRect(cgContext, macRect);
1617       // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
1618       HIThemeMenuItemDrawInfo drawInfo = {
1619         version: 0,
1620         itemType: kThemeMenuItemPlain,
1621         state: (IsDisabled(aFrame, eventState) ? kThemeMenuDisabled :
1622                  CheckBooleanAttr(aFrame, nsWidgetAtoms::mozmenuactive) ? kThemeMenuSelected :
1623                  kThemeMenuActive)
1624       };
1626       // XXX pass in the menu rect instead of always using the item rect
1627       HIRect ignored;
1628       HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
1629     }
1630       break;
1632     case NS_THEME_MENUSEPARATOR: {
1633       ThemeMenuState menuState;
1634       if (IsDisabled(aFrame, eventState)) {
1635         menuState = kThemeMenuDisabled;
1636       }
1637       else {
1638         menuState = CheckBooleanAttr(aFrame, nsWidgetAtoms::mozmenuactive) ?
1639                     kThemeMenuSelected : kThemeMenuActive;
1640       }
1642       HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
1643       HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
1644     }
1645       break;
1647     case NS_THEME_TOOLTIP:
1648       CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
1649       CGContextFillRect(cgContext, macRect);
1650       break;
1652     case NS_THEME_CHECKBOX:
1653     case NS_THEME_RADIO: {
1654       PRBool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
1655       DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
1656                           eventState, aFrame);
1657     }
1658       break;
1660     case NS_THEME_BUTTON:
1661       if (IsDefaultButton(aFrame)) {
1662         if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
1663           NS_WARNING("Unable to animate button!");
1664         }
1665         DrawButton(cgContext, kThemePushButton, macRect, true,
1666                    kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
1667       } else if (IsButtonTypeMenu(aFrame)) {
1668         DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
1669       } else {
1670         DrawPushButton(cgContext, macRect, eventState, aFrame);
1671       }
1672       break;
1674     case NS_THEME_BUTTON_BEVEL:
1675       DrawButton(cgContext, kThemeMediumBevelButton, macRect,
1676                  IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
1677                  eventState, aFrame);
1678       break;
1680     case NS_THEME_SPINNER: {
1681       ThemeDrawState state = kThemeStateActive;
1682       nsIContent* content = aFrame->GetContent();
1683       if (content->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::state,
1684                                NS_LITERAL_STRING("up"), eCaseMatters)) {
1685         state = kThemeStatePressedUp;
1686       }
1687       else if (content->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::state,
1688                                     NS_LITERAL_STRING("down"), eCaseMatters)) {
1689         state = kThemeStatePressedDown;
1690       }
1692       DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
1693                       kThemeAdornmentNone, eventState, aFrame);
1694     }
1695       break;
1697     case NS_THEME_TOOLBAR_BUTTON:
1698       DrawButton(cgContext, kThemePushButton, macRect,
1699                  IsDefaultButton(aFrame), kThemeButtonOn, kThemeAdornmentNone,
1700                  eventState, aFrame);
1701       break;
1703     case NS_THEME_TOOLBAR_SEPARATOR: {
1704       HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
1705       HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
1706     }
1707       break;
1709     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
1710     case NS_THEME_TOOLBAR: {
1711       NSWindow* win = NativeWindowForFrame(aFrame);
1712       if (ToolbarCanBeUnified(cgContext, macRect, win)) {
1713         DrawUnifiedToolbar(cgContext, macRect, win);
1714         break;
1715       }
1716       BOOL isMain = [win isMainWindow] || ![NSView focusView];
1717       CGRect drawRect = macRect;
1719       // top border
1720       drawRect.size.height = 1.0f;
1721       DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
1723       // background
1724       drawRect.origin.y += drawRect.size.height;
1725       drawRect.size.height = macRect.size.height - 2.0f;
1726       DrawNativeGreyColorInRect(cgContext, headerEndGrey, drawRect, isMain);
1728       // bottom border
1729       drawRect.origin.y += drawRect.size.height;
1730       drawRect.size.height = 1.0f;
1731       DrawNativeGreyColorInRect(cgContext, headerBorderGrey, drawRect, isMain);
1732     }
1733       break;
1735     case NS_THEME_TOOLBOX: {
1736       HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
1737       HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
1738     }
1739       break;
1741     case NS_THEME_STATUSBAR: 
1742       DrawStatusBar(cgContext, macRect, aFrame);
1743       break;
1745     case NS_THEME_DROPDOWN:
1746     case NS_THEME_DROPDOWN_TEXTFIELD:
1747       DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
1748       break;
1750     case NS_THEME_DROPDOWN_BUTTON:
1751       DrawButton(cgContext, kThemeArrowButton, macRect, PR_FALSE, kThemeButtonOn,
1752                  kThemeAdornmentArrowDownArrow, eventState, aFrame);
1753       break;
1755     case NS_THEME_GROUPBOX: {
1756       HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
1757       HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
1758       break;
1759     }
1761     case NS_THEME_TEXTFIELD:
1762       // HIThemeSetFill is not available on 10.3
1763       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1764       CGContextFillRect(cgContext, macRect);
1766       // XUL textboxes set the native appearance on the containing box, while
1767       // concrete focus is set on the html:input element within it. We can
1768       // though, check the focused attribute of xul textboxes in this case.
1769       // On Mac, focus rings are always shown for textboxes, so we do not need
1770       // to check the window's focus ring state here
1771       if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
1772         eventState |= NS_EVENT_STATE_FOCUS;
1773       }
1775       DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
1776                 IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
1777       break;
1778       
1779     case NS_THEME_SEARCHFIELD:
1780       DrawSearchField(cgContext, macRect, aFrame, eventState);
1781       break;
1783     case NS_THEME_PROGRESSBAR:
1784       if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
1785         NS_WARNING("Unable to animate progressbar!");
1786       }
1787       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame),
1788                    PR_TRUE, GetProgressValue(aFrame),
1789                    GetProgressMaxValue(aFrame), aFrame);
1790       break;
1792     case NS_THEME_PROGRESSBAR_VERTICAL:
1793       DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame),
1794                    PR_FALSE, GetProgressValue(aFrame),
1795                    GetProgressMaxValue(aFrame), aFrame);
1796       break;
1798     case NS_THEME_PROGRESSBAR_CHUNK:
1799     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
1800       // do nothing, covered by the progress bar cases above
1801       break;
1803     case NS_THEME_TREEVIEW_TWISTY:
1804       DrawButton(cgContext, kThemeDisclosureButton, macRect, PR_FALSE,
1805                  kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
1806       break;
1808     case NS_THEME_TREEVIEW_TWISTY_OPEN:
1809       DrawButton(cgContext, kThemeDisclosureButton, macRect, PR_FALSE,
1810                  kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
1811       break;
1813     case NS_THEME_TREEVIEW_HEADER_CELL: {
1814       TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
1815       DrawButton(cgContext, kThemeListHeaderButton, macRect, PR_FALSE,
1816                  sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
1817                  sortDirection == eTreeSortDirection_Ascending ?
1818                  kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
1819     }
1820       break;
1822     case NS_THEME_TREEVIEW_TREEITEM:
1823     case NS_THEME_TREEVIEW:
1824       // HIThemeSetFill is not available on 10.3
1825       // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
1826       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1827       CGContextFillRect(cgContext, macRect);
1828       break;
1830     case NS_THEME_TREEVIEW_HEADER:
1831       // do nothing, taken care of by individual header cells
1832     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
1833       // do nothing, taken care of by treeview header
1834     case NS_THEME_TREEVIEW_LINE:
1835       // do nothing, these lines don't exist on macos
1836       break;
1838     case NS_THEME_SCALE_HORIZONTAL:
1839     case NS_THEME_SCALE_VERTICAL: {
1840       PRInt32 curpos = CheckIntAttr(aFrame, nsWidgetAtoms::curpos, 0);
1841       PRInt32 minpos = CheckIntAttr(aFrame, nsWidgetAtoms::minpos, 0);
1842       PRInt32 maxpos = CheckIntAttr(aFrame, nsWidgetAtoms::maxpos, 100);
1843       if (!maxpos)
1844         maxpos = 100;
1846       PRBool reverse = aFrame->GetContent()->
1847         AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::dir,
1848                     NS_LITERAL_STRING("reverse"), eCaseMatters);
1849       DrawScale(cgContext, macRect, eventState,
1850                 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
1851                 curpos, minpos, maxpos, aFrame);
1852     }
1853       break;
1855     case NS_THEME_SCALE_THUMB_HORIZONTAL:
1856     case NS_THEME_SCALE_THUMB_VERTICAL:
1857       // do nothing, drawn by scale
1858       break;
1860     case NS_THEME_SCROLLBAR_SMALL:
1861     case NS_THEME_SCROLLBAR: {
1862       DrawScrollbar(cgContext, macRect, aFrame);
1863     }
1864       break;
1865     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
1866     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
1867 #if SCROLLBARS_VISUAL_DEBUG
1868       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 0, 0.6);
1869       CGContextFillRect(cgContext, macRect);
1870     break;
1871 #endif
1872     case NS_THEME_SCROLLBAR_BUTTON_UP:
1873     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
1874 #if SCROLLBARS_VISUAL_DEBUG
1875       CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
1876       CGContextFillRect(cgContext, macRect);
1877     break;
1878 #endif
1879     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
1880     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
1881 #if SCROLLBARS_VISUAL_DEBUG
1882       CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
1883       CGContextFillRect(cgContext, macRect);
1884     break;      
1885 #endif
1886     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
1887     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
1888       // do nothing, drawn by scrollbar
1889       break;
1891     case NS_THEME_TEXTFIELD_MULTILINE: {
1892       // we have to draw this by hand because there is no HITheme value for it
1893       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1894       
1895       CGContextFillRect(cgContext, macRect);
1897       CGContextSetLineWidth(cgContext, 1.0);
1898       CGContextSetShouldAntialias(cgContext, false);
1900       // stroke everything but the top line of the text area
1901       CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
1902       CGContextBeginPath(cgContext);
1903       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
1904       CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
1905       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
1906       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
1907       CGContextStrokePath(cgContext);
1909       // stroke the line across the top of the text area
1910       CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
1911       CGContextBeginPath(cgContext);
1912       CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
1913       CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
1914       CGContextStrokePath(cgContext);
1916       // draw a focus ring
1917       if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
1918         // We need to bring the rectangle in by 1 pixel on each side.
1919         CGRect cgr = CGRectMake(macRect.origin.x + 1,
1920                                 macRect.origin.y + 1,
1921                                 macRect.size.width - 2,
1922                                 macRect.size.height - 2);
1923         HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
1924       }
1925     }
1926       break;
1928     case NS_THEME_LISTBOX: {
1929       // We have to draw this by hand because kHIThemeFrameListBox drawing
1930       // is buggy on 10.5, see bug 579259.
1931       CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
1932       CGContextFillRect(cgContext, macRect);
1934       // #8E8E8E for the top border, #BEBEBE for the rest.
1935       int x = macRect.origin.x, y = macRect.origin.y;
1936       int w = macRect.size.width, h = macRect.size.height;
1937       CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
1938       CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
1939       CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
1940       CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
1941       CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
1942       CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
1943     }
1944       break;
1945     
1946     case NS_THEME_TAB:
1947       DrawTab(cgContext, macRect, eventState, aFrame);
1948       break;
1950     case NS_THEME_TAB_PANELS:
1951       DrawTabPanel(cgContext, macRect, aFrame);
1952       break;
1954     case NS_THEME_RESIZER:
1955       DrawResizer(cgContext, macRect, aFrame);
1956       break;
1957   }
1959   nativeDrawing.EndNativeDrawing();
1961   return NS_OK;
1963   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
1966 nsIntMargin
1967 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
1969   if (IsFrameRTL(aFrame))
1970     return nsIntMargin(aMargin.right, aMargin.top, aMargin.left, aMargin.bottom);
1972   return aMargin;
1975 static const nsIntMargin kAquaDropdownBorder(5, 1, 22, 2);
1976 static const nsIntMargin kAquaComboboxBorder(4, 3, 20, 3);
1977 static const nsIntMargin kAquaSearchfieldBorder(19, 3, 5, 2);
1979 NS_IMETHODIMP
1980 nsNativeThemeCocoa::GetWidgetBorder(nsIDeviceContext* aContext, 
1981                                     nsIFrame* aFrame,
1982                                     PRUint8 aWidgetType,
1983                                     nsIntMargin* aResult)
1985   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1987   aResult->SizeTo(0, 0, 0, 0);
1989   switch (aWidgetType) {
1990     case NS_THEME_BUTTON:
1991     {
1992       if (IsButtonTypeMenu(aFrame)) {
1993         *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
1994       } else {
1995         aResult->SizeTo(7, 1, 7, 3);
1996       }
1997       break;
1998     }
2000     case NS_THEME_CHECKBOX:
2001     case NS_THEME_RADIO:
2002     {
2003       // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
2004       // assume a border width of 2px.
2005       aResult->SizeTo(2, 2, 2, 2);
2006       break;
2007     }
2009     case NS_THEME_DROPDOWN:
2010     case NS_THEME_DROPDOWN_BUTTON:
2011       *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2012       break;
2014     case NS_THEME_DROPDOWN_TEXTFIELD:
2015       *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
2016       break;
2018     case NS_THEME_TEXTFIELD:
2019     {
2020       SInt32 frameOutset = 0;
2021       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2023       SInt32 textPadding = 0;
2024       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2026       frameOutset += textPadding;
2028       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2029       break;
2030     }
2032     case NS_THEME_TEXTFIELD_MULTILINE:
2033       aResult->SizeTo(1, 1, 1, 1);
2034       break;
2036     case NS_THEME_SEARCHFIELD:
2037       *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
2038       break;
2040     case NS_THEME_LISTBOX:
2041     {
2042       SInt32 frameOutset = 0;
2043       ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2044       aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2045       break;
2046     }
2048     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2049     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2050     {
2051       // There's only an endcap to worry about when both arrows are on the bottom
2052       NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2053       if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2054         PRBool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2056         nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2057         if (!scrollbarFrame) return NS_ERROR_FAILURE;
2058         PRBool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2060         // There isn't a metric for this, so just hardcode a best guess at the value.
2061         // This value is even less exact due to the fact that the endcap is partially concave.
2062         PRInt32 endcapSize = isSmall ? 5 : 6;
2064         if (isHorizontal)
2065           aResult->SizeTo(endcapSize, 0, 0, 0);
2066         else
2067           aResult->SizeTo(0, endcapSize, 0, 0);
2068       }
2069       break;
2070     }
2072     case NS_THEME_STATUSBAR:
2073       aResult->SizeTo(0, 1, 0, 0);
2074       break;
2075   }
2077   return NS_OK;
2079   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2082 // Return PR_FALSE here to indicate that CSS padding values should be used. There is
2083 // no reason to make a distinction between padding and border values, just specify
2084 // whatever values you want in GetWidgetBorder and only use this to return PR_TRUE
2085 // if you want to override CSS padding values.
2086 PRBool
2087 nsNativeThemeCocoa::GetWidgetPadding(nsIDeviceContext* aContext, 
2088                                      nsIFrame* aFrame,
2089                                      PRUint8 aWidgetType,
2090                                      nsIntMargin* aResult)
2092   // We don't want CSS padding being used for certain widgets.
2093   // See bug 381639 for an example of why.
2094   switch (aWidgetType) {
2095     case NS_THEME_BUTTON:
2096     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2097     // and have a meaningful baseline, so they can't have
2098     // author-specified padding.
2099     case NS_THEME_CHECKBOX:
2100     case NS_THEME_RADIO:
2101       aResult->SizeTo(0, 0, 0, 0);
2102       return PR_TRUE;
2103   }
2104   return PR_FALSE;
2107 PRBool
2108 nsNativeThemeCocoa::GetWidgetOverflow(nsIDeviceContext* aContext, nsIFrame* aFrame,
2109                                       PRUint8 aWidgetType, nsRect* aOverflowRect)
2111   switch (aWidgetType) {
2112     case NS_THEME_BUTTON:
2113     case NS_THEME_TEXTFIELD:
2114     case NS_THEME_TEXTFIELD_MULTILINE:
2115     case NS_THEME_SEARCHFIELD:
2116     case NS_THEME_LISTBOX:
2117     case NS_THEME_DROPDOWN:
2118     case NS_THEME_DROPDOWN_BUTTON:
2119     case NS_THEME_DROPDOWN_TEXTFIELD:
2120     case NS_THEME_CHECKBOX:
2121     case NS_THEME_RADIO:
2122     case NS_THEME_TAB:
2123     {
2124       // We assume that the above widgets can draw a focus ring that will be less than
2125       // or equal to 4 pixels thick.
2126       nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
2127       PRInt32 p2a = aContext->AppUnitsPerDevPixel();
2128       nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a),
2129                  NSIntPixelsToAppUnits(extraSize.top, p2a),
2130                  NSIntPixelsToAppUnits(extraSize.right, p2a),
2131                  NSIntPixelsToAppUnits(extraSize.bottom, p2a));
2132       aOverflowRect->Inflate(m);
2133       return PR_TRUE;
2134     }
2135   }
2137   return PR_FALSE;
2140 static const PRInt32 kRegularScrollbarThumbMinSize = 22;
2141 static const PRInt32 kSmallScrollbarThumbMinSize = 19;
2143 NS_IMETHODIMP
2144 nsNativeThemeCocoa::GetMinimumWidgetSize(nsIRenderingContext* aContext,
2145                                          nsIFrame* aFrame,
2146                                          PRUint8 aWidgetType,
2147                                          nsIntSize* aResult,
2148                                          PRBool* aIsOverridable)
2150   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2152   aResult->SizeTo(0,0);
2153   *aIsOverridable = PR_TRUE;
2155   switch (aWidgetType) {
2156     case NS_THEME_BUTTON:
2157     {
2158       aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2159                       pushButtonSettings.naturalSizes[miniControlSize].height);
2160       break;
2161     }
2163     case NS_THEME_SPINNER:
2164     {
2165       SInt32 buttonHeight = 0, buttonWidth = 0;
2166       ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2167       ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2168       aResult->SizeTo(buttonWidth, buttonHeight);
2169       *aIsOverridable = PR_FALSE;
2170       break;
2171     }
2173     case NS_THEME_DROPDOWN:
2174     case NS_THEME_DROPDOWN_BUTTON:
2175     {
2176       SInt32 popupHeight = 0;
2177       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2178       aResult->SizeTo(0, popupHeight);
2179       break;
2180     }
2182     case NS_THEME_TEXTFIELD:
2183     case NS_THEME_TEXTFIELD_MULTILINE:
2184     case NS_THEME_SEARCHFIELD:
2185     {
2186       // at minimum, we should be tall enough for 9pt text.
2187       // I'm using hardcoded values here because the appearance manager
2188       // values for the frame size are incorrect.
2189       aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2190       break;
2191     }
2192       
2193     case NS_THEME_PROGRESSBAR:
2194     {
2195       SInt32 barHeight = 0;
2196       ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2197       aResult->SizeTo(0, barHeight);
2198       break;
2199     }
2201     case NS_THEME_TREEVIEW_TWISTY:
2202     case NS_THEME_TREEVIEW_TWISTY_OPEN:   
2203     {
2204       SInt32 twistyHeight = 0, twistyWidth = 0;
2205       ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2206       ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2207       aResult->SizeTo(twistyWidth, twistyHeight);
2208       *aIsOverridable = PR_FALSE;
2209       break;
2210     }
2211     
2212     case NS_THEME_TREEVIEW_HEADER:
2213     case NS_THEME_TREEVIEW_HEADER_CELL:
2214     {
2215       SInt32 headerHeight = 0;
2216       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2217       aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
2218       break;
2219     }
2221     case NS_THEME_TAB:
2222     {
2223       aResult->SizeTo(0, NATURAL_MINI_TAB_BUTTON_HEIGHT);
2224       break;
2225     }
2227     case NS_THEME_SCALE_HORIZONTAL:
2228     {
2229       SInt32 scaleHeight = 0;
2230       ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
2231       aResult->SizeTo(scaleHeight, scaleHeight);
2232       *aIsOverridable = PR_FALSE;
2233       break;
2234     }
2236     case NS_THEME_SCALE_VERTICAL:
2237     {
2238       SInt32 scaleWidth = 0;
2239       ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
2240       aResult->SizeTo(scaleWidth, scaleWidth);
2241       *aIsOverridable = PR_FALSE;
2242       break;
2243     }
2244       
2245     case NS_THEME_SCROLLBAR_SMALL:
2246     {
2247       SInt32 scrollbarWidth = 0;
2248       ::GetThemeMetric(kThemeMetricSmallScrollBarWidth, &scrollbarWidth);
2249       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2250       *aIsOverridable = PR_FALSE;
2251       break;
2252     }
2254     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2255     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2256     {
2257       // Find our parent scrollbar frame in order to find out whether we're in
2258       // a small or a large scrollbar.
2259       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2260       if (!scrollbarFrame)
2261         return NS_ERROR_FAILURE;
2263       PRBool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2264       PRBool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2265       PRInt32& minSize = isHorizontal ? aResult->width : aResult->height;
2266       minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
2267       break;
2268     }
2270     case NS_THEME_SCROLLBAR:
2271     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2272     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2273     {
2274       // yeah, i know i'm cheating a little here, but i figure that it
2275       // really doesn't matter if the scrollbar is vertical or horizontal
2276       // and the width metric is a really good metric for every piece
2277       // of the scrollbar.
2279       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2280       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2282       PRInt32 themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2283                             kThemeMetricSmallScrollBarWidth :
2284                             kThemeMetricScrollBarWidth;
2285       SInt32 scrollbarWidth = 0;
2286       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2287       aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2288       *aIsOverridable = PR_FALSE;
2289       break;
2290     }
2292     case NS_THEME_SCROLLBAR_BUTTON_UP:
2293     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2294     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2295     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2296     {
2297       nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2298       if (!scrollbarFrame) return NS_ERROR_FAILURE;
2300       // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
2301       PRInt32 themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2302                             kThemeMetricSmallScrollBarWidth :
2303                             kThemeMetricScrollBarWidth;
2304       SInt32 scrollbarWidth = 0;
2305       ::GetThemeMetric(themeMetric, &scrollbarWidth);
2307       // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
2308       if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
2309         aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
2310       else
2311         aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
2313       *aIsOverridable = PR_FALSE;
2314       break;
2315     }
2316     case NS_THEME_RESIZER:
2317     {
2318       HIThemeGrowBoxDrawInfo drawInfo;
2319       drawInfo.version = 0;
2320       drawInfo.state = kThemeStateActive;
2321       drawInfo.kind = kHIThemeGrowBoxKindNormal;
2322       drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2323       drawInfo.size = kHIThemeGrowBoxSizeNormal;
2324       HIPoint pnt = { 0, 0 };
2325       HIRect bounds;
2326       HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
2327       aResult->SizeTo(bounds.size.width, bounds.size.height);
2328       *aIsOverridable = PR_FALSE;
2329     }
2330   }
2332   return NS_OK;
2334   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2337 NS_IMETHODIMP
2338 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, PRUint8 aWidgetType, 
2339                                      nsIAtom* aAttribute, PRBool* aShouldRepaint)
2341   // Some widget types just never change state.
2342   switch (aWidgetType) {
2343     case NS_THEME_TOOLBOX:
2344     case NS_THEME_TOOLBAR:
2345     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2346     case NS_THEME_TOOLBAR_BUTTON:
2347     case NS_THEME_SCROLLBAR_TRACK_VERTICAL: 
2348     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2349     case NS_THEME_STATUSBAR:
2350     case NS_THEME_STATUSBAR_PANEL:
2351     case NS_THEME_STATUSBAR_RESIZER_PANEL:
2352     case NS_THEME_TOOLTIP:
2353     case NS_THEME_TAB_PANELS:
2354     case NS_THEME_TAB_PANEL:
2355     case NS_THEME_DIALOG:
2356     case NS_THEME_MENUPOPUP:
2357     case NS_THEME_GROUPBOX:
2358     case NS_THEME_PROGRESSBAR_CHUNK:
2359     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2360     case NS_THEME_PROGRESSBAR:
2361     case NS_THEME_PROGRESSBAR_VERTICAL:
2362       *aShouldRepaint = PR_FALSE;
2363       return NS_OK;
2364   }
2366   // XXXdwh Not sure what can really be done here.  Can at least guess for
2367   // specific widgets that they're highly unlikely to have certain states.
2368   // For example, a toolbar doesn't care about any states.
2369   if (!aAttribute) {
2370     // Hover/focus/active changed.  Always repaint.
2371     *aShouldRepaint = PR_TRUE;
2372   } else {
2373     // Check the attribute to see if it's relevant.  
2374     // disabled, checked, dlgtype, default, etc.
2375     *aShouldRepaint = PR_FALSE;
2376     if (aAttribute == nsWidgetAtoms::disabled ||
2377         aAttribute == nsWidgetAtoms::checked ||
2378         aAttribute == nsWidgetAtoms::selected ||
2379         aAttribute == nsWidgetAtoms::mozmenuactive ||
2380         aAttribute == nsWidgetAtoms::sortdirection ||
2381         aAttribute == nsWidgetAtoms::focused ||
2382         aAttribute == nsWidgetAtoms::_default ||
2383         aAttribute == nsWidgetAtoms::open)
2384       *aShouldRepaint = PR_TRUE;
2385   }
2387   return NS_OK;
2390 NS_IMETHODIMP
2391 nsNativeThemeCocoa::ThemeChanged()
2393   // This is unimplemented because we don't care if gecko changes its theme
2394   // and Mac OS X doesn't have themes.
2395   return NS_OK;
2398 PRBool 
2399 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
2400                                       PRUint8 aWidgetType)
2402   // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
2403   // render natively even if native theme support is disabled.
2404   if (aWidgetType != NS_THEME_SCROLLBAR &&
2405       aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
2406     return PR_FALSE;
2408   // if this is a dropdown button in a combobox the answer is always no
2409   if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
2410     nsIFrame* parentFrame = aFrame->GetParent();
2411     if (parentFrame && (parentFrame->GetType() == nsWidgetAtoms::comboboxControlFrame))
2412       return PR_FALSE;
2413   }
2415   switch (aWidgetType) {
2416     case NS_THEME_LISTBOX:
2418     case NS_THEME_DIALOG:
2419     case NS_THEME_WINDOW:
2420     case NS_THEME_MENUPOPUP:
2421     case NS_THEME_MENUITEM:
2422     case NS_THEME_MENUSEPARATOR:
2423     case NS_THEME_TOOLTIP:
2424     
2425     case NS_THEME_CHECKBOX:
2426     case NS_THEME_CHECKBOX_CONTAINER:
2427     case NS_THEME_RADIO:
2428     case NS_THEME_RADIO_CONTAINER:
2429     case NS_THEME_GROUPBOX:
2430     case NS_THEME_BUTTON:
2431     case NS_THEME_BUTTON_BEVEL:
2432     case NS_THEME_SPINNER:
2433     case NS_THEME_TOOLBAR:
2434     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2435     case NS_THEME_STATUSBAR:
2436     case NS_THEME_TEXTFIELD:
2437     case NS_THEME_TEXTFIELD_MULTILINE:
2438     case NS_THEME_SEARCHFIELD:
2439     //case NS_THEME_TOOLBOX:
2440     //case NS_THEME_TOOLBAR_BUTTON:
2441     case NS_THEME_PROGRESSBAR:
2442     case NS_THEME_PROGRESSBAR_VERTICAL:
2443     case NS_THEME_PROGRESSBAR_CHUNK:
2444     case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2445     case NS_THEME_TOOLBAR_SEPARATOR:
2446     
2447     case NS_THEME_TAB_PANELS:
2448     case NS_THEME_TAB:
2449     
2450     case NS_THEME_TREEVIEW_TWISTY:
2451     case NS_THEME_TREEVIEW_TWISTY_OPEN:
2452     case NS_THEME_TREEVIEW:
2453     case NS_THEME_TREEVIEW_HEADER:
2454     case NS_THEME_TREEVIEW_HEADER_CELL:
2455     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2456     case NS_THEME_TREEVIEW_TREEITEM:
2457     case NS_THEME_TREEVIEW_LINE:
2459     case NS_THEME_SCALE_HORIZONTAL:
2460     case NS_THEME_SCALE_THUMB_HORIZONTAL:
2461     case NS_THEME_SCALE_VERTICAL:
2462     case NS_THEME_SCALE_THUMB_VERTICAL:
2464     case NS_THEME_SCROLLBAR:
2465     case NS_THEME_SCROLLBAR_SMALL:
2466     case NS_THEME_SCROLLBAR_BUTTON_UP:
2467     case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2468     case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2469     case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2470     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2471     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2472     case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2473     case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2475     case NS_THEME_DROPDOWN:
2476     case NS_THEME_DROPDOWN_BUTTON:
2477     case NS_THEME_DROPDOWN_TEXT:
2478     case NS_THEME_DROPDOWN_TEXTFIELD:
2479       return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
2480       break;
2482     case NS_THEME_RESIZER:
2483     {
2484       nsIFrame* parentFrame = aFrame->GetParent();
2485       if (!parentFrame || parentFrame->GetType() != nsWidgetAtoms::scrollFrame)
2486         return PR_TRUE;
2488       // Note that IsWidgetStyled is not called for resizers on Mac. This is
2489       // because for scrollable containers, the native resizer looks better
2490       // when scrollbars are present even when the style is overriden, and the
2491       // custom transparent resizer looks better when scrollbars are not
2492       // present.
2493       nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
2494       return (scrollFrame && scrollFrame->GetScrollbarVisibility());
2495       break;
2496     }
2497   }
2499   return PR_FALSE;
2502 PRBool
2503 nsNativeThemeCocoa::WidgetIsContainer(PRUint8 aWidgetType)
2505   // flesh this out at some point
2506   switch (aWidgetType) {
2507    case NS_THEME_DROPDOWN_BUTTON:
2508    case NS_THEME_RADIO:
2509    case NS_THEME_CHECKBOX:
2510    case NS_THEME_PROGRESSBAR:
2511     return PR_FALSE;
2512     break;
2513   }
2514   return PR_TRUE;
2517 PRBool
2518 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsPresContext* aPresContext, nsIFrame* aFrame, PRUint8 aWidgetType)
2520   if (aWidgetType == NS_THEME_DROPDOWN ||
2521       aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
2522       aWidgetType == NS_THEME_BUTTON ||
2523       aWidgetType == NS_THEME_RADIO ||
2524       aWidgetType == NS_THEME_CHECKBOX)
2525     return PR_TRUE;
2527   return PR_FALSE;
2530 PRBool
2531 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
2533   return PR_FALSE;
2536 nsITheme::Transparency
2537 nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, PRUint8 aWidgetType)
2539   switch (aWidgetType) {
2540   case NS_THEME_MENUPOPUP:
2541   case NS_THEME_TOOLTIP:
2542     return eTransparent;
2544   case NS_THEME_SCROLLBAR_SMALL:
2545   case NS_THEME_SCROLLBAR:
2546   case NS_THEME_STATUSBAR:
2547     // Knowing that scrollbars and statusbars are opaque improves
2548     // performance, because we create layers for them.
2549     return eOpaque;
2551   default:
2552     return eUnknownTransparency;
2553   }