1 /*
2 * This file is part of the WebKit project.
3 *
4 * Copyright (C) 2006 Apple Computer, Inc.
5 * Copyright (C) 2008, 2009 Google, Inc.
6 * Copyright (C) 2009 Kenneth Rohde Christiansen
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 *
23 */
24
25 #include "config.h"
26 #include "RenderThemeChromiumWin.h"
27
28 #include <windows.h>
29 #include <uxtheme.h>
30 #include <vssym32.h>
31
32 #include "ChromiumBridge.h"
33 #include "CSSValueKeywords.h"
34 #include "FontSelector.h"
35 #include "FontUtilsChromiumWin.h"
36 #include "GraphicsContext.h"
37 #include "HTMLMediaElement.h"
38 #include "HTMLNames.h"
39 #include "MediaControlElements.h"
40 #include "RenderBox.h"
41 #include "RenderSlider.h"
42 #include "ScrollbarTheme.h"
43 #include "TransparencyWin.h"
44 #include "WindowsVersion.h"
45
46 // FIXME: This dependency should eventually be removed.
47 #include <skia/ext/skia_utils_win.h>
48
49 #define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(structName, member) \
50 offsetof(structName, member) + \
51 (sizeof static_cast<structName*>(0)->member)
52 #define NONCLIENTMETRICS_SIZE_PRE_VISTA \
53 SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont)
54
55 namespace WebCore {
56
57 namespace {
58 class ThemePainter : public TransparencyWin {
59 public:
ThemePainter(GraphicsContext * context,const IntRect & r)60 ThemePainter(GraphicsContext* context, const IntRect& r)
61 {
62 TransformMode transformMode = getTransformMode(context->getCTM());
63 init(context, getLayerMode(context, transformMode), transformMode, r);
64 }
65
~ThemePainter()66 ~ThemePainter()
67 {
68 composite();
69 }
70
71 private:
canvasHasMultipleLayers(const SkCanvas * canvas)72 static bool canvasHasMultipleLayers(const SkCanvas* canvas)
73 {
74 SkCanvas::LayerIter iter(const_cast<SkCanvas*>(canvas), false);
75 iter.next(); // There is always at least one layer.
76 return !iter.done(); // There is > 1 layer if the the iterator can stil advance.
77 }
78
getLayerMode(GraphicsContext * context,TransformMode transformMode)79 static LayerMode getLayerMode(GraphicsContext* context, TransformMode transformMode)
80 {
81 if (context->platformContext()->isDrawingToImageBuffer()) // Might have transparent background.
82 return WhiteLayer;
83 else if (canvasHasMultipleLayers(context->platformContext()->canvas())) // Needs antialiasing help.
84 return OpaqueCompositeLayer;
85 else // Nothing interesting.
86 return transformMode == KeepTransform ? NoLayer : OpaqueCompositeLayer;
87 }
88
getTransformMode(const TransformationMatrix & matrix)89 static TransformMode getTransformMode(const TransformationMatrix& matrix)
90 {
91 if (matrix.b() != 0 || matrix.c() != 0) // Skew.
92 return Untransform;
93 else if (matrix.a() != 1.0 || matrix.d() != 1.0) // Scale.
94 return ScaleTransform;
95 else // Nothing interesting.
96 return KeepTransform;
97 }
98 };
99
100 } // namespace
101
getNonClientMetrics(NONCLIENTMETRICS * metrics)102 static void getNonClientMetrics(NONCLIENTMETRICS* metrics)
103 {
104 static UINT size = WebCore::isVistaOrNewer() ?
105 sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA;
106 metrics->cbSize = size;
107 bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, size, metrics, 0);
108 ASSERT(success);
109 }
110
111 static FontDescription smallSystemFont;
112 static FontDescription menuFont;
113 static FontDescription labelFont;
114
115 // Internal static helper functions. We don't put them in an anonymous
116 // namespace so they have easier access to the WebCore namespace.
117
supportsFocus(ControlPart appearance)118 static bool supportsFocus(ControlPart appearance)
119 {
120 switch (appearance) {
121 case PushButtonPart:
122 case ButtonPart:
123 case DefaultButtonPart:
124 case SearchFieldPart:
125 case TextFieldPart:
126 case TextAreaPart:
127 return true;
128 }
129 return false;
130 }
131
132 // Return the height of system font |font| in pixels. We use this size by
133 // default for some non-form-control elements.
systemFontSize(const LOGFONT & font)134 static float systemFontSize(const LOGFONT& font)
135 {
136 float size = -font.lfHeight;
137 if (size < 0) {
138 HFONT hFont = CreateFontIndirect(&font);
139 if (hFont) {
140 HDC hdc = GetDC(0); // What about printing? Is this the right DC?
141 if (hdc) {
142 HGDIOBJ hObject = SelectObject(hdc, hFont);
143 TEXTMETRIC tm;
144 GetTextMetrics(hdc, &tm);
145 SelectObject(hdc, hObject);
146 ReleaseDC(0, hdc);
147 size = tm.tmAscent;
148 }
149 DeleteObject(hFont);
150 }
151 }
152
153 // The "codepage 936" bit here is from Gecko; apparently this helps make
154 // fonts more legible in Simplified Chinese where the default font size is
155 // too small.
156 //
157 // FIXME: http://b/1119883 Since this is only used for "small caption",
158 // "menu", and "status bar" objects, I'm not sure how much this even
159 // matters. Plus the Gecko patch went in back in 2002, and maybe this
160 // isn't even relevant anymore. We should investigate whether this should
161 // be removed, or perhaps broadened to be "any CJK locale".
162 //
163 return ((size < 12.0f) && (GetACP() == 936)) ? 12.0f : size;
164 }
165
166 // Converts |points| to pixels. One point is 1/72 of an inch.
pointsToPixels(float points)167 static float pointsToPixels(float points)
168 {
169 static float pixelsPerInch = 0.0f;
170 if (!pixelsPerInch) {
171 HDC hdc = GetDC(0); // What about printing? Is this the right DC?
172 if (hdc) { // Can this ever actually be NULL?
173 pixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSY);
174 ReleaseDC(0, hdc);
175 } else {
176 pixelsPerInch = 96.0f;
177 }
178 }
179
180 static const float pointsPerInch = 72.0f;
181 return points / pointsPerInch * pixelsPerInch;
182 }
183
querySystemBlinkInterval(double defaultInterval)184 static double querySystemBlinkInterval(double defaultInterval)
185 {
186 UINT blinkTime = GetCaretBlinkTime();
187 if (blinkTime == 0)
188 return defaultInterval;
189 if (blinkTime == INFINITE)
190 return 0;
191 return blinkTime / 1000.0;
192 }
193
create()194 PassRefPtr<RenderTheme> RenderThemeChromiumWin::create()
195 {
196 return adoptRef(new RenderThemeChromiumWin);
197 }
198
themeForPage(Page * page)199 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
200 {
201 static RenderTheme* rt = RenderThemeChromiumWin::create().releaseRef();
202 return rt;
203 }
204
supportsFocusRing(const RenderStyle * style) const205 bool RenderThemeChromiumWin::supportsFocusRing(const RenderStyle* style) const
206 {
207 // Let webkit draw one of its halo rings around any focused element,
208 // except push buttons. For buttons we use the windows PBS_DEFAULTED
209 // styling to give it a blue border.
210 return style->appearance() == ButtonPart
211 || style->appearance() == PushButtonPart;
212 }
213
platformActiveSelectionBackgroundColor() const214 Color RenderThemeChromiumWin::platformActiveSelectionBackgroundColor() const
215 {
216 if (ChromiumBridge::layoutTestMode())
217 return Color(0x00, 0x00, 0xff); // Royal blue.
218 COLORREF color = GetSysColor(COLOR_HIGHLIGHT);
219 return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff);
220 }
221
platformInactiveSelectionBackgroundColor() const222 Color RenderThemeChromiumWin::platformInactiveSelectionBackgroundColor() const
223 {
224 if (ChromiumBridge::layoutTestMode())
225 return Color(0x99, 0x99, 0x99); // Medium gray.
226 COLORREF color = GetSysColor(COLOR_GRAYTEXT);
227 return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff);
228 }
229
platformActiveSelectionForegroundColor() const230 Color RenderThemeChromiumWin::platformActiveSelectionForegroundColor() const
231 {
232 if (ChromiumBridge::layoutTestMode())
233 return Color(0xff, 0xff, 0xcc); // Pale yellow.
234 COLORREF color = GetSysColor(COLOR_HIGHLIGHTTEXT);
235 return Color(GetRValue(color), GetGValue(color), GetBValue(color), 0xff);
236 }
237
platformInactiveSelectionForegroundColor() const238 Color RenderThemeChromiumWin::platformInactiveSelectionForegroundColor() const
239 {
240 return Color::white;
241 }
242
platformActiveTextSearchHighlightColor() const243 Color RenderThemeChromiumWin::platformActiveTextSearchHighlightColor() const
244 {
245 return Color(0xff, 0x96, 0x32); // Orange.
246 }
247
platformInactiveTextSearchHighlightColor() const248 Color RenderThemeChromiumWin::platformInactiveTextSearchHighlightColor() const
249 {
250 return Color(0xff, 0xff, 0x96); // Yellow.
251 }
252
systemFont(int propId,FontDescription & fontDescription) const253 void RenderThemeChromiumWin::systemFont(int propId, FontDescription& fontDescription) const
254 {
255 // This logic owes much to RenderThemeSafari.cpp.
256 FontDescription* cachedDesc = 0;
257 AtomicString faceName;
258 float fontSize = 0;
259 switch (propId) {
260 case CSSValueSmallCaption:
261 cachedDesc = &smallSystemFont;
262 if (!smallSystemFont.isAbsoluteSize()) {
263 NONCLIENTMETRICS metrics;
264 getNonClientMetrics(&metrics);
265 faceName = AtomicString(metrics.lfSmCaptionFont.lfFaceName, wcslen(metrics.lfSmCaptionFont.lfFaceName));
266 fontSize = systemFontSize(metrics.lfSmCaptionFont);
267 }
268 break;
269 case CSSValueMenu:
270 cachedDesc = &menuFont;
271 if (!menuFont.isAbsoluteSize()) {
272 NONCLIENTMETRICS metrics;
273 getNonClientMetrics(&metrics);
274 faceName = AtomicString(metrics.lfMenuFont.lfFaceName, wcslen(metrics.lfMenuFont.lfFaceName));
275 fontSize = systemFontSize(metrics.lfMenuFont);
276 }
277 break;
278 case CSSValueStatusBar:
279 cachedDesc = &labelFont;
280 if (!labelFont.isAbsoluteSize()) {
281 NONCLIENTMETRICS metrics;
282 getNonClientMetrics(&metrics);
283 faceName = metrics.lfStatusFont.lfFaceName;
284 fontSize = systemFontSize(metrics.lfStatusFont);
285 }
286 break;
287 case CSSValueWebkitMiniControl:
288 case CSSValueWebkitSmallControl:
289 case CSSValueWebkitControl:
290 faceName = defaultGUIFont();
291 // Why 2 points smaller? Because that's what Gecko does.
292 fontSize = defaultFontSize - pointsToPixels(2);
293 break;
294 default:
295 faceName = defaultGUIFont();
296 fontSize = defaultFontSize;
297 break;
298 }
299
300 if (!cachedDesc)
301 cachedDesc = &fontDescription;
302
303 if (fontSize) {
304 cachedDesc->firstFamily().setFamily(faceName);
305 cachedDesc->setIsAbsoluteSize(true);
306 cachedDesc->setGenericFamily(FontDescription::NoFamily);
307 cachedDesc->setSpecifiedSize(fontSize);
308 cachedDesc->setWeight(FontWeightNormal);
309 cachedDesc->setItalic(false);
310 }
311 fontDescription = *cachedDesc;
312 }
313
adjustSliderThumbSize(RenderObject * o) const314 void RenderThemeChromiumWin::adjustSliderThumbSize(RenderObject* o) const
315 {
316 // These sizes match what WinXP draws for various menus.
317 const int sliderThumbAlongAxis = 11;
318 const int sliderThumbAcrossAxis = 21;
319 if (o->style()->appearance() == SliderThumbHorizontalPart) {
320 o->style()->setWidth(Length(sliderThumbAlongAxis, Fixed));
321 o->style()->setHeight(Length(sliderThumbAcrossAxis, Fixed));
322 } else if (o->style()->appearance() == SliderThumbVerticalPart) {
323 o->style()->setWidth(Length(sliderThumbAcrossAxis, Fixed));
324 o->style()->setHeight(Length(sliderThumbAlongAxis, Fixed));
325 } else
326 RenderThemeChromiumSkia::adjustSliderThumbSize(o);
327 }
328
paintCheckbox(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)329 bool RenderThemeChromiumWin::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
330 {
331 return paintButton(o, i, r);
332 }
paintRadio(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)333 bool RenderThemeChromiumWin::paintRadio(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
334 {
335 return paintButton(o, i, r);
336 }
337
paintButton(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)338 bool RenderThemeChromiumWin::paintButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
339 {
340 const ThemeData& themeData = getThemeData(o);
341
342 WebCore::ThemePainter painter(i.context, r);
343 ChromiumBridge::paintButton(painter.context(),
344 themeData.m_part,
345 themeData.m_state,
346 themeData.m_classicState,
347 painter.drawRect());
348 return false;
349 }
350
paintTextField(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)351 bool RenderThemeChromiumWin::paintTextField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
352 {
353 return paintTextFieldInternal(o, i, r, true);
354 }
355
paintSliderTrack(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)356 bool RenderThemeChromiumWin::paintSliderTrack(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
357 {
358 const ThemeData& themeData = getThemeData(o);
359
360 WebCore::ThemePainter painter(i.context, r);
361 ChromiumBridge::paintTrackbar(painter.context(),
362 themeData.m_part,
363 themeData.m_state,
364 themeData.m_classicState,
365 painter.drawRect());
366 return false;
367 }
368
paintSliderThumb(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)369 bool RenderThemeChromiumWin::paintSliderThumb(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
370 {
371 return paintSliderTrack(o, i, r);
372 }
373
374 // Used to paint unstyled menulists (i.e. with the default border)
paintMenuList(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r)375 bool RenderThemeChromiumWin::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
376 {
377 if (!o->isBox())
378 return false;
379
380 const RenderBox* box = toRenderBox(o);
381 int borderRight = box->borderRight();
382 int borderLeft = box->borderLeft();
383 int borderTop = box->borderTop();
384 int borderBottom = box->borderBottom();
385
386 // If all the borders are 0, then tell skia not to paint the border on the
387 // textfield. FIXME: http://b/1210017 Figure out how to get Windows to not
388 // draw individual borders and then pass that to skia so we can avoid
389 // drawing any borders that are set to 0. For non-zero borders, we draw the
390 // border, but webkit just draws over it.
391 bool drawEdges = !(borderRight == 0 && borderLeft == 0 && borderTop == 0 && borderBottom == 0);
392
393 paintTextFieldInternal(o, i, r, drawEdges);
394
395 // Take padding and border into account. If the MenuList is smaller than
396 // the size of a button, make sure to shrink it appropriately and not put
397 // its x position to the left of the menulist.
398 const int buttonWidth = GetSystemMetrics(SM_CXVSCROLL);
399 int spacingLeft = borderLeft + box->paddingLeft();
400 int spacingRight = borderRight + box->paddingRight();
401 int spacingTop = borderTop + box->paddingTop();
402 int spacingBottom = borderBottom + box->paddingBottom();
403
404 int buttonX;
405 if (r.right() - r.x() < buttonWidth)
406 buttonX = r.x();
407 else
408 buttonX = o->style()->direction() == LTR ? r.right() - spacingRight - buttonWidth : r.x() + spacingLeft;
409
410 // Compute the rectangle of the button in the destination image.
411 IntRect rect(buttonX,
412 r.y() + spacingTop,
413 std::min(buttonWidth, r.right() - r.x()),
414 r.height() - (spacingTop + spacingBottom));
415
416 // Get the correct theme data for a textfield and paint the menu.
417 WebCore::ThemePainter painter(i.context, rect);
418 ChromiumBridge::paintMenuList(painter.context(),
419 CP_DROPDOWNBUTTON,
420 determineState(o),
421 determineClassicState(o),
422 painter.drawRect());
423 return false;
424 }
425
426 // static
setDefaultFontSize(int fontSize)427 void RenderThemeChromiumWin::setDefaultFontSize(int fontSize)
428 {
429 RenderThemeChromiumSkia::setDefaultFontSize(fontSize);
430
431 // Reset cached fonts.
432 smallSystemFont = menuFont = labelFont = FontDescription();
433 }
434
caretBlinkIntervalInternal() const435 double RenderThemeChromiumWin::caretBlinkIntervalInternal() const
436 {
437 // This involves a system call, so we cache the result.
438 static double blinkInterval = querySystemBlinkInterval(RenderTheme::caretBlinkInterval());
439 return blinkInterval;
440 }
441
determineState(RenderObject * o)442 unsigned RenderThemeChromiumWin::determineState(RenderObject* o)
443 {
444 unsigned result = TS_NORMAL;
445 ControlPart appearance = o->style()->appearance();
446 if (!isEnabled(o))
447 result = TS_DISABLED;
448 else if (isReadOnlyControl(o) && (TextFieldPart == appearance || TextAreaPart == appearance || SearchFieldPart == appearance))
449 result = ETS_READONLY; // Readonly is supported on textfields.
450 else if (isPressed(o)) // Active overrides hover and focused.
451 result = TS_PRESSED;
452 else if (supportsFocus(appearance) && isFocused(o))
453 result = ETS_FOCUSED;
454 else if (isHovered(o))
455 result = TS_HOT;
456 if (isChecked(o))
457 result += 4; // 4 unchecked states, 4 checked states.
458 return result;
459 }
460
determineSliderThumbState(RenderObject * o)461 unsigned RenderThemeChromiumWin::determineSliderThumbState(RenderObject* o)
462 {
463 unsigned result = TUS_NORMAL;
464 if (!isEnabled(o->parent()))
465 result = TUS_DISABLED;
466 else if (supportsFocus(o->style()->appearance()) && isFocused(o->parent()))
467 result = TUS_FOCUSED;
468 else if (toRenderSlider(o->parent())->inDragMode())
469 result = TUS_PRESSED;
470 else if (isHovered(o))
471 result = TUS_HOT;
472 return result;
473 }
474
determineClassicState(RenderObject * o)475 unsigned RenderThemeChromiumWin::determineClassicState(RenderObject* o)
476 {
477 unsigned result = 0;
478 if (!isEnabled(o))
479 result = DFCS_INACTIVE;
480 else if (isPressed(o)) // Active supersedes hover
481 result = DFCS_PUSHED;
482 else if (isHovered(o))
483 result = DFCS_HOT;
484 if (isChecked(o))
485 result |= DFCS_CHECKED;
486 return result;
487 }
488
getThemeData(RenderObject * o)489 ThemeData RenderThemeChromiumWin::getThemeData(RenderObject* o)
490 {
491 ThemeData result;
492 switch (o->style()->appearance()) {
493 case CheckboxPart:
494 result.m_part = BP_CHECKBOX;
495 result.m_state = determineState(o);
496 result.m_classicState = DFCS_BUTTONCHECK;
497 break;
498 case RadioPart:
499 result.m_part = BP_RADIOBUTTON;
500 result.m_state = determineState(o);
501 result.m_classicState = DFCS_BUTTONRADIO;
502 break;
503 case PushButtonPart:
504 case ButtonPart:
505 result.m_part = BP_PUSHBUTTON;
506 result.m_state = determineState(o);
507 result.m_classicState = DFCS_BUTTONPUSH;
508 break;
509 case SliderHorizontalPart:
510 result.m_part = TKP_TRACK;
511 result.m_state = TRS_NORMAL;
512 break;
513 case SliderVerticalPart:
514 result.m_part = TKP_TRACKVERT;
515 result.m_state = TRVS_NORMAL;
516 break;
517 case SliderThumbHorizontalPart:
518 result.m_part = TKP_THUMBBOTTOM;
519 result.m_state = determineSliderThumbState(o);
520 break;
521 case SliderThumbVerticalPart:
522 result.m_part = TKP_THUMBVERT;
523 result.m_state = determineSliderThumbState(o);
524 break;
525 case ListboxPart:
526 case MenulistPart:
527 case SearchFieldPart:
528 case TextFieldPart:
529 case TextAreaPart:
530 result.m_part = EP_EDITTEXT;
531 result.m_state = determineState(o);
532 break;
533 }
534
535 result.m_classicState |= determineClassicState(o);
536
537 return result;
538 }
539
paintTextFieldInternal(RenderObject * o,const RenderObject::PaintInfo & i,const IntRect & r,bool drawEdges)540 bool RenderThemeChromiumWin::paintTextFieldInternal(RenderObject* o,
541 const RenderObject::PaintInfo& i,
542 const IntRect& r,
543 bool drawEdges)
544 {
545 // Nasty hack to make us not paint the border on text fields with a
546 // border-radius. Webkit paints elements with border-radius for us.
547 // FIXME: Get rid of this if-check once we can properly clip rounded
548 // borders: http://b/1112604 and http://b/1108635
549 // FIXME: make sure we do the right thing if css background-clip is set.
550 if (o->style()->hasBorderRadius())
551 return false;
552
553 const ThemeData& themeData = getThemeData(o);
554
555 // Fallback to white if the specified color object is invalid.
556 Color backgroundColor(Color::white);
557 if (o->style()->backgroundColor().isValid()) {
558 backgroundColor = o->style()->backgroundColor();
559 }
560
561 // If we have background-image, don't fill the content area to expose the
562 // parent's background. Also, we shouldn't fill the content area if the
563 // alpha of the color is 0. The API of Windows GDI ignores the alpha.
564 //
565 // Note that we should paint the content area white if we have neither the
566 // background color nor background image explicitly specified to keep the
567 // appearance of select element consistent with other browsers.
568 bool fillContentArea = !o->style()->hasBackgroundImage() && backgroundColor.alpha() != 0;
569
570 WebCore::ThemePainter painter(i.context, r);
571 ChromiumBridge::paintTextField(painter.context(),
572 themeData.m_part,
573 themeData.m_state,
574 themeData.m_classicState,
575 painter.drawRect(),
576 backgroundColor,
577 fillContentArea,
578 drawEdges);
579 return false;
580 }
581
582 } // namespace WebCore
583