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