summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/platform/chromium/ThemeChromiumMac.mm
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/platform/chromium/ThemeChromiumMac.mm
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/platform/chromium/ThemeChromiumMac.mm')
-rw-r--r--Source/WebCore/platform/chromium/ThemeChromiumMac.mm844
1 files changed, 844 insertions, 0 deletions
diff --git a/Source/WebCore/platform/chromium/ThemeChromiumMac.mm b/Source/WebCore/platform/chromium/ThemeChromiumMac.mm
new file mode 100644
index 0000000..5e457f3
--- /dev/null
+++ b/Source/WebCore/platform/chromium/ThemeChromiumMac.mm
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2009 Google Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "ThemeChromiumMac.h"
+
+#import "BlockExceptions.h"
+#import "GraphicsContext.h"
+#import "LocalCurrentGraphicsContext.h"
+#import "ScrollView.h"
+#import "WebCoreSystemInterface.h"
+#import <Carbon/Carbon.h>
+#include <wtf/StdLibExtras.h>
+#import <objc/runtime.h>
+
+using namespace std;
+
+// This file (and its associated .h file) is a clone of ThemeMac.mm.
+// Because the original file is designed to run in-process inside a Cocoa view,
+// we must maintain a fork. Please maintain this file by performing parallel
+// changes to it.
+//
+// The only changes from ThemeMac should be:
+// - The classname change from ThemeMac to ThemeChromiumMac.
+// - The import of FlippedView() and its use as the parent view for cell
+// rendering.
+// - In updateStates() the code to update the cells' inactive state.
+// - In paintButton() the code to save/restore the window's default button cell.
+// - The Snow Leopard focus ring bug fix and its use around every call to
+// -[NSButtonCell drawWithFrame:inView:].
+//
+// For all other differences, if it was introduced in this file, then the
+// maintainer forgot to include it in the list; otherwise it is an update that
+// should have been applied to this file but was not.
+
+// FIXME: Default buttons really should be more like push buttons and not like buttons.
+
+// --- START fix for Snow Leopard focus ring bug ---
+
+// There is a bug in the Cocoa focus ring drawing code. The code calls +[NSView
+// focusView] (to get the currently focused view) and then calls an NSRect-
+// returning method on that view to obtain a clipping rect. However, if there is
+// no focused view (as there won't be if the destination is a context), the rect
+// returned from the method invocation on nil is garbage.
+//
+// The garbage fortunately does not clip the focus ring on Leopard, but
+// unfortunately does so on Snow Leopard. Therefore, if a runtime test shows
+// that focus ring drawing fails, we swizzle NSView to ensure it returns a valid
+// view with a valid clipping rectangle.
+//
+// FIXME: After the referenced bug is fixed on all supported platforms, remove
+// this code.
+//
+// References:
+// <http://crbug.com/27493>
+// <rdar://problem/7604051> (<http://openradar.appspot.com/7604051>)
+
+@interface TCMVisibleView : NSView
+
+@end
+
+@implementation TCMVisibleView
+
+- (struct CGRect)_focusRingVisibleRect
+{
+ return CGRectZero;
+}
+
+- (id)_focusRingClipAncestor
+{
+ return self;
+}
+
+@end
+
+@interface NSView (TCMInterposing)
++ (NSView *)TCMInterposing_focusView;
+@end
+
+namespace FocusIndicationFix {
+
+bool currentOSHasSetFocusRingStyleInBitmapBug()
+{
+ UInt32 pixel = 0;
+ UInt32* pixelPlane = &pixel;
+ UInt32** pixelPlanes = &pixelPlane;
+ NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(UInt8**)pixelPlanes
+ pixelsWide:1
+ pixelsHigh:1
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSCalibratedRGBColorSpace
+ bitmapFormat:NSAlphaFirstBitmapFormat
+ bytesPerRow:4
+ bitsPerPixel:32];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSMakeRect(0, 0, 1, 1));
+ [NSGraphicsContext restoreGraphicsState];
+ [bitmap release];
+
+ return !pixel;
+}
+
+bool swizzleFocusView()
+{
+ if (!currentOSHasSetFocusRingStyleInBitmapBug())
+ return false;
+
+ Class nsview = [NSView class];
+ Method m1 = class_getClassMethod(nsview, @selector(focusView));
+ Method m2 = class_getClassMethod(nsview, @selector(TCMInterposing_focusView));
+ if (m1 && m2) {
+ method_exchangeImplementations(m1, m2);
+ return true;
+ }
+
+ return false;
+}
+
+static bool interpose = false;
+
+// A class to restrict the amount of time spent messing with interposing. It
+// only stacks one-deep.
+class ScopedFixer {
+public:
+ ScopedFixer()
+ {
+ static bool swizzled = swizzleFocusView();
+ interpose = swizzled;
+ }
+
+ ~ScopedFixer()
+ {
+ interpose = false;
+ }
+};
+
+} // namespace FocusIndicationFix
+
+@implementation NSView (TCMInterposing)
+
++ (NSView *)TCMInterposing_focusView
+{
+ NSView *view = [self TCMInterposing_focusView]; // call original (was swizzled)
+ if (!view && FocusIndicationFix::interpose) {
+ static TCMVisibleView* fixedView = [[TCMVisibleView alloc] init];
+ view = fixedView;
+ }
+
+ return view;
+}
+
+@end
+
+// --- END fix for Snow Leopard focus ring bug ---
+
+namespace WebCore {
+
+// Pick up utility function from RenderThemeChromiumMac.
+extern NSView* FlippedView();
+
+enum {
+ topMargin,
+ rightMargin,
+ bottomMargin,
+ leftMargin
+};
+
+Theme* platformTheme()
+{
+ DEFINE_STATIC_LOCAL(ThemeChromiumMac, themeMac, ());
+ return &themeMac;
+}
+
+// Helper functions used by a bunch of different control parts.
+
+static NSControlSize controlSizeForFont(const Font& font)
+{
+ int fontSize = font.pixelSize();
+ if (fontSize >= 16)
+ return NSRegularControlSize;
+ if (fontSize >= 11)
+ return NSSmallControlSize;
+ return NSMiniControlSize;
+}
+
+static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
+{
+ IntSize controlSize = sizes[nsControlSize];
+ if (zoomFactor != 1.0f)
+ controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor);
+ LengthSize result = zoomedSize;
+ if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0)
+ result.setWidth(Length(controlSize.width(), Fixed));
+ if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0)
+ result.setHeight(Length(controlSize.height(), Fixed));
+ return result;
+}
+
+static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes)
+{
+ return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes);
+}
+
+static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
+{
+ if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) &&
+ minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor))
+ return NSRegularControlSize;
+ if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) &&
+ minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor))
+ return NSSmallControlSize;
+ return NSMiniControlSize;
+}
+
+static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor)
+{
+ ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor);
+ if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same.
+ [cell setControlSize:(NSControlSize)size];
+}
+
+static void updateStates(NSCell* cell, ControlStates states)
+{
+ // Hover state is not supported by Aqua.
+
+ // Pressed state
+ bool oldPressed = [cell isHighlighted];
+ bool pressed = states & PressedState;
+ if (pressed != oldPressed)
+ [cell setHighlighted:pressed];
+
+ // Enabled state
+ bool oldEnabled = [cell isEnabled];
+ bool enabled = states & EnabledState;
+ if (enabled != oldEnabled)
+ [cell setEnabled:enabled];
+
+ // Focused state
+ bool oldFocused = [cell showsFirstResponder];
+ bool focused = states & FocusState;
+ if (focused != oldFocused)
+ [cell setShowsFirstResponder:focused];
+
+ // Checked and Indeterminate
+ bool oldIndeterminate = [cell state] == NSMixedState;
+ bool indeterminate = (states & IndeterminateState);
+ bool checked = states & CheckedState;
+ bool oldChecked = [cell state] == NSOnState;
+ if (oldIndeterminate != indeterminate || checked != oldChecked)
+ [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)];
+
+ // Window Inactive state
+ NSControlTint oldTint = [cell controlTint];
+ bool windowInactive = (states & WindowInactiveState);
+ NSControlTint tint = windowInactive ? static_cast<NSControlTint>(NSClearControlTint)
+ : [NSColor currentControlTint];
+ if (tint != oldTint)
+ [cell setControlTint:tint];
+}
+
+static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states)
+{
+ if (states & ReadOnlyState)
+ return kThemeStateUnavailableInactive;
+ if (!(states & EnabledState))
+ return kThemeStateUnavailableInactive;
+
+ // Do not process PressedState if !EnabledState or ReadOnlyState.
+ if (states & PressedState) {
+ if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini)
+ return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown;
+ return kThemeStatePressed;
+ }
+ return kThemeStateActive;
+}
+
+static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor)
+{
+ // 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 = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor);
+ int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor);
+ IntRect result(zoomedRect);
+ if (widthDelta < 0) {
+ result.setX(result.x() - margins[leftMargin] * zoomFactor);
+ result.setWidth(result.width() - widthDelta);
+ }
+ if (heightDelta < 0) {
+ result.setY(result.y() - margins[topMargin] * zoomFactor);
+ result.setHeight(result.height() - heightDelta);
+ }
+ return result;
+}
+
+// Checkboxes
+
+static const IntSize* checkboxSizes()
+{
+ static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) };
+ return sizes;
+}
+
+static const int* checkboxMargins(NSControlSize controlSize)
+{
+ static const int margins[3][4] =
+ {
+ { 3, 4, 4, 2 },
+ { 4, 3, 3, 3 },
+ { 4, 3, 3, 3 },
+ };
+ return margins[controlSize];
+}
+
+static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
+{
+ // If the width and height are both specified, then we have nothing to do.
+ if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
+ return zoomedSize;
+
+ // Use the font size to determine the intrinsic width of the control.
+ return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes());
+}
+
+static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
+{
+ static NSButtonCell *checkboxCell;
+ if (!checkboxCell) {
+ checkboxCell = [[NSButtonCell alloc] init];
+ [checkboxCell setButtonType:NSSwitchButton];
+ [checkboxCell setTitle:nil];
+ [checkboxCell setAllowsMixedState:YES];
+ [checkboxCell setFocusRingType:NSFocusRingTypeExterior];
+ }
+
+ // Set the control size based off the rectangle we're painting into.
+ setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor);
+
+ // Update the various states we respond to.
+ updateStates(checkboxCell, states);
+
+ return checkboxCell;
+}
+
+// FIXME: Share more code with radio buttons.
+static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
+{
+ BEGIN_BLOCK_OBJC_EXCEPTIONS
+
+ // Determine the width and height needed for the control and prepare the cell for painting.
+ NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor);
+ LocalCurrentGraphicsContext localContext(context);
+
+ context->save();
+
+ NSControlSize controlSize = [checkboxCell controlSize];
+ IntSize zoomedSize = checkboxSizes()[controlSize];
+ zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
+
+ if (zoomFactor != 1.0f) {
+ inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
+ inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
+ context->translate(inflatedRect.x(), inflatedRect.y());
+ context->scale(FloatSize(zoomFactor, zoomFactor));
+ context->translate(-inflatedRect.x(), -inflatedRect.y());
+ }
+
+ {
+ FocusIndicationFix::ScopedFixer fix;
+ [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
+ }
+ [checkboxCell setControlView:nil];
+
+ context->restore();
+
+ END_BLOCK_OBJC_EXCEPTIONS
+}
+
+// Radio Buttons
+
+static const IntSize* radioSizes()
+{
+ static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) };
+ return sizes;
+}
+
+static const int* radioMargins(NSControlSize controlSize)
+{
+ static const int margins[3][4] =
+ {
+ { 2, 2, 4, 2 },
+ { 3, 2, 3, 2 },
+ { 1, 0, 2, 0 },
+ };
+ return margins[controlSize];
+}
+
+static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor)
+{
+ // If the width and height are both specified, then we have nothing to do.
+ if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
+ return zoomedSize;
+
+ // Use the font size to determine the intrinsic width of the control.
+ return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes());
+}
+
+static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor)
+{
+ static NSButtonCell *radioCell;
+ if (!radioCell) {
+ radioCell = [[NSButtonCell alloc] init];
+ [radioCell setButtonType:NSRadioButton];
+ [radioCell setTitle:nil];
+ [radioCell setFocusRingType:NSFocusRingTypeExterior];
+ }
+
+ // Set the control size based off the rectangle we're painting into.
+ setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor);
+
+ // Update the various states we respond to.
+ updateStates(radioCell, states);
+
+ return radioCell;
+}
+
+static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
+{
+ // Determine the width and height needed for the control and prepare the cell for painting.
+ NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor);
+ LocalCurrentGraphicsContext localContext(context);
+
+ context->save();
+
+ NSControlSize controlSize = [radioCell controlSize];
+ IntSize zoomedSize = radioSizes()[controlSize];
+ zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
+
+ if (zoomFactor != 1.0f) {
+ inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
+ inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
+ context->translate(inflatedRect.x(), inflatedRect.y());
+ context->scale(FloatSize(zoomFactor, zoomFactor));
+ context->translate(-inflatedRect.x(), -inflatedRect.y());
+ }
+
+ BEGIN_BLOCK_OBJC_EXCEPTIONS
+ {
+ FocusIndicationFix::ScopedFixer fix;
+ [radioCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
+ }
+ [radioCell setControlView:nil];
+ END_BLOCK_OBJC_EXCEPTIONS
+
+ context->restore();
+}
+
+// Buttons
+
+// Buttons really only constrain height. They respect width.
+static const IntSize* buttonSizes()
+{
+ static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) };
+ return sizes;
+}
+
+#if ENABLE(DATALIST)
+static const IntSize* listButtonSizes()
+{
+ static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) };
+ return sizes;
+}
+#endif
+
+static const int* buttonMargins(NSControlSize controlSize)
+{
+ static const int margins[3][4] =
+ {
+ { 4, 6, 7, 6 },
+ { 4, 5, 6, 5 },
+ { 0, 1, 1, 1 },
+ };
+ return margins[controlSize];
+}
+
+static void setupButtonCell(NSButtonCell *&buttonCell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
+{
+ if (!buttonCell) {
+ buttonCell = [[NSButtonCell alloc] init];
+ [buttonCell setTitle:nil];
+ [buttonCell setButtonType:NSMomentaryPushInButton];
+ if (states & DefaultState)
+ [buttonCell setKeyEquivalent:@"\r"];
+ }
+
+ // Set the control size based off the rectangle we're painting into.
+ const IntSize* sizes = buttonSizes();
+#if ENABLE(DATALIST)
+ if (part == ListButtonPart) {
+ [buttonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
+ sizes = listButtonSizes();
+ } else
+#endif
+ if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) {
+ // Use the square button
+ if ([buttonCell bezelStyle] != NSShadowlessSquareBezelStyle)
+ [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];
+ } else if ([buttonCell bezelStyle] != NSRoundedBezelStyle)
+ [buttonCell setBezelStyle:NSRoundedBezelStyle];
+
+ setControlSize(buttonCell, sizes, zoomedRect.size(), zoomFactor);
+
+ // Update the various states we respond to.
+ updateStates(buttonCell, states);
+}
+
+static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor)
+{
+ bool isDefault = states & DefaultState;
+ static NSButtonCell *cells[2];
+ setupButtonCell(cells[isDefault], part, states, zoomedRect, zoomFactor);
+ return cells[isDefault];
+}
+
+static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView)
+{
+ BEGIN_BLOCK_OBJC_EXCEPTIONS
+
+ // Determine the width and height needed for the control and prepare the cell for painting.
+ NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor);
+ LocalCurrentGraphicsContext localContext(context);
+
+ NSControlSize controlSize = [buttonCell controlSize];
+#if ENABLE(DATALIST)
+ IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize];
+#else
+ IntSize zoomedSize = buttonSizes()[controlSize];
+#endif
+ zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ IntRect inflatedRect = zoomedRect;
+ if ([buttonCell bezelStyle] == NSRoundedBezelStyle) {
+ // Center the button within the available space.
+ if (inflatedRect.height() > zoomedSize.height()) {
+ inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2);
+ inflatedRect.setHeight(zoomedSize.height());
+ }
+
+ // Now inflate it to account for the shadow.
+ inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
+
+ if (zoomFactor != 1.0f) {
+ inflatedRect.setWidth(inflatedRect.width() / zoomFactor);
+ inflatedRect.setHeight(inflatedRect.height() / zoomFactor);
+ context->translate(inflatedRect.x(), inflatedRect.y());
+ context->scale(FloatSize(zoomFactor, zoomFactor));
+ context->translate(-inflatedRect.x(), -inflatedRect.y());
+ }
+ }
+
+ {
+ FocusIndicationFix::ScopedFixer fix;
+ [buttonCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()];
+ }
+ [buttonCell setControlView:nil];
+
+ END_BLOCK_OBJC_EXCEPTIONS
+}
+
+// Stepper
+
+static const IntSize* stepperSizes()
+{
+ static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) };
+ return sizes;
+}
+
+// We don't use controlSizeForFont() for steppers because the stepper height
+// should be equal to or less than the corresponding text field height,
+static NSControlSize stepperControlSizeForFont(const Font& font)
+{
+ int fontSize = font.pixelSize();
+ if (fontSize >= 18)
+ return NSRegularControlSize;
+ if (fontSize >= 13)
+ return NSSmallControlSize;
+ return NSMiniControlSize;
+}
+
+static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*)
+{
+ // We don't use NSStepperCell because there are no ways to draw an
+ // NSStepperCell with the up button highlighted.
+
+ HIThemeButtonDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states);
+ drawInfo.adornment = kThemeAdornmentDefault;
+ ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
+ if (controlSize == NSSmallControlSize)
+ drawInfo.kind = kThemeIncDecButtonSmall;
+ else if (controlSize == NSMiniControlSize)
+ drawInfo.kind = kThemeIncDecButtonMini;
+ else
+ drawInfo.kind = kThemeIncDecButton;
+
+ IntRect rect(zoomedRect);
+ context->save();
+ if (zoomFactor != 1.0f) {
+ rect.setWidth(rect.width() / zoomFactor);
+ rect.setHeight(rect.height() / zoomFactor);
+ context->translate(rect.x(), rect.y());
+ context->scale(FloatSize(zoomFactor, zoomFactor));
+ context->translate(-rect.x(), -rect.y());
+ }
+ CGRect bounds(rect);
+ // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'.
+ CGRect backgroundBounds;
+ HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds);
+ if (bounds.origin.x != backgroundBounds.origin.x)
+ bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x;
+ if (bounds.origin.y != backgroundBounds.origin.y)
+ bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y;
+ HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0);
+ context->restore();
+}
+
+// Theme overrides
+
+int ThemeChromiumMac::baselinePositionAdjustment(ControlPart part) const
+{
+ if (part == CheckboxPart || part == RadioPart)
+ return -2;
+ return Theme::baselinePositionAdjustment(part);
+}
+
+FontDescription ThemeChromiumMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const
+{
+ switch (part) {
+ case PushButtonPart: {
+ FontDescription fontDescription;
+ fontDescription.setIsAbsoluteSize(true);
+ fontDescription.setGenericFamily(FontDescription::SerifFamily);
+
+ NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]];
+ fontDescription.firstFamily().setFamily([nsFont familyName]);
+ fontDescription.setComputedSize([nsFont pointSize] * zoomFactor);
+ fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor);
+ return fontDescription;
+ }
+ default:
+ return Theme::controlFont(part, font, zoomFactor);
+ }
+}
+
+LengthSize ThemeChromiumMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const
+{
+ switch (part) {
+ case CheckboxPart:
+ return checkboxSize(font, zoomedSize, zoomFactor);
+ case RadioPart:
+ return radioSize(font, zoomedSize, zoomFactor);
+ case PushButtonPart:
+ // Height is reset to auto so that specified heights can be ignored.
+ return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes());
+#if ENABLE(DATALIST)
+ case ListButtonPart:
+ return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes());
+#endif
+ case InnerSpinButtonPart:
+ // We don't use inner spin buttons on Mac.
+ return LengthSize(Length(Fixed), Length(Fixed));
+ case OuterSpinButtonPart:
+ if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto())
+ return zoomedSize;
+ return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes());
+ default:
+ return zoomedSize;
+ }
+}
+
+LengthSize ThemeChromiumMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const
+{
+ switch (part) {
+ case SquareButtonPart:
+ case DefaultButtonPart:
+ case ButtonPart:
+ case ListButtonPart:
+ return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed));
+ case InnerSpinButtonPart:
+ // We don't use inner spin buttons on Mac.
+ return LengthSize(Length(Fixed), Length(Fixed));
+ case OuterSpinButtonPart: {
+ IntSize base = stepperSizes()[NSMiniControlSize];
+ return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed),
+ Length(static_cast<int>(base.height() * zoomFactor), Fixed));
+ }
+ default:
+ return Theme::minimumControlSize(part, font, zoomFactor);
+ }
+}
+
+LengthBox ThemeChromiumMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
+{
+ switch (part) {
+ case SquareButtonPart:
+ case DefaultButtonPart:
+ case ButtonPart:
+ case ListButtonPart:
+ return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value());
+ default:
+ return Theme::controlBorder(part, font, zoomedBox, zoomFactor);
+ }
+}
+
+LengthBox ThemeChromiumMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const
+{
+ switch (part) {
+ case PushButtonPart: {
+ // 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 HTML <button> will match our rendering by default, since we're using a consistent
+ // padding.
+ const int padding = 8 * zoomFactor;
+ return LengthBox(0, padding, 0, padding);
+ }
+ default:
+ return Theme::controlPadding(part, font, zoomedBox, zoomFactor);
+ }
+}
+
+void ThemeChromiumMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const
+{
+ BEGIN_BLOCK_OBJC_EXCEPTIONS
+ switch (part) {
+ 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.
+ NSCell *cell = checkbox(states, zoomedRect, zoomFactor);
+ NSControlSize controlSize = [cell controlSize];
+ IntSize zoomedSize = checkboxSizes()[controlSize];
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
+ zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor);
+ break;
+ }
+ case RadioPart: {
+ // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button
+ // shadow". We don't consider this part of the bounds of the control in WebKit.
+ NSCell *cell = radio(states, zoomedRect, zoomFactor);
+ NSControlSize controlSize = [cell controlSize];
+ IntSize zoomedSize = radioSizes()[controlSize];
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
+ zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor);
+ break;
+ }
+ case PushButtonPart:
+ case DefaultButtonPart:
+ case ButtonPart: {
+ NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor);
+ NSControlSize controlSize = [cell controlSize];
+
+ // We inflate the rect as needed to account for the Aqua button's shadow.
+ if ([cell bezelStyle] == NSRoundedBezelStyle) {
+ IntSize zoomedSize = buttonSizes()[controlSize];
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored.
+ zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor);
+ }
+ break;
+ }
+ case OuterSpinButtonPart: {
+ static const int stepperMargin[4] = { 0, 0, 0, 0 };
+ ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor);
+ IntSize zoomedSize = stepperSizes()[controlSize];
+ zoomedSize.setHeight(zoomedSize.height() * zoomFactor);
+ zoomedSize.setWidth(zoomedSize.width() * zoomFactor);
+ zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor);
+ break;
+ }
+ default:
+ break;
+ }
+ END_BLOCK_OBJC_EXCEPTIONS
+}
+
+void ThemeChromiumMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const
+{
+ switch (part) {
+ case CheckboxPart:
+ paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView);
+ break;
+ case RadioPart:
+ paintRadio(states, context, zoomedRect, zoomFactor, scrollView);
+ break;
+ case PushButtonPart:
+ case DefaultButtonPart:
+ case ButtonPart:
+ case SquareButtonPart:
+ case ListButtonPart:
+ paintButton(part, states, context, zoomedRect, zoomFactor, scrollView);
+ break;
+ case OuterSpinButtonPart:
+ paintStepper(states, context, zoomedRect, zoomFactor, scrollView);
+ break;
+ default:
+ break;
+ }
+}
+
+}