d0961454cbd11a3304b6a9dc2973440f313660ae
[WebKit-https.git] / WebCore / rendering / RenderThemeMac.mm
1 /*
2  * This file is part of the theme implementation for form controls in WebCore.
3  *
4  * Copyright (C) 2005, 2006 Apple Computer, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #import "config.h"
23 #import "RenderThemeMac.h"
24
25 #import "cssstyleselector.h"
26 #import "CSSValueKeywords.h"
27 #import "Document.h"
28 #import "FoundationExtras.h"
29 #import "FrameView.h"
30 #import "GraphicsContext.h"
31 #import "Image.h"
32 #import "LocalCurrentGraphicsContext.h"
33 #import "RenderView.h"
34 #import "WebCoreSystemInterface.h"
35
36 // The methods in this file are specific to the Mac OS X platform.
37
38 namespace WebCore {
39
40 enum {
41     topMargin,
42     rightMargin,
43     bottomMargin,
44     leftMargin
45 };
46
47 enum {
48     topPadding,
49     rightPadding,
50     bottomPadding,
51     leftPadding
52 };
53     
54 RenderTheme* theme()
55 {
56     static RenderThemeMac macTheme;
57     return &macTheme;
58 }
59
60 RenderThemeMac::RenderThemeMac()
61     : checkbox(nil)
62     , radio(nil)
63     , button(nil)
64     , resizeCornerImage(0)
65 {
66 }
67
68 Color RenderThemeMac::platformActiveSelectionBackgroundColor() const
69 {
70     NSColor* color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
71     return Color((int)(255 * [color redComponent]), (int)(255 * [color greenComponent]), (int)(255 * [color blueComponent]));
72 }
73
74 Color RenderThemeMac::platformInactiveSelectionBackgroundColor() const
75 {
76     NSColor* color = [[NSColor secondarySelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace];
77     return Color((int)(255 * [color redComponent]), (int)(255 * [color greenComponent]), (int)(255 * [color blueComponent]));
78 }
79
80 void RenderThemeMac::systemFont(int propId, FontDescription& fontDescription) const
81 {
82     static FontDescription systemFont;
83     static FontDescription smallSystemFont;
84     static FontDescription menuFont;
85     static FontDescription labelFont;
86     static FontDescription miniControlFont;
87     static FontDescription smallControlFont;
88     static FontDescription controlFont;
89     
90     FontDescription* cachedDesc;
91     NSFont* font = nil;
92     switch (propId) {
93         case CSS_VAL_SMALL_CAPTION:
94             cachedDesc = &smallSystemFont;
95             if (!smallSystemFont.isAbsoluteSize())
96                 font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
97             break;
98         case CSS_VAL_MENU:
99             cachedDesc = &menuFont;
100             if (!menuFont.isAbsoluteSize())
101                 font = [NSFont menuFontOfSize:[NSFont systemFontSize]];
102             break;
103         case CSS_VAL_STATUS_BAR:
104             cachedDesc = &labelFont;
105             if (!labelFont.isAbsoluteSize())
106                 font = [NSFont labelFontOfSize:[NSFont labelFontSize]];
107             break;
108         case CSS_VAL__WEBKIT_MINI_CONTROL:
109             cachedDesc = &miniControlFont;
110             if (!miniControlFont.isAbsoluteSize())
111                 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]];
112             break;
113         case CSS_VAL__WEBKIT_SMALL_CONTROL:
114             cachedDesc = &smallControlFont;
115             if (!smallControlFont.isAbsoluteSize())
116                 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
117             break;
118         case CSS_VAL__WEBKIT_CONTROL:
119             cachedDesc = &controlFont;
120             if (!controlFont.isAbsoluteSize())
121                 font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
122             break;
123         default:
124             cachedDesc = &systemFont;
125             if (!systemFont.isAbsoluteSize())
126                 font = [NSFont systemFontOfSize:[NSFont systemFontSize]];
127     }
128     
129     if (font) {
130         cachedDesc->setIsAbsoluteSize(true);
131         cachedDesc->setGenericFamily(FontDescription::NoFamily);
132         cachedDesc->firstFamily().setFamily([font familyName]);
133         cachedDesc->setSpecifiedSize([font pointSize]);
134         NSFontTraitMask traits = [[NSFontManager sharedFontManager] traitsOfFont:font];
135         cachedDesc->setBold(traits & NSBoldFontMask);
136         cachedDesc->setItalic(traits & NSItalicFontMask);
137     }
138     fontDescription = *cachedDesc;
139 }
140
141 bool RenderThemeMac::isControlStyled(const RenderStyle* style, const BorderData& border, 
142                                      const BackgroundLayer& background, const Color& backgroundColor) const
143 {
144     if (style->appearance() == TextFieldAppearance || style->appearance() == TextAreaAppearance || style->appearance() == ListboxAppearance)
145         return style->border() != border;
146     return RenderTheme::isControlStyled(style, border, background, backgroundColor);
147 }
148
149 void RenderThemeMac::paintResizeControl(GraphicsContext* c, const IntRect& r)
150 {
151     if (!resizeCornerImage)
152         resizeCornerImage = Image::loadPlatformResource("textAreaResizeCorner");
153
154     IntPoint imagePoint(r.right() - resizeCornerImage->width(), r.bottom() - resizeCornerImage->height());
155     c->drawImage(resizeCornerImage, imagePoint);
156 }
157
158 void RenderThemeMac::adjustRepaintRect(const RenderObject* o, IntRect& r)
159 {
160     switch (o->style()->appearance()) {
161         case CheckboxAppearance: {
162             // Since we query the prototype cell, we need to update its state to match.
163             setCheckboxCellState(o, r);
164         
165             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
166             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
167             r = inflateRect(r, checkboxSizes()[[checkbox controlSize]], checkboxMargins());
168             break;
169         }
170         case RadioAppearance: {
171             // Since we query the prototype cell, we need to update its state to match.
172             setRadioCellState(o, r);
173         
174             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
175             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
176             r = inflateRect(r, radioSizes()[[radio controlSize]], radioMargins());
177             break;
178         }
179         case PushButtonAppearance:
180         case ButtonAppearance: {
181             // Since we query the prototype cell, we need to update its state to match.
182             setButtonCellState(o, r);
183         
184             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
185             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
186             if ([button bezelStyle] == NSRoundedBezelStyle)
187                 r = inflateRect(r, buttonSizes()[[button controlSize]], buttonMargins());
188             break;
189         }
190         case MenulistAppearance: {
191             setPopupButtonCellState(o, r); 
192             r = inflateRect(r, popupButtonSizes()[[popupButton controlSize]], popupButtonMargins());
193             break;
194         }
195         default:
196             break;
197     }
198 }
199
200 IntRect RenderThemeMac::inflateRect(const IntRect& r, const IntSize& size, const int* margins) const
201 {
202     // Only do the inflation if the available width/height are too small.  Otherwise try to
203     // fit the glow/check space into the available box's width/height.
204     int widthDelta = r.width() - (size.width() + margins[leftMargin] + margins[rightMargin]);
205     int heightDelta = r.height() - (size.height() + margins[topMargin] + margins[bottomMargin]);
206     IntRect result(r);
207     if (widthDelta < 0) {
208         result.setX(result.x() - margins[leftMargin]);
209         result.setWidth(result.width() - widthDelta);
210     }
211     if (heightDelta < 0) {
212         result.setY(result.y() - margins[topMargin]);
213         result.setHeight(result.height() - heightDelta);
214     }
215     return result;
216 }
217
218 void RenderThemeMac::updateCheckedState(NSCell* cell, const RenderObject* o)
219 {
220     bool oldIndeterminate = [cell state] == NSMixedState;
221     bool indeterminate = isIndeterminate(o);
222     bool checked = isChecked(o);
223     
224     if (oldIndeterminate != indeterminate) {
225         [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];
226         return;
227     }
228
229     bool oldChecked = [cell state] == NSOnState;
230     if (checked != oldChecked)
231         [cell setState:checked ? NSOnState : NSOffState];
232 }
233
234 void RenderThemeMac::updateEnabledState(NSCell* cell, const RenderObject* o)
235 {
236     bool oldEnabled = [cell isEnabled];
237     bool enabled = isEnabled(o);
238     if (enabled != oldEnabled)
239         [cell setEnabled:enabled];
240 }
241
242 void RenderThemeMac::updateFocusedState(NSCell* cell, const RenderObject* o)
243 {
244     // FIXME: Need to add a key window test here, or the element will look
245     // focused even when in the background.
246     bool oldFocused = [cell showsFirstResponder];
247     bool focused = (o->element() && o->document()->focusNode() == o->element()) && (o->style()->outlineStyleIsAuto());
248     if (focused != oldFocused)
249         [cell setShowsFirstResponder:focused];
250 }
251
252 void RenderThemeMac::updatePressedState(NSCell* cell, const RenderObject* o)
253 {
254     bool oldPressed = [cell isHighlighted];
255     bool pressed = (o->element() && o->element()->active());
256     if (pressed != oldPressed)
257         [cell setHighlighted:pressed];
258 }
259
260 short RenderThemeMac::baselinePosition(const RenderObject* o) const
261 {
262     if (o->style()->appearance() == CheckboxAppearance || o->style()->appearance() == RadioAppearance)
263         return o->marginTop() + o->height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
264     return RenderTheme::baselinePosition(o);
265 }
266
267 bool RenderThemeMac::controlSupportsTints(const RenderObject* o) const
268 {
269     if (!isEnabled(o))
270         return false;
271     
272     // Checkboxes only have tint when checked.
273     if (o->style()->appearance() == CheckboxAppearance)
274         return isChecked(o);
275     
276     // For now assume other controls have tint if enabled.
277     return true;
278 }
279
280 NSControlSize RenderThemeMac::controlSizeForFont(RenderStyle* style) const
281 {
282     int fontSize = style->fontSize();
283     if (fontSize >= 16)
284         return NSRegularControlSize;
285     if (fontSize >= 11)
286         return NSSmallControlSize;
287     return NSMiniControlSize;
288 }
289
290 void RenderThemeMac::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize)
291 {
292     NSControlSize size;
293     if (minSize.width() >= sizes[NSRegularControlSize].width() &&
294         minSize.height() >= sizes[NSRegularControlSize].height())
295         size = NSRegularControlSize;
296     else if (minSize.width() >= sizes[NSSmallControlSize].width() &&
297              minSize.height() >= sizes[NSSmallControlSize].height())
298         size = NSSmallControlSize;
299     else
300         size = NSMiniControlSize;
301     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
302         [cell setControlSize:size];
303 }
304
305 IntSize RenderThemeMac::sizeForFont(RenderStyle* style, const IntSize* sizes) const
306 {
307     return sizes[controlSizeForFont(style)];
308 }
309
310 void RenderThemeMac::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const
311 {
312     // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
313     IntSize size = sizeForFont(style, sizes);
314     if (style->width().isIntrinsicOrAuto() && size.width() > 0)
315         style->setWidth(Length(size.width(), Fixed));
316     if (style->height().isAuto() && size.height() > 0)
317         style->setHeight(Length(size.height(), Fixed));
318 }
319
320 void RenderThemeMac::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const
321 {
322     FontDescription fontDescription;
323     fontDescription.setIsAbsoluteSize(true);
324     fontDescription.setGenericFamily(FontDescription::SerifFamily);
325     
326     NSFont* font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSize]];
327     fontDescription.firstFamily().setFamily([font familyName]);
328     fontDescription.setComputedSize([font pointSize]);
329     fontDescription.setSpecifiedSize([font pointSize]);
330
331     if (style->setFontDescription(fontDescription))
332         style->font().update();
333 }
334
335 bool RenderThemeMac::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
336 {
337     // Determine the width and height needed for the control and prepare the cell for painting.
338     setCheckboxCellState(o, r);
339     
340     // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
341     // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
342     IntRect inflatedRect = inflateRect(r, checkboxSizes()[[checkbox controlSize]], checkboxMargins());
343     [checkbox drawWithFrame:NSRect(inflatedRect) inView:o->view()->frameView()->getDocumentView()];
344     [checkbox setControlView: nil];
345     
346     return false;
347 }
348
349 const IntSize* RenderThemeMac::checkboxSizes() const
350 {
351     static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
352     return sizes;
353 }
354
355 const int* RenderThemeMac::checkboxMargins() const
356 {
357     static const int margins[3][4] = 
358     {
359         { 3, 4, 4, 2 },
360         { 4, 3, 3, 3 },
361         { 4, 3, 3, 3 },
362     };
363     return margins[[checkbox controlSize]];
364 }
365
366 void RenderThemeMac::setCheckboxCellState(const RenderObject* o, const IntRect& r)
367 {
368     if (!checkbox) {
369         checkbox = HardRetainWithNSRelease([[NSButtonCell alloc] init]);
370         [checkbox setButtonType:NSSwitchButton];
371         [checkbox setTitle:nil];
372         [checkbox setAllowsMixedState:YES];
373     }
374     
375     // Set the control size based off the rectangle we're painting into.
376     setControlSize(checkbox, checkboxSizes(), r.size());
377     
378     // Update the various states we respond to.
379     updateCheckedState(checkbox, o);
380     updateEnabledState(checkbox, o);
381     updatePressedState(checkbox, o);
382     updateFocusedState(checkbox, o);
383 }
384
385 void RenderThemeMac::setCheckboxSize(RenderStyle* style) const
386 {
387     // If the width and height are both specified, then we have nothing to do.
388     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
389         return;
390     
391     // Use the font size to determine the intrinsic width of the control.
392     setSizeFromFont(style, checkboxSizes());
393 }
394
395 bool RenderThemeMac::paintRadio(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
396 {
397     // Determine the width and height needed for the control and prepare the cell for painting.
398     setRadioCellState(o, r);
399     
400     // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
401     // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
402     IntRect inflatedRect = inflateRect(r, radioSizes()[[radio controlSize]], radioMargins());
403     [radio drawWithFrame:NSRect(inflatedRect) inView:o->view()->frameView()->getDocumentView()];
404     [radio setControlView: nil];
405     
406     return false;
407 }
408
409 const IntSize* RenderThemeMac::radioSizes() const
410 {
411     static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
412     return sizes;
413 }
414
415 const int* RenderThemeMac::radioMargins() const
416 {
417     static const int margins[3][4] = 
418     {
419         { 2, 2, 4, 2 },
420         { 3, 2, 3, 2 },
421         { 1, 0, 2, 0 },
422     };
423     return margins[[radio controlSize]];
424 }
425
426 void RenderThemeMac::setRadioCellState(const RenderObject* o, const IntRect& r)
427 {
428     if (!radio) {
429         radio = HardRetainWithNSRelease([[NSButtonCell alloc] init]);
430         [radio setButtonType:NSRadioButton];
431         [radio setTitle:nil];
432     }
433     
434     // Set the control size based off the rectangle we're painting into.
435     setControlSize(radio, radioSizes(), r.size());
436     
437     // Update the various states we respond to.
438     updateCheckedState(radio, o);
439     updateEnabledState(radio, o);
440     updatePressedState(radio, o);
441     updateFocusedState(radio, o);
442 }
443
444
445 void RenderThemeMac::setRadioSize(RenderStyle* style) const
446 {
447     // If the width and height are both specified, then we have nothing to do.
448     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
449         return;
450     
451     // Use the font size to determine the intrinsic width of the control.
452     setSizeFromFont(style, radioSizes());
453 }
454
455 void RenderThemeMac::setButtonPaddingFromControlSize(RenderStyle* style, NSControlSize size) const
456 {
457     // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
458     // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
459     // by definition constrained, since we select mini only for small cramped environments.
460     // This also guarantees the HTML4 <button> will match our rendering by default, since we're using a consistent
461     // padding.
462     const int padding = 8;
463     style->setPaddingLeft(Length(padding, Fixed));
464     style->setPaddingRight(Length(padding, Fixed));
465     style->setPaddingTop(Length(0, Fixed));
466     style->setPaddingBottom(Length(0, Fixed));
467 }
468
469 void RenderThemeMac::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
470 {
471     // There are three appearance constants for buttons.
472     // (1) Push-button is the constant for the default Aqua system button.  Push buttons will not scale vertically and will not allow
473     // custom fonts or colors.  <input>s use this constant.  This button will allow custom colors and font weights/variants but won't
474     // scale vertically.
475     // (2) square-button is the constant for the square button.  This button will allow custom fonts and colors and will scale vertically.
476     // (3) Button is the constant that means "pick the best button as appropriate."  <button>s use this constant.  This button will
477     // also scale vertically and allow custom fonts and colors.  It will attempt to use Aqua if possible and will make this determination
478     // solely on the rectangle of the control.
479     
480     // Determine our control size based off our font.
481     NSControlSize controlSize = controlSizeForFont(style);
482
483     if (style->appearance() == PushButtonAppearance) {
484         // Ditch the border.
485         style->resetBorder();
486
487         // Height is locked to auto.
488         style->setHeight(Length(Auto));
489         
490         // White-space is locked to pre
491         style->setWhiteSpace(PRE);
492
493         // Set the button's vertical size.
494         setButtonSize(style);
495
496         // Add in the padding that we'd like to use.
497         setButtonPaddingFromControlSize(style, controlSize);
498
499         // Our font is locked to the appropriate system font size for the control.  To clarify, we first use the CSS-specified font to figure out
500         // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
501         // system font for the control size instead.
502         setFontFromControlSize(selector, style, controlSize);
503     } else {
504         // Set a min-height so that we can't get smaller than the mini button.
505         style->setMinHeight(Length(15, Fixed));
506         
507         // Reset the top and bottom borders.
508         style->resetBorderTop();
509         style->resetBorderBottom();
510     }
511 }
512
513 const IntSize* RenderThemeMac::buttonSizes() const
514 {
515     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
516     return sizes;
517 }
518
519 const int* RenderThemeMac::buttonMargins() const
520 {
521     static const int margins[3][4] = 
522     {
523         { 4, 6, 7, 6 },
524         { 4, 5, 6, 5 },
525         { 0, 1, 1, 1 },
526     };
527     return margins[[button controlSize]];
528 }
529
530 void RenderThemeMac::setButtonSize(RenderStyle* style) const
531 {
532     // If the width and height are both specified, then we have nothing to do.
533     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
534         return;
535     
536     // Use the font size to determine the intrinsic width of the control.
537     setSizeFromFont(style, buttonSizes());
538 }
539
540 void RenderThemeMac::setButtonCellState(const RenderObject* o, const IntRect& r)
541 {
542     if (!button) {
543         button = HardRetainWithNSRelease([[NSButtonCell alloc] init]);
544         [button setTitle:nil];
545         [button setButtonType:NSMomentaryPushInButton];
546     }
547
548     // Set the control size based off the rectangle we're painting into.
549     if (o->style()->appearance() == SquareButtonAppearance ||
550         r.height() > buttonSizes()[NSRegularControlSize].height()) {
551         // Use the square button
552         if ([button bezelStyle] != NSShadowlessSquareBezelStyle)
553             [button setBezelStyle:NSShadowlessSquareBezelStyle];
554     } else if ([button bezelStyle] != NSRoundedBezelStyle)
555         [button setBezelStyle:NSRoundedBezelStyle];
556             
557     setControlSize(button, buttonSizes(), r.size());
558     
559     // Update the various states we respond to.
560     updateCheckedState(button, o);
561     updateEnabledState(button, o);
562     updatePressedState(button, o);
563     updateFocusedState(button, o);
564 }
565
566 bool RenderThemeMac::paintButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
567 {
568     LocalCurrentGraphicsContext LocalCurrentGraphicsContext(paintInfo.p);
569
570     // Determine the width and height needed for the control and prepare the cell for painting.
571     setButtonCellState(o, r);
572     
573     // We inflate the rect as needed to account for padding included in the cell to accommodate the button
574     // shadow.  We don't consider this part of the bounds of the control in WebKit.
575     IntSize size = buttonSizes()[[button controlSize]];
576     size.setWidth(r.width());
577     IntRect inflatedRect = r;
578     if ([button bezelStyle] == NSRoundedBezelStyle) {
579         // Center the button within the available space.
580         if (inflatedRect.height() > size.height()) {
581             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - size.height()) / 2);
582             inflatedRect.setHeight(size.height());
583         }
584         
585         // Now inflate it to account for the shadow.
586         inflatedRect = inflateRect(inflatedRect, size, buttonMargins());
587     }
588
589     [button drawWithFrame:NSRect(inflatedRect) inView:o->view()->frameView()->getDocumentView()];
590     [button setControlView:nil];
591
592     return false;
593 }
594
595 bool RenderThemeMac::paintTextField(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
596 {
597     LocalCurrentGraphicsContext LocalCurrentGraphicsContext(paintInfo.p);
598     wkDrawBezeledTextFieldCell(r, isEnabled(o) && !isReadOnlyControl(o));
599     return false;
600 }
601
602 void RenderThemeMac::adjustTextFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
603 {
604 }
605
606 bool RenderThemeMac::paintTextArea(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
607 {
608     LocalCurrentGraphicsContext LocalCurrentGraphicsContext(paintInfo.p);
609     wkDrawBezeledTextArea(r, isEnabled(o) && !isReadOnlyControl(o));
610     return false;
611 }
612
613 void RenderThemeMac::adjustTextAreaStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
614 {
615 }
616
617 const int* RenderThemeMac::popupButtonMargins() const
618 {
619     static const int margins[3][4] = 
620     {
621         { 0, 3, 1, 3 },
622         { 0, 3, 2, 3 },
623         { 0, 1, 0, 1 }
624     };
625     return margins[[popupButton controlSize]];
626 }
627
628 const IntSize* RenderThemeMac::popupButtonSizes() const
629 {
630     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
631     return sizes;
632 }
633
634 const int* RenderThemeMac::popupButtonPadding(NSControlSize size) const
635 {
636     static const int padding[3][4] = 
637     {
638         { 2, 26, 3, 8 },
639         { 2, 23, 3, 8 },
640         { 2, 22, 3, 10 }
641     };
642     return padding[size];
643 }
644
645 void RenderThemeMac::setPopupPaddingFromControlSize(RenderStyle* style, NSControlSize size) const
646 {
647     style->setPaddingLeft(Length(popupButtonPadding(size)[leftPadding], Fixed));
648     style->setPaddingRight(Length(popupButtonPadding(size)[rightPadding], Fixed));
649     style->setPaddingTop(Length(popupButtonPadding(size)[topPadding], Fixed));
650     style->setPaddingBottom(Length(popupButtonPadding(size)[bottomPadding], Fixed));
651 }
652
653 bool RenderThemeMac::paintMenuList(RenderObject* o, const RenderObject::PaintInfo&, const IntRect& r)
654 {
655     setPopupButtonCellState(o, r);
656     
657     IntRect inflatedRect = r;
658     IntSize size = popupButtonSizes()[[popupButton controlSize]];
659     size.setWidth(r.width());
660
661     // Now inflate it to account for the shadow.
662     inflatedRect = inflateRect(inflatedRect, size, popupButtonMargins());
663         
664     [popupButton drawWithFrame:inflatedRect inView:o->view()->frameView()->getDocumentView()];
665     [popupButton setControlView:nil];
666     return false;
667 }
668
669 const float baseFontSize = 11.0;
670 const float baseArrowHeight = 4.0;
671 const float baseArrowWidth = 5.0;
672 const float baseSpaceBetweenArrows = 2.0;
673 const int arrowPaddingLeft = 6;
674 const int arrowPaddingRight = 6;
675 const int paddingBeforeSeparator = 4;
676 const int baseBorderRadius = 5;
677 const int styledPopupPaddingLeft = 8;
678 const int styledPopupPaddingTop = 1;
679 const int styledPopupPaddingBottom = 2;
680
681 static void TopGradientInterpolate( void *info, const CGFloat *inData, CGFloat *outData )
682 {
683     static float dark[4] = { 1, 1, 1, 0.4 };
684     static float light[4] = { 1, 1, 1, 0.15 };
685     float a = inData[0];
686     int i = 0;
687     for( i = 0; i < 4; i++ )
688         outData[i] = ( 1.0 - a ) * dark[i] + a * light[i];
689 }
690
691 static void BottomGradientInterpolate( void *info, const CGFloat *inData, CGFloat *outData )
692 {
693     static float dark[4] = { 1, 1, 1, 0 };
694     static float light[4] = { 1, 1, 1, 0.3 };
695     float a = inData[0];
696     int i = 0;
697     for( i = 0; i < 4; i++ )
698         outData[i] = ( 1.0 - a ) * dark[i] + a * light[i];
699 }
700
701 static void MainGradientInterpolate( void *info, const CGFloat *inData, CGFloat *outData )
702 {
703     static float dark[4] = { 0, 0, 0, 0.15 };
704     static float light[4] = { 0, 0, 0, 0 };
705     float a = inData[0];
706     int i = 0;
707     for( i = 0; i < 4; i++ )
708         outData[i] = ( 1.0 - a ) * dark[i] + a * light[i];
709 }
710
711 void RenderThemeMac::paintMenuListButtonGradients(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
712 {
713     CGContextRef context = i.p->platformContext();
714         
715     i.p->save();
716
717     int radius = o->style()->borderTopLeftRadius().width();
718     
719     CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();    
720
721     FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0);
722     struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL };
723     CGFunctionRef topFunction = CGFunctionCreate( NULL, 1, NULL, 4, NULL, &topCallbacks );
724     CGShadingRef topShading = CGShadingCreateAxial( cspace, CGPointMake(topGradient.x(),  topGradient.y()), 
725                             CGPointMake(topGradient.x(), topGradient.bottom()), topFunction, false, false );
726
727     FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0, r.width() - 2 * radius, r.height() / 2.0);
728     struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL };
729     CGFunctionRef bottomFunction = CGFunctionCreate( NULL, 1, NULL, 4, NULL, &bottomCallbacks );
730     CGShadingRef bottomShading = CGShadingCreateAxial( cspace, CGPointMake(bottomGradient.x(),  bottomGradient.y()), 
731                             CGPointMake(bottomGradient.x(), bottomGradient.bottom()), bottomFunction, false, false );
732
733     struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL };
734     CGFunctionRef mainFunction = CGFunctionCreate( NULL, 1, NULL, 4, NULL, &mainCallbacks );
735     CGShadingRef mainShading = CGShadingCreateAxial( cspace, CGPointMake(r.x(),  r.y()), 
736                             CGPointMake(r.x(), r.bottom()), mainFunction, false, false );
737
738     CGShadingRef leftShading = CGShadingCreateAxial( cspace, CGPointMake(r.x(),  r.y()), 
739                             CGPointMake(r.x() + radius, r.y()), mainFunction, false, false );                        
740
741     CGShadingRef rightShading = CGShadingCreateAxial( cspace, CGPointMake(r.right(),  r.y()), 
742                             CGPointMake(r.right() - radius, r.y()), mainFunction, false, false );                             
743     i.p->save();
744     CGContextClipToRect(context, r);
745     i.p->addRoundedRectClip(r, 
746         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
747         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
748     CGContextDrawShading(context, mainShading);  
749     i.p->restore();      
750
751     i.p->save();
752     CGContextClipToRect(context, topGradient);
753     i.p->addRoundedRectClip(enclosingIntRect(topGradient), 
754         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
755         IntSize(), IntSize());
756     CGContextDrawShading(context, topShading);  
757     i.p->restore();      
758
759     i.p->save();
760     CGContextClipToRect(context, bottomGradient);
761     i.p->addRoundedRectClip(enclosingIntRect(bottomGradient), 
762         IntSize(), IntSize(),
763         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
764     CGContextDrawShading(context, bottomShading); 
765     i.p->restore();
766     
767     i.p->save();
768     CGContextClipToRect(context, r);
769     i.p->addRoundedRectClip(r, 
770         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
771         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
772     CGContextDrawShading(context, leftShading);
773     CGContextDrawShading(context, rightShading);
774     i.p->restore();
775     
776     i.p->restore();
777 }
778
779 bool RenderThemeMac::paintMenuListButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
780 {        
781     i.p->save();
782         
783     IntRect bounds = IntRect(r.x() + o->style()->borderLeftWidth(), 
784                              r.y() + o->style()->borderTopWidth(), 
785                              r.width() - o->style()->borderLeftWidth() - o->style()->borderRightWidth(),
786                              r.height() - o->style()->borderTopWidth() - o->style()->borderBottomWidth());
787     // Draw the gradients to give the styled popup menu a button appearance
788     paintMenuListButtonGradients(o, i, bounds);
789                 
790     float fontScale = o->style()->fontSize() / baseFontSize;
791     float centerY = bounds.y() + bounds.height() / 2.0;
792     float arrowHeight = baseArrowHeight * fontScale;
793     float arrowWidth = baseArrowWidth * fontScale;    
794     float leftEdge = bounds.right() - arrowPaddingRight - arrowWidth;
795     float spaceBetweenArrows = baseSpaceBetweenArrows * fontScale;
796     
797     i.p->setFillColor(o->style()->color());
798     i.p->setPen(Pen(o->style()->color()));
799     
800     FloatPoint arrow1[3];
801     arrow1[0] = FloatPoint(leftEdge, centerY - spaceBetweenArrows / 2.0);
802     arrow1[1] = FloatPoint(leftEdge + arrowWidth, centerY - spaceBetweenArrows / 2.0);
803     arrow1[2] = FloatPoint(leftEdge + arrowWidth / 2.0, centerY - spaceBetweenArrows / 2.0 - arrowHeight);
804
805     // Draw the top arrow
806     i.p->drawConvexPolygon(3, arrow1, true);
807
808     FloatPoint arrow2[3];
809     arrow2[0] = FloatPoint(leftEdge, centerY + spaceBetweenArrows / 2.0);
810     arrow2[1] = FloatPoint(leftEdge + arrowWidth, centerY + spaceBetweenArrows / 2.0);
811     arrow2[2] = FloatPoint(leftEdge + arrowWidth / 2.0, centerY + spaceBetweenArrows / 2.0 + arrowHeight);   
812     
813     // Draw the bottom arrow
814     i.p->drawConvexPolygon(3, arrow2, true); 
815     
816     Color leftSeparatorColor(0, 0, 0, 40);
817     Color rightSeparatorColor(255, 255, 255, 40);
818     int separatorSpace = 2;
819     int leftEdgeOfSeparator = int(leftEdge - arrowPaddingLeft); // FIXME: Round?
820     
821     // Draw the separator to the left of the arrows
822     i.p->setPen(Pen(leftSeparatorColor));
823     i.p->drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()), IntPoint(leftEdgeOfSeparator, bounds.bottom()));
824
825     i.p->setPen(Pen(rightSeparatorColor));
826     i.p->drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()), IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.bottom()));
827
828     i.p->restore();
829     return false;
830 }
831
832 void RenderThemeMac::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
833 {
834     NSControlSize controlSize = controlSizeForFont(style);
835
836     style->resetBorder();
837     
838     // Height is locked to auto.
839     style->setHeight(Length(Auto));
840     
841     // White-space is locked to pre
842     style->setWhiteSpace(PRE);
843
844     // Set the foreground color to black when we have the aqua look
845     style->setColor(Color::black);
846
847     // Set the button's vertical size.
848     setButtonSize(style);
849
850     // Add in the padding that we'd like to use.
851     setPopupPaddingFromControlSize(style, controlSize);
852
853     // Our font is locked to the appropriate system font size for the control.  To clarify, we first use the CSS-specified font to figure out
854     // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
855     // system font for the control size instead.
856     setFontFromControlSize(selector, style, controlSize);
857 }
858
859 void RenderThemeMac::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
860 {        
861     float fontScale = style->fontSize() / baseFontSize;
862     float arrowWidth = baseArrowWidth * fontScale;
863     
864     // We're overriding the padding to allow for the arrow control.  WinIE doesn't honor padding on selects, so
865     // this shouldn't cause problems on the web.  If IE7 changes that, we should reconsider this.
866     style->setPaddingLeft(Length(styledPopupPaddingLeft, Fixed));
867     style->setPaddingRight(Length(int(ceilf(arrowWidth + arrowPaddingLeft + arrowPaddingRight + paddingBeforeSeparator)), Fixed));
868     style->setPaddingTop(Length(styledPopupPaddingTop, Fixed));
869     style->setPaddingBottom(Length(styledPopupPaddingBottom, Fixed));
870     
871     if (style->hasBorderRadius())
872         style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up?
873     
874     const int minHeight = 15;
875     style->setMinHeight(Length(minHeight, Fixed));
876 }
877
878 void RenderThemeMac::setPopupButtonCellState(const RenderObject* o, const IntRect& r)
879 {
880     if (!popupButton) {
881         popupButton = HardRetainWithNSRelease([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
882         [popupButton setUsesItemFromMenu:NO];
883     }
884
885     // Set the control size based off the rectangle we're painting into.
886     setControlSize(popupButton, popupButtonSizes(), r.size());
887
888     // Update the various states we respond to.
889     updateCheckedState(popupButton, o);
890     updateEnabledState(popupButton, o);
891     updatePressedState(popupButton, o);
892     updateFocusedState(popupButton, o);
893 }
894
895 int RenderThemeMac::minimumMenuListSize(RenderStyle* style) const
896 {
897     int fontSize = style->fontSize();
898     if (fontSize >= 13)
899         return 9;
900     if (fontSize >= 11)
901         return 5;
902     return 0;
903 }
904
905 }