1 /* 2 * Copyright (C) 2007 Apple Inc. 3 * Copyright (C) 2007 Alp Toker <alp (at) atoker.com> 4 * Copyright (C) 2008 Collabora Ltd. 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., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25 #include "config.h" 26 #include "core/rendering/RenderThemeChromiumDefault.h" 27 28 #include "core/CSSValueKeywords.h" 29 #include "core/UserAgentStyleSheets.h" 30 #include "core/rendering/PaintInfo.h" 31 #include "core/rendering/RenderObject.h" 32 #include "core/rendering/RenderProgress.h" 33 #include "platform/LayoutTestSupport.h" 34 #include "platform/graphics/Color.h" 35 #include "platform/graphics/GraphicsContext.h" 36 #include "platform/graphics/GraphicsContextStateSaver.h" 37 #include "public/platform/Platform.h" 38 #include "public/platform/WebRect.h" 39 #include "public/platform/WebThemeEngine.h" 40 #include "wtf/StdLibExtras.h" 41 42 namespace blink { 43 44 static bool useMockTheme() 45 { 46 return LayoutTestSupport::isRunningLayoutTest(); 47 } 48 49 unsigned RenderThemeChromiumDefault::m_activeSelectionBackgroundColor = 50 0xff1e90ff; 51 unsigned RenderThemeChromiumDefault::m_activeSelectionForegroundColor = 52 Color::black; 53 unsigned RenderThemeChromiumDefault::m_inactiveSelectionBackgroundColor = 54 0xffc8c8c8; 55 unsigned RenderThemeChromiumDefault::m_inactiveSelectionForegroundColor = 56 0xff323232; 57 58 double RenderThemeChromiumDefault::m_caretBlinkInterval; 59 60 static const unsigned defaultButtonBackgroundColor = 0xffdddddd; 61 62 static WebThemeEngine::State getWebThemeState(const RenderTheme* theme, const RenderObject* o) 63 { 64 if (!theme->isEnabled(o)) 65 return WebThemeEngine::StateDisabled; 66 if (useMockTheme() && theme->isReadOnlyControl(o)) 67 return WebThemeEngine::StateReadonly; 68 if (theme->isPressed(o)) 69 return WebThemeEngine::StatePressed; 70 if (useMockTheme() && theme->isFocused(o)) 71 return WebThemeEngine::StateFocused; 72 if (theme->isHovered(o)) 73 return WebThemeEngine::StateHover; 74 75 return WebThemeEngine::StateNormal; 76 } 77 78 PassRefPtr<RenderTheme> RenderThemeChromiumDefault::create() 79 { 80 return adoptRef(new RenderThemeChromiumDefault()); 81 } 82 83 // RenderTheme::theme for Android is defined in RenderThemeChromiumAndroid.cpp. 84 #if !OS(ANDROID) 85 RenderTheme& RenderTheme::theme() 86 { 87 DEFINE_STATIC_REF(RenderTheme, renderTheme, (RenderThemeChromiumDefault::create())); 88 return *renderTheme; 89 } 90 #endif 91 92 RenderThemeChromiumDefault::RenderThemeChromiumDefault() 93 { 94 m_caretBlinkInterval = RenderTheme::caretBlinkInterval(); 95 } 96 97 RenderThemeChromiumDefault::~RenderThemeChromiumDefault() 98 { 99 } 100 101 bool RenderThemeChromiumDefault::supportsFocusRing(const RenderStyle* style) const 102 { 103 if (useMockTheme()) { 104 // Don't use focus rings for buttons when mocking controls. 105 return style->appearance() == ButtonPart 106 || style->appearance() == PushButtonPart 107 || style->appearance() == SquareButtonPart; 108 } 109 110 return RenderThemeChromiumSkia::supportsFocusRing(style); 111 } 112 113 Color RenderThemeChromiumDefault::systemColor(CSSValueID cssValueId) const 114 { 115 static const Color defaultButtonGrayColor(0xffdddddd); 116 static const Color defaultMenuColor(0xfff7f7f7); 117 118 if (cssValueId == CSSValueButtonface) { 119 if (useMockTheme()) 120 return Color(0xc0, 0xc0, 0xc0); 121 return defaultButtonGrayColor; 122 } 123 if (cssValueId == CSSValueMenu) 124 return defaultMenuColor; 125 return RenderTheme::systemColor(cssValueId); 126 } 127 128 String RenderThemeChromiumDefault::extraDefaultStyleSheet() 129 { 130 // FIXME: We should not have OS() branches here. 131 // We should have something like RenderThemeWin, RenderThemeLinux, or 132 // should concatenate UA stylesheets on build time. 133 #if !OS(WIN) 134 return RenderThemeChromiumSkia::extraDefaultStyleSheet() + 135 #if !OS(ANDROID) 136 String(themeInputMultipleFieldsCss, sizeof(themeInputMultipleFieldsCss)) + 137 #endif 138 String(themeChromiumLinuxCss, sizeof(themeChromiumLinuxCss)); 139 #else 140 return RenderThemeChromiumSkia::extraDefaultStyleSheet() + 141 String(themeInputMultipleFieldsCss, sizeof(themeInputMultipleFieldsCss)); 142 #endif 143 } 144 145 Color RenderThemeChromiumDefault::activeListBoxSelectionBackgroundColor() const 146 { 147 return Color(0x28, 0x28, 0x28); 148 } 149 150 Color RenderThemeChromiumDefault::activeListBoxSelectionForegroundColor() const 151 { 152 return Color::black; 153 } 154 155 Color RenderThemeChromiumDefault::inactiveListBoxSelectionBackgroundColor() const 156 { 157 return Color(0xc8, 0xc8, 0xc8); 158 } 159 160 Color RenderThemeChromiumDefault::inactiveListBoxSelectionForegroundColor() const 161 { 162 return Color(0x32, 0x32, 0x32); 163 } 164 165 Color RenderThemeChromiumDefault::platformActiveSelectionBackgroundColor() const 166 { 167 if (useMockTheme()) 168 return Color(0x00, 0x00, 0xff); // Royal blue. 169 return m_activeSelectionBackgroundColor; 170 } 171 172 Color RenderThemeChromiumDefault::platformInactiveSelectionBackgroundColor() const 173 { 174 if (useMockTheme()) 175 return Color(0x99, 0x99, 0x99); // Medium gray. 176 return m_inactiveSelectionBackgroundColor; 177 } 178 179 Color RenderThemeChromiumDefault::platformActiveSelectionForegroundColor() const 180 { 181 if (useMockTheme()) 182 return Color(0xff, 0xff, 0xcc); // Pale yellow. 183 return m_activeSelectionForegroundColor; 184 } 185 186 Color RenderThemeChromiumDefault::platformInactiveSelectionForegroundColor() const 187 { 188 if (useMockTheme()) 189 return Color::white; 190 return m_inactiveSelectionForegroundColor; 191 } 192 193 IntSize RenderThemeChromiumDefault::sliderTickSize() const 194 { 195 if (useMockTheme()) 196 return IntSize(1, 3); 197 return IntSize(1, 6); 198 } 199 200 int RenderThemeChromiumDefault::sliderTickOffsetFromTrackCenter() const 201 { 202 if (useMockTheme()) 203 return 11; 204 return -16; 205 } 206 207 void RenderThemeChromiumDefault::adjustSliderThumbSize(RenderStyle* style, Element* element) const 208 { 209 IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartSliderThumb); 210 211 // FIXME: Mock theme doesn't handle zoomed sliders. 212 float zoomLevel = useMockTheme() ? 1 : style->effectiveZoom(); 213 if (style->appearance() == SliderThumbHorizontalPart) { 214 style->setWidth(Length(size.width() * zoomLevel, Fixed)); 215 style->setHeight(Length(size.height() * zoomLevel, Fixed)); 216 } else if (style->appearance() == SliderThumbVerticalPart) { 217 style->setWidth(Length(size.height() * zoomLevel, Fixed)); 218 style->setHeight(Length(size.width() * zoomLevel, Fixed)); 219 } else 220 RenderThemeChromiumSkia::adjustSliderThumbSize(style, element); 221 } 222 223 void RenderThemeChromiumDefault::setCaretBlinkInterval(double interval) 224 { 225 m_caretBlinkInterval = interval; 226 } 227 228 double RenderThemeChromiumDefault::caretBlinkIntervalInternal() const 229 { 230 return m_caretBlinkInterval; 231 } 232 233 void RenderThemeChromiumDefault::setSelectionColors( 234 unsigned activeBackgroundColor, 235 unsigned activeForegroundColor, 236 unsigned inactiveBackgroundColor, 237 unsigned inactiveForegroundColor) 238 { 239 m_activeSelectionBackgroundColor = activeBackgroundColor; 240 m_activeSelectionForegroundColor = activeForegroundColor; 241 m_inactiveSelectionBackgroundColor = inactiveBackgroundColor; 242 m_inactiveSelectionForegroundColor = inactiveForegroundColor; 243 } 244 245 bool RenderThemeChromiumDefault::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& rect) 246 { 247 WebThemeEngine::ExtraParams extraParams; 248 WebCanvas* canvas = i.context->canvas(); 249 extraParams.button.checked = isChecked(o); 250 extraParams.button.indeterminate = isIndeterminate(o); 251 252 float zoomLevel = o->style()->effectiveZoom(); 253 GraphicsContextStateSaver stateSaver(*i.context, false); 254 IntRect unzoomedRect = rect; 255 if (zoomLevel != 1) { 256 stateSaver.save(); 257 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); 258 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); 259 i.context->translate(unzoomedRect.x(), unzoomedRect.y()); 260 i.context->scale(zoomLevel, zoomLevel); 261 i.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); 262 } 263 264 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartCheckbox, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams); 265 return false; 266 } 267 268 void RenderThemeChromiumDefault::setCheckboxSize(RenderStyle* style) const 269 { 270 // If the width and height are both specified, then we have nothing to do. 271 if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) 272 return; 273 274 IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartCheckbox); 275 float zoomLevel = style->effectiveZoom(); 276 size.setWidth(size.width() * zoomLevel); 277 size.setHeight(size.height() * zoomLevel); 278 setSizeIfAuto(style, size); 279 } 280 281 bool RenderThemeChromiumDefault::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& rect) 282 { 283 WebThemeEngine::ExtraParams extraParams; 284 WebCanvas* canvas = i.context->canvas(); 285 extraParams.button.checked = isChecked(o); 286 287 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartRadio, getWebThemeState(this, o), WebRect(rect), &extraParams); 288 return false; 289 } 290 291 void RenderThemeChromiumDefault::setRadioSize(RenderStyle* style) const 292 { 293 // If the width and height are both specified, then we have nothing to do. 294 if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) 295 return; 296 297 IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartRadio); 298 float zoomLevel = style->effectiveZoom(); 299 size.setWidth(size.width() * zoomLevel); 300 size.setHeight(size.height() * zoomLevel); 301 setSizeIfAuto(style, size); 302 } 303 304 bool RenderThemeChromiumDefault::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& rect) 305 { 306 WebThemeEngine::ExtraParams extraParams; 307 WebCanvas* canvas = i.context->canvas(); 308 extraParams.button.hasBorder = true; 309 extraParams.button.backgroundColor = useMockTheme() ? 0xffc0c0c0 : defaultButtonBackgroundColor; 310 if (o->hasBackground()) 311 extraParams.button.backgroundColor = o->resolveColor(CSSPropertyBackgroundColor).rgb(); 312 313 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartButton, getWebThemeState(this, o), WebRect(rect), &extraParams); 314 return false; 315 } 316 317 bool RenderThemeChromiumDefault::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& rect) 318 { 319 // WebThemeEngine does not handle border rounded corner and background image 320 // so return true to draw CSS border and background. 321 if (o->style()->hasBorderRadius() || o->style()->hasBackgroundImage()) 322 return true; 323 324 ControlPart part = o->style()->appearance(); 325 326 WebThemeEngine::ExtraParams extraParams; 327 extraParams.textField.isTextArea = part == TextAreaPart; 328 extraParams.textField.isListbox = part == ListboxPart; 329 330 WebCanvas* canvas = i.context->canvas(); 331 332 Color backgroundColor = o->resolveColor(CSSPropertyBackgroundColor); 333 extraParams.textField.backgroundColor = backgroundColor.rgb(); 334 335 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartTextField, getWebThemeState(this, o), WebRect(rect), &extraParams); 336 return false; 337 } 338 339 bool RenderThemeChromiumDefault::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& rect) 340 { 341 if (!o->isBox()) 342 return false; 343 344 const int right = rect.x() + rect.width(); 345 const int middle = rect.y() + rect.height() / 2; 346 347 WebThemeEngine::ExtraParams extraParams; 348 extraParams.menuList.arrowY = middle; 349 const RenderBox* box = toRenderBox(o); 350 // Match Chromium Win behaviour of showing all borders if any are shown. 351 extraParams.menuList.hasBorder = box->borderRight() || box->borderLeft() || box->borderTop() || box->borderBottom(); 352 extraParams.menuList.hasBorderRadius = o->style()->hasBorderRadius(); 353 // Fallback to transparent if the specified color object is invalid. 354 Color backgroundColor(Color::transparent); 355 if (o->hasBackground()) 356 backgroundColor = o->resolveColor(CSSPropertyBackgroundColor); 357 extraParams.menuList.backgroundColor = backgroundColor.rgb(); 358 359 // If we have a background image, don't fill the content area to expose the 360 // parent's background. Also, we shouldn't fill the content area if the 361 // alpha of the color is 0. The API of Windows GDI ignores the alpha. 362 // FIXME: the normal Aura theme doesn't care about this, so we should 363 // investigate if we really need fillContentArea. 364 extraParams.menuList.fillContentArea = !o->style()->hasBackgroundImage() && backgroundColor.alpha(); 365 366 if (useMockTheme()) { 367 // The size and position of the drop-down button is different between 368 // the mock theme and the regular aura theme. 369 int spacingTop = box->borderTop() + box->paddingTop(); 370 int spacingBottom = box->borderBottom() + box->paddingBottom(); 371 int spacingRight = box->borderRight() + box->paddingRight(); 372 extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 4 + spacingRight: right - 13 - spacingRight; 373 extraParams.menuList.arrowHeight = rect.height() - spacingBottom - spacingTop; 374 } else { 375 extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 7 : right - 13; 376 } 377 378 WebCanvas* canvas = i.context->canvas(); 379 380 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartMenuList, getWebThemeState(this, o), WebRect(rect), &extraParams); 381 return false; 382 } 383 384 bool RenderThemeChromiumDefault::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& rect) 385 { 386 if (!o->isBox()) 387 return false; 388 389 const int right = rect.x() + rect.width(); 390 const int middle = rect.y() + rect.height() / 2; 391 392 WebThemeEngine::ExtraParams extraParams; 393 extraParams.menuList.arrowY = middle; 394 extraParams.menuList.hasBorder = false; 395 extraParams.menuList.hasBorderRadius = o->style()->hasBorderRadius(); 396 extraParams.menuList.backgroundColor = Color::transparent; 397 extraParams.menuList.fillContentArea = false; 398 399 if (useMockTheme()) { 400 const RenderBox* box = toRenderBox(o); 401 // The size and position of the drop-down button is different between 402 // the mock theme and the regular aura theme. 403 int spacingTop = box->borderTop() + box->paddingTop(); 404 int spacingBottom = box->borderBottom() + box->paddingBottom(); 405 int spacingRight = box->borderRight() + box->paddingRight(); 406 extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 4 + spacingRight: right - 13 - spacingRight; 407 extraParams.menuList.arrowHeight = rect.height() - spacingBottom - spacingTop; 408 } else { 409 extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 7 : right - 13; 410 } 411 412 WebCanvas* canvas = i.context->canvas(); 413 414 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartMenuList, getWebThemeState(this, o), WebRect(rect), &extraParams); 415 return false; 416 } 417 418 bool RenderThemeChromiumDefault::paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& rect) 419 { 420 WebThemeEngine::ExtraParams extraParams; 421 WebCanvas* canvas = i.context->canvas(); 422 extraParams.slider.vertical = o->style()->appearance() == SliderVerticalPart; 423 424 paintSliderTicks(o, i, rect); 425 426 // FIXME: Mock theme doesn't handle zoomed sliders. 427 float zoomLevel = useMockTheme() ? 1 : o->style()->effectiveZoom(); 428 GraphicsContextStateSaver stateSaver(*i.context, false); 429 IntRect unzoomedRect = rect; 430 if (zoomLevel != 1) { 431 stateSaver.save(); 432 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); 433 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); 434 i.context->translate(unzoomedRect.x(), unzoomedRect.y()); 435 i.context->scale(zoomLevel, zoomLevel); 436 i.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); 437 } 438 439 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartSliderTrack, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams); 440 441 return false; 442 } 443 444 bool RenderThemeChromiumDefault::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& rect) 445 { 446 WebThemeEngine::ExtraParams extraParams; 447 WebCanvas* canvas = i.context->canvas(); 448 extraParams.slider.vertical = o->style()->appearance() == SliderThumbVerticalPart; 449 extraParams.slider.inDrag = isPressed(o); 450 451 // FIXME: Mock theme doesn't handle zoomed sliders. 452 float zoomLevel = useMockTheme() ? 1 : o->style()->effectiveZoom(); 453 GraphicsContextStateSaver stateSaver(*i.context, false); 454 IntRect unzoomedRect = rect; 455 if (zoomLevel != 1) { 456 stateSaver.save(); 457 unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel); 458 unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel); 459 i.context->translate(unzoomedRect.x(), unzoomedRect.y()); 460 i.context->scale(zoomLevel, zoomLevel); 461 i.context->translate(-unzoomedRect.x(), -unzoomedRect.y()); 462 } 463 464 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartSliderThumb, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams); 465 return false; 466 } 467 468 void RenderThemeChromiumDefault::adjustInnerSpinButtonStyle(RenderStyle* style, Element*) const 469 { 470 IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartInnerSpinButton); 471 472 style->setWidth(Length(size.width(), Fixed)); 473 style->setMinWidth(Length(size.width(), Fixed)); 474 } 475 476 bool RenderThemeChromiumDefault::paintInnerSpinButton(RenderObject* o, const PaintInfo& i, const IntRect& rect) 477 { 478 WebThemeEngine::ExtraParams extraParams; 479 WebCanvas* canvas = i.context->canvas(); 480 extraParams.innerSpin.spinUp = (controlStatesForRenderer(o) & SpinUpControlState); 481 extraParams.innerSpin.readOnly = isReadOnlyControl(o); 482 483 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartInnerSpinButton, getWebThemeState(this, o), WebRect(rect), &extraParams); 484 return false; 485 } 486 487 bool RenderThemeChromiumDefault::paintProgressBar(RenderObject* o, const PaintInfo& i, const IntRect& rect) 488 { 489 if (!o->isProgress()) 490 return true; 491 492 RenderProgress* renderProgress = toRenderProgress(o); 493 IntRect valueRect = progressValueRectFor(renderProgress, rect); 494 495 WebThemeEngine::ExtraParams extraParams; 496 extraParams.progressBar.determinate = renderProgress->isDeterminate(); 497 extraParams.progressBar.valueRectX = valueRect.x(); 498 extraParams.progressBar.valueRectY = valueRect.y(); 499 extraParams.progressBar.valueRectWidth = valueRect.width(); 500 extraParams.progressBar.valueRectHeight = valueRect.height(); 501 502 DirectionFlippingScope scope(o, i, rect); 503 WebCanvas* canvas = i.context->canvas(); 504 Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartProgressBar, getWebThemeState(this, o), WebRect(rect), &extraParams); 505 return false; 506 } 507 508 bool RenderThemeChromiumDefault::shouldOpenPickerWithF4Key() const 509 { 510 return true; 511 } 512 513 bool RenderThemeChromiumDefault::shouldUseFallbackTheme(RenderStyle* style) const 514 { 515 if (useMockTheme()) { 516 // The mock theme can't handle zoomed controls, so we fall back to the "fallback" theme. 517 ControlPart part = style->appearance(); 518 if (part == CheckboxPart || part == RadioPart) 519 return style->effectiveZoom() != 1; 520 } 521 return RenderTheme::shouldUseFallbackTheme(style); 522 } 523 524 } // namespace blink 525