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