From debdd19170efc99ba2ea55eaf6e408f24dbcbc77 Mon Sep 17 00:00:00 2001 From: "pinkerton@google.com" Date: Wed, 27 Aug 2008 20:30:12 +0000 Subject: For RenderThemeMac.h into pending and create new RenderThemeMac in port to impl our version. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@1459 0039d316-1c4b-4281-b951-d872f2087c98 --- webkit/port/rendering/RenderThemeMac.mm | 1540 +++++++++++++++++++++++++++++++ 1 file changed, 1540 insertions(+) create mode 100644 webkit/port/rendering/RenderThemeMac.mm (limited to 'webkit/port') diff --git a/webkit/port/rendering/RenderThemeMac.mm b/webkit/port/rendering/RenderThemeMac.mm new file mode 100644 index 0000000..e44862f --- /dev/null +++ b/webkit/port/rendering/RenderThemeMac.mm @@ -0,0 +1,1540 @@ +/* + * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * 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. + */ + +#import "config.h" +#import "RenderThemeMac.h" + +#import "BitmapImage.h" +#import "CSSStyleSelector.h" +#import "CSSValueKeywords.h" +#import "Document.h" +#import "Element.h" +#import "FoundationExtras.h" +#import "FrameView.h" +#import "GraphicsContext.h" +#import "HTMLInputElement.h" +#import "HTMLMediaElement.h" +#import "HTMLNames.h" +#import "Image.h" +#import "LocalCurrentGraphicsContext.h" +#import "RenderSlider.h" +#import "RenderView.h" +#import "SharedBuffer.h" +#import "WebCoreSystemInterface.h" +#import +#import +#import +#import + +#ifdef BUILDING_ON_TIGER +typedef unsigned NSUInteger; +#endif + +using std::min; + +// The methods in this file are specific to the Mac OS X platform. + +// FIXME: The platform-independent code in this class should be factored out and +// merged with RenderThemeSafari. + +@interface WebCoreRenderThemeNotificationObserver : NSObject +{ + WebCore::RenderTheme *_theme; +} + +- (id)initWithTheme:(WebCore::RenderTheme *)theme; +- (void)systemColorsDidChange:(NSNotification *)notification; + +@end + +@implementation WebCoreRenderThemeNotificationObserver + +- (id)initWithTheme:(WebCore::RenderTheme *)theme +{ + [super init]; + _theme = theme; + + return self; +} + +- (void)systemColorsDidChange:(NSNotification *)notification +{ + ASSERT([[notification name] isEqualToString:NSSystemColorsDidChangeNotification]); + _theme->platformColorsDidChange(); +} + +@end + +namespace WebCore { + +using namespace HTMLNames; + +enum { + topMargin, + rightMargin, + bottomMargin, + leftMargin +}; + +enum { + topPadding, + rightPadding, + bottomPadding, + leftPadding +}; + +RenderTheme* theme() +{ + static RenderThemeMac* macTheme = new RenderThemeMac; + return macTheme; +} + +RenderThemeMac::RenderThemeMac() + : m_resizeCornerImage(0) + , m_isSliderThumbHorizontalPressed(false) + , m_isSliderThumbVerticalPressed(false) + , m_notificationObserver(AdoptNS, [[WebCoreRenderThemeNotificationObserver alloc] initWithTheme:this]) +{ + [[NSNotificationCenter defaultCenter] addObserver:m_notificationObserver.get() + selector:@selector(systemColorsDidChange:) + name:NSSystemColorsDidChangeNotification + object:nil]; +} + +RenderThemeMac::~RenderThemeMac() +{ + [[NSNotificationCenter defaultCenter] removeObserver:m_notificationObserver.get()]; + delete m_resizeCornerImage; +} + +Color RenderThemeMac::platformActiveSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast(255.0 * [color redComponent]), static_cast(255.0 * [color greenComponent]), static_cast(255.0 * [color blueComponent])); +} + +Color RenderThemeMac::platformInactiveSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor secondarySelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast(255.0 * [color redComponent]), static_cast(255.0 * [color greenComponent]), static_cast(255.0 * [color blueComponent])); +} + +Color RenderThemeMac::activeListBoxSelectionBackgroundColor() const +{ + NSColor* color = [[NSColor alternateSelectedControlColor] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + return Color(static_cast(255.0 * [color redComponent]), static_cast(255.0 * [color greenComponent]), static_cast(255.0 * [color blueComponent])); +} + +void RenderThemeMac::systemFont(int cssValueId, Document*, 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; + NSFont* font = nil; + switch (cssValueId) { + case CSS_VAL_SMALL_CAPTION: + cachedDesc = &smallSystemFont; + if (!smallSystemFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + break; + case CSS_VAL_MENU: + cachedDesc = &menuFont; + if (!menuFont.isAbsoluteSize()) + font = [NSFont menuFontOfSize:[NSFont systemFontSize]]; + break; + case CSS_VAL_STATUS_BAR: + cachedDesc = &labelFont; + if (!labelFont.isAbsoluteSize()) + font = [NSFont labelFontOfSize:[NSFont labelFontSize]]; + break; + case CSS_VAL__WEBKIT_MINI_CONTROL: + cachedDesc = &miniControlFont; + if (!miniControlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]]; + break; + case CSS_VAL__WEBKIT_SMALL_CONTROL: + cachedDesc = &smallControlFont; + if (!smallControlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]; + break; + case CSS_VAL__WEBKIT_CONTROL: + cachedDesc = &controlFont; + if (!controlFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + break; + default: + cachedDesc = &systemFont; + if (!systemFont.isAbsoluteSize()) + font = [NSFont systemFontOfSize:[NSFont systemFontSize]]; + } + + if (font) { + cachedDesc->setIsAbsoluteSize(true); + cachedDesc->setGenericFamily(FontDescription::NoFamily); + cachedDesc->firstFamily().setFamily([font familyName]); + cachedDesc->setSpecifiedSize([font pointSize]); + NSFontTraitMask traits = [[NSFontManager sharedFontManager] traitsOfFont:font]; + cachedDesc->setBold(traits & NSBoldFontMask); + cachedDesc->setItalic(traits & NSItalicFontMask); + } + fontDescription = *cachedDesc; +} + +bool RenderThemeMac::isControlStyled(const RenderStyle* style, const BorderData& border, + const BackgroundLayer& background, const Color& backgroundColor) const +{ + if (style->appearance() == TextFieldAppearance || style->appearance() == TextAreaAppearance || style->appearance() == ListboxAppearance) + return style->border() != border; + return RenderTheme::isControlStyled(style, border, background, backgroundColor); +} + +void RenderThemeMac::paintResizeControl(GraphicsContext* c, const IntRect& r) +{ + Image* resizeCornerImage = this->resizeCornerImage(); + IntPoint imagePoint(r.right() - resizeCornerImage->width(), r.bottom() - resizeCornerImage->height()); + c->drawImage(resizeCornerImage, imagePoint); +} + +void RenderThemeMac::adjustRepaintRect(const RenderObject* o, IntRect& r) +{ + switch (o->style()->appearance()) { + case CheckboxAppearance: { + // Since we query the prototype cell, we need to update its state to match. + setCheckboxCellState(o, r); + + // 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()[[checkbox() controlSize]], checkboxMargins()); + break; + } + case RadioAppearance: { + // Since we query the prototype cell, we need to update its state to match. + setRadioCellState(o, r); + + // 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()[[radio() controlSize]], radioMargins()); + break; + } + case PushButtonAppearance: + case ButtonAppearance: { + // Since we query the prototype cell, we need to update its state to match. + setButtonCellState(o, r); + + // 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 ([button() bezelStyle] == NSRoundedBezelStyle) + r = inflateRect(r, buttonSizes()[[button() controlSize]], buttonMargins()); + break; + } + case MenulistAppearance: { + setPopupButtonCellState(o, r); + r = inflateRect(r, popupButtonSizes()[[popupButton() controlSize]], popupButtonMargins()); + break; + } + default: + break; + } +} + +IntRect RenderThemeMac::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; +} + +void RenderThemeMac::updateCheckedState(NSCell* cell, const RenderObject* o) +{ + bool oldIndeterminate = [cell state] == NSMixedState; + bool indeterminate = isIndeterminate(o); + bool checked = isChecked(o); + + if (oldIndeterminate != indeterminate) { + [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)]; + return; + } + + bool oldChecked = [cell state] == NSOnState; + if (checked != oldChecked) + [cell setState:checked ? NSOnState : NSOffState]; +} + +void RenderThemeMac::updateEnabledState(NSCell* cell, const RenderObject* o) +{ + bool oldEnabled = [cell isEnabled]; + bool enabled = isEnabled(o); + if (enabled != oldEnabled) + [cell setEnabled:enabled]; +} + +void RenderThemeMac::updateFocusedState(NSCell* cell, const RenderObject* o) +{ + bool oldFocused = [cell showsFirstResponder]; + bool focused = isFocused(o) && o->style()->outlineStyleIsAuto(); + if (focused != oldFocused) + [cell setShowsFirstResponder:focused]; +} + +void RenderThemeMac::updatePressedState(NSCell* cell, const RenderObject* o) +{ + bool oldPressed = [cell isHighlighted]; + bool pressed = (o->element() && o->element()->active()); + if (pressed != oldPressed) + [cell setHighlighted:pressed]; +} + +short RenderThemeMac::baselinePosition(const RenderObject* o) const +{ + if (o->style()->appearance() == CheckboxAppearance || o->style()->appearance() == RadioAppearance) + return o->marginTop() + o->height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit. + return RenderTheme::baselinePosition(o); +} + +bool RenderThemeMac::controlSupportsTints(const RenderObject* o) const +{ + // An alternate way to implement this would be to get the appropriate cell object + // and call the private _needRedrawOnWindowChangedKeyState method. An advantage of + // that would be that we would match AppKit behavior more closely, but a disadvantage + // would be that we would rely on an AppKit SPI method. + + if (!isEnabled(o)) + return false; + + // Checkboxes only have tint when checked. + if (o->style()->appearance() == CheckboxAppearance) + return isChecked(o); + + // For now assume other controls have tint if enabled. + return true; +} + +NSControlSize RenderThemeMac::controlSizeForFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= 16) + return NSRegularControlSize; + if (fontSize >= 11) + return NSSmallControlSize; + return NSMiniControlSize; +} + +void RenderThemeMac::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 RenderThemeMac::sizeForFont(RenderStyle* style, const IntSize* sizes) const +{ + return sizes[controlSizeForFont(style)]; +} + +IntSize RenderThemeMac::sizeForSystemFont(RenderStyle* style, const IntSize* sizes) const +{ + return sizes[controlSizeForSystemFont(style)]; +} + +void RenderThemeMac::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 RenderThemeMac::setFontFromControlSize(CSSStyleSelector* selector, RenderStyle* style, NSControlSize controlSize) const +{ + FontDescription fontDescription; + fontDescription.setIsAbsoluteSize(true); + fontDescription.setGenericFamily(FontDescription::SerifFamily); + + NSFont* font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSize]]; + fontDescription.firstFamily().setFamily([font familyName]); + fontDescription.setComputedSize([font pointSize]); + fontDescription.setSpecifiedSize([font pointSize]); + + // Reset line height + style->setLineHeight(RenderStyle::initialLineHeight()); + + if (style->setFontDescription(fontDescription)) + style->font().update(0); +} + +NSControlSize RenderThemeMac::controlSizeForSystemFont(RenderStyle* style) const +{ + int fontSize = style->fontSize(); + if (fontSize >= [NSFont systemFontSizeForControlSize:NSRegularControlSize]) + return NSRegularControlSize; + if (fontSize >= [NSFont systemFontSizeForControlSize:NSSmallControlSize]) + return NSSmallControlSize; + return NSMiniControlSize; +} + +bool RenderThemeMac::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo&, const IntRect& r) +{ + struct HIThemeButtonDrawInfo button_info = { + 0, kThemeStateActive, kThemeCheckBox, 0, kThemeAdornmentNone, { 0 } + }; + // Determine the width and height needed for the control and prepare the cell for painting. + setCheckboxCellState(o, r); + // 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. + IntRect inflatedRect = inflateRect(r, checkboxSizes()[NSSmallControlSize], checkboxMargins()); + // draw the button + HIRect bounds = { { inflatedRect.x(), inflatedRect.y() }, { inflatedRect.width(), inflatedRect.height() } }; + HIRect label = { { 0, 0 }, { 0, 0 } }; + HIThemeDrawButton(&bounds, &button_info, static_cast([[NSGraphicsContext currentContext] graphicsPort]), kHIThemeOrientationNormal, &label); + + return false; +} + +const IntSize* RenderThemeMac::checkboxSizes() const +{ + static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; + return sizes; +} + +const int* RenderThemeMac::checkboxMargins() const +{ + static const int margins[3][4] = + { + { 3, 4, 4, 2 }, + { 4, 3, 3, 3 }, + { 4, 3, 3, 3 }, + }; + return margins[[checkbox() controlSize]]; +} + +void RenderThemeMac::setCheckboxCellState(const RenderObject* o, const IntRect& r) +{ + NSButtonCell* checkbox = this->checkbox(); + + // Set the control size based off the rectangle we're painting into. + setControlSize(checkbox, checkboxSizes(), r.size()); + + // Update the various states we respond to. + updateCheckedState(checkbox, o); + updateEnabledState(checkbox, o); + updatePressedState(checkbox, o); + updateFocusedState(checkbox, o); +} + +void RenderThemeMac::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 RenderThemeMac::paintRadio(RenderObject* o, const RenderObject::PaintInfo&, const IntRect& r) +{ + struct HIThemeButtonDrawInfo button_info = { + 0, kThemeStateActive, kThemeRadioButton, 0, kThemeAdornmentNone, { 0 } + }; + // Determine the width and height needed for the control and prepare the cell for painting. + setRadioCellState(o, r); + // 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. + IntRect inflatedRect = inflateRect(r, radioSizes()[NSSmallControlSize], radioMargins()); + // draw the button + HIRect bounds = { { inflatedRect.x(), inflatedRect.y() }, { inflatedRect.width(), inflatedRect.height() } }; + HIRect label = { { 0, 0 }, { 0, 0 } }; + HIThemeDrawButton(&bounds, &button_info, static_cast([[NSGraphicsContext currentContext] graphicsPort]), kHIThemeOrientationNormal, &label); + + return false; +} + +const IntSize* RenderThemeMac::radioSizes() const +{ + static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; + return sizes; +} + +const int* RenderThemeMac::radioMargins() const +{ + static const int margins[3][4] = + { + { 2, 2, 4, 2 }, + { 3, 2, 3, 2 }, + { 1, 0, 2, 0 }, + }; + return margins[[radio() controlSize]]; +} + +void RenderThemeMac::setRadioCellState(const RenderObject* o, const IntRect& r) +{ + NSButtonCell* radio = this->radio(); + + // Set the control size based off the rectangle we're painting into. + setControlSize(radio, radioSizes(), r.size()); + + // Update the various states we respond to. + updateCheckedState(radio, o); + updateEnabledState(radio, o); + updatePressedState(radio, o); + updateFocusedState(radio, o); +} + + +void RenderThemeMac::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 RenderThemeMac::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