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