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 37using 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 74namespace WebCore { 75 76enum { 77 topMargin, 78 rightMargin, 79 bottomMargin, 80 leftMargin 81}; 82 83Theme* 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 91static 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 101static 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 114static LengthSize sizeFromFont(const Font& font, const LengthSize& zoomedSize, float zoomFactor, const IntSize* sizes) 115{ 116 return sizeFromNSControlSize(controlSizeForFont(font), zoomedSize, zoomFactor, sizes); 117} 118 119static 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 130static 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 137static 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 171static 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 187static 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 207static const IntSize* checkboxSizes() 208{ 209 static const IntSize sizes[3] = { IntSize(14, 14), IntSize(12, 12), IntSize(10, 10) }; 210 return sizes; 211} 212 213static 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 224static 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 234static 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. 255static 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 GraphicsContextStateSaver stateSaver(*context); 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 END_BLOCK_OBJC_EXCEPTIONS 283} 284 285// Radio Buttons 286 287static const IntSize* radioSizes() 288{ 289 static const IntSize sizes[3] = { IntSize(14, 15), IntSize(12, 13), IntSize(10, 10) }; 290 return sizes; 291} 292 293static const int* radioMargins(NSControlSize controlSize) 294{ 295 static const int margins[3][4] = 296 { 297 { 2, 2, 4, 2 }, 298 { 3, 2, 3, 2 }, 299 { 1, 0, 2, 0 }, 300 }; 301 return margins[controlSize]; 302} 303 304static LengthSize radioSize(const Font& font, const LengthSize& zoomedSize, float zoomFactor) 305{ 306 // If the width and height are both specified, then we have nothing to do. 307 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 308 return zoomedSize; 309 310 // Use the font size to determine the intrinsic width of the control. 311 return sizeFromFont(font, zoomedSize, zoomFactor, radioSizes()); 312} 313 314static NSButtonCell *radio(ControlStates states, const IntRect& zoomedRect, float zoomFactor) 315{ 316 static NSButtonCell *radioCell; 317 if (!radioCell) { 318 radioCell = [[NSButtonCell alloc] init]; 319 [radioCell setButtonType:NSRadioButton]; 320 [radioCell setTitle:nil]; 321 [radioCell setFocusRingType:NSFocusRingTypeExterior]; 322 } 323 324 // Set the control size based off the rectangle we're painting into. 325 setControlSize(radioCell, radioSizes(), zoomedRect.size(), zoomFactor); 326 327 // Update the various states we respond to. 328 updateStates(radioCell, states); 329 330 return radioCell; 331} 332 333static void paintRadio(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView* scrollView) 334{ 335 // Determine the width and height needed for the control and prepare the cell for painting. 336 NSButtonCell *radioCell = radio(states, zoomedRect, zoomFactor); 337 LocalCurrentGraphicsContext localContext(context); 338 339 GraphicsContextStateSaver stateSaver(*context); 340 341 NSControlSize controlSize = [radioCell controlSize]; 342 IntSize zoomedSize = radioSizes()[controlSize]; 343 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 344 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 345 IntRect inflatedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 346 347 if (zoomFactor != 1.0f) { 348 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 349 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 350 context->translate(inflatedRect.x(), inflatedRect.y()); 351 context->scale(FloatSize(zoomFactor, zoomFactor)); 352 context->translate(-inflatedRect.x(), -inflatedRect.y()); 353 } 354 355 BEGIN_BLOCK_OBJC_EXCEPTIONS 356 [radioCell drawWithFrame:NSRect(inflatedRect) inView:ThemeMac::ensuredView(scrollView)]; 357 [radioCell setControlView:nil]; 358 END_BLOCK_OBJC_EXCEPTIONS 359} 360 361// Buttons 362 363// Buttons really only constrain height. They respect width. 364static const IntSize* buttonSizes() 365{ 366 static const IntSize sizes[3] = { IntSize(0, 21), IntSize(0, 18), IntSize(0, 15) }; 367 return sizes; 368} 369 370#if ENABLE(DATALIST) 371static const IntSize* listButtonSizes() 372{ 373 static const IntSize sizes[3] = { IntSize(21, 21), IntSize(19, 18), IntSize(17, 16) }; 374 return sizes; 375} 376#endif 377 378static const int* buttonMargins(NSControlSize controlSize) 379{ 380 static const int margins[3][4] = 381 { 382 { 4, 6, 7, 6 }, 383 { 4, 5, 6, 5 }, 384 { 0, 1, 1, 1 }, 385 }; 386 return margins[controlSize]; 387} 388 389enum ButtonCellType { NormalButtonCell, DefaultButtonCell }; 390 391static NSButtonCell *leakButtonCell(ButtonCellType type) 392{ 393 NSButtonCell *cell = [[NSButtonCell alloc] init]; 394 [cell setTitle:nil]; 395 [cell setButtonType:NSMomentaryPushInButton]; 396 if (type == DefaultButtonCell) 397 [cell setKeyEquivalent:@"\r"]; 398 return cell; 399} 400 401static void setUpButtonCell(NSButtonCell *cell, ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 402{ 403 // Set the control size based off the rectangle we're painting into. 404 const IntSize* sizes = buttonSizes(); 405#if ENABLE(DATALIST) 406 if (part == ListButtonPart) { 407 [cell setBezelStyle:NSRoundedDisclosureBezelStyle]; 408 sizes = listButtonSizes(); 409 } else 410#endif 411 if (part == SquareButtonPart || zoomedRect.height() > buttonSizes()[NSRegularControlSize].height() * zoomFactor) { 412 // Use the square button 413 if ([cell bezelStyle] != NSShadowlessSquareBezelStyle) 414 [cell setBezelStyle:NSShadowlessSquareBezelStyle]; 415 } else if ([cell bezelStyle] != NSRoundedBezelStyle) 416 [cell setBezelStyle:NSRoundedBezelStyle]; 417 418 setControlSize(cell, sizes, zoomedRect.size(), zoomFactor); 419 420 // Update the various states we respond to. 421 updateStates(cell, states); 422} 423 424static NSButtonCell *button(ControlPart part, ControlStates states, const IntRect& zoomedRect, float zoomFactor) 425{ 426 NSButtonCell *cell; 427 if (states & DefaultState) { 428 static NSButtonCell *defaultCell = leakButtonCell(DefaultButtonCell); 429 cell = defaultCell; 430 } else { 431 static NSButtonCell *normalCell = leakButtonCell(NormalButtonCell); 432 cell = normalCell; 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 LocalCurrentGraphicsContext localContext(context); 445 446 NSControlSize controlSize = [buttonCell controlSize]; 447#if ENABLE(DATALIST) 448 IntSize zoomedSize = (part == ListButtonPart ? listButtonSizes() : buttonSizes())[controlSize]; 449#else 450 IntSize zoomedSize = buttonSizes()[controlSize]; 451#endif 452 zoomedSize.setWidth(zoomedRect.width()); // Buttons don't ever constrain width, so the zoomed width can just be honored. 453 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 454 IntRect inflatedRect = zoomedRect; 455 if ([buttonCell bezelStyle] == NSRoundedBezelStyle) { 456 // Center the button within the available space. 457 if (inflatedRect.height() > zoomedSize.height()) { 458 inflatedRect.setY(inflatedRect.y() + (inflatedRect.height() - zoomedSize.height()) / 2); 459 inflatedRect.setHeight(zoomedSize.height()); 460 } 461 462 // Now inflate it to account for the shadow. 463 inflatedRect = inflateRect(inflatedRect, zoomedSize, buttonMargins(controlSize), zoomFactor); 464 465 if (zoomFactor != 1.0f) { 466 inflatedRect.setWidth(inflatedRect.width() / zoomFactor); 467 inflatedRect.setHeight(inflatedRect.height() / zoomFactor); 468 context->translate(inflatedRect.x(), inflatedRect.y()); 469 context->scale(FloatSize(zoomFactor, zoomFactor)); 470 context->translate(-inflatedRect.x(), -inflatedRect.y()); 471 } 472 } 473 474 NSView *view = ThemeMac::ensuredView(scrollView); 475 NSWindow *window = [view window]; 476 NSButtonCell *previousDefaultButtonCell = [window defaultButtonCell]; 477 478 if (states & DefaultState) { 479 [window setDefaultButtonCell:buttonCell]; 480 wkAdvanceDefaultButtonPulseAnimation(buttonCell); 481 } else if ([previousDefaultButtonCell isEqual:buttonCell]) 482 [window setDefaultButtonCell:nil]; 483 484 [buttonCell drawWithFrame:NSRect(inflatedRect) inView:view]; 485 [buttonCell setControlView:nil]; 486 487 if (![previousDefaultButtonCell isEqual:buttonCell]) 488 [window setDefaultButtonCell:previousDefaultButtonCell]; 489 490 END_BLOCK_OBJC_EXCEPTIONS 491} 492 493// Stepper 494 495static const IntSize* stepperSizes() 496{ 497 static const IntSize sizes[3] = { IntSize(19, 27), IntSize(15, 22), IntSize(13, 15) }; 498 return sizes; 499} 500 501// We don't use controlSizeForFont() for steppers because the stepper height 502// should be equal to or less than the corresponding text field height, 503static NSControlSize stepperControlSizeForFont(const Font& font) 504{ 505 int fontSize = font.pixelSize(); 506 if (fontSize >= 18) 507 return NSRegularControlSize; 508 if (fontSize >= 13) 509 return NSSmallControlSize; 510 return NSMiniControlSize; 511} 512 513static void paintStepper(ControlStates states, GraphicsContext* context, const IntRect& zoomedRect, float zoomFactor, ScrollView*) 514{ 515 // We don't use NSStepperCell because there are no ways to draw an 516 // NSStepperCell with the up button highlighted. 517 518 HIThemeButtonDrawInfo drawInfo; 519 drawInfo.version = 0; 520 drawInfo.state = convertControlStatesToThemeDrawState(kThemeIncDecButton, states); 521 drawInfo.adornment = kThemeAdornmentDefault; 522 ControlSize controlSize = controlSizeFromPixelSize(stepperSizes(), zoomedRect.size(), zoomFactor); 523 if (controlSize == NSSmallControlSize) 524 drawInfo.kind = kThemeIncDecButtonSmall; 525 else if (controlSize == NSMiniControlSize) 526 drawInfo.kind = kThemeIncDecButtonMini; 527 else 528 drawInfo.kind = kThemeIncDecButton; 529 530 IntRect rect(zoomedRect); 531 GraphicsContextStateSaver stateSaver(*context); 532 if (zoomFactor != 1.0f) { 533 rect.setWidth(rect.width() / zoomFactor); 534 rect.setHeight(rect.height() / zoomFactor); 535 context->translate(rect.x(), rect.y()); 536 context->scale(FloatSize(zoomFactor, zoomFactor)); 537 context->translate(-rect.x(), -rect.y()); 538 } 539 CGRect bounds(rect); 540 // Adjust 'bounds' so that HIThemeDrawButton(bounds,...) draws exactly on 'rect'. 541 CGRect backgroundBounds; 542 HIThemeGetButtonBackgroundBounds(&bounds, &drawInfo, &backgroundBounds); 543 if (bounds.origin.x != backgroundBounds.origin.x) 544 bounds.origin.x += bounds.origin.x - backgroundBounds.origin.x; 545 if (bounds.origin.y != backgroundBounds.origin.y) 546 bounds.origin.y += bounds.origin.y - backgroundBounds.origin.y; 547 HIThemeDrawButton(&bounds, &drawInfo, context->platformContext(), kHIThemeOrientationNormal, 0); 548} 549 550// This will ensure that we always return a valid NSView, even if ScrollView doesn't have an associated document NSView. 551// 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. 552NSView *ThemeMac::ensuredView(ScrollView* scrollView) 553{ 554 if (NSView *documentView = scrollView->documentView()) 555 return documentView; 556 557 // Use a fake flipped view. 558 static NSView *flippedView = [[WebCoreFlippedView alloc] init]; 559 560 return flippedView; 561} 562 563// Theme overrides 564 565int ThemeMac::baselinePositionAdjustment(ControlPart part) const 566{ 567 if (part == CheckboxPart || part == RadioPart) 568 return -2; 569 return Theme::baselinePositionAdjustment(part); 570} 571 572FontDescription ThemeMac::controlFont(ControlPart part, const Font& font, float zoomFactor) const 573{ 574 switch (part) { 575 case PushButtonPart: { 576 FontDescription fontDescription; 577 fontDescription.setIsAbsoluteSize(true); 578 fontDescription.setGenericFamily(FontDescription::SerifFamily); 579 580 NSFont* nsFont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:controlSizeForFont(font)]]; 581 fontDescription.firstFamily().setFamily([nsFont familyName]); 582 fontDescription.setComputedSize([nsFont pointSize] * zoomFactor); 583 fontDescription.setSpecifiedSize([nsFont pointSize] * zoomFactor); 584 return fontDescription; 585 } 586 default: 587 return Theme::controlFont(part, font, zoomFactor); 588 } 589} 590 591LengthSize ThemeMac::controlSize(ControlPart part, const Font& font, const LengthSize& zoomedSize, float zoomFactor) const 592{ 593 switch (part) { 594 case CheckboxPart: 595 return checkboxSize(font, zoomedSize, zoomFactor); 596 case RadioPart: 597 return radioSize(font, zoomedSize, zoomFactor); 598 case PushButtonPart: 599 // Height is reset to auto so that specified heights can be ignored. 600 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, buttonSizes()); 601#if ENABLE(DATALIST) 602 case ListButtonPart: 603 return sizeFromFont(font, LengthSize(zoomedSize.width(), Length()), zoomFactor, listButtonSizes()); 604#endif 605 case InnerSpinButtonPart: 606 // We don't use inner spin buttons on Mac. 607 return LengthSize(Length(Fixed), Length(Fixed)); 608 case OuterSpinButtonPart: 609 if (!zoomedSize.width().isIntrinsicOrAuto() && !zoomedSize.height().isIntrinsicOrAuto()) 610 return zoomedSize; 611 return sizeFromNSControlSize(stepperControlSizeForFont(font), zoomedSize, zoomFactor, stepperSizes()); 612 default: 613 return zoomedSize; 614 } 615} 616 617LengthSize ThemeMac::minimumControlSize(ControlPart part, const Font& font, float zoomFactor) const 618{ 619 switch (part) { 620 case SquareButtonPart: 621 case DefaultButtonPart: 622 case ButtonPart: 623 case ListButtonPart: 624 return LengthSize(Length(0, Fixed), Length(static_cast<int>(15 * zoomFactor), Fixed)); 625 case InnerSpinButtonPart: 626 // We don't use inner spin buttons on Mac. 627 return LengthSize(Length(Fixed), Length(Fixed)); 628 case OuterSpinButtonPart: { 629 IntSize base = stepperSizes()[NSMiniControlSize]; 630 return LengthSize(Length(static_cast<int>(base.width() * zoomFactor), Fixed), 631 Length(static_cast<int>(base.height() * zoomFactor), Fixed)); 632 } 633 default: 634 return Theme::minimumControlSize(part, font, zoomFactor); 635 } 636} 637 638LengthBox ThemeMac::controlBorder(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 639{ 640 switch (part) { 641 case SquareButtonPart: 642 case DefaultButtonPart: 643 case ButtonPart: 644 case ListButtonPart: 645 return LengthBox(0, zoomedBox.right().value(), 0, zoomedBox.left().value()); 646 default: 647 return Theme::controlBorder(part, font, zoomedBox, zoomFactor); 648 } 649} 650 651LengthBox ThemeMac::controlPadding(ControlPart part, const Font& font, const LengthBox& zoomedBox, float zoomFactor) const 652{ 653 switch (part) { 654 case PushButtonPart: { 655 // Just use 8px. AppKit wants to use 11px for mini buttons, but that padding is just too large 656 // for real-world Web sites (creating a huge necessary minimum width for buttons whose space is 657 // by definition constrained, since we select mini only for small cramped environments. 658 // This also guarantees the HTML <button> will match our rendering by default, since we're using a consistent 659 // padding. 660 const int padding = 8 * zoomFactor; 661 return LengthBox(0, padding, 0, padding); 662 } 663 default: 664 return Theme::controlPadding(part, font, zoomedBox, zoomFactor); 665 } 666} 667 668void ThemeMac::inflateControlPaintRect(ControlPart part, ControlStates states, IntRect& zoomedRect, float zoomFactor) const 669{ 670 BEGIN_BLOCK_OBJC_EXCEPTIONS 671 switch (part) { 672 case CheckboxPart: { 673 // We inflate the rect as needed to account for padding included in the cell to accommodate the checkbox 674 // shadow" and the check. We don't consider this part of the bounds of the control in WebKit. 675 NSCell *cell = checkbox(states, zoomedRect, zoomFactor); 676 NSControlSize controlSize = [cell controlSize]; 677 IntSize zoomedSize = checkboxSizes()[controlSize]; 678 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 679 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 680 zoomedRect = inflateRect(zoomedRect, zoomedSize, checkboxMargins(controlSize), zoomFactor); 681 break; 682 } 683 case RadioPart: { 684 // We inflate the rect as needed to account for padding included in the cell to accommodate the radio button 685 // shadow". We don't consider this part of the bounds of the control in WebKit. 686 NSCell *cell = radio(states, zoomedRect, zoomFactor); 687 NSControlSize controlSize = [cell controlSize]; 688 IntSize zoomedSize = radioSizes()[controlSize]; 689 zoomedSize.setHeight(zoomedSize.height() * zoomFactor); 690 zoomedSize.setWidth(zoomedSize.width() * zoomFactor); 691 zoomedRect = inflateRect(zoomedRect, zoomedSize, radioMargins(controlSize), zoomFactor); 692 break; 693 } 694 case PushButtonPart: 695 case DefaultButtonPart: 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 OuterSpinButtonPart: { 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 DefaultButtonPart: 735 case ButtonPart: 736 case SquareButtonPart: 737 case ListButtonPart: 738 paintButton(part, states, context, zoomedRect, zoomFactor, scrollView); 739 break; 740 case OuterSpinButtonPart: 741 paintStepper(states, context, zoomedRect, zoomFactor, scrollView); 742 break; 743 default: 744 break; 745 } 746} 747 748} 749