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