1/* 2 * Copyright (C) 2008, 2010, 2011, 2012 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "core/platform/mac/ThemeMac.h" 28 29#import <Carbon/Carbon.h> 30#import "core/platform/ScrollView.h" 31#include "core/platform/graphics/GraphicsContextStateSaver.h" 32#import "core/platform/mac/BlockExceptions.h" 33#import "core/platform/mac/LocalCurrentGraphicsContext.h" 34#import "core/platform/mac/WebCoreNSCellExtras.h" 35#include "wtf/StdLibExtras.h" 36 37using namespace std; 38 39NSRect focusRingClipRect; 40 41// This is a view whose sole purpose is to tell AppKit that it's flipped. 42@interface WebCoreFlippedView : NSControl 43@end 44 45@implementation WebCoreFlippedView 46 47- (BOOL)isFlipped 48{ 49 return YES; 50} 51 52- (NSText *)currentEditor 53{ 54 return nil; 55} 56 57- (BOOL)_automaticFocusRingDisabled 58{ 59 return YES; 60} 61 62- (NSRect)_focusRingVisibleRect 63{ 64 if (NSIsEmptyRect(focusRingClipRect)) 65 return [self visibleRect]; 66 67 NSRect rect = focusRingClipRect; 68 rect.origin.y = [self bounds].size.height - NSMaxY(rect); 69 70 return rect; 71} 72 73- (NSView *)_focusRingClipAncestor 74{ 75 return self; 76} 77 78@end 79 80@implementation NSFont (WebCoreTheme) 81 82- (NSString*)webCoreFamilyName 83{ 84 if ([[self familyName] hasPrefix:@"."]) 85 return [self fontName]; 86 87 return [self familyName]; 88} 89 90@end 91 92namespace WebCore { 93 94enum { 95 topMargin, 96 rightMargin, 97 bottomMargin, 98 leftMargin 99}; 100 101Theme* platformTheme() 102{ 103 DEFINE_STATIC_LOCAL(ThemeMac, themeMac, ()); 104 return &themeMac; 105} 106 107// Helper functions used by a bunch of different control parts. 108 109static NSControlSize controlSizeForFont(const Font& font) 110{ 111 int fontSize = font.pixelSize(); 112 if (fontSize >= 16) 113 return NSRegularControlSize; 114 if (fontSize >= 11) 115 return NSSmallControlSize; 116 return NSMiniControlSize; 117} 118 119static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 120{ 121 IntSize controlSize = sizes[nsControlSize]; 122 if (zoomFactor != 1.0f) 123 controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor); 124 LengthSize result = zoomedSize; 125 if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0) 126 result.setWidth(Length(controlSize.width(), Fixed)); 127 if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0) 128 result.setHeight(Length(controlSize.height(), Fixed)); 129 return result; 130} 131 132static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 133{ 134 return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes); 135} 136 137static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 138{ 139 if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) && 140 minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor)) 141 return NSRegularControlSize; 142 if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) && 143 minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor)) 144 return NSSmallControlSize; 145 return NSMiniControlSize; 146} 147 148static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 149{ 150 ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor); 151 if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. 152 [cell setControlSize:(NSControlSize)size]; 153} 154 155static void updateStates(NSCell* cell, ControlStates states) 156{ 157 // Hover state is not supported by Aqua. 158 159 // Pressed state 160 bool oldPressed = [cell isHighlighted]; 161 bool pressed = states & PressedState; 162 if (pressed != oldPressed) 163 [cell setHighlighted:pressed]; 164 165 // Enabled state 166 bool oldEnabled = [cell isEnabled]; 167 bool enabled = states & EnabledState; 168 if (enabled != oldEnabled) 169 [cell setEnabled:enabled]; 170 171#if BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 172 // Focused state 173 bool oldFocused = [cell showsFirstResponder]; 174 bool focused = states & FocusState; 175 if (focused != oldFocused) 176 [cell setShowsFirstResponder:focused]; 177#endif 178 179 // Checked and Indeterminate 180 bool oldIndeterminate = [cell state] == NSMixedState; 181 bool indeterminate = (states & IndeterminateState); 182 bool checked = states & CheckedState; 183 bool oldChecked = [cell state] == NSOnState; 184 if (oldIndeterminate != indeterminate || checked != oldChecked) 185 [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)]; 186 187 // Window inactive state does not need to be checked explicitly, since we paint parented to 188 // a view in a window whose key state can be detected. 189} 190 191static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states) 192{ 193 if (states & ReadOnlyState) 194 return kThemeStateUnavailableInactive; 195 if (!(states & EnabledState)) 196 return kThemeStateUnavailableInactive; 197 198 // Do not process PressedState if !EnabledState or ReadOnlyState. 199 if (states & PressedState) { 200 if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini) 201 return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown; 202 return kThemeStatePressed; 203 } 204 return kThemeStateActive; 205} 206 207static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor) 208{ 209 // Only do the inflation if the available width/height are too small. Otherwise try to 210 // fit the glow/check space into the available box's width/height. 211 int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor); 212 int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor); 213 IntRect result(zoomedRect); 214 if (widthDelta < 0) { 215 result.setX(result.x() - margins[leftMargin] * zoomFactor); 216 result.setWidth(result.width() - widthDelta); 217 } 218 if (heightDelta < 0) { 219 result.setY(result.y() - margins[topMargin] * zoomFactor); 220 result.setHeight(result.height() - heightDelta); 221 } 222 return result; 223} 224 225// Checkboxes 226 227static const IntSize* checkboxSizes() 228{ 229 static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; 230 return sizes; 231} 232 233static const int* checkboxMargins(NSControlSize controlSize) 234{ 235 static const int margins[3][4] = 236 { 237 { 3, 4, 4, 2 }, 238 { 4, 3, 3, 3 }, 239 { 4, 3, 3, 3 }, 240 }; 241 return margins[controlSize]; 242} 243 244static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 245{ 246 // If the width and height are both specified, then we have nothing to do. 247 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 248 return zoomedSize; 249 250 // Use the font size to determine the intrinsic width of the control. 251 return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes()); 252} 253 254static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 255{ 256 static NSButtonCell *checkboxCell; 257 if (!checkboxCell) { 258 checkboxCell = [[NSButtonCell alloc] init]; 259 [checkboxCell setButtonType:NSSwitchButton]; 260 [checkboxCell setTitle:nil]; 261 [checkboxCell setAllowsMixedState:YES]; 262 [checkboxCell setFocusRingType:NSFocusRingTypeExterior]; 263 } 264 265 // Set the control size based off the rectangle we're painting into. 266 setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor); 267 268 // Update the various states we respond to. 269 updateStates(checkboxCell, states); 270 271 return checkboxCell; 272} 273 274// FIXME: Share more code with radio buttons. 275static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 276{ 277 BEGIN_BLOCK_OBJC_EXCEPTIONS 278 279 // Determine the width and height needed for the control and prepare the cell for painting. 280 NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor); 281 GraphicsContextStateSaver stateSaver(*context); 282 283 NSControlSize controlSize = [checkboxCell controlSize]; 284 IntSize zoomedSize = checkboxSizes()[controlSize]; 285 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 286 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 287 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 288 289 if (zoomFactor != 1.0f) { 290 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 291 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 292 context->translate(inflatedRect.x(), inflatedRect.y()); 293 context->scale(FloatSize(zoomFactor, zoomFactor)); 294 context->translate(-inflatedRect.x(), -inflatedRect.y()); 295 } 296 297 LocalCurrentGraphicsContext localContext(context); 298 NSView *view = ThemeMac::ensuredView(scrollView); 299 [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:view]; 300#if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 301 if (states & FocusState) 302 [checkboxCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 303#endif 304 [checkboxCell setControlView:nil]; 305 306 END_BLOCK_OBJC_EXCEPTIONS 307} 308 309// Radio Buttons 310 311static const IntSize* radioSizes() 312{ 313 static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; 314 return sizes; 315} 316 317static const int* radioMargins(NSControlSize controlSize) 318{ 319 static const int margins[3][4] = 320 { 321 { 2, 2, 4, 2 }, 322 { 3, 2, 3, 2 }, 323 { 1, 0, 2, 0 }, 324 }; 325 return margins[controlSize]; 326} 327 328static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 329{ 330 // If the width and height are both specified, then we have nothing to do. 331 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 332 return zoomedSize; 333 334 // Use the font size to determine the intrinsic width of the control. 335 return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes()); 336} 337 338static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 339{ 340 static NSButtonCell *radioCell; 341 if (!radioCell) { 342 radioCell = [[NSButtonCell alloc] init]; 343 [radioCell setButtonType:NSRadioButton]; 344 [radioCell setTitle:nil]; 345 [radioCell setFocusRingType:NSFocusRingTypeExterior]; 346 } 347 348 // Set the control size based off the rectangle we're painting into. 349 setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor); 350 351 // Update the various states we respond to. 352 updateStates(radioCell, states); 353 354 return radioCell; 355} 356 357static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 358{ 359 // Determine the width and height needed for the control and prepare the cell for painting. 360 NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor); 361 GraphicsContextStateSaver stateSaver(*context); 362 363 NSControlSize controlSize = [radioCell controlSize]; 364 IntSize zoomedSize = radioSizes()[controlSize]; 365 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 366 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 367 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 368 369 if (zoomFactor != 1.0f) { 370 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 371 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 372 context->translate(inflatedRect.x(), inflatedRect.y()); 373 context->scale(FloatSize(zoomFactor, zoomFactor)); 374 context->translate(-inflatedRect.x(), -inflatedRect.y()); 375 } 376 377 LocalCurrentGraphicsContext localContext(context); 378 BEGIN_BLOCK_OBJC_EXCEPTIONS 379 NSView *view = ThemeMac::ensuredView(scrollView); 380 [radioCell drawWithFrame:NSRect(inflatedRect) inView:view]; 381#if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 382 if (states & FocusState) 383 [radioCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 384#endif 385 [radioCell setControlView:nil]; 386 END_BLOCK_OBJC_EXCEPTIONS 387} 388 389// Buttons 390 391// Buttons really only constrain height. They respect width. 392static const IntSize* buttonSizes() 393{ 394 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; 395 return sizes; 396} 397 398static const int* buttonMargins(NSControlSize controlSize) 399{ 400 static const int margins[3][4] = 401 { 402 { 4, 6, 7, 6 }, 403 { 4, 5, 6, 5 }, 404 { 0, 1, 1, 1 }, 405 }; 406 return margins[controlSize]; 407} 408 409static void setUpButtonCell(NSButtonCell *cell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 410{ 411 // Set the control size based off the rectangle we're painting into. 412 const IntSize* sizes = buttonSizes(); 413 if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) { 414 // Use the square button 415 if ([cell bezelStyle] != NSShadowlessSquareBezelStyle) 416 [cell setBezelStyle:NSShadowlessSquareBezelStyle]; 417 } else if ([cell bezelStyle] != NSRoundedBezelStyle) 418 [cell setBezelStyle:NSRoundedBezelStyle]; 419 420 setControlSize(cell, sizes, zoomedRect.size(), zoomFactor); 421 422 // Update the various states we respond to. 423 updateStates(cell, states); 424} 425 426static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 427{ 428 static NSButtonCell *cell = nil; 429 if (!cell) { 430 cell = [[NSButtonCell alloc] init]; 431 [cell setTitle:nil]; 432 [cell setButtonType:NSMomentaryPushInButton]; 433 } 434 setUpButtonCell(cell, part, states, zoomedRect, zoomFactor); 435 return cell; 436} 437 438static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 439{ 440 BEGIN_BLOCK_OBJC_EXCEPTIONS 441 442 // Determine the width and height needed for the control and prepare the cell for painting. 443 NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor); 444 GraphicsContextStateSaver stateSaver(*context); 445 446 NSControlSize controlSize = [buttonCell controlSize]; 447 IntSize zoomedSize = buttonSizes()[controlSize]; 448 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 449 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 450 IntRect inflatedRect = zoomedRect; 451 if ([buttonCell bezelStyle] == NSRoundedBezelStyle) { 452 // Center the button within the available space. 453 if (inflatedRect.height() > zoomedSize.height()) { 454 inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2); 455 inflatedRect.setHeight(zoomedSize.height()); 456 } 457 458 // Now inflate it to account for the shadow. 459 inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 460 461 if (zoomFactor != 1.0f) { 462 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 463 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 464 context->translate(inflatedRect.x(), inflatedRect.y()); 465 context->scale(FloatSize(zoomFactor, zoomFactor)); 466 context->translate(-inflatedRect.x(), -inflatedRect.y()); 467 } 468 } 469 470 LocalCurrentGraphicsContext localContext(context); 471 NSView *view = ThemeMac::ensuredView(scrollView); 472 NSWindow *window = [view window]; 473 474 [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view]; 475#if !BUTTON_CELL_DRAW_WITH_FRAME_DRAWS_FOCUS_RING 476 if (states & FocusState) 477 [buttonCell _web_drawFocusRingWithFrame:NSRect(inflatedRect) inView:view]; 478#endif 479 [buttonCell setControlView:nil]; 480 481 END_BLOCK_OBJC_EXCEPTIONS 482} 483 484// Stepper 485 486static const IntSize* stepperSizes() 487{ 488 static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) }; 489 return sizes; 490} 491 492// We don't use controlSizeForFont() for steppers because the stepper height 493// should be equal to or less than the corresponding text field height, 494static NSControlSize stepperControlSizeForFont(const Font& font) 495{ 496 int fontSize = font.pixelSize(); 497 if (fontSize >= 18) 498 return NSRegularControlSize; 499 if (fontSize >= 13) 500 return NSSmallControlSize; 501 return NSMiniControlSize; 502} 503 504static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*) 505{ 506 // We don't use NSStepperCell because there are no ways to draw an 507 // NSStepperCell with the up button highlighted. 508 509 HIThemeButtonDrawInfo drawInfo; 510 drawInfo.version = 0; 511 drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states); 512 drawInfo.adornment = kThemeAdornmentDefault; 513 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 514 if (controlSize == NSSmallControlSize) 515 drawInfo.kind = kThemeIncDecButtonSmall; 516 else if (controlSize == NSMiniControlSize) 517 drawInfo.kind = kThemeIncDecButtonMini; 518 else 519 drawInfo.kind = kThemeIncDecButton; 520 521 IntRect rect(zoomedRect); 522 GraphicsContextStateSaver stateSaver(*context); 523 if (zoomFactor != 1.0f) { 524 rect.setWidth(rect.width() / zoomFactor); 525 rect.setHeight(rect.height() / zoomFactor); 526 context->translate(rect.x(), rect.y()); 527 context->scale(FloatSize(zoomFactor, zoomFactor)); 528 context->translate(-rect.x(), -rect.y()); 529 } 530 CGRect bounds(rect); 531 CGRect backgroundBounds; 532 HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds); 533 // Center the stepper rectangle in the specified area. 534 backgroundBounds.origin.x = bounds.origin.x + (bounds.size.width - backgroundBounds.size.width) / 2; 535 if (backgroundBounds.size.height < bounds.size.height) { 536 int heightDiff = clampToInteger(bounds.size.height - backgroundBounds.size.height); 537 backgroundBounds.origin.y = bounds.origin.y + (heightDiff / 2) + 1; 538 } 539 540 LocalCurrentGraphicsContext localContext(context); 541 HIThemeDrawButton(&backgroundBounds, &drawInfo, localContext.cgContext(), kHIThemeOrientationNormal, 0); 542} 543 544// This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView. 545// If the ScrollView doesn't have an NSView, we will return a fake NSView whose sole purpose is to tell AppKit that it's flipped. 546NSView *ThemeMac::ensuredView(ScrollView* scrollView) 547{ 548 549 // Use a fake flipped view. 550 static NSView *flippedView = [[WebCoreFlippedView alloc] init]; 551 [flippedView setFrameSize:NSSizeFromCGSize(scrollView->contentsSize())]; 552 553 return flippedView; 554} 555 556void ThemeMac::setFocusRingClipRect(const FloatRect& rect) 557{ 558 focusRingClipRect = rect; 559} 560 561// Theme overrides 562 563int ThemeMac::baselinePositionAdjustment(ControlPart part) const 564{ 565 if (part == CheckboxPart || part == RadioPart) 566 return -2; 567 return Theme::baselinePositionAdjustment(part); 568} 569 570FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const 571{ 572 switch (part) { 573 case PushButtonPart: { 574 FontDescription fontDescription; 575 fontDescription.setIsAbsoluteSize(true); 576 fontDescription.setGenericFamily(FontDescription::SerifFamily); 577 578 NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]]; 579 fontDescription.firstFamily().setFamily([nsFont webCoreFamilyName]); 580 fontDescription.setComputedSize([nsFont pointSize] * zoomFactor); 581 fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor); 582 return fontDescription; 583 } 584 default: 585 return Theme::controlFont(part, font, zoomFactor); 586 } 587} 588 589LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const 590{ 591 switch (part) { 592 case CheckboxPart: 593 return checkboxSize(font, zoomedSize, zoomFactor); 594 case RadioPart: 595 return radioSize(font, zoomedSize, zoomFactor); 596 case PushButtonPart: 597 // Height is reset to auto so that specified heights can be ignored. 598 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes()); 599 case InnerSpinButtonPart: 600 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 601 return zoomedSize; 602 return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes()); 603 default: 604 return zoomedSize; 605 } 606} 607 608LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const 609{ 610 switch (part) { 611 case SquareButtonPart: 612 case ButtonPart: 613 return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed)); 614 case InnerSpinButtonPart:{ 615 IntSize base = stepperSizes()[NSMiniControlSize]; 616 return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed), 617 Length(static_cast<int>(base.height() * zoomFactor), Fixed)); 618 } 619 default: 620 return Theme::minimumControlSize(part, font, zoomFactor); 621 } 622} 623 624LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 625{ 626 switch (part) { 627 case SquareButtonPart: 628 case ButtonPart: 629 return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value()); 630 default: 631 return Theme::controlBorder(part, font, zoomedBox, zoomFactor); 632 } 633} 634 635LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 636{ 637 switch (part) { 638 case PushButtonPart: { 639 // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large 640 // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is 641 // by definition constrained, since we select mini only for small cramped environments. 642 // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent 643 // padding. 644 const int padding = 8 * zoomFactor; 645 return LengthBox(0, padding, 0, padding); 646 } 647 default: 648 return Theme::controlPadding(part, font, zoomedBox, zoomFactor); 649 } 650} 651 652void ThemeMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const 653{ 654 BEGIN_BLOCK_OBJC_EXCEPTIONS 655 switch (part) { 656 case CheckboxPart: { 657 // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox 658 // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. 659 NSCell *cell = checkbox(states, zoomedRect, zoomFactor); 660 NSControlSize controlSize = [cell controlSize]; 661 IntSize zoomedSize = checkboxSizes()[controlSize]; 662 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 663 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 664 zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 665 break; 666 } 667 case RadioPart: { 668 // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button 669 // shadow". We don't consider this part of the bounds of the control in WebKit. 670 NSCell *cell = radio(states, zoomedRect, zoomFactor); 671 NSControlSize controlSize = [cell controlSize]; 672 IntSize zoomedSize = radioSizes()[controlSize]; 673 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 674 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 675 zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 676 break; 677 } 678 case PushButtonPart: 679 case ButtonPart: { 680 NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor); 681 NSControlSize controlSize = [cell controlSize]; 682 683 // We inflate the rect as needed to account for the Aqua button's shadow. 684 if ([cell bezelStyle] == NSRoundedBezelStyle) { 685 IntSize zoomedSize = buttonSizes()[controlSize]; 686 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 687 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 688 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 689 } 690 break; 691 } 692 case InnerSpinButtonPart: { 693 static const int stepperMargin[4] = { 0, 0, 0, 0 }; 694 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 695 IntSize zoomedSize = stepperSizes()[controlSize]; 696 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 697 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 698 zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor); 699 break; 700 } 701 default: 702 break; 703 } 704 END_BLOCK_OBJC_EXCEPTIONS 705} 706 707void ThemeMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const 708{ 709 switch (part) { 710 case CheckboxPart: 711 paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView); 712 break; 713 case RadioPart: 714 paintRadio(states, context, zoomedRect, zoomFactor, scrollView); 715 break; 716 case PushButtonPart: 717 case ButtonPart: 718 case SquareButtonPart: 719 paintButton(part, states, context, zoomedRect, zoomFactor, scrollView); 720 break; 721 case InnerSpinButtonPart: 722 paintStepper(states, context, zoomedRect, zoomFactor, scrollView); 723 break; 724 default: 725 break; 726 } 727} 728 729} 730