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