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