1/**************************************************************************** 2** 3** Copyright (C) 2016 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the QtWidgets module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40/* 41 Note: The qdoc comments for QMacStyle are contained in 42 .../doc/src/qstyles.qdoc. 43*/ 44 45#include <AppKit/AppKit.h> 46 47#include "qmacstyle_mac_p.h" 48#include "qmacstyle_mac_p_p.h" 49 50#define QMAC_QAQUASTYLE_SIZE_CONSTRAIN 51//#define DEBUG_SIZE_CONSTRAINT 52 53#include <QtCore/qoperatingsystemversion.h> 54#include <QtCore/qvariant.h> 55#include <QtCore/qvarlengtharray.h> 56 57#include <QtCore/private/qcore_mac_p.h> 58 59#include <QtGui/qpainterpath.h> 60#include <QtGui/private/qcoregraphics_p.h> 61#include <QtGui/qpa/qplatformfontdatabase.h> 62#include <QtGui/qpa/qplatformtheme.h> 63 64#include <QtWidgets/private/qstyleanimation_p.h> 65 66#if QT_CONFIG(mdiarea) 67#include <QtWidgets/qmdisubwindow.h> 68#endif 69#if QT_CONFIG(scrollbar) 70#include <QtWidgets/qscrollbar.h> 71#endif 72#if QT_CONFIG(tabbar) 73#include <QtWidgets/private/qtabbar_p.h> 74#endif 75#if QT_CONFIG(wizard) 76#include <QtWidgets/qwizard.h> 77#endif 78 79#include <cmath> 80 81QT_USE_NAMESPACE 82 83static QWindow *qt_getWindow(const QWidget *widget) 84{ 85 return widget ? widget->window()->windowHandle() : 0; 86} 87 88@interface QT_MANGLE_NAMESPACE(QIndeterminateProgressIndicator) : NSProgressIndicator 89 90@property (readonly, nonatomic) NSInteger animators; 91 92- (instancetype)init; 93 94- (void)startAnimation; 95- (void)stopAnimation; 96 97- (void)drawWithFrame:(CGRect)rect inView:(NSView *)view; 98 99@end 100 101QT_NAMESPACE_ALIAS_OBJC_CLASS(QIndeterminateProgressIndicator); 102 103@implementation QIndeterminateProgressIndicator 104 105- (instancetype)init 106{ 107 if ((self = [super init])) { 108 _animators = 0; 109 self.indeterminate = YES; 110 self.usesThreadedAnimation = NO; 111 self.alphaValue = 0.0; 112 } 113 114 return self; 115} 116 117- (void)startAnimation 118{ 119 if (_animators == 0) { 120 self.hidden = NO; 121 [super startAnimation:self]; 122 } 123 ++_animators; 124} 125 126- (void)stopAnimation 127{ 128 --_animators; 129 if (_animators == 0) { 130 [super stopAnimation:self]; 131 self.hidden = YES; 132 [self removeFromSuperviewWithoutNeedingDisplay]; 133 } 134} 135 136- (void)drawWithFrame:(CGRect)rect inView:(NSView *)view 137{ 138 // The alphaValue change is not strictly necessary, but feels safer. 139 self.alphaValue = 1.0; 140 if (self.superview != view) 141 [view addSubview:self]; 142 if (!CGRectEqualToRect(self.frame, rect)) 143 self.frame = rect; 144 [self drawRect:rect]; 145 self.alphaValue = 0.0; 146} 147 148@end 149 150@interface QT_MANGLE_NAMESPACE(QVerticalSplitView) : NSSplitView 151- (BOOL)isVertical; 152@end 153 154QT_NAMESPACE_ALIAS_OBJC_CLASS(QVerticalSplitView); 155 156@implementation QVerticalSplitView 157- (BOOL)isVertical 158{ 159 return YES; 160} 161@end 162 163// See render code in drawPrimitive(PE_FrameTabWidget) 164@interface QT_MANGLE_NAMESPACE(QDarkNSBox) : NSBox 165@end 166 167QT_NAMESPACE_ALIAS_OBJC_CLASS(QDarkNSBox); 168 169@implementation QDarkNSBox 170- (instancetype)init 171{ 172 if ((self = [super init])) { 173 self.title = @""; 174 self.titlePosition = NSNoTitle; 175 self.boxType = NSBoxCustom; 176 self.cornerRadius = 3; 177 self.borderColor = [NSColor.controlColor colorWithAlphaComponent:0.1]; 178 self.fillColor = [NSColor.darkGrayColor colorWithAlphaComponent:0.2]; 179 } 180 181 return self; 182} 183 184- (void)drawRect:(NSRect)rect 185{ 186 [super drawRect:rect]; 187} 188@end 189 190QT_BEGIN_NAMESPACE 191 192// The following constants are used for adjusting the size 193// of push buttons so that they are drawn inside their bounds. 194const int QMacStylePrivate::PushButtonLeftOffset = 6; 195const int QMacStylePrivate::PushButtonRightOffset = 12; 196const int QMacStylePrivate::PushButtonContentPadding = 6; 197 198QVector<QPointer<QObject> > QMacStylePrivate::scrollBars; 199 200// Title bar gradient colors for Lion were determined by inspecting PSDs exported 201// using CoreUI's CoreThemeDocument; there is no public API to retrieve them 202 203static QLinearGradient titlebarGradientActive() 204{ 205 static QLinearGradient darkGradient = [](){ 206 QLinearGradient gradient; 207 // FIXME: colors are chosen somewhat arbitrarily and could be fine-tuned, 208 // or ideally determined by calling a native API. 209 gradient.setColorAt(0, QColor(47, 47, 47)); 210 return gradient; 211 }(); 212 static QLinearGradient lightGradient = [](){ 213 QLinearGradient gradient; 214 gradient.setColorAt(0, QColor(235, 235, 235)); 215 gradient.setColorAt(0.5, QColor(210, 210, 210)); 216 gradient.setColorAt(0.75, QColor(195, 195, 195)); 217 gradient.setColorAt(1, QColor(180, 180, 180)); 218 return gradient; 219 }(); 220 return qt_mac_applicationIsInDarkMode() ? darkGradient : lightGradient; 221} 222 223static QLinearGradient titlebarGradientInactive() 224{ 225 static QLinearGradient darkGradient = [](){ 226 QLinearGradient gradient; 227 gradient.setColorAt(1, QColor(42, 42, 42)); 228 return gradient; 229 }(); 230 static QLinearGradient lightGradient = [](){ 231 QLinearGradient gradient; 232 gradient.setColorAt(0, QColor(250, 250, 250)); 233 gradient.setColorAt(1, QColor(225, 225, 225)); 234 return gradient; 235 }(); 236 return qt_mac_applicationIsInDarkMode() ? darkGradient : lightGradient; 237} 238 239#if QT_CONFIG(tabwidget) 240/* 241 Since macOS 10.14 AppKit is using transparency more extensively, especially for the 242 dark theme. Inactive buttons, for example, are semi-transparent. And we use them to 243 draw tab widget's tab bar. The combination of NSBox (also a part of tab widget) 244 and these transparent buttons gives us an undesired side-effect: an outline of 245 NSBox is visible through transparent buttons. To avoid this, we have this hack below: 246 we clip the area where the line would be visible through the buttons. The area we 247 want to clip away can be described as an intersection of the option's rect and 248 the tab widget's tab bar rect. But some adjustments are required, since those rects 249 are anyway adjusted during the rendering and they are not exactly what you'll see on 250 the screen. Thus this switch-statement inside. 251*/ 252static void clipTabBarFrame(const QStyleOption *option, const QMacStyle *style, CGContextRef ctx) 253{ 254 Q_ASSERT(option); 255 Q_ASSERT(style); 256 Q_ASSERT(ctx); 257 258 if (qt_mac_applicationIsInDarkMode()) { 259 QTabWidget *tabWidget = qobject_cast<QTabWidget *>(option->styleObject); 260 Q_ASSERT(tabWidget); 261 262 QRect tabBarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, option, tabWidget).adjusted(2, 0, -3, 0); 263 switch (tabWidget->tabPosition()) { 264 case QTabWidget::South: 265 tabBarRect.setY(tabBarRect.y() + tabBarRect.height() / 2); 266 break; 267 case QTabWidget::North: 268 case QTabWidget::West: 269 tabBarRect = tabBarRect.adjusted(0, 2, 0, -2); 270 break; 271 case QTabWidget::East: 272 tabBarRect = tabBarRect.adjusted(tabBarRect.width() / 2, 2, tabBarRect.width() / 2, -2); 273 } 274 275 const QRegion clipPath = QRegion(option->rect) - tabBarRect; 276 QVarLengthArray<CGRect, 3> cgRects; 277 for (const QRect &qtRect : clipPath) 278 cgRects.push_back(qtRect.toCGRect()); 279 if (cgRects.size()) 280 CGContextClipToRects(ctx, &cgRects[0], size_t(cgRects.size())); 281 } 282} 283#endif 284 285static const QColor titlebarSeparatorLineActive(111, 111, 111); 286static const QColor titlebarSeparatorLineInactive(131, 131, 131); 287static const QColor darkModeSeparatorLine(88, 88, 88); 288 289// Gradient colors used for the dock widget title bar and 290// non-unifed tool bar bacground. 291static const QColor lightMainWindowGradientBegin(240, 240, 240); 292static const QColor lightMainWindowGradientEnd(200, 200, 200); 293static const QColor darkMainWindowGradientBegin(47, 47, 47); 294static const QColor darkMainWindowGradientEnd(47, 47, 47); 295 296static const int DisclosureOffset = 4; 297 298static const qreal titleBarIconTitleSpacing = 5; 299static const qreal titleBarTitleRightMargin = 12; 300static const qreal titleBarButtonSpacing = 8; 301 302// Tab bar colors 303// active: window is active 304// selected: tab is selected 305// hovered: tab is hovered 306bool isDarkMode() { return qt_mac_applicationIsInDarkMode(); } 307 308#if QT_CONFIG(tabbar) 309static const QColor lightTabBarTabBackgroundActive(190, 190, 190); 310static const QColor darkTabBarTabBackgroundActive(38, 38, 38); 311static const QColor tabBarTabBackgroundActive() { return isDarkMode() ? darkTabBarTabBackgroundActive : lightTabBarTabBackgroundActive; } 312 313static const QColor lightTabBarTabBackgroundActiveHovered(178, 178, 178); 314static const QColor darkTabBarTabBackgroundActiveHovered(32, 32, 32); 315static const QColor tabBarTabBackgroundActiveHovered() { return isDarkMode() ? darkTabBarTabBackgroundActiveHovered : lightTabBarTabBackgroundActiveHovered; } 316 317static const QColor lightTabBarTabBackgroundActiveSelected(211, 211, 211); 318static const QColor darkTabBarTabBackgroundActiveSelected(52, 52, 52); 319static const QColor tabBarTabBackgroundActiveSelected() { return isDarkMode() ? darkTabBarTabBackgroundActiveSelected : lightTabBarTabBackgroundActiveSelected; } 320 321static const QColor lightTabBarTabBackground(227, 227, 227); 322static const QColor darkTabBarTabBackground(38, 38, 38); 323static const QColor tabBarTabBackground() { return isDarkMode() ? darkTabBarTabBackground : lightTabBarTabBackground; } 324 325static const QColor lightTabBarTabBackgroundSelected(246, 246, 246); 326static const QColor darkTabBarTabBackgroundSelected(52, 52, 52); 327static const QColor tabBarTabBackgroundSelected() { return isDarkMode() ? darkTabBarTabBackgroundSelected : lightTabBarTabBackgroundSelected; } 328 329static const QColor lightTabBarTabLineActive(160, 160, 160); 330static const QColor darkTabBarTabLineActive(90, 90, 90); 331static const QColor tabBarTabLineActive() { return isDarkMode() ? darkTabBarTabLineActive : lightTabBarTabLineActive; } 332 333static const QColor lightTabBarTabLineActiveHovered(150, 150, 150); 334static const QColor darkTabBarTabLineActiveHovered(90, 90, 90); 335static const QColor tabBarTabLineActiveHovered() { return isDarkMode() ? darkTabBarTabLineActiveHovered : lightTabBarTabLineActiveHovered; } 336 337static const QColor lightTabBarTabLine(210, 210, 210); 338static const QColor darkTabBarTabLine(90, 90, 90); 339static const QColor tabBarTabLine() { return isDarkMode() ? darkTabBarTabLine : lightTabBarTabLine; } 340 341static const QColor lightTabBarTabLineSelected(189, 189, 189); 342static const QColor darkTabBarTabLineSelected(90, 90, 90); 343static const QColor tabBarTabLineSelected() { return isDarkMode() ? darkTabBarTabLineSelected : lightTabBarTabLineSelected; } 344 345static const QColor tabBarCloseButtonBackgroundHovered(162, 162, 162); 346static const QColor tabBarCloseButtonBackgroundPressed(153, 153, 153); 347static const QColor tabBarCloseButtonBackgroundSelectedHovered(192, 192, 192); 348static const QColor tabBarCloseButtonBackgroundSelectedPressed(181, 181, 181); 349static const QColor tabBarCloseButtonCross(100, 100, 100); 350static const QColor tabBarCloseButtonCrossSelected(115, 115, 115); 351 352static const int closeButtonSize = 14; 353static const qreal closeButtonCornerRadius = 2.0; 354#endif // QT_CONFIG(tabbar) 355 356#ifndef QT_NO_ACCESSIBILITY // This ifdef to avoid "unused function" warning. 357QBrush brushForToolButton(bool isOnKeyWindow) 358{ 359 // When a toolbutton in a toolbar is in the 'ON' state, we draw a 360 // partially transparent background. The colors must be different 361 // for 'Aqua' and 'DarkAqua' appearances though. 362 if (isDarkMode()) 363 return isOnKeyWindow ? QColor(73, 73, 73, 100) : QColor(56, 56, 56, 100); 364 365 return isOnKeyWindow ? QColor(0, 0, 0, 28) : QColor(0, 0, 0, 21); 366} 367#endif // QT_NO_ACCESSIBILITY 368 369 370static const int headerSectionArrowHeight = 6; 371static const int headerSectionSeparatorInset = 2; 372 373// One for each of QStyleHelper::WidgetSizePolicy 374static const QMarginsF comboBoxFocusRingMargins[3] = { 375 { 0.5, 2, 3.5, 4 }, 376 { 0.5, 1, 2.5, 4 }, 377 { 0.5, 1.5, 2.5, 3.5 } 378}; 379 380static const QMarginsF pullDownButtonShadowMargins[3] = { 381 { 0.5, -1, 0.5, 2 }, 382 { 0.5, -1.5, 0.5, 2.5 }, 383 { 0.5, 0, 0.5, 1 } 384}; 385 386static const QMarginsF pushButtonShadowMargins[3] = { 387 { 1.5, -1.5, 1.5, 4.5 }, 388 { 1.5, -1, 1.5, 4 }, 389 { 1.5, 0.5, 1.5, 2.5 } 390}; 391 392// These are frame heights as reported by Xcode 9's Interface Builder. 393// Alignemnet rectangle's heights match for push and popup buttons 394// with respective values 21, 18 and 15. 395 396static const qreal comboBoxDefaultHeight[3] = { 397 26, 22, 19 398}; 399 400static const qreal pushButtonDefaultHeight[3] = { 401 32, 28, 16 402}; 403 404static const qreal popupButtonDefaultHeight[3] = { 405 26, 22, 15 406}; 407 408static const int toolButtonArrowSize = 7; 409static const int toolButtonArrowMargin = 2; 410 411static const qreal focusRingWidth = 3.5; 412 413// An application can force 'Aqua' theme while the system theme is one of 414// the 'Dark' variants. Since in Qt we sometimes use NSControls and even 415// NSCells directly without attaching them to any view hierarchy, we have 416// to set NSAppearance.currentAppearance to 'Aqua' manually, to make sure 417// the correct rendering path is triggered. Apple recommends us to un-set 418// the current appearance back after we finished with drawing. This is what 419// AppearanceSync is for. 420 421class AppearanceSync { 422public: 423 AppearanceSync() 424 { 425#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) 426 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave 427 && !qt_mac_applicationIsInDarkMode()) { 428 auto requiredAppearanceName = NSApplication.sharedApplication.effectiveAppearance.name; 429 if (![NSAppearance.currentAppearance.name isEqualToString:requiredAppearanceName]) { 430 previous = NSAppearance.currentAppearance; 431 NSAppearance.currentAppearance = [NSAppearance appearanceNamed:requiredAppearanceName]; 432 } 433 } 434#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) 435 } 436 437 ~AppearanceSync() 438 { 439 if (previous) 440 NSAppearance.currentAppearance = previous; 441 } 442 443private: 444 NSAppearance *previous = nil; 445 446 Q_DISABLE_COPY(AppearanceSync) 447}; 448 449static bool setupScroller(NSScroller *scroller, const QStyleOptionSlider *sb) 450{ 451 const qreal length = sb->maximum - sb->minimum + sb->pageStep; 452 if (qFuzzyIsNull(length)) 453 return false; 454 const qreal proportion = sb->pageStep / length; 455 const qreal range = qreal(sb->maximum - sb->minimum); 456 qreal value = range ? qreal(sb->sliderValue - sb->minimum) / range : 0; 457 if (sb->orientation == Qt::Horizontal && sb->direction == Qt::RightToLeft) 458 value = 1.0 - value; 459 460 scroller.frame = sb->rect.toCGRect(); 461 scroller.floatValue = value; 462 scroller.knobProportion = proportion; 463 return true; 464} 465 466static bool setupSlider(NSSlider *slider, const QStyleOptionSlider *sl) 467{ 468 if (sl->minimum >= sl->maximum) 469 return false; 470 471 slider.frame = sl->rect.toCGRect(); 472 slider.minValue = sl->minimum; 473 slider.maxValue = sl->maximum; 474 slider.intValue = sl->sliderPosition; 475 slider.enabled = sl->state & QStyle::State_Enabled; 476 if (sl->tickPosition != QSlider::NoTicks) { 477 // Set numberOfTickMarks, but TicksBothSides will be treated differently 478 int interval = sl->tickInterval; 479 if (interval == 0) { 480 interval = sl->pageStep; 481 if (interval == 0) 482 interval = sl->singleStep; 483 if (interval == 0) 484 interval = 1; // return false? 485 } 486 slider.numberOfTickMarks = 1 + ((sl->maximum - sl->minimum) / interval); 487 488 const bool ticksAbove = sl->tickPosition == QSlider::TicksAbove; 489 if (sl->orientation == Qt::Horizontal) 490 slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionAbove : NSTickMarkPositionBelow; 491 else 492 slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionLeading : NSTickMarkPositionTrailing; 493 } else { 494 slider.numberOfTickMarks = 0; 495 } 496 497 // Ensure the values set above are reflected when asking 498 // the cell for its metrics and to draw itself. 499 [slider layoutSubtreeIfNeeded]; 500 501 return true; 502} 503 504static bool isInMacUnifiedToolbarArea(QWindow *window, int windowY) 505{ 506 QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); 507 QPlatformNativeInterface::NativeResourceForIntegrationFunction function = 508 nativeInterface->nativeResourceFunctionForIntegration("testContentBorderPosition"); 509 if (!function) 510 return false; // Not Cocoa platform plugin. 511 512 typedef bool (*TestContentBorderPositionFunction)(QWindow *, int); 513 return (reinterpret_cast<TestContentBorderPositionFunction>(function))(window, windowY); 514} 515 516 517#if QT_CONFIG(tabbar) 518static void drawTabCloseButton(QPainter *p, bool hover, bool selected, bool pressed, bool documentMode) 519{ 520 p->setRenderHints(QPainter::Antialiasing); 521 QRect rect(0, 0, closeButtonSize, closeButtonSize); 522 const int width = rect.width(); 523 const int height = rect.height(); 524 525 if (hover) { 526 // draw background circle 527 QColor background; 528 if (selected) { 529 if (documentMode) 530 background = pressed ? tabBarCloseButtonBackgroundSelectedPressed : tabBarCloseButtonBackgroundSelectedHovered; 531 else 532 background = QColor(255, 255, 255, pressed ? 150 : 100); // Translucent white 533 } else { 534 background = pressed ? tabBarCloseButtonBackgroundPressed : tabBarCloseButtonBackgroundHovered; 535 if (!documentMode) 536 background = background.lighter(pressed ? 135 : 140); // Lighter tab background, lighter color 537 } 538 539 p->setPen(Qt::transparent); 540 p->setBrush(background); 541 p->drawRoundedRect(rect, closeButtonCornerRadius, closeButtonCornerRadius); 542 } 543 544 // draw cross 545 const int margin = 3; 546 QPen crossPen; 547 crossPen.setColor(selected ? (documentMode ? tabBarCloseButtonCrossSelected : Qt::white) : tabBarCloseButtonCross); 548 crossPen.setWidthF(1.1); 549 crossPen.setCapStyle(Qt::FlatCap); 550 p->setPen(crossPen); 551 p->drawLine(margin, margin, width - margin, height - margin); 552 p->drawLine(margin, height - margin, width - margin, margin); 553} 554 555QRect rotateTabPainter(QPainter *p, QTabBar::Shape shape, QRect tabRect) 556{ 557 const auto tabDirection = QMacStylePrivate::tabDirection(shape); 558 if (QMacStylePrivate::verticalTabs(tabDirection)) { 559 int newX, newY, newRot; 560 if (tabDirection == QMacStylePrivate::East) { 561 newX = tabRect.width(); 562 newY = tabRect.y(); 563 newRot = 90; 564 } else { 565 newX = 0; 566 newY = tabRect.y() + tabRect.height(); 567 newRot = -90; 568 } 569 tabRect.setRect(0, 0, tabRect.height(), tabRect.width()); 570 QTransform transform; 571 transform.translate(newX, newY); 572 transform.rotate(newRot); 573 p->setTransform(transform, true); 574 } 575 return tabRect; 576} 577 578void drawTabShape(QPainter *p, const QStyleOptionTab *tabOpt, bool isUnified, int tabOverlap) 579{ 580 QRect rect = tabOpt->rect; 581 if (QMacStylePrivate::verticalTabs(QMacStylePrivate::tabDirection(tabOpt->shape))) 582 rect = rect.adjusted(-tabOverlap, 0, 0, 0); 583 else 584 rect = rect.adjusted(0, -tabOverlap, 0, 0); 585 586 p->translate(rect.x(), rect.y()); 587 rect.moveLeft(0); 588 rect.moveTop(0); 589 const QRect tabRect = rotateTabPainter(p, tabOpt->shape, rect); 590 591 const int width = tabRect.width(); 592 const int height = tabRect.height(); 593 const bool active = (tabOpt->state & QStyle::State_Active); 594 const bool selected = (tabOpt->state & QStyle::State_Selected); 595 596 const QRect bodyRect(1, 2, width - 2, height - 3); 597 const QRect topLineRect(1, 0, width - 2, 1); 598 const QRect bottomLineRect(1, height - 1, width - 2, 1); 599 if (selected) { 600 // fill body 601 if (tabOpt->documentMode && isUnified) { 602 p->save(); 603 p->setCompositionMode(QPainter::CompositionMode_Source); 604 p->fillRect(tabRect, QColor(Qt::transparent)); 605 p->restore(); 606 } else if (active) { 607 p->fillRect(bodyRect, tabBarTabBackgroundActiveSelected()); 608 // top line 609 p->fillRect(topLineRect, tabBarTabLineSelected()); 610 } else { 611 p->fillRect(bodyRect, tabBarTabBackgroundSelected()); 612 } 613 } else { 614 // when the mouse is over non selected tabs they get a new color 615 const bool hover = (tabOpt->state & QStyle::State_MouseOver); 616 if (hover) { 617 // fill body 618 p->fillRect(bodyRect, tabBarTabBackgroundActiveHovered()); 619 // bottom line 620 p->fillRect(bottomLineRect, isDarkMode() ? QColor(Qt::black) : tabBarTabLineActiveHovered()); 621 } 622 } 623 624 // separator lines between tabs 625 const QRect leftLineRect(0, 1, 1, height - 2); 626 const QRect rightLineRect(width - 1, 1, 1, height - 2); 627 const QColor separatorLineColor = active ? tabBarTabLineActive() : tabBarTabLine(); 628 p->fillRect(leftLineRect, separatorLineColor); 629 p->fillRect(rightLineRect, separatorLineColor); 630} 631 632void drawTabBase(QPainter *p, const QStyleOptionTabBarBase *tbb, const QWidget *w) 633{ 634 QRect r = tbb->rect; 635 if (QMacStylePrivate::verticalTabs(QMacStylePrivate::tabDirection(tbb->shape))) 636 r.setWidth(w->width()); 637 else 638 r.setHeight(w->height()); 639 640 const QRect tabRect = rotateTabPainter(p, tbb->shape, r); 641 const int width = tabRect.width(); 642 const int height = tabRect.height(); 643 const bool active = (tbb->state & QStyle::State_Active); 644 645 // fill body 646 const QRect bodyRect(0, 1, width, height - 1); 647 const QColor bodyColor = active ? tabBarTabBackgroundActive() : tabBarTabBackground(); 648 p->fillRect(bodyRect, bodyColor); 649 650 // top line 651 const QRect topLineRect(0, 0, width, 1); 652 const QColor topLineColor = active ? tabBarTabLineActive() : tabBarTabLine(); 653 p->fillRect(topLineRect, topLineColor); 654 655 // bottom line 656 const QRect bottomLineRect(0, height - 1, width, 1); 657 bool isDocument = false; 658 if (const QTabBar *tabBar = qobject_cast<const QTabBar*>(w)) 659 isDocument = tabBar->documentMode(); 660 const QColor bottomLineColor = isDocument && isDarkMode() ? QColor(Qt::black) : active ? tabBarTabLineActive() : tabBarTabLine(); 661 p->fillRect(bottomLineRect, bottomLineColor); 662} 663#endif 664 665static QStyleHelper::WidgetSizePolicy getControlSize(const QStyleOption *option, const QWidget *widget) 666{ 667 const auto wsp = QStyleHelper::widgetSizePolicy(widget, option); 668 if (wsp == QStyleHelper::SizeDefault) 669 return QStyleHelper::SizeLarge; 670 671 return wsp; 672} 673 674#if QT_CONFIG(treeview) 675static inline bool isTreeView(const QWidget *widget) 676{ 677 return (widget && widget->parentWidget() && 678 qobject_cast<const QTreeView *>(widget->parentWidget())); 679} 680#endif 681 682static QString qt_mac_removeMnemonics(const QString &original) 683{ 684 QString returnText(original.size(), 0); 685 int finalDest = 0; 686 int currPos = 0; 687 int l = original.length(); 688 while (l) { 689 if (original.at(currPos) == QLatin1Char('&')) { 690 ++currPos; 691 --l; 692 if (l == 0) 693 break; 694 } else if (original.at(currPos) == QLatin1Char('(') && l >= 4 && 695 original.at(currPos + 1) == QLatin1Char('&') && 696 original.at(currPos + 2) != QLatin1Char('&') && 697 original.at(currPos + 3) == QLatin1Char(')')) { 698 /* remove mnemonics its format is "\s*(&X)" */ 699 int n = 0; 700 while (finalDest > n && returnText.at(finalDest - n - 1).isSpace()) 701 ++n; 702 finalDest -= n; 703 currPos += 4; 704 l -= 4; 705 continue; 706 } 707 returnText[finalDest] = original.at(currPos); 708 ++currPos; 709 ++finalDest; 710 --l; 711 } 712 returnText.truncate(finalDest); 713 return returnText; 714} 715 716static bool qt_macWindowMainWindow(const QWidget *window) 717{ 718 if (QWindow *w = window->windowHandle()) { 719 if (w->handle()) { 720 if (NSWindow *nswindow = static_cast<NSWindow*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow(QByteArrayLiteral("nswindow"), w))) { 721 return [nswindow isMainWindow]; 722 } 723 } 724 } 725 return false; 726} 727 728/***************************************************************************** 729 QMacCGStyle globals 730 *****************************************************************************/ 731const int macItemFrame = 2; // menu item frame width 732const int macItemHMargin = 3; // menu item hor text margin 733const int macRightBorder = 12; // right border on mac 734 735/***************************************************************************** 736 QMacCGStyle utility functions 737 *****************************************************************************/ 738 739enum QAquaMetric { 740 // Prepend kThemeMetric to get the HIToolBox constant. 741 // Represents the values already used in QMacStyle. 742 CheckBoxHeight = 0, 743 CheckBoxWidth, 744 EditTextFrameOutset, 745 FocusRectOutset, 746 HSliderHeight, 747 HSliderTickHeight, 748 LargeProgressBarThickness, 749 ListHeaderHeight, 750 MenuSeparatorHeight, // GetThemeMenuSeparatorHeight 751 MiniCheckBoxHeight, 752 MiniCheckBoxWidth, 753 MiniHSliderHeight, 754 MiniHSliderTickHeight, 755 MiniPopupButtonHeight, 756 MiniPushButtonHeight, 757 MiniRadioButtonHeight, 758 MiniRadioButtonWidth, 759 MiniVSliderTickWidth, 760 MiniVSliderWidth, 761 NormalProgressBarThickness, 762 PopupButtonHeight, 763 ProgressBarShadowOutset, 764 PushButtonHeight, 765 RadioButtonHeight, 766 RadioButtonWidth, 767 SeparatorSize, 768 SmallCheckBoxHeight, 769 SmallCheckBoxWidth, 770 SmallHSliderHeight, 771 SmallHSliderTickHeight, 772 SmallPopupButtonHeight, 773 SmallProgressBarShadowOutset, 774 SmallPushButtonHeight, 775 SmallRadioButtonHeight, 776 SmallRadioButtonWidth, 777 SmallVSliderTickWidth, 778 SmallVSliderWidth, 779 VSliderTickWidth, 780 VSliderWidth 781}; 782 783static const int qt_mac_aqua_metrics[] = { 784 // Values as of macOS 10.12.4 and Xcode 8.3.1 785 18 /* CheckBoxHeight */, 786 18 /* CheckBoxWidth */, 787 1 /* EditTextFrameOutset */, 788 4 /* FocusRectOutset */, 789 22 /* HSliderHeight */, 790 5 /* HSliderTickHeight */, 791 16 /* LargeProgressBarThickness */, 792 17 /* ListHeaderHeight */, 793 12 /* MenuSeparatorHeight, aka GetThemeMenuSeparatorHeight */, 794 11 /* MiniCheckBoxHeight */, 795 10 /* MiniCheckBoxWidth */, 796 12 /* MiniHSliderHeight */, 797 4 /* MiniHSliderTickHeight */, 798 15 /* MiniPopupButtonHeight */, 799 16 /* MiniPushButtonHeight */, 800 11 /* MiniRadioButtonHeight */, 801 10 /* MiniRadioButtonWidth */, 802 4 /* MiniVSliderTickWidth */, 803 12 /* MiniVSliderWidth */, 804 12 /* NormalProgressBarThickness */, 805 20 /* PopupButtonHeight */, 806 4 /* ProgressBarShadowOutset */, 807 20 /* PushButtonHeight */, 808 18 /* RadioButtonHeight */, 809 18 /* RadioButtonWidth */, 810 1 /* SeparatorSize */, 811 16 /* SmallCheckBoxHeight */, 812 14 /* SmallCheckBoxWidth */, 813 15 /* SmallHSliderHeight */, 814 4 /* SmallHSliderTickHeight */, 815 17 /* SmallPopupButtonHeight */, 816 2 /* SmallProgressBarShadowOutset */, 817 17 /* SmallPushButtonHeight */, 818 15 /* SmallRadioButtonHeight */, 819 14 /* SmallRadioButtonWidth */, 820 4 /* SmallVSliderTickWidth */, 821 15 /* SmallVSliderWidth */, 822 5 /* VSliderTickWidth */, 823 22 /* VSliderWidth */ 824}; 825 826static inline int qt_mac_aqua_get_metric(QAquaMetric m) 827{ 828 return qt_mac_aqua_metrics[m]; 829} 830 831static QSize qt_aqua_get_known_size(QStyle::ContentsType ct, const QWidget *widg, QSize szHint, 832 QStyleHelper::WidgetSizePolicy sz) 833{ 834 QSize ret(-1, -1); 835 if (sz != QStyleHelper::SizeSmall && sz != QStyleHelper::SizeLarge && sz != QStyleHelper::SizeMini) { 836 qDebug("Not sure how to return this..."); 837 return ret; 838 } 839 if ((widg && widg->testAttribute(Qt::WA_SetFont)) || !QApplication::desktopSettingsAware()) { 840 // If you're using a custom font and it's bigger than the default font, 841 // then no constraints for you. If you are smaller, we can try to help you out 842 QFont font = qt_app_fonts_hash()->value(widg->metaObject()->className(), QFont()); 843 if (widg->font().pointSize() > font.pointSize()) 844 return ret; 845 } 846 847 if (ct == QStyle::CT_CustomBase && widg) { 848#if QT_CONFIG(pushbutton) 849 if (qobject_cast<const QPushButton *>(widg)) 850 ct = QStyle::CT_PushButton; 851#endif 852 else if (qobject_cast<const QRadioButton *>(widg)) 853 ct = QStyle::CT_RadioButton; 854#if QT_CONFIG(checkbox) 855 else if (qobject_cast<const QCheckBox *>(widg)) 856 ct = QStyle::CT_CheckBox; 857#endif 858#if QT_CONFIG(combobox) 859 else if (qobject_cast<const QComboBox *>(widg)) 860 ct = QStyle::CT_ComboBox; 861#endif 862#if QT_CONFIG(toolbutton) 863 else if (qobject_cast<const QToolButton *>(widg)) 864 ct = QStyle::CT_ToolButton; 865#endif 866 else if (qobject_cast<const QSlider *>(widg)) 867 ct = QStyle::CT_Slider; 868#if QT_CONFIG(progressbar) 869 else if (qobject_cast<const QProgressBar *>(widg)) 870 ct = QStyle::CT_ProgressBar; 871#endif 872#if QT_CONFIG(lineedit) 873 else if (qobject_cast<const QLineEdit *>(widg)) 874 ct = QStyle::CT_LineEdit; 875#endif 876#if QT_CONFIG(itemviews) 877 else if (qobject_cast<const QHeaderView *>(widg)) 878 ct = QStyle::CT_HeaderSection; 879#endif 880#if QT_CONFIG(menubar) 881 else if (qobject_cast<const QMenuBar *>(widg)) 882 ct = QStyle::CT_MenuBar; 883#endif 884#if QT_CONFIG(sizegrip) 885 else if (qobject_cast<const QSizeGrip *>(widg)) 886 ct = QStyle::CT_SizeGrip; 887#endif 888 else 889 return ret; 890 } 891 892 switch (ct) { 893#if QT_CONFIG(pushbutton) 894 case QStyle::CT_PushButton: { 895 const QPushButton *psh = qobject_cast<const QPushButton *>(widg); 896 // If this comparison is false, then the widget was not a push button. 897 // This is bad and there's very little we can do since we were requested to find a 898 // sensible size for a widget that pretends to be a QPushButton but is not. 899 if(psh) { 900 QString buttonText = qt_mac_removeMnemonics(psh->text()); 901 if (buttonText.contains(QLatin1Char('\n'))) 902 ret = QSize(-1, -1); 903 else if (sz == QStyleHelper::SizeLarge) 904 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight)); 905 else if (sz == QStyleHelper::SizeSmall) 906 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPushButtonHeight)); 907 else if (sz == QStyleHelper::SizeMini) 908 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPushButtonHeight)); 909 910 if (!psh->icon().isNull()){ 911 // If the button got an icon, and the icon is larger than the 912 // button, we can't decide on a default size 913 ret.setWidth(-1); 914 if (ret.height() < psh->iconSize().height()) 915 ret.setHeight(-1); 916 } 917 else if (buttonText == QLatin1String("OK") || buttonText == QLatin1String("Cancel")){ 918 // Aqua Style guidelines restrict the size of OK and Cancel buttons to 68 pixels. 919 // However, this doesn't work for German, therefore only do it for English, 920 // I suppose it would be better to do some sort of lookups for languages 921 // that like to have really long words. 922 // FIXME This is not exactly true. Out of context, OK buttons have their 923 // implicit size calculated the same way as any other button. Inside a 924 // QDialogButtonBox, their size should be calculated such that the action 925 // or accept button (i.e., rightmost) and cancel button have the same width. 926 ret.setWidth(69); 927 } 928 } else { 929 // The only sensible thing to do is to return whatever the style suggests... 930 if (sz == QStyleHelper::SizeLarge) 931 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight)); 932 else if (sz == QStyleHelper::SizeSmall) 933 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPushButtonHeight)); 934 else if (sz == QStyleHelper::SizeMini) 935 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPushButtonHeight)); 936 else 937 // Since there's no default size we return the large size... 938 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight)); 939 } 940#endif 941#if 0 //Not sure we are applying the rules correctly for RadioButtons/CheckBoxes --Sam 942 } else if (ct == QStyle::CT_RadioButton) { 943 QRadioButton *rdo = static_cast<QRadioButton *>(widg); 944 // Exception for case where multiline radio button text requires no size constrainment 945 if (rdo->text().find('\n') != -1) 946 return ret; 947 if (sz == QStyleHelper::SizeLarge) 948 ret = QSize(-1, qt_mac_aqua_get_metric(RadioButtonHeight)); 949 else if (sz == QStyleHelper::SizeSmall) 950 ret = QSize(-1, qt_mac_aqua_get_metric(SmallRadioButtonHeight)); 951 else if (sz == QStyleHelper::SizeMini) 952 ret = QSize(-1, qt_mac_aqua_get_metric(MiniRadioButtonHeight)); 953 } else if (ct == QStyle::CT_CheckBox) { 954 if (sz == QStyleHelper::SizeLarge) 955 ret = QSize(-1, qt_mac_aqua_get_metric(CheckBoxHeight)); 956 else if (sz == QStyleHelper::SizeSmall) 957 ret = QSize(-1, qt_mac_aqua_get_metric(SmallCheckBoxHeight)); 958 else if (sz == QStyleHelper::SizeMini) 959 ret = QSize(-1, qt_mac_aqua_get_metric(MiniCheckBoxHeight)); 960#endif 961 break; 962 } 963 case QStyle::CT_SizeGrip: 964 // Not HIG kosher: mimic what we were doing earlier until we support 4-edge resizing in MDI subwindows 965 if (sz == QStyleHelper::SizeLarge || sz == QStyleHelper::SizeSmall) { 966 int s = sz == QStyleHelper::SizeSmall ? 16 : 22; // large: pixel measured from HITheme, small: from my hat 967 int width = 0; 968#if QT_CONFIG(mdiarea) 969 if (widg && qobject_cast<QMdiSubWindow *>(widg->parentWidget())) 970 width = s; 971#endif 972 ret = QSize(width, s); 973 } 974 break; 975 case QStyle::CT_ComboBox: 976 switch (sz) { 977 case QStyleHelper::SizeLarge: 978 ret = QSize(-1, qt_mac_aqua_get_metric(PopupButtonHeight)); 979 break; 980 case QStyleHelper::SizeSmall: 981 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPopupButtonHeight)); 982 break; 983 case QStyleHelper::SizeMini: 984 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPopupButtonHeight)); 985 break; 986 default: 987 break; 988 } 989 break; 990 case QStyle::CT_ToolButton: 991 if (sz == QStyleHelper::SizeSmall) { 992 int width = 0, height = 0; 993 if (szHint == QSize(-1, -1)) { //just 'guess'.. 994#if QT_CONFIG(toolbutton) 995 const QToolButton *bt = qobject_cast<const QToolButton *>(widg); 996 // If this conversion fails then the widget was not what it claimed to be. 997 if(bt) { 998 if (!bt->icon().isNull()) { 999 QSize iconSize = bt->iconSize(); 1000 QSize pmSize = bt->icon().actualSize(QSize(32, 32), QIcon::Normal); 1001 width = qMax(width, qMax(iconSize.width(), pmSize.width())); 1002 height = qMax(height, qMax(iconSize.height(), pmSize.height())); 1003 } 1004 if (!bt->text().isNull() && bt->toolButtonStyle() != Qt::ToolButtonIconOnly) { 1005 int text_width = bt->fontMetrics().horizontalAdvance(bt->text()), 1006 text_height = bt->fontMetrics().height(); 1007 if (bt->toolButtonStyle() == Qt::ToolButtonTextUnderIcon) { 1008 width = qMax(width, text_width); 1009 height += text_height; 1010 } else { 1011 width += text_width; 1012 width = qMax(height, text_height); 1013 } 1014 } 1015 } else 1016#endif 1017 { 1018 // Let's return the size hint... 1019 width = szHint.width(); 1020 height = szHint.height(); 1021 } 1022 } else { 1023 width = szHint.width(); 1024 height = szHint.height(); 1025 } 1026 width = qMax(20, width + 5); //border 1027 height = qMax(20, height + 5); //border 1028 ret = QSize(width, height); 1029 } 1030 break; 1031 case QStyle::CT_Slider: { 1032 int w = -1; 1033 const QSlider *sld = qobject_cast<const QSlider *>(widg); 1034 // If this conversion fails then the widget was not what it claimed to be. 1035 if(sld) { 1036 if (sz == QStyleHelper::SizeLarge) { 1037 if (sld->orientation() == Qt::Horizontal) { 1038 w = qt_mac_aqua_get_metric(HSliderHeight); 1039 if (sld->tickPosition() != QSlider::NoTicks) 1040 w += qt_mac_aqua_get_metric(HSliderTickHeight); 1041 } else { 1042 w = qt_mac_aqua_get_metric(VSliderWidth); 1043 if (sld->tickPosition() != QSlider::NoTicks) 1044 w += qt_mac_aqua_get_metric(VSliderTickWidth); 1045 } 1046 } else if (sz == QStyleHelper::SizeSmall) { 1047 if (sld->orientation() == Qt::Horizontal) { 1048 w = qt_mac_aqua_get_metric(SmallHSliderHeight); 1049 if (sld->tickPosition() != QSlider::NoTicks) 1050 w += qt_mac_aqua_get_metric(SmallHSliderTickHeight); 1051 } else { 1052 w = qt_mac_aqua_get_metric(SmallVSliderWidth); 1053 if (sld->tickPosition() != QSlider::NoTicks) 1054 w += qt_mac_aqua_get_metric(SmallVSliderTickWidth); 1055 } 1056 } else if (sz == QStyleHelper::SizeMini) { 1057 if (sld->orientation() == Qt::Horizontal) { 1058 w = qt_mac_aqua_get_metric(MiniHSliderHeight); 1059 if (sld->tickPosition() != QSlider::NoTicks) 1060 w += qt_mac_aqua_get_metric(MiniHSliderTickHeight); 1061 } else { 1062 w = qt_mac_aqua_get_metric(MiniVSliderWidth); 1063 if (sld->tickPosition() != QSlider::NoTicks) 1064 w += qt_mac_aqua_get_metric(MiniVSliderTickWidth); 1065 } 1066 } 1067 } else { 1068 // This is tricky, we were requested to find a size for a slider which is not 1069 // a slider. We don't know if this is vertical or horizontal or if we need to 1070 // have tick marks or not. 1071 // For this case we will return an horizontal slider without tick marks. 1072 w = qt_mac_aqua_get_metric(HSliderHeight); 1073 w += qt_mac_aqua_get_metric(HSliderTickHeight); 1074 } 1075 if (sld->orientation() == Qt::Horizontal) 1076 ret.setHeight(w); 1077 else 1078 ret.setWidth(w); 1079 break; 1080 } 1081#if QT_CONFIG(progressbar) 1082 case QStyle::CT_ProgressBar: { 1083 int finalValue = -1; 1084 Qt::Orientation orient = Qt::Horizontal; 1085 if (const QProgressBar *pb = qobject_cast<const QProgressBar *>(widg)) 1086 orient = pb->orientation(); 1087 1088 if (sz == QStyleHelper::SizeLarge) 1089 finalValue = qt_mac_aqua_get_metric(LargeProgressBarThickness) 1090 + qt_mac_aqua_get_metric(ProgressBarShadowOutset); 1091 else 1092 finalValue = qt_mac_aqua_get_metric(NormalProgressBarThickness) 1093 + qt_mac_aqua_get_metric(SmallProgressBarShadowOutset); 1094 if (orient == Qt::Horizontal) 1095 ret.setHeight(finalValue); 1096 else 1097 ret.setWidth(finalValue); 1098 break; 1099 } 1100#endif 1101#if QT_CONFIG(combobox) 1102 case QStyle::CT_LineEdit: 1103 if (!widg || !qobject_cast<QComboBox *>(widg->parentWidget())) { 1104 //should I take into account the font dimentions of the lineedit? -Sam 1105 if (sz == QStyleHelper::SizeLarge) 1106 ret = QSize(-1, 21); 1107 else 1108 ret = QSize(-1, 19); 1109 } 1110 break; 1111#endif 1112 case QStyle::CT_HeaderSection: 1113#if QT_CONFIG(treeview) 1114 if (isTreeView(widg)) 1115 ret = QSize(-1, qt_mac_aqua_get_metric(ListHeaderHeight)); 1116#endif 1117 break; 1118 case QStyle::CT_MenuBar: 1119 if (sz == QStyleHelper::SizeLarge) { 1120 ret = QSize(-1, [[NSApp mainMenu] menuBarHeight]); 1121 // In the qt_mac_set_native_menubar(false) case, 1122 // we come it here with a zero-height main menu, 1123 // preventing the in-window menu from displaying. 1124 // Use 22 pixels for the height, by observation. 1125 if (ret.height() <= 0) 1126 ret.setHeight(22); 1127 } 1128 break; 1129 default: 1130 break; 1131 } 1132 return ret; 1133} 1134 1135 1136#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) 1137static QStyleHelper::WidgetSizePolicy qt_aqua_guess_size(const QWidget *widg, QSize large, QSize small, QSize mini) 1138{ 1139 Q_UNUSED(widg); 1140 1141 if (large == QSize(-1, -1)) { 1142 if (small != QSize(-1, -1)) 1143 return QStyleHelper::SizeSmall; 1144 if (mini != QSize(-1, -1)) 1145 return QStyleHelper::SizeMini; 1146 return QStyleHelper::SizeDefault; 1147 } else if (small == QSize(-1, -1)) { 1148 if (mini != QSize(-1, -1)) 1149 return QStyleHelper::SizeMini; 1150 return QStyleHelper::SizeLarge; 1151 } else if (mini == QSize(-1, -1)) { 1152 return QStyleHelper::SizeLarge; 1153 } 1154 1155 if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL")) 1156 return QStyleHelper::SizeSmall; 1157 else if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI")) 1158 return QStyleHelper::SizeMini; 1159 1160 return QStyleHelper::SizeLarge; 1161} 1162#endif 1163 1164void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int hMargin, int vMargin, const CocoaControl &cw) const 1165{ 1166 QPainterPath focusRingPath; 1167 focusRingPath.setFillRule(Qt::OddEvenFill); 1168 1169 qreal hOffset = 0.0; 1170 qreal vOffset = 0.0; 1171 switch (cw.type) { 1172 case Box: 1173 case Button_SquareButton: 1174 case SegmentedControl_Middle: 1175 case TextField: { 1176 auto innerRect = targetRect; 1177 if (cw.type == TextField) 1178 innerRect = innerRect.adjusted(hMargin, vMargin, -hMargin, -vMargin).adjusted(0.5, 0.5, -0.5, -0.5); 1179 const auto outerRect = innerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth); 1180 const auto outerRadius = focusRingWidth; 1181 focusRingPath.addRect(innerRect); 1182 focusRingPath.addRoundedRect(outerRect, outerRadius, outerRadius); 1183 break; 1184 } 1185 case Button_CheckBox: { 1186 const auto cbInnerRadius = (cw.size == QStyleHelper::SizeMini ? 2.0 : 3.0); 1187 const auto cbSize = cw.size == QStyleHelper::SizeLarge ? 13 : 1188 cw.size == QStyleHelper::SizeSmall ? 11 : 9; // As measured 1189 hOffset = hMargin + (cw.size == QStyleHelper::SizeLarge ? 2.5 : 1190 cw.size == QStyleHelper::SizeSmall ? 2.0 : 1.0); // As measured 1191 vOffset = 0.5 * qreal(targetRect.height() - cbSize); 1192 const auto cbInnerRect = QRectF(0, 0, cbSize, cbSize); 1193 const auto cbOuterRadius = cbInnerRadius + focusRingWidth; 1194 const auto cbOuterRect = cbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth); 1195 focusRingPath.addRoundedRect(cbOuterRect, cbOuterRadius, cbOuterRadius); 1196 focusRingPath.addRoundedRect(cbInnerRect, cbInnerRadius, cbInnerRadius); 1197 break; 1198 } 1199 case Button_RadioButton: { 1200 const auto rbSize = cw.size == QStyleHelper::SizeLarge ? 15 : 1201 cw.size == QStyleHelper::SizeSmall ? 13 : 9; // As measured 1202 hOffset = hMargin + (cw.size == QStyleHelper::SizeLarge ? 1.5 : 1203 cw.size == QStyleHelper::SizeSmall ? 1.0 : 1.0); // As measured 1204 vOffset = 0.5 * qreal(targetRect.height() - rbSize); 1205 const auto rbInnerRect = QRectF(0, 0, rbSize, rbSize); 1206 const auto rbOuterRect = rbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth); 1207 focusRingPath.addEllipse(rbInnerRect); 1208 focusRingPath.addEllipse(rbOuterRect); 1209 break; 1210 } 1211 case Button_PopupButton: 1212 case Button_PullDown: 1213 case Button_PushButton: 1214 case SegmentedControl_Single: { 1215 const qreal innerRadius = cw.type == Button_PushButton ? 3 : 4; 1216 const qreal outerRadius = innerRadius + focusRingWidth; 1217 hOffset = targetRect.left(); 1218 vOffset = targetRect.top(); 1219 const auto innerRect = targetRect.translated(-targetRect.topLeft()); 1220 const auto outerRect = innerRect.adjusted(-hMargin, -vMargin, hMargin, vMargin); 1221 focusRingPath.addRoundedRect(innerRect, innerRadius, innerRadius); 1222 focusRingPath.addRoundedRect(outerRect, outerRadius, outerRadius); 1223 break; 1224 } 1225 case ComboBox: 1226 case SegmentedControl_First: 1227 case SegmentedControl_Last: { 1228 hOffset = targetRect.left(); 1229 vOffset = targetRect.top(); 1230 const qreal innerRadius = 8; 1231 const qreal outerRadius = innerRadius + focusRingWidth; 1232 const auto innerRect = targetRect.translated(-targetRect.topLeft()); 1233 const auto outerRect = innerRect.adjusted(-hMargin, -vMargin, hMargin, vMargin); 1234 1235 const auto cbFocusFramePath = [](const QRectF &rect, qreal tRadius, qreal bRadius) { 1236 QPainterPath path; 1237 1238 if (tRadius > 0) { 1239 const auto topLeftCorner = QRectF(rect.topLeft(), QSizeF(tRadius, tRadius)); 1240 path.arcMoveTo(topLeftCorner, 180); 1241 path.arcTo(topLeftCorner, 180, -90); 1242 } else { 1243 path.moveTo(rect.topLeft()); 1244 } 1245 const auto rightEdge = rect.right() - bRadius; 1246 path.arcTo(rightEdge, rect.top(), bRadius, bRadius, 90, -90); 1247 path.arcTo(rightEdge, rect.bottom() - bRadius, bRadius, bRadius, 0, -90); 1248 if (tRadius > 0) 1249 path.arcTo(rect.left(), rect.bottom() - tRadius, tRadius, tRadius, 270, -90); 1250 else 1251 path.lineTo(rect.bottomLeft()); 1252 path.closeSubpath(); 1253 1254 return path; 1255 }; 1256 1257 const auto innerPath = cbFocusFramePath(innerRect, 0, innerRadius); 1258 focusRingPath.addPath(innerPath); 1259 const auto outerPath = cbFocusFramePath(outerRect, 2 * focusRingWidth, outerRadius); 1260 focusRingPath.addPath(outerPath); 1261 break; 1262 } 1263 default: 1264 Q_UNREACHABLE(); 1265 } 1266 1267 auto focusRingColor = qt_mac_toQColor(NSColor.keyboardFocusIndicatorColor.CGColor); 1268 if (!qt_mac_applicationIsInDarkMode()) { 1269 // This color already has alpha ~ 0.25, this value is too small - the ring is 1270 // very pale and nothing like the native one. 0.39 makes it better (not ideal 1271 // anyway). The color seems to be correct in dark more without any modification. 1272 focusRingColor.setAlphaF(0.39); 1273 } 1274 1275 p->save(); 1276 p->setRenderHint(QPainter::Antialiasing); 1277 1278 if (cw.type == SegmentedControl_First) { 1279 // TODO Flip left-right 1280 } 1281 p->translate(hOffset, vOffset); 1282 p->fillPath(focusRingPath, focusRingColor); 1283 p->restore(); 1284} 1285 1286QPainterPath QMacStylePrivate::windowPanelPath(const QRectF &r) const 1287{ 1288 static const qreal CornerPointOffset = 5.5; 1289 static const qreal CornerControlOffset = 2.1; 1290 1291 QPainterPath path; 1292 // Top-left corner 1293 path.moveTo(r.left(), r.top() + CornerPointOffset); 1294 path.cubicTo(r.left(), r.top() + CornerControlOffset, 1295 r.left() + CornerControlOffset, r.top(), 1296 r.left() + CornerPointOffset, r.top()); 1297 // Top-right corner 1298 path.lineTo(r.right() - CornerPointOffset, r.top()); 1299 path.cubicTo(r.right() - CornerControlOffset, r.top(), 1300 r.right(), r.top() + CornerControlOffset, 1301 r.right(), r.top() + CornerPointOffset); 1302 // Bottom-right corner 1303 path.lineTo(r.right(), r.bottom() - CornerPointOffset); 1304 path.cubicTo(r.right(), r.bottom() - CornerControlOffset, 1305 r.right() - CornerControlOffset, r.bottom(), 1306 r.right() - CornerPointOffset, r.bottom()); 1307 // Bottom-right corner 1308 path.lineTo(r.left() + CornerPointOffset, r.bottom()); 1309 path.cubicTo(r.left() + CornerControlOffset, r.bottom(), 1310 r.left(), r.bottom() - CornerControlOffset, 1311 r.left(), r.bottom() - CornerPointOffset); 1312 path.lineTo(r.left(), r.top() + CornerPointOffset); 1313 1314 return path; 1315} 1316 1317QMacStylePrivate::CocoaControlType QMacStylePrivate::windowButtonCocoaControl(QStyle::SubControl sc) const 1318{ 1319 struct WindowButtons { 1320 QStyle::SubControl sc; 1321 QMacStylePrivate::CocoaControlType ct; 1322 }; 1323 1324 static const WindowButtons buttons[] = { 1325 { QStyle::SC_TitleBarCloseButton, QMacStylePrivate::Button_WindowClose }, 1326 { QStyle::SC_TitleBarMinButton, QMacStylePrivate::Button_WindowMiniaturize }, 1327 { QStyle::SC_TitleBarMaxButton, QMacStylePrivate::Button_WindowZoom } 1328 }; 1329 1330 for (const auto &wb : buttons) 1331 if (wb.sc == sc) 1332 return wb.ct; 1333 1334 return NoControl; 1335} 1336 1337 1338#if QT_CONFIG(tabbar) 1339void QMacStylePrivate::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const 1340{ 1341 Q_ASSERT(textRect); 1342 Q_ASSERT(iconRect); 1343 QRect tr = opt->rect; 1344 const bool verticalTabs = opt->shape == QTabBar::RoundedEast 1345 || opt->shape == QTabBar::RoundedWest 1346 || opt->shape == QTabBar::TriangularEast 1347 || opt->shape == QTabBar::TriangularWest; 1348 if (verticalTabs) 1349 tr.setRect(0, 0, tr.height(), tr.width()); // 0, 0 as we will have a translate transform 1350 1351 int verticalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget); 1352 int horizontalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget); 1353 const int hpadding = 4; 1354 const int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2; 1355 if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth) 1356 verticalShift = -verticalShift; 1357 tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding); 1358 1359 // left widget 1360 if (!opt->leftButtonSize.isEmpty()) { 1361 const int buttonSize = verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width(); 1362 tr.setLeft(tr.left() + 4 + buttonSize); 1363 // make text aligned to center 1364 if (opt->rightButtonSize.isEmpty()) 1365 tr.setRight(tr.right() - 4 - buttonSize); 1366 } 1367 // right widget 1368 if (!opt->rightButtonSize.isEmpty()) { 1369 const int buttonSize = verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width(); 1370 tr.setRight(tr.right() - 4 - buttonSize); 1371 // make text aligned to center 1372 if (opt->leftButtonSize.isEmpty()) 1373 tr.setLeft(tr.left() + 4 + buttonSize); 1374 } 1375 1376 // icon 1377 if (!opt->icon.isNull()) { 1378 QSize iconSize = opt->iconSize; 1379 if (!iconSize.isValid()) { 1380 int iconExtent = proxyStyle->pixelMetric(QStyle::PM_SmallIconSize); 1381 iconSize = QSize(iconExtent, iconExtent); 1382 } 1383 QSize tabIconSize = opt->icon.actualSize(iconSize, 1384 (opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled, 1385 (opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off); 1386 // High-dpi icons do not need adjustment; make sure tabIconSize is not larger than iconSize 1387 tabIconSize = QSize(qMin(tabIconSize.width(), iconSize.width()), qMin(tabIconSize.height(), iconSize.height())); 1388 1389 const int stylePadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2 - hpadding; 1390 1391 if (opt->documentMode) { 1392 // documents show the icon as part of the the text 1393 const int textWidth = 1394 opt->fontMetrics.boundingRect(tr, Qt::AlignCenter | Qt::TextShowMnemonic, opt->text).width(); 1395 *iconRect = QRect(tr.center().x() - textWidth / 2 - stylePadding - tabIconSize.width(), 1396 tr.center().y() - tabIconSize.height() / 2, 1397 tabIconSize.width(), tabIconSize.height()); 1398 } else { 1399 *iconRect = QRect(tr.left() + stylePadding, tr.center().y() - tabIconSize.height() / 2, 1400 tabIconSize.width(), tabIconSize.height()); 1401 } 1402 if (!verticalTabs) 1403 *iconRect = proxyStyle->visualRect(opt->direction, opt->rect, *iconRect); 1404 1405 tr.setLeft(tr.left() + stylePadding + tabIconSize.width() + 4); 1406 tr.setRight(tr.right() - stylePadding - tabIconSize.width() - 4); 1407 } 1408 1409 if (!verticalTabs) 1410 tr = proxyStyle->visualRect(opt->direction, opt->rect, tr); 1411 1412 *textRect = tr; 1413} 1414 1415QMacStylePrivate::Direction QMacStylePrivate::tabDirection(QTabBar::Shape shape) 1416{ 1417 switch (shape) { 1418 case QTabBar::RoundedSouth: 1419 case QTabBar::TriangularSouth: 1420 return South; 1421 case QTabBar::RoundedNorth: 1422 case QTabBar::TriangularNorth: 1423 return North; 1424 case QTabBar::RoundedWest: 1425 case QTabBar::TriangularWest: 1426 return West; 1427 case QTabBar::RoundedEast: 1428 case QTabBar::TriangularEast: 1429 return East; 1430 } 1431} 1432 1433bool QMacStylePrivate::verticalTabs(QMacStylePrivate::Direction direction) 1434{ 1435 return (direction == QMacStylePrivate::East 1436 || direction == QMacStylePrivate::West); 1437} 1438 1439#endif // QT_CONFIG(tabbar) 1440 1441QStyleHelper::WidgetSizePolicy QMacStylePrivate::effectiveAquaSizeConstrain(const QStyleOption *option, 1442 const QWidget *widg, 1443 QStyle::ContentsType ct, 1444 QSize szHint, QSize *insz) const 1445{ 1446 QStyleHelper::WidgetSizePolicy sz = aquaSizeConstrain(option, widg, ct, szHint, insz); 1447 if (sz == QStyleHelper::SizeDefault) 1448 return QStyleHelper::SizeLarge; 1449 return sz; 1450} 1451 1452QStyleHelper::WidgetSizePolicy QMacStylePrivate::aquaSizeConstrain(const QStyleOption *option, const QWidget *widg, 1453 QStyle::ContentsType ct, QSize szHint, QSize *insz) const 1454{ 1455#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) 1456 if (option) { 1457 if (option->state & QStyle::State_Small) 1458 return QStyleHelper::SizeSmall; 1459 if (option->state & QStyle::State_Mini) 1460 return QStyleHelper::SizeMini; 1461 } 1462 1463 if (!widg) { 1464 if (insz) 1465 *insz = QSize(); 1466 if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL")) 1467 return QStyleHelper::SizeSmall; 1468 if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI")) 1469 return QStyleHelper::SizeMini; 1470 return QStyleHelper::SizeDefault; 1471 } 1472 1473 QSize large = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeLarge), 1474 small = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeSmall), 1475 mini = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeMini); 1476 bool guess_size = false; 1477 QStyleHelper::WidgetSizePolicy ret = QStyleHelper::SizeDefault; 1478 QStyleHelper::WidgetSizePolicy wsp = QStyleHelper::widgetSizePolicy(widg); 1479 if (wsp == QStyleHelper::SizeDefault) 1480 guess_size = true; 1481 else if (wsp == QStyleHelper::SizeMini) 1482 ret = QStyleHelper::SizeMini; 1483 else if (wsp == QStyleHelper::SizeSmall) 1484 ret = QStyleHelper::SizeSmall; 1485 else if (wsp == QStyleHelper::SizeLarge) 1486 ret = QStyleHelper::SizeLarge; 1487 if (guess_size) 1488 ret = qt_aqua_guess_size(widg, large, small, mini); 1489 1490 QSize *sz = 0; 1491 if (ret == QStyleHelper::SizeSmall) 1492 sz = &small; 1493 else if (ret == QStyleHelper::SizeLarge) 1494 sz = &large; 1495 else if (ret == QStyleHelper::SizeMini) 1496 sz = &mini; 1497 if (insz) 1498 *insz = sz ? *sz : QSize(-1, -1); 1499#ifdef DEBUG_SIZE_CONSTRAINT 1500 if (sz) { 1501 const char *size_desc = "Unknown"; 1502 if (sz == &small) 1503 size_desc = "Small"; 1504 else if (sz == &large) 1505 size_desc = "Large"; 1506 else if (sz == &mini) 1507 size_desc = "Mini"; 1508 qDebug("%s - %s: %s taken (%d, %d) [%d, %d]", 1509 widg ? widg->objectName().toLatin1().constData() : "*Unknown*", 1510 widg ? widg->metaObject()->className() : "*Unknown*", size_desc, widg->width(), widg->height(), 1511 sz->width(), sz->height()); 1512 } 1513#endif 1514 return ret; 1515#else 1516 if (insz) 1517 *insz = QSize(); 1518 Q_UNUSED(widg); 1519 Q_UNUSED(ct); 1520 Q_UNUSED(szHint); 1521 return QStyleHelper::SizeDefault; 1522#endif 1523} 1524 1525uint qHash(const QMacStylePrivate::CocoaControl &cw, uint seed = 0) 1526{ 1527 return ((cw.type << 2) | cw.size) ^ seed; 1528} 1529 1530QMacStylePrivate::CocoaControl::CocoaControl() 1531 : type(NoControl), size(QStyleHelper::SizeDefault) 1532{ 1533} 1534 1535QMacStylePrivate::CocoaControl::CocoaControl(CocoaControlType t, QStyleHelper::WidgetSizePolicy s) 1536 : type(t), size(s) 1537{ 1538} 1539 1540bool QMacStylePrivate::CocoaControl::operator==(const CocoaControl &other) const 1541{ 1542 return other.type == type && other.size == size; 1543} 1544 1545QSizeF QMacStylePrivate::CocoaControl::defaultFrameSize() const 1546{ 1547 // We need this because things like NSView.alignmentRectInsets 1548 // or -[NSCell titleRectForBounds:] won't work unless the control 1549 // has a reasonable frame set. IOW, it's a chicken and egg problem. 1550 // These values are as observed in Xcode 9's Interface Builder. 1551 1552 if (type == Button_PushButton) 1553 return QSizeF(-1, pushButtonDefaultHeight[size]); 1554 1555 if (type == Button_PopupButton 1556 || type == Button_PullDown) 1557 return QSizeF(-1, popupButtonDefaultHeight[size]); 1558 1559 if (type == ComboBox) 1560 return QSizeF(-1, comboBoxDefaultHeight[size]); 1561 1562 return QSizeF(); 1563} 1564 1565QRectF QMacStylePrivate::CocoaControl::adjustedControlFrame(const QRectF &rect) const 1566{ 1567 QRectF frameRect; 1568 const auto frameSize = defaultFrameSize(); 1569 if (type == QMacStylePrivate::Button_SquareButton) { 1570 frameRect = rect.adjusted(3, 1, -3, -1) 1571 .adjusted(focusRingWidth, focusRingWidth, -focusRingWidth, -focusRingWidth); 1572 } else if (type == QMacStylePrivate::Button_PushButton) { 1573 // Start from the style option's top-left corner. 1574 frameRect = QRectF(rect.topLeft(), 1575 QSizeF(rect.width(), frameSize.height())); 1576 if (size == QStyleHelper::SizeSmall) 1577 frameRect = frameRect.translated(0, 1.5); 1578 else if (size == QStyleHelper::SizeMini) 1579 frameRect = frameRect.adjusted(0, 0, -8, 0).translated(4, 4); 1580 } else { 1581 // Center in the style option's rect. 1582 frameRect = QRectF(QPointF(0, (rect.height() - frameSize.height()) / 2.0), 1583 QSizeF(rect.width(), frameSize.height())); 1584 frameRect = frameRect.translated(rect.topLeft()); 1585 if (type == QMacStylePrivate::Button_PullDown || type == QMacStylePrivate::Button_PopupButton) { 1586 if (size == QStyleHelper::SizeLarge) 1587 frameRect = frameRect.adjusted(0, 0, -6, 0).translated(3, 0); 1588 else if (size == QStyleHelper::SizeSmall) 1589 frameRect = frameRect.adjusted(0, 0, -4, 0).translated(2, 1); 1590 else if (size == QStyleHelper::SizeMini) 1591 frameRect = frameRect.adjusted(0, 0, -9, 0).translated(5, 0); 1592 } else if (type == QMacStylePrivate::ComboBox) { 1593 frameRect = frameRect.adjusted(0, 0, -6, 0).translated(4, 0); 1594 } 1595 } 1596 1597 return frameRect; 1598} 1599 1600QMarginsF QMacStylePrivate::CocoaControl::titleMargins() const 1601{ 1602 if (type == QMacStylePrivate::Button_PushButton) { 1603 if (size == QStyleHelper::SizeLarge) 1604 return QMarginsF(12, 5, 12, 9); 1605 if (size == QStyleHelper::SizeSmall) 1606 return QMarginsF(12, 4, 12, 9); 1607 if (size == QStyleHelper::SizeMini) 1608 return QMarginsF(10, 1, 10, 2); 1609 } 1610 1611 if (type == QMacStylePrivate::Button_PullDown) { 1612 if (size == QStyleHelper::SizeLarge) 1613 return QMarginsF(7.5, 2.5, 22.5, 5.5); 1614 if (size == QStyleHelper::SizeSmall) 1615 return QMarginsF(7.5, 2, 20.5, 4); 1616 if (size == QStyleHelper::SizeMini) 1617 return QMarginsF(4.5, 0, 16.5, 2); 1618 } 1619 1620 if (type == QMacStylePrivate::Button_SquareButton) 1621 return QMarginsF(6, 1, 6, 2); 1622 1623 return QMarginsF(); 1624} 1625 1626bool QMacStylePrivate::CocoaControl::getCocoaButtonTypeAndBezelStyle(NSButtonType *buttonType, NSBezelStyle *bezelStyle) const 1627{ 1628 switch (type) { 1629 case Button_CheckBox: 1630 *buttonType = NSSwitchButton; 1631 *bezelStyle = NSRegularSquareBezelStyle; 1632 break; 1633 case Button_Disclosure: 1634 *buttonType = NSOnOffButton; 1635 *bezelStyle = NSDisclosureBezelStyle; 1636 break; 1637 case Button_RadioButton: 1638 *buttonType = NSRadioButton; 1639 *bezelStyle = NSRegularSquareBezelStyle; 1640 break; 1641 case Button_SquareButton: 1642 *buttonType = NSPushOnPushOffButton; 1643 *bezelStyle = NSShadowlessSquareBezelStyle; 1644 break; 1645 case Button_PushButton: 1646 *buttonType = NSPushOnPushOffButton; 1647 *bezelStyle = NSRoundedBezelStyle; 1648 break; 1649 default: 1650 return false; 1651 } 1652 1653 return true; 1654} 1655 1656QMacStylePrivate::CocoaControlType cocoaControlType(const QStyleOption *opt, const QWidget *w) 1657{ 1658 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { 1659 const bool hasMenu = btn->features & QStyleOptionButton::HasMenu; 1660 // When the contents won't fit in a large sized button, 1661 // and WA_MacNormalSize is not set, make the button square. 1662 // Threshold used to be at 34, not 32. 1663 const auto maxNonSquareHeight = pushButtonDefaultHeight[QStyleHelper::SizeLarge]; 1664 const bool isSquare = (btn->features & QStyleOptionButton::Flat) 1665 || (btn->rect.height() > maxNonSquareHeight 1666 && !(w && w->testAttribute(Qt::WA_MacNormalSize))); 1667 return (isSquare? QMacStylePrivate::Button_SquareButton : 1668 hasMenu ? QMacStylePrivate::Button_PullDown : 1669 QMacStylePrivate::Button_PushButton); 1670 } 1671 1672 if (const auto *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 1673 if (combo->editable) 1674 return QMacStylePrivate::ComboBox; 1675 // TODO Me may support square, non-editable combo boxes, but not more than that 1676 return QMacStylePrivate::Button_PopupButton; 1677 } 1678 1679 return QMacStylePrivate::NoControl; 1680} 1681 1682/** 1683 Carbon draws comboboxes (and other views) outside the rect given as argument. Use this function to obtain 1684 the corresponding inner rect for drawing the same combobox so that it stays inside the given outerBounds. 1685*/ 1686CGRect QMacStylePrivate::comboboxInnerBounds(const CGRect &outerBounds, const CocoaControl &cocoaWidget) 1687{ 1688 CGRect innerBounds = outerBounds; 1689 // Carbon draw parts of the view outside the rect. 1690 // So make the rect a bit smaller to compensate 1691 // (I wish HIThemeGetButtonBackgroundBounds worked) 1692 if (cocoaWidget.type == Button_PopupButton) { 1693 switch (cocoaWidget.size) { 1694 case QStyleHelper::SizeSmall: 1695 innerBounds.origin.x += 3; 1696 innerBounds.origin.y += 3; 1697 innerBounds.size.width -= 6; 1698 innerBounds.size.height -= 7; 1699 break; 1700 case QStyleHelper::SizeMini: 1701 innerBounds.origin.x += 2; 1702 innerBounds.origin.y += 2; 1703 innerBounds.size.width -= 5; 1704 innerBounds.size.height -= 6; 1705 break; 1706 case QStyleHelper::SizeLarge: 1707 case QStyleHelper::SizeDefault: 1708 innerBounds.origin.x += 2; 1709 innerBounds.origin.y += 2; 1710 innerBounds.size.width -= 5; 1711 innerBounds.size.height -= 6; 1712 } 1713 } else if (cocoaWidget.type == ComboBox) { 1714 switch (cocoaWidget.size) { 1715 case QStyleHelper::SizeSmall: 1716 innerBounds.origin.x += 3; 1717 innerBounds.origin.y += 3; 1718 innerBounds.size.width -= 7; 1719 innerBounds.size.height -= 8; 1720 break; 1721 case QStyleHelper::SizeMini: 1722 innerBounds.origin.x += 3; 1723 innerBounds.origin.y += 3; 1724 innerBounds.size.width -= 4; 1725 innerBounds.size.height -= 8; 1726 break; 1727 case QStyleHelper::SizeLarge: 1728 case QStyleHelper::SizeDefault: 1729 innerBounds.origin.x += 3; 1730 innerBounds.origin.y += 2; 1731 innerBounds.size.width -= 6; 1732 innerBounds.size.height -= 8; 1733 } 1734 } 1735 1736 return innerBounds; 1737} 1738 1739/** 1740 Inside a combobox Qt places a line edit widget. The size of this widget should depend on the kind 1741 of combobox we choose to draw. This function calculates and returns this size. 1742*/ 1743QRectF QMacStylePrivate::comboboxEditBounds(const QRectF &outerBounds, const CocoaControl &cw) 1744{ 1745 QRectF ret = outerBounds; 1746 if (cw.type == ComboBox) { 1747 switch (cw.size) { 1748 case QStyleHelper::SizeLarge: 1749 ret = ret.adjusted(0, 0, -25, 0).translated(2, 4.5); 1750 ret.setHeight(16); 1751 break; 1752 case QStyleHelper::SizeSmall: 1753 ret = ret.adjusted(0, 0, -22, 0).translated(2, 3); 1754 ret.setHeight(14); 1755 break; 1756 case QStyleHelper::SizeMini: 1757 ret = ret.adjusted(0, 0, -19, 0).translated(2, 2.5); 1758 ret.setHeight(10.5); 1759 break; 1760 default: 1761 break; 1762 } 1763 } else if (cw.type == Button_PopupButton) { 1764 switch (cw.size) { 1765 case QStyleHelper::SizeLarge: 1766 ret.adjust(10, 1, -23, -4); 1767 break; 1768 case QStyleHelper::SizeSmall: 1769 ret.adjust(10, 4, -20, -3); 1770 break; 1771 case QStyleHelper::SizeMini: 1772 ret.adjust(9, 0, -19, 0); 1773 ret.setHeight(13); 1774 break; 1775 default: 1776 break; 1777 } 1778 } 1779 return ret; 1780} 1781 1782QMacStylePrivate::QMacStylePrivate() 1783 : backingStoreNSView(nil) 1784{ 1785 if (auto *ssf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::SmallFont)) 1786 smallSystemFont = *ssf; 1787 if (auto *msf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::MiniFont)) 1788 miniSystemFont = *msf; 1789} 1790 1791QMacStylePrivate::~QMacStylePrivate() 1792{ 1793 QMacAutoReleasePool pool; 1794 for (NSView *b : cocoaControls) 1795 [b release]; 1796 for (NSCell *cell : cocoaCells) 1797 [cell release]; 1798} 1799 1800NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const 1801{ 1802 if (widget.type == QMacStylePrivate::NoControl 1803 || widget.size == QStyleHelper::SizeDefault) 1804 return nil; 1805 1806 if (widget.type == Box) { 1807 if (__builtin_available(macOS 10.14, *)) { 1808 if (qt_mac_applicationIsInDarkMode()) { 1809 // See render code in drawPrimitive(PE_FrameTabWidget) 1810 widget.type = Box_Dark; 1811 } 1812 } 1813 } 1814 1815 NSView *bv = cocoaControls.value(widget, nil); 1816 if (!bv) { 1817 switch (widget.type) { 1818 case Box: { 1819 NSBox *box = [[NSBox alloc] init]; 1820 bv = box; 1821 box.title = @""; 1822 box.titlePosition = NSNoTitle; 1823 break; 1824 } 1825 case Box_Dark: 1826 bv = [[QDarkNSBox alloc] init]; 1827 break; 1828 case Button_CheckBox: 1829 case Button_Disclosure: 1830 case Button_PushButton: 1831 case Button_RadioButton: 1832 case Button_SquareButton: { 1833 NSButton *bc = [[NSButton alloc] init]; 1834 bc.title = @""; 1835 // See below for style and bezel setting. 1836 bv = bc; 1837 break; 1838 } 1839 case Button_PopupButton: 1840 case Button_PullDown: { 1841 NSPopUpButton *bc = [[NSPopUpButton alloc] init]; 1842 bc.title = @""; 1843 if (widget.type == Button_PullDown) 1844 bc.pullsDown = YES; 1845 bv = bc; 1846 break; 1847 } 1848 case Button_WindowClose: 1849 case Button_WindowMiniaturize: 1850 case Button_WindowZoom: { 1851 const NSWindowButton button = [=] { 1852 switch (widget.type) { 1853 case Button_WindowClose: 1854 return NSWindowCloseButton; 1855 case Button_WindowMiniaturize: 1856 return NSWindowMiniaturizeButton; 1857 case Button_WindowZoom: 1858 return NSWindowZoomButton; 1859 default: 1860 break; 1861 } 1862 Q_UNREACHABLE(); 1863 } (); 1864 const auto styleMask = NSWindowStyleMaskTitled 1865 | NSWindowStyleMaskClosable 1866 | NSWindowStyleMaskMiniaturizable 1867 | NSWindowStyleMaskResizable; 1868 bv = [NSWindow standardWindowButton:button forStyleMask:styleMask]; 1869 [bv retain]; 1870 break; 1871 } 1872 case ComboBox: 1873 bv = [[NSComboBox alloc] init]; 1874 break; 1875 case ProgressIndicator_Determinate: 1876 bv = [[NSProgressIndicator alloc] init]; 1877 break; 1878 case ProgressIndicator_Indeterminate: 1879 bv = [[QIndeterminateProgressIndicator alloc] init]; 1880 break; 1881 case Scroller_Horizontal: 1882 bv = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 200, 20)]; 1883 break; 1884 case Scroller_Vertical: 1885 // Cocoa sets the orientation from the view's frame 1886 // at construction time, and it cannot be changed later. 1887 bv = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 20, 200)]; 1888 break; 1889 case Slider_Horizontal: 1890 bv = [[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 200, 20)]; 1891 break; 1892 case Slider_Vertical: 1893 // Cocoa sets the orientation from the view's frame 1894 // at construction time, and it cannot be changed later. 1895 bv = [[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 20, 200)]; 1896 break; 1897 case SplitView_Horizontal: 1898 bv = [[NSSplitView alloc] init]; 1899 break; 1900 case SplitView_Vertical: 1901 bv = [[QVerticalSplitView alloc] init]; 1902 break; 1903 case TextField: 1904 bv = [[NSTextField alloc] init]; 1905 break; 1906 default: 1907 break; 1908 } 1909 1910 if ([bv isKindOfClass:[NSControl class]]) { 1911 auto *ctrl = static_cast<NSControl *>(bv); 1912 switch (widget.size) { 1913 case QStyleHelper::SizeSmall: 1914 ctrl.controlSize = NSControlSizeSmall; 1915 break; 1916 case QStyleHelper::SizeMini: 1917 ctrl.controlSize = NSControlSizeMini; 1918 break; 1919 default: 1920 break; 1921 } 1922 } else if (widget.type == ProgressIndicator_Determinate || 1923 widget.type == ProgressIndicator_Indeterminate) { 1924 auto *pi = static_cast<NSProgressIndicator *>(bv); 1925 pi.indeterminate = (widget.type == ProgressIndicator_Indeterminate); 1926 switch (widget.size) { 1927 case QStyleHelper::SizeSmall: 1928 pi.controlSize = NSControlSizeSmall; 1929 break; 1930 case QStyleHelper::SizeMini: 1931 pi.controlSize = NSControlSizeMini; 1932 break; 1933 default: 1934 break; 1935 } 1936 } 1937 1938 cocoaControls.insert(widget, bv); 1939 } 1940 1941 NSButtonType buttonType; 1942 NSBezelStyle bezelStyle; 1943 if (widget.getCocoaButtonTypeAndBezelStyle(&buttonType, &bezelStyle)) { 1944 // FIXME We need to reset the button's type and 1945 // bezel style properties, even when cached. 1946 auto *button = static_cast<NSButton *>(bv); 1947 button.buttonType = buttonType; 1948 button.bezelStyle = bezelStyle; 1949 if (widget.type == Button_CheckBox) 1950 button.allowsMixedState = YES; 1951 } 1952 1953 return bv; 1954} 1955 1956NSCell *QMacStylePrivate::cocoaCell(CocoaControl widget) const 1957{ 1958 NSCell *cell = cocoaCells[widget]; 1959 if (!cell) { 1960 switch (widget.type) { 1961 case Stepper: 1962 cell = [[NSStepperCell alloc] init]; 1963 break; 1964 case Button_Disclosure: { 1965 NSButtonCell *bc = [[NSButtonCell alloc] init]; 1966 bc.buttonType = NSOnOffButton; 1967 bc.bezelStyle = NSDisclosureBezelStyle; 1968 cell = bc; 1969 break; 1970 } 1971 default: 1972 break; 1973 } 1974 1975 switch (widget.size) { 1976 case QStyleHelper::SizeSmall: 1977 cell.controlSize = NSControlSizeSmall; 1978 break; 1979 case QStyleHelper::SizeMini: 1980 cell.controlSize = NSControlSizeMini; 1981 break; 1982 default: 1983 break; 1984 } 1985 1986 cocoaCells.insert(widget, cell); 1987 } 1988 1989 return cell; 1990} 1991 1992void QMacStylePrivate::drawNSViewInRect(NSView *view, const QRectF &rect, QPainter *p, 1993 __attribute__((noescape)) DrawRectBlock drawRectBlock) const 1994{ 1995 QMacCGContext ctx(p); 1996 setupNSGraphicsContext(ctx, YES); 1997 1998 // FIXME: The rect that we get in is relative to the widget that we're drawing 1999 // style on behalf of, and doesn't take into account the offset of that widget 2000 // to the widget that owns the backingstore, which we are placing the native 2001 // view into below. This means most of the views are placed in the upper left 2002 // corner of backingStoreNSView, which does not map to where the actual widget 2003 // is, and which may cause problems such as triggering a setNeedsDisplay of the 2004 // backingStoreNSView for the wrong rect. We work around this by making the view 2005 // layer-backed, which prevents triggering display of the backingStoreNSView, but 2006 // but there may be other issues lurking here due to the wrong position. QTBUG-68023 2007 view.wantsLayer = YES; 2008 2009 // FIXME: We are also setting the frame of the incoming view a lot at the call 2010 // sites of this function, making it unclear who's actually responsible for 2011 // maintaining the size and position of the view. In theory the call sites 2012 // should ensure the _size_ of the view is correct, and then let this code 2013 // take care of _positioning_ the view at the right place inside backingStoreNSView. 2014 // For now we pass on the rect as is, to prevent any regressions until this 2015 // can be investigated properly. 2016 view.frame = rect.toCGRect(); 2017 2018 [backingStoreNSView addSubview:view]; 2019 2020 // FIXME: Based on the code below, this method isn't drawing an NSView into 2021 // a rect, it's drawing _part of the NSView_, defined by the incoming clip 2022 // or dirty rect, into the current graphics context. We're doing some manual 2023 // translations at the call sites that would indicate that this relationship 2024 // is a bit fuzzy. 2025 const CGRect dirtyRect = rect.toCGRect(); 2026 2027 if (drawRectBlock) 2028 drawRectBlock(ctx, dirtyRect); 2029 else 2030 [view drawRect:dirtyRect]; 2031 2032 [view removeFromSuperviewWithoutNeedingDisplay]; 2033 2034 restoreNSGraphicsContext(ctx); 2035} 2036 2037void QMacStylePrivate::resolveCurrentNSView(QWindow *window) const 2038{ 2039 backingStoreNSView = window ? (NSView *)window->winId() : nil; 2040} 2041 2042QMacStyle::QMacStyle() 2043 : QCommonStyle(*new QMacStylePrivate) 2044{ 2045 QMacAutoReleasePool pool; 2046 2047 static QMacNotificationObserver scrollbarStyleObserver(nil, 2048 NSPreferredScrollerStyleDidChangeNotification, []() { 2049 // Purge destroyed scroll bars 2050 QMacStylePrivate::scrollBars.removeAll(QPointer<QObject>()); 2051 2052 QEvent event(QEvent::StyleChange); 2053 for (const auto &o : QMacStylePrivate::scrollBars) 2054 QCoreApplication::sendEvent(o, &event); 2055 }); 2056 2057#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) 2058 Q_D(QMacStyle); 2059 // FIXME: Tie this logic into theme change, or even polish/unpolish 2060 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) { 2061 d->appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { 2062 Q_D(QMacStyle); 2063 for (NSView *b : d->cocoaControls) 2064 [b release]; 2065 d->cocoaControls.clear(); 2066 }); 2067 } 2068#endif 2069} 2070 2071QMacStyle::~QMacStyle() 2072{ 2073} 2074 2075void QMacStyle::polish(QPalette &) 2076{ 2077} 2078 2079void QMacStyle::polish(QApplication *) 2080{ 2081} 2082 2083void QMacStyle::unpolish(QApplication *) 2084{ 2085} 2086 2087void QMacStyle::polish(QWidget* w) 2088{ 2089 if (false 2090#if QT_CONFIG(menu) 2091 || qobject_cast<QMenu*>(w) 2092# if QT_CONFIG(combobox) 2093 || qobject_cast<QComboBoxPrivateContainer *>(w) 2094# endif 2095#endif 2096#if QT_CONFIG(mdiarea) 2097 || qobject_cast<QMdiSubWindow *>(w) 2098#endif 2099 ) { 2100 w->setAttribute(Qt::WA_TranslucentBackground, true); 2101 w->setAutoFillBackground(false); 2102 } 2103 2104#if QT_CONFIG(tabbar) 2105 if (QTabBar *tb = qobject_cast<QTabBar*>(w)) { 2106 if (tb->documentMode()) { 2107 w->setAttribute(Qt::WA_Hover); 2108 w->setFont(qt_app_fonts_hash()->value("QSmallFont", QFont())); 2109 QPalette p = w->palette(); 2110 p.setColor(QPalette::WindowText, QColor(17, 17, 17)); 2111 w->setPalette(p); 2112 w->setAttribute(Qt::WA_SetPalette, false); 2113 w->setAttribute(Qt::WA_SetFont, false); 2114 } 2115 } 2116#endif 2117 2118 QCommonStyle::polish(w); 2119 2120 if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) { 2121 rubber->setWindowOpacity(0.25); 2122 rubber->setAttribute(Qt::WA_PaintOnScreen, false); 2123 rubber->setAttribute(Qt::WA_NoSystemBackground, false); 2124 } 2125 2126 if (qobject_cast<QScrollBar*>(w)) { 2127 w->setAttribute(Qt::WA_OpaquePaintEvent, false); 2128 w->setAttribute(Qt::WA_Hover, true); 2129 w->setMouseTracking(true); 2130 } 2131} 2132 2133void QMacStyle::unpolish(QWidget* w) 2134{ 2135 if ( 2136#if QT_CONFIG(menu) 2137 qobject_cast<QMenu*>(w) && 2138#endif 2139 !w->testAttribute(Qt::WA_SetPalette)) 2140 { 2141 w->setPalette(QPalette()); 2142 w->setWindowOpacity(1.0); 2143 } 2144 2145#if QT_CONFIG(combobox) 2146 if (QComboBox *combo = qobject_cast<QComboBox *>(w)) { 2147 if (!combo->isEditable()) { 2148 if (QWidget *widget = combo->findChild<QComboBoxPrivateContainer *>()) 2149 widget->setWindowOpacity(1.0); 2150 } 2151 } 2152#endif 2153 2154#if QT_CONFIG(tabbar) 2155 if (qobject_cast<QTabBar*>(w)) { 2156 if (!w->testAttribute(Qt::WA_SetFont)) 2157 w->setFont(QFont()); 2158 if (!w->testAttribute(Qt::WA_SetPalette)) 2159 w->setPalette(QPalette()); 2160 } 2161#endif 2162 2163 if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) { 2164 rubber->setWindowOpacity(1.0); 2165 rubber->setAttribute(Qt::WA_PaintOnScreen, true); 2166 rubber->setAttribute(Qt::WA_NoSystemBackground, true); 2167 } 2168 2169 if (QFocusFrame *frame = qobject_cast<QFocusFrame *>(w)) 2170 frame->setAttribute(Qt::WA_NoSystemBackground, true); 2171 2172 QCommonStyle::unpolish(w); 2173 2174 if (qobject_cast<QScrollBar*>(w)) { 2175 w->setAttribute(Qt::WA_OpaquePaintEvent, true); 2176 w->setAttribute(Qt::WA_Hover, false); 2177 w->setMouseTracking(false); 2178 } 2179} 2180 2181int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const 2182{ 2183 Q_D(const QMacStyle); 2184 const int controlSize = getControlSize(opt, widget); 2185 int ret = 0; 2186 2187 switch (metric) { 2188#if QT_CONFIG(tabbar) 2189 case PM_TabCloseIndicatorWidth: 2190 case PM_TabCloseIndicatorHeight: 2191 ret = closeButtonSize; 2192 break; 2193#endif 2194 case PM_ToolBarIconSize: 2195 ret = proxy()->pixelMetric(PM_LargeIconSize); 2196 break; 2197 case PM_FocusFrameVMargin: 2198 case PM_FocusFrameHMargin: 2199 ret = qt_mac_aqua_get_metric(FocusRectOutset); 2200 break; 2201 case PM_DialogButtonsSeparator: 2202 ret = -5; 2203 break; 2204 case PM_DialogButtonsButtonHeight: { 2205 QSize sz; 2206 ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); 2207 if (sz == QSize(-1, -1)) 2208 ret = 32; 2209 else 2210 ret = sz.height(); 2211 break; } 2212 case PM_DialogButtonsButtonWidth: { 2213 QSize sz; 2214 ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); 2215 if (sz == QSize(-1, -1)) 2216 ret = 70; 2217 else 2218 ret = sz.width(); 2219 break; } 2220 2221 case PM_MenuBarHMargin: 2222 ret = 8; 2223 break; 2224 2225 case PM_MenuBarVMargin: 2226 ret = 0; 2227 break; 2228 2229 case PM_MenuBarPanelWidth: 2230 ret = 0; 2231 break; 2232 2233 case PM_MenuButtonIndicator: 2234 ret = toolButtonArrowSize; 2235 break; 2236 2237 case QStyle::PM_MenuDesktopFrameWidth: 2238 ret = 5; 2239 break; 2240 2241 case PM_CheckBoxLabelSpacing: 2242 case PM_RadioButtonLabelSpacing: 2243 ret = [=] { 2244 if (opt) { 2245 if (opt->state & State_Mini) 2246 return 4; 2247 if (opt->state & State_Small) 2248 return 3; 2249 } 2250 return 2; 2251 } (); 2252 break; 2253 case PM_MenuScrollerHeight: 2254 ret = 15; // I hate having magic numbers in here... 2255 break; 2256 case PM_DefaultFrameWidth: 2257#if QT_CONFIG(mainwindow) 2258 if (widget && (widget->isWindow() || !widget->parentWidget() 2259 || (qobject_cast<const QMainWindow*>(widget->parentWidget()) 2260 && static_cast<QMainWindow *>(widget->parentWidget())->centralWidget() == widget)) 2261 && qobject_cast<const QAbstractScrollArea *>(widget)) 2262 ret = 0; 2263 else 2264#endif 2265 // The combo box popup has no frame. 2266 if (qstyleoption_cast<const QStyleOptionComboBox *>(opt) != 0) 2267 ret = 0; 2268 else 2269 ret = 1; 2270 break; 2271 case PM_MaximumDragDistance: 2272 ret = -1; 2273 break; 2274 case PM_ScrollBarSliderMin: 2275 ret = 24; 2276 break; 2277 case PM_SpinBoxFrameWidth: 2278 ret = qt_mac_aqua_get_metric(EditTextFrameOutset); 2279 break; 2280 case PM_ButtonShiftHorizontal: 2281 case PM_ButtonShiftVertical: 2282 ret = 0; 2283 break; 2284 case PM_SliderLength: 2285 ret = 17; 2286 break; 2287 // Returns the number of pixels to use for the business part of the 2288 // slider (i.e., the non-tickmark portion). The remaining space is shared 2289 // equally between the tickmark regions. 2290 case PM_SliderControlThickness: 2291 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 2292 int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); 2293 int ticks = sl->tickPosition; 2294 int n = 0; 2295 if (ticks & QSlider::TicksAbove) 2296 ++n; 2297 if (ticks & QSlider::TicksBelow) 2298 ++n; 2299 if (!n) { 2300 ret = space; 2301 break; 2302 } 2303 2304 int thick = 6; // Magic constant to get 5 + 16 + 5 2305 if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks) 2306 thick += proxy()->pixelMetric(PM_SliderLength, sl, widget) / 4; 2307 2308 space -= thick; 2309 if (space > 0) 2310 thick += (space * 2) / (n + 2); 2311 ret = thick; 2312 } else { 2313 ret = 0; 2314 } 2315 break; 2316 case PM_SmallIconSize: 2317 ret = int(QStyleHelper::dpiScaled(16., opt)); 2318 break; 2319 2320 case PM_LargeIconSize: 2321 ret = int(QStyleHelper::dpiScaled(32., opt)); 2322 break; 2323 2324 case PM_IconViewIconSize: 2325 ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget); 2326 break; 2327 2328 case PM_ButtonDefaultIndicator: 2329 ret = 0; 2330 break; 2331 case PM_TitleBarHeight: { 2332 NSUInteger style = NSWindowStyleMaskTitled; 2333 if (widget && ((widget->windowFlags() & Qt::Tool) == Qt::Tool)) 2334 style |= NSWindowStyleMaskUtilityWindow; 2335 ret = int([NSWindow frameRectForContentRect:NSZeroRect 2336 styleMask:style].size.height); 2337 break; } 2338 case QStyle::PM_TabBarTabHSpace: 2339 switch (d->aquaSizeConstrain(opt, widget)) { 2340 case QStyleHelper::SizeLarge: 2341 ret = QCommonStyle::pixelMetric(metric, opt, widget); 2342 break; 2343 case QStyleHelper::SizeSmall: 2344 ret = 20; 2345 break; 2346 case QStyleHelper::SizeMini: 2347 ret = 16; 2348 break; 2349 case QStyleHelper::SizeDefault: 2350#if QT_CONFIG(tabbar) 2351 const QStyleOptionTab *tb = qstyleoption_cast<const QStyleOptionTab *>(opt); 2352 if (tb && tb->documentMode) 2353 ret = 30; 2354 else 2355#endif 2356 ret = QCommonStyle::pixelMetric(metric, opt, widget); 2357 break; 2358 } 2359 break; 2360 case PM_TabBarTabVSpace: 2361 ret = 4; 2362 break; 2363 case PM_TabBarTabShiftHorizontal: 2364 case PM_TabBarTabShiftVertical: 2365 ret = 0; 2366 break; 2367 case PM_TabBarBaseHeight: 2368 ret = 0; 2369 break; 2370 case PM_TabBarTabOverlap: 2371 ret = 1; 2372 break; 2373 case PM_TabBarBaseOverlap: 2374 switch (d->aquaSizeConstrain(opt, widget)) { 2375 case QStyleHelper::SizeDefault: 2376 case QStyleHelper::SizeLarge: 2377 ret = 11; 2378 break; 2379 case QStyleHelper::SizeSmall: 2380 ret = 8; 2381 break; 2382 case QStyleHelper::SizeMini: 2383 ret = 7; 2384 break; 2385 } 2386 break; 2387 case PM_ScrollBarExtent: { 2388 const QStyleHelper::WidgetSizePolicy size = d->effectiveAquaSizeConstrain(opt, widget); 2389 ret = static_cast<int>([NSScroller 2390 scrollerWidthForControlSize:static_cast<NSControlSize>(size) 2391 scrollerStyle:[NSScroller preferredScrollerStyle]]); 2392 break; } 2393 case PM_IndicatorHeight: { 2394 switch (d->aquaSizeConstrain(opt, widget)) { 2395 case QStyleHelper::SizeDefault: 2396 case QStyleHelper::SizeLarge: 2397 ret = qt_mac_aqua_get_metric(CheckBoxHeight); 2398 break; 2399 case QStyleHelper::SizeMini: 2400 ret = qt_mac_aqua_get_metric(MiniCheckBoxHeight); 2401 break; 2402 case QStyleHelper::SizeSmall: 2403 ret = qt_mac_aqua_get_metric(SmallCheckBoxHeight); 2404 break; 2405 } 2406 break; } 2407 case PM_IndicatorWidth: { 2408 switch (d->aquaSizeConstrain(opt, widget)) { 2409 case QStyleHelper::SizeDefault: 2410 case QStyleHelper::SizeLarge: 2411 ret = qt_mac_aqua_get_metric(CheckBoxWidth); 2412 break; 2413 case QStyleHelper::SizeMini: 2414 ret = qt_mac_aqua_get_metric(MiniCheckBoxWidth); 2415 break; 2416 case QStyleHelper::SizeSmall: 2417 ret = qt_mac_aqua_get_metric(SmallCheckBoxWidth); 2418 break; 2419 } 2420 ++ret; 2421 break; } 2422 case PM_ExclusiveIndicatorHeight: { 2423 switch (d->aquaSizeConstrain(opt, widget)) { 2424 case QStyleHelper::SizeDefault: 2425 case QStyleHelper::SizeLarge: 2426 ret = qt_mac_aqua_get_metric(RadioButtonHeight); 2427 break; 2428 case QStyleHelper::SizeMini: 2429 ret = qt_mac_aqua_get_metric(MiniRadioButtonHeight); 2430 break; 2431 case QStyleHelper::SizeSmall: 2432 ret = qt_mac_aqua_get_metric(SmallRadioButtonHeight); 2433 break; 2434 } 2435 break; } 2436 case PM_ExclusiveIndicatorWidth: { 2437 switch (d->aquaSizeConstrain(opt, widget)) { 2438 case QStyleHelper::SizeDefault: 2439 case QStyleHelper::SizeLarge: 2440 ret = qt_mac_aqua_get_metric(RadioButtonWidth); 2441 break; 2442 case QStyleHelper::SizeMini: 2443 ret = qt_mac_aqua_get_metric(MiniRadioButtonWidth); 2444 break; 2445 case QStyleHelper::SizeSmall: 2446 ret = qt_mac_aqua_get_metric(SmallRadioButtonWidth); 2447 break; 2448 } 2449 ++ret; 2450 break; } 2451 case PM_MenuVMargin: 2452 ret = 4; 2453 break; 2454 case PM_MenuPanelWidth: 2455 ret = 0; 2456 break; 2457 case PM_ToolTipLabelFrameWidth: 2458 ret = 0; 2459 break; 2460 case PM_SizeGripSize: { 2461 QStyleHelper::WidgetSizePolicy aSize; 2462 if (widget && widget->window()->windowType() == Qt::Tool) 2463 aSize = QStyleHelper::SizeSmall; 2464 else 2465 aSize = QStyleHelper::SizeLarge; 2466 const QSize size = qt_aqua_get_known_size(CT_SizeGrip, widget, QSize(), aSize); 2467 ret = size.width(); 2468 break; } 2469 case PM_MdiSubWindowFrameWidth: 2470 ret = 1; 2471 break; 2472 case PM_DockWidgetFrameWidth: 2473 ret = 0; 2474 break; 2475 case PM_DockWidgetTitleMargin: 2476 ret = 0; 2477 break; 2478 case PM_DockWidgetSeparatorExtent: 2479 ret = 1; 2480 break; 2481 case PM_ToolBarHandleExtent: 2482 ret = 11; 2483 break; 2484 case PM_ToolBarItemMargin: 2485 ret = 0; 2486 break; 2487 case PM_ToolBarItemSpacing: 2488 ret = 4; 2489 break; 2490 case PM_SplitterWidth: 2491 ret = qMax(7, QApplication::globalStrut().width()); 2492 break; 2493 case PM_LayoutLeftMargin: 2494 case PM_LayoutTopMargin: 2495 case PM_LayoutRightMargin: 2496 case PM_LayoutBottomMargin: 2497 { 2498 bool isWindow = false; 2499 if (opt) { 2500 isWindow = (opt->state & State_Window); 2501 } else if (widget) { 2502 isWindow = widget->isWindow(); 2503 } 2504 2505 if (isWindow) { 2506 /* 2507 AHIG would have (20, 8, 10) here but that makes 2508 no sense. It would also have 14 for the top margin 2509 but this contradicts both Builder and most 2510 applications. 2511 */ 2512 return_SIZE(20, 10, 10); // AHIG 2513 } else { 2514 // hack to detect QTabWidget 2515 if (widget && widget->parentWidget() 2516 && widget->parentWidget()->sizePolicy().controlType() == QSizePolicy::TabWidget) { 2517 if (metric == PM_LayoutTopMargin) { 2518 /* 2519 Builder would have 14 (= 20 - 6) instead of 12, 2520 but that makes the tab look disproportionate. 2521 */ 2522 return_SIZE(12, 6, 6); // guess 2523 } else { 2524 return_SIZE(20 /* Builder */, 8 /* guess */, 8 /* guess */); 2525 } 2526 } else { 2527 /* 2528 Child margins are highly inconsistent in AHIG and Builder. 2529 */ 2530 return_SIZE(12, 8, 6); // guess 2531 } 2532 } 2533 } 2534 case PM_LayoutHorizontalSpacing: 2535 case PM_LayoutVerticalSpacing: 2536 return -1; 2537 case PM_MenuHMargin: 2538 ret = 0; 2539 break; 2540 case PM_ToolBarExtensionExtent: 2541 ret = 21; 2542 break; 2543 case PM_ToolBarFrameWidth: 2544 ret = 1; 2545 break; 2546 case PM_ScrollView_ScrollBarOverlap: 2547 ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay ? 2548 pixelMetric(PM_ScrollBarExtent, opt, widget) : 0; 2549 break; 2550 default: 2551 ret = QCommonStyle::pixelMetric(metric, opt, widget); 2552 break; 2553 } 2554 return ret; 2555} 2556 2557QPalette QMacStyle::standardPalette() const 2558{ 2559 auto platformTheme = QGuiApplicationPrivate::platformTheme(); 2560 auto styleNames = platformTheme->themeHint(QPlatformTheme::StyleNames); 2561 if (styleNames.toStringList().contains("macintosh")) 2562 return QPalette(); // Inherit everything from theme 2563 else 2564 return QStyle::standardPalette(); 2565} 2566 2567int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, 2568 QStyleHintReturn *hret) const 2569{ 2570 QMacAutoReleasePool pool; 2571 2572 int ret = 0; 2573 switch (sh) { 2574 case SH_Slider_SnapToValue: 2575 case SH_PrintDialog_RightAlignButtons: 2576 case SH_FontDialog_SelectAssociatedText: 2577 case SH_MenuBar_MouseTracking: 2578 case SH_Menu_MouseTracking: 2579 case SH_ComboBox_ListMouseTracking: 2580 case SH_MainWindow_SpaceBelowMenuBar: 2581 case SH_ItemView_ChangeHighlightOnFocus: 2582 ret = 1; 2583 break; 2584 case SH_ToolBox_SelectedPageTitleBold: 2585 ret = 0; 2586 break; 2587 case SH_DialogButtonBox_ButtonsHaveIcons: 2588 ret = 0; 2589 break; 2590 case SH_Menu_SelectionWrap: 2591 ret = false; 2592 break; 2593 case SH_Menu_KeyboardSearch: 2594 ret = true; 2595 break; 2596 case SH_Menu_SpaceActivatesItem: 2597 ret = true; 2598 break; 2599 case SH_Slider_AbsoluteSetButtons: 2600 ret = Qt::LeftButton|Qt::MiddleButton; 2601 break; 2602 case SH_Slider_PageSetButtons: 2603 ret = 0; 2604 break; 2605 case SH_ScrollBar_ContextMenu: 2606 ret = false; 2607 break; 2608 case SH_TitleBar_AutoRaise: 2609 ret = true; 2610 break; 2611 case SH_Menu_AllowActiveAndDisabled: 2612 ret = false; 2613 break; 2614 case SH_Menu_SubMenuPopupDelay: 2615 ret = 100; 2616 break; 2617 case SH_Menu_SubMenuUniDirection: 2618 ret = true; 2619 break; 2620 case SH_Menu_SubMenuSloppySelectOtherActions: 2621 ret = false; 2622 break; 2623 case SH_Menu_SubMenuResetWhenReenteringParent: 2624 ret = true; 2625 break; 2626 case SH_Menu_SubMenuDontStartSloppyOnLeave: 2627 ret = true; 2628 break; 2629 2630 case SH_ScrollBar_LeftClickAbsolutePosition: { 2631 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; 2632 bool result = [defaults boolForKey:@"AppleScrollerPagingBehavior"]; 2633 if(QApplication::keyboardModifiers() & Qt::AltModifier) 2634 ret = !result; 2635 else 2636 ret = result; 2637 break; } 2638 case SH_TabBar_PreferNoArrows: 2639 ret = true; 2640 break; 2641 /* 2642 case SH_DialogButtons_DefaultButton: 2643 ret = QDialogButtons::Reject; 2644 break; 2645 */ 2646 case SH_GroupBox_TextLabelVerticalAlignment: 2647 ret = Qt::AlignTop; 2648 break; 2649 case SH_ScrollView_FrameOnlyAroundContents: 2650 ret = QCommonStyle::styleHint(sh, opt, w, hret); 2651 break; 2652 case SH_Menu_FillScreenWithScroll: 2653 ret = false; 2654 break; 2655 case SH_Menu_Scrollable: 2656 ret = true; 2657 break; 2658 case SH_RichText_FullWidthSelection: 2659 ret = true; 2660 break; 2661 case SH_BlinkCursorWhenTextSelected: 2662 ret = false; 2663 break; 2664 case SH_ScrollBar_StopMouseOverSlider: 2665 ret = true; 2666 break; 2667 case SH_ListViewExpand_SelectMouseType: 2668 ret = QEvent::MouseButtonRelease; 2669 break; 2670 case SH_TabBar_SelectMouseType: 2671#if QT_CONFIG(tabbar) 2672 if (const QStyleOptionTabBarBase *opt2 = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) { 2673 ret = opt2->documentMode ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; 2674 } else 2675#endif 2676 { 2677 ret = QEvent::MouseButtonRelease; 2678 } 2679 break; 2680 case SH_ComboBox_Popup: 2681 if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) 2682 ret = !cmb->editable; 2683 else 2684 ret = 0; 2685 break; 2686 case SH_Workspace_FillSpaceOnMaximize: 2687 ret = true; 2688 break; 2689 case SH_Widget_ShareActivation: 2690 ret = true; 2691 break; 2692 case SH_Header_ArrowAlignment: 2693 ret = Qt::AlignRight; 2694 break; 2695 case SH_TabBar_Alignment: { 2696#if QT_CONFIG(tabwidget) 2697 if (const QTabWidget *tab = qobject_cast<const QTabWidget*>(w)) { 2698 if (tab->documentMode()) { 2699 ret = Qt::AlignLeft; 2700 break; 2701 } 2702 } 2703#endif 2704#if QT_CONFIG(tabbar) 2705 if (const QTabBar *tab = qobject_cast<const QTabBar*>(w)) { 2706 if (tab->documentMode()) { 2707 ret = Qt::AlignLeft; 2708 break; 2709 } 2710 } 2711#endif 2712 ret = Qt::AlignCenter; 2713 } break; 2714 case SH_UnderlineShortcut: 2715 ret = false; 2716 break; 2717 case SH_ToolTipLabel_Opacity: 2718 ret = 242; // About 95% 2719 break; 2720 case SH_Button_FocusPolicy: 2721 ret = Qt::TabFocus; 2722 break; 2723 case SH_EtchDisabledText: 2724 ret = false; 2725 break; 2726 case SH_FocusFrame_Mask: { 2727 ret = true; 2728 if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { 2729 const uchar fillR = 192, fillG = 191, fillB = 190; 2730 QImage img; 2731 2732 QSize pixmapSize = opt->rect.size(); 2733 if (!pixmapSize.isEmpty()) { 2734 QPixmap pix(pixmapSize); 2735 pix.fill(QColor(fillR, fillG, fillB)); 2736 QPainter pix_paint(&pix); 2737 proxy()->drawControl(CE_FocusFrame, opt, &pix_paint, w); 2738 pix_paint.end(); 2739 img = pix.toImage(); 2740 } 2741 2742 const QRgb *sptr = (QRgb*)img.bits(), *srow; 2743 const int sbpl = img.bytesPerLine(); 2744 const int w = sbpl/4, h = img.height(); 2745 2746 QImage img_mask(img.width(), img.height(), QImage::Format_ARGB32); 2747 QRgb *dptr = (QRgb*)img_mask.bits(), *drow; 2748 const int dbpl = img_mask.bytesPerLine(); 2749 2750 for (int y = 0; y < h; ++y) { 2751 srow = sptr+((y*sbpl)/4); 2752 drow = dptr+((y*dbpl)/4); 2753 for (int x = 0; x < w; ++x) { 2754 const int redDiff = qRed(*srow) - fillR; 2755 const int greenDiff = qGreen(*srow) - fillG; 2756 const int blueDiff = qBlue(*srow) - fillB; 2757 const int diff = (redDiff * redDiff) + (greenDiff * greenDiff) + (blueDiff * blueDiff); 2758 (*drow++) = (diff < 10) ? 0xffffffff : 0xff000000; 2759 ++srow; 2760 } 2761 } 2762 QBitmap qmask = QBitmap::fromImage(img_mask); 2763 mask->region = QRegion(qmask); 2764 } 2765 break; } 2766 case SH_TitleBar_NoBorder: 2767 ret = 1; 2768 break; 2769 case SH_RubberBand_Mask: 2770 ret = 0; 2771 break; 2772 case SH_ComboBox_LayoutDirection: 2773 ret = Qt::LeftToRight; 2774 break; 2775 case SH_ItemView_EllipsisLocation: 2776 ret = Qt::AlignHCenter; 2777 break; 2778 case SH_ItemView_ShowDecorationSelected: 2779 ret = true; 2780 break; 2781 case SH_TitleBar_ModifyNotification: 2782 ret = false; 2783 break; 2784 case SH_ScrollBar_RollBetweenButtons: 2785 ret = true; 2786 break; 2787 case SH_WindowFrame_Mask: 2788 ret = false; 2789 break; 2790 case SH_TabBar_ElideMode: 2791 ret = Qt::ElideRight; 2792 break; 2793#if QT_CONFIG(dialogbuttonbox) 2794 case SH_DialogButtonLayout: 2795 ret = QDialogButtonBox::MacLayout; 2796 break; 2797#endif 2798 case SH_FormLayoutWrapPolicy: 2799 ret = QFormLayout::DontWrapRows; 2800 break; 2801 case SH_FormLayoutFieldGrowthPolicy: 2802 ret = QFormLayout::FieldsStayAtSizeHint; 2803 break; 2804 case SH_FormLayoutFormAlignment: 2805 ret = Qt::AlignHCenter | Qt::AlignTop; 2806 break; 2807 case SH_FormLayoutLabelAlignment: 2808 ret = Qt::AlignRight; 2809 break; 2810 case SH_ComboBox_PopupFrameStyle: 2811 ret = QFrame::NoFrame; 2812 break; 2813 case SH_MessageBox_TextInteractionFlags: 2814 ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard; 2815 break; 2816 case SH_SpellCheckUnderlineStyle: 2817 ret = QTextCharFormat::DashUnderline; 2818 break; 2819 case SH_MessageBox_CenterButtons: 2820 ret = false; 2821 break; 2822 case SH_MenuBar_AltKeyNavigation: 2823 ret = false; 2824 break; 2825 case SH_ItemView_MovementWithoutUpdatingSelection: 2826 ret = false; 2827 break; 2828 case SH_FocusFrame_AboveWidget: 2829 ret = true; 2830 break; 2831#if QT_CONFIG(wizard) 2832 case SH_WizardStyle: 2833 ret = QWizard::MacStyle; 2834 break; 2835#endif 2836 case SH_ItemView_ArrowKeysNavigateIntoChildren: 2837 ret = false; 2838 break; 2839 case SH_Menu_FlashTriggeredItem: 2840 ret = true; 2841 break; 2842 case SH_Menu_FadeOutOnHide: 2843 ret = true; 2844 break; 2845 case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: 2846 ret = true; 2847 break; 2848#if QT_CONFIG(tabbar) 2849 case SH_TabBar_CloseButtonPosition: 2850 ret = QTabBar::LeftSide; 2851 break; 2852#endif 2853 case SH_DockWidget_ButtonsHaveFrame: 2854 ret = false; 2855 break; 2856 case SH_ScrollBar_Transient: 2857 if ((qobject_cast<const QScrollBar *>(w) && w->parent() && 2858 qobject_cast<QAbstractScrollArea*>(w->parent()->parent())) 2859#ifndef QT_NO_ACCESSIBILITY 2860 || (opt && QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ScrollBar)) 2861#endif 2862 ) { 2863 ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay; 2864 } 2865 break; 2866#if QT_CONFIG(itemviews) 2867 case SH_ItemView_ScrollMode: 2868 ret = QAbstractItemView::ScrollPerPixel; 2869 break; 2870#endif 2871 case SH_TitleBar_ShowToolTipsOnButtons: 2872 // min/max/close buttons on windows don't show tool tips 2873 ret = false; 2874 break; 2875 case SH_ComboBox_AllowWheelScrolling: 2876 ret = false; 2877 break; 2878 case SH_SpinBox_ButtonsInsideFrame: 2879 ret = false; 2880 break; 2881 case SH_Table_GridLineColor: 2882 ret = int(qt_mac_toQColor(NSColor.gridColor).rgba()); 2883 break; 2884 default: 2885 ret = QCommonStyle::styleHint(sh, opt, w, hret); 2886 break; 2887 } 2888 return ret; 2889} 2890 2891QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, 2892 const QStyleOption *opt) const 2893{ 2894 switch (iconMode) { 2895 case QIcon::Disabled: { 2896 QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); 2897 int imgh = img.height(); 2898 int imgw = img.width(); 2899 QRgb pixel; 2900 for (int y = 0; y < imgh; ++y) { 2901 for (int x = 0; x < imgw; ++x) { 2902 pixel = img.pixel(x, y); 2903 img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), 2904 qAlpha(pixel) / 2)); 2905 } 2906 } 2907 return QPixmap::fromImage(img); 2908 } 2909 default: 2910 ; 2911 } 2912 return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt); 2913} 2914 2915 2916QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, 2917 const QWidget *widget) const 2918{ 2919 // The default implementation of QStyle::standardIconImplementation() is to call standardPixmap() 2920 // I don't want infinite recursion so if we do get in that situation, just return the Window's 2921 // standard pixmap instead (since there is no mac-specific icon then). This should be fine until 2922 // someone changes how Windows standard 2923 // pixmap works. 2924 static bool recursionGuard = false; 2925 2926 if (recursionGuard) 2927 return QCommonStyle::standardPixmap(standardPixmap, opt, widget); 2928 2929 recursionGuard = true; 2930 QIcon icon = proxy()->standardIcon(standardPixmap, opt, widget); 2931 recursionGuard = false; 2932 int size; 2933 switch (standardPixmap) { 2934 default: 2935 size = 32; 2936 break; 2937 case SP_MessageBoxCritical: 2938 case SP_MessageBoxQuestion: 2939 case SP_MessageBoxInformation: 2940 case SP_MessageBoxWarning: 2941 size = 64; 2942 break; 2943 } 2944 return icon.pixmap(qt_getWindow(widget), QSize(size, size)); 2945} 2946 2947void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, 2948 const QWidget *w) const 2949{ 2950 Q_D(const QMacStyle); 2951 const AppearanceSync appSync; 2952 QMacCGContext cg(p); 2953 QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr; 2954 d->resolveCurrentNSView(window); 2955 switch (pe) { 2956 case PE_IndicatorArrowUp: 2957 case PE_IndicatorArrowDown: 2958 case PE_IndicatorArrowRight: 2959 case PE_IndicatorArrowLeft: { 2960 p->save(); 2961 p->setRenderHint(QPainter::Antialiasing); 2962 const int xOffset = 1; // FIXME: opt->direction == Qt::LeftToRight ? 2 : -1; 2963 qreal halfSize = 0.5 * qMin(opt->rect.width(), opt->rect.height()); 2964 const qreal penWidth = qMax(halfSize / 3.0, 1.25); 2965#if QT_CONFIG(toolbutton) 2966 if (const QToolButton *tb = qobject_cast<const QToolButton *>(w)) { 2967 // When stroking the arrow, make sure it fits in the tool button 2968 if (tb->arrowType() != Qt::NoArrow 2969 || tb->popupMode() == QToolButton::MenuButtonPopup) 2970 halfSize -= penWidth; 2971 } 2972#endif 2973 2974 QTransform transform; 2975 transform.translate(opt->rect.center().x() + xOffset, opt->rect.center().y() + 2); 2976 QPainterPath path; 2977 switch(pe) { 2978 default: 2979 case PE_IndicatorArrowDown: 2980 break; 2981 case PE_IndicatorArrowUp: 2982 transform.rotate(180); 2983 break; 2984 case PE_IndicatorArrowLeft: 2985 transform.rotate(90); 2986 break; 2987 case PE_IndicatorArrowRight: 2988 transform.rotate(-90); 2989 break; 2990 } 2991 p->setTransform(transform); 2992 2993 path.moveTo(-halfSize, -halfSize * 0.5); 2994 path.lineTo(0.0, halfSize * 0.5); 2995 path.lineTo(halfSize, -halfSize * 0.5); 2996 2997 const QPen arrowPen(opt->palette.text(), penWidth, 2998 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); 2999 p->strokePath(path, arrowPen); 3000 p->restore(); 3001 break; } 3002#if QT_CONFIG(tabbar) 3003 case PE_FrameTabBarBase: 3004 if (const QStyleOptionTabBarBase *tbb 3005 = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) { 3006 if (tbb->documentMode) { 3007 p->save(); 3008 drawTabBase(p, tbb, w); 3009 p->restore(); 3010 return; 3011 } 3012#if QT_CONFIG(tabwidget) 3013 QRegion region(tbb->rect); 3014 region -= tbb->tabBarRect; 3015 p->save(); 3016 p->setClipRegion(region); 3017 QStyleOptionTabWidgetFrame twf; 3018 twf.QStyleOption::operator=(*tbb); 3019 twf.shape = tbb->shape; 3020 switch (QMacStylePrivate::tabDirection(twf.shape)) { 3021 case QMacStylePrivate::North: 3022 twf.rect = twf.rect.adjusted(0, 0, 0, 10); 3023 break; 3024 case QMacStylePrivate::South: 3025 twf.rect = twf.rect.adjusted(0, -10, 0, 0); 3026 break; 3027 case QMacStylePrivate::West: 3028 twf.rect = twf.rect.adjusted(0, 0, 10, 0); 3029 break; 3030 case QMacStylePrivate::East: 3031 twf.rect = twf.rect.adjusted(0, -10, 0, 0); 3032 break; 3033 } 3034 proxy()->drawPrimitive(PE_FrameTabWidget, &twf, p, w); 3035 p->restore(); 3036#endif 3037 } 3038 break; 3039#endif 3040 case PE_PanelTipLabel: 3041 p->fillRect(opt->rect, opt->palette.brush(QPalette::ToolTipBase)); 3042 break; 3043 case PE_FrameGroupBox: 3044 if (const auto *groupBox = qstyleoption_cast<const QStyleOptionFrame *>(opt)) 3045 if (groupBox->features & QStyleOptionFrame::Flat) { 3046 QCommonStyle::drawPrimitive(pe, groupBox, p, w); 3047 break; 3048 } 3049#if QT_CONFIG(tabwidget) 3050 Q_FALLTHROUGH(); 3051 case PE_FrameTabWidget: 3052#endif 3053 { 3054 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Box, QStyleHelper::SizeLarge); 3055 auto *box = static_cast<NSBox *>(d->cocoaControl(cw)); 3056 // FIXME Since macOS 10.14, simply calling drawRect: won't display anything anymore. 3057 // The AppKit team is aware of this and has proposed a couple of solutions. 3058 // The first solution was to call displayRectIgnoringOpacity:inContext: instead. 3059 // However, it doesn't seem to work on 10.13. More importantly, dark mode on 10.14 3060 // is extremely slow. Light mode works fine. 3061 // The second solution is to subclass NSBox and reimplement a trivial drawRect: which 3062 // would only call super. This works without any issue on 10.13, but a double border 3063 // shows on 10.14 in both light and dark modes. 3064 // The code below picks what works on each version and mode. On 10.13 and earlier, we 3065 // simply call drawRect: on a regular NSBox. On 10.14, we call displayRectIgnoringOpacity: 3066 // inContext:, but only in light mode. In dark mode, we use a custom NSBox subclass, 3067 // QDarkNSBox, of type NSBoxCustom. Its appearance is close enough to the real thing so 3068 // we can use this for now. 3069 auto adjustedRect = opt->rect; 3070 bool needTranslation = false; 3071 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave 3072 && !qt_mac_applicationIsInDarkMode()) { 3073 // In Aqua theme we have to use the 'default' NSBox (as opposite 3074 // to the 'custom' QDarkNSBox we use in dark theme). Since -drawRect: 3075 // does nothing in default NSBox, we call -displayRectIgnoringOpaticty:. 3076 // Unfortunately, the resulting box is smaller then the actual rect we 3077 // wanted. This can be seen, e.g. because tabs (buttons) are misaligned 3078 // vertically and even worse, if QTabWidget has autoFillBackground 3079 // set, this background overpaints NSBox making it to disappear. 3080 // We trick our NSBox to render in a larger rectangle, so that 3081 // the actuall result (which is again smaller than requested), 3082 // more or less is what we really want. We'll have to adjust CTM 3083 // and translate accordingly. 3084 adjustedRect.adjust(0, 0, 6, 6); 3085 needTranslation = true; 3086 } 3087 d->drawNSViewInRect(box, adjustedRect, p, ^(CGContextRef ctx, const CGRect &rect) { 3088#if QT_CONFIG(tabwidget) 3089 if (QTabWidget *tabWidget = qobject_cast<QTabWidget *>(opt->styleObject)) 3090 clipTabBarFrame(opt, this, ctx); 3091#endif 3092 CGContextTranslateCTM(ctx, 0, rect.origin.y + rect.size.height); 3093 CGContextScaleCTM(ctx, 1, -1); 3094 if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave 3095 || [box isMemberOfClass:QDarkNSBox.class]) { 3096 [box drawRect:rect]; 3097 } else { 3098 if (needTranslation) 3099 CGContextTranslateCTM(ctx, -3.0, 5.0); 3100 [box displayRectIgnoringOpacity:box.bounds inContext:NSGraphicsContext.currentContext]; 3101 } 3102 }); 3103 break; 3104 } 3105 case PE_IndicatorToolBarSeparator: { 3106 QPainterPath path; 3107 if (opt->state & State_Horizontal) { 3108 int xpoint = opt->rect.center().x(); 3109 path.moveTo(xpoint + 0.5, opt->rect.top() + 1); 3110 path.lineTo(xpoint + 0.5, opt->rect.bottom()); 3111 } else { 3112 int ypoint = opt->rect.center().y(); 3113 path.moveTo(opt->rect.left() + 2 , ypoint + 0.5); 3114 path.lineTo(opt->rect.right() + 1, ypoint + 0.5); 3115 } 3116 QPainterPathStroker theStroker; 3117 theStroker.setCapStyle(Qt::FlatCap); 3118 theStroker.setDashPattern(QVector<qreal>() << 1 << 2); 3119 path = theStroker.createStroke(path); 3120 const auto dark = qt_mac_applicationIsInDarkMode() ? opt->palette.dark().color().darker() 3121 : QColor(0, 0, 0, 119); 3122 p->fillPath(path, dark); 3123 } 3124 break; 3125 case PE_FrameWindow: 3126 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { 3127 if (w && w->inherits("QMdiSubWindow")) { 3128 p->save(); 3129 p->setPen(QPen(frame->palette.dark().color(), frame->lineWidth)); 3130 p->setBrush(frame->palette.window()); 3131 p->drawRect(frame->rect); 3132 p->restore(); 3133 } 3134 } 3135 break; 3136 case PE_IndicatorDockWidgetResizeHandle: { 3137 // The docwidget resize handle is drawn as a one-pixel wide line. 3138 p->save(); 3139 if (opt->state & State_Horizontal) { 3140 p->setPen(QColor(160, 160, 160)); 3141 p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); 3142 } else { 3143 p->setPen(QColor(145, 145, 145)); 3144 p->drawLine(opt->rect.topRight(), opt->rect.bottomRight()); 3145 } 3146 p->restore(); 3147 } break; 3148 case PE_IndicatorToolBarHandle: { 3149 p->save(); 3150 QPainterPath path; 3151 int x = opt->rect.x() + 6; 3152 int y = opt->rect.y() + 7; 3153 static const int RectHeight = 2; 3154 if (opt->state & State_Horizontal) { 3155 while (y < opt->rect.height() - RectHeight - 5) { 3156 path.moveTo(x, y); 3157 path.addEllipse(x, y, RectHeight, RectHeight); 3158 y += 6; 3159 } 3160 } else { 3161 while (x < opt->rect.width() - RectHeight - 5) { 3162 path.moveTo(x, y); 3163 path.addEllipse(x, y, RectHeight, RectHeight); 3164 x += 6; 3165 } 3166 } 3167 p->setPen(Qt::NoPen); 3168 QColor dark = opt->palette.dark().color().darker(); 3169 dark.setAlphaF(0.50); 3170 p->fillPath(path, dark); 3171 p->restore(); 3172 3173 break; 3174 } 3175 case PE_IndicatorHeaderArrow: 3176 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { 3177 // In HITheme, up is down, down is up and hamburgers eat people. 3178 if (header->sortIndicator != QStyleOptionHeader::None) 3179 proxy()->drawPrimitive( 3180 (header->sortIndicator == QStyleOptionHeader::SortDown) ? 3181 PE_IndicatorArrowUp : PE_IndicatorArrowDown, header, p, w); 3182 } 3183 break; 3184 case PE_IndicatorMenuCheckMark: { 3185 QColor pc; 3186 if (opt->state & State_On) 3187 pc = opt->palette.highlightedText().color(); 3188 else 3189 pc = opt->palette.text().color(); 3190 3191 QCFType<CGColorRef> checkmarkColor = CGColorCreateGenericRGB(static_cast<CGFloat>(pc.redF()), 3192 static_cast<CGFloat>(pc.greenF()), 3193 static_cast<CGFloat>(pc.blueF()), 3194 static_cast<CGFloat>(pc.alphaF())); 3195 // kCTFontUIFontSystem and others give the same result 3196 // as kCTFontUIFontMenuItemMark. However, the latter is 3197 // more reminiscent to HITheme's kThemeMenuItemMarkFont. 3198 // See also the font for small- and mini-sized widgets, 3199 // where we end up using the generic system font type. 3200 const CTFontUIFontType fontType = (opt->state & State_Mini) ? kCTFontUIFontMiniSystem : 3201 (opt->state & State_Small) ? kCTFontUIFontSmallSystem : 3202 kCTFontUIFontMenuItemMark; 3203 // Similarly for the font size, where there is a small difference 3204 // between regular combobox and item view items, and and menu items. 3205 // However, we ignore any difference for small- and mini-sized widgets. 3206 const CGFloat fontSize = fontType == kCTFontUIFontMenuItemMark ? opt->fontMetrics.height() : 0.0; 3207 QCFType<CTFontRef> checkmarkFont = CTFontCreateUIFontForLanguage(fontType, fontSize, NULL); 3208 3209 CGContextSaveGState(cg); 3210 CGContextSetShouldSmoothFonts(cg, NO); // Same as HITheme and Cocoa menu checkmarks 3211 3212 // Baseline alignment tweaks for QComboBox and QMenu 3213 const CGFloat vOffset = (opt->state & State_Mini) ? 0.0 : 3214 (opt->state & State_Small) ? 1.0 : 3215 0.75; 3216 3217 CGContextTranslateCTM(cg, 0, opt->rect.bottom()); 3218 CGContextScaleCTM(cg, 1, -1); 3219 // Translate back to the original position and add rect origin and offset 3220 CGContextTranslateCTM(cg, opt->rect.x(), vOffset); 3221 3222 // CTFont has severe difficulties finding the checkmark character among its 3223 // glyphs. Fortunately, CTLine knows its ways inside the Cocoa labyrinth. 3224 static const CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName }; 3225 static const int numValues = sizeof(keys) / sizeof(keys[0]); 3226 const CFTypeRef values[] = { (CFTypeRef)checkmarkFont, (CFTypeRef)checkmarkColor }; 3227 Q_STATIC_ASSERT((sizeof(values) / sizeof(values[0])) == numValues); 3228 QCFType<CFDictionaryRef> attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 3229 numValues, NULL, NULL); 3230 // U+2713: CHECK MARK 3231 QCFType<CFAttributedStringRef> checkmarkString = CFAttributedStringCreate(kCFAllocatorDefault, (CFStringRef)@"\u2713", attributes); 3232 QCFType<CTLineRef> line = CTLineCreateWithAttributedString(checkmarkString); 3233 3234 CTLineDraw((CTLineRef)line, cg); 3235 CGContextFlush(cg); // CTLineDraw's documentation says it doesn't flush 3236 3237 CGContextRestoreGState(cg); 3238 break; } 3239 case PE_IndicatorViewItemCheck: 3240 case PE_IndicatorRadioButton: 3241 case PE_IndicatorCheckBox: { 3242 const bool isEnabled = opt->state & State_Enabled; 3243 const bool isPressed = opt->state & State_Sunken; 3244 const bool isRadioButton = (pe == PE_IndicatorRadioButton); 3245 const auto ct = isRadioButton ? QMacStylePrivate::Button_RadioButton : QMacStylePrivate::Button_CheckBox; 3246 const auto cs = d->effectiveAquaSizeConstrain(opt, w); 3247 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 3248 auto *tb = static_cast<NSButton *>(d->cocoaControl(cw)); 3249 tb.enabled = isEnabled; 3250 tb.state = (opt->state & State_NoChange) ? NSMixedState : 3251 (opt->state & State_On) ? NSOnState : NSOffState; 3252 [tb highlight:isPressed]; 3253 const auto vOffset = [=] { 3254 // As measured 3255 if (cs == QStyleHelper::SizeMini) 3256 return ct == QMacStylePrivate::Button_CheckBox ? -0.5 : 0.5; 3257 3258 return cs == QStyleHelper::SizeSmall ? 0.5 : 0.0; 3259 } (); 3260 d->drawNSViewInRect(tb, opt->rect, p, ^(CGContextRef ctx, const CGRect &rect) { 3261 CGContextTranslateCTM(ctx, 0, vOffset); 3262 [tb.cell drawInteriorWithFrame:rect inView:tb]; 3263 }); 3264 break; } 3265 case PE_FrameFocusRect: 3266 // Use the our own focus widget stuff. 3267 break; 3268 case PE_IndicatorBranch: { 3269 if (!(opt->state & State_Children)) 3270 break; 3271 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Button_Disclosure, QStyleHelper::SizeLarge); 3272 NSButtonCell *triangleCell = static_cast<NSButtonCell *>(d->cocoaCell(cw)); 3273 [triangleCell setState:(opt->state & State_Open) ? NSOnState : NSOffState]; 3274 bool viewHasFocus = (w && w->hasFocus()) || (opt->state & State_HasFocus); 3275 [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleDark : NSBackgroundStyleLight]; 3276 3277 d->setupNSGraphicsContext(cg, NO); 3278 3279 QRect qtRect = opt->rect.adjusted(DisclosureOffset, 0, -DisclosureOffset, 0); 3280 CGRect rect = CGRectMake(qtRect.x() + 1, qtRect.y(), qtRect.width(), qtRect.height()); 3281 CGContextTranslateCTM(cg, rect.origin.x, rect.origin.y + rect.size.height); 3282 CGContextScaleCTM(cg, 1, -1); 3283 CGContextTranslateCTM(cg, -rect.origin.x, -rect.origin.y); 3284 3285 [triangleCell drawBezelWithFrame:NSRectFromCGRect(rect) inView:[triangleCell controlView]]; 3286 3287 d->restoreNSGraphicsContext(cg); 3288 break; } 3289 3290 case PE_Frame: { 3291 QPen oldPen = p->pen(); 3292 p->setPen(opt->palette.base().color().darker(140)); 3293 p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); 3294 p->setPen(opt->palette.base().color().darker(180)); 3295 p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); 3296 p->setPen(oldPen); 3297 break; } 3298 3299 case PE_FrameLineEdit: 3300 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { 3301 if (frame->state & State_Sunken) { 3302 const bool isEnabled = opt->state & State_Enabled; 3303 const bool isReadOnly = opt->state & State_ReadOnly; 3304 const bool isRounded = frame->features & QStyleOptionFrame::Rounded; 3305 const auto cs = d->effectiveAquaSizeConstrain(opt, w, CT_LineEdit); 3306 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::TextField, cs); 3307 auto *tf = static_cast<NSTextField *>(d->cocoaControl(cw)); 3308 tf.enabled = isEnabled; 3309 tf.editable = !isReadOnly; 3310 tf.bezeled = YES; 3311 static_cast<NSTextFieldCell *>(tf.cell).bezelStyle = isRounded ? NSTextFieldRoundedBezel : NSTextFieldSquareBezel; 3312 tf.frame = opt->rect.toCGRect(); 3313 d->drawNSViewInRect(tf, opt->rect, p, ^(CGContextRef, const CGRect &rect) { 3314 if (!qt_mac_applicationIsInDarkMode()) { 3315 // In 'Dark' mode controls are transparent, so we do not 3316 // over-paint the (potentially custom) color in the background. 3317 // In 'Light' mode we have to care about the correct 3318 // background color. See the comments below for PE_PanelLineEdit. 3319 CGContextRef cgContext = NSGraphicsContext.currentContext.CGContext; 3320 // See QMacCGContext, here we expect bitmap context created with 3321 // color space 'kCGColorSpaceSRGB', if it's something else - we 3322 // give up. 3323 if (cgContext ? bool(CGBitmapContextGetColorSpace(cgContext)) : false) { 3324 tf.drawsBackground = YES; 3325 const QColor bgColor = frame->palette.brush(QPalette::Base).color(); 3326 tf.backgroundColor = [NSColor colorWithSRGBRed:bgColor.redF() 3327 green:bgColor.greenF() 3328 blue:bgColor.blueF() 3329 alpha:bgColor.alphaF()]; 3330 if (bgColor.alpha() != 255) { 3331 // No way we can have it bezeled and transparent ... 3332 tf.bordered = YES; 3333 } 3334 } 3335 } 3336 3337 [tf.cell drawWithFrame:rect inView:tf]; 3338 }); 3339 } else { 3340 QCommonStyle::drawPrimitive(pe, opt, p, w); 3341 } 3342 } 3343 break; 3344 case PE_PanelLineEdit: 3345 { 3346 const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(opt); 3347 if (qt_mac_applicationIsInDarkMode() || (panel && panel->lineWidth <= 0)) { 3348 // QCommonStyle::drawPrimitive(PE_PanelLineEdit) fill the background with 3349 // a proper color, defined in opt->palette and then, if lineWidth > 0, it 3350 // calls QMacStyle::drawPrimitive(PE_FrameLineEdit). We use NSTextFieldCell 3351 // to handle PE_FrameLineEdit, which will use system-default background. 3352 // In 'Dark' mode it's transparent and thus it's not over-painted. 3353 QCommonStyle::drawPrimitive(pe, opt, p, w); 3354 } else { 3355 // In 'Light' mode, if panel->lineWidth > 0, we have to use the correct 3356 // background color when drawing PE_FrameLineEdit, so let's call it 3357 // directly and set the proper color there. 3358 drawPrimitive(PE_FrameLineEdit, opt, p, w); 3359 } 3360 3361 // Draw the focus frame for widgets other than QLineEdit (e.g. for line edits in Webkit). 3362 // Focus frame is drawn outside the rectangle passed in the option-rect. 3363 if (panel) { 3364#if QT_CONFIG(lineedit) 3365 if ((opt->state & State_HasFocus) && !qobject_cast<const QLineEdit*>(w)) { 3366 int vmargin = pixelMetric(QStyle::PM_FocusFrameVMargin); 3367 int hmargin = pixelMetric(QStyle::PM_FocusFrameHMargin); 3368 QStyleOptionFrame focusFrame = *panel; 3369 focusFrame.rect = panel->rect.adjusted(-hmargin, -vmargin, hmargin, vmargin); 3370 drawControl(CE_FocusFrame, &focusFrame, p, w); 3371 } 3372#endif 3373 } 3374 } 3375 break; 3376 case PE_PanelScrollAreaCorner: { 3377 const QBrush brush(opt->palette.brush(QPalette::Base)); 3378 p->fillRect(opt->rect, brush); 3379 p->setPen(QPen(QColor(217, 217, 217))); 3380 p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); 3381 p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); 3382 } break; 3383 case PE_FrameStatusBarItem: 3384 break; 3385#if QT_CONFIG(tabbar) 3386 case PE_IndicatorTabClose: { 3387 // Make close button visible only on the hovered tab. 3388 QTabBar *tabBar = qobject_cast<QTabBar*>(w->parentWidget()); 3389 const QWidget *closeBtn = w; 3390 if (!tabBar) { 3391 // QStyleSheetStyle instead of CloseButton (which has 3392 // a QTabBar as a parent widget) uses the QTabBar itself: 3393 tabBar = qobject_cast<QTabBar *>(const_cast<QWidget*>(w)); 3394 closeBtn = decltype(closeBtn)(property("_q_styleSheetRealCloseButton").value<void *>()); 3395 } 3396 if (tabBar) { 3397 const bool documentMode = tabBar->documentMode(); 3398 const QTabBarPrivate *tabBarPrivate = static_cast<QTabBarPrivate *>(QObjectPrivate::get(tabBar)); 3399 const int hoveredTabIndex = tabBarPrivate->hoveredTabIndex(); 3400 if (!documentMode || 3401 (hoveredTabIndex != -1 && ((closeBtn == tabBar->tabButton(hoveredTabIndex, QTabBar::LeftSide)) || 3402 (closeBtn == tabBar->tabButton(hoveredTabIndex, QTabBar::RightSide))))) { 3403 const bool hover = (opt->state & State_MouseOver); 3404 const bool selected = (opt->state & State_Selected); 3405 const bool pressed = (opt->state & State_Sunken); 3406 drawTabCloseButton(p, hover, selected, pressed, documentMode); 3407 } 3408 } 3409 } break; 3410#endif // QT_CONFIG(tabbar) 3411 case PE_PanelStatusBar: { 3412 // Fill the status bar with the titlebar gradient. 3413 QLinearGradient linearGrad; 3414 if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active)) { 3415 linearGrad = titlebarGradientActive(); 3416 } else { 3417 linearGrad = titlebarGradientInactive(); 3418 } 3419 3420 linearGrad.setStart(0, opt->rect.top()); 3421 linearGrad.setFinalStop(0, opt->rect.bottom()); 3422 p->fillRect(opt->rect, linearGrad); 3423 3424 // Draw the black separator line at the top of the status bar. 3425 if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active)) 3426 p->setPen(titlebarSeparatorLineActive); 3427 else 3428 p->setPen(titlebarSeparatorLineInactive); 3429 p->drawLine(opt->rect.left(), opt->rect.top(), opt->rect.right(), opt->rect.top()); 3430 3431 break; 3432 } 3433 case PE_PanelMenu: { 3434 p->save(); 3435 p->fillRect(opt->rect, Qt::transparent); 3436 p->setPen(Qt::transparent); 3437 p->setBrush(opt->palette.window()); 3438 p->setRenderHint(QPainter::Antialiasing, true); 3439 const QPainterPath path = d->windowPanelPath(opt->rect); 3440 p->drawPath(path); 3441 p->restore(); 3442 } break; 3443 3444 default: 3445 QCommonStyle::drawPrimitive(pe, opt, p, w); 3446 break; 3447 } 3448} 3449 3450static QPixmap darkenPixmap(const QPixmap &pixmap) 3451{ 3452 QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); 3453 int imgh = img.height(); 3454 int imgw = img.width(); 3455 int h, s, v, a; 3456 QRgb pixel; 3457 for (int y = 0; y < imgh; ++y) { 3458 for (int x = 0; x < imgw; ++x) { 3459 pixel = img.pixel(x, y); 3460 a = qAlpha(pixel); 3461 QColor hsvColor(pixel); 3462 hsvColor.getHsv(&h, &s, &v); 3463 s = qMin(100, s * 2); 3464 v = v / 2; 3465 hsvColor.setHsv(h, s, v); 3466 pixel = hsvColor.rgb(); 3467 img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), a)); 3468 } 3469 } 3470 return QPixmap::fromImage(img); 3471} 3472 3473void QMacStylePrivate::setupVerticalInvertedXform(CGContextRef cg, bool reverse, bool vertical, const CGRect &rect) const 3474{ 3475 if (vertical) { 3476 CGContextTranslateCTM(cg, rect.size.height, 0); 3477 CGContextRotateCTM(cg, M_PI_2); 3478 } 3479 if (vertical != reverse) { 3480 CGContextTranslateCTM(cg, rect.size.width, 0); 3481 CGContextScaleCTM(cg, -1, 1); 3482 } 3483} 3484 3485void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, 3486 const QWidget *w) const 3487{ 3488 Q_D(const QMacStyle); 3489 const AppearanceSync sync; 3490 const QMacAutoReleasePool pool; 3491 QMacCGContext cg(p); 3492 QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr; 3493 d->resolveCurrentNSView(window); 3494 switch (ce) { 3495 case CE_HeaderSection: 3496 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { 3497 State flags = header->state; 3498 QRect ir = header->rect; 3499 3500 3501#if 0 // FIXME: What's this solving exactly? 3502 bool noVerticalHeader = true; 3503#if QT_CONFIG(tableview) 3504 if (w) 3505 if (const QTableView *table = qobject_cast<const QTableView *>(w->parentWidget())) 3506 noVerticalHeader = !table->verticalHeader()->isVisible(); 3507#endif 3508 3509 const bool drawLeftBorder = header->orientation == Qt::Vertical 3510 || header->position == QStyleOptionHeader::OnlyOneSection 3511 || (header->position == QStyleOptionHeader::Beginning && noVerticalHeader); 3512#endif 3513 3514 const bool pressed = (flags & State_Sunken) && !(flags & State_On); 3515 p->fillRect(ir, pressed ? header->palette.dark() : header->palette.button()); 3516 p->setPen(QPen(header->palette.dark(), 1.0)); 3517 if (header->orientation == Qt::Horizontal) 3518 p->drawLine(QLineF(ir.right() + 0.5, ir.top() + headerSectionSeparatorInset, 3519 ir.right() + 0.5, ir.bottom() - headerSectionSeparatorInset)); 3520 else 3521 p->drawLine(QLineF(ir.left() + headerSectionSeparatorInset, ir.bottom(), 3522 ir.right() - headerSectionSeparatorInset, ir.bottom())); 3523 } 3524 3525 break; 3526 case CE_HeaderLabel: 3527 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { 3528 p->save(); 3529 QRect textr = header->rect; 3530 if (!header->icon.isNull()) { 3531 QIcon::Mode mode = QIcon::Disabled; 3532 if (opt->state & State_Enabled) 3533 mode = QIcon::Normal; 3534 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); 3535 QPixmap pixmap = header->icon.pixmap(window, QSize(iconExtent, iconExtent), mode); 3536 3537 QRect pixr = header->rect; 3538 pixr.setY(header->rect.center().y() - (pixmap.height() / pixmap.devicePixelRatio() - 1) / 2); 3539 proxy()->drawItemPixmap(p, pixr, Qt::AlignVCenter, pixmap); 3540 textr.translate(pixmap.width() / pixmap.devicePixelRatio() + 2, 0); 3541 } 3542 3543 proxy()->drawItemText(p, textr, header->textAlignment | Qt::AlignVCenter, header->palette, 3544 header->state & State_Enabled, header->text, QPalette::ButtonText); 3545 p->restore(); 3546 } 3547 break; 3548 case CE_ToolButtonLabel: 3549 if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { 3550 QStyleOptionToolButton myTb = *tb; 3551 myTb.state &= ~State_AutoRaise; 3552#ifndef QT_NO_ACCESSIBILITY 3553 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) { 3554 QRect cr = tb->rect; 3555 int shiftX = 0; 3556 int shiftY = 0; 3557 bool needText = false; 3558 int alignment = 0; 3559 bool down = tb->state & (State_Sunken | State_On); 3560 if (down) { 3561 shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, w); 3562 shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, tb, w); 3563 } 3564 // The down state is special for QToolButtons in a toolbar on the Mac 3565 // The text is a bit bolder and gets a drop shadow and the icons are also darkened. 3566 // This doesn't really fit into any particular case in QIcon, so we 3567 // do the majority of the work ourselves. 3568 if (!(tb->features & QStyleOptionToolButton::Arrow)) { 3569 Qt::ToolButtonStyle tbstyle = tb->toolButtonStyle; 3570 if (tb->icon.isNull() && !tb->text.isEmpty()) 3571 tbstyle = Qt::ToolButtonTextOnly; 3572 3573 switch (tbstyle) { 3574 case Qt::ToolButtonTextOnly: { 3575 needText = true; 3576 alignment = Qt::AlignCenter; 3577 break; } 3578 case Qt::ToolButtonIconOnly: 3579 case Qt::ToolButtonTextBesideIcon: 3580 case Qt::ToolButtonTextUnderIcon: { 3581 QRect pr = cr; 3582 QIcon::Mode iconMode = (tb->state & State_Enabled) ? QIcon::Normal 3583 : QIcon::Disabled; 3584 QIcon::State iconState = (tb->state & State_On) ? QIcon::On 3585 : QIcon::Off; 3586 QPixmap pixmap = tb->icon.pixmap(window, 3587 tb->rect.size().boundedTo(tb->iconSize), 3588 iconMode, iconState); 3589 3590 // Draw the text if it's needed. 3591 if (tb->toolButtonStyle != Qt::ToolButtonIconOnly) { 3592 needText = true; 3593 if (tb->toolButtonStyle == Qt::ToolButtonTextUnderIcon) { 3594 pr.setHeight(pixmap.size().height() / pixmap.devicePixelRatio() + 6); 3595 cr.adjust(0, pr.bottom(), 0, -3); 3596 alignment |= Qt::AlignCenter; 3597 } else { 3598 pr.setWidth(pixmap.width() / pixmap.devicePixelRatio() + 8); 3599 cr.adjust(pr.right(), 0, 0, 0); 3600 alignment |= Qt::AlignLeft | Qt::AlignVCenter; 3601 } 3602 } 3603 if (opt->state & State_Sunken) { 3604 pr.translate(shiftX, shiftY); 3605 pixmap = darkenPixmap(pixmap); 3606 } 3607 proxy()->drawItemPixmap(p, pr, Qt::AlignCenter, pixmap); 3608 break; } 3609 default: 3610 Q_ASSERT(false); 3611 break; 3612 } 3613 3614 if (needText) { 3615 QPalette pal = tb->palette; 3616 QPalette::ColorRole role = QPalette::NoRole; 3617 if (!proxy()->styleHint(SH_UnderlineShortcut, tb, w)) 3618 alignment |= Qt::TextHideMnemonic; 3619 if (down) 3620 cr.translate(shiftX, shiftY); 3621 if (tbstyle == Qt::ToolButtonTextOnly 3622 || (tbstyle != Qt::ToolButtonTextOnly && !down)) { 3623 QPen pen = p->pen(); 3624 QColor light = down || isDarkMode() ? Qt::black : Qt::white; 3625 light.setAlphaF(0.375f); 3626 p->setPen(light); 3627 p->drawText(cr.adjusted(0, 1, 0, 1), alignment, tb->text); 3628 p->setPen(pen); 3629 if (down && tbstyle == Qt::ToolButtonTextOnly) { 3630 pal = QApplication::palette("QMenu"); 3631 pal.setCurrentColorGroup(tb->palette.currentColorGroup()); 3632 role = QPalette::HighlightedText; 3633 } 3634 } 3635 proxy()->drawItemText(p, cr, alignment, pal, 3636 tb->state & State_Enabled, tb->text, role); 3637 } 3638 } else { 3639 QCommonStyle::drawControl(ce, &myTb, p, w); 3640 } 3641 } else 3642#endif // QT_NO_ACCESSIBILITY 3643 { 3644 QCommonStyle::drawControl(ce, &myTb, p, w); 3645 } 3646 } 3647 break; 3648 case CE_ToolBoxTabShape: 3649 QCommonStyle::drawControl(ce, opt, p, w); 3650 break; 3651 case CE_PushButtonBevel: 3652 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { 3653 if (!(btn->state & (State_Raised | State_Sunken | State_On))) 3654 break; 3655 3656 if (btn->features & QStyleOptionButton::CommandLinkButton) { 3657 QCommonStyle::drawControl(ce, opt, p, w); 3658 break; 3659 } 3660 3661 const bool hasFocus = btn->state & State_HasFocus; 3662 const bool isActive = btn->state & State_Active; 3663 3664 // a focused auto-default button within an active window 3665 // takes precedence over a normal default button 3666 if ((btn->features & QStyleOptionButton::AutoDefaultButton) 3667 && isActive && hasFocus) 3668 d->autoDefaultButton = btn->styleObject; 3669 else if (d->autoDefaultButton == btn->styleObject) 3670 d->autoDefaultButton = nullptr; 3671 3672 const bool isEnabled = btn->state & State_Enabled; 3673 const bool isPressed = btn->state & State_Sunken; 3674 const bool isHighlighted = isActive && 3675 ((btn->state & State_On) 3676 || (btn->features & QStyleOptionButton::DefaultButton) 3677 || (btn->features & QStyleOptionButton::AutoDefaultButton 3678 && d->autoDefaultButton == btn->styleObject)); 3679 const bool hasMenu = btn->features & QStyleOptionButton::HasMenu; 3680 const auto ct = cocoaControlType(btn, w); 3681 const auto cs = d->effectiveAquaSizeConstrain(btn, w); 3682 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 3683 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw)); 3684 // Ensure same size and location as we used to have with HITheme. 3685 // This is more convoluted than we initialy thought. See for example 3686 // differences between plain and menu button frames. 3687 const QRectF frameRect = cw.adjustedControlFrame(btn->rect); 3688 pb.frame = frameRect.toCGRect(); 3689 3690 pb.enabled = isEnabled; 3691 [pb highlight:isPressed]; 3692 pb.state = isHighlighted && !isPressed ? NSOnState : NSOffState; 3693 d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) { 3694 [pb.cell drawBezelWithFrame:r inView:pb.superview]; 3695 }); 3696 [pb highlight:NO]; 3697 3698 if (hasMenu && cw.type == QMacStylePrivate::Button_SquareButton) { 3699 // Using -[NSPopuButtonCell drawWithFrame:inView:] above won't do 3700 // it right because we don't set the text in the native button. 3701 const int mbi = proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, w); 3702 const auto ir = frameRect.toRect(); 3703 int arrowYOffset = 0; 3704#if 0 3705 // FIXME What's this for again? 3706 if (!w) { 3707 // adjustment for Qt Quick Controls 3708 arrowYOffset -= ir.top(); 3709 if (cw.second == QStyleHelper::SizeSmall) 3710 arrowYOffset += 1; 3711 } 3712#endif 3713 const auto ar = visualRect(btn->direction, ir, QRect(ir.right() - mbi - 6, ir.height() / 2 - arrowYOffset, mbi, mbi)); 3714 3715 QStyleOption arrowOpt = *opt; 3716 arrowOpt.rect = ar; 3717 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, w); 3718 } 3719 3720 3721 if (btn->state & State_HasFocus) { 3722 // TODO Remove and use QFocusFrame instead. 3723 const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, btn, w); 3724 const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, btn, w); 3725 QRectF focusRect; 3726 if (cw.type == QMacStylePrivate::Button_SquareButton) { 3727 focusRect = frameRect; 3728 } else { 3729 focusRect = QRectF::fromCGRect([pb alignmentRectForFrame:pb.frame]); 3730 if (cw.type == QMacStylePrivate::Button_PushButton) 3731 focusRect -= pushButtonShadowMargins[cw.size]; 3732 else if (cw.type == QMacStylePrivate::Button_PullDown) 3733 focusRect -= pullDownButtonShadowMargins[cw.size]; 3734 } 3735 d->drawFocusRing(p, focusRect, hMargin, vMargin, cw); 3736 } 3737 } 3738 break; 3739 case CE_PushButtonLabel: 3740 if (const QStyleOptionButton *b = qstyleoption_cast<const QStyleOptionButton *>(opt)) { 3741 QStyleOptionButton btn(*b); 3742 // We really don't want the label to be drawn the same as on 3743 // windows style if it has an icon and text, then it should be more like a 3744 // tab. So, cheat a little here. However, if it *is* only an icon 3745 // the windows style works great, so just use that implementation. 3746 const bool isEnabled = btn.state & State_Enabled; 3747 const bool hasMenu = btn.features & QStyleOptionButton::HasMenu; 3748 const bool hasIcon = !btn.icon.isNull(); 3749 const bool hasText = !btn.text.isEmpty(); 3750 const bool isActive = btn.state & State_Active; 3751 const bool isPressed = btn.state & State_Sunken; 3752 3753 const auto ct = cocoaControlType(&btn, w); 3754 3755 if (!hasMenu && ct != QMacStylePrivate::Button_SquareButton) { 3756 if (isPressed 3757 || (isActive && isEnabled 3758 && ((btn.state & State_On) 3759 || ((btn.features & QStyleOptionButton::DefaultButton) && !d->autoDefaultButton) 3760 || d->autoDefaultButton == btn.styleObject))) 3761 btn.palette.setColor(QPalette::ButtonText, Qt::white); 3762 } 3763 3764 if ((!hasIcon && !hasMenu) || (hasIcon && !hasText)) { 3765 QCommonStyle::drawControl(ce, &btn, p, w); 3766 } else { 3767 QRect freeContentRect = btn.rect; 3768 QRect textRect = itemTextRect( 3769 btn.fontMetrics, freeContentRect, Qt::AlignCenter, isEnabled, btn.text); 3770 if (hasMenu) { 3771 textRect.moveTo(w ? 15 : 11, textRect.top()); // Supports Qt Quick Controls 3772 } 3773 // Draw the icon: 3774 if (hasIcon) { 3775 int contentW = textRect.width(); 3776 if (hasMenu) 3777 contentW += proxy()->pixelMetric(PM_MenuButtonIndicator) + 4; 3778 QIcon::Mode mode = isEnabled ? QIcon::Normal : QIcon::Disabled; 3779 if (mode == QIcon::Normal && btn.state & State_HasFocus) 3780 mode = QIcon::Active; 3781 // Decide if the icon is should be on or off: 3782 QIcon::State state = QIcon::Off; 3783 if (btn.state & State_On) 3784 state = QIcon::On; 3785 QPixmap pixmap = btn.icon.pixmap(window, btn.iconSize, mode, state); 3786 int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio(); 3787 int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio(); 3788 contentW += pixmapWidth + QMacStylePrivate::PushButtonContentPadding; 3789 int iconLeftOffset = freeContentRect.x() + (freeContentRect.width() - contentW) / 2; 3790 int iconTopOffset = freeContentRect.y() + (freeContentRect.height() - pixmapHeight) / 2; 3791 QRect iconDestRect(iconLeftOffset, iconTopOffset, pixmapWidth, pixmapHeight); 3792 QRect visualIconDestRect = visualRect(btn.direction, freeContentRect, iconDestRect); 3793 proxy()->drawItemPixmap(p, visualIconDestRect, Qt::AlignLeft | Qt::AlignVCenter, pixmap); 3794 int newOffset = iconDestRect.x() + iconDestRect.width() 3795 + QMacStylePrivate::PushButtonContentPadding - textRect.x(); 3796 textRect.adjust(newOffset, 0, newOffset, 0); 3797 } 3798 // Draw the text: 3799 if (hasText) { 3800 textRect = visualRect(btn.direction, freeContentRect, textRect); 3801 proxy()->drawItemText(p, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, btn.palette, 3802 isEnabled, btn.text, QPalette::ButtonText); 3803 } 3804 } 3805 } 3806 break; 3807#if QT_CONFIG(combobox) 3808 case CE_ComboBoxLabel: 3809 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 3810 auto comboCopy = *cb; 3811 comboCopy.direction = Qt::LeftToRight; 3812 // The rectangle will be adjusted to SC_ComboBoxEditField with comboboxEditBounds() 3813 QCommonStyle::drawControl(CE_ComboBoxLabel, &comboCopy, p, w); 3814 } 3815 break; 3816#endif // #if QT_CONFIG(combobox) 3817#if QT_CONFIG(tabbar) 3818 case CE_TabBarTabShape: 3819 if (const auto *tabOpt = qstyleoption_cast<const QStyleOptionTab *>(opt)) { 3820 if (tabOpt->documentMode) { 3821 p->save(); 3822 bool isUnified = false; 3823 if (w) { 3824 QRect tabRect = tabOpt->rect; 3825 QPoint windowTabStart = w->mapTo(w->window(), tabRect.topLeft()); 3826 isUnified = isInMacUnifiedToolbarArea(w->window()->windowHandle(), windowTabStart.y()); 3827 } 3828 3829 const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, opt, w); 3830 drawTabShape(p, tabOpt, isUnified, tabOverlap); 3831 3832 p->restore(); 3833 return; 3834 } 3835 3836 const bool isActive = tabOpt->state & State_Active; 3837 const bool isEnabled = tabOpt->state & State_Enabled; 3838 const bool isPressed = tabOpt->state & State_Sunken; 3839 const bool isSelected = tabOpt->state & State_Selected; 3840 const auto tabDirection = QMacStylePrivate::tabDirection(tabOpt->shape); 3841 const bool verticalTabs = tabDirection == QMacStylePrivate::East 3842 || tabDirection == QMacStylePrivate::West; 3843 3844 QStyleOptionTab::TabPosition tp = tabOpt->position; 3845 QStyleOptionTab::SelectedPosition sp = tabOpt->selectedPosition; 3846 if (tabOpt->direction == Qt::RightToLeft && !verticalTabs) { 3847 if (tp == QStyleOptionTab::Beginning) 3848 tp = QStyleOptionTab::End; 3849 else if (tp == QStyleOptionTab::End) 3850 tp = QStyleOptionTab::Beginning; 3851 3852 if (sp == QStyleOptionTab::NextIsSelected) 3853 sp = QStyleOptionTab::PreviousIsSelected; 3854 else if (sp == QStyleOptionTab::PreviousIsSelected) 3855 sp = QStyleOptionTab::NextIsSelected; 3856 } 3857 3858 // Alas, NSSegmentedControl and NSSegmentedCell are letting us down. 3859 // We're not able to draw it at will, either calling -[drawSegment: 3860 // inFrame:withView:], -[drawRect:] or anything in between. Besides, 3861 // there's no public API do draw the pressed state, AFAICS. We'll use 3862 // a push NSButton instead and clip the CGContext. 3863 // NOTE/TODO: this is not true. On 10.13 NSSegmentedControl works with 3864 // some (black?) magic/magic dances, on 10.14 it simply works (was 3865 // it fixed in AppKit?). But, indeed, we cannot make a tab 'pressed' 3866 // with NSSegmentedControl (only selected), so we stay with buttons 3867 // (mixing buttons and NSSegmentedControl for such a simple thing 3868 // is too much work). 3869 3870 const auto cs = d->effectiveAquaSizeConstrain(opt, w); 3871 // Extra hacks to get the proper pressed appreance when not selected or selected and inactive 3872 const bool needsInactiveHack = (!isActive && isSelected); 3873 const bool isBigSurOrAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur; 3874 const auto ct = !needsInactiveHack && (isSelected || tp == QStyleOptionTab::OnlyOneTab) ? 3875 QMacStylePrivate::Button_PushButton : 3876 QMacStylePrivate::Button_PopupButton; 3877 const bool isPopupButton = ct == QMacStylePrivate::Button_PopupButton; 3878 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 3879 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw)); 3880 3881 auto vOffset = isPopupButton ? 1 : 2; 3882 if (isBigSurOrAbove) { 3883 // Make it 1, otherwise, offset is very visible compared 3884 // to selected tab (which is not a popup button). 3885 vOffset = 1; 3886 } 3887 3888 if (tabDirection == QMacStylePrivate::East) 3889 vOffset -= 1; 3890 const auto outerAdjust = isPopupButton ? 1 : 4; 3891 const auto innerAdjust = isPopupButton ? 20 : 10; 3892 QRectF frameRect = tabOpt->rect; 3893 if (verticalTabs) 3894 frameRect = QRectF(frameRect.y(), frameRect.x(), frameRect.height(), frameRect.width()); 3895 // Adjust before clipping 3896 frameRect = frameRect.translated(0, vOffset); 3897 switch (tp) { 3898 case QStyleOptionTab::Beginning: 3899 // Pressed state hack: tweak adjustments in preparation for flip below 3900 if (!isSelected && tabDirection == QMacStylePrivate::West) 3901 frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); 3902 else 3903 frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); 3904 3905 if (isSelected && isBigSurOrAbove) { 3906 // 1 pixed of 'roundness' is still visible on the right 3907 // (the left is OK, it's rounded). 3908 frameRect = frameRect.adjusted(0, 0, 1, 0); 3909 } 3910 3911 break; 3912 case QStyleOptionTab::Middle: 3913 frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0); 3914 3915 if (isSelected && isBigSurOrAbove) { 3916 // 1 pixel of 'roundness' is still visible on both 3917 // sides - left and right. 3918 frameRect = frameRect.adjusted(-1, 0, 1, 0); 3919 } 3920 break; 3921 case QStyleOptionTab::End: 3922 // Pressed state hack: tweak adjustments in preparation for flip below 3923 if (isSelected || tabDirection == QMacStylePrivate::West) 3924 frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); 3925 else 3926 frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); 3927 3928 if (isSelected && isBigSurOrAbove) { 3929 // 1 pixel of 'roundness' is still visible on the left. 3930 frameRect = frameRect.adjusted(-1, 0, 0, 0); 3931 } 3932 break; 3933 case QStyleOptionTab::OnlyOneTab: 3934 frameRect = frameRect.adjusted(-outerAdjust, 0, outerAdjust, 0); 3935 break; 3936 } 3937 pb.frame = frameRect.toCGRect(); 3938 3939 pb.enabled = isEnabled; 3940 [pb highlight:isPressed]; 3941 // Set off state when inactive. See needsInactiveHack for when it's selected 3942 pb.state = (isActive && isSelected && !isPressed) ? NSOnState : NSOffState; 3943 3944 const auto drawBezelBlock = ^(CGContextRef ctx, const CGRect &r) { 3945 CGContextClipToRect(ctx, opt->rect.toCGRect()); 3946 if (!isSelected || needsInactiveHack) { 3947 // Final stage of the pressed state hack: flip NSPopupButton rendering 3948 if (!verticalTabs && tp == QStyleOptionTab::End) { 3949 CGContextTranslateCTM(ctx, opt->rect.right(), 0); 3950 CGContextScaleCTM(ctx, -1, 1); 3951 CGContextTranslateCTM(ctx, -frameRect.left(), 0); 3952 } else if (tabDirection == QMacStylePrivate::West && tp == QStyleOptionTab::Beginning) { 3953 CGContextTranslateCTM(ctx, 0, opt->rect.top()); 3954 CGContextScaleCTM(ctx, 1, -1); 3955 CGContextTranslateCTM(ctx, 0, -frameRect.right()); 3956 } else if (tabDirection == QMacStylePrivate::East && tp == QStyleOptionTab::End) { 3957 CGContextTranslateCTM(ctx, 0, opt->rect.bottom()); 3958 CGContextScaleCTM(ctx, 1, -1); 3959 CGContextTranslateCTM(ctx, 0, -frameRect.left()); 3960 } 3961 } 3962 3963 // Rotate and translate CTM when vertical 3964 // On macOS: positive angle is CW, negative is CCW 3965 if (tabDirection == QMacStylePrivate::West) { 3966 CGContextTranslateCTM(ctx, 0, frameRect.right()); 3967 CGContextRotateCTM(ctx, -M_PI_2); 3968 CGContextTranslateCTM(ctx, -frameRect.left(), 0); 3969 } else if (tabDirection == QMacStylePrivate::East) { 3970 CGContextTranslateCTM(ctx, opt->rect.right(), 0); 3971 CGContextRotateCTM(ctx, M_PI_2); 3972 } 3973 3974 // Now, if it's a trick with a popup button, it has an arrow 3975 // which makes no sense on tabs. 3976 NSPopUpArrowPosition oldPosition = NSPopUpArrowAtCenter; 3977 NSPopUpButtonCell *pbCell = nil; 3978 auto rAdjusted = r; 3979 if (isPopupButton && (tp == QStyleOptionTab::OnlyOneTab || isBigSurOrAbove)) { 3980 // Note: starting from macOS BigSur NSPopupButton has this 3981 // arrow 'button' in a different place and it became 3982 // quite visible 'in between' inactive tabs. 3983 pbCell = static_cast<NSPopUpButtonCell *>(pb.cell); 3984 oldPosition = pbCell.arrowPosition; 3985 pbCell.arrowPosition = NSPopUpNoArrow; 3986 if (pb.state == NSControlStateValueOff) { 3987 // NSPopUpButton in this state is smaller. 3988 rAdjusted.origin.x -= 3; 3989 rAdjusted.size.width += 6; 3990 if (isBigSurOrAbove) { 3991 if (tp == QStyleOptionTab::End) 3992 rAdjusted.origin.x -= 2; 3993 } 3994 } 3995 } 3996 3997 [pb.cell drawBezelWithFrame:rAdjusted inView:pb.superview]; 3998 3999 if (pbCell) // Restore, we may reuse it for a ComboBox. 4000 pbCell.arrowPosition = oldPosition; 4001 }; 4002 4003 if (needsInactiveHack) { 4004 // First, render tab as non-selected tab on a pixamp 4005 const qreal pixelRatio = p->device()->devicePixelRatioF(); 4006 QImage tabPixmap(opt->rect.size() * pixelRatio, QImage::Format_ARGB32_Premultiplied); 4007 tabPixmap.setDevicePixelRatio(pixelRatio); 4008 tabPixmap.fill(Qt::transparent); 4009 QPainter tabPainter(&tabPixmap); 4010 d->drawNSViewInRect(pb, frameRect, &tabPainter, ^(CGContextRef ctx, const CGRect &r) { 4011 CGContextTranslateCTM(ctx, -opt->rect.left(), -opt->rect.top()); 4012 drawBezelBlock(ctx, r); 4013 }); 4014 tabPainter.end(); 4015 4016 // Then, darken it with the proper shade of gray 4017 const qreal inactiveGray = 0.898; // As measured 4018 const int inactiveGray8 = qRound(inactiveGray * 255.0); 4019 const QRgb inactiveGrayRGB = qRgb(inactiveGray8, inactiveGray8, inactiveGray8); 4020 for (int l = 0; l < tabPixmap.height(); ++l) { 4021 auto *line = reinterpret_cast<QRgb*>(tabPixmap.scanLine(l)); 4022 for (int i = 0; i < tabPixmap.width(); ++i) { 4023 if (qAlpha(line[i]) == 255) { 4024 line[i] = inactiveGrayRGB; 4025 } else if (qAlpha(line[i]) > 128) { 4026 const int g = qRound(inactiveGray * qRed(line[i])); 4027 line[i] = qRgba(g, g, g, qAlpha(line[i])); 4028 } 4029 } 4030 } 4031 4032 // Finally, draw the tab pixmap on the current painter 4033 p->drawImage(opt->rect, tabPixmap); 4034 } else { 4035 d->drawNSViewInRect(pb, frameRect, p, drawBezelBlock); 4036 } 4037 4038 if (!isSelected && sp != QStyleOptionTab::NextIsSelected 4039 && tp != QStyleOptionTab::End 4040 && tp != QStyleOptionTab::OnlyOneTab) { 4041 static const QPen separatorPen(Qt::black, 1.0); 4042 p->save(); 4043 p->setOpacity(isEnabled ? 0.105 : 0.06); // As measured 4044 p->setPen(separatorPen); 4045 if (tabDirection == QMacStylePrivate::West) { 4046 p->drawLine(QLineF(opt->rect.left() + 1.5, opt->rect.bottom(), 4047 opt->rect.right() - 0.5, opt->rect.bottom())); 4048 } else if (tabDirection == QMacStylePrivate::East) { 4049 p->drawLine(QLineF(opt->rect.left(), opt->rect.bottom(), 4050 opt->rect.right() - 0.5, opt->rect.bottom())); 4051 } else { 4052 p->drawLine(QLineF(opt->rect.right(), opt->rect.top() + 1.0, 4053 opt->rect.right(), opt->rect.bottom() - 0.5)); 4054 } 4055 p->restore(); 4056 } 4057 4058 // TODO Needs size adjustment to fit the focus ring 4059 if (tabOpt->state & State_HasFocus) { 4060 QMacStylePrivate::CocoaControlType focusRingType; 4061 switch (tp) { 4062 case QStyleOptionTab::Beginning: 4063 focusRingType = verticalTabs ? QMacStylePrivate::SegmentedControl_Last 4064 : QMacStylePrivate::SegmentedControl_First; 4065 break; 4066 case QStyleOptionTab::Middle: 4067 focusRingType = QMacStylePrivate::SegmentedControl_Middle; 4068 break; 4069 case QStyleOptionTab::End: 4070 focusRingType = verticalTabs ? QMacStylePrivate::SegmentedControl_First 4071 : QMacStylePrivate::SegmentedControl_Last; 4072 break; 4073 case QStyleOptionTab::OnlyOneTab: 4074 focusRingType = QMacStylePrivate::SegmentedControl_Single; 4075 break; 4076 } 4077 } 4078 } 4079 break; 4080 case CE_TabBarTabLabel: 4081 if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { 4082 QStyleOptionTab myTab = *tab; 4083 const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape); 4084 const bool verticalTabs = tabDirection == QMacStylePrivate::East 4085 || tabDirection == QMacStylePrivate::West; 4086 4087 // Check to see if we use have the same as the system font 4088 // (QComboMenuItem is internal and should never be seen by the 4089 // outside world, unless they read the source, in which case, it's 4090 // their own fault). 4091 const bool nonDefaultFont = p->font() != qt_app_fonts_hash()->value("QComboMenuItem"); 4092 4093 if (!myTab.documentMode && (myTab.state & State_Selected) && (myTab.state & State_Active)) 4094 if (const auto *tabBar = qobject_cast<const QTabBar *>(w)) 4095 if (!tabBar->tabTextColor(tabBar->currentIndex()).isValid()) 4096 myTab.palette.setColor(QPalette::WindowText, Qt::white); 4097 4098 if (myTab.documentMode && isDarkMode()) { 4099 bool active = (myTab.state & State_Selected) && (myTab.state & State_Active); 4100 myTab.palette.setColor(QPalette::WindowText, active ? Qt::white : Qt::gray); 4101 } 4102 4103 int heightOffset = 0; 4104 if (verticalTabs) { 4105 heightOffset = -1; 4106 } else if (nonDefaultFont) { 4107 if (p->fontMetrics().height() == myTab.rect.height()) 4108 heightOffset = 2; 4109 } 4110 myTab.rect.setHeight(myTab.rect.height() + heightOffset); 4111 4112 QCommonStyle::drawControl(ce, &myTab, p, w); 4113 } 4114 break; 4115#endif 4116#if QT_CONFIG(dockwidget) 4117 case CE_DockWidgetTitle: 4118 if (const auto *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) { 4119 const bool isVertical = dwOpt->verticalTitleBar; 4120 const auto effectiveRect = isVertical ? opt->rect.transposed() : opt->rect; 4121 p->save(); 4122 if (isVertical) { 4123 p->translate(effectiveRect.left(), effectiveRect.top() + effectiveRect.width()); 4124 p->rotate(-90); 4125 p->translate(-effectiveRect.left(), -effectiveRect.top()); 4126 } 4127 4128 // fill title bar background 4129 QLinearGradient linearGrad; 4130 linearGrad.setStart(QPointF(0, 0)); 4131 linearGrad.setFinalStop(QPointF(0, 2 * effectiveRect.height())); 4132 linearGrad.setColorAt(0, opt->palette.button().color()); 4133 linearGrad.setColorAt(1, opt->palette.dark().color()); 4134 p->fillRect(effectiveRect, linearGrad); 4135 4136 // draw horizontal line at bottom 4137 p->setPen(opt->palette.dark().color()); 4138 p->drawLine(effectiveRect.bottomLeft(), effectiveRect.bottomRight()); 4139 4140 if (!dwOpt->title.isEmpty()) { 4141 auto titleRect = proxy()->subElementRect(SE_DockWidgetTitleBarText, opt, w); 4142 if (isVertical) 4143 titleRect = QRect(effectiveRect.left() + opt->rect.bottom() - titleRect.bottom(), 4144 effectiveRect.top() + titleRect.left() - opt->rect.left(), 4145 titleRect.height(), 4146 titleRect.width()); 4147 4148 const auto text = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width()); 4149 proxy()->drawItemText(p, titleRect, Qt::AlignCenter, dwOpt->palette, 4150 dwOpt->state & State_Enabled, text, QPalette::WindowText); 4151 } 4152 p->restore(); 4153 } 4154 break; 4155#endif 4156 case CE_FocusFrame: { 4157 const auto *ff = qobject_cast<const QFocusFrame *>(w); 4158 const auto *ffw = ff ? ff->widget() : nullptr; 4159 const auto ct = [=] { 4160 if (ffw) { 4161 if (ffw->inherits("QCheckBox")) 4162 return QMacStylePrivate::Button_CheckBox; 4163 if (ffw->inherits("QRadioButton")) 4164 return QMacStylePrivate::Button_RadioButton; 4165 if (ffw->inherits("QLineEdit") || ffw->inherits("QTextEdit")) 4166 return QMacStylePrivate::TextField; 4167 } 4168 4169 return QMacStylePrivate::Box; // Not really, just make it the default 4170 } (); 4171 const auto cs = ffw ? (ffw->testAttribute(Qt::WA_MacMiniSize) ? QStyleHelper::SizeMini : 4172 ffw->testAttribute(Qt::WA_MacSmallSize) ? QStyleHelper::SizeSmall : 4173 QStyleHelper::SizeLarge) : 4174 QStyleHelper::SizeLarge; 4175 const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, w); 4176 const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, opt, w); 4177 d->drawFocusRing(p, opt->rect, hMargin, vMargin, QMacStylePrivate::CocoaControl(ct, cs)); 4178 break; } 4179 case CE_MenuEmptyArea: 4180 // Skip: PE_PanelMenu fills in everything 4181 break; 4182 case CE_MenuItem: 4183 case CE_MenuHMargin: 4184 case CE_MenuVMargin: 4185 case CE_MenuTearoff: 4186 case CE_MenuScroller: 4187 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { 4188 const bool active = mi->state & State_Selected; 4189 if (active) 4190 p->fillRect(mi->rect, mi->palette.highlight()); 4191 4192 const QStyleHelper::WidgetSizePolicy widgetSize = d->aquaSizeConstrain(opt, w); 4193 4194 if (ce == CE_MenuTearoff) { 4195 p->setPen(QPen(mi->palette.dark().color(), 1, Qt::DashLine)); 4196 p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2 - 1, 4197 mi->rect.x() + mi->rect.width() - 4, 4198 mi->rect.y() + mi->rect.height() / 2 - 1); 4199 p->setPen(QPen(mi->palette.light().color(), 1, Qt::DashLine)); 4200 p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2, 4201 mi->rect.x() + mi->rect.width() - 4, 4202 mi->rect.y() + mi->rect.height() / 2); 4203 } else if (ce == CE_MenuScroller) { 4204 const QSize scrollerSize = QSize(10, 8); 4205 const int scrollerVOffset = 5; 4206 const int left = mi->rect.x() + (mi->rect.width() - scrollerSize.width()) / 2; 4207 const int right = left + scrollerSize.width(); 4208 int top; 4209 int bottom; 4210 if (opt->state & State_DownArrow) { 4211 bottom = mi->rect.y() + scrollerVOffset; 4212 top = bottom + scrollerSize.height(); 4213 } else { 4214 bottom = mi->rect.bottom() - scrollerVOffset; 4215 top = bottom - scrollerSize.height(); 4216 } 4217 p->save(); 4218 p->setRenderHint(QPainter::Antialiasing); 4219 QPainterPath path; 4220 path.moveTo(left, bottom); 4221 path.lineTo(right, bottom); 4222 path.lineTo((left + right) / 2, top); 4223 p->fillPath(path, opt->palette.buttonText()); 4224 p->restore(); 4225 } else if (ce != CE_MenuItem) { 4226 break; 4227 } 4228 4229 if (mi->menuItemType == QStyleOptionMenuItem::Separator) { 4230 CGColorRef separatorColor = [NSColor quaternaryLabelColor].CGColor; 4231 const QRect separatorRect = QRect(mi->rect.left(), mi->rect.center().y(), mi->rect.width(), 2); 4232 p->fillRect(separatorRect, qt_mac_toQColor(separatorColor)); 4233 break; 4234 } 4235 4236 const int maxpmw = mi->maxIconWidth; 4237 const bool enabled = mi->state & State_Enabled; 4238 4239 int xpos = mi->rect.x() + 18; 4240 int checkcol = maxpmw; 4241 if (!enabled) 4242 p->setPen(mi->palette.text().color()); 4243 else if (active) 4244 p->setPen(mi->palette.highlightedText().color()); 4245 else 4246 p->setPen(mi->palette.buttonText().color()); 4247 4248 if (mi->checked) { 4249 QStyleOption checkmarkOpt; 4250 checkmarkOpt.initFrom(w); 4251 4252 const int mw = checkcol + macItemFrame; 4253 const int mh = mi->rect.height() + macItemFrame; 4254 const int xp = mi->rect.x() + macItemFrame; 4255 checkmarkOpt.rect = QRect(xp, mi->rect.y() - checkmarkOpt.fontMetrics.descent(), mw, mh); 4256 4257 checkmarkOpt.state.setFlag(State_On, active); 4258 checkmarkOpt.state.setFlag(State_Enabled, enabled); 4259 if (widgetSize == QStyleHelper::SizeMini) 4260 checkmarkOpt.state |= State_Mini; 4261 else if (widgetSize == QStyleHelper::SizeSmall) 4262 checkmarkOpt.state |= State_Small; 4263 4264 // We let drawPrimitive(PE_IndicatorMenuCheckMark) pick the right color 4265 checkmarkOpt.palette.setColor(QPalette::HighlightedText, p->pen().color()); 4266 checkmarkOpt.palette.setColor(QPalette::Text, p->pen().color()); 4267 4268 proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &checkmarkOpt, p, w); 4269 } 4270 if (!mi->icon.isNull()) { 4271 QIcon::Mode mode = (mi->state & State_Enabled) ? QIcon::Normal 4272 : QIcon::Disabled; 4273 // Always be normal or disabled to follow the Mac style. 4274 int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize); 4275 QSize iconSize(smallIconSize, smallIconSize); 4276#if QT_CONFIG(combobox) 4277 if (const QComboBox *comboBox = qobject_cast<const QComboBox *>(w)) { 4278 iconSize = comboBox->iconSize(); 4279 } 4280#endif 4281 QPixmap pixmap = mi->icon.pixmap(window, iconSize, mode); 4282 int pixw = pixmap.width() / pixmap.devicePixelRatio(); 4283 int pixh = pixmap.height() / pixmap.devicePixelRatio(); 4284 QRect cr(xpos, mi->rect.y(), checkcol, mi->rect.height()); 4285 QRect pmr(0, 0, pixw, pixh); 4286 pmr.moveCenter(cr.center()); 4287 p->drawPixmap(pmr.topLeft(), pixmap); 4288 xpos += pixw + 6; 4289 } 4290 4291 QString s = mi->text; 4292 const auto text_flags = Qt::AlignVCenter | Qt::TextHideMnemonic 4293 | Qt::TextSingleLine | Qt::AlignAbsolute; 4294 int yPos = mi->rect.y(); 4295 if (widgetSize == QStyleHelper::SizeMini) 4296 yPos += 1; 4297 4298 const bool isSubMenu = mi->menuItemType == QStyleOptionMenuItem::SubMenu; 4299 const int tabwidth = isSubMenu ? 9 : mi->tabWidth; 4300 4301 QString rightMarginText; 4302 if (isSubMenu) 4303 rightMarginText = QStringLiteral("\u25b6\ufe0e"); // U+25B6 U+FE0E: BLACK RIGHT-POINTING TRIANGLE 4304 4305 // If present, save and remove embedded shorcut from text 4306 const int tabIndex = s.indexOf(QLatin1Char('\t')); 4307 if (tabIndex >= 0) { 4308 if (!isSubMenu) // ... but ignore it if it's a submenu. 4309 rightMarginText = s.mid(tabIndex + 1); 4310 s = s.left(tabIndex); 4311 } 4312 4313 p->save(); 4314 if (!rightMarginText.isEmpty()) { 4315 p->setFont(qt_app_fonts_hash()->value("QMenuItem", p->font())); 4316 int xp = mi->rect.right() - tabwidth - macRightBorder + 2; 4317 if (!isSubMenu) 4318 xp -= macItemHMargin + macItemFrame + 3; // Adjust for shortcut 4319 p->drawText(xp, yPos, tabwidth, mi->rect.height(), text_flags | Qt::AlignRight, rightMarginText); 4320 } 4321 4322 if (!s.isEmpty()) { 4323 const int xm = macItemFrame + maxpmw + macItemHMargin; 4324 QFont myFont = mi->font; 4325 // myFont may not have any "hard" flags set. We override 4326 // the point size so that when it is resolved against the device, this font will win. 4327 // This is mainly to handle cases where someone sets the font on the window 4328 // and then the combo inherits it and passes it onward. At that point the resolve mask 4329 // is very, very weak. This makes it stonger. 4330 myFont.setPointSizeF(QFontInfo(mi->font).pointSizeF()); 4331 4332 // QTBUG-65653: Our own text rendering doesn't look good enough, especially on non-retina 4333 // displays. Worked around here while waiting for a proper fix in QCoreTextFontEngine. 4334 // Only if we're not using QCoreTextFontEngine we do fallback to our own text rendering. 4335 const auto *fontEngine = QFontPrivate::get(myFont)->engineForScript(QChar::Script_Common); 4336 Q_ASSERT(fontEngine); 4337 if (fontEngine->type() == QFontEngine::Multi) { 4338 fontEngine = static_cast<const QFontEngineMulti *>(fontEngine)->engine(0); 4339 Q_ASSERT(fontEngine); 4340 } 4341 if (fontEngine->type() == QFontEngine::Mac) { 4342 NSFont *f = (NSFont *)(CTFontRef)fontEngine->handle(); 4343 4344 // Respect the menu item palette as set in the style option. 4345 const auto pc = p->pen().color(); 4346 NSColor *c = [NSColor colorWithSRGBRed:pc.redF() 4347 green:pc.greenF() 4348 blue:pc.blueF() 4349 alpha:pc.alphaF()]; 4350 4351 s = qt_mac_removeMnemonics(s); 4352 4353 QMacCGContext cgCtx(p); 4354 d->setupNSGraphicsContext(cgCtx, YES); 4355 4356 // Draw at point instead of in rect, as the rect we've computed for the menu item 4357 // is based on the font metrics we got from HarfBuzz, so we may risk having CoreText 4358 // line-break the string if it doesn't fit the given rect. It's better to draw outside 4359 // the rect and possibly overlap something than to have part of the text disappear. 4360 [s.toNSString() drawAtPoint:CGPointMake(xpos, yPos) 4361 withAttributes:@{ NSFontAttributeName:f, NSForegroundColorAttributeName:c, 4362 NSObliquenessAttributeName: [NSNumber numberWithDouble: myFont.italic() ? 0.3 : 0.0]}]; 4363 4364 d->restoreNSGraphicsContext(cgCtx); 4365 } else { 4366 p->setFont(myFont); 4367 p->drawText(xpos, yPos, mi->rect.width() - xm - tabwidth + 1, 4368 mi->rect.height(), text_flags, s); 4369 } 4370 } 4371 p->restore(); 4372 } 4373 break; 4374 case CE_MenuBarItem: 4375 case CE_MenuBarEmptyArea: 4376 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { 4377 const bool selected = (opt->state & State_Selected) && (opt->state & State_Enabled) && (opt->state & State_Sunken); 4378 const QBrush bg = selected ? mi->palette.highlight() : mi->palette.window(); 4379 p->fillRect(mi->rect, bg); 4380 4381 if (ce != CE_MenuBarItem) 4382 break; 4383 4384 if (!mi->icon.isNull()) { 4385 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); 4386 drawItemPixmap(p, mi->rect, 4387 Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip 4388 | Qt::TextSingleLine, 4389 mi->icon.pixmap(window, QSize(iconExtent, iconExtent), 4390 (mi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled)); 4391 } else { 4392 drawItemText(p, mi->rect, 4393 Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip 4394 | Qt::TextSingleLine, 4395 mi->palette, mi->state & State_Enabled, 4396 mi->text, selected ? QPalette::HighlightedText : QPalette::ButtonText); 4397 } 4398 } 4399 break; 4400 case CE_ProgressBarLabel: 4401 case CE_ProgressBarGroove: 4402 // Do nothing. All done in CE_ProgressBarContents. Only keep these for proxy style overrides. 4403 break; 4404 case CE_ProgressBarContents: 4405 if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { 4406 const bool isIndeterminate = (pb->minimum == 0 && pb->maximum == 0); 4407 const bool vertical = pb->orientation == Qt::Vertical; 4408 const bool inverted = pb->invertedAppearance; 4409 bool reverse = (!vertical && (pb->direction == Qt::RightToLeft)); 4410 if (inverted) 4411 reverse = !reverse; 4412 4413 QRect rect = pb->rect; 4414 if (vertical) 4415 rect = rect.transposed(); 4416 const CGRect cgRect = rect.toCGRect(); 4417 4418 const auto aquaSize = d->effectiveAquaSizeConstrain(opt, w); 4419 const QProgressStyleAnimation *animation = qobject_cast<QProgressStyleAnimation*>(d->animation(opt->styleObject)); 4420 QIndeterminateProgressIndicator *ipi = nil; 4421 if (isIndeterminate || animation) 4422 ipi = static_cast<QIndeterminateProgressIndicator *>(d->cocoaControl({ QMacStylePrivate::ProgressIndicator_Indeterminate, aquaSize })); 4423 if (isIndeterminate) { 4424 // QIndeterminateProgressIndicator derives from NSProgressIndicator. We use a single 4425 // instance that we start animating as soon as one of the progress bars is indeterminate. 4426 // Since they will be in sync (as it's the case in Cocoa), we just need to draw it with 4427 // the right geometry when the animation triggers an update. However, we can't hide it 4428 // entirely between frames since that would stop the animation, so we just set its alpha 4429 // value to 0. Same if we remove it from its superview. See QIndeterminateProgressIndicator 4430 // implementation for details. 4431 if (!animation && opt->styleObject) { 4432 auto *animation = new QProgressStyleAnimation(d->animateSpeed(QMacStylePrivate::AquaProgressBar), opt->styleObject); 4433 // NSProgressIndicator is heavier to draw than the HITheme API, so we reduce the frame rate a couple notches. 4434 animation->setFrameRate(QStyleAnimation::FifteenFps); 4435 d->startAnimation(animation); 4436 [ipi startAnimation]; 4437 } 4438 4439 d->setupNSGraphicsContext(cg, NO); 4440 d->setupVerticalInvertedXform(cg, reverse, vertical, cgRect); 4441 [ipi drawWithFrame:cgRect inView:d->backingStoreNSView]; 4442 d->restoreNSGraphicsContext(cg); 4443 } else { 4444 if (animation) { 4445 d->stopAnimation(opt->styleObject); 4446 [ipi stopAnimation]; 4447 } 4448 4449 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::ProgressIndicator_Determinate, aquaSize); 4450 auto *pi = static_cast<NSProgressIndicator *>(d->cocoaControl(cw)); 4451 d->drawNSViewInRect(pi, rect, p, ^(CGContextRef ctx, const CGRect &rect) { 4452 d->setupVerticalInvertedXform(ctx, reverse, vertical, rect); 4453 pi.minValue = pb->minimum; 4454 pi.maxValue = pb->maximum; 4455 pi.doubleValue = pb->progress; 4456 [pi drawRect:rect]; 4457 }); 4458 } 4459 } 4460 break; 4461 case CE_SizeGrip: { 4462 // This is not HIG kosher: Fall back to the old stuff until we decide what to do. 4463#ifndef QT_NO_MDIAREA 4464 if (!w || !qobject_cast<QMdiSubWindow *>(w->parentWidget())) 4465#endif 4466 break; 4467 4468 if (w->testAttribute(Qt::WA_MacOpaqueSizeGrip)) 4469 p->fillRect(opt->rect, opt->palette.window()); 4470 4471 QPen lineColor = QColor(82, 82, 82, 192); 4472 lineColor.setWidth(1); 4473 p->save(); 4474 p->setRenderHint(QPainter::Antialiasing); 4475 p->setPen(lineColor); 4476 const Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : qApp->layoutDirection(); 4477 const int NumLines = 3; 4478 for (int l = 0; l < NumLines; ++l) { 4479 const int offset = (l * 4 + 3); 4480 QPoint start, end; 4481 if (layoutDirection == Qt::LeftToRight) { 4482 start = QPoint(opt->rect.width() - offset, opt->rect.height() - 1); 4483 end = QPoint(opt->rect.width() - 1, opt->rect.height() - offset); 4484 } else { 4485 start = QPoint(offset, opt->rect.height() - 1); 4486 end = QPoint(1, opt->rect.height() - offset); 4487 } 4488 p->drawLine(start, end); 4489 } 4490 p->restore(); 4491 break; 4492 } 4493 case CE_Splitter: 4494 if (opt->rect.width() > 1 && opt->rect.height() > 1) { 4495 const bool isVertical = !(opt->state & QStyle::State_Horizontal); 4496 // Qt refers to the layout orientation, while Cocoa refers to the divider's. 4497 const auto ct = isVertical ? QMacStylePrivate::SplitView_Horizontal : QMacStylePrivate::SplitView_Vertical; 4498 const auto cw = QMacStylePrivate::CocoaControl(ct, QStyleHelper::SizeLarge); 4499 auto *sv = static_cast<NSSplitView *>(d->cocoaControl(cw)); 4500 sv.frame = opt->rect.toCGRect(); 4501 d->drawNSViewInRect(sv, opt->rect, p, ^(CGContextRef, const CGRect &rect) { 4502 [sv drawDividerInRect:rect]; 4503 }); 4504 } else { 4505 QPen oldPen = p->pen(); 4506 p->setPen(opt->palette.dark().color()); 4507 if (opt->state & QStyle::State_Horizontal) 4508 p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); 4509 else 4510 p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); 4511 p->setPen(oldPen); 4512 } 4513 break; 4514 case CE_RubberBand: 4515 if (const QStyleOptionRubberBand *rubber = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { 4516 QColor fillColor(opt->palette.color(QPalette::Disabled, QPalette::Highlight)); 4517 if (!rubber->opaque) { 4518 QColor strokeColor; 4519 // I retrieved these colors from the Carbon-Dev mailing list 4520 strokeColor.setHsvF(0, 0, 0.86, 1.0); 4521 fillColor.setHsvF(0, 0, 0.53, 0.25); 4522 if (opt->rect.width() * opt->rect.height() <= 3) { 4523 p->fillRect(opt->rect, strokeColor); 4524 } else { 4525 QPen oldPen = p->pen(); 4526 QBrush oldBrush = p->brush(); 4527 QPen pen(strokeColor); 4528 p->setPen(pen); 4529 p->setBrush(fillColor); 4530 QRect adjusted = opt->rect.adjusted(1, 1, -1, -1); 4531 if (adjusted.isValid()) 4532 p->drawRect(adjusted); 4533 p->setPen(oldPen); 4534 p->setBrush(oldBrush); 4535 } 4536 } else { 4537 p->fillRect(opt->rect, fillColor); 4538 } 4539 } 4540 break; 4541#ifndef QT_NO_TOOLBAR 4542 case CE_ToolBar: { 4543 const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(opt); 4544 const bool isDarkMode = qt_mac_applicationIsInDarkMode(); 4545 4546 // Unified title and toolbar drawing. In this mode the cocoa platform plugin will 4547 // fill the top toolbar area part with a background gradient that "unifies" with 4548 // the title bar. The following code fills the toolBar area with transparent pixels 4549 // to make that gradient visible. 4550 if (w) { 4551#if QT_CONFIG(mainwindow) 4552 if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(w->window())) { 4553 if (toolBar && toolBar->toolBarArea == Qt::TopToolBarArea && mainWindow->unifiedTitleAndToolBarOnMac()) { 4554 // fill with transparent pixels. 4555 p->save(); 4556 p->setCompositionMode(QPainter::CompositionMode_Source); 4557 p->fillRect(opt->rect, Qt::transparent); 4558 p->restore(); 4559 4560 // Draw a horizontal separator line at the toolBar bottom if the "unified" area ends here. 4561 // There might be additional toolbars or other widgets such as tab bars in document 4562 // mode below. Determine this by making a unified toolbar area test for the row below 4563 // this toolbar. 4564 const QPoint windowToolbarEnd = w->mapTo(w->window(), opt->rect.bottomLeft()); 4565 const bool isEndOfUnifiedArea = !isInMacUnifiedToolbarArea(w->window()->windowHandle(), windowToolbarEnd.y() + 1); 4566 if (isEndOfUnifiedArea) { 4567 const int margin = qt_mac_aqua_get_metric(SeparatorSize); 4568 const auto separatorRect = QRect(opt->rect.left(), opt->rect.bottom(), opt->rect.width(), margin); 4569 p->fillRect(separatorRect, isDarkMode ? darkModeSeparatorLine : opt->palette.dark().color()); 4570 } 4571 break; 4572 } 4573 } 4574#endif 4575 } 4576 4577 // draw background gradient 4578 QLinearGradient linearGrad; 4579 if (opt->state & State_Horizontal) 4580 linearGrad = QLinearGradient(0, opt->rect.top(), 0, opt->rect.bottom()); 4581 else 4582 linearGrad = QLinearGradient(opt->rect.left(), 0, opt->rect.right(), 0); 4583 4584 QColor mainWindowGradientBegin = isDarkMode ? darkMainWindowGradientBegin : lightMainWindowGradientBegin; 4585 QColor mainWindowGradientEnd = isDarkMode ? darkMainWindowGradientEnd : lightMainWindowGradientEnd; 4586 4587 linearGrad.setColorAt(0, mainWindowGradientBegin); 4588 linearGrad.setColorAt(1, mainWindowGradientEnd); 4589 p->fillRect(opt->rect, linearGrad); 4590 4591 p->save(); 4592 QRect toolbarRect = isDarkMode ? opt->rect.adjusted(0, 0, 0, 1) : opt->rect; 4593 if (opt->state & State_Horizontal) { 4594 p->setPen(isDarkMode ? darkModeSeparatorLine : mainWindowGradientBegin.lighter(114)); 4595 p->drawLine(toolbarRect.topLeft(), toolbarRect.topRight()); 4596 p->setPen(isDarkMode ? darkModeSeparatorLine :mainWindowGradientEnd.darker(114)); 4597 p->drawLine(toolbarRect.bottomLeft(), toolbarRect.bottomRight()); 4598 } else { 4599 p->setPen(isDarkMode ? darkModeSeparatorLine : mainWindowGradientBegin.lighter(114)); 4600 p->drawLine(toolbarRect.topLeft(), toolbarRect.bottomLeft()); 4601 p->setPen(isDarkMode ? darkModeSeparatorLine : mainWindowGradientEnd.darker(114)); 4602 p->drawLine(toolbarRect.topRight(), toolbarRect.bottomRight()); 4603 } 4604 p->restore(); 4605 4606 4607 } break; 4608#endif 4609 default: 4610 QCommonStyle::drawControl(ce, opt, p, w); 4611 break; 4612 } 4613} 4614 4615static void setLayoutItemMargins(int left, int top, int right, int bottom, QRect *rect, Qt::LayoutDirection dir) 4616{ 4617 if (dir == Qt::RightToLeft) { 4618 rect->adjust(-right, top, -left, bottom); 4619 } else { 4620 rect->adjust(left, top, right, bottom); 4621 } 4622} 4623 4624QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt, 4625 const QWidget *widget) const 4626{ 4627 Q_D(const QMacStyle); 4628 QRect rect; 4629 const int controlSize = getControlSize(opt, widget); 4630 4631 switch (sr) { 4632#if QT_CONFIG(itemviews) 4633 case SE_ItemViewItemText: 4634 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { 4635 int fw = proxy()->pixelMetric(PM_FocusFrameHMargin, opt, widget); 4636 // We add the focusframeargin between icon and text in commonstyle 4637 rect = QCommonStyle::subElementRect(sr, opt, widget); 4638 if (vopt->features & QStyleOptionViewItem::HasDecoration) 4639 rect.adjust(-fw, 0, 0, 0); 4640 } 4641 break; 4642#endif 4643 case SE_ToolBoxTabContents: 4644 rect = QCommonStyle::subElementRect(sr, opt, widget); 4645 break; 4646 case SE_PushButtonBevel: 4647 case SE_PushButtonContents: 4648 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { 4649 // Comment from the old HITheme days: 4650 // "Unlike Carbon, we want the button to always be drawn inside its bounds. 4651 // Therefore, the button is a bit smaller, so that even if it got focus, 4652 // the focus 'shadow' will be inside. Adjust the content rect likewise." 4653 // In the future, we should consider using -[NSCell titleRectForBounds:]. 4654 // Since it requires configuring the NSButton fully, i.e. frame, image, 4655 // title and font, we keep things more manual until we are more familiar 4656 // with side effects when changing NSButton state. 4657 const auto ct = cocoaControlType(btn, widget); 4658 const auto cs = d->effectiveAquaSizeConstrain(btn, widget); 4659 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 4660 auto frameRect = cw.adjustedControlFrame(btn->rect); 4661 if (sr == SE_PushButtonContents) { 4662 frameRect -= cw.titleMargins(); 4663 } else if (cw.type != QMacStylePrivate::Button_SquareButton) { 4664 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw)); 4665 frameRect = QRectF::fromCGRect([pb alignmentRectForFrame:frameRect.toCGRect()]); 4666 if (cw.type == QMacStylePrivate::Button_PushButton) 4667 frameRect -= pushButtonShadowMargins[cw.size]; 4668 else if (cw.type == QMacStylePrivate::Button_PullDown) 4669 frameRect -= pullDownButtonShadowMargins[cw.size]; 4670 } 4671 rect = frameRect.toRect(); 4672 } 4673 break; 4674 case SE_HeaderLabel: { 4675 int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); 4676 rect.setRect(opt->rect.x() + margin, opt->rect.y(), 4677 opt->rect.width() - margin * 2, opt->rect.height() - 2); 4678 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { 4679 // Subtract width needed for arrow, if there is one 4680 if (header->sortIndicator != QStyleOptionHeader::None) { 4681 if (opt->state & State_Horizontal) 4682 rect.setWidth(rect.width() - (headerSectionArrowHeight) - (margin * 2)); 4683 else 4684 rect.setHeight(rect.height() - (headerSectionArrowHeight) - (margin * 2)); 4685 } 4686 } 4687 rect = visualRect(opt->direction, opt->rect, rect); 4688 break; 4689 } 4690 case SE_HeaderArrow: { 4691 int h = opt->rect.height(); 4692 int w = opt->rect.width(); 4693 int x = opt->rect.x(); 4694 int y = opt->rect.y(); 4695 int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); 4696 4697 if (opt->state & State_Horizontal) { 4698 rect.setRect(x + w - margin * 2 - headerSectionArrowHeight, y + 5, 4699 headerSectionArrowHeight, h - margin * 2 - 5); 4700 } else { 4701 rect.setRect(x + 5, y + h - margin * 2 - headerSectionArrowHeight, 4702 w - margin * 2 - 5, headerSectionArrowHeight); 4703 } 4704 rect = visualRect(opt->direction, opt->rect, rect); 4705 break; 4706 } 4707 case SE_ProgressBarGroove: 4708 // Wrong in the secondary dimension, but accurate enough in the main dimension. 4709 rect = opt->rect; 4710 break; 4711 case SE_ProgressBarLabel: 4712 break; 4713 case SE_ProgressBarContents: 4714 rect = opt->rect; 4715 break; 4716 case SE_TreeViewDisclosureItem: { 4717 rect = opt->rect; 4718 // As previously returned by HIThemeGetButtonContentBounds 4719 rect.setLeft(rect.left() + 2 + DisclosureOffset); 4720 break; 4721 } 4722#if QT_CONFIG(tabwidget) 4723 case SE_TabWidgetLeftCorner: 4724 if (const QStyleOptionTabWidgetFrame *twf 4725 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { 4726 switch (twf->shape) { 4727 case QTabBar::RoundedNorth: 4728 case QTabBar::TriangularNorth: 4729 rect = QRect(QPoint(0, 0), twf->leftCornerWidgetSize); 4730 break; 4731 case QTabBar::RoundedSouth: 4732 case QTabBar::TriangularSouth: 4733 rect = QRect(QPoint(0, twf->rect.height() - twf->leftCornerWidgetSize.height()), 4734 twf->leftCornerWidgetSize); 4735 break; 4736 default: 4737 break; 4738 } 4739 rect = visualRect(twf->direction, twf->rect, rect); 4740 } 4741 break; 4742 case SE_TabWidgetRightCorner: 4743 if (const QStyleOptionTabWidgetFrame *twf 4744 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { 4745 switch (twf->shape) { 4746 case QTabBar::RoundedNorth: 4747 case QTabBar::TriangularNorth: 4748 rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), 0), 4749 twf->rightCornerWidgetSize); 4750 break; 4751 case QTabBar::RoundedSouth: 4752 case QTabBar::TriangularSouth: 4753 rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), 4754 twf->rect.height() - twf->rightCornerWidgetSize.height()), 4755 twf->rightCornerWidgetSize); 4756 break; 4757 default: 4758 break; 4759 } 4760 rect = visualRect(twf->direction, twf->rect, rect); 4761 } 4762 break; 4763 case SE_TabWidgetTabContents: 4764 rect = QCommonStyle::subElementRect(sr, opt, widget); 4765 if (const auto *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { 4766 if (twf->lineWidth != 0) { 4767 switch (QMacStylePrivate::tabDirection(twf->shape)) { 4768 case QMacStylePrivate::North: 4769 rect.adjust(+1, +14, -1, -1); 4770 break; 4771 case QMacStylePrivate::South: 4772 rect.adjust(+1, +1, -1, -14); 4773 break; 4774 case QMacStylePrivate::West: 4775 rect.adjust(+14, +1, -1, -1); 4776 break; 4777 case QMacStylePrivate::East: 4778 rect.adjust(+1, +1, -14, -1); 4779 } 4780 } 4781 } 4782 break; 4783 case SE_TabBarTabText: 4784 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { 4785 QRect dummyIconRect; 4786 d->tabLayout(tab, widget, &rect, &dummyIconRect); 4787 } 4788 break; 4789 case SE_TabBarTabLeftButton: 4790 case SE_TabBarTabRightButton: 4791 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { 4792 bool selected = tab->state & State_Selected; 4793 int verticalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, tab, widget); 4794 int horizontalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, tab, widget); 4795 int hpadding = 5; 4796 4797 bool verticalTabs = tab->shape == QTabBar::RoundedEast 4798 || tab->shape == QTabBar::RoundedWest 4799 || tab->shape == QTabBar::TriangularEast 4800 || tab->shape == QTabBar::TriangularWest; 4801 4802 QRect tr = tab->rect; 4803 if (tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::TriangularSouth) 4804 verticalShift = -verticalShift; 4805 if (verticalTabs) { 4806 qSwap(horizontalShift, verticalShift); 4807 horizontalShift *= -1; 4808 verticalShift *= -1; 4809 } 4810 if (tab->shape == QTabBar::RoundedWest || tab->shape == QTabBar::TriangularWest) 4811 horizontalShift = -horizontalShift; 4812 4813 tr.adjust(0, 0, horizontalShift, verticalShift); 4814 if (selected) 4815 { 4816 tr.setBottom(tr.bottom() - verticalShift); 4817 tr.setRight(tr.right() - horizontalShift); 4818 } 4819 4820 QSize size = (sr == SE_TabBarTabLeftButton) ? tab->leftButtonSize : tab->rightButtonSize; 4821 int w = size.width(); 4822 int h = size.height(); 4823 int midHeight = static_cast<int>(qCeil(float(tr.height() - h) / 2)); 4824 int midWidth = ((tr.width() - w) / 2); 4825 4826 bool atTheTop = true; 4827 switch (tab->shape) { 4828 case QTabBar::RoundedWest: 4829 case QTabBar::TriangularWest: 4830 atTheTop = (sr == SE_TabBarTabLeftButton); 4831 break; 4832 case QTabBar::RoundedEast: 4833 case QTabBar::TriangularEast: 4834 atTheTop = (sr == SE_TabBarTabRightButton); 4835 break; 4836 default: 4837 if (sr == SE_TabBarTabLeftButton) 4838 rect = QRect(tab->rect.x() + hpadding, midHeight, w, h); 4839 else 4840 rect = QRect(tab->rect.right() - w - hpadding, midHeight, w, h); 4841 rect = visualRect(tab->direction, tab->rect, rect); 4842 } 4843 if (verticalTabs) { 4844 if (atTheTop) 4845 rect = QRect(midWidth, tr.y() + tab->rect.height() - hpadding - h, w, h); 4846 else 4847 rect = QRect(midWidth, tr.y() + hpadding, w, h); 4848 } 4849 } 4850 break; 4851#endif 4852 case SE_LineEditContents: 4853 rect = QCommonStyle::subElementRect(sr, opt, widget); 4854#if QT_CONFIG(combobox) 4855 if (widget && qobject_cast<const QComboBox*>(widget->parentWidget())) 4856 rect.adjust(-1, -2, 0, 0); 4857 else 4858#endif 4859 rect.adjust(-1, -1, 0, +1); 4860 break; 4861 case SE_CheckBoxLayoutItem: 4862 rect = opt->rect; 4863 if (controlSize == QStyleHelper::SizeLarge) { 4864 setLayoutItemMargins(+2, +3, -9, -4, &rect, opt->direction); 4865 } else if (controlSize == QStyleHelper::SizeSmall) { 4866 setLayoutItemMargins(+1, +5, 0 /* fix */, -6, &rect, opt->direction); 4867 } else { 4868 setLayoutItemMargins(0, +7, 0 /* fix */, -6, &rect, opt->direction); 4869 } 4870 break; 4871 case SE_ComboBoxLayoutItem: 4872#ifndef QT_NO_TOOLBAR 4873 if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) { 4874 // Do nothing, because QToolbar needs the entire widget rect. 4875 // Otherwise it will be clipped. Equivalent to 4876 // widget->setAttribute(Qt::WA_LayoutUsesWidgetRect), but without 4877 // all the hassle. 4878 } else 4879#endif 4880 { 4881 rect = opt->rect; 4882 if (controlSize == QStyleHelper::SizeLarge) { 4883 rect.adjust(+3, +2, -3, -4); 4884 } else if (controlSize == QStyleHelper::SizeSmall) { 4885 setLayoutItemMargins(+2, +1, -3, -4, &rect, opt->direction); 4886 } else { 4887 setLayoutItemMargins(+1, 0, -2, 0, &rect, opt->direction); 4888 } 4889 } 4890 break; 4891 case SE_LabelLayoutItem: 4892 rect = opt->rect; 4893 setLayoutItemMargins(+1, 0 /* SHOULD be -1, done for alignment */, 0, 0 /* SHOULD be -1, done for alignment */, &rect, opt->direction); 4894 break; 4895 case SE_ProgressBarLayoutItem: { 4896 rect = opt->rect; 4897 int bottom = SIZE(3, 8, 8); 4898 if (opt->state & State_Horizontal) { 4899 rect.adjust(0, +1, 0, -bottom); 4900 } else { 4901 setLayoutItemMargins(+1, 0, -bottom, 0, &rect, opt->direction); 4902 } 4903 break; 4904 } 4905 case SE_PushButtonLayoutItem: 4906 if (const QStyleOptionButton *buttonOpt 4907 = qstyleoption_cast<const QStyleOptionButton *>(opt)) { 4908 if ((buttonOpt->features & QStyleOptionButton::Flat)) 4909 break; // leave rect alone 4910 } 4911 rect = opt->rect; 4912 if (controlSize == QStyleHelper::SizeLarge) { 4913 rect.adjust(+6, +4, -6, -8); 4914 } else if (controlSize == QStyleHelper::SizeSmall) { 4915 rect.adjust(+5, +4, -5, -6); 4916 } else { 4917 rect.adjust(+1, 0, -1, -2); 4918 } 4919 break; 4920 case SE_RadioButtonLayoutItem: 4921 rect = opt->rect; 4922 if (controlSize == QStyleHelper::SizeLarge) { 4923 setLayoutItemMargins(+2, +2 /* SHOULD BE +3, done for alignment */, 4924 0, -4 /* SHOULD BE -3, done for alignment */, &rect, opt->direction); 4925 } else if (controlSize == QStyleHelper::SizeSmall) { 4926 rect.adjust(0, +6, 0 /* fix */, -5); 4927 } else { 4928 rect.adjust(0, +6, 0 /* fix */, -7); 4929 } 4930 break; 4931 case SE_SliderLayoutItem: 4932 if (const QStyleOptionSlider *sliderOpt 4933 = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 4934 rect = opt->rect; 4935 if (sliderOpt->tickPosition == QSlider::NoTicks) { 4936 int above = SIZE(3, 0, 2); 4937 int below = SIZE(4, 3, 0); 4938 if (sliderOpt->orientation == Qt::Horizontal) { 4939 rect.adjust(0, +above, 0, -below); 4940 } else { 4941 rect.adjust(+above, 0, -below, 0); //### Seems that QSlider flip the position of the ticks in reverse mode. 4942 } 4943 } else if (sliderOpt->tickPosition == QSlider::TicksAbove) { 4944 int below = SIZE(3, 2, 0); 4945 if (sliderOpt->orientation == Qt::Horizontal) { 4946 rect.setHeight(rect.height() - below); 4947 } else { 4948 rect.setWidth(rect.width() - below); 4949 } 4950 } else if (sliderOpt->tickPosition == QSlider::TicksBelow) { 4951 int above = SIZE(3, 2, 0); 4952 if (sliderOpt->orientation == Qt::Horizontal) { 4953 rect.setTop(rect.top() + above); 4954 } else { 4955 rect.setLeft(rect.left() + above); 4956 } 4957 } 4958 } 4959 break; 4960 case SE_FrameLayoutItem: 4961 // hack because QStyleOptionFrame doesn't have a frameStyle member 4962 if (const QFrame *frame = qobject_cast<const QFrame *>(widget)) { 4963 rect = opt->rect; 4964 switch (frame->frameStyle() & QFrame::Shape_Mask) { 4965 case QFrame::HLine: 4966 rect.adjust(0, +1, 0, -1); 4967 break; 4968 case QFrame::VLine: 4969 rect.adjust(+1, 0, -1, 0); 4970 break; 4971 default: 4972 ; 4973 } 4974 } 4975 break; 4976 case SE_GroupBoxLayoutItem: 4977 rect = opt->rect; 4978 if (const QStyleOptionGroupBox *groupBoxOpt = 4979 qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { 4980 /* 4981 AHIG is very inconsistent when it comes to group boxes. 4982 Basically, we make sure that (non-checkable) group boxes 4983 and tab widgets look good when laid out side by side. 4984 */ 4985 if (groupBoxOpt->subControls & (QStyle::SC_GroupBoxCheckBox 4986 | QStyle::SC_GroupBoxLabel)) { 4987 int delta; 4988 if (groupBoxOpt->subControls & QStyle::SC_GroupBoxCheckBox) { 4989 delta = SIZE(8, 4, 4); // guess 4990 } else { 4991 delta = SIZE(15, 12, 12); // guess 4992 } 4993 rect.setTop(rect.top() + delta); 4994 } 4995 } 4996 rect.setBottom(rect.bottom() - 1); 4997 break; 4998#if QT_CONFIG(tabwidget) 4999 case SE_TabWidgetLayoutItem: 5000 if (const QStyleOptionTabWidgetFrame *tabWidgetOpt = 5001 qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { 5002 /* 5003 AHIG specifies "12 or 14" as the distance from the window 5004 edge. We choose 14 and since the default top margin is 20, 5005 the overlap is 6. 5006 */ 5007 rect = tabWidgetOpt->rect; 5008 if (tabWidgetOpt->shape == QTabBar::RoundedNorth) 5009 rect.setTop(rect.top() + SIZE(6 /* AHIG */, 3 /* guess */, 2 /* AHIG */)); 5010 } 5011 break; 5012#endif 5013#if QT_CONFIG(dockwidget) 5014 case SE_DockWidgetCloseButton: 5015 case SE_DockWidgetFloatButton: 5016 case SE_DockWidgetTitleBarText: 5017 case SE_DockWidgetIcon: { 5018 int iconSize = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); 5019 int buttonMargin = proxy()->pixelMetric(PM_DockWidgetTitleBarButtonMargin, opt, widget); 5020 QRect srect = opt->rect; 5021 5022 const QStyleOptionDockWidget *dwOpt 5023 = qstyleoption_cast<const QStyleOptionDockWidget*>(opt); 5024 bool canClose = dwOpt == 0 ? true : dwOpt->closable; 5025 bool canFloat = dwOpt == 0 ? false : dwOpt->floatable; 5026 5027 const bool verticalTitleBar = dwOpt->verticalTitleBar; 5028 5029 // If this is a vertical titlebar, we transpose and work as if it was 5030 // horizontal, then transpose again. 5031 if (verticalTitleBar) 5032 srect = srect.transposed(); 5033 5034 do { 5035 int right = srect.right(); 5036 int left = srect.left(); 5037 5038 QRect closeRect; 5039 if (canClose) { 5040 QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, 5041 opt, widget).actualSize(QSize(iconSize, iconSize)); 5042 sz += QSize(buttonMargin, buttonMargin); 5043 if (verticalTitleBar) 5044 sz = sz.transposed(); 5045 closeRect = QRect(left, 5046 srect.center().y() - sz.height()/2, 5047 sz.width(), sz.height()); 5048 left = closeRect.right() + 1; 5049 } 5050 if (sr == SE_DockWidgetCloseButton) { 5051 rect = closeRect; 5052 break; 5053 } 5054 5055 QRect floatRect; 5056 if (canFloat) { 5057 QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarNormalButton, 5058 opt, widget).actualSize(QSize(iconSize, iconSize)); 5059 sz += QSize(buttonMargin, buttonMargin); 5060 if (verticalTitleBar) 5061 sz = sz.transposed(); 5062 floatRect = QRect(left, 5063 srect.center().y() - sz.height()/2, 5064 sz.width(), sz.height()); 5065 left = floatRect.right() + 1; 5066 } 5067 if (sr == SE_DockWidgetFloatButton) { 5068 rect = floatRect; 5069 break; 5070 } 5071 5072 QRect iconRect; 5073 if (const QDockWidget *dw = qobject_cast<const QDockWidget*>(widget)) { 5074 QIcon icon; 5075 if (dw->isFloating()) 5076 icon = dw->windowIcon(); 5077 if (!icon.isNull() 5078 && icon.cacheKey() != QApplication::windowIcon().cacheKey()) { 5079 QSize sz = icon.actualSize(QSize(rect.height(), rect.height())); 5080 if (verticalTitleBar) 5081 sz = sz.transposed(); 5082 iconRect = QRect(right - sz.width(), srect.center().y() - sz.height()/2, 5083 sz.width(), sz.height()); 5084 right = iconRect.left() - 1; 5085 } 5086 } 5087 if (sr == SE_DockWidgetIcon) { 5088 rect = iconRect; 5089 break; 5090 } 5091 5092 QRect textRect = QRect(left, srect.top(), 5093 right - left, srect.height()); 5094 if (sr == SE_DockWidgetTitleBarText) { 5095 rect = textRect; 5096 break; 5097 } 5098 } while (false); 5099 5100 if (verticalTitleBar) { 5101 rect = QRect(srect.left() + rect.top() - srect.top(), 5102 srect.top() + srect.right() - rect.right(), 5103 rect.height(), rect.width()); 5104 } else { 5105 rect = visualRect(opt->direction, srect, rect); 5106 } 5107 break; 5108 } 5109#endif 5110 default: 5111 rect = QCommonStyle::subElementRect(sr, opt, widget); 5112 break; 5113 } 5114 return rect; 5115} 5116 5117void QMacStylePrivate::drawToolbarButtonArrow(const QStyleOption *opt, QPainter *p) const 5118{ 5119 Q_Q(const QMacStyle); 5120 QStyleOption arrowOpt = *opt; 5121 arrowOpt.rect = QRect(opt->rect.right() - (toolButtonArrowSize + toolButtonArrowMargin), 5122 opt->rect.bottom() - (toolButtonArrowSize + toolButtonArrowMargin), 5123 toolButtonArrowSize, 5124 toolButtonArrowSize); 5125 q->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &arrowOpt, p); 5126} 5127 5128void QMacStylePrivate::setupNSGraphicsContext(CGContextRef cg, bool flipped) const 5129{ 5130 CGContextSaveGState(cg); 5131 [NSGraphicsContext saveGraphicsState]; 5132 5133 [NSGraphicsContext setCurrentContext: 5134 [NSGraphicsContext graphicsContextWithCGContext:cg flipped:flipped]]; 5135} 5136 5137void QMacStylePrivate::restoreNSGraphicsContext(CGContextRef cg) const 5138{ 5139 [NSGraphicsContext restoreGraphicsState]; 5140 CGContextRestoreGState(cg); 5141} 5142 5143void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, 5144 const QWidget *widget) const 5145{ 5146 Q_D(const QMacStyle); 5147 const AppearanceSync sync; 5148 QMacCGContext cg(p); 5149 QWindow *window = widget && widget->window() ? widget->window()->windowHandle() : nullptr; 5150 d->resolveCurrentNSView(window); 5151 switch (cc) { 5152 case CC_ScrollBar: 5153 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5154 5155 const bool drawTrack = sb->subControls & SC_ScrollBarGroove; 5156 const bool drawKnob = sb->subControls & SC_ScrollBarSlider; 5157 if (!drawTrack && !drawKnob) 5158 break; 5159 5160 const bool isHorizontal = sb->orientation == Qt::Horizontal; 5161 5162 if (opt && opt->styleObject && !QMacStylePrivate::scrollBars.contains(opt->styleObject)) 5163 QMacStylePrivate::scrollBars.append(QPointer<QObject>(opt->styleObject)); 5164 5165 static const CGFloat knobWidths[] = { 7.0, 5.0, 5.0 }; 5166 static const CGFloat expandedKnobWidths[] = { 11.0, 9.0, 9.0 }; 5167 const auto cocoaSize = d->effectiveAquaSizeConstrain(opt, widget); 5168 const CGFloat maxExpandScale = expandedKnobWidths[cocoaSize] / knobWidths[cocoaSize]; 5169 5170 const bool isTransient = proxy()->styleHint(SH_ScrollBar_Transient, opt, widget); 5171 if (!isTransient) 5172 d->stopAnimation(opt->styleObject); 5173 bool wasActive = false; 5174 CGFloat opacity = 0.0; 5175 CGFloat expandScale = 1.0; 5176 CGFloat expandOffset = 0.0; 5177 bool shouldExpand = false; 5178 5179 if (QObject *styleObject = opt->styleObject) { 5180 const int oldPos = styleObject->property("_q_stylepos").toInt(); 5181 const int oldMin = styleObject->property("_q_stylemin").toInt(); 5182 const int oldMax = styleObject->property("_q_stylemax").toInt(); 5183 const QRect oldRect = styleObject->property("_q_stylerect").toRect(); 5184 const QStyle::State oldState = static_cast<QStyle::State>(styleObject->property("_q_stylestate").value<QStyle::State::Int>()); 5185 const uint oldActiveControls = styleObject->property("_q_stylecontrols").toUInt(); 5186 5187 // a scrollbar is transient when the scrollbar itself and 5188 // its sibling are both inactive (ie. not pressed/hovered/moved) 5189 const bool transient = isTransient && !opt->activeSubControls && !(sb->state & State_On); 5190 5191 if (!transient || 5192 oldPos != sb->sliderPosition || 5193 oldMin != sb->minimum || 5194 oldMax != sb->maximum || 5195 oldRect != sb->rect || 5196 oldState != sb->state || 5197 oldActiveControls != sb->activeSubControls) { 5198 5199 // if the scrollbar is transient or its attributes, geometry or 5200 // state has changed, the opacity is reset back to 100% opaque 5201 opacity = 1.0; 5202 5203 styleObject->setProperty("_q_stylepos", sb->sliderPosition); 5204 styleObject->setProperty("_q_stylemin", sb->minimum); 5205 styleObject->setProperty("_q_stylemax", sb->maximum); 5206 styleObject->setProperty("_q_stylerect", sb->rect); 5207 styleObject->setProperty("_q_stylestate", static_cast<QStyle::State::Int>(sb->state)); 5208 styleObject->setProperty("_q_stylecontrols", static_cast<uint>(sb->activeSubControls)); 5209 5210 QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject)); 5211 if (transient) { 5212 if (!anim) { 5213 anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Deactivating, styleObject); 5214 d->startAnimation(anim); 5215 } else if (anim->mode() == QScrollbarStyleAnimation::Deactivating) { 5216 // the scrollbar was already fading out while the 5217 // state changed -> restart the fade out animation 5218 anim->setCurrentTime(0); 5219 } 5220 } else if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { 5221 d->stopAnimation(styleObject); 5222 } 5223 } 5224 5225 QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject)); 5226 if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { 5227 // once a scrollbar was active (hovered/pressed), it retains 5228 // the active look even if it's no longer active while fading out 5229 if (oldActiveControls) 5230 anim->setActive(true); 5231 5232 wasActive = anim->wasActive(); 5233 opacity = anim->currentValue(); 5234 } 5235 5236 shouldExpand = isTransient && (opt->activeSubControls || wasActive); 5237 if (shouldExpand) { 5238 if (!anim && !oldActiveControls) { 5239 // Start expand animation only once and when entering 5240 anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Activating, styleObject); 5241 d->startAnimation(anim); 5242 } 5243 if (anim && anim->mode() == QScrollbarStyleAnimation::Activating) { 5244 expandScale = 1.0 + (maxExpandScale - 1.0) * anim->currentValue(); 5245 expandOffset = 5.5 * (1.0 - anim->currentValue()); 5246 } else { 5247 // Keep expanded state after the animation ends, and when fading out 5248 expandScale = maxExpandScale; 5249 expandOffset = 0.0; 5250 } 5251 } 5252 } 5253 5254 d->setupNSGraphicsContext(cg, NO /* flipped */); 5255 5256 const auto controlType = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical; 5257 const auto cw = QMacStylePrivate::CocoaControl(controlType, cocoaSize); 5258 NSScroller *scroller = static_cast<NSScroller *>(d->cocoaControl(cw)); 5259 5260 const QColor bgColor = QStyleHelper::backgroundColor(opt->palette, widget); 5261 const bool hasDarkBg = bgColor.red() < 128 && bgColor.green() < 128 && bgColor.blue() < 128; 5262 if (isTransient) { 5263 // macOS behavior: as soon as one color channel is >= 128, 5264 // the background is considered bright, scroller is dark. 5265 scroller.knobStyle = hasDarkBg? NSScrollerKnobStyleLight : NSScrollerKnobStyleDark; 5266 } else { 5267 scroller.knobStyle = NSScrollerKnobStyleDefault; 5268 } 5269 5270 scroller.scrollerStyle = isTransient ? NSScrollerStyleOverlay : NSScrollerStyleLegacy; 5271 5272 if (!setupScroller(scroller, sb)) 5273 break; 5274 5275 if (isTransient) { 5276 CGContextBeginTransparencyLayerWithRect(cg, scroller.frame, nullptr); 5277 CGContextSetAlpha(cg, opacity); 5278 } 5279 5280 if (drawTrack) { 5281 // Draw the track when hovering. Expand by shifting the track rect. 5282 if (!isTransient || opt->activeSubControls || wasActive) { 5283 CGRect trackRect = scroller.bounds; 5284 if (isHorizontal) 5285 trackRect.origin.y += expandOffset; 5286 else 5287 trackRect.origin.x += expandOffset; 5288 [scroller drawKnobSlotInRect:trackRect highlight:NO]; 5289 } 5290 } 5291 5292 if (drawKnob) { 5293 if (shouldExpand) { 5294 // -[NSScroller drawKnob] is not useful here because any scaling applied 5295 // will only be used to draw the hi-DPI artwork. And even if did scale, 5296 // the stretched knob would look wrong, actually. So we need to draw the 5297 // scroller manually when it's being hovered. 5298 const CGFloat scrollerWidth = [NSScroller scrollerWidthForControlSize:scroller.controlSize scrollerStyle:scroller.scrollerStyle]; 5299 const CGFloat knobWidth = knobWidths[cocoaSize] * expandScale; 5300 // Cocoa can help get the exact knob length in the current orientation 5301 const CGRect scrollerKnobRect = CGRectInset([scroller rectForPart:NSScrollerKnob], 1, 1); 5302 const CGFloat knobLength = isHorizontal ? scrollerKnobRect.size.width : scrollerKnobRect.size.height; 5303 const CGFloat knobPos = isHorizontal ? scrollerKnobRect.origin.x : scrollerKnobRect.origin.y; 5304 const CGFloat knobOffset = qRound((scrollerWidth + expandOffset - knobWidth) / 2.0); 5305 const CGFloat knobRadius = knobWidth / 2.0; 5306 CGRect knobRect; 5307 if (isHorizontal) 5308 knobRect = CGRectMake(knobPos, knobOffset, knobLength, knobWidth); 5309 else 5310 knobRect = CGRectMake(knobOffset, knobPos, knobWidth, knobLength); 5311 QCFType<CGPathRef> knobPath = CGPathCreateWithRoundedRect(knobRect, knobRadius, knobRadius, nullptr); 5312 CGContextAddPath(cg, knobPath); 5313 CGContextSetAlpha(cg, 0.5); 5314 CGColorRef knobColor = hasDarkBg ? NSColor.whiteColor.CGColor : NSColor.blackColor.CGColor; 5315 CGContextSetFillColorWithColor(cg, knobColor); 5316 CGContextFillPath(cg); 5317 } else { 5318 [scroller drawKnob]; 5319 5320 if (!isTransient && opt->activeSubControls) { 5321 // The knob should appear darker (going from 0.76 down to 0.49). 5322 // But no blending mode can help darken enough in a single pass, 5323 // so we resort to drawing the knob twice with a small help from 5324 // blending. This brings the gray level to a close enough 0.53. 5325 CGContextSetBlendMode(cg, kCGBlendModePlusDarker); 5326 [scroller drawKnob]; 5327 } 5328 } 5329 } 5330 5331 if (isTransient) 5332 CGContextEndTransparencyLayer(cg); 5333 5334 d->restoreNSGraphicsContext(cg); 5335 } 5336 break; 5337 case CC_Slider: 5338 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5339 const bool isHorizontal = sl->orientation == Qt::Horizontal; 5340 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical; 5341 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5342 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5343 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw)); 5344 if (!setupSlider(slider, sl)) 5345 break; 5346 5347 const bool hasTicks = sl->tickPosition != QSlider::NoTicks; 5348 const bool hasDoubleTicks = sl->tickPosition == QSlider::TicksBothSides; 5349 const bool drawKnob = sl->subControls & SC_SliderHandle; 5350 const bool drawBar = sl->subControls & SC_SliderGroove; 5351 const bool drawTicks = sl->subControls & SC_SliderTickmarks; 5352 const bool isPressed = sl->state & State_Sunken; 5353 5354 CGPoint pressPoint; 5355 if (isPressed) { 5356 const CGRect knobRect = [slider.cell knobRectFlipped:slider.isFlipped]; 5357 pressPoint.x = CGRectGetMidX(knobRect); 5358 pressPoint.y = CGRectGetMidY(knobRect); 5359 [slider.cell startTrackingAt:pressPoint inView:slider]; 5360 } 5361 5362 d->drawNSViewInRect(slider, opt->rect, p, ^(CGContextRef ctx, const CGRect &rect) { 5363 5364 // Since the GC is flipped, upsideDown means *not* inverted when vertical. 5365 const bool verticalFlip = !isHorizontal && !sl->upsideDown; // FIXME: && !isSierraOrLater 5366 5367 if (isHorizontal) { 5368 if (sl->upsideDown) { 5369 CGContextTranslateCTM(ctx, rect.size.width, rect.origin.y); 5370 CGContextScaleCTM(ctx, -1, 1); 5371 } else { 5372 CGContextTranslateCTM(ctx, 0, rect.origin.y); 5373 } 5374 } else if (verticalFlip) { 5375 CGContextTranslateCTM(ctx, rect.origin.x, rect.size.height); 5376 CGContextScaleCTM(ctx, 1, -1); 5377 } 5378 5379 if (hasDoubleTicks) { 5380 // This ain't HIG kosher: eye-proved constants 5381 if (isHorizontal) 5382 CGContextTranslateCTM(ctx, 0, 4); 5383 else 5384 CGContextTranslateCTM(ctx, 1, 0); 5385 } 5386 5387#if 0 5388 // FIXME: Sadly, this part doesn't work. It seems to somehow polute the 5389 // NSSlider's internal state and, when we need to use the "else" part, 5390 // the slider's frame is not in sync with its cell dimensions. 5391 const bool drawAllParts = drawKnob && drawBar && (!hasTicks || drawTicks); 5392 if (drawAllParts && !hasDoubleTicks && (!verticalFlip || drawTicks)) { 5393 // Draw eveything at once if we're going to, except for inverted vertical 5394 // sliders which need to be drawn part by part because of the shadow below 5395 // the knob. Same for two-sided tickmarks. 5396 if (verticalFlip && drawTicks) { 5397 // Since tickmarks are always rendered symmetrically, a vertically 5398 // flipped slider with tickmarks only needs to get its value flipped. 5399 slider.intValue = slider.maxValue - slider.intValue + slider.minValue; 5400 } 5401 [slider drawRect:CGRectZero]; 5402 } else 5403#endif 5404 { 5405 NSSliderCell *cell = slider.cell; 5406 5407 const int numberOfTickMarks = slider.numberOfTickMarks; 5408 // This ain't HIG kosher: force tick-less bar position. 5409 if (hasDoubleTicks) 5410 slider.numberOfTickMarks = 0; 5411 5412 const CGRect barRect = [cell barRectFlipped:slider.isFlipped]; 5413 if (drawBar) { 5414 if (!isHorizontal && !sl->upsideDown && (hasDoubleTicks || !hasTicks)) { 5415 // The logic behind verticalFlip and upsideDown is the twisted one. 5416 // Bar is the only part of the cell affected by this 'flipped' 5417 // parameter in the call below, all other parts (knob, etc.) 'fixed' 5418 // by scaling/translating. With ticks on one side it's not a problem 5419 // at all - the bar is gray anyway. Without ticks or with ticks on 5420 // the both sides, for inverted appearance and vertical orientation - 5421 // we must flip so that knob and blue filling work in accordance. 5422 [cell drawBarInside:barRect flipped:true]; 5423 } else { 5424 [cell drawBarInside:barRect flipped:!verticalFlip]; 5425 } 5426 // This ain't HIG kosher: force unfilled bar look. 5427 if (hasDoubleTicks) 5428 slider.numberOfTickMarks = numberOfTickMarks; 5429 } 5430 5431 if (hasTicks && drawTicks) { 5432 if (!drawBar && hasDoubleTicks) 5433 slider.numberOfTickMarks = numberOfTickMarks; 5434 5435 [cell drawTickMarks]; 5436 5437 if (hasDoubleTicks) { 5438 // This ain't HIG kosher: just slap a set of tickmarks on each side, like we used to. 5439 CGAffineTransform tickMarksFlip; 5440 const CGRect tickMarkRect = [cell rectOfTickMarkAtIndex:0]; 5441 if (isHorizontal) { 5442 tickMarksFlip = CGAffineTransformMakeTranslation(0, rect.size.height - tickMarkRect.size.height - 3); 5443 tickMarksFlip = CGAffineTransformScale(tickMarksFlip, 1, -1); 5444 } else { 5445 tickMarksFlip = CGAffineTransformMakeTranslation(rect.size.width - tickMarkRect.size.width / 2, 0); 5446 tickMarksFlip = CGAffineTransformScale(tickMarksFlip, -1, 1); 5447 } 5448 CGContextConcatCTM(ctx, tickMarksFlip); 5449 [cell drawTickMarks]; 5450 CGContextConcatCTM(ctx, CGAffineTransformInvert(tickMarksFlip)); 5451 } 5452 } 5453 5454 if (drawKnob) { 5455 // This ain't HIG kosher: force round knob look. 5456 if (hasDoubleTicks) 5457 slider.numberOfTickMarks = 0; 5458 [cell drawKnob]; 5459 } 5460 } 5461 }); 5462 5463 if (isPressed) 5464 [slider.cell stopTracking:pressPoint at:pressPoint inView:slider mouseIsUp:NO]; 5465 } 5466 break; 5467#if QT_CONFIG(spinbox) 5468 case CC_SpinBox: 5469 if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { 5470 if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { 5471 const auto lineEditRect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxEditField, widget); 5472 QStyleOptionFrame frame; 5473 static_cast<QStyleOption &>(frame) = *opt; 5474 frame.rect = lineEditRect; 5475 frame.state |= State_Sunken; 5476 frame.lineWidth = 1; 5477 frame.midLineWidth = 0; 5478 frame.features = QStyleOptionFrame::None; 5479 frame.frameShape = QFrame::Box; 5480 drawPrimitive(PE_FrameLineEdit, &frame, p, widget); 5481 } 5482 if (sb->subControls & (SC_SpinBoxUp | SC_SpinBoxDown)) { 5483 const QRect updown = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget) 5484 | proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); 5485 5486 d->setupNSGraphicsContext(cg, NO); 5487 5488 const auto aquaSize = d->effectiveAquaSizeConstrain(opt, widget); 5489 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Stepper, aquaSize); 5490 NSStepperCell *cell = static_cast<NSStepperCell *>(d->cocoaCell(cw)); 5491 cell.enabled = (sb->state & State_Enabled); 5492 5493 const CGRect newRect = [cell drawingRectForBounds:updown.toCGRect()]; 5494 5495 const bool upPressed = sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken); 5496 const bool downPressed = sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken); 5497 const CGFloat x = CGRectGetMidX(newRect); 5498 const CGFloat y = upPressed ? -3 : 3; // Weird coordinate shift going on. Verified with Hopper 5499 const CGPoint pressPoint = CGPointMake(x, y); 5500 // Pretend we're pressing the mouse on the right button. Unfortunately, NSStepperCell has no 5501 // API to highlight a specific button. The highlighted property works only on the down button. 5502 if (upPressed || downPressed) 5503 [cell startTrackingAt:pressPoint inView:d->backingStoreNSView]; 5504 5505 [cell drawWithFrame:newRect inView:d->backingStoreNSView]; 5506 5507 if (upPressed || downPressed) 5508 [cell stopTracking:pressPoint at:pressPoint inView:d->backingStoreNSView mouseIsUp:NO]; 5509 5510 d->restoreNSGraphicsContext(cg); 5511 } 5512 } 5513 break; 5514#endif 5515#if QT_CONFIG(combobox) 5516 case CC_ComboBox: 5517 if (const auto *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 5518 const bool isEnabled = combo->state & State_Enabled; 5519 const bool isPressed = combo->state & State_Sunken; 5520 5521 const auto ct = cocoaControlType(combo, widget); 5522 const auto cs = d->effectiveAquaSizeConstrain(combo, widget); 5523 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5524 auto *cc = static_cast<NSControl *>(d->cocoaControl(cw)); 5525 cc.enabled = isEnabled; 5526 QRectF frameRect = cw.adjustedControlFrame(combo->rect);; 5527 if (cw.type == QMacStylePrivate::Button_PopupButton) { 5528 // Non-editable QComboBox 5529 auto *pb = static_cast<NSPopUpButton *>(cc); 5530 // FIXME Old offsets. Try to move to adjustedControlFrame() 5531 if (cw.size == QStyleHelper::SizeSmall) { 5532 frameRect = frameRect.translated(0, 1); 5533 } else if (cw.size == QStyleHelper::SizeMini) { 5534 // Same 0.5 pt misalignment as AppKit and fit the focus ring 5535 frameRect = frameRect.translated(2, -0.5); 5536 } 5537 pb.frame = frameRect.toCGRect(); 5538 [pb highlight:isPressed]; 5539 d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) { 5540 [pb.cell drawBezelWithFrame:r inView:pb.superview]; 5541 }); 5542 } else if (cw.type == QMacStylePrivate::ComboBox) { 5543 // Editable QComboBox 5544 auto *cb = static_cast<NSComboBox *>(cc); 5545 const auto frameRect = cw.adjustedControlFrame(combo->rect); 5546 cb.frame = frameRect.toCGRect(); 5547 5548 // This API was requested to Apple in rdar #36197888. We know it's safe to use up to macOS 10.13.3 5549 if (NSButtonCell *cell = static_cast<NSButtonCell *>([cc.cell qt_valueForPrivateKey:@"_buttonCell"])) { 5550 cell.highlighted = isPressed; 5551 } else { 5552 // TODO Render to pixmap and darken the button manually 5553 } 5554 5555 d->drawNSViewInRect(cb, frameRect, p, ^(CGContextRef, const CGRect &r) { 5556 // FIXME This is usually drawn in the control's superview, but we wouldn't get inactive look in this case 5557 [cb.cell drawWithFrame:r inView:cb]; 5558 }); 5559 } 5560 5561 if (combo->state & State_HasFocus) { 5562 // TODO Remove and use QFocusFrame instead. 5563 const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, combo, widget); 5564 const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, combo, widget); 5565 QRectF focusRect; 5566 if (cw.type == QMacStylePrivate::Button_PopupButton) { 5567 focusRect = QRectF::fromCGRect([cc alignmentRectForFrame:cc.frame]); 5568 focusRect -= pullDownButtonShadowMargins[cw.size]; 5569 if (cw.size == QStyleHelper::SizeSmall) 5570 focusRect = focusRect.translated(0, 1); 5571 else if (cw.size == QStyleHelper::SizeMini) 5572 focusRect = focusRect.translated(2, -1); 5573 } else if (cw.type == QMacStylePrivate::ComboBox) { 5574 focusRect = frameRect - comboBoxFocusRingMargins[cw.size]; 5575 } 5576 d->drawFocusRing(p, focusRect, hMargin, vMargin, cw); 5577 } 5578 } 5579 break; 5580#endif // QT_CONFIG(combobox) 5581 case CC_TitleBar: 5582 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { 5583 const bool isActive = (titlebar->state & State_Active) 5584 && (titlebar->titleBarState & State_Active); 5585 5586 p->fillRect(opt->rect, Qt::transparent); 5587 p->setRenderHint(QPainter::Antialiasing); 5588 p->setClipRect(opt->rect, Qt::IntersectClip); 5589 5590 // FIXME A single drawPath() with 0-sized pen 5591 // doesn't look as good as this double fillPath(). 5592 const auto outerFrameRect = QRectF(opt->rect.adjusted(0, 0, 0, opt->rect.height())); 5593 QPainterPath outerFramePath = d->windowPanelPath(outerFrameRect); 5594 p->fillPath(outerFramePath, opt->palette.dark()); 5595 5596 const auto frameAdjust = 1.0 / p->device()->devicePixelRatioF(); 5597 const auto innerFrameRect = outerFrameRect.adjusted(frameAdjust, frameAdjust, -frameAdjust, 0); 5598 QPainterPath innerFramePath = d->windowPanelPath(innerFrameRect); 5599 if (isActive) { 5600 QLinearGradient g; 5601 g.setStart(QPointF(0, 0)); 5602 g.setFinalStop(QPointF(0, 2 * opt->rect.height())); 5603 g.setColorAt(0, opt->palette.button().color()); 5604 g.setColorAt(1, opt->palette.dark().color()); 5605 p->fillPath(innerFramePath, g); 5606 } else { 5607 p->fillPath(innerFramePath, opt->palette.button()); 5608 } 5609 5610 if (titlebar->subControls & (SC_TitleBarCloseButton 5611 | SC_TitleBarMaxButton 5612 | SC_TitleBarMinButton 5613 | SC_TitleBarNormalButton)) { 5614 const bool isHovered = (titlebar->state & State_MouseOver); 5615 static const SubControl buttons[] = { 5616 SC_TitleBarCloseButton, SC_TitleBarMinButton, SC_TitleBarMaxButton 5617 }; 5618 for (const auto sc : buttons) { 5619 const auto ct = d->windowButtonCocoaControl(sc); 5620 const auto cw = QMacStylePrivate::CocoaControl(ct, QStyleHelper::SizeLarge); 5621 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw)); 5622 wb.enabled = (sc & titlebar->subControls) && isActive; 5623 [wb highlight:(titlebar->state & State_Sunken) && (sc & titlebar->activeSubControls)]; 5624 Q_UNUSED(isHovered); // FIXME No public API for this 5625 5626 const auto buttonRect = proxy()->subControlRect(CC_TitleBar, titlebar, sc, widget); 5627 d->drawNSViewInRect(wb, buttonRect, p, ^(CGContextRef, const CGRect &rect) { 5628 auto *wbCell = static_cast<NSButtonCell *>(wb.cell); 5629 [wbCell drawWithFrame:rect inView:wb]; 5630 }); 5631 } 5632 } 5633 5634 if (titlebar->subControls & SC_TitleBarLabel) { 5635 const auto tr = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarLabel, widget); 5636 if (!titlebar->icon.isNull()) { 5637 const auto iconExtent = proxy()->pixelMetric(PM_SmallIconSize); 5638 const auto iconSize = QSize(iconExtent, iconExtent); 5639 const auto iconPos = tr.x() - titlebar->icon.actualSize(iconSize).width() - qRound(titleBarIconTitleSpacing); 5640 // Only render the icon if it'll be fully visible 5641 if (iconPos < tr.right() - titleBarIconTitleSpacing) 5642 p->drawPixmap(iconPos, tr.y(), titlebar->icon.pixmap(window, iconSize, QIcon::Normal)); 5643 } 5644 5645 if (!titlebar->text.isEmpty()) 5646 drawItemText(p, tr, Qt::AlignCenter, opt->palette, isActive, titlebar->text, QPalette::Text); 5647 } 5648 } 5649 break; 5650 case CC_GroupBox: 5651 if (const QStyleOptionGroupBox *gb 5652 = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { 5653 5654 QStyleOptionGroupBox groupBox(*gb); 5655 const bool flat = groupBox.features & QStyleOptionFrame::Flat; 5656 if (!flat) 5657 groupBox.state |= QStyle::State_Mini; // Force mini-sized checkbox to go with small-sized label 5658 else 5659 groupBox.subControls = groupBox.subControls & ~SC_GroupBoxFrame; // We don't like frames and ugly lines 5660 5661 const bool didSetFont = widget && widget->testAttribute(Qt::WA_SetFont); 5662 const bool didModifySubControls = !didSetFont && QApplication::desktopSettingsAware(); 5663 if (didModifySubControls) 5664 groupBox.subControls = groupBox.subControls & ~SC_GroupBoxLabel; 5665 QCommonStyle::drawComplexControl(cc, &groupBox, p, widget); 5666 if (didModifySubControls) { 5667 const QRect rect = proxy()->subControlRect(CC_GroupBox, &groupBox, SC_GroupBoxLabel, widget); 5668 const bool rtl = groupBox.direction == Qt::RightToLeft; 5669 const int alignment = Qt::TextHideMnemonic | (rtl ? Qt::AlignRight : Qt::AlignLeft); 5670 const QFont savedFont = p->font(); 5671 if (!flat) 5672 p->setFont(d->smallSystemFont); 5673 proxy()->drawItemText(p, rect, alignment, groupBox.palette, groupBox.state & State_Enabled, groupBox.text, QPalette::WindowText); 5674 if (!flat) 5675 p->setFont(savedFont); 5676 } 5677 } 5678 break; 5679 case CC_ToolButton: 5680 if (const QStyleOptionToolButton *tb 5681 = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { 5682#ifndef QT_NO_ACCESSIBILITY 5683 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) { 5684 if (tb->subControls & SC_ToolButtonMenu) { 5685 QStyleOption arrowOpt = *tb; 5686 arrowOpt.rect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); 5687 arrowOpt.rect.setY(arrowOpt.rect.y() + arrowOpt.rect.height() / 2); 5688 arrowOpt.rect.setHeight(arrowOpt.rect.height() / 2); 5689 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); 5690 } else if ((tb->features & QStyleOptionToolButton::HasMenu) 5691 && (tb->toolButtonStyle != Qt::ToolButtonTextOnly && !tb->icon.isNull())) { 5692 d->drawToolbarButtonArrow(tb, p); 5693 } 5694 if (tb->state & State_On) { 5695 NSView *view = window ? (NSView *)window->winId() : nil; 5696 bool isKey = false; 5697 if (view) 5698 isKey = [view.window isKeyWindow]; 5699 5700 QBrush brush(brushForToolButton(isKey)); 5701 QPainterPath path; 5702 path.addRoundedRect(QRectF(tb->rect.x(), tb->rect.y(), tb->rect.width(), tb->rect.height() + 4), 4, 4); 5703 p->setRenderHint(QPainter::Antialiasing); 5704 p->fillPath(path, brush); 5705 } 5706 proxy()->drawControl(CE_ToolButtonLabel, opt, p, widget); 5707 } else 5708#endif // QT_NO_ACCESSIBILITY 5709 { 5710 auto bflags = tb->state; 5711 if (tb->subControls & SC_ToolButton) 5712 bflags |= State_Sunken; 5713 auto mflags = tb->state; 5714 if (tb->subControls & SC_ToolButtonMenu) 5715 mflags |= State_Sunken; 5716 5717 if (tb->subControls & SC_ToolButton) { 5718 if (bflags & (State_Sunken | State_On | State_Raised)) { 5719 const bool isEnabled = tb->state & State_Enabled; 5720 const bool isPressed = tb->state & State_Sunken; 5721 const bool isHighlighted = (tb->state & State_Active) && (tb->state & State_On); 5722 const auto ct = QMacStylePrivate::Button_PushButton; 5723 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5724 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5725 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw)); 5726 pb.bezelStyle = NSShadowlessSquareBezelStyle; // TODO Use NSTexturedRoundedBezelStyle in the future. 5727 pb.frame = opt->rect.toCGRect(); 5728 pb.buttonType = NSPushOnPushOffButton; 5729 pb.enabled = isEnabled; 5730 [pb highlight:isPressed]; 5731 pb.state = isHighlighted && !isPressed ? NSOnState : NSOffState; 5732 const auto buttonRect = proxy()->subControlRect(cc, tb, SC_ToolButton, widget); 5733 d->drawNSViewInRect(pb, buttonRect, p, ^(CGContextRef, const CGRect &rect) { 5734 [pb.cell drawBezelWithFrame:rect inView:pb]; 5735 }); 5736 } 5737 } 5738 5739 if (tb->subControls & SC_ToolButtonMenu) { 5740 const auto menuRect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); 5741 QStyleOption arrowOpt = *tb; 5742 arrowOpt.rect = QRect(menuRect.x() + ((menuRect.width() - toolButtonArrowSize) / 2), 5743 menuRect.height() - (toolButtonArrowSize + toolButtonArrowMargin), 5744 toolButtonArrowSize, 5745 toolButtonArrowSize); 5746 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); 5747 } else if (tb->features & QStyleOptionToolButton::HasMenu) { 5748 d->drawToolbarButtonArrow(tb, p); 5749 } 5750 QRect buttonRect = proxy()->subControlRect(CC_ToolButton, tb, SC_ToolButton, widget); 5751 int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); 5752 QStyleOptionToolButton label = *tb; 5753 label.rect = buttonRect.adjusted(fw, fw, -fw, -fw); 5754 proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget); 5755 } 5756 } 5757 break; 5758#if QT_CONFIG(dial) 5759 case CC_Dial: 5760 if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) 5761 QStyleHelper::drawDial(dial, p); 5762 break; 5763#endif 5764 default: 5765 QCommonStyle::drawComplexControl(cc, opt, p, widget); 5766 break; 5767 } 5768} 5769 5770QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, 5771 const QStyleOptionComplex *opt, 5772 const QPoint &pt, const QWidget *widget) const 5773{ 5774 Q_D(const QMacStyle); 5775 SubControl sc = QStyle::SC_None; 5776 switch (cc) { 5777 case CC_ComboBox: 5778 if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 5779 sc = QCommonStyle::hitTestComplexControl(cc, cmb, pt, widget); 5780 if (!cmb->editable && sc != QStyle::SC_None) 5781 sc = SC_ComboBoxArrow; // A bit of a lie, but what we want 5782 } 5783 break; 5784 case CC_Slider: 5785 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5786 if (!sl->rect.contains(pt)) 5787 break; 5788 5789 const bool hasTicks = sl->tickPosition != QSlider::NoTicks; 5790 const bool isHorizontal = sl->orientation == Qt::Horizontal; 5791 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical; 5792 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5793 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5794 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw)); 5795 if (!setupSlider(slider, sl)) 5796 break; 5797 5798 NSSliderCell *cell = slider.cell; 5799 const auto barRect = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]); 5800 const auto knobRect = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]); 5801 if (knobRect.contains(pt)) { 5802 sc = SC_SliderHandle; 5803 } else if (barRect.contains(pt)) { 5804 sc = SC_SliderGroove; 5805 } else if (hasTicks) { 5806 sc = SC_SliderTickmarks; 5807 } 5808 } 5809 break; 5810 case CC_ScrollBar: 5811 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5812 if (!sb->rect.contains(pt)) { 5813 sc = SC_None; 5814 break; 5815 } 5816 5817 const bool isHorizontal = sb->orientation == Qt::Horizontal; 5818 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical; 5819 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5820 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5821 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw)); 5822 if (!setupScroller(scroller, sb)) { 5823 sc = SC_None; 5824 break; 5825 } 5826 5827 // Since -[NSScroller testPart:] doesn't want to cooperate, we do it the 5828 // straightforward way. In any case, macOS doesn't return line-sized changes 5829 // with NSScroller since 10.7, according to the aforementioned method's doc. 5830 const auto knobRect = QRectF::fromCGRect([scroller rectForPart:NSScrollerKnob]); 5831 if (isHorizontal) { 5832 const bool isReverse = sb->direction == Qt::RightToLeft; 5833 if (pt.x() < knobRect.left()) 5834 sc = isReverse ? SC_ScrollBarAddPage : SC_ScrollBarSubPage; 5835 else if (pt.x() > knobRect.right()) 5836 sc = isReverse ? SC_ScrollBarSubPage : SC_ScrollBarAddPage; 5837 else 5838 sc = SC_ScrollBarSlider; 5839 } else { 5840 if (pt.y() < knobRect.top()) 5841 sc = SC_ScrollBarSubPage; 5842 else if (pt.y() > knobRect.bottom()) 5843 sc = SC_ScrollBarAddPage; 5844 else 5845 sc = SC_ScrollBarSlider; 5846 } 5847 } 5848 break; 5849 default: 5850 sc = QCommonStyle::hitTestComplexControl(cc, opt, pt, widget); 5851 break; 5852 } 5853 return sc; 5854} 5855 5856QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, 5857 const QWidget *widget) const 5858{ 5859 Q_D(const QMacStyle); 5860 QRect ret; 5861 switch (cc) { 5862 case CC_ScrollBar: 5863 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5864 const bool isHorizontal = sb->orientation == Qt::Horizontal; 5865 const bool isReverseHorizontal = isHorizontal && (sb->direction == Qt::RightToLeft); 5866 5867 NSScrollerPart part = NSScrollerNoPart; 5868 if (sc == SC_ScrollBarSlider) { 5869 part = NSScrollerKnob; 5870 } else if (sc == SC_ScrollBarGroove) { 5871 part = NSScrollerKnobSlot; 5872 } else if (sc == SC_ScrollBarSubPage || sc == SC_ScrollBarAddPage) { 5873 if ((!isReverseHorizontal && sc == SC_ScrollBarSubPage) 5874 || (isReverseHorizontal && sc == SC_ScrollBarAddPage)) 5875 part = NSScrollerDecrementPage; 5876 else 5877 part = NSScrollerIncrementPage; 5878 } 5879 // And nothing else since 10.7 5880 5881 if (part != NSScrollerNoPart) { 5882 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical; 5883 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5884 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5885 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw)); 5886 if (setupScroller(scroller, sb)) 5887 ret = QRectF::fromCGRect([scroller rectForPart:part]).toRect(); 5888 } 5889 } 5890 break; 5891 case CC_Slider: 5892 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 5893 const bool hasTicks = sl->tickPosition != QSlider::NoTicks; 5894 const bool isHorizontal = sl->orientation == Qt::Horizontal; 5895 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical; 5896 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 5897 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5898 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw)); 5899 if (!setupSlider(slider, sl)) 5900 break; 5901 5902 NSSliderCell *cell = slider.cell; 5903 if (sc == SC_SliderHandle) { 5904 ret = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]).toRect(); 5905 if (isHorizontal) { 5906 ret.setTop(sl->rect.top()); 5907 ret.setBottom(sl->rect.bottom()); 5908 } else { 5909 ret.setLeft(sl->rect.left()); 5910 ret.setRight(sl->rect.right()); 5911 } 5912 } else if (sc == SC_SliderGroove) { 5913 ret = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]).toRect(); 5914 } else if (hasTicks && sc == SC_SliderTickmarks) { 5915 const auto tickMarkRect = QRectF::fromCGRect([cell rectOfTickMarkAtIndex:0]); 5916 if (isHorizontal) 5917 ret = QRect(sl->rect.left(), tickMarkRect.top(), sl->rect.width(), tickMarkRect.height()); 5918 else 5919 ret = QRect(tickMarkRect.left(), sl->rect.top(), tickMarkRect.width(), sl->rect.height()); 5920 } 5921 5922 // Invert if needed and extend to the actual bounds of the slider 5923 if (isHorizontal) { 5924 if (sl->upsideDown) { 5925 ret = QRect(sl->rect.right() - ret.right(), sl->rect.top(), ret.width(), sl->rect.height()); 5926 } else { 5927 ret.setTop(sl->rect.top()); 5928 ret.setBottom(sl->rect.bottom()); 5929 } 5930 } else { 5931 if (!sl->upsideDown) { 5932 ret = QRect(sl->rect.left(), sl->rect.bottom() - ret.bottom(), sl->rect.width(), ret.height()); 5933 } else { 5934 ret.setLeft(sl->rect.left()); 5935 ret.setRight(sl->rect.right()); 5936 } 5937 } 5938 } 5939 break; 5940 case CC_TitleBar: 5941 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { 5942 // The title bar layout is as follows: close, min, zoom, icon, title 5943 // [ x _ + @ Window Title ] 5944 // Center the icon and title until it starts to overlap with the buttons. 5945 // The icon doesn't count towards SC_TitleBarLabel, but it's still rendered 5946 // next to the title text. See drawComplexControl(). 5947 if (sc == SC_TitleBarLabel) { 5948 qreal labelWidth = titlebar->fontMetrics.horizontalAdvance(titlebar->text) + 1; // FIXME Rounding error? 5949 qreal labelHeight = titlebar->fontMetrics.height(); 5950 5951 const auto lastButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarMaxButton, widget); 5952 qreal controlsSpacing = lastButtonRect.right() + titleBarButtonSpacing; 5953 if (!titlebar->icon.isNull()) { 5954 const auto iconSize = proxy()->pixelMetric(PM_SmallIconSize); 5955 const auto actualIconSize = titlebar->icon.actualSize(QSize(iconSize, iconSize)).width();; 5956 controlsSpacing += actualIconSize + titleBarIconTitleSpacing; 5957 } 5958 5959 const qreal labelPos = qMax(controlsSpacing, (opt->rect.width() - labelWidth) / 2.0); 5960 labelWidth = qMin(labelWidth, opt->rect.width() - (labelPos + titleBarTitleRightMargin)); 5961 ret = QRect(labelPos, (opt->rect.height() - labelHeight) / 2, 5962 labelWidth, labelHeight); 5963 } else { 5964 const auto currentButton = d->windowButtonCocoaControl(sc); 5965 if (currentButton == QMacStylePrivate::NoControl) 5966 break; 5967 5968 QPointF buttonPos = titlebar->rect.topLeft() + QPointF(titleBarButtonSpacing, 0); 5969 QSizeF buttonSize; 5970 for (int ct = QMacStylePrivate::Button_WindowClose; ct <= currentButton; ct++) { 5971 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::CocoaControlType(ct), 5972 QStyleHelper::SizeLarge); 5973 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw)); 5974 if (ct == currentButton) 5975 buttonSize = QSizeF::fromCGSize(wb.frame.size); 5976 else 5977 buttonPos.rx() += wb.frame.size.width + titleBarButtonSpacing; 5978 } 5979 5980 const auto vOffset = (opt->rect.height() - buttonSize.height()) / 2.0; 5981 ret = QRectF(buttonPos, buttonSize).translated(0, vOffset).toRect(); 5982 } 5983 } 5984 break; 5985 case CC_ComboBox: 5986 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 5987 const auto ct = cocoaControlType(combo, widget); 5988 const auto cs = d->effectiveAquaSizeConstrain(combo, widget); 5989 const auto cw = QMacStylePrivate::CocoaControl(ct, cs); 5990 const auto editRect = QMacStylePrivate::comboboxEditBounds(cw.adjustedControlFrame(combo->rect), cw); 5991 5992 switch (sc) { 5993 case SC_ComboBoxEditField:{ 5994 ret = editRect.toAlignedRect(); 5995 break; } 5996 case SC_ComboBoxArrow:{ 5997 ret = editRect.toAlignedRect(); 5998 ret.setX(ret.x() + ret.width()); 5999 ret.setWidth(combo->rect.right() - ret.right()); 6000 break; } 6001 case SC_ComboBoxListBoxPopup:{ 6002 if (combo->editable) { 6003 const CGRect inner = QMacStylePrivate::comboboxInnerBounds(combo->rect.toCGRect(), cw); 6004 const int comboTop = combo->rect.top(); 6005 ret = QRect(qRound(inner.origin.x), 6006 comboTop, 6007 qRound(inner.origin.x - combo->rect.left() + inner.size.width), 6008 editRect.bottom() - comboTop + 2); 6009 } else { 6010 ret = QRect(combo->rect.x() + 4 - 11, 6011 combo->rect.y() + 1, 6012 editRect.width() + 10 + 11, 6013 1); 6014 } 6015 break; } 6016 default: 6017 break; 6018 } 6019 } 6020 break; 6021 case CC_GroupBox: 6022 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { 6023 bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; 6024 const bool flat = groupBox->features & QStyleOptionFrame::Flat; 6025 bool hasNoText = !checkable && groupBox->text.isEmpty(); 6026 switch (sc) { 6027 case SC_GroupBoxLabel: 6028 case SC_GroupBoxCheckBox: { 6029 // Cheat and use the smaller font if we need to 6030 const bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; 6031 const bool fontIsSet = (widget && widget->testAttribute(Qt::WA_SetFont)) 6032 || !QApplication::desktopSettingsAware(); 6033 const int margin = flat || hasNoText ? 0 : 9; 6034 ret = groupBox->rect.adjusted(margin, 0, -margin, 0); 6035 6036 const QFontMetricsF fm = flat || fontIsSet ? QFontMetricsF(groupBox->fontMetrics) : QFontMetricsF(d->smallSystemFont); 6037 const QSizeF s = fm.size(Qt::AlignHCenter | Qt::AlignVCenter, qt_mac_removeMnemonics(groupBox->text), 0, nullptr); 6038 const int tw = qCeil(s.width()); 6039 const int h = qCeil(fm.height()); 6040 ret.setHeight(h); 6041 6042 QRect labelRect = alignedRect(groupBox->direction, groupBox->textAlignment, 6043 QSize(tw, h), ret); 6044 if (flat && checkable) 6045 labelRect.moveLeft(labelRect.left() + 4); 6046 int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt, widget); 6047 bool rtl = groupBox->direction == Qt::RightToLeft; 6048 if (sc == SC_GroupBoxLabel) { 6049 if (checkable) { 6050 int newSum = indicatorWidth + 1; 6051 int newLeft = labelRect.left() + (rtl ? -newSum : newSum); 6052 labelRect.moveLeft(newLeft); 6053 if (flat) 6054 labelRect.moveTop(labelRect.top() + 3); 6055 else 6056 labelRect.moveTop(labelRect.top() + 4); 6057 } else if (flat) { 6058 int newLeft = labelRect.left() - (rtl ? 3 : -3); 6059 labelRect.moveLeft(newLeft); 6060 labelRect.moveTop(labelRect.top() + 3); 6061 } else { 6062 int newLeft = labelRect.left() - (rtl ? 3 : 2); 6063 labelRect.moveLeft(newLeft); 6064 labelRect.moveTop(labelRect.top() + 4); 6065 } 6066 ret = labelRect; 6067 } 6068 6069 if (sc == SC_GroupBoxCheckBox) { 6070 int left = rtl ? labelRect.right() - indicatorWidth : labelRect.left() - 1; 6071 int top = flat ? ret.top() + 1 : ret.top() + 5; 6072 ret.setRect(left, top, 6073 indicatorWidth, proxy()->pixelMetric(PM_IndicatorHeight, opt, widget)); 6074 } 6075 break; 6076 } 6077 case SC_GroupBoxContents: 6078 case SC_GroupBoxFrame: { 6079 QFontMetrics fm = groupBox->fontMetrics; 6080 int yOffset = 3; 6081 if (!flat) { 6082 if (widget && !widget->testAttribute(Qt::WA_SetFont) 6083 && QApplication::desktopSettingsAware()) 6084 fm = QFontMetrics(qt_app_fonts_hash()->value("QSmallFont", QFont())); 6085 yOffset = 5; 6086 } 6087 6088 if (hasNoText) 6089 yOffset = -qCeil(QFontMetricsF(fm).height()); 6090 ret = opt->rect.adjusted(0, qCeil(QFontMetricsF(fm).height()) + yOffset, 0, 0); 6091 if (sc == SC_GroupBoxContents) { 6092 if (flat) 6093 ret.adjust(3, -5, -3, -4); // guess too 6094 else 6095 ret.adjust(3, 3, -3, -4); // guess 6096 } 6097 } 6098 break; 6099 default: 6100 ret = QCommonStyle::subControlRect(cc, groupBox, sc, widget); 6101 break; 6102 } 6103 } 6104 break; 6105#if QT_CONFIG(spinbox) 6106 case CC_SpinBox: 6107 if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { 6108 QStyleHelper::WidgetSizePolicy aquaSize = d->effectiveAquaSizeConstrain(spin, widget); 6109 const auto fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, spin, widget); 6110 int spinner_w; 6111 int spinBoxSep; 6112 switch (aquaSize) { 6113 case QStyleHelper::SizeLarge: 6114 spinner_w = 14; 6115 spinBoxSep = 2; 6116 break; 6117 case QStyleHelper::SizeSmall: 6118 spinner_w = 12; 6119 spinBoxSep = 2; 6120 break; 6121 case QStyleHelper::SizeMini: 6122 spinner_w = 10; 6123 spinBoxSep = 1; 6124 break; 6125 default: 6126 Q_UNREACHABLE(); 6127 } 6128 6129 switch (sc) { 6130 case SC_SpinBoxUp: 6131 case SC_SpinBoxDown: { 6132 if (spin->buttonSymbols == QAbstractSpinBox::NoButtons) 6133 break; 6134 6135 const int y = fw; 6136 const int x = spin->rect.width() - spinner_w; 6137 ret.setRect(x + spin->rect.x(), y + spin->rect.y(), spinner_w, spin->rect.height() - y * 2); 6138 int hackTranslateX; 6139 switch (aquaSize) { 6140 case QStyleHelper::SizeLarge: 6141 hackTranslateX = 0; 6142 break; 6143 case QStyleHelper::SizeSmall: 6144 hackTranslateX = -2; 6145 break; 6146 case QStyleHelper::SizeMini: 6147 hackTranslateX = -1; 6148 break; 6149 default: 6150 Q_UNREACHABLE(); 6151 } 6152 6153 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Stepper, aquaSize); 6154 NSStepperCell *cell = static_cast<NSStepperCell *>(d->cocoaCell(cw)); 6155 const CGRect outRect = [cell drawingRectForBounds:ret.toCGRect()]; 6156 ret = QRectF::fromCGRect(outRect).toRect(); 6157 6158 switch (sc) { 6159 case SC_SpinBoxUp: 6160 ret.setHeight(ret.height() / 2); 6161 break; 6162 case SC_SpinBoxDown: 6163 ret.setY(ret.y() + ret.height() / 2); 6164 break; 6165 default: 6166 Q_ASSERT(0); 6167 break; 6168 } 6169 ret.translate(hackTranslateX, 0); // hack: position the buttons correctly (weird that we need this) 6170 ret = visualRect(spin->direction, spin->rect, ret); 6171 break; 6172 } 6173 case SC_SpinBoxEditField: 6174 ret = spin->rect.adjusted(fw, fw, -fw, -fw); 6175 if (spin->buttonSymbols != QAbstractSpinBox::NoButtons) { 6176 ret.setWidth(spin->rect.width() - spinBoxSep - spinner_w); 6177 ret = visualRect(spin->direction, spin->rect, ret); 6178 } 6179 break; 6180 default: 6181 ret = QCommonStyle::subControlRect(cc, spin, sc, widget); 6182 break; 6183 } 6184 } 6185 break; 6186#endif 6187 case CC_ToolButton: 6188 ret = QCommonStyle::subControlRect(cc, opt, sc, widget); 6189 if (sc == SC_ToolButtonMenu) { 6190#ifndef QT_NO_ACCESSIBILITY 6191 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) 6192 ret.adjust(-toolButtonArrowMargin, 0, 0, 0); 6193#endif 6194 ret.adjust(-1, 0, 0, 0); 6195 } 6196 break; 6197 default: 6198 ret = QCommonStyle::subControlRect(cc, opt, sc, widget); 6199 break; 6200 } 6201 return ret; 6202} 6203 6204QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, 6205 const QSize &csz, const QWidget *widget) const 6206{ 6207 Q_D(const QMacStyle); 6208 QSize sz(csz); 6209 bool useAquaGuideline = true; 6210 6211 switch (ct) { 6212#if QT_CONFIG(spinbox) 6213 case CT_SpinBox: 6214 if (const QStyleOptionSpinBox *vopt = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { 6215 const bool hasButtons = (vopt->buttonSymbols != QAbstractSpinBox::NoButtons); 6216 const int buttonWidth = hasButtons ? proxy()->subControlRect(CC_SpinBox, vopt, SC_SpinBoxUp, widget).width() : 0; 6217 sz += QSize(buttonWidth, 0); 6218 } 6219 break; 6220#endif 6221 case QStyle::CT_TabWidget: 6222 // the size between the pane and the "contentsRect" (+4,+4) 6223 // (the "contentsRect" is on the inside of the pane) 6224 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); 6225 /** 6226 This is supposed to show the relationship between the tabBar and 6227 the stack widget of a QTabWidget. 6228 Unfortunately ascii is not a good way of representing graphics..... 6229 PS: The '=' line is the painted frame. 6230 6231 top ---+ 6232 | 6233 | 6234 | 6235 | vvv just outside the painted frame is the "pane" 6236 - -|- - - - - - - - - - <-+ 6237 TAB BAR +=====^============ | +2 pixels 6238 - - -|- - -|- - - - - - - <-+ 6239 | | ^ ^^^ just inside the painted frame is the "contentsRect" 6240 | | | 6241 | overlap | 6242 | | | 6243 bottom ------+ <-+ +14 pixels 6244 | 6245 v 6246 ------------------------------ <- top of stack widget 6247 6248 6249 To summarize: 6250 * 2 is the distance between the pane and the contentsRect 6251 * The 14 and the 1's are the distance from the contentsRect to the stack widget. 6252 (same value as used in SE_TabWidgetTabContents) 6253 * overlap is how much the pane should overlap the tab bar 6254 */ 6255 // then add the size between the stackwidget and the "contentsRect" 6256#if QT_CONFIG(tabwidget) 6257 if (const QStyleOptionTabWidgetFrame *twf 6258 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { 6259 QSize extra(0,0); 6260 const int overlap = pixelMetric(PM_TabBarBaseOverlap, opt, widget); 6261 const int gapBetweenTabbarAndStackWidget = 2 + 14 - overlap; 6262 6263 const auto tabDirection = QMacStylePrivate::tabDirection(twf->shape); 6264 if (tabDirection == QMacStylePrivate::North 6265 || tabDirection == QMacStylePrivate::South) { 6266 extra = QSize(2, gapBetweenTabbarAndStackWidget + 1); 6267 } else { 6268 extra = QSize(gapBetweenTabbarAndStackWidget + 1, 2); 6269 } 6270 sz+= extra; 6271 } 6272#endif 6273 break; 6274#if QT_CONFIG(tabbar) 6275 case QStyle::CT_TabBarTab: 6276 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { 6277 const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont)) 6278 || !QApplication::desktopSettingsAware(); 6279 const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape); 6280 const bool verticalTabs = tabDirection == QMacStylePrivate::East 6281 || tabDirection == QMacStylePrivate::West; 6282 if (verticalTabs) 6283 sz = sz.transposed(); 6284 6285 int defaultTabHeight; 6286 const auto cs = d->effectiveAquaSizeConstrain(opt, widget); 6287 switch (cs) { 6288 case QStyleHelper::SizeLarge: 6289 if (tab->documentMode) 6290 defaultTabHeight = 24; 6291 else 6292 defaultTabHeight = 21; 6293 break; 6294 case QStyleHelper::SizeSmall: 6295 defaultTabHeight = 18; 6296 break; 6297 case QStyleHelper::SizeMini: 6298 defaultTabHeight = 16; 6299 break; 6300 default: 6301 break; 6302 } 6303 6304 const bool widthSet = !differentFont && tab->icon.isNull(); 6305 if (widthSet) { 6306 const auto textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, tab->text); 6307 sz.rwidth() = textSize.width(); 6308 sz.rheight() = qMax(defaultTabHeight, textSize.height()); 6309 } else { 6310 sz.rheight() = qMax(defaultTabHeight, sz.height()); 6311 } 6312 sz.rwidth() += proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget); 6313 6314 if (verticalTabs) 6315 sz = sz.transposed(); 6316 6317 int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height()); 6318 int maxWidgetWidth = qMax(tab->leftButtonSize.width(), tab->rightButtonSize.width()); 6319 6320 int widgetWidth = 0; 6321 int widgetHeight = 0; 6322 int padding = 0; 6323 if (tab->leftButtonSize.isValid()) { 6324 padding += 8; 6325 widgetWidth += tab->leftButtonSize.width(); 6326 widgetHeight += tab->leftButtonSize.height(); 6327 } 6328 if (tab->rightButtonSize.isValid()) { 6329 padding += 8; 6330 widgetWidth += tab->rightButtonSize.width(); 6331 widgetHeight += tab->rightButtonSize.height(); 6332 } 6333 6334 if (verticalTabs) { 6335 sz.setWidth(qMax(sz.width(), maxWidgetWidth)); 6336 sz.setHeight(sz.height() + widgetHeight + padding); 6337 } else { 6338 if (widthSet) 6339 sz.setWidth(sz.width() + widgetWidth + padding); 6340 sz.setHeight(qMax(sz.height(), maxWidgetHeight)); 6341 } 6342 } 6343 break; 6344#endif 6345 case QStyle::CT_PushButton: { 6346 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) 6347 if (btn->features & QStyleOptionButton::CommandLinkButton) 6348 return QCommonStyle::sizeFromContents(ct, opt, sz, widget); 6349 6350 // By default, we fit the contents inside a normal rounded push button. 6351 // Do this by add enough space around the contents so that rounded 6352 // borders (including highlighting when active) will show. 6353 // TODO Use QFocusFrame and get rid of these horrors. 6354 QSize macsz; 6355 const auto controlSize = d->effectiveAquaSizeConstrain(opt, widget, CT_PushButton, sz, &macsz); 6356 // FIXME See comment in CT_PushButton case in qt_aqua_get_known_size(). 6357 if (macsz.width() != -1) 6358 sz.setWidth(macsz.width()); 6359 else 6360 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12; 6361 // All values as measured from HIThemeGetButtonBackgroundBounds() 6362 if (controlSize != QStyleHelper::SizeMini) 6363 sz.rwidth() += 12; // We like 12 over here. 6364 if (controlSize == QStyleHelper::SizeLarge && sz.height() > 16) 6365 sz.rheight() += pushButtonDefaultHeight[QStyleHelper::SizeLarge] - 16; 6366 else if (controlSize == QStyleHelper::SizeMini) 6367 sz.setHeight(24); // FIXME Our previous HITheme-based logic returned this. 6368 else 6369 sz.setHeight(pushButtonDefaultHeight[controlSize]); 6370 break; 6371 } 6372 case QStyle::CT_MenuItem: 6373 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { 6374 int maxpmw = mi->maxIconWidth; 6375#if QT_CONFIG(combobox) 6376 const QComboBox *comboBox = qobject_cast<const QComboBox *>(widget); 6377#endif 6378 int w = sz.width(), 6379 h = sz.height(); 6380 if (mi->menuItemType == QStyleOptionMenuItem::Separator) { 6381 w = 10; 6382 h = qt_mac_aqua_get_metric(MenuSeparatorHeight); 6383 } else { 6384 h = mi->fontMetrics.height() + 2; 6385 if (!mi->icon.isNull()) { 6386#if QT_CONFIG(combobox) 6387 if (comboBox) { 6388 const QSize &iconSize = comboBox->iconSize(); 6389 h = qMax(h, iconSize.height() + 4); 6390 maxpmw = qMax(maxpmw, iconSize.width()); 6391 } else 6392#endif 6393 { 6394 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); 6395 h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4); 6396 } 6397 } 6398 } 6399 if (mi->text.contains(QLatin1Char('\t'))) 6400 w += 12; 6401 else if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) 6402 w += 35; // Not quite exactly as it seems to depend on other factors 6403 if (maxpmw) 6404 w += maxpmw + 6; 6405 // add space for a check. All items have place for a check too. 6406 w += 20; 6407#if QT_CONFIG(combobox) 6408 if (comboBox && comboBox->isVisible()) { 6409 QStyleOptionComboBox cmb; 6410 cmb.initFrom(comboBox); 6411 cmb.editable = false; 6412 cmb.subControls = QStyle::SC_ComboBoxEditField; 6413 cmb.activeSubControls = QStyle::SC_None; 6414 w = qMax(w, subControlRect(QStyle::CC_ComboBox, &cmb, 6415 QStyle::SC_ComboBoxEditField, 6416 comboBox).width()); 6417 } else 6418#endif 6419 { 6420 w += 12; 6421 } 6422 sz = QSize(w, h); 6423 } 6424 break; 6425 case CT_MenuBarItem: 6426 if (!sz.isEmpty()) 6427 sz += QSize(12, 4); // Constants from QWindowsStyle 6428 break; 6429 case CT_ToolButton: 6430 sz.rwidth() += 10; 6431 sz.rheight() += 10; 6432 if (const auto *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) 6433 if (tb->features & QStyleOptionToolButton::Menu) 6434 sz.rwidth() += toolButtonArrowMargin; 6435 return sz; 6436 case CT_ComboBox: 6437 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { 6438 const auto controlSize = d->effectiveAquaSizeConstrain(opt, widget); 6439 if (!cb->editable) { 6440 // Same as CT_PushButton, because we have to fit the focus 6441 // ring and a non-editable combo box is a NSPopUpButton. 6442 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12; 6443 // All values as measured from HIThemeGetButtonBackgroundBounds() 6444 if (controlSize != QStyleHelper::SizeMini) 6445 sz.rwidth() += 12; // We like 12 over here. 6446#if 0 6447 // TODO Maybe support square combo boxes 6448 if (controlSize == QStyleHelper::SizeLarge && sz.height() > 16) 6449 sz.rheight() += popupButtonDefaultHeight[QStyleHelper::SizeLarge] - 16; 6450 else 6451#endif 6452 } else { 6453 sz.rwidth() += 50; // FIXME Double check this 6454 } 6455 6456 // This should be enough to fit the focus ring 6457 if (controlSize == QStyleHelper::SizeMini) 6458 sz.setHeight(24); // FIXME Our previous HITheme-based logic returned this for CT_PushButton. 6459 else 6460 sz.setHeight(pushButtonDefaultHeight[controlSize]); 6461 6462 return sz; 6463 } 6464 break; 6465 case CT_Menu: { 6466 if (proxy() == this) { 6467 sz = csz; 6468 } else { 6469 QStyleHintReturnMask menuMask; 6470 QStyleOption myOption = *opt; 6471 myOption.rect.setSize(sz); 6472 if (proxy()->styleHint(SH_Menu_Mask, &myOption, widget, &menuMask)) 6473 sz = menuMask.region.boundingRect().size(); 6474 } 6475 break; } 6476 case CT_HeaderSection:{ 6477 const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt); 6478 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); 6479 if (header->text.contains(QLatin1Char('\n'))) 6480 useAquaGuideline = false; 6481 break; } 6482 case CT_ScrollBar : 6483 // Make sure that the scroll bar is large enough to display the thumb indicator. 6484 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { 6485 const int minimumSize = 24; // Smallest knob size, but Cocoa doesn't seem to care 6486 if (slider->orientation == Qt::Horizontal) 6487 sz = sz.expandedTo(QSize(minimumSize, sz.height())); 6488 else 6489 sz = sz.expandedTo(QSize(sz.width(), minimumSize)); 6490 } 6491 break; 6492#if QT_CONFIG(itemviews) 6493 case CT_ItemViewItem: 6494 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { 6495 sz = QCommonStyle::sizeFromContents(ct, vopt, csz, widget); 6496 sz.setHeight(sz.height() + 2); 6497 } 6498 break; 6499#endif 6500 6501 default: 6502 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); 6503 } 6504 6505 if (useAquaGuideline && ct != CT_PushButton) { 6506 // TODO Probably going away at some point 6507 QSize macsz; 6508 if (d->aquaSizeConstrain(opt, widget, ct, sz, &macsz) != QStyleHelper::SizeDefault) { 6509 if (macsz.width() != -1) 6510 sz.setWidth(macsz.width()); 6511 if (macsz.height() != -1) 6512 sz.setHeight(macsz.height()); 6513 } 6514 } 6515 6516 // The sizes that Carbon and the guidelines gives us excludes the focus frame. 6517 // We compensate for this by adding some extra space here to make room for the frame when drawing: 6518 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){ 6519 if (combo->editable) { 6520 const auto widgetSize = d->aquaSizeConstrain(opt, widget); 6521 QMacStylePrivate::CocoaControl cw; 6522 cw.type = combo->editable ? QMacStylePrivate::ComboBox : QMacStylePrivate::Button_PopupButton; 6523 cw.size = widgetSize; 6524 const CGRect diffRect = QMacStylePrivate::comboboxInnerBounds(CGRectZero, cw); 6525 sz.rwidth() -= qRound(diffRect.size.width); 6526 sz.rheight() -= qRound(diffRect.size.height); 6527 } 6528 } 6529 return sz; 6530} 6531 6532void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, 6533 bool enabled, const QString &text, QPalette::ColorRole textRole) const 6534{ 6535 if(flags & Qt::TextShowMnemonic) 6536 flags |= Qt::TextHideMnemonic; 6537 QCommonStyle::drawItemText(p, r, flags, pal, enabled, text, textRole); 6538} 6539 6540bool QMacStyle::event(QEvent *e) 6541{ 6542 Q_D(QMacStyle); 6543 if(e->type() == QEvent::FocusIn) { 6544 QWidget *f = 0; 6545 QWidget *focusWidget = QApplication::focusWidget(); 6546#if QT_CONFIG(graphicsview) 6547 if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) { 6548 QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : 0; 6549 if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) { 6550 QGraphicsProxyWidget *proxy = static_cast<QGraphicsProxyWidget *>(focusItem); 6551 if (proxy->widget()) 6552 focusWidget = proxy->widget()->focusWidget(); 6553 } 6554 } 6555#endif 6556 6557 if (focusWidget && focusWidget->testAttribute(Qt::WA_MacShowFocusRect)) { 6558#if QT_CONFIG(spinbox) 6559 if (const auto sb = qobject_cast<QAbstractSpinBox *>(focusWidget)) 6560 f = sb->property("_q_spinbox_lineedit").value<QWidget *>(); 6561 else 6562#endif 6563 f = focusWidget; 6564 } 6565 if (f) { 6566 if(!d->focusWidget) 6567 d->focusWidget = new QFocusFrame(f); 6568 d->focusWidget->setWidget(f); 6569 } else if(d->focusWidget) { 6570 d->focusWidget->setWidget(0); 6571 } 6572 } else if(e->type() == QEvent::FocusOut) { 6573 if(d->focusWidget) 6574 d->focusWidget->setWidget(0); 6575 } 6576 return false; 6577} 6578 6579QIcon QMacStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *opt, 6580 const QWidget *widget) const 6581{ 6582 switch (standardIcon) { 6583 default: 6584 return QCommonStyle::standardIcon(standardIcon, opt, widget); 6585 case SP_ToolBarHorizontalExtensionButton: 6586 case SP_ToolBarVerticalExtensionButton: { 6587 QPixmap pixmap(QLatin1String(":/qt-project.org/styles/macstyle/images/toolbar-ext.png")); 6588 if (standardIcon == SP_ToolBarVerticalExtensionButton) { 6589 QPixmap pix2(pixmap.height(), pixmap.width()); 6590 pix2.setDevicePixelRatio(pixmap.devicePixelRatio()); 6591 pix2.fill(Qt::transparent); 6592 QPainter p(&pix2); 6593 p.translate(pix2.width(), 0); 6594 p.rotate(90); 6595 p.drawPixmap(0, 0, pixmap); 6596 return pix2; 6597 } 6598 return pixmap; 6599 } 6600 } 6601} 6602 6603int QMacStyle::layoutSpacing(QSizePolicy::ControlType control1, 6604 QSizePolicy::ControlType control2, 6605 Qt::Orientation orientation, 6606 const QStyleOption *option, 6607 const QWidget *widget) const 6608{ 6609 const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; 6610 const int controlSize = getControlSize(option, widget); 6611 6612 if (control2 == QSizePolicy::ButtonBox) { 6613 /* 6614 AHIG seems to prefer a 12-pixel margin between group 6615 boxes and the row of buttons. The 20 pixel comes from 6616 Builder. 6617 */ 6618 if (control1 & (QSizePolicy::Frame // guess 6619 | QSizePolicy::GroupBox // (AHIG, guess, guess) 6620 | QSizePolicy::TabWidget // guess 6621 | ButtonMask)) { // AHIG 6622 return_SIZE(14, 8, 8); 6623 } else if (control1 == QSizePolicy::LineEdit) { 6624 return_SIZE(8, 8, 8); // Interface Builder 6625 } else { 6626 return_SIZE(20, 7, 7); // Interface Builder 6627 } 6628 } 6629 6630 if ((control1 | control2) & ButtonMask) { 6631 if (control1 == QSizePolicy::LineEdit) 6632 return_SIZE(8, 8, 8); // Interface Builder 6633 else if (control2 == QSizePolicy::LineEdit) { 6634 if (orientation == Qt::Vertical) 6635 return_SIZE(20, 7, 7); // Interface Builder 6636 else 6637 return_SIZE(20, 8, 8); 6638 } 6639 return_SIZE(14, 8, 8); // Interface Builder 6640 } 6641 6642 switch (CT2(control1, control2)) { 6643 case CT1(QSizePolicy::Label): // guess 6644 case CT2(QSizePolicy::Label, QSizePolicy::DefaultType): // guess 6645 case CT2(QSizePolicy::Label, QSizePolicy::CheckBox): // AHIG 6646 case CT2(QSizePolicy::Label, QSizePolicy::ComboBox): // AHIG 6647 case CT2(QSizePolicy::Label, QSizePolicy::LineEdit): // guess 6648 case CT2(QSizePolicy::Label, QSizePolicy::RadioButton): // AHIG 6649 case CT2(QSizePolicy::Label, QSizePolicy::Slider): // guess 6650 case CT2(QSizePolicy::Label, QSizePolicy::SpinBox): // guess 6651 case CT2(QSizePolicy::Label, QSizePolicy::ToolButton): // guess 6652 return_SIZE(8, 6, 5); 6653 case CT1(QSizePolicy::ToolButton): 6654 return 8; // AHIG 6655 case CT1(QSizePolicy::CheckBox): 6656 case CT2(QSizePolicy::CheckBox, QSizePolicy::RadioButton): 6657 case CT2(QSizePolicy::RadioButton, QSizePolicy::CheckBox): 6658 if (orientation == Qt::Vertical) 6659 return_SIZE(8, 8, 7); // AHIG and Builder 6660 break; 6661 case CT1(QSizePolicy::RadioButton): 6662 if (orientation == Qt::Vertical) 6663 return 5; // (Builder, guess, AHIG) 6664 } 6665 6666 if (orientation == Qt::Horizontal 6667 && (control2 & (QSizePolicy::CheckBox | QSizePolicy::RadioButton))) 6668 return_SIZE(12, 10, 8); // guess 6669 6670 if ((control1 | control2) & (QSizePolicy::Frame 6671 | QSizePolicy::GroupBox 6672 | QSizePolicy::TabWidget)) { 6673 /* 6674 These values were chosen so that nested container widgets 6675 look good side by side. Builder uses 8, which looks way 6676 too small, and AHIG doesn't say anything. 6677 */ 6678 return_SIZE(16, 10, 10); // guess 6679 } 6680 6681 if ((control1 | control2) & (QSizePolicy::Line | QSizePolicy::Slider)) 6682 return_SIZE(12, 10, 8); // AHIG 6683 6684 if ((control1 | control2) & QSizePolicy::LineEdit) 6685 return_SIZE(10, 8, 8); // AHIG 6686 6687 /* 6688 AHIG and Builder differ by up to 4 pixels for stacked editable 6689 comboboxes. We use some values that work fairly well in all 6690 cases. 6691 */ 6692 if ((control1 | control2) & QSizePolicy::ComboBox) 6693 return_SIZE(10, 8, 7); // guess 6694 6695 /* 6696 Builder defaults to 8, 6, 5 in lots of cases, but most of the time the 6697 result looks too cramped. 6698 */ 6699 return_SIZE(10, 8, 6); // guess 6700} 6701 6702QT_END_NAMESPACE 6703