[Mac] Radio buttons and checkboxes vanish when redrawn
[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 "WebCoreSystemInterface.h"
34 #import <Carbon/Carbon.h>
35 #include <wtf/StdLibExtras.h>
36
37 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
38 @interface NSButtonCell(Details)
39 - (void)_setState:(NSInteger)value animated:(BOOL)animated;
40 - (void)_setHighlighted:(BOOL)flag animated:(BOOL)animated;
41 - (void)_renderCurrentAnimationFrameInContext:(CGContextRef)ctxt atLocation:(NSPoint)where;
42 - (BOOL)_stateAnimationRunning;
43 @end
44 #endif
45
46 static NSRect focusRingClipRect;
47 static BOOL themeWindowHasKeyAppearance;
48
49 @interface WebCoreThemeWindow : NSWindow {
50 }
51
52 @end
53
54 @implementation WebCoreThemeWindow
55
56 - (BOOL)hasKeyAppearance
57 {
58     return themeWindowHasKeyAppearance;
59 }
60
61 @end
62
63 @interface WebCoreThemeView : NSControl
64 @end
65
66 @implementation WebCoreThemeView
67
68 - (NSWindow *)window
69 {
70     static WebCoreThemeWindow *window = [[WebCoreThemeWindow alloc] init];
71
72     return window;
73 }
74
75 - (BOOL)isFlipped
76 {
77     return YES;
78 }
79
80 - (NSText *)currentEditor
81 {
82     return nil;
83 }
84
85 - (BOOL)_automaticFocusRingDisabled
86 {
87     return YES;
88 }
89
90 - (NSRect)_focusRingVisibleRect
91 {
92     if (NSIsEmptyRect(focusRingClipRect))
93         return [self visibleRect];
94
95     NSRect rect = focusRingClipRect;
96 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090
97     rect.origin.y = [self bounds].size.height - NSMaxY(rect);
98 #endif
99
100     return rect;
101 }
102
103 - (NSView *)_focusRingClipAncestor
104 {
105     return self;
106 }
107
108 - (void)addSubview:(NSView*)subview
109 {
110     // By doing nothing in this method we forbid controls from adding subviews.
111     // This tells AppKit to not use Layer-backed animation for control rendering.
112     UNUSED_PARAM(subview);
113 }
114 @end
115
116 @implementation NSFont (WebCoreTheme)
117
118 - (NSString*)webCoreFamilyName
119 {
120     if ([[self familyName] hasPrefix:@"."])
121         return [self fontName];
122
123     return [self familyName];
124 }
125
126 @end
127
128 // FIXME: Default buttons really should be more like push buttons and not like buttons.
129
130 namespace WebCore {
131
132 enum {
133     topMargin,
134     rightMargin,
135     bottomMargin,
136     leftMargin
137 };
138
139 Theme* platformTheme()
140 {
141     DEPRECATED_DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ());
142     return &themeMac;
143 }
144
145 // Helper functions used by a bunch of different control parts.
146
147 static NSControlSize controlSizeForFont(const Font& font)
148 {
149     int fontSize = font.pixelSize();
150     if (fontSize >= 16)
151         return NSRegularControlSize;
152     if (fontSize >= 11)
153         return NSSmallControlSize;
154     return NSMiniControlSize;
155 }
156
157 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
158 {
159     IntSize controlSize = sizes[nsControlSize];
160     if (zoomFactor != 1.0f)
161         controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
162     LengthSize result = zoomedSize;
163     if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
164         result.setWidth(Length(controlSize.width(), Fixed));
165     if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
166         result.setHeight(Length(controlSize.height(), Fixed));
167     return result;
168 }
169
170 static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const std::array<IntSize, 3>& sizes)
171 {
172     return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
173 }
174
175 static ControlSize controlSizeFromPixelSize(const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
176 {
177     if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
178         minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
179         return NSRegularControlSize;
180     if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
181         minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
182         return NSSmallControlSize;
183     return NSMiniControlSize;
184 }
185
186 static void setControlSize(NSCell* cell, const std::array<IntSize, 3>& sizes, const IntSize& minZoomedSize, float zoomFactor)
187 {
188     ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
189     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
190         [cell setControlSize:(NSControlSize)size];
191 }
192
193 static void updateStates(NSCell* cell, const ControlStates* controlStates, bool useAnimation = false)
194 {
195 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 10100
196     UNUSED_PARAM(useAnimation);
197 #endif
198     ControlStates::States states = controlStates->states();
199
200     // Hover state is not supported by Aqua.
201     
202     // Pressed state
203     bool oldPressed = [cell isHighlighted];
204     bool pressed = states & ControlStates::PressedState;
205     if (pressed != oldPressed) {
206 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
207         [(NSButtonCell*)cell _setHighlighted:pressed animated:useAnimation];
208 #else
209         [cell setHighlighted:pressed];
210 #endif
211     }
212     
213     // Enabled state
214     bool oldEnabled = [cell isEnabled];
215     bool enabled = states & ControlStates::EnabledState;
216     if (enabled != oldEnabled)
217         [cell setEnabled:enabled];
218
219     // Checked and Indeterminate
220     bool oldIndeterminate = [cell state] == NSMixedState;
221     bool indeterminate = (states & ControlStates::IndeterminateState);
222     bool checked = states & ControlStates::CheckedState;
223     bool oldChecked = [cell state] == NSOnState;
224     if (oldIndeterminate != indeterminate || checked != oldChecked) {
225         NSCellStateValue newState = indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState);
226 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
227         [(NSButtonCell*)cell _setState:newState animated:useAnimation];
228 #else
229         [cell setState:newState];
230 #endif
231     }
232
233     // Window inactive state does not need to be checked explicitly, since we paint parented to 
234     // a view in a window whose key state can be detected.
235 }
236
237 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, const ControlStates* controlStates)
238 {
239     ControlStates::States states = controlStates->states();
240
241     if (states & ControlStates::ReadOnlyState)
242         return kThemeStateUnavailableInactive;
243     if (!(states & ControlStates::EnabledState))
244         return kThemeStateUnavailableInactive;
245
246     // Do not process PressedState if !EnabledState or ReadOnlyState.
247     if (states & ControlStates::PressedState) {
248         if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
249             return states & ControlStates::SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
250         return kThemeStatePressed;
251     }
252     return kThemeStateActive;
253 }
254
255 static FloatRect inflateRect(const FloatRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
256 {
257     // Only do the inflation if the available width/height are too small.  Otherwise try to
258     // fit the glow/check space into the available box's width/height.
259     int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
260     int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
261     FloatRect result(zoomedRect);
262     if (widthDelta < 0) {
263         result.setX(result.x() - margins[leftMargin] * zoomFactor);
264         result.setWidth(result.width() - widthDelta);
265     }
266     if (heightDelta < 0) {
267         result.setY(result.y() - margins[topMargin] * zoomFactor);
268         result.setHeight(result.height() - heightDelta);
269     }
270     return result;
271 }
272
273 // Checkboxes and radio buttons
274
275 static const std::array<IntSize, 3>& checkboxSizes()
276 {
277     static const std::array<IntSize, 3> sizes = { { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) } };
278     return sizes;
279 }
280
281 static const int* checkboxMargins(NSControlSize controlSize)
282 {
283     static const int margins[3][4] =
284     {
285         // top right bottom left
286         { 2, 2, 2, 2 },
287         { 2, 1, 2, 1 },
288         { 0, 0, 1, 0 },
289     };
290     return margins[controlSize];
291 }
292
293 static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
294 {
295     // If the width and height are both specified, then we have nothing to do.
296     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
297         return zoomedSize;
298
299     // Use the font size to determine the intrinsic width of the control.
300     return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
301 }
302
303 // Radio Buttons
304
305 static const std::array<IntSize, 3>& radioSizes()
306 {
307     static const std::array<IntSize, 3> sizes = { { IntSize(16, 16), IntSize(12, 12), IntSize(10, 10) } };
308     return sizes;
309 }
310
311 static const int* radioMargins(NSControlSize controlSize)
312 {
313     static const int margins[3][4] =
314     {
315         // top right bottom left
316         { 1, 0, 1, 2 },
317         { 1, 1, 2, 1 },
318         { 0, 0, 1, 1 },
319     };
320     return margins[controlSize];
321 }
322
323 static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
324 {
325     // If the width and height are both specified, then we have nothing to do.
326     if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
327         return zoomedSize;
328
329     // Use the font size to determine the intrinsic width of the control.
330     return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
331 }
332     
333 static void configureToggleButton(NSCell* cell, ControlPart buttonType, const ControlStates* states, const IntSize& zoomedSize, float zoomFactor, bool isStateChange)
334 {
335     // Set the control size based off the rectangle we're painting into.
336     setControlSize(cell, buttonType == CheckboxPart ? checkboxSizes() : radioSizes(), zoomedSize, zoomFactor);
337
338     // Update the various states we respond to.
339     updateStates(cell, states, isStateChange);
340 }
341     
342 static NSButtonCell *createToggleButtonCell(ControlPart buttonType)
343 {
344     NSButtonCell *toggleButtonCell = [[NSButtonCell alloc] init];
345     
346     if (buttonType == CheckboxPart) {
347         [toggleButtonCell setButtonType:NSSwitchButton];
348         [toggleButtonCell setAllowsMixedState:YES];
349     } else {
350         ASSERT(buttonType == RadioPart);
351         [toggleButtonCell setButtonType:NSRadioButton];
352     }
353     
354     [toggleButtonCell setTitle:nil];
355     [toggleButtonCell setFocusRingType:NSFocusRingTypeExterior];
356     return toggleButtonCell;
357 }
358     
359 static NSButtonCell *sharedRadioCell(const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
360 {
361     static NSButtonCell *radioCell;
362     if (!radioCell)
363         radioCell = createToggleButtonCell(RadioPart);
364
365     configureToggleButton(radioCell, RadioPart, states, zoomedSize, zoomFactor, false);
366     return radioCell;
367 }
368     
369 static NSButtonCell *sharedCheckboxCell(const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
370 {
371     static NSButtonCell *checkboxCell;
372     if (!checkboxCell)
373         checkboxCell = createToggleButtonCell(CheckboxPart);
374
375     configureToggleButton(checkboxCell, CheckboxPart, states, zoomedSize, zoomFactor, false);
376     return checkboxCell;
377 }
378
379 static bool drawCellFocusRing(NSCell *cell, NSRect cellFrame, NSView *controlView)
380 {
381     wkDrawCellFocusRingWithFrameAtTime(cell, cellFrame, controlView, std::numeric_limits<double>::max());
382     return false;
383 }
384
385 static void paintToggleButton(ControlPart buttonType, ControlStates* controlStates, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
386 {
387     BEGIN_BLOCK_OBJC_EXCEPTIONS
388
389     NSButtonCell *toggleButtonCell = static_cast<NSButtonCell*>(controlStates->platformControl());
390     IntSize zoomedRectSize = IntSize(zoomedRect.size());
391
392     if (controlStates->isDirty()) {
393         if (!toggleButtonCell)
394             toggleButtonCell = createToggleButtonCell(buttonType);
395         configureToggleButton(toggleButtonCell, buttonType, controlStates, zoomedRectSize, zoomFactor, true);
396     } else {
397         if (!toggleButtonCell) {
398             if (buttonType == CheckboxPart)
399                 toggleButtonCell = sharedCheckboxCell(controlStates, zoomedRectSize, zoomFactor);
400             else {
401                 ASSERT(buttonType == RadioPart);
402                 toggleButtonCell = sharedRadioCell(controlStates, zoomedRectSize, zoomFactor);
403             }
404         }
405         configureToggleButton(toggleButtonCell, buttonType, controlStates, zoomedRectSize, zoomFactor, false);
406     }
407     controlStates->setDirty(false);
408
409     GraphicsContextStateSaver stateSaver(*context);
410
411     NSControlSize controlSize = [toggleButtonCell controlSize];
412     IntSize zoomedSize = buttonType == CheckboxPart ? checkboxSizes()[controlSize] : radioSizes()[controlSize];
413     zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
414     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
415     const int* controlMargins = buttonType == CheckboxPart ? checkboxMargins(controlSize) : radioMargins(controlSize);
416     FloatRect inflatedRect = inflateRect(zoomedRect, zoomedSize, controlMargins, zoomFactor);
417
418     if (zoomFactor != 1.0f) {
419         inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
420         inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
421         context->translate(inflatedRect.x(), inflatedRect.y());
422         context->scale(FloatSize(zoomFactor, zoomFactor));
423         context->translate(-inflatedRect.x(), -inflatedRect.y());
424     }
425
426     LocalCurrentGraphicsContext localContext(context);
427     NSView *view = ThemeMac::ensuredView(scrollView, controlStates);
428
429     bool isAnimating = false;
430     bool needsRepaint = false;
431
432 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
433     if ([toggleButtonCell _stateAnimationRunning]) {
434         // AppKit's drawWithFrame appears to render the cell centered in the
435         // provided rectangle/frame, so we need to manually position the
436         // animated cell at the correct location.
437         context->translate(inflatedRect.x(), inflatedRect.y());
438         context->scale(FloatSize(1, -1));
439         context->translate(0, -inflatedRect.height());
440         [toggleButtonCell _renderCurrentAnimationFrameInContext:context->platformContext() atLocation:NSMakePoint(0, 0)];
441         isAnimating = [toggleButtonCell _stateAnimationRunning];
442     } else
443         [toggleButtonCell drawWithFrame:NSRect(inflatedRect) inView:view];
444 #else
445     [toggleButtonCell drawWithFrame:NSRect(inflatedRect) inView:view];
446 #endif
447
448     if (!isAnimating && (controlStates->states() & ControlStates::FocusState))
449         needsRepaint = drawCellFocusRing(toggleButtonCell, inflatedRect, view);
450     [toggleButtonCell setControlView:nil];
451
452 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 10100
453     needsRepaint |= [toggleButtonCell _stateAnimationRunning];
454 #endif
455     controlStates->setNeedsRepaint(needsRepaint);
456     if (needsRepaint)
457         controlStates->setPlatformControl(toggleButtonCell);
458
459     END_BLOCK_OBJC_EXCEPTIONS
460 }
461
462 // Buttons
463
464 // Buttons really only constrain height. They respect width.
465 static const std::array<IntSize, 3>& buttonSizes()
466 {
467     static const std::array<IntSize, 3> sizes = { { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) } };
468     return sizes;
469 }
470
471 static const int* buttonMargins(NSControlSize controlSize)
472 {
473     static const int margins[3][4] =
474     {
475         { 4, 6, 7, 6 },
476         { 4, 5, 6, 5 },
477         { 0, 1, 1, 1 },
478     };
479     return margins[controlSize];
480 }
481
482 enum ButtonCellType { NormalButtonCell, DefaultButtonCell };
483
484 static NSButtonCell *leakButtonCell(ButtonCellType type)
485 {
486     NSButtonCell *cell = [[NSButtonCell alloc] init];
487     [cell setTitle:nil];
488     [cell setButtonType:NSMomentaryPushInButton];
489     if (type == DefaultButtonCell)
490         [cell setKeyEquivalent:@"\r"];
491     return cell;
492 }
493
494 static void setUpButtonCell(NSButtonCell *cell, ControlPart part, const ControlStates* states, const IntSize& zoomedSize, float zoomFactor)
495 {
496     // Set the control size based off the rectangle we're painting into.
497     const std::array<IntSize, 3>& sizes = buttonSizes();
498     if (part == SquareButtonPart || zoomedSize.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
499         // Use the square button
500         if ([cell bezelStyle] != NSShadowlessSquareBezelStyle)
501             [cell setBezelStyle:NSShadowlessSquareBezelStyle];
502     } else if ([cell bezelStyle] != NSRoundedBezelStyle)
503         [cell setBezelStyle:NSRoundedBezelStyle];
504
505     setControlSize(cell, sizes, zoomedSize, zoomFactor);
506
507     // Update the various states we respond to.
508     updateStates(cell, states);
509 }
510
511 static NSButtonCell *button(ControlPart part, const ControlStates* controlStates, const IntSize& zoomedSize, float zoomFactor)
512 {
513     ControlStates::States states = controlStates->states();
514     NSButtonCell *cell;
515     if (states & ControlStates::DefaultState) {
516         static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell);
517         cell = defaultCell;
518     } else {
519         static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell);
520         cell = normalCell;
521     }
522     setUpButtonCell(cell, part, controlStates, zoomedSize, zoomFactor);
523     return cell;
524 }
525
526 static void paintButton(ControlPart part, ControlStates* controlStates, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
527 {
528     BEGIN_BLOCK_OBJC_EXCEPTIONS
529     
530     // Determine the width and height needed for the control and prepare the cell for painting.
531     ControlStates::States states = controlStates->states();
532     NSButtonCell *buttonCell = button(part, controlStates, IntSize(zoomedRect.size()), zoomFactor);
533     GraphicsContextStateSaver stateSaver(*context);
534
535     NSControlSize controlSize = [buttonCell controlSize];
536     IntSize zoomedSize = buttonSizes()[controlSize];
537     zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
538     zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
539     FloatRect inflatedRect = zoomedRect;
540     if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
541         // Center the button within the available space.
542         if (inflatedRect.height() > zoomedSize.height()) {
543             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
544             inflatedRect.setHeight(zoomedSize.height());
545         }
546
547         // Now inflate it to account for the shadow.
548         inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
549
550         if (zoomFactor != 1.0f) {
551             inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
552             inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
553             context->translate(inflatedRect.x(), inflatedRect.y());
554             context->scale(FloatSize(zoomFactor, zoomFactor));
555             context->translate(-inflatedRect.x(), -inflatedRect.y());
556         }
557     } 
558
559     LocalCurrentGraphicsContext localContext(context);
560     NSView *view = ThemeMac::ensuredView(scrollView, controlStates);
561     NSWindow *window = [view window];
562     NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell];
563
564     if (states & ControlStates::DefaultState) {
565         [window setDefaultButtonCell:buttonCell];
566         wkAdvanceDefaultButtonPulseAnimation(buttonCell);
567     } else if ([previousDefaultButtonCell isEqual:buttonCell])
568         [window setDefaultButtonCell:nil];
569
570     [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view];
571     bool needsRepaint = false;
572     if (states & ControlStates::FocusState)
573         needsRepaint = drawCellFocusRing(buttonCell, inflatedRect, view);
574
575     controlStates->setNeedsRepaint(needsRepaint);
576
577     [buttonCell setControlView:nil];
578
579     if (![previousDefaultButtonCell isEqual:buttonCell])
580         [window setDefaultButtonCell:previousDefaultButtonCell];
581
582     END_BLOCK_OBJC_EXCEPTIONS
583 }
584
585 // Stepper
586
587 static const std::array<IntSize, 3>& stepperSizes()
588 {
589     static const std::array<IntSize, 3> sizes = { { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) } };
590     return sizes;
591 }
592
593 // We don't use controlSizeForFont() for steppers because the stepper height
594 // should be equal to or less than the corresponding text field height,
595 static NSControlSize stepperControlSizeForFont(const Font& font)
596 {
597     int fontSize = font.pixelSize();
598     if (fontSize >= 18)
599         return NSRegularControlSize;
600     if (fontSize >= 13)
601         return NSSmallControlSize;
602     return NSMiniControlSize;
603 }
604
605 static void paintStepper(ControlStates* states, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView*)
606 {
607     // We don't use NSStepperCell because there are no ways to draw an
608     // NSStepperCell with the up button highlighted.
609
610     HIThemeButtonDrawInfo drawInfo;
611     drawInfo.version = 0;
612     drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
613     drawInfo.adornment = kThemeAdornmentDefault;
614     ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), IntSize(zoomedRect.size()), zoomFactor);
615     if (controlSize == NSSmallControlSize)
616         drawInfo.kind = kThemeIncDecButtonSmall;
617     else if (controlSize == NSMiniControlSize)
618         drawInfo.kind = kThemeIncDecButtonMini;
619     else
620         drawInfo.kind = kThemeIncDecButton;
621
622     IntRect rect(zoomedRect);
623     GraphicsContextStateSaver stateSaver(*context);
624     if (zoomFactor != 1.0f) {
625         rect.setWidth(rect.width() / zoomFactor);
626         rect.setHeight(rect.height() / zoomFactor);
627         context->translate(rect.x(), rect.y());
628         context->scale(FloatSize(zoomFactor, zoomFactor));
629         context->translate(-rect.x(), -rect.y());
630     }
631     CGRect bounds(rect);
632     CGRect backgroundBounds;
633     HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
634     // Center the stepper rectangle in the specified area.
635     backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2;
636     if (backgroundBounds.size.height < bounds.size.height) {
637         int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height);
638         backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1;
639     }
640
641     LocalCurrentGraphicsContext localContext(context);
642     HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0);
643 }
644
645 // This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView.
646 // If the ScrollView doesn't have an NSView, we will return a fake NSView set up in the way AppKit expects.
647 NSView *ThemeMac::ensuredView(ScrollView* scrollView, const ControlStates* controlStates)
648 {
649     if (NSView *documentView = scrollView->documentView())
650         return documentView;
651
652     // Use a fake view.
653     static WebCoreThemeView *themeView = [[WebCoreThemeView alloc] init];
654     [themeView setFrameSize:NSSizeFromCGSize(scrollView->totalContentsSize())];
655
656     themeWindowHasKeyAppearance = !(controlStates->states() & ControlStates::WindowInactiveState);
657
658     return themeView;
659 }
660
661 void ThemeMac::setFocusRingClipRect(const FloatRect& rect)
662 {
663     focusRingClipRect = rect;
664 }
665
666 // Theme overrides
667
668 int ThemeMac::baselinePositionAdjustment(ControlPart part) const
669 {
670     if (part == CheckboxPart || part == RadioPart)
671         return -2;
672     return Theme::baselinePositionAdjustment(part);
673 }
674
675 FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
676 {
677     switch (part) {
678         case PushButtonPart: {
679             FontDescription fontDescription;
680             fontDescription.setIsAbsoluteSize(true);
681             fontDescription.setGenericFamily(FontDescription::SerifFamily);
682
683             NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
684             fontDescription.setOneFamily([nsFont webCoreFamilyName]);
685             fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
686             fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
687             return fontDescription;
688         }
689         default:
690             return Theme::controlFont(part, font, zoomFactor);
691     }
692 }
693
694 LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
695 {
696     switch (part) {
697         case CheckboxPart:
698             return checkboxSize(font, zoomedSize, zoomFactor);
699         case RadioPart:
700             return radioSize(font, zoomedSize, zoomFactor);
701         case PushButtonPart:
702             // Height is reset to auto so that specified heights can be ignored.
703             return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
704         case InnerSpinButtonPart:
705             if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
706                 return zoomedSize;
707             return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
708         default:
709             return zoomedSize;
710     }
711 }
712
713 LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
714 {
715     switch (part) {
716         case SquareButtonPart:
717         case DefaultButtonPart:
718         case ButtonPart:
719             return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
720         case InnerSpinButtonPart:{
721             IntSize base = stepperSizes()[NSMiniControlSize];
722             return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
723                               Length(static_cast<int>(base.height() * zoomFactor), Fixed));
724         }
725         default:
726             return Theme::minimumControlSize(part, font, zoomFactor);
727     }
728 }
729
730 LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
731 {
732     switch (part) {
733         case SquareButtonPart:
734         case DefaultButtonPart:
735         case ButtonPart:
736             return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
737         default:
738             return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
739     }
740 }
741
742 LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
743 {
744     switch (part) {
745         case PushButtonPart: {
746             // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
747             // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
748             // by definition constrained, since we select mini only for small cramped environments.
749             // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
750             // padding.
751             const int padding = 8 * zoomFactor;
752             return LengthBox(2, padding, 3, padding);
753         }
754         default:
755             return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
756     }
757 }
758
759 void ThemeMac::inflateControlPaintRect(ControlPart part, const ControlStates* states, FloatRect& zoomedRect, float zoomFactor) const
760 {
761     BEGIN_BLOCK_OBJC_EXCEPTIONS
762     IntSize zoomRectSize = IntSize(zoomedRect.size());
763     switch (part) {
764         case CheckboxPart: {
765             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
766             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
767             NSCell *cell = sharedCheckboxCell(states, zoomRectSize, zoomFactor);
768             NSControlSize controlSize = [cell controlSize];
769             IntSize zoomedSize = checkboxSizes()[controlSize];
770             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
771             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
772             zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
773             break;
774         }
775         case RadioPart: {
776             // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
777             // shadow".  We don't consider this part of the bounds of the control in WebKit.
778             NSCell *cell = sharedRadioCell(states, zoomRectSize, zoomFactor);
779             NSControlSize controlSize = [cell controlSize];
780             IntSize zoomedSize = radioSizes()[controlSize];
781             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
782             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
783             zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
784             break;
785         }
786         case PushButtonPart:
787         case DefaultButtonPart:
788         case ButtonPart: {
789             NSButtonCell *cell = button(part, states, zoomRectSize, zoomFactor);
790             NSControlSize controlSize = [cell controlSize];
791
792             // We inflate the rect as needed to account for the Aqua button's shadow.
793             if ([cell bezelStyle] == NSRoundedBezelStyle) {
794                 IntSize zoomedSize = buttonSizes()[controlSize];
795                 zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
796                 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
797                 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
798             }
799             break;
800         }
801         case InnerSpinButtonPart: {
802             static const int stepperMargin[4] = { 0, 0, 0, 0 };
803             ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomRectSize, zoomFactor);
804             IntSize zoomedSize = stepperSizes()[controlSize];
805             zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
806             zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
807             zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
808             break;
809         }
810         default:
811             break;
812     }
813     END_BLOCK_OBJC_EXCEPTIONS
814 }
815
816 void ThemeMac::paint(ControlPart part, ControlStates* states, GraphicsContext* context, const FloatRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
817 {
818     switch (part) {
819         case CheckboxPart:
820             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView);
821             break;
822         case RadioPart:
823             paintToggleButton(part, states, context, zoomedRect, zoomFactor, scrollView);
824             break;
825         case PushButtonPart:
826         case DefaultButtonPart:
827         case ButtonPart:
828         case SquareButtonPart:
829             paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
830             break;
831         case InnerSpinButtonPart:
832             paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
833             break;
834         default:
835             break;
836     }
837 }
838
839 }