99500de3b7e59c48c5316f340016596de35a540f
[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 (style->appearance() == TextFieldAppearance || style->appearance() == TextAreaAppearance || style->appearance() == ListboxAppearance)
201         return style->border() != border;
202     return RenderTheme::isControlStyled(style, border, background, backgroundColor);
203 }
204
205 void RenderThemeSafari::adjustRepaintRect(const RenderObject* o, IntRect& r)
206 {
207     NSControlSize controlSize = controlSizeForFont(o->style());
208
209     switch (o->style()->appearance()) {
210         case CheckboxAppearance: {
211             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
212             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
213             r = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize));
214             break;
215         }
216         case RadioAppearance: {
217             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
218             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
219             r = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize));
220             break;
221         }
222         case PushButtonAppearance:
223         case ButtonAppearance: {
224             // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox
225             // shadow" and the check.  We don't consider this part of the bounds of the control in WebKit.
226             if (r.height() <= buttonSizes()[NSRegularControlSize].height())
227                 r = inflateRect(r, buttonSizes()[controlSize], buttonMargins(controlSize));
228             break;
229         }
230         case MenulistAppearance: {
231             r = inflateRect(r, popupButtonSizes()[controlSize], popupButtonMargins(controlSize));
232             break;
233         }
234         default:
235             break;
236     }
237 }
238
239 IntRect RenderThemeSafari::inflateRect(const IntRect& r, const IntSize& size, const int* margins) const
240 {
241     // Only do the inflation if the available width/height are too small.  Otherwise try to
242     // fit the glow/check space into the available box's width/height.
243     int widthDelta = r.width() - (size.width() + margins[leftMargin] + margins[rightMargin]);
244     int heightDelta = r.height() - (size.height() + margins[topMargin] + margins[bottomMargin]);
245     IntRect result(r);
246     if (widthDelta < 0) {
247         result.setX(result.x() - margins[leftMargin]);
248         result.setWidth(result.width() - widthDelta);
249     }
250     if (heightDelta < 0) {
251         result.setY(result.y() - margins[topMargin]);
252         result.setHeight(result.height() - heightDelta);
253     }
254     return result;
255 }
256
257 short RenderThemeSafari::baselinePosition(const RenderObject* o) const
258 {
259     if (o->style()->appearance() == CheckboxAppearance || o->style()->appearance() == RadioAppearance)
260         return o->marginTop() + o->height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
261     return RenderTheme::baselinePosition(o);
262 }
263
264 bool RenderThemeSafari::controlSupportsTints(const RenderObject* o) const
265 {
266     if (!isEnabled(o))
267         return false;
268
269     // Checkboxes only have tint when checked.
270     if (o->style()->appearance() == CheckboxAppearance)
271         return isChecked(o);
272
273     // For now assume other controls have tint if enabled.
274     return true;
275 }
276
277 NSControlSize RenderThemeSafari::controlSizeForFont(RenderStyle* style) const
278 {
279     int fontSize = style->fontSize();
280     if (fontSize >= 16)
281         return NSRegularControlSize;
282     if (fontSize >= 11)
283         return NSSmallControlSize;
284     return NSMiniControlSize;
285 }
286 /*
287 void RenderThemeSafari::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize)
288 {
289     NSControlSize size;
290     if (minSize.width() >= sizes[NSRegularControlSize].width() &&
291         minSize.height() >= sizes[NSRegularControlSize].height())
292         size = NSRegularControlSize;
293     else if (minSize.width() >= sizes[NSSmallControlSize].width() &&
294              minSize.height() >= sizes[NSSmallControlSize].height())
295         size = NSSmallControlSize;
296     else
297         size = NSMiniControlSize;
298     if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
299         [cell setControlSize:size];
300 }
301 */
302 IntSize RenderThemeSafari::sizeForFont(RenderStyle* style, const IntSize* sizes) const
303 {
304     return sizes[controlSizeForFont(style)];
305 }
306
307 IntSize RenderThemeSafari::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const
308 {
309     return sizes[controlSizeForSystemFont(style)];
310 }
311
312 void RenderThemeSafari::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const
313 {
314     // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
315     IntSize size = sizeForFont(style, sizes);
316     if (style->width().isIntrinsicOrAuto() && size.width() > 0)
317         style->setWidth(Length(size.width(), Fixed));
318     if (style->height().isAuto() && size.height() > 0)
319         style->setHeight(Length(size.height(), Fixed));
320 }
321
322 void RenderThemeSafari::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const
323 {
324     FontDescription fontDescription;
325     fontDescription.setIsAbsoluteSize(true);
326     fontDescription.setGenericFamily(FontDescription::SerifFamily);
327
328     float fontSize = systemFontSizeForControlSize(controlSize);
329     fontDescription.firstFamily().setFamily("Lucida Grande");
330     fontDescription.setComputedSize(fontSize);
331     fontDescription.setSpecifiedSize(fontSize);
332
333     // Reset line height
334     style->setLineHeight(RenderStyle::initialLineHeight());
335
336     if (style->setFontDescription(fontDescription))
337         style->font().update(selector->fontSelector());
338 }
339
340 NSControlSize RenderThemeSafari::controlSizeForSystemFont(RenderStyle* style) const
341 {
342     int fontSize = style->fontSize();
343     if (fontSize >= 13)
344         return NSRegularControlSize;
345     if (fontSize >= 11)
346         return NSSmallControlSize;
347     return NSMiniControlSize;
348 }
349
350 bool RenderThemeSafari::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
351 {
352     NSControlSize controlSize = controlSizeForFont(o->style());
353
354     IntRect inflatedRect = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize));  
355     paintThemePart(CheckboxPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
356
357     return false;
358 }
359
360 const IntSize* RenderThemeSafari::checkboxSizes() const
361 {
362     static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
363     return sizes;
364 }
365
366 const int* RenderThemeSafari::checkboxMargins(NSControlSize controlSize) const
367 {
368     static const int margins[3][4] =
369     {
370         { 2, 2, 2, 2 },
371         { 2, 2, 2, 1 },
372         { 1, 0, 0, 0 },
373     };
374     return margins[controlSize];
375 }
376
377 void RenderThemeSafari::setCheckboxSize(RenderStyle* style) const
378 {
379     // If the width and height are both specified, then we have nothing to do.
380     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
381         return;
382
383     // Use the font size to determine the intrinsic width of the control.
384     setSizeFromFont(style, checkboxSizes());
385 }
386
387 bool RenderThemeSafari::paintRadio(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
388 {
389     NSControlSize controlSize = controlSizeForFont(o->style());
390  
391     IntRect inflatedRect = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize));    
392     paintThemePart(RadioButtonPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
393
394     return false;
395 }
396
397 const IntSize* RenderThemeSafari::radioSizes() const
398 {
399     static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
400     return sizes;
401 }
402
403 const int* RenderThemeSafari::radioMargins(NSControlSize controlSize) const
404 {
405     static const int margins[3][4] =
406     {
407         { 1, 2, 2, 2 },
408         { 0, 1, 2, 1 },
409         { 0, 0, 1, 0 },
410      };
411     return margins[controlSize];
412 }
413
414 void RenderThemeSafari::setRadioSize(RenderStyle* style) const
415 {
416     // If the width and height are both specified, then we have nothing to do.
417     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
418         return;
419
420     // Use the font size to determine the intrinsic width of the control.
421     setSizeFromFont(style, radioSizes());
422 }
423
424 void RenderThemeSafari::setButtonPaddingFromControlSize(RenderStyle* style, NSControlSize size) const
425 {
426     // Just use 8px.  AppKit wants to use 11px for mini buttons, but that padding is just too large
427     // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is
428     // by definition constrained, since we select mini only for small cramped environments.
429     // This also guarantees the HTML4 <button> will match our rendering by default, since we're using a consistent
430     // padding.
431     const int padding = 8;
432     style->setPaddingLeft(Length(padding, Fixed));
433     style->setPaddingRight(Length(padding, Fixed));
434     style->setPaddingTop(Length(0, Fixed));
435     style->setPaddingBottom(Length(0, Fixed));
436 }
437
438 void RenderThemeSafari::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
439 {
440     // There are three appearance constants for buttons.
441     // (1) Push-button is the constant for the default Aqua system button.  Push buttons will not scale vertically and will not allow
442     // custom fonts or colors.  <input>s use this constant.  This button will allow custom colors and font weights/variants but won't
443     // scale vertically.
444     // (2) square-button is the constant for the square button.  This button will allow custom fonts and colors and will scale vertically.
445     // (3) Button is the constant that means "pick the best button as appropriate."  <button>s use this constant.  This button will
446     // also scale vertically and allow custom fonts and colors.  It will attempt to use Aqua if possible and will make this determination
447     // solely on the rectangle of the control.
448
449     // Determine our control size based off our font.
450     NSControlSize controlSize = controlSizeForFont(style);
451
452     if (style->appearance() == PushButtonAppearance) {
453         // Ditch the border.
454         style->resetBorder();
455
456         // Height is locked to auto.
457         style->setHeight(Length(Auto));
458
459         // White-space is locked to pre
460         style->setWhiteSpace(PRE);
461
462         // Set the button's vertical size.
463         setButtonSize(style);
464
465         // Add in the padding that we'd like to use.
466         setButtonPaddingFromControlSize(style, controlSize);
467
468         // 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
469         // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
470         // system font for the control size instead.
471         setFontFromControlSize(selector, style, controlSize);
472     } else {
473         // Set a min-height so that we can't get smaller than the mini button.
474         style->setMinHeight(Length(15, Fixed));
475
476         // Reset the top and bottom borders.
477         style->resetBorderTop();
478         style->resetBorderBottom();
479     }
480 }
481
482 const IntSize* RenderThemeSafari::buttonSizes() const
483 {
484     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
485     return sizes;
486 }
487
488 const int* RenderThemeSafari::buttonMargins(NSControlSize controlSize) const
489 {
490     static const int margins[3][4] =
491     {
492         { 4, 6, 7, 6 },
493         { 4, 5, 6, 5 },
494         { 0, 1, 1, 1 },
495     };
496     return margins[controlSize];
497 }
498
499 void RenderThemeSafari::setButtonSize(RenderStyle* style) const
500 {
501     // If the width and height are both specified, then we have nothing to do.
502     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
503         return;
504
505     // Use the font size to determine the intrinsic width of the control.
506     setSizeFromFont(style, buttonSizes());
507 }
508
509 bool RenderThemeSafari::paintButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
510 {
511     // We inflate the rect as needed to account for padding included in the cell to accommodate the button
512     // shadow.  We don't consider this part of the bounds of the control in WebKit.
513
514     NSControlSize controlSize = controlSizeFromRect(r, buttonSizes());
515     IntRect inflatedRect = r;
516
517     ThemePart part;
518     if (r.height() <= buttonSizes()[NSRegularControlSize].height()) {
519         // Push button
520         part = PushButtonPart;
521
522         IntSize size = buttonSizes()[controlSize];
523         size.setWidth(r.width());
524
525         // Center the button within the available space.
526         if (inflatedRect.height() > size.height()) {
527             inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - size.height()) / 2);
528             inflatedRect.setHeight(size.height());
529         }
530
531         // Now inflate it to account for the shadow.
532         inflatedRect = inflateRect(inflatedRect, size, buttonMargins(controlSize));
533     } else
534         part = SquareButtonPart;
535
536     paintThemePart(part, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o));
537     return false;
538 }
539
540 bool RenderThemeSafari::paintTextField(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
541 {
542     paintThemePart(TextFieldPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState);
543     return false;
544 }
545
546 void RenderThemeSafari::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const
547 {
548 }
549
550 bool RenderThemeSafari::paintCapsLockIndicator(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
551 {    
552 #if defined(SAFARI_THEME_VERSION) && SAFARI_THEME_VERSION >= 1
553     if (paintInfo.context->paintingDisabled())
554         return true;
555
556     paintThemePart(CapsLockPart, paintInfo.context->platformContext(), r, (NSControlSize)0, (ThemeControlState)0);
557
558     return false;
559 #else
560     return true;
561 #endif
562 }
563
564 bool RenderThemeSafari::paintTextArea(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
565 {
566     paintThemePart(TextAreaPart, paintInfo.context->platformContext(), r, (NSControlSize)0, determineState(o) & ~FocusedState);
567     return false;
568 }
569
570 void RenderThemeSafari::adjustTextAreaStyle(CSSStyleSelector*, RenderStyle*, Element*) const
571 {
572 }
573
574 const int* RenderThemeSafari::popupButtonMargins(NSControlSize size) const
575 {
576     static const int margins[3][4] =
577     {
578         { 2, 3, 3, 3 },
579         { 1, 3, 3, 3 },
580         { 0, 1, 0, 1 }
581     };
582     return margins[size];
583 }
584
585 const IntSize* RenderThemeSafari::popupButtonSizes() const
586 {
587     static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
588     return sizes;
589 }
590
591 const int* RenderThemeSafari::popupButtonPadding(NSControlSize size) const
592 {
593     static const int padding[3][4] =
594     {
595         { 2, 26, 3, 8 },
596         { 2, 23, 3, 8 },
597         { 2, 22, 3, 10 }
598     };
599     return padding[size];
600 }
601
602 bool RenderThemeSafari::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& info, const IntRect& r)
603 {
604     NSControlSize controlSize = controlSizeFromRect(r, popupButtonSizes());
605     IntRect inflatedRect = r;
606     IntSize size = popupButtonSizes()[controlSize];
607     size.setWidth(r.width());
608
609     // Now inflate it to account for the shadow.
610     if (r.width() >= minimumMenuListSize(o->style()))
611         inflatedRect = inflateRect(inflatedRect, size, popupButtonMargins(controlSize));
612
613     paintThemePart(DropDownButtonPart, info.context->platformContext(), inflatedRect, controlSize, determineState(o));
614
615     return false;
616 }
617
618 const float baseFontSize = 11.0f;
619 const float baseArrowHeight = 5.0f;
620 const float baseArrowWidth = 7.0f;
621 const int arrowPaddingLeft = 5;
622 const int arrowPaddingRight = 5;
623 const int paddingBeforeSeparator = 4;
624 const int baseBorderRadius = 5;
625 const int styledPopupPaddingLeft = 8;
626 const int styledPopupPaddingTop = 1;
627 const int styledPopupPaddingBottom = 2;
628
629 static void TopGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
630 {
631     static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.4f };
632     static float light[4] = { 1.0f, 1.0f, 1.0f, 0.15f };
633     float a = inData[0];
634     int i = 0;
635     for (i = 0; i < 4; i++)
636         outData[i] = (1.0f - a) * dark[i] + a * light[i];
637 }
638
639 static void BottomGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
640 {
641     static float dark[4] = { 1.0f, 1.0f, 1.0f, 0.0f };
642     static float light[4] = { 1.0f, 1.0f, 1.0f, 0.3f };
643     float a = inData[0];
644     int i = 0;
645     for (i = 0; i < 4; i++)
646         outData[i] = (1.0f - a) * dark[i] + a * light[i];
647 }
648
649 static void MainGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
650 {
651     static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.15f };
652     static float light[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
653     float a = inData[0];
654     int i = 0;
655     for (i = 0; i < 4; i++)
656         outData[i] = (1.0f - a) * dark[i] + a * light[i];
657 }
658
659 static void TrackGradientInterpolate(void* info, const CGFloat* inData, CGFloat* outData)
660 {
661     static float dark[4] = { 0.0f, 0.0f, 0.0f, 0.678f };
662     static float light[4] = { 0.0f, 0.0f, 0.0f, 0.13f };
663     float a = inData[0];
664     int i = 0;
665     for (i = 0; i < 4; i++)
666         outData[i] = (1.0f - a) * dark[i] + a * light[i];
667 }
668
669 void RenderThemeSafari::paintMenuListButtonGradients(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
670 {
671     CGContextRef context = paintInfo.context->platformContext();
672
673     paintInfo.context->save();
674
675     int radius = o->style()->borderTopLeftRadius().width();
676
677     RetainPtr<CGColorSpaceRef> cspace(AdoptCF, CGColorSpaceCreateDeviceRGB());
678
679     FloatRect topGradient(r.x(), r.y(), r.width(), r.height() / 2.0f);
680     struct CGFunctionCallbacks topCallbacks = { 0, TopGradientInterpolate, NULL };
681     RetainPtr<CGFunctionRef> topFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &topCallbacks));
682     RetainPtr<CGShadingRef> topShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(topGradient.x(), topGradient.y()), CGPointMake(topGradient.x(), topGradient.bottom()), topFunction.get(), false, false));
683
684     FloatRect bottomGradient(r.x() + radius, r.y() + r.height() / 2.0f, r.width() - 2.0f * radius, r.height() / 2.0f);
685     struct CGFunctionCallbacks bottomCallbacks = { 0, BottomGradientInterpolate, NULL };
686     RetainPtr<CGFunctionRef> bottomFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &bottomCallbacks));
687     RetainPtr<CGShadingRef> bottomShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(bottomGradient.x(),  bottomGradient.y()), CGPointMake(bottomGradient.x(), bottomGradient.bottom()), bottomFunction.get(), false, false));
688
689     struct CGFunctionCallbacks mainCallbacks = { 0, MainGradientInterpolate, NULL };
690     RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
691     RetainPtr<CGShadingRef> mainShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.x(),  r.y()), CGPointMake(r.x(), r.bottom()), mainFunction.get(), false, false));
692
693     RetainPtr<CGShadingRef> leftShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.x(),  r.y()), CGPointMake(r.x() + radius, r.y()), mainFunction.get(), false, false));
694
695     RetainPtr<CGShadingRef> rightShading(AdoptCF, CGShadingCreateAxial(cspace.get(), CGPointMake(r.right(),  r.y()), CGPointMake(r.right() - radius, r.y()), mainFunction.get(), false, false));
696     paintInfo.context->save();
697     CGContextClipToRect(context, r);
698     paintInfo.context->addRoundedRectClip(r,
699         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
700         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
701     CGContextDrawShading(context, mainShading.get());
702     paintInfo.context->restore();
703
704     paintInfo.context->save();
705     CGContextClipToRect(context, topGradient);
706     paintInfo.context->addRoundedRectClip(enclosingIntRect(topGradient),
707         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
708         IntSize(), IntSize());
709     CGContextDrawShading(context, topShading.get());
710     paintInfo.context->restore();
711
712     paintInfo.context->save();
713     CGContextClipToRect(context, bottomGradient);
714     paintInfo.context->addRoundedRectClip(enclosingIntRect(bottomGradient),
715         IntSize(), IntSize(),
716         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
717     CGContextDrawShading(context, bottomShading.get());
718     paintInfo.context->restore();
719
720     paintInfo.context->save();
721     CGContextClipToRect(context, r);
722     paintInfo.context->addRoundedRectClip(r,
723         o->style()->borderTopLeftRadius(), o->style()->borderTopRightRadius(),
724         o->style()->borderBottomLeftRadius(), o->style()->borderBottomRightRadius());
725     CGContextDrawShading(context, leftShading.get());
726     CGContextDrawShading(context, rightShading.get());
727     paintInfo.context->restore();
728
729     paintInfo.context->restore();
730 }
731
732 bool RenderThemeSafari::paintMenuListButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
733 {
734     paintInfo.context->save();
735
736     IntRect bounds = IntRect(r.x() + o->style()->borderLeftWidth(),
737                              r.y() + o->style()->borderTopWidth(),
738                              r.width() - o->style()->borderLeftWidth() - o->style()->borderRightWidth(),
739                              r.height() - o->style()->borderTopWidth() - o->style()->borderBottomWidth());
740     // Draw the gradients to give the styled popup menu a button appearance
741     paintMenuListButtonGradients(o, paintInfo, bounds);
742     
743     // 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
744     float fontScale = min(o->style()->fontSize() / baseFontSize, bounds.height() / baseArrowHeight);
745     float centerY = bounds.y() + bounds.height() / 2.0f;
746     float arrowHeight = baseArrowHeight * fontScale;
747     float arrowWidth = baseArrowWidth * fontScale;
748     float leftEdge = bounds.right() - arrowPaddingRight - arrowWidth;
749
750     if (bounds.width() < arrowWidth + arrowPaddingLeft)
751         return false;
752
753     paintInfo.context->setFillColor(o->style()->color());
754     paintInfo.context->setStrokeColor(NoStroke);
755
756     FloatPoint arrow[3];
757     arrow[0] = FloatPoint(leftEdge, centerY - arrowHeight / 2.0f);
758     arrow[1] = FloatPoint(leftEdge + arrowWidth, centerY - arrowHeight / 2.0f);
759     arrow[2] = FloatPoint(leftEdge + arrowWidth / 2.0f, centerY + arrowHeight / 2.0f);
760
761     // Draw the arrow
762     paintInfo.context->drawConvexPolygon(3, arrow, true);
763
764     Color leftSeparatorColor(0, 0, 0, 40);
765     Color rightSeparatorColor(255, 255, 255, 40);
766     
767     // FIXME: Should the separator thickness and space be scaled up by fontScale?
768     int separatorSpace = 2;
769     int leftEdgeOfSeparator = static_cast<int>(leftEdge - arrowPaddingLeft); // FIXME: Round?
770
771     // Draw the separator to the left of the arrows
772     paintInfo.context->setStrokeThickness(1.0f);
773     paintInfo.context->setStrokeStyle(SolidStroke);
774     paintInfo.context->setStrokeColor(leftSeparatorColor);
775     paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator, bounds.y()),
776                                 IntPoint(leftEdgeOfSeparator, bounds.bottom()));
777
778     paintInfo.context->setStrokeColor(rightSeparatorColor);
779     paintInfo.context->drawLine(IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.y()),
780                                 IntPoint(leftEdgeOfSeparator + separatorSpace, bounds.bottom()));
781
782     paintInfo.context->restore();
783     return false;
784 }
785
786 void RenderThemeSafari::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
787 {
788     NSControlSize controlSize = controlSizeForFont(style);
789
790     style->resetBorder();
791     style->resetPadding();
792     
793     // Height is locked to auto.
794     style->setHeight(Length(Auto));
795
796     // White-space is locked to pre
797     style->setWhiteSpace(PRE);
798
799     // Set the foreground color to black or gray when we have the aqua look.
800     // Cast to RGB32 is to work around a compiler bug.
801     style->setColor(e->isEnabled() ? static_cast<RGBA32>(Color::black) : Color::darkGray);
802
803     // Set the button's vertical size.
804     setButtonSize(style);
805
806     // 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
807     // a reasonable control size, but once that control size is determined, we throw that font away and use the appropriate
808     // system font for the control size instead.
809     setFontFromControlSize(selector, style, controlSize);
810 }
811
812 int RenderThemeSafari::popupInternalPaddingLeft(RenderStyle* style) const
813 {
814     if (style->appearance() == MenulistAppearance)
815         return popupButtonPadding(controlSizeForFont(style))[leftPadding];
816     if (style->appearance() == MenulistButtonAppearance)
817         return styledPopupPaddingLeft;
818     return 0;
819 }
820
821 int RenderThemeSafari::popupInternalPaddingRight(RenderStyle* style) const
822 {
823     if (style->appearance() == MenulistAppearance)
824         return popupButtonPadding(controlSizeForFont(style))[rightPadding];
825     if (style->appearance() == MenulistButtonAppearance) {
826         float fontScale = style->fontSize() / baseFontSize;
827         float arrowWidth = baseArrowWidth * fontScale;
828         return static_cast<int>(ceilf(arrowWidth + arrowPaddingLeft + arrowPaddingRight + paddingBeforeSeparator));
829     }
830     return 0;
831 }
832
833 int RenderThemeSafari::popupInternalPaddingTop(RenderStyle* style) const
834 {
835     if (style->appearance() == MenulistAppearance)
836         return popupButtonPadding(controlSizeForFont(style))[topPadding];
837     if (style->appearance() == MenulistButtonAppearance)
838         return styledPopupPaddingTop;
839     return 0;
840 }
841
842 int RenderThemeSafari::popupInternalPaddingBottom(RenderStyle* style) const
843 {
844     if (style->appearance() == MenulistAppearance)
845         return popupButtonPadding(controlSizeForFont(style))[bottomPadding];
846     if (style->appearance() == MenulistButtonAppearance)
847         return styledPopupPaddingBottom;
848     return 0;
849 }
850
851 void RenderThemeSafari::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
852 {
853     float fontScale = style->fontSize() / baseFontSize;
854     
855     style->resetPadding();
856     style->setBorderRadius(IntSize(int(baseBorderRadius + fontScale - 1), int(baseBorderRadius + fontScale - 1))); // FIXME: Round up?
857
858     const int minHeight = 15;
859     style->setMinHeight(Length(minHeight, Fixed));
860     
861     style->setLineHeight(RenderStyle::initialLineHeight());
862 }
863
864 const IntSize* RenderThemeSafari::menuListSizes() const
865 {
866     static const IntSize sizes[3] = { IntSize(9, 0), IntSize(5, 0), IntSize(0, 0) };
867     return sizes;
868 }
869
870 int RenderThemeSafari::minimumMenuListSize(RenderStyle* style) const
871 {
872     return sizeForSystemFont(style, menuListSizes()).width();
873 }
874
875 const int trackWidth = 5;
876 const int trackRadius = 2;
877
878 bool RenderThemeSafari::paintSliderTrack(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
879 {
880     IntRect bounds = r;
881
882     if (o->style()->appearance() ==  SliderHorizontalAppearance) {
883         bounds.setHeight(trackWidth);
884         bounds.setY(r.y() + r.height() / 2 - trackWidth / 2);
885     } else if (o->style()->appearance() == SliderVerticalAppearance) {
886         bounds.setWidth(trackWidth);
887         bounds.setX(r.x() + r.width() / 2 - trackWidth / 2);
888     }
889
890     CGContextRef context = paintInfo.context->platformContext();
891     RetainPtr<CGColorSpaceRef> cspace(AdoptCF, CGColorSpaceCreateDeviceRGB());
892
893     paintInfo.context->save();
894     CGContextClipToRect(context, bounds);
895
896     struct CGFunctionCallbacks mainCallbacks = { 0, TrackGradientInterpolate, NULL };
897     RetainPtr<CGFunctionRef> mainFunction(AdoptCF, CGFunctionCreate(NULL, 1, NULL, 4, NULL, &mainCallbacks));
898     RetainPtr<CGShadingRef> mainShading;
899     if (o->style()->appearance() == SliderVerticalAppearance)
900         mainShading.adoptCF(CGShadingCreateAxial(cspace.get(), CGPointMake(bounds.x(),  bounds.bottom()), CGPointMake(bounds.right(), bounds.bottom()), mainFunction.get(), false, false));
901     else
902         mainShading.adoptCF(CGShadingCreateAxial(cspace.get(), CGPointMake(bounds.x(),  bounds.y()), CGPointMake(bounds.x(), bounds.bottom()), mainFunction.get(), false, false));
903
904     IntSize radius(trackRadius, trackRadius);
905     paintInfo.context->addRoundedRectClip(bounds,
906         radius, radius,
907         radius, radius);
908     CGContextDrawShading(context, mainShading.get());
909     paintInfo.context->restore();
910     
911     return false;
912 }
913
914 void RenderThemeSafari::adjustSliderThumbStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const 
915
916     style->setBoxShadow(0); 
917
918
919 const float verticalSliderHeightPadding = 0.1f;
920
921 bool RenderThemeSafari::paintSliderThumb(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
922 {
923     ASSERT(o->parent()->isSlider());
924
925     bool pressed = static_cast<RenderSlider*>(o->parent())->inDragMode();
926     ThemeControlState state = determineState(o->parent());
927     state &= ~SafariTheme::PressedState;
928     if (pressed)
929         state |= SafariTheme::PressedState;
930
931     paintThemePart(SliderThumbPart, paintInfo.context->platformContext(), r, NSSmallControlSize, state);
932     return false;
933 }
934
935 const int sliderThumbWidth = 15;
936 const int sliderThumbHeight = 15;
937
938 void RenderThemeSafari::adjustSliderThumbSize(RenderObject* o) const
939 {
940     if (o->style()->appearance() == SliderThumbHorizontalAppearance || o->style()->appearance() == SliderThumbVerticalAppearance) {
941         o->style()->setWidth(Length(sliderThumbWidth, Fixed));
942         o->style()->setHeight(Length(sliderThumbHeight, Fixed));
943     }
944 }
945
946 bool RenderThemeSafari::paintSearchField(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect& r)
947 {
948     paintThemePart(SearchFieldPart, paintInfo.context->platformContext(), r, controlSizeFromRect(r, searchFieldSizes()), determineState(o));
949     return false;
950 }
951
952 const IntSize* RenderThemeSafari::searchFieldSizes() const
953 {
954     static const IntSize sizes[3] = { IntSize(0, 22), IntSize(0, 19), IntSize(0, 15) };
955     return sizes;
956 }
957
958 void RenderThemeSafari::setSearchFieldSize(RenderStyle* style) const
959 {
960     // If the width and height are both specified, then we have nothing to do.
961     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
962         return;
963     
964     // Use the font size to determine the intrinsic width of the control.
965     setSizeFromFont(style, searchFieldSizes());
966 }
967
968 void RenderThemeSafari::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
969 {
970     // Override border.
971     style->resetBorder();
972     const short borderWidth = 2;
973     style->setBorderLeftWidth(borderWidth);
974     style->setBorderLeftStyle(INSET);
975     style->setBorderRightWidth(borderWidth);
976     style->setBorderRightStyle(INSET);
977     style->setBorderBottomWidth(borderWidth);
978     style->setBorderBottomStyle(INSET);
979     style->setBorderTopWidth(borderWidth);
980     style->setBorderTopStyle(INSET);    
981     
982     // Override height.
983     style->setHeight(Length(Auto));
984     setSearchFieldSize(style);
985     
986     // Override padding size to match AppKit text positioning.
987     const int padding = 1;
988     style->setPaddingLeft(Length(padding, Fixed));
989     style->setPaddingRight(Length(padding, Fixed));
990     style->setPaddingTop(Length(padding, Fixed));
991     style->setPaddingBottom(Length(padding, Fixed));
992     
993     NSControlSize controlSize = controlSizeForFont(style);
994     setFontFromControlSize(selector, style, controlSize);
995 }
996
997 bool RenderThemeSafari::paintSearchFieldCancelButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
998 {
999     Node* input = o->node()->shadowAncestorNode();
1000     ASSERT(input);
1001     RenderObject* renderer = input->renderer();
1002     ASSERT(renderer);
1003
1004     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1005
1006     paintThemePart(SearchFieldCancelButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1007     return false;
1008 }
1009
1010 const IntSize* RenderThemeSafari::cancelButtonSizes() const
1011 {
1012     static const IntSize sizes[3] = { IntSize(16, 13), IntSize(13, 11), IntSize(13, 9) };
1013     return sizes;
1014 }
1015
1016 void RenderThemeSafari::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1017 {
1018     IntSize size = sizeForSystemFont(style, cancelButtonSizes());
1019     style->setWidth(Length(size.width(), Fixed));
1020     style->setHeight(Length(size.height(), Fixed));
1021 }
1022
1023 const IntSize* RenderThemeSafari::resultsButtonSizes() const
1024 {
1025     static const IntSize sizes[3] = { IntSize(19, 13), IntSize(17, 11), IntSize(17, 9) };
1026     return sizes;
1027 }
1028
1029 const int emptyResultsOffset = 9;
1030 void RenderThemeSafari::adjustSearchFieldDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1031 {
1032     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1033     style->setWidth(Length(size.width() - emptyResultsOffset, Fixed));
1034     style->setHeight(Length(size.height(), Fixed));
1035 }
1036
1037 bool RenderThemeSafari::paintSearchFieldDecoration(RenderObject*, const RenderObject::PaintInfo&, const IntRect&)
1038 {
1039     return false;
1040 }
1041
1042 void RenderThemeSafari::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1043 {
1044     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1045     style->setWidth(Length(size.width(), Fixed));
1046     style->setHeight(Length(size.height(), Fixed));
1047 }
1048
1049 bool RenderThemeSafari::paintSearchFieldResultsDecoration(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
1050 {
1051     Node* input = o->node()->shadowAncestorNode();
1052     ASSERT(input);
1053     RenderObject* renderer = input->renderer();
1054     ASSERT(renderer);
1055
1056     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1057
1058     paintThemePart(SearchFieldResultsDecorationPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1059     return false;
1060 }
1061
1062 const int resultsArrowWidth = 5;
1063 void RenderThemeSafari::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
1064 {
1065     IntSize size = sizeForSystemFont(style, resultsButtonSizes());
1066     style->setWidth(Length(size.width() + resultsArrowWidth, Fixed));
1067     style->setHeight(Length(size.height(), Fixed));
1068 }
1069
1070 bool RenderThemeSafari::paintSearchFieldResultsButton(RenderObject* o, const RenderObject::PaintInfo& paintInfo, const IntRect&)
1071 {
1072     Node* input = o->node()->shadowAncestorNode();
1073     ASSERT(input);
1074     RenderObject* renderer = input->renderer();
1075     ASSERT(renderer);
1076
1077     IntRect searchRect = renderer->absoluteBoundingBoxRect();
1078
1079     paintThemePart(SearchFieldResultsButtonPart, paintInfo.context->platformContext(), searchRect, controlSizeFromRect(searchRect, searchFieldSizes()), determineState(o));
1080     return false;
1081 }
1082
1083 } // namespace WebCore
1084
1085 #endif // defined(USE_SAFARI_THEME)