1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2009 Google Inc. All Rights Reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #import "config.h" 28 #import "ThemeChromiumMac.h" 29 30 #import "BlockExceptions.h" 31 #import "GraphicsContext.h" 32 #import "LocalCurrentGraphicsContext.h" 33 #import "ScrollView.h" 34 #import "WebCoreSystemInterface.h" 35 #import <Carbon/Carbon.h> 36 #include <wtf/StdLibExtras.h> 37 #import <objc/runtime.h> 38 39 using namespace std; 40 41 // This file (and its associated .h file) is a clone of ThemeMac.mm. 42 // Because the original file is designed to run in-process inside a Cocoa view, 43 // we must maintain a fork. Please maintain this file by performing parallel 44 // changes to it. 45 // 46 // The only changes from ThemeMac should be: 47 // - The classname change from ThemeMac to ThemeChromiumMac. 48 // - The import of FlippedView() and its use as the parent view for cell 49 // rendering. 50 // - In updateStates() the code to update the cells' inactive state. 51 // - In paintButton() the code to save/restore the window's default button cell. 52 // - The Snow Leopard focus ring bug fix and its use around every call to 53 // -[NSButtonCell drawWithFrame:inView:]. 54 // 55 // For all other differences, if it was introduced in this file, then the 56 // maintainer forgot to include it in the list; otherwise it is an update that 57 // should have been applied to this file but was not. 58 59 // FIXME: Default buttons really should be more like push buttons and not like buttons. 60 61 // --- START fix for Snow Leopard focus ring bug --- 62 63 // There is a bug in the Cocoa focus ring drawing code. The code calls +[NSView 64 // focusView] (to get the currently focused view) and then calls an NSRect- 65 // returning method on that view to obtain a clipping rect. However, if there is 66 // no focused view (as there won't be if the destination is a context), the rect 67 // returned from the method invocation on nil is garbage. 68 // 69 // The garbage fortunately does not clip the focus ring on Leopard, but 70 // unfortunately does so on Snow Leopard. Therefore, if a runtime test shows 71 // that focus ring drawing fails, we swizzle NSView to ensure it returns a valid 72 // view with a valid clipping rectangle. 73 // 74 // FIXME: After the referenced bug is fixed on all supported platforms, remove 75 // this code. 76 // 77 // References: 78 // <http://crbug.com/27493> 79 // <rdar://problem/7604051> (<http://openradar.appspot.com/7604051>) 80 81 @interface TCMVisibleView : NSView 82 83 @end 84 85 @implementation TCMVisibleView 86 87 - (struct CGRect)_focusRingVisibleRect 88 { 89 return CGRectZero; 90 } 91 92 - (id)_focusRingClipAncestor 93 { 94 return self; 95 } 96 97 @end 98 99 @interface NSView (TCMInterposing) 100 + (NSView *)TCMInterposing_focusView; 101 @end 102 103 namespace FocusIndicationFix { 104 105 bool currentOSHasSetFocusRingStyleInBitmapBug() 106 { 107 UInt32 pixel = 0; 108 UInt32* pixelPlane = &pixel; 109 UInt32** pixelPlanes = &pixelPlane; 110 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(UInt8**)pixelPlanes 111 pixelsWide:1 112 pixelsHigh:1 113 bitsPerSample:8 114 samplesPerPixel:4 115 hasAlpha:YES 116 isPlanar:NO 117 colorSpaceName:NSCalibratedRGBColorSpace 118 bitmapFormat:NSAlphaFirstBitmapFormat 119 bytesPerRow:4 120 bitsPerPixel:32]; 121 [NSGraphicsContext saveGraphicsState]; 122 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]]; 123 NSSetFocusRingStyle(NSFocusRingOnly); 124 NSRectFill(NSMakeRect(0, 0, 1, 1)); 125 [NSGraphicsContext restoreGraphicsState]; 126 [bitmap release]; 127 128 return !pixel; 129 } 130 131 bool swizzleFocusView() 132 { 133 if (!currentOSHasSetFocusRingStyleInBitmapBug()) 134 return false; 135 136 Class nsview = [NSView class]; 137 Method m1 = class_getClassMethod(nsview, @selector(focusView)); 138 Method m2 = class_getClassMethod(nsview, @selector(TCMInterposing_focusView)); 139 if (m1 && m2) { 140 method_exchangeImplementations(m1, m2); 141 return true; 142 } 143 144 return false; 145 } 146 147 static bool interpose = false; 148 149 // A class to restrict the amount of time spent messing with interposing. It 150 // only stacks one-deep. 151 class ScopedFixer { 152 public: 153 ScopedFixer() 154 { 155 static bool swizzled = swizzleFocusView(); 156 interpose = swizzled; 157 } 158 159 ~ScopedFixer() 160 { 161 interpose = false; 162 } 163 }; 164 165 } // namespace FocusIndicationFix 166 167 @implementation NSView (TCMInterposing) 168 169 + (NSView *)TCMInterposing_focusView 170 { 171 NSView *view = [self TCMInterposing_focusView]; // call original (was swizzled) 172 if (!view && FocusIndicationFix::interpose) { 173 static TCMVisibleView* fixedView = [[TCMVisibleView alloc] init]; 174 view = fixedView; 175 } 176 177 return view; 178 } 179 180 @end 181 182 // --- END fix for Snow Leopard focus ring bug --- 183 184 namespace WebCore { 185 186 // Pick up utility function from RenderThemeChromiumMac. 187 extern NSView* FlippedView(); 188 189 enum { 190 topMargin, 191 rightMargin, 192 bottomMargin, 193 leftMargin 194 }; 195 196 Theme* platformTheme() 197 { 198 DEFINE_STATIC_LOCAL(ThemeChromiumMac, themeMac, ()); 199 return &themeMac; 200 } 201 202 // Helper functions used by a bunch of different control parts. 203 204 static NSControlSize controlSizeForFont(const Font& font) 205 { 206 int fontSize = font.pixelSize(); 207 if (fontSize >= 16) 208 return NSRegularControlSize; 209 if (fontSize >= 11) 210 return NSSmallControlSize; 211 return NSMiniControlSize; 212 } 213 214 static LengthSize sizeFromNSControlSize(NSControlSize nsControlSize, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 215 { 216 IntSize controlSize = sizes[nsControlSize]; 217 if (zoomFactor != 1.0f) 218 controlSize = IntSize(controlSize.width() * zoomFactor, controlSize.height() * zoomFactor); 219 LengthSize result = zoomedSize; 220 if (zoomedSize.width().isIntrinsicOrAuto() && controlSize.width() > 0) 221 result.setWidth(Length(controlSize.width(), Fixed)); 222 if (zoomedSize.height().isIntrinsicOrAuto() && controlSize.height() > 0) 223 result.setHeight(Length(controlSize.height(), Fixed)); 224 return result; 225 } 226 227 static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 228 { 229 return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes); 230 } 231 232 static ControlSize controlSizeFromPixelSize(const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 233 { 234 if (minZoomedSize.width() >= static_cast<int>(sizes[NSRegularControlSize].width() * zoomFactor) && 235 minZoomedSize.height() >= static_cast<int>(sizes[NSRegularControlSize].height() * zoomFactor)) 236 return NSRegularControlSize; 237 if (minZoomedSize.width() >= static_cast<int>(sizes[NSSmallControlSize].width() * zoomFactor) && 238 minZoomedSize.height() >= static_cast<int>(sizes[NSSmallControlSize].height() * zoomFactor)) 239 return NSSmallControlSize; 240 return NSMiniControlSize; 241 } 242 243 static void setControlSize(NSCell* cell, const IntSize* sizes, const IntSize& minZoomedSize, float zoomFactor) 244 { 245 ControlSize size = controlSizeFromPixelSize(sizes, minZoomedSize, zoomFactor); 246 if (size != [cell controlSize]) // Only update if we have to, since AppKit does work even if the size is the same. 247 [cell setControlSize:(NSControlSize)size]; 248 } 249 250 static void updateStates(NSCell* cell, ControlStates states) 251 { 252 // Hover state is not supported by Aqua. 253 254 // Pressed state 255 bool oldPressed = [cell isHighlighted]; 256 bool pressed = states & PressedState; 257 if (pressed != oldPressed) 258 [cell setHighlighted:pressed]; 259 260 // Enabled state 261 bool oldEnabled = [cell isEnabled]; 262 bool enabled = states & EnabledState; 263 if (enabled != oldEnabled) 264 [cell setEnabled:enabled]; 265 266 // Focused state 267 bool oldFocused = [cell showsFirstResponder]; 268 bool focused = states & FocusState; 269 if (focused != oldFocused) 270 [cell setShowsFirstResponder:focused]; 271 272 // Checked and Indeterminate 273 bool oldIndeterminate = [cell state] == NSMixedState; 274 bool indeterminate = (states & IndeterminateState); 275 bool checked = states & CheckedState; 276 bool oldChecked = [cell state] == NSOnState; 277 if (oldIndeterminate != indeterminate || checked != oldChecked) 278 [cell setState:indeterminate ? NSMixedState : (checked ? NSOnState : NSOffState)]; 279 280 // Window Inactive state 281 NSControlTint oldTint = [cell controlTint]; 282 bool windowInactive = (states & WindowInactiveState); 283 NSControlTint tint = windowInactive ? static_cast<NSControlTint>(NSClearControlTint) 284 : [NSColor currentControlTint]; 285 if (tint != oldTint) 286 [cell setControlTint:tint]; 287 } 288 289 static ThemeDrawState convertControlStatesToThemeDrawState(ThemeButtonKind kind, ControlStates states) 290 { 291 if (states & ReadOnlyState) 292 return kThemeStateUnavailableInactive; 293 if (!(states & EnabledState)) 294 return kThemeStateUnavailableInactive; 295 296 // Do not process PressedState if !EnabledState or ReadOnlyState. 297 if (states & PressedState) { 298 if (kind == kThemeIncDecButton || kind == kThemeIncDecButtonSmall || kind == kThemeIncDecButtonMini) 299 return states & SpinUpState ? kThemeStatePressedUp : kThemeStatePressedDown; 300 return kThemeStatePressed; 301 } 302 return kThemeStateActive; 303 } 304 305 static IntRect inflateRect(const IntRect& zoomedRect, const IntSize& zoomedSize, const int* margins, float zoomFactor) 306 { 307 // Only do the inflation if the available width/height are too small. Otherwise try to 308 // fit the glow/check space into the available box's width/height. 309 int widthDelta = zoomedRect.width() - (zoomedSize.width() + margins[leftMargin] * zoomFactor + margins[rightMargin] * zoomFactor); 310 int heightDelta = zoomedRect.height() - (zoomedSize.height() + margins[topMargin] * zoomFactor + margins[bottomMargin] * zoomFactor); 311 IntRect result(zoomedRect); 312 if (widthDelta < 0) { 313 result.setX(result.x() - margins[leftMargin] * zoomFactor); 314 result.setWidth(result.width() - widthDelta); 315 } 316 if (heightDelta < 0) { 317 result.setY(result.y() - margins[topMargin] * zoomFactor); 318 result.setHeight(result.height() - heightDelta); 319 } 320 return result; 321 } 322 323 // Checkboxes 324 325 static const IntSize* checkboxSizes() 326 { 327 static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; 328 return sizes; 329 } 330 331 static const int* checkboxMargins(NSControlSize controlSize) 332 { 333 static const int margins[3][4] = 334 { 335 { 3, 4, 4, 2 }, 336 { 4, 3, 3, 3 }, 337 { 4, 3, 3, 3 }, 338 }; 339 return margins[controlSize]; 340 } 341 342 static LengthSize checkboxSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 343 { 344 // If the width and height are both specified, then we have nothing to do. 345 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 346 return zoomedSize; 347 348 // Use the font size to determine the intrinsic width of the control. 349 return sizeFromFont(font, zoomedSize, zoomFactor, checkboxSizes()); 350 } 351 352 static NSButtonCell *checkbox(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 353 { 354 static NSButtonCell *checkboxCell; 355 if (!checkboxCell) { 356 checkboxCell = [[NSButtonCell alloc] init]; 357 [checkboxCell setButtonType:NSSwitchButton]; 358 [checkboxCell setTitle:nil]; 359 [checkboxCell setAllowsMixedState:YES]; 360 [checkboxCell setFocusRingType:NSFocusRingTypeExterior]; 361 } 362 363 // Set the control size based off the rectangle we're painting into. 364 setControlSize(checkboxCell, checkboxSizes(), zoomedRect.size(), zoomFactor); 365 366 // Update the various states we respond to. 367 updateStates(checkboxCell, states); 368 369 return checkboxCell; 370 } 371 372 // FIXME: Share more code with radio buttons. 373 static void paintCheckbox(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 374 { 375 BEGIN_BLOCK_OBJC_EXCEPTIONS 376 377 // Determine the width and height needed for the control and prepare the cell for painting. 378 NSButtonCell *checkboxCell = checkbox(states, zoomedRect, zoomFactor); 379 LocalCurrentGraphicsContext localContext(context); 380 381 context->save(); 382 383 NSControlSize controlSize = [checkboxCell controlSize]; 384 IntSize zoomedSize = checkboxSizes()[controlSize]; 385 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 386 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 387 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 388 389 if (zoomFactor != 1.0f) { 390 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 391 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 392 context->translate(inflatedRect.x(), inflatedRect.y()); 393 context->scale(FloatSize(zoomFactor, zoomFactor)); 394 context->translate(-inflatedRect.x(), -inflatedRect.y()); 395 } 396 397 { 398 FocusIndicationFix::ScopedFixer fix; 399 [checkboxCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()]; 400 } 401 [checkboxCell setControlView:nil]; 402 403 context->restore(); 404 405 END_BLOCK_OBJC_EXCEPTIONS 406 } 407 408 // Radio Buttons 409 410 static const IntSize* radioSizes() 411 { 412 static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; 413 return sizes; 414 } 415 416 static const int* radioMargins(NSControlSize controlSize) 417 { 418 static const int margins[3][4] = 419 { 420 { 2, 2, 4, 2 }, 421 { 3, 2, 3, 2 }, 422 { 1, 0, 2, 0 }, 423 }; 424 return margins[controlSize]; 425 } 426 427 static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 428 { 429 // If the width and height are both specified, then we have nothing to do. 430 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 431 return zoomedSize; 432 433 // Use the font size to determine the intrinsic width of the control. 434 return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes()); 435 } 436 437 static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 438 { 439 static NSButtonCell *radioCell; 440 if (!radioCell) { 441 radioCell = [[NSButtonCell alloc] init]; 442 [radioCell setButtonType:NSRadioButton]; 443 [radioCell setTitle:nil]; 444 [radioCell setFocusRingType:NSFocusRingTypeExterior]; 445 } 446 447 // Set the control size based off the rectangle we're painting into. 448 setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor); 449 450 // Update the various states we respond to. 451 updateStates(radioCell, states); 452 453 return radioCell; 454 } 455 456 static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 457 { 458 // Determine the width and height needed for the control and prepare the cell for painting. 459 NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor); 460 LocalCurrentGraphicsContext localContext(context); 461 462 context->save(); 463 464 NSControlSize controlSize = [radioCell controlSize]; 465 IntSize zoomedSize = radioSizes()[controlSize]; 466 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 467 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 468 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 469 470 if (zoomFactor != 1.0f) { 471 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 472 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 473 context->translate(inflatedRect.x(), inflatedRect.y()); 474 context->scale(FloatSize(zoomFactor, zoomFactor)); 475 context->translate(-inflatedRect.x(), -inflatedRect.y()); 476 } 477 478 BEGIN_BLOCK_OBJC_EXCEPTIONS 479 { 480 FocusIndicationFix::ScopedFixer fix; 481 [radioCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()]; 482 } 483 [radioCell setControlView:nil]; 484 END_BLOCK_OBJC_EXCEPTIONS 485 486 context->restore(); 487 } 488 489 // Buttons 490 491 // Buttons really only constrain height. They respect width. 492 static const IntSize* buttonSizes() 493 { 494 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; 495 return sizes; 496 } 497 498 #if ENABLE(DATALIST) 499 static const IntSize* listButtonSizes() 500 { 501 static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) }; 502 return sizes; 503 } 504 #endif 505 506 static const int* buttonMargins(NSControlSize controlSize) 507 { 508 static const int margins[3][4] = 509 { 510 { 4, 6, 7, 6 }, 511 { 4, 5, 6, 5 }, 512 { 0, 1, 1, 1 }, 513 }; 514 return margins[controlSize]; 515 } 516 517 static void setupButtonCell(NSButtonCell *&buttonCell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 518 { 519 if (!buttonCell) { 520 buttonCell = [[NSButtonCell alloc] init]; 521 [buttonCell setTitle:nil]; 522 [buttonCell setButtonType:NSMomentaryPushInButton]; 523 if (states & DefaultState) 524 [buttonCell setKeyEquivalent:@"\r"]; 525 } 526 527 // Set the control size based off the rectangle we're painting into. 528 const IntSize* sizes = buttonSizes(); 529 #if ENABLE(DATALIST) 530 if (part == ListButtonPart) { 531 [buttonCell setBezelStyle:NSRoundedDisclosureBezelStyle]; 532 sizes = listButtonSizes(); 533 } else 534 #endif 535 if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) { 536 // Use the square button 537 if ([buttonCell bezelStyle] != NSShadowlessSquareBezelStyle) 538 [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle]; 539 } else if ([buttonCell bezelStyle] != NSRoundedBezelStyle) 540 [buttonCell setBezelStyle:NSRoundedBezelStyle]; 541 542 setControlSize(buttonCell, sizes, zoomedRect.size(), zoomFactor); 543 544 // Update the various states we respond to. 545 updateStates(buttonCell, states); 546 } 547 548 static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 549 { 550 bool isDefault = states & DefaultState; 551 static NSButtonCell *cells[2]; 552 setupButtonCell(cells[isDefault], part, states, zoomedRect, zoomFactor); 553 return cells[isDefault]; 554 } 555 556 static void paintButton(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 557 { 558 BEGIN_BLOCK_OBJC_EXCEPTIONS 559 560 // Determine the width and height needed for the control and prepare the cell for painting. 561 NSButtonCell *buttonCell = button(part, states, zoomedRect, zoomFactor); 562 LocalCurrentGraphicsContext localContext(context); 563 564 NSControlSize controlSize = [buttonCell controlSize]; 565 #if ENABLE(DATALIST) 566 IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize]; 567 #else 568 IntSize zoomedSize = buttonSizes()[controlSize]; 569 #endif 570 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 571 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 572 IntRect inflatedRect = zoomedRect; 573 if ([buttonCell bezelStyle] == NSRoundedBezelStyle) { 574 // Center the button within the available space. 575 if (inflatedRect.height() > zoomedSize.height()) { 576 inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2); 577 inflatedRect.setHeight(zoomedSize.height()); 578 } 579 580 // Now inflate it to account for the shadow. 581 inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 582 583 if (zoomFactor != 1.0f) { 584 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 585 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 586 context->translate(inflatedRect.x(), inflatedRect.y()); 587 context->scale(FloatSize(zoomFactor, zoomFactor)); 588 context->translate(-inflatedRect.x(), -inflatedRect.y()); 589 } 590 } 591 592 { 593 FocusIndicationFix::ScopedFixer fix; 594 [buttonCell drawWithFrame:NSRect(inflatedRect) inView:FlippedView()]; 595 } 596 [buttonCell setControlView:nil]; 597 598 END_BLOCK_OBJC_EXCEPTIONS 599 } 600 601 // Stepper 602 603 static const IntSize* stepperSizes() 604 { 605 static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) }; 606 return sizes; 607 } 608 609 // We don't use controlSizeForFont() for steppers because the stepper height 610 // should be equal to or less than the corresponding text field height, 611 static NSControlSize stepperControlSizeForFont(const Font& font) 612 { 613 int fontSize = font.pixelSize(); 614 if (fontSize >= 18) 615 return NSRegularControlSize; 616 if (fontSize >= 13) 617 return NSSmallControlSize; 618 return NSMiniControlSize; 619 } 620 621 static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*) 622 { 623 // We don't use NSStepperCell because there are no ways to draw an 624 // NSStepperCell with the up button highlighted. 625 626 HIThemeButtonDrawInfo drawInfo; 627 drawInfo.version = 0; 628 drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states); 629 drawInfo.adornment = kThemeAdornmentDefault; 630 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 631 if (controlSize == NSSmallControlSize) 632 drawInfo.kind = kThemeIncDecButtonSmall; 633 else if (controlSize == NSMiniControlSize) 634 drawInfo.kind = kThemeIncDecButtonMini; 635 else 636 drawInfo.kind = kThemeIncDecButton; 637 638 IntRect rect(zoomedRect); 639 context->save(); 640 if (zoomFactor != 1.0f) { 641 rect.setWidth(rect.width() / zoomFactor); 642 rect.setHeight(rect.height() / zoomFactor); 643 context->translate(rect.x(), rect.y()); 644 context->scale(FloatSize(zoomFactor, zoomFactor)); 645 context->translate(-rect.x(), -rect.y()); 646 } 647 CGRect bounds(rect); 648 // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'. 649 CGRect backgroundBounds; 650 HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds); 651 if (bounds.origin.x != backgroundBounds.origin.x) 652 bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x; 653 if (bounds.origin.y != backgroundBounds.origin.y) 654 bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y; 655 HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0); 656 context->restore(); 657 } 658 659 // Theme overrides 660 661 int ThemeChromiumMac::baselinePositionAdjustment(ControlPart part) const 662 { 663 if (part == CheckboxPart || part == RadioPart) 664 return -2; 665 return Theme::baselinePositionAdjustment(part); 666 } 667 668 FontDescription ThemeChromiumMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const 669 { 670 switch (part) { 671 case PushButtonPart: { 672 FontDescription fontDescription; 673 fontDescription.setIsAbsoluteSize(true); 674 fontDescription.setGenericFamily(FontDescription::SerifFamily); 675 676 NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]]; 677 fontDescription.firstFamily().setFamily([nsFont familyName]); 678 fontDescription.setComputedSize([nsFont pointSize] * zoomFactor); 679 fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor); 680 return fontDescription; 681 } 682 default: 683 return Theme::controlFont(part, font, zoomFactor); 684 } 685 } 686 687 LengthSize ThemeChromiumMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const 688 { 689 switch (part) { 690 case CheckboxPart: 691 return checkboxSize(font, zoomedSize, zoomFactor); 692 case RadioPart: 693 return radioSize(font, zoomedSize, zoomFactor); 694 case PushButtonPart: 695 // Height is reset to auto so that specified heights can be ignored. 696 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes()); 697 #if ENABLE(DATALIST) 698 case ListButtonPart: 699 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes()); 700 #endif 701 case InnerSpinButtonPart: 702 // We don't use inner spin buttons on Mac. 703 return LengthSize(Length(Fixed), Length(Fixed)); 704 case OuterSpinButtonPart: 705 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 706 return zoomedSize; 707 return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes()); 708 default: 709 return zoomedSize; 710 } 711 } 712 713 LengthSize ThemeChromiumMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const 714 { 715 switch (part) { 716 case SquareButtonPart: 717 case DefaultButtonPart: 718 case ButtonPart: 719 case ListButtonPart: 720 return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed)); 721 case InnerSpinButtonPart: 722 // We don't use inner spin buttons on Mac. 723 return LengthSize(Length(Fixed), Length(Fixed)); 724 case OuterSpinButtonPart: { 725 IntSize base = stepperSizes()[NSMiniControlSize]; 726 return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed), 727 Length(static_cast<int>(base.height() * zoomFactor), Fixed)); 728 } 729 default: 730 return Theme::minimumControlSize(part, font, zoomFactor); 731 } 732 } 733 734 LengthBox ThemeChromiumMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 735 { 736 switch (part) { 737 case SquareButtonPart: 738 case DefaultButtonPart: 739 case ButtonPart: 740 case ListButtonPart: 741 return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value()); 742 default: 743 return Theme::controlBorder(part, font, zoomedBox, zoomFactor); 744 } 745 } 746 747 LengthBox ThemeChromiumMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 748 { 749 switch (part) { 750 case PushButtonPart: { 751 // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large 752 // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is 753 // by definition constrained, since we select mini only for small cramped environments. 754 // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent 755 // padding. 756 const int padding = 8 * zoomFactor; 757 return LengthBox(0, padding, 0, padding); 758 } 759 default: 760 return Theme::controlPadding(part, font, zoomedBox, zoomFactor); 761 } 762 } 763 764 void ThemeChromiumMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const 765 { 766 BEGIN_BLOCK_OBJC_EXCEPTIONS 767 switch (part) { 768 case CheckboxPart: { 769 // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox 770 // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. 771 NSCell *cell = checkbox(states, zoomedRect, zoomFactor); 772 NSControlSize controlSize = [cell controlSize]; 773 IntSize zoomedSize = checkboxSizes()[controlSize]; 774 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 775 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 776 zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 777 break; 778 } 779 case RadioPart: { 780 // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button 781 // shadow". We don't consider this part of the bounds of the control in WebKit. 782 NSCell *cell = radio(states, zoomedRect, zoomFactor); 783 NSControlSize controlSize = [cell controlSize]; 784 IntSize zoomedSize = radioSizes()[controlSize]; 785 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 786 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 787 zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 788 break; 789 } 790 case PushButtonPart: 791 case DefaultButtonPart: 792 case ButtonPart: { 793 NSButtonCell *cell = button(part, states, zoomedRect, zoomFactor); 794 NSControlSize controlSize = [cell controlSize]; 795 796 // We inflate the rect as needed to account for the Aqua button's shadow. 797 if ([cell bezelStyle] == NSRoundedBezelStyle) { 798 IntSize zoomedSize = buttonSizes()[controlSize]; 799 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 800 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 801 zoomedRect = inflateRect(zoomedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 802 } 803 break; 804 } 805 case OuterSpinButtonPart: { 806 static const int stepperMargin[4] = { 0, 0, 0, 0 }; 807 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 808 IntSize zoomedSize = stepperSizes()[controlSize]; 809 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 810 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 811 zoomedRect = inflateRect(zoomedRect, zoomedSize, stepperMargin, zoomFactor); 812 break; 813 } 814 default: 815 break; 816 } 817 END_BLOCK_OBJC_EXCEPTIONS 818 } 819 820 void ThemeChromiumMac::paint(ControlPart part, ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) const 821 { 822 switch (part) { 823 case CheckboxPart: 824 paintCheckbox(states, context, zoomedRect, zoomFactor, scrollView); 825 break; 826 case RadioPart: 827 paintRadio(states, context, zoomedRect, zoomFactor, scrollView); 828 break; 829 case PushButtonPart: 830 case DefaultButtonPart: 831 case ButtonPart: 832 case SquareButtonPart: 833 case ListButtonPart: 834 paintButton(part, states, context, zoomedRect, zoomFactor, scrollView); 835 break; 836 case OuterSpinButtonPart: 837 paintStepper(states, context, zoomedRect, zoomFactor, scrollView); 838 break; 839 default: 840 break; 841 } 842 } 843 844 } 845