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