Don't crash if SafariTheme can't be loaded
[WebKit-https.git] / WebCore / rendering / RenderThemeSafari.cpp
1 /*
2  * Copyright (C) 2007 Apple Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "RenderThemeSafari.h"
23
24 #ifdef USE_SAFARI_THEME
25
26 #include "CSSValueKeywords.h"
27 #include "Document.h"
28 #include "Element.h"
29 #include "FrameView.h"
30 #include "GraphicsContext.h"
31 #include "HTMLInputElement.h"
32 #include "RenderSlider.h"
33 #include "RenderView.h"
34 #include "RetainPtr.h"
35 #include "cssstyleselector.h"
36 #include <CoreGraphics/CoreGraphics.h>
37  
38 using std::min;
39
40 // FIXME: The platform-independent code in this class should be factored out and merged with RenderThemeMac. 
41  
42 namespace WebCore {
43
44 using namespace SafariTheme;
45
46 enum {
47     topMargin,
48     rightMargin,
49     bottomMargin,
50     leftMargin
51 };
52
53 enum {
54     topPadding,
55     rightPadding,
56     bottomPadding,
57     leftPadding
58 };
59
60 RenderTheme* theme()
61 {
62     static RenderThemeSafari safariTheme;
63     return &safariTheme;
64 }
65
66 static paintThemePartPtr paintThemePart;
67
68 ThemeControlState RenderThemeSafari::determineState(RenderObject* o) const
69 {
70     ThemeControlState result = 0;
71     if (isEnabled(o) && !isReadOnlyControl(o))
72         result |= SafariTheme::EnabledState;
73     if (isPressed(o))
74         result |= SafariTheme::PressedState;
75     if (isChecked(o))
76         result |= SafariTheme::CheckedState;
77     if (isIndeterminate(o))
78         result |= SafariTheme::IndeterminateCheckedState;
79     if (isFocused(o))
80         result |= SafariTheme::FocusedState;
81     return result;
82 }
83
84 static NSControlSize controlSizeFromRect(const IntRect& rect, const IntSize sizes[])
85 {
86     if (sizes[NSRegularControlSize].height() == rect.height())
87         return NSRegularControlSize;
88     else if (sizes[NSMiniControlSize].height() == rect.height())
89         return NSMiniControlSize;
90     
91     return NSSmallControlSize;
92 }
93
94 RenderThemeSafari::RenderThemeSafari()
95     : m_themeDLL(0)
96 {
97     m_themeDLL = ::LoadLibrary(SAFARITHEMEDLL);
98     if (m_themeDLL) {
99         paintThemePart = (paintThemePartPtr)GetProcAddress(m_themeDLL, "paintThemePart");
100     }
101 }
102
103 RenderThemeSafari::~RenderThemeSafari()
104 {
105     if (!m_themeDLL)
106         return;
107
108     // we don't need to close the themes here because uxtheme should do that for us
109     // anyway (and we could crash if uxtheme has done cleanup already)
110
111     ::FreeLibrary(m_themeDLL);
112 }
113
114 Color RenderThemeSafari::platformActiveSelectionBackgroundColor() const
115 {
116     return Color(181, 213, 255);
117 }
118
119 Color RenderThemeSafari::platformInactiveSelectionBackgroundColor() const
120 {
121     return Color(212, 212, 212);
122 }
123
124 Color RenderThemeSafari::activeListBoxSelectionBackgroundColor() const
125 {
126     // FIXME: This should probably just be a darker version of the platformActiveSelectionBackgroundColor
127     return Color(56, 117, 215);
128 }
129
130 static float systemFontSizeForControlSize(NSControlSize controlSize)
131 {
132     static float sizes[] = { 13.0f, 11.0f, 9.0f };
133     
134     return sizes[controlSize];
135 }
136
137 void RenderThemeSafari::systemFont(int propId, FontDescription& fontDescription) const
138 {
139     static FontDescription systemFont;
140     static FontDescription smallSystemFont;
141     static FontDescription menuFont;
142     static FontDescription labelFont;
143     static FontDescription miniControlFont;
144     static FontDescription smallControlFont;
145     static FontDescription controlFont;
146
147     FontDescription* cachedDesc;
148     float fontSize = 0;
149     switch (propId) {
150         case CSS_VAL_SMALL_CAPTION:
151             cachedDesc = &smallSystemFont;
152             if (!smallSystemFont.isAbsoluteSize())
153                 fontSize = systemFontSizeForControlSize(NSSmallControlSize);
154             break;
155         case CSS_VAL_MENU:
156             cachedDesc = &menuFont;
157             if (!menuFont.isAbsoluteSize())
158                 fontSize = systemFontSizeForControlSize(NSRegularControlSize);
159             break;
160         case CSS_VAL_STATUS_BAR:
161             cachedDesc = &labelFont;
162             if (!labelFont.isAbsoluteSize())
163                 fontSize = 10.0f;
164             break;
165         case CSS_VAL__WEBKIT_MINI_CONTROL:
166             cachedDesc = &miniControlFont;
167             if (!miniControlFont.isAbsoluteSize())
168                 fontSize = systemFontSizeForControlSize(NSMiniControlSize);
169             break;
170         case CSS_VAL__WEBKIT_SMALL_CONTROL:
171             cachedDesc = &smallControlFont;
172             if (!smallControlFont.isAbsoluteSize())
173                 fontSize = systemFontSizeForControlSize(NSSmallControlSize);
174             break;
175         case CSS_VAL__WEBKIT_CONTROL:
176             cachedDesc = &controlFont;
177             if (!controlFont.isAbsoluteSize())
178                 fontSize = systemFontSizeForControlSize(NSRegularControlSize);
179             break;
180         default:
181             cachedDesc = &systemFont;
182             if (!systemFont.isAbsoluteSize())
183                 fontSize = 13.0f;
184     }
185
186     if (fontSize) {
187         cachedDesc->setIsAbsoluteSize(true);
188         cachedDesc->setGenericFamily(FontDescription::NoFamily);
189         cachedDesc->firstFamily().setFamily("Lucida Grande");
190         cachedDesc->setSpecifiedSize(fontSize);
191         cachedDesc->setBold(false);
192         cachedDesc->setItalic(false);
193     }
194     fontDescription = *cachedDesc;
195 }
196
197 bool RenderThemeSafari::isControlStyled(const RenderStyle* style, const BorderData& border,
198                                      const BackgroundLayer& background, const Color& backgroundColor) const
199 {
200     // If we didn't find SafariTheme.dll we won't be able to paint any themed controls.
201     if (!paintThemePart)
202         return true;
203
204     if (style->appearance() == TextFieldAppearance || style->appearance() == TextAreaAppearance || style->appearance() == ListboxAppearance)
205         return style->border() != border;
206     return RenderTheme::isControlStyled(style, border, background, backgroundColor);
207 }
208
209 void RenderThemeSafari::adjustRepaintRect(const RenderObject* o, IntRect& r)
210 {
211     NSControlSize controlSize = controlSizeForFont(o->style());
212
213     switch (o->style()->appearance()) {
214         case CheckboxAppearance: {
215             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
216             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
217             r = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize));
218             break;
219         }
220         case RadioAppearance: {
221             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
222             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
223             r = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize));
224             break;
225         }
226         case PushButtonAppearance:
227         case ButtonAppearance: {
228             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
229             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
230             if (r.height() <= buttonSizes()[NSRegularControlSize].height())
231                 r = inflateRect(r, buttonSizes()[controlSize], buttonMargins(controlSize));
232             break;
233         }
234         case MenulistAppearance: {
235             r = inflateRect(r, popupButtonSizes()[controlSize], popupButtonMargins(controlSize));
236             break;
237         }
238         default:
239             break;
240     }
241 }
242
243 IntRect RenderThemeSafari::inflateRect(const IntRect& r, const IntSize& size, const int* margins) const
244 {
245     // Only do the inflation if the available width/height are too small.  Otherwise try to
246     // fit the glow/check space into the available box's width/height.
247     int widthDelta = r.width() - (size.width() + margins[leftMargin] + margins[rightMargin]);
248     int heightDelta = r.height() - (size.height() + margins[topMargin] + margins[bottomMargin]);
249     IntRect result(r);
250     if (widthDelta < 0) {
251         result.setX(result.x() - margins[leftMargin]);
252         result.setWidth(result.width() - widthDelta);
253     }
254     if (heightDelta < 0) {
255         result.setY(result.y() - margins[topMargin]);
256         result.setHeight(result.height() - heightDelta);
257     }
258     return result;
259 }
260
261 short RenderThemeSafari::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 RenderThemeSafari::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 RenderThemeSafari::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 RenderThemeSafari::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 RenderThemeSafari::sizeForFont(RenderStyle* style, const IntSize* sizes) const
307 {
308     return sizes[controlSizeForFont(style)];
309 }
310
311 IntSize RenderThemeSafari::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const
312 {
313     return sizes[controlSizeForSystemFont(style)];
314 }
315
316 void RenderThemeSafari::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const
317 {
318     // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
319     IntSize size = sizeForFont(style, sizes);
320     if (style->width().isIntrinsicOrAuto() && size.width() > 0)
321         style->setWidth(Length(size.width(), Fixed));
322     if (style->height().isAuto() && size.height() > 0)
323         style->setHeight(Length(size.height(), Fixed));
324 }
325
326 void RenderThemeSafari::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const
327 {
328     FontDescription fontDescription;
329     fontDescription.setIsAbsoluteSize(true);
330     fontDescription.setGenericFamily(FontDescription::SerifFamily);
331
332     float fontSize = systemFontSizeForControlSize(controlSize);
333     fontDescription.firstFamily().setFamily("Lucida Grande");
334     fontDescription.setComputedSize(fontSize);
335     fontDescription.setSpecifiedSize(fontSize);
336
337     // Reset line height
338     style->setLineHeight(RenderStyle::initialLineHeight());
339
340     if (style->setFontDescription(fontDescription))
341         style->font().update(selector->fontSelector());
342 }
343
344 NSControlSize RenderThemeSafari::controlSizeForSystemFont(RenderStyle* style) const
345 {
346     int fontSize = style->fontSize();
347     if (fontSize >= 13)
348         return NSRegularControlSize;
349     if (fontSize >= 11)
350         return NSSmallControlSize;
351     return NSMiniControlSize;
352 }
353
354 bool RenderThemeSafari::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
355 {
356     ASSERT(paintThemePart);
357
358     NSControlSize controlSize = controlSizeForFont(o->style());
359
360     IntRect inflatedRect = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize));  
361     paintThemePart(CheckboxPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
362
363     return false;
364 }
365
366 const IntSize* RenderThemeSafari::checkboxSizes() const
367 {
368     static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
369     return sizes;
370 }
371
372 const int* RenderThemeSafari::checkboxMargins(NSControlSize controlSize) const
373 {
374     static const int margins[3][4] =
375     {
376         { 2, 2, 2, 2 },
377         { 2, 2, 2, 1 },
378         { 1, 0, 0, 0 },
379     };
380     return margins[controlSize];
381 }
382
383 void RenderThemeSafari::setCheckboxSize(RenderStyle* style) const
384 {
385     // If the width and height are both specified, then we have nothing to do.
386     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
387         return;
388
389     // Use the font size to determine the intrinsic width of the control.
390     setSizeFromFont(style, checkboxSizes());
391 }
392
393 bool RenderThemeSafari::paintRadio(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
394 {
395     ASSERT(paintThemePart);
396
397     NSControlSize controlSize = controlSizeForFont(o->style());
398  
399     IntRect inflatedRect = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize));    
400     paintThemePart(RadioButtonPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
401
402     return false;
403 }
404
405 const IntSize* RenderThemeSafari::radioSizes() const
406 {
407     static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
408     return sizes;
409 }
410
411 const int* RenderThemeSafari::radioMargins(NSControlSize controlSize) const
412 {
413     static const int margins[3][4] =
414     {
415         { 1, 2, 2, 2 },
416         { 0, 1, 2, 1 },
417         { 0, 0, 1, 0 },
418      };
419     return margins[controlSize];
420 }
421
422 void RenderThemeSafari::setRadioSize(RenderStyle* style) const
423 {
424     // If the width and height are both specified, then we have nothing to do.
425     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
426         return;
427
428     // Use the font size to determine the intrinsic width of the control.
429     setSizeFromFont(style, radioSizes());
430 }
431
432 void RenderThemeSafari::setButtonPaddingFromControlSize(RenderStyle* style, NSControlSize size) const
433 {
434     // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
435     // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
436     // by definition constrained, since we select mini only for small cramped environments.
437     // This also guarantees the HTML4 <button> will match our rendering by default, since we're using a consistent
438     // padding.
439     const int padding = 8;
440     style->setPaddingLeft(Length(padding, Fixed));
441     style->setPaddingRight(Length(padding, Fixed));
442     style->setPaddingTop(Length(0, Fixed));
443     style->setPaddingBottom(Length(0, Fixed));
444 }
445
446 void RenderThemeSafari::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
447 {
448     // There are three appearance constants for buttons.
449     // (1) Push-button is the constant for the default Aqua system button.  Push buttons will not scale vertically and will not allow
450     // custom fonts or colors.  <input>s use this constant.  This button will allow custom colors and font weights/variants but won't
451     // scale vertically.
452     // (2) square-button is the constant for the square button.  This button will allow custom fonts and colors and will scale vertically.
453     // (3) Button is the constant that means "pick the best button as appropriate."  <button>s use this constant.  This button will
454     // also scale vertically and allow custom fonts and colors.  It will attempt to use Aqua if possible and will make this determination
455     // solely on the rectangle of the control.
456
457     // Determine our control size based off our font.
458     NSControlSize controlSize = controlSizeForFont(style);
459
460     if (style->appearance() == PushButtonAppearance) {
461         // Ditch the border.
462         style->resetBorder();
463
464         // Height is locked to auto.
465         style->setHeight(Length(Auto));
466
467         // White-space is locked to pre
468         style->setWhiteSpace(PRE);
469
470         // Set the button's vertical size.
471         setButtonSize(style);
472
473         // Add in the padding that we'd like to use.
474         setButtonPaddingFromControlSize(style, controlSize);
475
476         // 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
477         // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
478         // system font for the control size instead.
479         setFontFromControlSize(selector, style, controlSize);
480     } else {
481         // Set a min-height so that we can't get smaller than the mini button.
482         style->setMinHeight(Length(15, Fixed));
483
484         // Reset the top and bottom borders.
485         style->resetBorderTop();
486         style->resetBorderBottom();
487     }
488 }
489
490 const IntSize* RenderThemeSafari::buttonSizes() const
491 {
492     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
493     return sizes;
494 }
495
496 const int* RenderThemeSafari::buttonMargins(NSControlSize controlSize) const
497 {
498     static const int margins[3][4] =
499     {
500         { 4, 6, 7, 6 },
501         { 4, 5, 6, 5 },
502         { 0, 1, 1, 1 },
503     };
504     return margins[controlSize];
505 }
506
507 void RenderThemeSafari::setButtonSize(RenderStyle* style) const
508 {
509     // If the width and height are both specified, then we have nothing to do.
510     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
511         return;
512
513     // Use the font size to determine the intrinsic width of the control.
514     setSizeFromFont(style, buttonSizes());
515 }
516
517 bool RenderThemeSafari::paintButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
518 {
519     ASSERT(paintThemePart);
520
521     // We inflate the rect as needed to account for padding included in the cell to accommodate the button
522     // shadow.  We don't consider this part of the bounds of the control in WebKit.
523
524     NSControlSize controlSize = controlSizeFromRect(r, buttonSizes());
525     IntRect inflatedRect = r;
526
527     ThemePart part;
528     if (r.height() <= buttonSizes()[NSRegularControlSize].height()) {
529         // Push button
530         part = PushButtonPart;
531
532         IntSize size = buttonSizes()[controlSize];
533         size.setWidth(r.width());
534
535         // Center the button within the available space.
536         if (inflatedRect.height() > size.height()) {
537             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - size.height()) / 2);
538             inflatedRect.setHeight(size.height());
539         }
540
541         // Now inflate it to account for the shadow.
542         inflatedRect = inflateRect(inflatedRect, size, buttonMargins(controlSize));
543     } else
544         part = SquareButtonPart;
545
546     paintThemePart(part, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
547     return false;
548 }
549
550 bool RenderThemeSafari::paintTextField(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
551 {
552     ASSERT(paintThemePart);
553
554     paintThemePart(TextFieldPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState);
555     return false;
556 }
557
558 void RenderThemeSafari::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const
559 {
560 }
561
562 bool RenderThemeSafari::paintCapsLockIndicator(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
563 {    
564 #if defined(SAFARI_THEME_VERSION) && SAFARI_THEME_VERSION >= 1
565     ASSERT(paintThemePart);
566
567     if (paintInfo.context->paintingDisabled())
568         return true;
569
570     paintThemePart(CapsLockPart, paintInfo.context->platformContext(), r, (NSControlSize)0, (ThemeControlState)0);
571
572     return false;
573 #else
574     return true;
575 #endif
576 }
577
578 bool RenderThemeSafari::paintTextArea(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
579 {
580     ASSERT(paintThemePart);
581
582     paintThemePart(TextAreaPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState);
583     return false;
584 }
585
586 void RenderThemeSafari::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const
587 {
588 }
589
590 const int* RenderThemeSafari::popupButtonMargins(NSControlSize size) const
591 {
592     static const int margins[3][4] =
593     {
594         { 2, 3, 3, 3 },
595         { 1, 3, 3, 3 },
596         { 0, 1, 0, 1 }
597     };
598     return margins[size];
599 }
600
601 const IntSize* RenderThemeSafari::popupButtonSizes() const
602 {
603     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
604     return sizes;
605 }
606
607 const int* RenderThemeSafari::popupButtonPadding(NSControlSize size) const
608 {
609     static const int padding[3][4] =
610     {
611         { 2, 26, 3, 8 },
612         { 2, 23, 3, 8 },
613         { 2, 22, 3, 10 }
614     };
615     return padding[size];
616 }
617
618 bool RenderThemeSafari::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& info, const IntRect& r)
619 {
620     ASSERT(paintThemePart);
621
622     NSControlSize controlSize = controlSizeFromRect(r, popupButtonSizes());
623     IntRect inflatedRect = r;
624     IntSize size = popupButtonSizes()[controlSize];
625     size.setWidth(r.width());
626
627     // Now inflate it to account for the shadow.
628     if (r.width() >= minimumMenuListSize(o->style()))
629         inflatedRect = inflateRect(inflatedRect, size, popupButtonMargins(controlSize));
630
631     paintThemePart(DropDownButtonPart, info.context->platformContext(), inflatedRect, controlSize, determineState(o));
632
633     return false;
634 }
635
636 const float baseFontSize = 11.0f;
637 const float baseArrowHeight = 5.0f;
638 const float baseArrowWidth = 7.0f;
639 const int arrowPaddingLeft = 5;
640 const int arrowPaddingRight = 5;
641 const int paddingBeforeSeparator = 4;
642 const int baseBorderRadius = 5;
643 const int styledPopupPaddingLeft = 8;
644 const int styledPopupPaddingTop = 1;
645 const int styledPopupPaddingBottom = 2;
646
647 static void TopGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
648 {
649     static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.4f };
650     static float light[4] = { 1.0f, 1.0f, 1.0f, 0.15f };
651     float a = inData[0];
652     int i = 0;
653     for (i = 0; i < 4; i++)
654         outData[i] = (1.0f - a) * dark[i] + a * light[i];
655 }
656
657 static void BottomGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
658 {
659     static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.0f };
660     static float light[4] = { 1.0f, 1.0f, 1.0f, 0.3f };
661     float a = inData[0];
662     int i = 0;
663     for (i = 0; i < 4; i++)
664         outData[i] = (1.0f - a) * dark[i] + a * light[i];
665 }
666
667 static void MainGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
668 {
669     static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.15f };
670     static float light[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
671     float a = inData[0];
672     int i = 0;
673     for (i = 0; i < 4; i++)
674         outData[i] = (1.0f - a) * dark[i] + a * light[i];
675 }
676
677 static void TrackGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
678 {
679     static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.678f };
680     static float light[4] = { 0.0f, 0.0f, 0.0f, 0.13f };
681     float a = inData[0];
682     int i = 0;
683     for (i = 0; i < 4; i++)
684         outData[i] = (1.0f - a) * dark[i] + a * light[i];
685 }
686
687 void RenderThemeSafari::paintMenuListButtonGradients(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
688 {
689     CGContextRef context = paintInfo.context->platformContext();
690
691     paintInfo.context->save();
692
693     int radius = o->style()->borderTopLeftRadius().width();
694
695     RetainPtr<CGColorSpaceRef> cspace(AdoptCF, CGColorSpaceCreateDeviceRGB());
696
697     FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0f);
698     struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL };
699     RetainPtr<CGFunctionRef> topFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &topCallbacks));
700     RetainPtr<CGShadingRef> topShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(topGradient.x(), topGradient.y()), CGPointMake(topGradient.x(), topGradient.bottom()), topFunction.get(), false, false));
701
702     FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0f, r.width() - 2.0f * radius, r.height() / 2.0f);
703     struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL };
704     RetainPtr<CGFunctionRef> bottomFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &bottomCallbacks));
705     RetainPtr<CGShadingRef> bottomShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(bottomGradient.x(),  bottomGradient.y()), CGPointMake(bottomGradient.x(), bottomGradient.bottom()), bottomFunction.get(), false, false));
706
707     struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL };
708     RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
709     RetainPtr<CGShadingRef> mainShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.x(),  r.y()), CGPointMake(r.x(), r.bottom()), mainFunction.get(), false, false));
710
711     RetainPtr<CGShadingRef> leftShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.x(),  r.y()), CGPointMake(r.x() + radius, r.y()), mainFunction.get(), false, false));
712
713     RetainPtr<CGShadingRef> rightShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.right(),  r.y()), CGPointMake(r.right() - radius, r.y()), mainFunction.get(), false, false));
714     paintInfo.context->save();
715     CGContextClipToRect(context, r);
716     paintInfo.context->addRoundedRectClip(r,
717         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
718         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
719     CGContextDrawShading(context, mainShading.get());
720     paintInfo.context->restore();
721
722     paintInfo.context->save();
723     CGContextClipToRect(context, topGradient);
724     paintInfo.context->addRoundedRectClip(enclosingIntRect(topGradient),
725         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
726         IntSize(), IntSize());
727     CGContextDrawShading(context, topShading.get());
728     paintInfo.context->restore();
729
730     paintInfo.context->save();
731     CGContextClipToRect(context, bottomGradient);
732     paintInfo.context->addRoundedRectClip(enclosingIntRect(bottomGradient),
733         IntSize(), IntSize(),
734         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
735     CGContextDrawShading(context, bottomShading.get());
736     paintInfo.context->restore();
737
738     paintInfo.context->save();
739     CGContextClipToRect(context, r);
740     paintInfo.context->addRoundedRectClip(r,
741         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
742         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
743     CGContextDrawShading(context, leftShading.get());
744     CGContextDrawShading(context, rightShading.get());
745     paintInfo.context->restore();
746
747     paintInfo.context->restore();
748 }
749
750 bool RenderThemeSafari::paintMenuListButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
751 {
752     paintInfo.context->save();
753
754     IntRect bounds = IntRect(r.x() + o->style()->borderLeftWidth(),
755                              r.y() + o->style()->borderTopWidth(),
756                              r.width() - o->style()->borderLeftWidth() - o->style()->borderRightWidth(),
757                              r.height() - o->style()->borderTopWidth() - o->style()->borderBottomWidth());
758     // Draw the gradients to give the styled popup menu a button appearance
759     paintMenuListButtonGradients(o, paintInfo, bounds);
760     
761     // Since we actually know the size of the control here, we restrict the font scale to make sure the arrow will fit vertically in the bounds
762     float fontScale = min(o->style()->fontSize() / baseFontSize, bounds.height() / baseArrowHeight);
763     float centerY = bounds.y() + bounds.height() / 2.0f;
764     float arrowHeight = baseArrowHeight * fontScale;
765     float arrowWidth = baseArrowWidth * fontScale;
766     float leftEdge = bounds.right() - arrowPaddingRight - arrowWidth;
767
768     if (bounds.width() < arrowWidth + arrowPaddingLeft)
769         return false;
770
771     paintInfo.context->setFillColor(o->style()->color());
772     paintInfo.context->setStrokeColor(NoStroke);
773
774     FloatPoint arrow[3];
775     arrow[0] = FloatPoint(leftEdge, centerY - arrowHeight / 2.0f);
776     arrow[1] = FloatPoint(leftEdge + arrowWidth, centerY - arrowHeight / 2.0f);
777     arrow[2] = FloatPoint(leftEdge + arrowWidth / 2.0f, centerY + arrowHeight / 2.0f);
778
779     // Draw the arrow
780     paintInfo.context->drawConvexPolygon(3, arrow, true);
781
782     Color leftSeparatorColor(0, 0, 0, 40);
783     Color rightSeparatorColor(255, 255, 255, 40);
784     
785     // FIXME: Should the separator thickness and space be scaled up by fontScale?
786     int separatorSpace = 2;
787     int leftEdgeOfSeparator = static_cast<int>(leftEdge - arrowPaddingLeft); // FIXME: Round?
788
789     // Draw the separator to the left of the arrows
790     paintInfo.context->setStrokeThickness(1.0f);
791     paintInfo.context->setStrokeStyle(SolidStroke);
792     paintInfo.context->setStrokeColor(leftSeparatorColor);
793     paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()),
794                                 IntPoint(leftEdgeOfSeparator, bounds.bottom()));
795
796     paintInfo.context->setStrokeColor(rightSeparatorColor);
797     paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()),
798                                 IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.bottom()));
799
800     paintInfo.context->restore();
801     return false;
802 }
803
804 void RenderThemeSafari::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
805 {
806     NSControlSize controlSize = controlSizeForFont(style);
807
808     style->resetBorder();
809     style->resetPadding();
810     
811     // Height is locked to auto.
812     style->setHeight(Length(Auto));
813
814     // White-space is locked to pre
815     style->setWhiteSpace(PRE);
816
817     // Set the foreground color to black or gray when we have the aqua look.
818     // Cast to RGB32 is to work around a compiler bug.
819     style->setColor(e->isEnabled() ? static_cast<RGBA32>(Color::black) : Color::darkGray);
820
821     // Set the button's vertical size.
822     setButtonSize(style);
823
824     // 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
825     // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
826     // system font for the control size instead.
827     setFontFromControlSize(selector, style, controlSize);
828 }
829
830 int RenderThemeSafari::popupInternalPaddingLeft(RenderStyle* style) const
831 {
832     if (style->appearance() == MenulistAppearance)
833         return popupButtonPadding(controlSizeForFont(style))[leftPadding];
834     if (style->appearance() == MenulistButtonAppearance)
835         return styledPopupPaddingLeft;
836     return 0;
837 }
838
839 int RenderThemeSafari::popupInternalPaddingRight(RenderStyle* style) const
840 {
841     if (style->appearance() == MenulistAppearance)
842         return popupButtonPadding(controlSizeForFont(style))[rightPadding];
843     if (style->appearance() == MenulistButtonAppearance) {
844         float fontScale = style->fontSize() / baseFontSize;
845         float arrowWidth = baseArrowWidth * fontScale;
846         return static_cast<int>(ceilf(arrowWidth + arrowPaddingLeft + arrowPaddingRight + paddingBeforeSeparator));
847     }
848     return 0;
849 }
850
851 int RenderThemeSafari::popupInternalPaddingTop(RenderStyle* style) const
852 {
853     if (style->appearance() == MenulistAppearance)
854         return popupButtonPadding(controlSizeForFont(style))[topPadding];
855     if (style->appearance() == MenulistButtonAppearance)
856         return styledPopupPaddingTop;
857     return 0;
858 }
859
860 int RenderThemeSafari::popupInternalPaddingBottom(RenderStyle* style) const
861 {
862     if (style->appearance() == MenulistAppearance)
863         return popupButtonPadding(controlSizeForFont(style))[bottomPadding];
864     if (style->appearance() == MenulistButtonAppearance)
865         return styledPopupPaddingBottom;
866     return 0;
867 }
868
869 void RenderThemeSafari::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
870 {
871     float fontScale = style->fontSize() / baseFontSize;
872     
873     style->resetPadding();
874     style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up?
875
876     const int minHeight = 15;
877     style->setMinHeight(Length(minHeight, Fixed));
878     
879     style->setLineHeight(RenderStyle::initialLineHeight());
880 }
881
882 const IntSize* RenderThemeSafari::menuListSizes() const
883 {
884     static const IntSize sizes[3] = { IntSize(9, 0), IntSize(5, 0), IntSize(0, 0) };
885     return sizes;
886 }
887
888 int RenderThemeSafari::minimumMenuListSize(RenderStyle* style) const
889 {
890     return sizeForSystemFont(style, menuListSizes()).width();
891 }
892
893 const int trackWidth = 5;
894 const int trackRadius = 2;
895
896 bool RenderThemeSafari::paintSliderTrack(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
897 {
898     IntRect bounds = r;
899
900     if (o->style()->appearance() ==  SliderHorizontalAppearance) {
901         bounds.setHeight(trackWidth);
902         bounds.setY(r.y() + r.height() / 2 - trackWidth / 2);
903     } else if (o->style()->appearance() == SliderVerticalAppearance) {
904         bounds.setWidth(trackWidth);
905         bounds.setX(r.x() + r.width() / 2 - trackWidth / 2);
906     }
907
908     CGContextRef context = paintInfo.context->platformContext();
909     RetainPtr<CGColorSpaceRef> cspace(AdoptCF, CGColorSpaceCreateDeviceRGB());
910
911     paintInfo.context->save();
912     CGContextClipToRect(context, bounds);
913
914     struct CGFunctionCallbacks mainCallbacks = { 0, TrackGradientInterpolate, NULL };
915     RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
916     RetainPtr<CGShadingRef> mainShading;
917     if (o->style()->appearance() == SliderVerticalAppearance)
918         mainShading.adoptCF(CGShadingCreateAxial(cspace.get(), CGPointMake(bounds.x(),  bounds.bottom()), CGPointMake(bounds.right(), bounds.bottom()), mainFunction.get(), false, false));
919     else
920         mainShading.adoptCF(CGShadingCreateAxial(cspace.get(), CGPointMake(bounds.x(),  bounds.y()), CGPointMake(bounds.x(), bounds.bottom()), mainFunction.get(), false, false));
921
922     IntSize radius(trackRadius, trackRadius);
923     paintInfo.context->addRoundedRectClip(bounds,
924         radius, radius,
925         radius, radius);
926     CGContextDrawShading(context, mainShading.get());
927     paintInfo.context->restore();
928     
929     return false;
930 }
931
932 void RenderThemeSafari::adjustSliderThumbStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const 
933
934     style->setBoxShadow(0); 
935
936
937 const float verticalSliderHeightPadding = 0.1f;
938
939 bool RenderThemeSafari::paintSliderThumb(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
940 {
941     ASSERT(paintThemePart);
942
943     ASSERT(o->parent()->isSlider());
944
945     bool pressed = static_cast<RenderSlider*>(o->parent())->inDragMode();
946     ThemeControlState state = determineState(o->parent());
947     state &= ~SafariTheme::PressedState;
948     if (pressed)
949         state |= SafariTheme::PressedState;
950
951     paintThemePart(SliderThumbPart, paintInfo.context->platformContext(), r, NSSmallControlSize, state);
952     return false;
953 }
954
955 const int sliderThumbWidth = 15;
956 const int sliderThumbHeight = 15;
957
958 void RenderThemeSafari::adjustSliderThumbSize(RenderObject* o) const
959 {
960     if (o->style()->appearance() == SliderThumbHorizontalAppearance || o->style()->appearance() == SliderThumbVerticalAppearance) {
961         o->style()->setWidth(Length(sliderThumbWidth, Fixed));
962         o->style()->setHeight(Length(sliderThumbHeight, Fixed));
963     }
964 }
965
966 bool RenderThemeSafari::paintSearchField(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
967 {
968     ASSERT(paintThemePart);
969
970     paintThemePart(SearchFieldPart, paintInfo.context->platformContext(), r, controlSizeFromRect(r, searchFieldSizes()), determineState(o));
971     return false;
972 }
973
974 const IntSize* RenderThemeSafari::searchFieldSizes() const
975 {
976     static const IntSize sizes[3] = { IntSize(0, 22), IntSize(0, 19), IntSize(0, 15) };
977     return sizes;
978 }
979
980 void RenderThemeSafari::setSearchFieldSize(RenderStyle* style) const
981 {
982     // If the width and height are both specified, then we have nothing to do.
983     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
984         return;
985     
986     // Use the font size to determine the intrinsic width of the control.
987     setSizeFromFont(style, searchFieldSizes());
988 }
989
990 void RenderThemeSafari::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
991 {
992     // Override border.
993     style->resetBorder();
994     const short borderWidth = 2;
995     style->setBorderLeftWidth(borderWidth);
996     style->setBorderLeftStyle(INSET);
997     style->setBorderRightWidth(borderWidth);
998     style->setBorderRightStyle(INSET);
999     style->setBorderBottomWidth(borderWidth);
1000     style->setBorderBottomStyle(INSET);
1001     style->setBorderTopWidth(borderWidth);
1002     style->setBorderTopStyle(INSET);    
1003     
1004     // Override height.
1005     style->setHeight(Length(Auto));
1006     setSearchFieldSize(style);
1007     
1008     // Override padding size to match AppKit text positioning.
1009     const int padding = 1;
1010     style->setPaddingLeft(Length(padding, Fixed));
1011     style->setPaddingRight(Length(padding, Fixed));
1012     style->setPaddingTop(Length(padding, Fixed));
1013     style->setPaddingBottom(Length(padding, Fixed));
1014     
1015     NSControlSize controlSize = controlSizeForFont(style);
1016     setFontFromControlSize(selector, style, controlSize);
1017 }
1018
1019 bool RenderThemeSafari::paintSearchFieldCancelButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
1020 {
1021     ASSERT(paintThemePart);
1022
1023     Node* input = o->node()->shadowAncestorNode();
1024     ASSERT(input);
1025     RenderObject* renderer = input->renderer();
1026     ASSERT(renderer);
1027
1028     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1029
1030     paintThemePart(SearchFieldCancelButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1031     return false;
1032 }
1033
1034 const IntSize* RenderThemeSafari::cancelButtonSizes() const
1035 {
1036     static const IntSize sizes[3] = { IntSize(16, 13), IntSize(13, 11), IntSize(13, 9) };
1037     return sizes;
1038 }
1039
1040 void RenderThemeSafari::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1041 {
1042     IntSize size = sizeForSystemFont(style, cancelButtonSizes());
1043     style->setWidth(Length(size.width(), Fixed));
1044     style->setHeight(Length(size.height(), Fixed));
1045 }
1046
1047 const IntSize* RenderThemeSafari::resultsButtonSizes() const
1048 {
1049     static const IntSize sizes[3] = { IntSize(19, 13), IntSize(17, 11), IntSize(17, 9) };
1050     return sizes;
1051 }
1052
1053 const int emptyResultsOffset = 9;
1054 void RenderThemeSafari::adjustSearchFieldDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1055 {
1056     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1057     style->setWidth(Length(size.width() - emptyResultsOffset, Fixed));
1058     style->setHeight(Length(size.height(), Fixed));
1059 }
1060
1061 bool RenderThemeSafari::paintSearchFieldDecoration(RenderObject*, const RenderObject::PaintInfo&, const IntRect&)
1062 {
1063     return false;
1064 }
1065
1066 void RenderThemeSafari::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1067 {
1068     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1069     style->setWidth(Length(size.width(), Fixed));
1070     style->setHeight(Length(size.height(), Fixed));
1071 }
1072
1073 bool RenderThemeSafari::paintSearchFieldResultsDecoration(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
1074 {
1075     ASSERT(paintThemePart);
1076
1077     Node* input = o->node()->shadowAncestorNode();
1078     ASSERT(input);
1079     RenderObject* renderer = input->renderer();
1080     ASSERT(renderer);
1081
1082     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1083
1084     paintThemePart(SearchFieldResultsDecorationPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1085     return false;
1086 }
1087
1088 const int resultsArrowWidth = 5;
1089 void RenderThemeSafari::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1090 {
1091     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1092     style->setWidth(Length(size.width() + resultsArrowWidth, Fixed));
1093     style->setHeight(Length(size.height(), Fixed));
1094 }
1095
1096 bool RenderThemeSafari::paintSearchFieldResultsButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
1097 {
1098     ASSERT(paintThemePart);
1099
1100     Node* input = o->node()->shadowAncestorNode();
1101     ASSERT(input);
1102     RenderObject* renderer = input->renderer();
1103     ASSERT(renderer);
1104
1105     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1106
1107     paintThemePart(SearchFieldResultsButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1108     return false;
1109 }
1110
1111 } // namespace WebCore
1112
1113 #endif // defined(USE_SAFARI_THEME)