/* * Copyright (C) 2007, 2008, 2009 Apple Inc. * Copyright (C) 2009 Kenneth Rohde Christiansen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #include "RenderThemeSafari.h" #include "RenderThemeWin.h" #include "Settings.h" #if USE(SAFARI_THEME) #include "CSSValueKeywords.h" #include "Document.h" #include "Element.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsContextCG.h" #include "HTMLInputElement.h" #include "HTMLMediaElement.h" #include "HTMLNames.h" #include "PaintInfo.h" #include "RenderMediaControls.h" #include "RenderSlider.h" #include "RenderView.h" #include "RetainPtr.h" #include "SoftLinking.h" #include "cssstyleselector.h" #include using std::min; // FIXME: The platform-independent code in this class should be factored out and merged with RenderThemeMac. namespace WebCore { using namespace HTMLNames; using namespace SafariTheme; enum { topMargin, rightMargin, bottomMargin, leftMargin }; enum { topPadding, rightPadding, bottomPadding, leftPadding }; PassRefPtr RenderThemeSafari::create() { return adoptRef(new RenderThemeSafari); } PassRefPtr RenderTheme::themeForPage(Page* page) { static RenderTheme* safariTheme = RenderThemeSafari::create().releaseRef(); static RenderTheme* windowsTheme = RenderThemeWin::create().releaseRef(); // FIXME: This is called before Settings has been initialized by WebKit, so will return a // potentially wrong answer the very first time it's called (see // ). if (Settings::shouldPaintNativeControls()) { RenderTheme::setCustomFocusRingColor(safariTheme->platformFocusRingColor()); return windowsTheme; // keep the reference of one. } return safariTheme; // keep the reference of one. } #ifdef DEBUG_ALL SOFT_LINK_DEBUG_LIBRARY(SafariTheme) #else SOFT_LINK_LIBRARY(SafariTheme) #endif SOFT_LINK(SafariTheme, paintThemePart, void, __stdcall, (ThemePart part, CGContextRef context, const CGRect& rect, NSControlSize size, ThemeControlState state), (part, context, rect, size, state)) #if defined(SAFARI_THEME_VERSION) && SAFARI_THEME_VERSION >= 2 SOFT_LINK(SafariTheme, STPaintProgressIndicator, void, APIENTRY, (ProgressIndicatorType type, CGContextRef context, const CGRect& rect, NSControlSize size, ThemeControlState state, float value), (type, context, rect, size, state, value)) #endif SOFT_LINK_OPTIONAL(SafariTheme, STCopyThemeColor, CGColorRef, APIENTRY, (unsigned color, SafariTheme::ThemeControlState)); static const unsigned stFocusRingColorID = 4; static const unsigned aquaFocusRingColor = 0xFF7DADD9; static RGBA32 makeRGBAFromCGColor(CGColorRef color) { const CGFloat* components = CGColorGetComponents(color); return makeRGBA(255 * components[0], 255 * components[1], 255 * components[2], 255 * components[3]); } ThemeControlState RenderThemeSafari::determineState(RenderObject* o) const { ThemeControlState result = 0; if (isActive(o)) result |= SafariTheme::ActiveState; if (isEnabled(o) && !isReadOnlyControl(o)) result |= SafariTheme::EnabledState; if (isPressed(o)) result |= SafariTheme::PressedState; if (isChecked(o)) result |= SafariTheme::CheckedState; if (isIndeterminate(o)) result |= SafariTheme::IndeterminateCheckedState; if (isFocused(o)) result |= SafariTheme::FocusedState; if (isDefault(o)) result |= SafariTheme::DefaultState; return result; } static NSControlSize controlSizeFromRect(const IntRect& rect, const IntSize sizes[]) { if (sizes[NSRegularControlSize].height() == rect.height()) return NSRegularControlSize; else if (sizes[NSMiniControlSize].height() == rect.height()) return NSMiniControlSize; return NSSmallControlSize; } RenderThemeSafari::RenderThemeSafari() { } RenderThemeSafari::~RenderThemeSafari() { } Color RenderThemeSafari::platformActiveSelectionBackgroundColor() const { return Color(181, 213, 255); } Color RenderThemeSafari::platformInactiveSelectionBackgroundColor() const { return Color(212, 212, 212); } Color RenderThemeSafari::activeListBoxSelectionBackgroundColor() const { // FIXME: This should probably just be a darker version of the platformActiveSelectionBackgroundColor return Color(56, 117, 215); } Color RenderThemeSafari::platformFocusRingColor() const { static Color focusRingColor; if (!focusRingColor.isValid()) { if (STCopyThemeColorPtr()) { RetainPtr color(AdoptCF, STCopyThemeColorPtr()(stFocusRingColorID, SafariTheme::ActiveState)); focusRingColor = makeRGBAFromCGColor(color.get()); } if (!focusRingColor.isValid()) focusRingColor = aquaFocusRingColor; } return focusRingColor; } static float systemFontSizeForControlSize(NSControlSize controlSize) { static float sizes[] = { 13.0f, 11.0f, 9.0f }; return sizes[controlSize]; } void RenderThemeSafari::systemFont(int propId, FontDescription& fontDescription) const { static FontDescription systemFont; static FontDescription smallSystemFont; static FontDescription menuFont; static FontDescription labelFont; static FontDescription miniControlFont; static FontDescription smallControlFont; static FontDescription controlFont; FontDescription* cachedDesc; float fontSize = 0; switch (propId) { case CSSValueSmallCaption: cachedDesc = &smallSystemFont; if (!smallSystemFont.isAbsoluteSize()) fontSize = systemFontSizeForControlSize(NSSmallControlSize); break; case CSSValueMenu: cachedDesc = &menuFont; if (!menuFont.isAbsoluteSize()) fontSize = systemFontSizeForControlSize(NSRegularControlSize); break; case CSSValueStatusBar: cachedDesc = &labelFont; if (!labelFont.isAbsoluteSize()) fontSize = 10.0f; break; case CSSValueWebkitMiniControl: cachedDesc = &miniControlFont; if (!miniControlFont.isAbsoluteSize()) fontSize = systemFontSizeForControlSize(NSMiniControlSize); break; case CSSValueWebkitSmallControl: cachedDesc = &smallControlFont; if (!smallControlFont.isAbsoluteSize()) fontSize = systemFontSizeForControlSize(NSSmallControlSize); break; case CSSValueWebkitControl: cachedDesc = &controlFont; if (!controlFont.isAbsoluteSize()) fontSize = systemFontSizeForControlSize(NSRegularControlSize); break; default: cachedDesc = &systemFont; if (!systemFont.isAbsoluteSize()) fontSize = 13.0f; } if (fontSize) { cachedDesc->setIsAbsoluteSize(true); cachedDesc->setGenericFamily(FontDescription::NoFamily); cachedDesc->firstFamily().setFamily("Lucida Grande"); cachedDesc->setSpecifiedSize(fontSize); cachedDesc->setWeight(FontWeightNormal); cachedDesc->setItalic(false); } fontDescription = *cachedDesc; } bool RenderThemeSafari::isControlStyled(const RenderStyle* style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const { // If we didn't find SafariTheme.dll we won't be able to paint any themed controls. if (!SafariThemeLibrary()) return true; if (style->appearance() == TextFieldPart || style->appearance() == TextAreaPart || style->appearance() == ListboxPart) return style->border() != border; return RenderTheme::isControlStyled(style, border, background, backgroundColor); } void RenderThemeSafari::adjustRepaintRect(const RenderObject* o, IntRect& r) { NSControlSize controlSize = controlSizeForFont(o->style()); switch (o->style()->appearance()) { case CheckboxPart: { // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. r = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize)); break; } case RadioPart: { // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. r = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize)); break; } case PushButtonPart: case DefaultButtonPart: case ButtonPart: { // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. if (r.height() <= buttonSizes()[NSRegularControlSize].height()) r = inflateRect(r, buttonSizes()[controlSize], buttonMargins(controlSize)); break; } case MenulistPart: { r = inflateRect(r, popupButtonSizes()[controlSize], popupButtonMargins(controlSize)); break; } default: break; } } IntRect RenderThemeSafari::inflateRect(const IntRect& r, const IntSize& size, const int* margins) const { // Only do the inflation if the available width/height are too small. Otherwise try to // fit the glow/check space into the available box's width/height. int widthDelta = r.width() - (size.width() + margins[leftMargin] + margins[rightMargin]); int heightDelta = r.height() - (size.height() + margins[topMargin] + margins[bottomMargin]); IntRect result(r); if (widthDelta < 0) { result.setX(result.x() - margins[leftMargin]); result.setWidth(result.width() - widthDelta); } if (heightDelta < 0) { result.setY(result.y() - margins[topMargin]); result.setHeight(result.height() - heightDelta); } return result; } int RenderThemeSafari::baselinePosition(const RenderObject* o) const { if (!o->isBox()) return 0; if (o->style()->appearance() == CheckboxPart || o->style()->appearance() == RadioPart) { const RenderBox* box = toRenderBox(o); return box->marginTop() + box->height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit. } return RenderTheme::baselinePosition(o); } bool RenderThemeSafari::controlSupportsTints(const RenderObject* o) const { if (!isEnabled(o)) return false; // Checkboxes only have tint when checked. if (o->style()->appearance() == CheckboxPart) return isChecked(o); // For now assume other controls have tint if enabled. return true; } NSControlSize RenderThemeSafari::controlSizeForFont(RenderStyle* style) const { int fontSize = style->fontSize(); if (fontSize >= 16) return NSRegularControlSize; if (fontSize >= 11) return NSSmallControlSize; return NSMiniControlSize; } /* void RenderThemeSafari::setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minSize) { NSControlSize size; if (minSize.width() >= sizes[NSRegularControlSize].width() && minSize.height() >= sizes[NSRegularControlSize].height()) size = NSRegularControlSize; else if (minSize.width() >= sizes[NSSmallControlSize].width() && minSize.height() >= sizes[NSSmallControlSize].height()) size = NSSmallControlSize; else size = NSMiniControlSize; if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. [cell setControlSize:size]; } */ IntSize RenderThemeSafari::sizeForFont(RenderStyle* style, const IntSize* sizes) const { return sizes[controlSizeForFont(style)]; } IntSize RenderThemeSafari::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const { return sizes[controlSizeForSystemFont(style)]; } void RenderThemeSafari::setSizeFromFont(RenderStyle* style, const IntSize* sizes) const { // FIXME: Check is flawed, since it doesn't take min-width/max-width into account. IntSize size = sizeForFont(style, sizes); if (style->width().isIntrinsicOrAuto() && size.width() > 0) style->setWidth(Length(size.width(), Fixed)); if (style->height().isAuto() && size.height() > 0) style->setHeight(Length(size.height(), Fixed)); } void RenderThemeSafari::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const { FontDescription fontDescription; fontDescription.setIsAbsoluteSize(true); fontDescription.setGenericFamily(FontDescription::SerifFamily); float fontSize = systemFontSizeForControlSize(controlSize); fontDescription.firstFamily().setFamily("Lucida Grande"); fontDescription.setComputedSize(fontSize); fontDescription.setSpecifiedSize(fontSize); // Reset line height style->setLineHeight(RenderStyle::initialLineHeight()); if (style->setFontDescription(fontDescription)) style->font().update(selector->fontSelector()); } NSControlSize RenderThemeSafari::controlSizeForSystemFont(RenderStyle* style) const { int fontSize = style->fontSize(); if (fontSize >= 13) return NSRegularControlSize; if (fontSize >= 11) return NSSmallControlSize; return NSMiniControlSize; } bool RenderThemeSafari::paintCheckbox(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) { ASSERT(SafariThemeLibrary()); NSControlSize controlSize = controlSizeForFont(o->style()); IntRect inflatedRect = inflateRect(r, checkboxSizes()[controlSize], checkboxMargins(controlSize)); paintThemePart(SafariTheme::CheckboxPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o)); return false; } const IntSize* RenderThemeSafari::checkboxSizes() const { static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; return sizes; } const int* RenderThemeSafari::checkboxMargins(NSControlSize controlSize) const { static const int margins[3][4] = { { 2, 2, 2, 2 }, { 2, 2, 2, 1 }, { 1, 0, 0, 0 }, }; return margins[controlSize]; } void RenderThemeSafari::setCheckboxSize(RenderStyle* style) const { // If the width and height are both specified, then we have nothing to do. if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) return; // Use the font size to determine the intrinsic width of the control. setSizeFromFont(style, checkboxSizes()); } bool RenderThemeSafari::paintRadio(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) { ASSERT(SafariThemeLibrary()); NSControlSize controlSize = controlSizeForFont(o->style()); IntRect inflatedRect = inflateRect(r, radioSizes()[controlSize], radioMargins(controlSize)); paintThemePart(RadioButtonPart, paintInfo.context->platformContext(), inflatedRect, controlSize, determineState(o)); return false; } const IntSize* RenderThemeSafari::radioSizes() const { static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; return sizes; } const int* RenderThemeSafari::radioMargins(NSControlSize controlSize) const { static const int margins[3][4] = { { 1, 2, 2, 2 }, { 0, 1, 2, 1 }, { 0, 0, 1, 0 }, }; return margins[controlSize]; } void RenderThemeSafari::setRadioSize(RenderStyle* style) const { // If the width and height are both specified, then we have nothing to do. if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto()) return; // Use the font size to determine the intrinsic width of the control. setSizeFromFont(style, radioSizes()); } void RenderThemeSafari::setButtonPaddingFromControlSize(RenderStyle* style, NSControlSize size) const { // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is // by definition constrained, since we select mini only for small cramped environments. // This also guarantees the HTML4