5b3b7975f6dc665884a65d323a59570435d41812
[WebKit-https.git] / Source / WebCore / platform / mac / ThemeMac.mm
1 /*
2  * Copyright (C) 2008, 2010, 2011, 2012 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "ThemeMac.h"
28
29 #import "BlockExceptions.h"
30 #import "GraphicsContext.h"
31 #import "LocalCurrentGraphicsContext.h"
32 #import "ScrollView.h"
33 #import "WebCoreNSCellExtras.h"
34 #import "WebCoreSystemInterface.h"
35 #import <Carbon/Carbon.h>
36 #include <wtf/StdLibExtras.h>
37
38 using namespace std;
39
40 NSRect focusRingClipRect;
41
42 // This is a view whose sole purpose is to tell AppKit that it's flipped.
43 @interface WebCoreFlippedView : NSControl
44 @end
45
46 @implementation WebCoreFlippedView
47
48 - (BOOL)isFlipped
49 {
50     return YES;
51 }
52
53 - (NSText *)currentEditor
54 {
55     return nil;
56 }
57
58 - (BOOL)_automaticFocusRingDisabled
59 {
60     return YES;
61 }
62
63 - (NSRect)_focusRingVisibleRect
64 {
65     if (NSIsEmptyRect(focusRingClipRect))
66         return [self visibleRect];
67
68     NSRect rect = focusRingClipRect;
69 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090
70     rect.origin.y = [self bounds].size.height - NSMaxY(rect);
71 #endif
72
73     return rect;
74 }
75
76 - (NSView *)_focusRingClipAncestor
77 {
78     return self;
79 }
80
81 @end
82
83 @implementation NSFont (WebCoreTheme)
84
85 - (NSString*)webCoreFamilyName
86 {
87     if ([[self familyName] hasPrefix:@"."])
88         return [self fontName];
89
90     return [self familyName];
91 }
92
93 @end
94
95 // FIXME: Default buttons really should be more like push buttons and not like buttons.
96
97 namespace WebCore {
98
99 enum {
100     topMargin,
101     rightMargin,
102     bottomMargin,
103     leftMargin
104 };
105
106 Theme* platformTheme()
107 {
108     DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ());
109     return &themeMac;
110 }
111
112 // Helper functions used by a bunch of different control parts.
113
114 static NSControlSize controlSizeForFont(const Font& font)
115 {
116     int fontSize = font.pixelSize();
117     if (fontSize >= 16)
118         return NSRegularControlSize;
119     if (fontSize >= 11)
120         return NSSmallControlSize;
121     return NSMiniControlSize;
122 }
123
124 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
125 {
126     IntSize controlSize = sizes[nsControlSize];
127     if (zoomFactor != 1.0f)
128         controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
129     LengthSize result = zoomedSize;
130     if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
131         result.setWidth(Length(controlSize.width(), Fixed));
132     if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
133         result.setHeight(Length(controlSize.height(), Fixed));
134     return result;
135 }
136
137 static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
138 {
139     return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
140 }
141
142 static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
143 {
144     if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
145         minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
146         return NSRegularControlSize;
147     if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
148         minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
149         return NSSmallControlSize;
150     return NSMiniControlSize;
151 }
152
153 static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
154 {
155     ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
156     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
157         [cell setControlSize:(NSControlSize)size];
158 }
159
160 static void updateStates(NSCell* cell, ControlStates states)
161 {
162     // Hover state is not supported by Aqua.
163     
164     // Pressed state
165     bool oldPressed = [cell isHighlighted];
166     bool pressed = states & PressedState;
167     if (pressed != oldPressed)
168         [cell setHighlighted:pressed];
169     
170     // Enabled state
171     bool oldEnabled = [cell isEnabled];
172     bool enabled = states & EnabledState;
173     if (enabled != oldEnabled)
174         [cell setEnabled:enabled];
175     
176 #if BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING
177     // Focused state
178     bool oldFocused = [cell showsFirstResponder];
179     bool focused = states & FocusState;
180     if (focused != oldFocused)
181         [cell setShowsFirstResponder:focused];
182 #endif
183
184     // Checked and Indeterminate
185     bool oldIndeterminate = [cell state] == NSMixedState;
186     bool indeterminate = (states & IndeterminateState);
187     bool checked = states & CheckedState;
188     bool oldChecked = [cell state] == NSOnState;
189     if (oldIndeterminate != indeterminate || checked != oldChecked)
190         [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];
191         
192     // Window inactive state does not need to be checked explicitly, since we paint parented to 
193     // a view in a window whose key state can be detected.
194 }
195
196 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states)
197 {
198     if (states & ReadOnlyState)
199         return kThemeStateUnavailableInactive;
200     if (!(states & EnabledState))
201         return kThemeStateUnavailableInactive;
202
203     // Do not process PressedState if !EnabledState or ReadOnlyState.
204     if (states & PressedState) {
205         if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
206             return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
207         return kThemeStatePressed;
208     }
209     return kThemeStateActive;
210 }
211
212 static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
213 {
214     // Only do the inflation if the available width/height are too small.  Otherwise try to
215     // fit the glow/check space into the available box's width/height.
216     int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
217     int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
218     IntRect result(zoomedRect);
219     if (widthDelta < 0) {
220         result.setX(result.x() - margins[leftMargin] * zoomFactor);
221         result.setWidth(result.width() - widthDelta);
222     }
223     if (heightDelta < 0) {
224         result.setY(result.y() - margins[topMargin] * zoomFactor);
225         result.setHeight(result.height() - heightDelta);
226     }
227     return result;
228 }
229
230 // Checkboxes
231
232 static const IntSize* checkboxSizes()
233 {
234     static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
235     return sizes;
236 }
237
238 static const int* checkboxMargins(NSControlSize controlSize)
239 {
240     static const int margins[3][4] =
241     {
242         { 3, 4, 4, 2 },
243         { 4, 3, 3, 3 },
244         { 4, 3, 3, 3 },
245     };
246     return margins[controlSize];
247 }
248
249 static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
250 {
251     // If the width and height are both specified, then we have nothing to do.
252     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
253         return zoomedSize;
254
255     // Use the font size to determine the intrinsic width of the control.
256     return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
257 }
258
259 static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
260 {
261     static NSButtonCell *checkboxCell;
262     if (!checkboxCell) {
263         checkboxCell = [[NSButtonCell alloc] init];
264         [checkboxCell setButtonType:NSSwitchButton];
265         [checkboxCell setTitle:nil];
266         [checkboxCell setAllowsMixedState:YES];
267         [checkboxCell setFocusRingType:NSFocusRingTypeExterior];
268     }
269     
270     // Set the control size based off the rectangle we're painting into.
271     setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor);
272
273     // Update the various states we respond to.
274     updateStates(checkboxCell, states);
275     
276     return checkboxCell;
277 }
278
279 // FIXME: Share more code with radio buttons.
280 static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
281 {
282     BEGIN_BLOCK_OBJC_EXCEPTIONS
283
284     // Determine the width and height needed for the control and prepare the cell for painting.
285     NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor);
286     GraphicsContextStateSaver stateSaver(*context);
287
288     NSControlSize controlSize = [checkboxCell controlSize];
289     IntSize zoomedSize = checkboxSizes()[controlSize];
290     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
291     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
292     IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
293     
294     if (zoomFactor != 1.0f) {
295         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
296         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
297         context->translate(inflatedRect.x(), inflatedRect.y());
298         context->scale(FloatSize(zoomFactor, zoomFactor));
299         context->translate(-inflatedRect.x(), -inflatedRect.y());
300     }
301
302     LocalCurrentGraphicsContext localContext(context);
303     NSView *view = ThemeMac::ensuredView(scrollView);
304     [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:view];
305 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING
306     if (states & FocusState)
307         [checkboxCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view];
308 #endif
309     [checkboxCell setControlView:nil];
310     
311     END_BLOCK_OBJC_EXCEPTIONS
312 }
313
314 // Radio Buttons
315
316 static const IntSize* radioSizes()
317 {
318     static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
319     return sizes;
320 }
321
322 static const int* radioMargins(NSControlSize controlSize)
323 {
324     static const int margins[3][4] =
325     {
326         { 2, 2, 4, 2 },
327         { 3, 2, 3, 2 },
328         { 1, 0, 2, 0 },
329     };
330     return margins[controlSize];
331 }
332
333 static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
334 {
335     // If the width and height are both specified, then we have nothing to do.
336     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
337         return zoomedSize;
338
339     // Use the font size to determine the intrinsic width of the control.
340     return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
341 }
342
343 static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
344 {
345     static NSButtonCell *radioCell;
346     if (!radioCell) {
347         radioCell = [[NSButtonCell alloc] init];
348         [radioCell setButtonType:NSRadioButton];
349         [radioCell setTitle:nil];
350         [radioCell setFocusRingType:NSFocusRingTypeExterior];
351     }
352     
353     // Set the control size based off the rectangle we're painting into.
354     setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor);
355
356     // Update the various states we respond to.
357     updateStates(radioCell, states);
358     
359     return radioCell;
360 }
361
362 static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
363 {
364     // Determine the width and height needed for the control and prepare the cell for painting.
365     NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor);
366     GraphicsContextStateSaver stateSaver(*context);
367
368     NSControlSize controlSize = [radioCell controlSize];
369     IntSize zoomedSize = radioSizes()[controlSize];
370     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
371     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
372     IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
373     
374     if (zoomFactor != 1.0f) {
375         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
376         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
377         context->translate(inflatedRect.x(), inflatedRect.y());
378         context->scale(FloatSize(zoomFactor, zoomFactor));
379         context->translate(-inflatedRect.x(), -inflatedRect.y());
380     }
381
382     LocalCurrentGraphicsContext localContext(context);
383     BEGIN_BLOCK_OBJC_EXCEPTIONS
384     NSView *view = ThemeMac::ensuredView(scrollView);
385     [radioCell drawWithFrame:NSRect(inflatedRect) inView:view];
386 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING
387     if (states & FocusState)
388         [radioCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view];
389 #endif
390     [radioCell setControlView:nil];
391     END_BLOCK_OBJC_EXCEPTIONS
392 }
393
394 // Buttons
395
396 // Buttons really only constrain height. They respect width.
397 static const IntSize* buttonSizes()
398 {
399     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
400     return sizes;
401 }
402
403 static const int* buttonMargins(NSControlSize controlSize)
404 {
405     static const int margins[3][4] =
406     {
407         { 4, 6, 7, 6 },
408         { 4, 5, 6, 5 },
409         { 0, 1, 1, 1 },
410     };
411     return margins[controlSize];
412 }
413
414 enum ButtonCellType { NormalButtonCell, DefaultButtonCell };
415
416 static NSButtonCell *leakButtonCell(ButtonCellType type)
417 {
418     NSButtonCell *cell = [[NSButtonCell alloc] init];
419     [cell setTitle:nil];
420     [cell setButtonType:NSMomentaryPushInButton];
421     if (type == DefaultButtonCell)
422         [cell setKeyEquivalent:@"\r"];
423     return cell;
424 }
425
426 static void setUpButtonCell(NSButtonCell *cell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
427 {
428     // Set the control size based off the rectangle we're painting into.
429     const IntSize* sizes = buttonSizes();
430     if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
431         // Use the square button
432         if ([cell bezelStyle] != NSShadowlessSquareBezelStyle)
433             [cell setBezelStyle:NSShadowlessSquareBezelStyle];
434     } else if ([cell bezelStyle] != NSRoundedBezelStyle)
435         [cell setBezelStyle:NSRoundedBezelStyle];
436
437     setControlSize(cell, sizes, zoomedRect.size(), zoomFactor);
438
439     // Update the various states we respond to.
440     updateStates(cell, states);
441 }
442
443 static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
444 {
445     NSButtonCell *cell;
446     if (states & DefaultState) {
447         static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell);
448         cell = defaultCell;
449     } else {
450         static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell);
451         cell = normalCell;
452     }
453     setUpButtonCell(cell, part, states, zoomedRect, zoomFactor);    
454     return cell;
455 }
456
457 static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
458 {
459     BEGIN_BLOCK_OBJC_EXCEPTIONS
460     
461     // Determine the width and height needed for the control and prepare the cell for painting.
462     NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor);
463     GraphicsContextStateSaver stateSaver(*context);
464
465     NSControlSize controlSize = [buttonCell controlSize];
466     IntSize zoomedSize = buttonSizes()[controlSize];
467     zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
468     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
469     IntRect inflatedRect = zoomedRect;
470     if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
471         // Center the button within the available space.
472         if (inflatedRect.height() > zoomedSize.height()) {
473             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
474             inflatedRect.setHeight(zoomedSize.height());
475         }
476
477         // Now inflate it to account for the shadow.
478         inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
479
480         if (zoomFactor != 1.0f) {
481             inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
482             inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
483             context->translate(inflatedRect.x(), inflatedRect.y());
484             context->scale(FloatSize(zoomFactor, zoomFactor));
485             context->translate(-inflatedRect.x(), -inflatedRect.y());
486         }
487     } 
488
489     LocalCurrentGraphicsContext localContext(context);
490     NSView *view = ThemeMac::ensuredView(scrollView);
491     NSWindow *window = [view window];
492     NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell];
493
494     if (states & DefaultState) {
495         [window setDefaultButtonCell:buttonCell];
496         wkAdvanceDefaultButtonPulseAnimation(buttonCell);
497     } else if ([previousDefaultButtonCell isEqual:buttonCell])
498         [window setDefaultButtonCell:nil];
499
500     [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view];
501 #if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING
502     if (states & FocusState)
503         [buttonCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view];
504 #endif
505     [buttonCell setControlView:nil];
506
507     if (![previousDefaultButtonCell isEqual:buttonCell])
508         [window setDefaultButtonCell:previousDefaultButtonCell];
509
510     END_BLOCK_OBJC_EXCEPTIONS
511 }
512
513 // Stepper
514
515 static const IntSize* stepperSizes()
516 {
517     static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) };
518     return sizes;
519 }
520
521 // We don't use controlSizeForFont() for steppers because the stepper height
522 // should be equal to or less than the corresponding text field height,
523 static NSControlSize stepperControlSizeForFont(const Font& font)
524 {
525     int fontSize = font.pixelSize();
526     if (fontSize >= 18)
527         return NSRegularControlSize;
528     if (fontSize >= 13)
529         return NSSmallControlSize;
530     return NSMiniControlSize;
531 }
532
533 static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*)
534 {
535     // We don't use NSStepperCell because there are no ways to draw an
536     // NSStepperCell with the up button highlighted.
537
538     HIThemeButtonDrawInfo drawInfo;
539     drawInfo.version = 0;
540     drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
541     drawInfo.adornment = kThemeAdornmentDefault;
542     ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
543     if (controlSize == NSSmallControlSize)
544         drawInfo.kind = kThemeIncDecButtonSmall;
545     else if (controlSize == NSMiniControlSize)
546         drawInfo.kind = kThemeIncDecButtonMini;
547     else
548         drawInfo.kind = kThemeIncDecButton;
549
550     IntRect rect(zoomedRect);
551     GraphicsContextStateSaver stateSaver(*context);
552     if (zoomFactor != 1.0f) {
553         rect.setWidth(rect.width() / zoomFactor);
554         rect.setHeight(rect.height() / zoomFactor);
555         context->translate(rect.x(), rect.y());
556         context->scale(FloatSize(zoomFactor, zoomFactor));
557         context->translate(-rect.x(), -rect.y());
558     }
559     CGRect bounds(rect);
560     CGRect backgroundBounds;
561     HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
562     // Center the stepper rectangle in the specified area.
563     backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2;
564     if (backgroundBounds.size.height < bounds.size.height) {
565         int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height);
566         backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1;
567     }
568
569     LocalCurrentGraphicsContext localContext(context);
570     HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0);
571 }
572
573 // This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView.
574 // If the ScrollView doesn't have an NSView, we will return a fake NSView whose sole purpose is to tell AppKit that it's flipped.
575 NSView *ThemeMac::ensuredView(ScrollView* scrollView)
576 {
577     if (NSView *documentView = scrollView->documentView())
578         return documentView;
579
580     // Use a fake flipped view.
581     static NSView *flippedView = [[WebCoreFlippedView alloc] init];
582     [flippedView setFrameSize:NSSizeFromCGSize(scrollView->totalContentsSize())];
583
584     return flippedView;
585 }
586
587 void ThemeMac::setFocusRingClipRect(const FloatRect& rect)
588 {
589     focusRingClipRect = rect;
590 }
591
592 // Theme overrides
593
594 int ThemeMac::baselinePositionAdjustment(ControlPart part) const
595 {
596     if (part == CheckboxPart || part == RadioPart)
597         return -2;
598     return Theme::baselinePositionAdjustment(part);
599 }
600
601 FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
602 {
603     switch (part) {
604         case PushButtonPart: {
605             FontDescription fontDescription;
606             fontDescription.setIsAbsoluteSize(true);
607             fontDescription.setGenericFamily(FontDescription::SerifFamily);
608
609             NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
610             fontDescription.setOneFamily([nsFont webCoreFamilyName]);
611             fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
612             fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
613             return fontDescription;
614         }
615         default:
616             return Theme::controlFont(part, font, zoomFactor);
617     }
618 }
619
620 LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
621 {
622     switch (part) {
623         case CheckboxPart:
624             return checkboxSize(font, zoomedSize, zoomFactor);
625         case RadioPart:
626             return radioSize(font, zoomedSize, zoomFactor);
627         case PushButtonPart:
628             // Height is reset to auto so that specified heights can be ignored.
629             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
630         case InnerSpinButtonPart:
631             if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
632                 return zoomedSize;
633             return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
634         default:
635             return zoomedSize;
636     }
637 }
638
639 LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
640 {
641     switch (part) {
642         case SquareButtonPart:
643         case DefaultButtonPart:
644         case ButtonPart:
645             return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
646         case InnerSpinButtonPart:{
647             IntSize base = stepperSizes()[NSMiniControlSize];
648             return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
649                               Length(static_cast<int>(base.height() * zoomFactor), Fixed));
650         }
651         default:
652             return Theme::minimumControlSize(part, font, zoomFactor);
653     }
654 }
655
656 LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
657 {
658     switch (part) {
659         case SquareButtonPart:
660         case DefaultButtonPart:
661         case ButtonPart:
662             return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
663         default:
664             return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
665     }
666 }
667
668 LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
669 {
670     switch (part) {
671         case PushButtonPart: {
672             // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
673             // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
674             // by definition constrained, since we select mini only for small cramped environments.
675             // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
676             // padding.
677             const int padding = 8 * zoomFactor;
678             return LengthBox(0, padding, 0, padding);
679         }
680         default:
681             return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
682     }
683 }
684
685 void ThemeMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const
686 {
687     BEGIN_BLOCK_OBJC_EXCEPTIONS
688     switch (part) {
689         case CheckboxPart: {
690             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
691             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
692             NSCell *cell = checkbox(states, zoomedRect, zoomFactor);
693             NSControlSize controlSize = [cell controlSize];
694             IntSize zoomedSize = checkboxSizes()[controlSize];
695             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
696             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
697             zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
698             break;
699         }
700         case RadioPart: {
701             // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
702             // shadow".  We don't consider this part of the bounds of the control in WebKit.
703             NSCell *cell = radio(states, zoomedRect, zoomFactor);
704             NSControlSize controlSize = [cell controlSize];
705             IntSize zoomedSize = radioSizes()[controlSize];
706             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
707             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
708             zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
709             break;
710         }
711         case PushButtonPart:
712         case DefaultButtonPart:
713         case ButtonPart: {
714             NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor);
715             NSControlSize controlSize = [cell controlSize];
716
717             // We inflate the rect as needed to account for the Aqua button's shadow.
718             if ([cell bezelStyle] == NSRoundedBezelStyle) {
719                 IntSize zoomedSize = buttonSizes()[controlSize];
720                 zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
721                 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
722                 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
723             }
724             break;
725         }
726         case InnerSpinButtonPart: {
727             static const int stepperMargin[4] = { 0, 0, 0, 0 };
728             ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
729             IntSize zoomedSize = stepperSizes()[controlSize];
730             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
731             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
732             zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
733             break;
734         }
735         default:
736             break;
737     }
738     END_BLOCK_OBJC_EXCEPTIONS
739 }
740
741 void ThemeMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const
742 {
743     switch (part) {
744         case CheckboxPart:
745             paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView);
746             break;
747         case RadioPart:
748             paintRadio(states, context, zoomedRect, zoomFactor, scrollView);
749             break;
750         case PushButtonPart:
751         case DefaultButtonPart:
752         case ButtonPart:
753         case SquareButtonPart:
754             paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
755             break;
756         case InnerSpinButtonPart:
757             paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
758             break;
759         default:
760             break;
761     }
762 }
763
764 }