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