• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    context->save();
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    context->restore();
283
284    END_BLOCK_OBJC_EXCEPTIONS
285}
286
287// Radio Buttons
288
289static const IntSize* radioSizes()
290{
291    static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
292    return sizes;
293}
294
295static const int* radioMargins(NSControlSize controlSize)
296{
297    static const int margins[3][4] =
298    {
299        { 2, 2, 4, 2 },
300        { 3, 2, 3, 2 },
301        { 1, 0, 2, 0 },
302    };
303    return margins[controlSize];
304}
305
306static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
307{
308    // If the width and height are both specified, then we have nothing to do.
309    if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
310        return zoomedSize;
311
312    // Use the font size to determine the intrinsic width of the control.
313    return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
314}
315
316static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
317{
318    static NSButtonCell *radioCell;
319    if (!radioCell) {
320        radioCell = [[NSButtonCell alloc] init];
321        [radioCell setButtonType:NSRadioButton];
322        [radioCell setTitle:nil];
323        [radioCell setFocusRingType:NSFocusRingTypeExterior];
324    }
325
326    // Set the control size based off the rectangle we're painting into.
327    setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor);
328
329    // Update the various states we respond to.
330    updateStates(radioCell, states);
331
332    return radioCell;
333}
334
335static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
336{
337    // Determine the width and height needed for the control and prepare the cell for painting.
338    NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor);
339    LocalCurrentGraphicsContext localContext(context);
340
341    context->save();
342
343    NSControlSize controlSize = [radioCell controlSize];
344    IntSize zoomedSize = radioSizes()[controlSize];
345    zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
346    zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
347    IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
348
349    if (zoomFactor != 1.0f) {
350        inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
351        inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
352        context->translate(inflatedRect.x(), inflatedRect.y());
353        context->scale(FloatSize(zoomFactor, zoomFactor));
354        context->translate(-inflatedRect.x(), -inflatedRect.y());
355    }
356
357    BEGIN_BLOCK_OBJC_EXCEPTIONS
358    [radioCell drawWithFrame:NSRect(inflatedRect) inView:ThemeMac::ensuredView(scrollView)];
359    [radioCell setControlView:nil];
360    END_BLOCK_OBJC_EXCEPTIONS
361
362    context->restore();
363}
364
365// Buttons
366
367// Buttons really only constrain height. They respect width.
368static const IntSize* buttonSizes()
369{
370    static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
371    return sizes;
372}
373
374#if ENABLE(DATALIST)
375static const IntSize* listButtonSizes()
376{
377    static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) };
378    return sizes;
379}
380#endif
381
382static const int* buttonMargins(NSControlSize controlSize)
383{
384    static const int margins[3][4] =
385    {
386        { 4, 6, 7, 6 },
387        { 4, 5, 6, 5 },
388        { 0, 1, 1, 1 },
389    };
390    return margins[controlSize];
391}
392
393enum ButtonCellType { NormalButtonCell, DefaultButtonCell };
394
395static NSButtonCell *leakButtonCell(ButtonCellType type)
396{
397    NSButtonCell *cell = [[NSButtonCell alloc] init];
398    [cell setTitle:nil];
399    [cell setButtonType:NSMomentaryPushInButton];
400    if (type == DefaultButtonCell)
401        [cell setKeyEquivalent:@"\r"];
402    return cell;
403}
404
405static void setUpButtonCell(NSButtonCell *cell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
406{
407    // Set the control size based off the rectangle we're painting into.
408    const IntSize* sizes = buttonSizes();
409#if ENABLE(DATALIST)
410    if (part == ListButtonPart) {
411        [cell setBezelStyle:NSRoundedDisclosureBezelStyle];
412        sizes = listButtonSizes();
413    } else
414#endif
415    if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
416        // Use the square button
417        if ([cell bezelStyle] != NSShadowlessSquareBezelStyle)
418            [cell setBezelStyle:NSShadowlessSquareBezelStyle];
419    } else if ([cell bezelStyle] != NSRoundedBezelStyle)
420        [cell setBezelStyle:NSRoundedBezelStyle];
421
422    setControlSize(cell, sizes, zoomedRect.size(), zoomFactor);
423
424    // Update the various states we respond to.
425    updateStates(cell, states);
426}
427
428static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
429{
430    NSButtonCell *cell;
431    if (states & DefaultState) {
432        static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell);
433        cell = defaultCell;
434    } else {
435        static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell);
436        cell = normalCell;
437    }
438    setUpButtonCell(cell, part, states, zoomedRect, zoomFactor);
439    return cell;
440}
441
442static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
443{
444    BEGIN_BLOCK_OBJC_EXCEPTIONS
445
446    // Determine the width and height needed for the control and prepare the cell for painting.
447    NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor);
448    LocalCurrentGraphicsContext localContext(context);
449
450    NSControlSize controlSize = [buttonCell controlSize];
451#if ENABLE(DATALIST)
452    IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize];
453#else
454    IntSize zoomedSize = buttonSizes()[controlSize];
455#endif
456    zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
457    zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
458    IntRect inflatedRect = zoomedRect;
459    if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
460        // Center the button within the available space.
461        if (inflatedRect.height() > zoomedSize.height()) {
462            inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
463            inflatedRect.setHeight(zoomedSize.height());
464        }
465
466        // Now inflate it to account for the shadow.
467        inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
468
469        if (zoomFactor != 1.0f) {
470            inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
471            inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
472            context->translate(inflatedRect.x(), inflatedRect.y());
473            context->scale(FloatSize(zoomFactor, zoomFactor));
474            context->translate(-inflatedRect.x(), -inflatedRect.y());
475        }
476    }
477
478    NSView *view = ThemeMac::ensuredView(scrollView);
479    NSWindow *window = [view window];
480    NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell];
481
482    if (states & DefaultState) {
483        [window setDefaultButtonCell:buttonCell];
484        wkAdvanceDefaultButtonPulseAnimation(buttonCell);
485    } else if ([previousDefaultButtonCell isEqual:buttonCell])
486        [window setDefaultButtonCell:nil];
487
488    [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view];
489    [buttonCell setControlView:nil];
490
491    if (![previousDefaultButtonCell isEqual:buttonCell])
492        [window setDefaultButtonCell:previousDefaultButtonCell];
493
494    END_BLOCK_OBJC_EXCEPTIONS
495}
496
497// Stepper
498
499static const IntSize* stepperSizes()
500{
501    static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) };
502    return sizes;
503}
504
505// We don't use controlSizeForFont() for steppers because the stepper height
506// should be equal to or less than the corresponding text field height,
507static NSControlSize stepperControlSizeForFont(const Font& font)
508{
509    int fontSize = font.pixelSize();
510    if (fontSize >= 18)
511        return NSRegularControlSize;
512    if (fontSize >= 13)
513        return NSSmallControlSize;
514    return NSMiniControlSize;
515}
516
517static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*)
518{
519    // We don't use NSStepperCell because there are no ways to draw an
520    // NSStepperCell with the up button highlighted.
521
522    HIThemeButtonDrawInfo drawInfo;
523    drawInfo.version = 0;
524    drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
525    drawInfo.adornment = kThemeAdornmentDefault;
526    ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
527    if (controlSize == NSSmallControlSize)
528        drawInfo.kind = kThemeIncDecButtonSmall;
529    else if (controlSize == NSMiniControlSize)
530        drawInfo.kind = kThemeIncDecButtonMini;
531    else
532        drawInfo.kind = kThemeIncDecButton;
533
534    IntRect rect(zoomedRect);
535    context->save();
536    if (zoomFactor != 1.0f) {
537        rect.setWidth(rect.width() / zoomFactor);
538        rect.setHeight(rect.height() / zoomFactor);
539        context->translate(rect.x(), rect.y());
540        context->scale(FloatSize(zoomFactor, zoomFactor));
541        context->translate(-rect.x(), -rect.y());
542    }
543    CGRect bounds(rect);
544    // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'.
545    CGRect backgroundBounds;
546    HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
547    if (bounds.origin.x != backgroundBounds.origin.x)
548        bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x;
549    if (bounds.origin.y != backgroundBounds.origin.y)
550        bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y;
551    HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0);
552    context->restore();
553}
554
555// This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView.
556// 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.
557NSView *ThemeMac::ensuredView(ScrollView* scrollView)
558{
559    if (NSView *documentView = scrollView->documentView())
560        return documentView;
561
562    // Use a fake flipped view.
563    static NSView *flippedView = [[WebCoreFlippedView alloc] init];
564
565    return flippedView;
566}
567
568// Theme overrides
569
570int ThemeMac::baselinePositionAdjustment(ControlPart part) const
571{
572    if (part == CheckboxPart || part == RadioPart)
573        return -2;
574    return Theme::baselinePositionAdjustment(part);
575}
576
577FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
578{
579    switch (part) {
580        case PushButtonPart: {
581            FontDescription fontDescription;
582            fontDescription.setIsAbsoluteSize(true);
583            fontDescription.setGenericFamily(FontDescription::SerifFamily);
584
585            NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
586            fontDescription.firstFamily().setFamily([nsFont familyName]);
587            fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
588            fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
589            return fontDescription;
590        }
591        default:
592            return Theme::controlFont(part, font, zoomFactor);
593    }
594}
595
596LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
597{
598    switch (part) {
599        case CheckboxPart:
600            return checkboxSize(font, zoomedSize, zoomFactor);
601        case RadioPart:
602            return radioSize(font, zoomedSize, zoomFactor);
603        case PushButtonPart:
604            // Height is reset to auto so that specified heights can be ignored.
605            return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
606#if ENABLE(DATALIST)
607        case ListButtonPart:
608            return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes());
609#endif
610        case InnerSpinButtonPart:
611            // We don't use inner spin buttons on Mac.
612            return LengthSize(Length(Fixed), Length(Fixed));
613        case OuterSpinButtonPart:
614            if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
615                return zoomedSize;
616            return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
617        default:
618            return zoomedSize;
619    }
620}
621
622LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
623{
624    switch (part) {
625        case SquareButtonPart:
626        case DefaultButtonPart:
627        case ButtonPart:
628        case ListButtonPart:
629            return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
630        case InnerSpinButtonPart:
631            // We don't use inner spin buttons on Mac.
632            return LengthSize(Length(Fixed), Length(Fixed));
633        case OuterSpinButtonPart: {
634            IntSize base = stepperSizes()[NSMiniControlSize];
635            return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
636                              Length(static_cast<int>(base.height() * zoomFactor), Fixed));
637        }
638        default:
639            return Theme::minimumControlSize(part, font, zoomFactor);
640    }
641}
642
643LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
644{
645    switch (part) {
646        case SquareButtonPart:
647        case DefaultButtonPart:
648        case ButtonPart:
649        case ListButtonPart:
650            return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
651        default:
652            return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
653    }
654}
655
656LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
657{
658    switch (part) {
659        case PushButtonPart: {
660            // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
661            // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
662            // by definition constrained, since we select mini only for small cramped environments.
663            // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent
664            // padding.
665            const int padding = 8 * zoomFactor;
666            return LengthBox(0, padding, 0, padding);
667        }
668        default:
669            return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
670    }
671}
672
673void ThemeMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const
674{
675    BEGIN_BLOCK_OBJC_EXCEPTIONS
676    switch (part) {
677        case CheckboxPart: {
678            // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
679            // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
680            NSCell *cell = checkbox(states, zoomedRect, zoomFactor);
681            NSControlSize controlSize = [cell controlSize];
682            IntSize zoomedSize = checkboxSizes()[controlSize];
683            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
684            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
685            zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
686            break;
687        }
688        case RadioPart: {
689            // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
690            // shadow".  We don't consider this part of the bounds of the control in WebKit.
691            NSCell *cell = radio(states, zoomedRect, zoomFactor);
692            NSControlSize controlSize = [cell controlSize];
693            IntSize zoomedSize = radioSizes()[controlSize];
694            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
695            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
696            zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
697            break;
698        }
699        case PushButtonPart:
700        case DefaultButtonPart:
701        case ButtonPart: {
702            NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor);
703            NSControlSize controlSize = [cell controlSize];
704
705            // We inflate the rect as needed to account for the Aqua button's shadow.
706            if ([cell bezelStyle] == NSRoundedBezelStyle) {
707                IntSize zoomedSize = buttonSizes()[controlSize];
708                zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
709                zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
710                zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
711            }
712            break;
713        }
714        case OuterSpinButtonPart: {
715            static const int stepperMargin[4] = { 0, 0, 0, 0 };
716            ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
717            IntSize zoomedSize = stepperSizes()[controlSize];
718            zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
719            zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
720            zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
721            break;
722        }
723        default:
724            break;
725    }
726    END_BLOCK_OBJC_EXCEPTIONS
727}
728
729void ThemeMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const
730{
731    switch (part) {
732        case CheckboxPart:
733            paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView);
734            break;
735        case RadioPart:
736            paintRadio(states, context, zoomedRect, zoomFactor, scrollView);
737            break;
738        case PushButtonPart:
739        case DefaultButtonPart:
740        case ButtonPart:
741        case SquareButtonPart:
742        case ListButtonPart:
743            paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
744            break;
745        case OuterSpinButtonPart:
746            paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
747            break;
748        default:
749            break;
750    }
751}
752
753}
754