Warning: That file was not part of the compilation database. It may have many parsing errors.

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/private/qcoregraphics_p.h>
60#include <QtGui/qpa/qplatformfontdatabase.h>
61#include <QtGui/qpa/qplatformtheme.h>
62
63#include <QtWidgets/private/qstyleanimation_p.h>
64
65#if QT_CONFIG(mdiarea)
66#include <QtWidgets/qmdisubwindow.h>
67#endif
68#if QT_CONFIG(scrollbar)
69#include <QtWidgets/qscrollbar.h>
70#endif
71#if QT_CONFIG(tabbar)
72#include <QtWidgets/private/qtabbar_p.h>
73#endif
74#if QT_CONFIG(wizard)
75#include <QtWidgets/qwizard.h>
76#endif
77
78#include <cmath>
79
80QT_USE_NAMESPACE
81
82static QWindow *qt_getWindow(const QWidget *widget)
83{
84 return widget ? widget->window()->windowHandle() : 0;
85}
86
87@interface QT_MANGLE_NAMESPACE(QIndeterminateProgressIndicator) : NSProgressIndicator
88
89@property (readonly, nonatomic) NSInteger animators;
90
91- (instancetype)init;
92
93- (void)startAnimation;
94- (void)stopAnimation;
95
96- (void)drawWithFrame:(CGRect)rect inView:(NSView *)view;
97
98@end
99
100QT_NAMESPACE_ALIAS_OBJC_CLASS(QIndeterminateProgressIndicator);
101
102@implementation QIndeterminateProgressIndicator
103
104- (instancetype)init
105{
106 if ((self = [super init])) {
107 _animators = 0;
108 self.indeterminate = YES;
109 self.usesThreadedAnimation = NO;
110 self.alphaValue = 0.0;
111 }
112
113 return self;
114}
115
116- (void)startAnimation
117{
118 if (_animators == 0) {
119 self.hidden = NO;
120 [super startAnimation:self];
121 }
122 ++_animators;
123}
124
125- (void)stopAnimation
126{
127 --_animators;
128 if (_animators == 0) {
129 [super stopAnimation:self];
130 self.hidden = YES;
131 [self removeFromSuperviewWithoutNeedingDisplay];
132 }
133}
134
135- (void)drawWithFrame:(CGRect)rect inView:(NSView *)view
136{
137 // The alphaValue change is not strictly necessary, but feels safer.
138 self.alphaValue = 1.0;
139 if (self.superview != view)
140 [view addSubview:self];
141 if (!CGRectEqualToRect(self.frame, rect))
142 self.frame = rect;
143 [self drawRect:rect];
144 self.alphaValue = 0.0;
145}
146
147@end
148
149@interface QT_MANGLE_NAMESPACE(QVerticalSplitView) : NSSplitView
150- (BOOL)isVertical;
151@end
152
153QT_NAMESPACE_ALIAS_OBJC_CLASS(QVerticalSplitView);
154
155@implementation QVerticalSplitView
156- (BOOL)isVertical
157{
158 return YES;
159}
160@end
161
162// See render code in drawPrimitive(PE_FrameTabWidget)
163@interface QT_MANGLE_NAMESPACE(QDarkNSBox) : NSBox
164@end
165
166QT_NAMESPACE_ALIAS_OBJC_CLASS(QDarkNSBox);
167
168@implementation QDarkNSBox
169- (instancetype)init
170{
171 if ((self = [super init])) {
172 self.title = @"";
173 self.titlePosition = NSNoTitle;
174 self.boxType = NSBoxCustom;
175 self.cornerRadius = 3;
176 self.borderColor = [NSColor.controlColor colorWithAlphaComponent:0.1];
177 self.fillColor = [NSColor.darkGrayColor colorWithAlphaComponent:0.2];
178 }
179
180 return self;
181}
182
183- (void)drawRect:(NSRect)rect
184{
185 [super drawRect:rect];
186}
187@end
188
189QT_BEGIN_NAMESPACE
190
191// The following constants are used for adjusting the size
192// of push buttons so that they are drawn inside their bounds.
193const int QMacStylePrivate::PushButtonLeftOffset = 6;
194const int QMacStylePrivate::PushButtonRightOffset = 12;
195const int QMacStylePrivate::PushButtonContentPadding = 6;
196
197QVector<QPointer<QObject> > QMacStylePrivate::scrollBars;
198
199// Title bar gradient colors for Lion were determined by inspecting PSDs exported
200// using CoreUI's CoreThemeDocument; there is no public API to retrieve them
201
202static QLinearGradient titlebarGradientActive()
203{
204 static QLinearGradient darkGradient = [](){
205 QLinearGradient gradient;
206 // FIXME: colors are chosen somewhat arbitrarily and could be fine-tuned,
207 // or ideally determined by calling a native API.
208 gradient.setColorAt(0, QColor(47, 47, 47));
209 return gradient;
210 }();
211 static QLinearGradient lightGradient = [](){
212 QLinearGradient gradient;
213 gradient.setColorAt(0, QColor(235, 235, 235));
214 gradient.setColorAt(0.5, QColor(210, 210, 210));
215 gradient.setColorAt(0.75, QColor(195, 195, 195));
216 gradient.setColorAt(1, QColor(180, 180, 180));
217 return gradient;
218 }();
219 return qt_mac_applicationIsInDarkMode() ? darkGradient : lightGradient;
220}
221
222static QLinearGradient titlebarGradientInactive()
223{
224 static QLinearGradient darkGradient = [](){
225 QLinearGradient gradient;
226 gradient.setColorAt(1, QColor(42, 42, 42));
227 return gradient;
228 }();
229 static QLinearGradient lightGradient = [](){
230 QLinearGradient gradient;
231 gradient.setColorAt(0, QColor(250, 250, 250));
232 gradient.setColorAt(1, QColor(225, 225, 225));
233 return gradient;
234 }();
235 return qt_mac_applicationIsInDarkMode() ? darkGradient : lightGradient;
236}
237
238#if QT_CONFIG(tabwidget)
239static void clipTabBarFrame(const QStyleOption *option, const QMacStyle *style, CGContextRef ctx)
240{
241 Q_ASSERT(option);
242 Q_ASSERT(style);
243 Q_ASSERT(ctx);
244
245 if (qt_mac_applicationIsInDarkMode()) {
246 QTabWidget *tabWidget = qobject_cast<QTabWidget *>(option->styleObject);
247 Q_ASSERT(tabWidget);
248
249 const QRect tabBarRect = style->subElementRect(QStyle::SE_TabWidgetTabBar, option, tabWidget).adjusted(2, 2, -3, -2);
250 const QRegion clipPath = QRegion(option->rect) - tabBarRect;
251 QVarLengthArray<CGRect, 3> cgRects;
252 for (const QRect &qtRect : clipPath)
253 cgRects.push_back(qtRect.toCGRect());
254 if (cgRects.size())
255 CGContextClipToRects(ctx, &cgRects[0], size_t(cgRects.size()));
256 }
257}
258#endif
259
260static const QColor titlebarSeparatorLineActive(111, 111, 111);
261static const QColor titlebarSeparatorLineInactive(131, 131, 131);
262static const QColor darkModeSeparatorLine(88, 88, 88);
263
264// Gradient colors used for the dock widget title bar and
265// non-unifed tool bar bacground.
266static const QColor lightMainWindowGradientBegin(240, 240, 240);
267static const QColor lightMainWindowGradientEnd(200, 200, 200);
268static const QColor darkMainWindowGradientBegin(47, 47, 47);
269static const QColor darkMainWindowGradientEnd(47, 47, 47);
270
271static const int DisclosureOffset = 4;
272
273static const qreal titleBarIconTitleSpacing = 5;
274static const qreal titleBarTitleRightMargin = 12;
275static const qreal titleBarButtonSpacing = 8;
276
277// Tab bar colors
278// active: window is active
279// selected: tab is selected
280// hovered: tab is hovered
281bool isDarkMode() { return qt_mac_applicationIsInDarkMode(); }
282
283#if QT_CONFIG(tabbar)
284static const QColor lightTabBarTabBackgroundActive(190, 190, 190);
285static const QColor darkTabBarTabBackgroundActive(38, 38, 38);
286static const QColor tabBarTabBackgroundActive() { return isDarkMode() ? darkTabBarTabBackgroundActive : lightTabBarTabBackgroundActive; }
287
288static const QColor lightTabBarTabBackgroundActiveHovered(178, 178, 178);
289static const QColor darkTabBarTabBackgroundActiveHovered(32, 32, 32);
290static const QColor tabBarTabBackgroundActiveHovered() { return isDarkMode() ? darkTabBarTabBackgroundActiveHovered : lightTabBarTabBackgroundActiveHovered; }
291
292static const QColor lightTabBarTabBackgroundActiveSelected(211, 211, 211);
293static const QColor darkTabBarTabBackgroundActiveSelected(52, 52, 52);
294static const QColor tabBarTabBackgroundActiveSelected() { return isDarkMode() ? darkTabBarTabBackgroundActiveSelected : lightTabBarTabBackgroundActiveSelected; }
295
296static const QColor lightTabBarTabBackground(227, 227, 227);
297static const QColor darkTabBarTabBackground(38, 38, 38);
298static const QColor tabBarTabBackground() { return isDarkMode() ? darkTabBarTabBackground : lightTabBarTabBackground; }
299
300static const QColor lightTabBarTabBackgroundSelected(246, 246, 246);
301static const QColor darkTabBarTabBackgroundSelected(52, 52, 52);
302static const QColor tabBarTabBackgroundSelected() { return isDarkMode() ? darkTabBarTabBackgroundSelected : lightTabBarTabBackgroundSelected; }
303
304static const QColor lightTabBarTabLineActive(160, 160, 160);
305static const QColor darkTabBarTabLineActive(90, 90, 90);
306static const QColor tabBarTabLineActive() { return isDarkMode() ? darkTabBarTabLineActive : lightTabBarTabLineActive; }
307
308static const QColor lightTabBarTabLineActiveHovered(150, 150, 150);
309static const QColor darkTabBarTabLineActiveHovered(90, 90, 90);
310static const QColor tabBarTabLineActiveHovered() { return isDarkMode() ? darkTabBarTabLineActiveHovered : lightTabBarTabLineActiveHovered; }
311
312static const QColor lightTabBarTabLine(210, 210, 210);
313static const QColor darkTabBarTabLine(90, 90, 90);
314static const QColor tabBarTabLine() { return isDarkMode() ? darkTabBarTabLine : lightTabBarTabLine; }
315
316static const QColor lightTabBarTabLineSelected(189, 189, 189);
317static const QColor darkTabBarTabLineSelected(90, 90, 90);
318static const QColor tabBarTabLineSelected() { return isDarkMode() ? darkTabBarTabLineSelected : lightTabBarTabLineSelected; }
319
320static const QColor tabBarCloseButtonBackgroundHovered(162, 162, 162);
321static const QColor tabBarCloseButtonBackgroundPressed(153, 153, 153);
322static const QColor tabBarCloseButtonBackgroundSelectedHovered(192, 192, 192);
323static const QColor tabBarCloseButtonBackgroundSelectedPressed(181, 181, 181);
324static const QColor tabBarCloseButtonCross(100, 100, 100);
325static const QColor tabBarCloseButtonCrossSelected(115, 115, 115);
326
327static const int closeButtonSize = 14;
328static const qreal closeButtonCornerRadius = 2.0;
329#endif // QT_CONFIG(tabbar)
330
331static const int headerSectionArrowHeight = 6;
332static const int headerSectionSeparatorInset = 2;
333
334// One for each of QStyleHelper::WidgetSizePolicy
335static const QMarginsF comboBoxFocusRingMargins[3] = {
336 { 0.5, 2, 3.5, 4 },
337 { 0.5, 1, 2.5, 4 },
338 { 0.5, 1.5, 2.5, 3.5 }
339};
340
341static const QMarginsF pullDownButtonShadowMargins[3] = {
342 { 0.5, -1, 0.5, 2 },
343 { 0.5, -1.5, 0.5, 2.5 },
344 { 0.5, 0, 0.5, 1 }
345};
346
347static const QMarginsF pushButtonShadowMargins[3] = {
348 { 1.5, -1.5, 1.5, 4.5 },
349 { 1.5, -1, 1.5, 4 },
350 { 1.5, 0.5, 1.5, 2.5 }
351};
352
353// These are frame heights as reported by Xcode 9's Interface Builder.
354// Alignemnet rectangle's heights match for push and popup buttons
355// with respective values 21, 18 and 15.
356
357static const qreal comboBoxDefaultHeight[3] = {
358 26, 22, 19
359};
360
361static const qreal pushButtonDefaultHeight[3] = {
362 32, 28, 16
363};
364
365static const qreal popupButtonDefaultHeight[3] = {
366 26, 22, 15
367};
368
369static const int toolButtonArrowSize = 7;
370static const int toolButtonArrowMargin = 2;
371
372static const qreal focusRingWidth = 3.5;
373
374// An application can force 'Aqua' theme while the system theme is one of
375// the 'Dark' variants. Since in Qt we sometimes use NSControls and even
376// NSCells directly without attaching them to any view hierarchy, we have
377// to set NSAppearance.currentAppearance to 'Aqua' manually, to make sure
378// the correct rendering path is triggered. Apple recommends us to un-set
379// the current appearance back after we finished with drawing. This is what
380// AppearanceSync is for.
381
382class AppearanceSync {
383public:
384 AppearanceSync()
385 {
386#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
387 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave
388 && !qt_mac_applicationIsInDarkMode()) {
389 auto requiredAppearanceName = NSApplication.sharedApplication.effectiveAppearance.name;
390 if (![NSAppearance.currentAppearance.name isEqualToString:requiredAppearanceName]) {
391 previous = NSAppearance.currentAppearance;
392 NSAppearance.currentAppearance = [NSAppearance appearanceNamed:requiredAppearanceName];
393 }
394 }
395#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
396 }
397
398 ~AppearanceSync()
399 {
400 if (previous)
401 NSAppearance.currentAppearance = previous;
402 }
403
404private:
405 NSAppearance *previous = nil;
406
407 Q_DISABLE_COPY(AppearanceSync)
408};
409
410static bool setupScroller(NSScroller *scroller, const QStyleOptionSlider *sb)
411{
412 const qreal length = sb->maximum - sb->minimum + sb->pageStep;
413 if (qFuzzyIsNull(length))
414 return false;
415 const qreal proportion = sb->pageStep / length;
416 const qreal range = qreal(sb->maximum - sb->minimum);
417 qreal value = range ? qreal(sb->sliderValue - sb->minimum) / range : 0;
418 if (sb->orientation == Qt::Horizontal && sb->direction == Qt::RightToLeft)
419 value = 1.0 - value;
420
421 scroller.frame = sb->rect.toCGRect();
422 scroller.floatValue = value;
423 scroller.knobProportion = proportion;
424 return true;
425}
426
427static bool setupSlider(NSSlider *slider, const QStyleOptionSlider *sl)
428{
429 if (sl->minimum >= sl->maximum)
430 return false;
431
432 slider.frame = sl->rect.toCGRect();
433 slider.minValue = sl->minimum;
434 slider.maxValue = sl->maximum;
435 slider.intValue = sl->sliderPosition;
436 slider.enabled = sl->state & QStyle::State_Enabled;
437 if (sl->tickPosition != QSlider::NoTicks) {
438 // Set numberOfTickMarks, but TicksBothSides will be treated differently
439 int interval = sl->tickInterval;
440 if (interval == 0) {
441 interval = sl->pageStep;
442 if (interval == 0)
443 interval = sl->singleStep;
444 if (interval == 0)
445 interval = 1; // return false?
446 }
447 slider.numberOfTickMarks = 1 + ((sl->maximum - sl->minimum) / interval);
448
449 const bool ticksAbove = sl->tickPosition == QSlider::TicksAbove;
450 if (sl->orientation == Qt::Horizontal)
451 slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionAbove : NSTickMarkPositionBelow;
452 else
453 slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionLeading : NSTickMarkPositionTrailing;
454 } else {
455 slider.numberOfTickMarks = 0;
456 }
457
458 return true;
459}
460
461static void fixStaleGeometry(NSSlider *slider)
462{
463 // If it's later fixed in AppKit, this function is not needed.
464 // On macOS Mojave we suddenly have NSSliderCell with a cached
465 // (and stale) geometry, thus its -drawKnob, -drawBarInside:flipped:,
466 // -drawTickMarks fail to render the slider properly. Setting the number
467 // of tickmarks triggers an update in geometry.
468
469 Q_ASSERT(slider);
470
471 if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave)
472 return;
473
474 NSSliderCell *cell = slider.cell;
475 const NSRect barRect = [cell barRectFlipped:NO];
476 const NSSize sliderSize = slider.frame.size;
477 CGFloat difference = 0.;
478 if (slider.vertical)
479 difference = std::abs(sliderSize.height - barRect.size.height);
480 else
481 difference = std::abs(sliderSize.width - barRect.size.width);
482
483 if (difference > 6.) {
484 // Stale ...
485 const auto nOfTicks = slider.numberOfTickMarks;
486 // Non-zero, different from nOfTicks to force update
487 slider.numberOfTickMarks = nOfTicks + 10;
488 slider.numberOfTickMarks = nOfTicks;
489 }
490}
491
492static bool isInMacUnifiedToolbarArea(QWindow *window, int windowY)
493{
494 QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
495 QPlatformNativeInterface::NativeResourceForIntegrationFunction function =
496 nativeInterface->nativeResourceFunctionForIntegration("testContentBorderPosition");
497 if (!function)
498 return false; // Not Cocoa platform plugin.
499
500 typedef bool (*TestContentBorderPositionFunction)(QWindow *, int);
501 return (reinterpret_cast<TestContentBorderPositionFunction>(function))(window, windowY);
502}
503
504
505#if QT_CONFIG(tabbar)
506static void drawTabCloseButton(QPainter *p, bool hover, bool selected, bool pressed, bool documentMode)
507{
508 p->setRenderHints(QPainter::Antialiasing);
509 QRect rect(0, 0, closeButtonSize, closeButtonSize);
510 const int width = rect.width();
511 const int height = rect.height();
512
513 if (hover) {
514 // draw background circle
515 QColor background;
516 if (selected) {
517 if (documentMode)
518 background = pressed ? tabBarCloseButtonBackgroundSelectedPressed : tabBarCloseButtonBackgroundSelectedHovered;
519 else
520 background = QColor(255, 255, 255, pressed ? 150 : 100); // Translucent white
521 } else {
522 background = pressed ? tabBarCloseButtonBackgroundPressed : tabBarCloseButtonBackgroundHovered;
523 if (!documentMode)
524 background = background.lighter(pressed ? 135 : 140); // Lighter tab background, lighter color
525 }
526
527 p->setPen(Qt::transparent);
528 p->setBrush(background);
529 p->drawRoundedRect(rect, closeButtonCornerRadius, closeButtonCornerRadius);
530 }
531
532 // draw cross
533 const int margin = 3;
534 QPen crossPen;
535 crossPen.setColor(selected ? (documentMode ? tabBarCloseButtonCrossSelected : Qt::white) : tabBarCloseButtonCross);
536 crossPen.setWidthF(1.1);
537 crossPen.setCapStyle(Qt::FlatCap);
538 p->setPen(crossPen);
539 p->drawLine(margin, margin, width - margin, height - margin);
540 p->drawLine(margin, height - margin, width - margin, margin);
541}
542
543QRect rotateTabPainter(QPainter *p, QTabBar::Shape shape, QRect tabRect)
544{
545 const auto tabDirection = QMacStylePrivate::tabDirection(shape);
546 if (QMacStylePrivate::verticalTabs(tabDirection)) {
547 int newX, newY, newRot;
548 if (tabDirection == QMacStylePrivate::East) {
549 newX = tabRect.width();
550 newY = tabRect.y();
551 newRot = 90;
552 } else {
553 newX = 0;
554 newY = tabRect.y() + tabRect.height();
555 newRot = -90;
556 }
557 tabRect.setRect(0, 0, tabRect.height(), tabRect.width());
558 QMatrix m;
559 m.translate(newX, newY);
560 m.rotate(newRot);
561 p->setMatrix(m, true);
562 }
563 return tabRect;
564}
565
566void drawTabShape(QPainter *p, const QStyleOptionTab *tabOpt, bool isUnified, int tabOverlap)
567{
568 QRect rect = tabOpt->rect;
569 if (QMacStylePrivate::verticalTabs(QMacStylePrivate::tabDirection(tabOpt->shape)))
570 rect = rect.adjusted(-tabOverlap, 0, 0, 0);
571 else
572 rect = rect.adjusted(0, -tabOverlap, 0, 0);
573
574 p->translate(rect.x(), rect.y());
575 rect.moveLeft(0);
576 rect.moveTop(0);
577 const QRect tabRect = rotateTabPainter(p, tabOpt->shape, rect);
578
579 const int width = tabRect.width();
580 const int height = tabRect.height();
581 const bool active = (tabOpt->state & QStyle::State_Active);
582 const bool selected = (tabOpt->state & QStyle::State_Selected);
583
584 const QRect bodyRect(1, 2, width - 2, height - 3);
585 const QRect topLineRect(1, 0, width - 2, 1);
586 const QRect bottomLineRect(1, height - 1, width - 2, 1);
587 if (selected) {
588 // fill body
589 if (tabOpt->documentMode && isUnified) {
590 p->save();
591 p->setCompositionMode(QPainter::CompositionMode_Source);
592 p->fillRect(tabRect, QColor(Qt::transparent));
593 p->restore();
594 } else if (active) {
595 p->fillRect(bodyRect, tabBarTabBackgroundActiveSelected());
596 // top line
597 p->fillRect(topLineRect, tabBarTabLineSelected());
598 } else {
599 p->fillRect(bodyRect, tabBarTabBackgroundSelected());
600 }
601 } else {
602 // when the mouse is over non selected tabs they get a new color
603 const bool hover = (tabOpt->state & QStyle::State_MouseOver);
604 if (hover) {
605 // fill body
606 p->fillRect(bodyRect, tabBarTabBackgroundActiveHovered());
607 // bottom line
608 p->fillRect(bottomLineRect, isDarkMode() ? QColor(Qt::black) : tabBarTabLineActiveHovered());
609 }
610 }
611
612 // separator lines between tabs
613 const QRect leftLineRect(0, 1, 1, height - 2);
614 const QRect rightLineRect(width - 1, 1, 1, height - 2);
615 const QColor separatorLineColor = active ? tabBarTabLineActive() : tabBarTabLine();
616 p->fillRect(leftLineRect, separatorLineColor);
617 p->fillRect(rightLineRect, separatorLineColor);
618}
619
620void drawTabBase(QPainter *p, const QStyleOptionTabBarBase *tbb, const QWidget *w)
621{
622 QRect r = tbb->rect;
623 if (QMacStylePrivate::verticalTabs(QMacStylePrivate::tabDirection(tbb->shape)))
624 r.setWidth(w->width());
625 else
626 r.setHeight(w->height());
627
628 const QRect tabRect = rotateTabPainter(p, tbb->shape, r);
629 const int width = tabRect.width();
630 const int height = tabRect.height();
631 const bool active = (tbb->state & QStyle::State_Active);
632
633 // fill body
634 const QRect bodyRect(0, 1, width, height - 1);
635 const QColor bodyColor = active ? tabBarTabBackgroundActive() : tabBarTabBackground();
636 p->fillRect(bodyRect, bodyColor);
637
638 // top line
639 const QRect topLineRect(0, 0, width, 1);
640 const QColor topLineColor = active ? tabBarTabLineActive() : tabBarTabLine();
641 p->fillRect(topLineRect, topLineColor);
642
643 // bottom line
644 const QRect bottomLineRect(0, height - 1, width, 1);
645 bool isDocument = false;
646 if (const QTabBar *tabBar = qobject_cast<const QTabBar*>(w))
647 isDocument = tabBar->documentMode();
648 const QColor bottomLineColor = isDocument && isDarkMode() ? QColor(Qt::black) : active ? tabBarTabLineActive() : tabBarTabLine();
649 p->fillRect(bottomLineRect, bottomLineColor);
650}
651#endif
652
653static QStyleHelper::WidgetSizePolicy getControlSize(const QStyleOption *option, const QWidget *widget)
654{
655 const auto wsp = QStyleHelper::widgetSizePolicy(widget, option);
656 if (wsp == QStyleHelper::SizeDefault)
657 return QStyleHelper::SizeLarge;
658
659 return wsp;
660}
661
662#if QT_CONFIG(treeview)
663static inline bool isTreeView(const QWidget *widget)
664{
665 return (widget && widget->parentWidget() &&
666 qobject_cast<const QTreeView *>(widget->parentWidget()));
667}
668#endif
669
670static QString qt_mac_removeMnemonics(const QString &original)
671{
672 QString returnText(original.size(), 0);
673 int finalDest = 0;
674 int currPos = 0;
675 int l = original.length();
676 while (l) {
677 if (original.at(currPos) == QLatin1Char('&')) {
678 ++currPos;
679 --l;
680 if (l == 0)
681 break;
682 } else if (original.at(currPos) == QLatin1Char('(') && l >= 4 &&
683 original.at(currPos + 1) == QLatin1Char('&') &&
684 original.at(currPos + 2) != QLatin1Char('&') &&
685 original.at(currPos + 3) == QLatin1Char(')')) {
686 /* remove mnemonics its format is "\s*(&X)" */
687 int n = 0;
688 while (finalDest > n && returnText.at(finalDest - n - 1).isSpace())
689 ++n;
690 finalDest -= n;
691 currPos += 4;
692 l -= 4;
693 continue;
694 }
695 returnText[finalDest] = original.at(currPos);
696 ++currPos;
697 ++finalDest;
698 --l;
699 }
700 returnText.truncate(finalDest);
701 return returnText;
702}
703
704static bool qt_macWindowMainWindow(const QWidget *window)
705{
706 if (QWindow *w = window->windowHandle()) {
707 if (w->handle()) {
708 if (NSWindow *nswindow = static_cast<NSWindow*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow(QByteArrayLiteral("nswindow"), w))) {
709 return [nswindow isMainWindow];
710 }
711 }
712 }
713 return false;
714}
715
716/*****************************************************************************
717 QMacCGStyle globals
718 *****************************************************************************/
719const int macItemFrame = 2; // menu item frame width
720const int macItemHMargin = 3; // menu item hor text margin
721const int macRightBorder = 12; // right border on mac
722
723/*****************************************************************************
724 QMacCGStyle utility functions
725 *****************************************************************************/
726
727enum QAquaMetric {
728 // Prepend kThemeMetric to get the HIToolBox constant.
729 // Represents the values already used in QMacStyle.
730 CheckBoxHeight = 0,
731 CheckBoxWidth,
732 EditTextFrameOutset,
733 FocusRectOutset,
734 HSliderHeight,
735 HSliderTickHeight,
736 LargeProgressBarThickness,
737 ListHeaderHeight,
738 MenuSeparatorHeight, // GetThemeMenuSeparatorHeight
739 MiniCheckBoxHeight,
740 MiniCheckBoxWidth,
741 MiniHSliderHeight,
742 MiniHSliderTickHeight,
743 MiniPopupButtonHeight,
744 MiniPushButtonHeight,
745 MiniRadioButtonHeight,
746 MiniRadioButtonWidth,
747 MiniVSliderTickWidth,
748 MiniVSliderWidth,
749 NormalProgressBarThickness,
750 PopupButtonHeight,
751 ProgressBarShadowOutset,
752 PushButtonHeight,
753 RadioButtonHeight,
754 RadioButtonWidth,
755 SeparatorSize,
756 SmallCheckBoxHeight,
757 SmallCheckBoxWidth,
758 SmallHSliderHeight,
759 SmallHSliderTickHeight,
760 SmallPopupButtonHeight,
761 SmallProgressBarShadowOutset,
762 SmallPushButtonHeight,
763 SmallRadioButtonHeight,
764 SmallRadioButtonWidth,
765 SmallVSliderTickWidth,
766 SmallVSliderWidth,
767 VSliderTickWidth,
768 VSliderWidth
769};
770
771static const int qt_mac_aqua_metrics[] = {
772 // Values as of macOS 10.12.4 and Xcode 8.3.1
773 18 /* CheckBoxHeight */,
774 18 /* CheckBoxWidth */,
775 1 /* EditTextFrameOutset */,
776 4 /* FocusRectOutset */,
777 22 /* HSliderHeight */,
778 5 /* HSliderTickHeight */,
779 16 /* LargeProgressBarThickness */,
780 17 /* ListHeaderHeight */,
781 12 /* MenuSeparatorHeight, aka GetThemeMenuSeparatorHeight */,
782 11 /* MiniCheckBoxHeight */,
783 10 /* MiniCheckBoxWidth */,
784 12 /* MiniHSliderHeight */,
785 4 /* MiniHSliderTickHeight */,
786 15 /* MiniPopupButtonHeight */,
787 16 /* MiniPushButtonHeight */,
788 11 /* MiniRadioButtonHeight */,
789 10 /* MiniRadioButtonWidth */,
790 4 /* MiniVSliderTickWidth */,
791 12 /* MiniVSliderWidth */,
792 12 /* NormalProgressBarThickness */,
793 20 /* PopupButtonHeight */,
794 4 /* ProgressBarShadowOutset */,
795 20 /* PushButtonHeight */,
796 18 /* RadioButtonHeight */,
797 18 /* RadioButtonWidth */,
798 1 /* SeparatorSize */,
799 16 /* SmallCheckBoxHeight */,
800 14 /* SmallCheckBoxWidth */,
801 15 /* SmallHSliderHeight */,
802 4 /* SmallHSliderTickHeight */,
803 17 /* SmallPopupButtonHeight */,
804 2 /* SmallProgressBarShadowOutset */,
805 17 /* SmallPushButtonHeight */,
806 15 /* SmallRadioButtonHeight */,
807 14 /* SmallRadioButtonWidth */,
808 4 /* SmallVSliderTickWidth */,
809 15 /* SmallVSliderWidth */,
810 5 /* VSliderTickWidth */,
811 22 /* VSliderWidth */
812};
813
814static inline int qt_mac_aqua_get_metric(QAquaMetric m)
815{
816 return qt_mac_aqua_metrics[m];
817}
818
819static QSize qt_aqua_get_known_size(QStyle::ContentsType ct, const QWidget *widg, QSize szHint,
820 QStyleHelper::WidgetSizePolicy sz)
821{
822 QSize ret(-1, -1);
823 if (sz != QStyleHelper::SizeSmall && sz != QStyleHelper::SizeLarge && sz != QStyleHelper::SizeMini) {
824 qDebug("Not sure how to return this...");
825 return ret;
826 }
827 if ((widg && widg->testAttribute(Qt::WA_SetFont)) || !QApplication::desktopSettingsAware()) {
828 // If you're using a custom font and it's bigger than the default font,
829 // then no constraints for you. If you are smaller, we can try to help you out
830 QFont font = qt_app_fonts_hash()->value(widg->metaObject()->className(), QFont());
831 if (widg->font().pointSize() > font.pointSize())
832 return ret;
833 }
834
835 if (ct == QStyle::CT_CustomBase && widg) {
836#if QT_CONFIG(pushbutton)
837 if (qobject_cast<const QPushButton *>(widg))
838 ct = QStyle::CT_PushButton;
839#endif
840 else if (qobject_cast<const QRadioButton *>(widg))
841 ct = QStyle::CT_RadioButton;
842#if QT_CONFIG(checkbox)
843 else if (qobject_cast<const QCheckBox *>(widg))
844 ct = QStyle::CT_CheckBox;
845#endif
846#if QT_CONFIG(combobox)
847 else if (qobject_cast<const QComboBox *>(widg))
848 ct = QStyle::CT_ComboBox;
849#endif
850#if QT_CONFIG(toolbutton)
851 else if (qobject_cast<const QToolButton *>(widg))
852 ct = QStyle::CT_ToolButton;
853#endif
854 else if (qobject_cast<const QSlider *>(widg))
855 ct = QStyle::CT_Slider;
856#if QT_CONFIG(progressbar)
857 else if (qobject_cast<const QProgressBar *>(widg))
858 ct = QStyle::CT_ProgressBar;
859#endif
860#if QT_CONFIG(lineedit)
861 else if (qobject_cast<const QLineEdit *>(widg))
862 ct = QStyle::CT_LineEdit;
863#endif
864#if QT_CONFIG(itemviews)
865 else if (qobject_cast<const QHeaderView *>(widg))
866 ct = QStyle::CT_HeaderSection;
867#endif
868#if QT_CONFIG(menubar)
869 else if (qobject_cast<const QMenuBar *>(widg))
870 ct = QStyle::CT_MenuBar;
871#endif
872#if QT_CONFIG(sizegrip)
873 else if (qobject_cast<const QSizeGrip *>(widg))
874 ct = QStyle::CT_SizeGrip;
875#endif
876 else
877 return ret;
878 }
879
880 switch (ct) {
881#if QT_CONFIG(pushbutton)
882 case QStyle::CT_PushButton: {
883 const QPushButton *psh = qobject_cast<const QPushButton *>(widg);
884 // If this comparison is false, then the widget was not a push button.
885 // This is bad and there's very little we can do since we were requested to find a
886 // sensible size for a widget that pretends to be a QPushButton but is not.
887 if(psh) {
888 QString buttonText = qt_mac_removeMnemonics(psh->text());
889 if (buttonText.contains(QLatin1Char('\n')))
890 ret = QSize(-1, -1);
891 else if (sz == QStyleHelper::SizeLarge)
892 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight));
893 else if (sz == QStyleHelper::SizeSmall)
894 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPushButtonHeight));
895 else if (sz == QStyleHelper::SizeMini)
896 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPushButtonHeight));
897
898 if (!psh->icon().isNull()){
899 // If the button got an icon, and the icon is larger than the
900 // button, we can't decide on a default size
901 ret.setWidth(-1);
902 if (ret.height() < psh->iconSize().height())
903 ret.setHeight(-1);
904 }
905 else if (buttonText == QLatin1String("OK") || buttonText == QLatin1String("Cancel")){
906 // Aqua Style guidelines restrict the size of OK and Cancel buttons to 68 pixels.
907 // However, this doesn't work for German, therefore only do it for English,
908 // I suppose it would be better to do some sort of lookups for languages
909 // that like to have really long words.
910 // FIXME This is not exactly true. Out of context, OK buttons have their
911 // implicit size calculated the same way as any other button. Inside a
912 // QDialogButtonBox, their size should be calculated such that the action
913 // or accept button (i.e., rightmost) and cancel button have the same width.
914 ret.setWidth(69);
915 }
916 } else {
917 // The only sensible thing to do is to return whatever the style suggests...
918 if (sz == QStyleHelper::SizeLarge)
919 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight));
920 else if (sz == QStyleHelper::SizeSmall)
921 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPushButtonHeight));
922 else if (sz == QStyleHelper::SizeMini)
923 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPushButtonHeight));
924 else
925 // Since there's no default size we return the large size...
926 ret = QSize(-1, qt_mac_aqua_get_metric(PushButtonHeight));
927 }
928#endif
929#if 0 //Not sure we are applying the rules correctly for RadioButtons/CheckBoxes --Sam
930 } else if (ct == QStyle::CT_RadioButton) {
931 QRadioButton *rdo = static_cast<QRadioButton *>(widg);
932 // Exception for case where multiline radio button text requires no size constrainment
933 if (rdo->text().find('\n') != -1)
934 return ret;
935 if (sz == QStyleHelper::SizeLarge)
936 ret = QSize(-1, qt_mac_aqua_get_metric(RadioButtonHeight));
937 else if (sz == QStyleHelper::SizeSmall)
938 ret = QSize(-1, qt_mac_aqua_get_metric(SmallRadioButtonHeight));
939 else if (sz == QStyleHelper::SizeMini)
940 ret = QSize(-1, qt_mac_aqua_get_metric(MiniRadioButtonHeight));
941 } else if (ct == QStyle::CT_CheckBox) {
942 if (sz == QStyleHelper::SizeLarge)
943 ret = QSize(-1, qt_mac_aqua_get_metric(CheckBoxHeight));
944 else if (sz == QStyleHelper::SizeSmall)
945 ret = QSize(-1, qt_mac_aqua_get_metric(SmallCheckBoxHeight));
946 else if (sz == QStyleHelper::SizeMini)
947 ret = QSize(-1, qt_mac_aqua_get_metric(MiniCheckBoxHeight));
948#endif
949 break;
950 }
951 case QStyle::CT_SizeGrip:
952 // Not HIG kosher: mimic what we were doing earlier until we support 4-edge resizing in MDI subwindows
953 if (sz == QStyleHelper::SizeLarge || sz == QStyleHelper::SizeSmall) {
954 int s = sz == QStyleHelper::SizeSmall ? 16 : 22; // large: pixel measured from HITheme, small: from my hat
955 int width = 0;
956#if QT_CONFIG(mdiarea)
957 if (widg && qobject_cast<QMdiSubWindow *>(widg->parentWidget()))
958 width = s;
959#endif
960 ret = QSize(width, s);
961 }
962 break;
963 case QStyle::CT_ComboBox:
964 switch (sz) {
965 case QStyleHelper::SizeLarge:
966 ret = QSize(-1, qt_mac_aqua_get_metric(PopupButtonHeight));
967 break;
968 case QStyleHelper::SizeSmall:
969 ret = QSize(-1, qt_mac_aqua_get_metric(SmallPopupButtonHeight));
970 break;
971 case QStyleHelper::SizeMini:
972 ret = QSize(-1, qt_mac_aqua_get_metric(MiniPopupButtonHeight));
973 break;
974 default:
975 break;
976 }
977 break;
978 case QStyle::CT_ToolButton:
979 if (sz == QStyleHelper::SizeSmall) {
980 int width = 0, height = 0;
981 if (szHint == QSize(-1, -1)) { //just 'guess'..
982#if QT_CONFIG(toolbutton)
983 const QToolButton *bt = qobject_cast<const QToolButton *>(widg);
984 // If this conversion fails then the widget was not what it claimed to be.
985 if(bt) {
986 if (!bt->icon().isNull()) {
987 QSize iconSize = bt->iconSize();
988 QSize pmSize = bt->icon().actualSize(QSize(32, 32), QIcon::Normal);
989 width = qMax(width, qMax(iconSize.width(), pmSize.width()));
990 height = qMax(height, qMax(iconSize.height(), pmSize.height()));
991 }
992 if (!bt->text().isNull() && bt->toolButtonStyle() != Qt::ToolButtonIconOnly) {
993 int text_width = bt->fontMetrics().horizontalAdvance(bt->text()),
994 text_height = bt->fontMetrics().height();
995 if (bt->toolButtonStyle() == Qt::ToolButtonTextUnderIcon) {
996 width = qMax(width, text_width);
997 height += text_height;
998 } else {
999 width += text_width;
1000 width = qMax(height, text_height);
1001 }
1002 }
1003 } else
1004#endif
1005 {
1006 // Let's return the size hint...
1007 width = szHint.width();
1008 height = szHint.height();
1009 }
1010 } else {
1011 width = szHint.width();
1012 height = szHint.height();
1013 }
1014 width = qMax(20, width + 5); //border
1015 height = qMax(20, height + 5); //border
1016 ret = QSize(width, height);
1017 }
1018 break;
1019 case QStyle::CT_Slider: {
1020 int w = -1;
1021 const QSlider *sld = qobject_cast<const QSlider *>(widg);
1022 // If this conversion fails then the widget was not what it claimed to be.
1023 if(sld) {
1024 if (sz == QStyleHelper::SizeLarge) {
1025 if (sld->orientation() == Qt::Horizontal) {
1026 w = qt_mac_aqua_get_metric(HSliderHeight);
1027 if (sld->tickPosition() != QSlider::NoTicks)
1028 w += qt_mac_aqua_get_metric(HSliderTickHeight);
1029 } else {
1030 w = qt_mac_aqua_get_metric(VSliderWidth);
1031 if (sld->tickPosition() != QSlider::NoTicks)
1032 w += qt_mac_aqua_get_metric(VSliderTickWidth);
1033 }
1034 } else if (sz == QStyleHelper::SizeSmall) {
1035 if (sld->orientation() == Qt::Horizontal) {
1036 w = qt_mac_aqua_get_metric(SmallHSliderHeight);
1037 if (sld->tickPosition() != QSlider::NoTicks)
1038 w += qt_mac_aqua_get_metric(SmallHSliderTickHeight);
1039 } else {
1040 w = qt_mac_aqua_get_metric(SmallVSliderWidth);
1041 if (sld->tickPosition() != QSlider::NoTicks)
1042 w += qt_mac_aqua_get_metric(SmallVSliderTickWidth);
1043 }
1044 } else if (sz == QStyleHelper::SizeMini) {
1045 if (sld->orientation() == Qt::Horizontal) {
1046 w = qt_mac_aqua_get_metric(MiniHSliderHeight);
1047 if (sld->tickPosition() != QSlider::NoTicks)
1048 w += qt_mac_aqua_get_metric(MiniHSliderTickHeight);
1049 } else {
1050 w = qt_mac_aqua_get_metric(MiniVSliderWidth);
1051 if (sld->tickPosition() != QSlider::NoTicks)
1052 w += qt_mac_aqua_get_metric(MiniVSliderTickWidth);
1053 }
1054 }
1055 } else {
1056 // This is tricky, we were requested to find a size for a slider which is not
1057 // a slider. We don't know if this is vertical or horizontal or if we need to
1058 // have tick marks or not.
1059 // For this case we will return an horizontal slider without tick marks.
1060 w = qt_mac_aqua_get_metric(HSliderHeight);
1061 w += qt_mac_aqua_get_metric(HSliderTickHeight);
1062 }
1063 if (sld->orientation() == Qt::Horizontal)
1064 ret.setHeight(w);
1065 else
1066 ret.setWidth(w);
1067 break;
1068 }
1069#if QT_CONFIG(progressbar)
1070 case QStyle::CT_ProgressBar: {
1071 int finalValue = -1;
1072 Qt::Orientation orient = Qt::Horizontal;
1073 if (const QProgressBar *pb = qobject_cast<const QProgressBar *>(widg))
1074 orient = pb->orientation();
1075
1076 if (sz == QStyleHelper::SizeLarge)
1077 finalValue = qt_mac_aqua_get_metric(LargeProgressBarThickness)
1078 + qt_mac_aqua_get_metric(ProgressBarShadowOutset);
1079 else
1080 finalValue = qt_mac_aqua_get_metric(NormalProgressBarThickness)
1081 + qt_mac_aqua_get_metric(SmallProgressBarShadowOutset);
1082 if (orient == Qt::Horizontal)
1083 ret.setHeight(finalValue);
1084 else
1085 ret.setWidth(finalValue);
1086 break;
1087 }
1088#endif
1089#if QT_CONFIG(combobox)
1090 case QStyle::CT_LineEdit:
1091 if (!widg || !qobject_cast<QComboBox *>(widg->parentWidget())) {
1092 //should I take into account the font dimentions of the lineedit? -Sam
1093 if (sz == QStyleHelper::SizeLarge)
1094 ret = QSize(-1, 21);
1095 else
1096 ret = QSize(-1, 19);
1097 }
1098 break;
1099#endif
1100 case QStyle::CT_HeaderSection:
1101#if QT_CONFIG(treeview)
1102 if (isTreeView(widg))
1103 ret = QSize(-1, qt_mac_aqua_get_metric(ListHeaderHeight));
1104#endif
1105 break;
1106 case QStyle::CT_MenuBar:
1107 if (sz == QStyleHelper::SizeLarge) {
1108 ret = QSize(-1, [[NSApp mainMenu] menuBarHeight]);
1109 // In the qt_mac_set_native_menubar(false) case,
1110 // we come it here with a zero-height main menu,
1111 // preventing the in-window menu from displaying.
1112 // Use 22 pixels for the height, by observation.
1113 if (ret.height() <= 0)
1114 ret.setHeight(22);
1115 }
1116 break;
1117 default:
1118 break;
1119 }
1120 return ret;
1121}
1122
1123
1124#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT)
1125static QStyleHelper::WidgetSizePolicy qt_aqua_guess_size(const QWidget *widg, QSize large, QSize small, QSize mini)
1126{
1127 Q_UNUSED(widg);
1128
1129 if (large == QSize(-1, -1)) {
1130 if (small != QSize(-1, -1))
1131 return QStyleHelper::SizeSmall;
1132 if (mini != QSize(-1, -1))
1133 return QStyleHelper::SizeMini;
1134 return QStyleHelper::SizeDefault;
1135 } else if (small == QSize(-1, -1)) {
1136 if (mini != QSize(-1, -1))
1137 return QStyleHelper::SizeMini;
1138 return QStyleHelper::SizeLarge;
1139 } else if (mini == QSize(-1, -1)) {
1140 return QStyleHelper::SizeLarge;
1141 }
1142
1143 if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL"))
1144 return QStyleHelper::SizeSmall;
1145 else if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI"))
1146 return QStyleHelper::SizeMini;
1147
1148 return QStyleHelper::SizeLarge;
1149}
1150#endif
1151
1152void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int hMargin, int vMargin, const CocoaControl &cw) const
1153{
1154 QPainterPath focusRingPath;
1155 focusRingPath.setFillRule(Qt::OddEvenFill);
1156
1157 qreal hOffset = 0.0;
1158 qreal vOffset = 0.0;
1159 switch (cw.type) {
1160 case Box:
1161 case Button_SquareButton:
1162 case SegmentedControl_Middle:
1163 case TextField: {
1164 auto innerRect = targetRect;
1165 if (cw.type == TextField)
1166 innerRect = innerRect.adjusted(hMargin, vMargin, -hMargin, -vMargin).adjusted(0.5, 0.5, -0.5, -0.5);
1167 const auto outerRect = innerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth);
1168 const auto outerRadius = focusRingWidth;
1169 focusRingPath.addRect(innerRect);
1170 focusRingPath.addRoundedRect(outerRect, outerRadius, outerRadius);
1171 break;
1172 }
1173 case Button_CheckBox: {
1174 const auto cbInnerRadius = (cw.size == QStyleHelper::SizeMini ? 2.0 : 3.0);
1175 const auto cbSize = cw.size == QStyleHelper::SizeLarge ? 13 :
1176 cw.size == QStyleHelper::SizeSmall ? 11 : 9; // As measured
1177 hOffset = hMargin + (cw.size == QStyleHelper::SizeLarge ? 2.5 :
1178 cw.size == QStyleHelper::SizeSmall ? 2.0 : 1.0); // As measured
1179 vOffset = 0.5 * qreal(targetRect.height() - cbSize);
1180 const auto cbInnerRect = QRectF(0, 0, cbSize, cbSize);
1181 const auto cbOuterRadius = cbInnerRadius + focusRingWidth;
1182 const auto cbOuterRect = cbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth);
1183 focusRingPath.addRoundedRect(cbOuterRect, cbOuterRadius, cbOuterRadius);
1184 focusRingPath.addRoundedRect(cbInnerRect, cbInnerRadius, cbInnerRadius);
1185 break;
1186 }
1187 case Button_RadioButton: {
1188 const auto rbSize = cw.size == QStyleHelper::SizeLarge ? 15 :
1189 cw.size == QStyleHelper::SizeSmall ? 13 : 9; // As measured
1190 hOffset = hMargin + (cw.size == QStyleHelper::SizeLarge ? 1.5 :
1191 cw.size == QStyleHelper::SizeSmall ? 1.0 : 1.0); // As measured
1192 vOffset = 0.5 * qreal(targetRect.height() - rbSize);
1193 const auto rbInnerRect = QRectF(0, 0, rbSize, rbSize);
1194 const auto rbOuterRect = rbInnerRect.adjusted(-focusRingWidth, -focusRingWidth, focusRingWidth, focusRingWidth);
1195 focusRingPath.addEllipse(rbInnerRect);
1196 focusRingPath.addEllipse(rbOuterRect);
1197 break;
1198 }
1199 case Button_PopupButton:
1200 case Button_PullDown:
1201 case Button_PushButton:
1202 case SegmentedControl_Single: {
1203 const qreal innerRadius = cw.type == Button_PushButton ? 3 : 4;
1204 const qreal outerRadius = innerRadius + focusRingWidth;
1205 hOffset = targetRect.left();
1206 vOffset = targetRect.top();
1207 const auto innerRect = targetRect.translated(-targetRect.topLeft());
1208 const auto outerRect = innerRect.adjusted(-hMargin, -vMargin, hMargin, vMargin);
1209 focusRingPath.addRoundedRect(innerRect, innerRadius, innerRadius);
1210 focusRingPath.addRoundedRect(outerRect, outerRadius, outerRadius);
1211 break;
1212 }
1213 case ComboBox:
1214 case SegmentedControl_First:
1215 case SegmentedControl_Last: {
1216 hOffset = targetRect.left();
1217 vOffset = targetRect.top();
1218 const qreal innerRadius = 8;
1219 const qreal outerRadius = innerRadius + focusRingWidth;
1220 const auto innerRect = targetRect.translated(-targetRect.topLeft());
1221 const auto outerRect = innerRect.adjusted(-hMargin, -vMargin, hMargin, vMargin);
1222
1223 const auto cbFocusFramePath = [](const QRectF &rect, qreal tRadius, qreal bRadius) {
1224 QPainterPath path;
1225
1226 if (tRadius > 0) {
1227 const auto topLeftCorner = QRectF(rect.topLeft(), QSizeF(tRadius, tRadius));
1228 path.arcMoveTo(topLeftCorner, 180);
1229 path.arcTo(topLeftCorner, 180, -90);
1230 } else {
1231 path.moveTo(rect.topLeft());
1232 }
1233 const auto rightEdge = rect.right() - bRadius;
1234 path.arcTo(rightEdge, rect.top(), bRadius, bRadius, 90, -90);
1235 path.arcTo(rightEdge, rect.bottom() - bRadius, bRadius, bRadius, 0, -90);
1236 if (tRadius > 0)
1237 path.arcTo(rect.left(), rect.bottom() - tRadius, tRadius, tRadius, 270, -90);
1238 else
1239 path.lineTo(rect.bottomLeft());
1240 path.closeSubpath();
1241
1242 return path;
1243 };
1244
1245 const auto innerPath = cbFocusFramePath(innerRect, 0, innerRadius);
1246 focusRingPath.addPath(innerPath);
1247 const auto outerPath = cbFocusFramePath(outerRect, 2 * focusRingWidth, outerRadius);
1248 focusRingPath.addPath(outerPath);
1249 break;
1250 }
1251 default:
1252 Q_UNREACHABLE();
1253 }
1254
1255 auto focusRingColor = qt_mac_toQColor(NSColor.keyboardFocusIndicatorColor.CGColor);
1256 if (!qt_mac_applicationIsInDarkMode()) {
1257 // This color already has alpha ~ 0.25, this value is too small - the ring is
1258 // very pale and nothing like the native one. 0.39 makes it better (not ideal
1259 // anyway). The color seems to be correct in dark more without any modification.
1260 focusRingColor.setAlphaF(0.39);
1261 }
1262
1263 p->save();
1264 p->setRenderHint(QPainter::Antialiasing);
1265
1266 if (cw.type == SegmentedControl_First) {
1267 // TODO Flip left-right
1268 }
1269 p->translate(hOffset, vOffset);
1270 p->fillPath(focusRingPath, focusRingColor);
1271 p->restore();
1272}
1273
1274QPainterPath QMacStylePrivate::windowPanelPath(const QRectF &r) const
1275{
1276 static const qreal CornerPointOffset = 5.5;
1277 static const qreal CornerControlOffset = 2.1;
1278
1279 QPainterPath path;
1280 // Top-left corner
1281 path.moveTo(r.left(), r.top() + CornerPointOffset);
1282 path.cubicTo(r.left(), r.top() + CornerControlOffset,
1283 r.left() + CornerControlOffset, r.top(),
1284 r.left() + CornerPointOffset, r.top());
1285 // Top-right corner
1286 path.lineTo(r.right() - CornerPointOffset, r.top());
1287 path.cubicTo(r.right() - CornerControlOffset, r.top(),
1288 r.right(), r.top() + CornerControlOffset,
1289 r.right(), r.top() + CornerPointOffset);
1290 // Bottom-right corner
1291 path.lineTo(r.right(), r.bottom() - CornerPointOffset);
1292 path.cubicTo(r.right(), r.bottom() - CornerControlOffset,
1293 r.right() - CornerControlOffset, r.bottom(),
1294 r.right() - CornerPointOffset, r.bottom());
1295 // Bottom-right corner
1296 path.lineTo(r.left() + CornerPointOffset, r.bottom());
1297 path.cubicTo(r.left() + CornerControlOffset, r.bottom(),
1298 r.left(), r.bottom() - CornerControlOffset,
1299 r.left(), r.bottom() - CornerPointOffset);
1300 path.lineTo(r.left(), r.top() + CornerPointOffset);
1301
1302 return path;
1303}
1304
1305QMacStylePrivate::CocoaControlType QMacStylePrivate::windowButtonCocoaControl(QStyle::SubControl sc) const
1306{
1307 struct WindowButtons {
1308 QStyle::SubControl sc;
1309 QMacStylePrivate::CocoaControlType ct;
1310 };
1311
1312 static const WindowButtons buttons[] = {
1313 { QStyle::SC_TitleBarCloseButton, QMacStylePrivate::Button_WindowClose },
1314 { QStyle::SC_TitleBarMinButton, QMacStylePrivate::Button_WindowMiniaturize },
1315 { QStyle::SC_TitleBarMaxButton, QMacStylePrivate::Button_WindowZoom }
1316 };
1317
1318 for (const auto &wb : buttons)
1319 if (wb.sc == sc)
1320 return wb.ct;
1321
1322 return NoControl;
1323}
1324
1325
1326#if QT_CONFIG(tabbar)
1327void QMacStylePrivate::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const
1328{
1329 Q_ASSERT(textRect);
1330 Q_ASSERT(iconRect);
1331 QRect tr = opt->rect;
1332 const bool verticalTabs = opt->shape == QTabBar::RoundedEast
1333 || opt->shape == QTabBar::RoundedWest
1334 || opt->shape == QTabBar::TriangularEast
1335 || opt->shape == QTabBar::TriangularWest;
1336 if (verticalTabs)
1337 tr.setRect(0, 0, tr.height(), tr.width()); // 0, 0 as we will have a translate transform
1338
1339 int verticalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget);
1340 int horizontalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget);
1341 const int hpadding = 4;
1342 const int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2;
1343 if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth)
1344 verticalShift = -verticalShift;
1345 tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding);
1346
1347 // left widget
1348 if (!opt->leftButtonSize.isEmpty()) {
1349 const int buttonSize = verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width();
1350 tr.setLeft(tr.left() + 4 + buttonSize);
1351 // make text aligned to center
1352 if (opt->rightButtonSize.isEmpty())
1353 tr.setRight(tr.right() - 4 - buttonSize);
1354 }
1355 // right widget
1356 if (!opt->rightButtonSize.isEmpty()) {
1357 const int buttonSize = verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width();
1358 tr.setRight(tr.right() - 4 - buttonSize);
1359 // make text aligned to center
1360 if (opt->leftButtonSize.isEmpty())
1361 tr.setLeft(tr.left() + 4 + buttonSize);
1362 }
1363
1364 // icon
1365 if (!opt->icon.isNull()) {
1366 QSize iconSize = opt->iconSize;
1367 if (!iconSize.isValid()) {
1368 int iconExtent = proxyStyle->pixelMetric(QStyle::PM_SmallIconSize);
1369 iconSize = QSize(iconExtent, iconExtent);
1370 }
1371 QSize tabIconSize = opt->icon.actualSize(iconSize,
1372 (opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
1373 (opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off);
1374 // High-dpi icons do not need adjustment; make sure tabIconSize is not larger than iconSize
1375 tabIconSize = QSize(qMin(tabIconSize.width(), iconSize.width()), qMin(tabIconSize.height(), iconSize.height()));
1376
1377 *iconRect = QRect(tr.left(), tr.center().y() - tabIconSize.height() / 2,
1378 tabIconSize.width(), tabIconSize.height());
1379 if (!verticalTabs)
1380 *iconRect = proxyStyle->visualRect(opt->direction, opt->rect, *iconRect);
1381
1382 int stylePadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2;
1383 stylePadding -= hpadding;
1384
1385 tr.setLeft(tr.left() + stylePadding + tabIconSize.width() + 4);
1386 tr.setRight(tr.right() - stylePadding - tabIconSize.width() - 4);
1387 }
1388
1389 if (!verticalTabs)
1390 tr = proxyStyle->visualRect(opt->direction, opt->rect, tr);
1391
1392 *textRect = tr;
1393}
1394
1395QMacStylePrivate::Direction QMacStylePrivate::tabDirection(QTabBar::Shape shape)
1396{
1397 switch (shape) {
1398 case QTabBar::RoundedSouth:
1399 case QTabBar::TriangularSouth:
1400 return South;
1401 case QTabBar::RoundedNorth:
1402 case QTabBar::TriangularNorth:
1403 return North;
1404 case QTabBar::RoundedWest:
1405 case QTabBar::TriangularWest:
1406 return West;
1407 case QTabBar::RoundedEast:
1408 case QTabBar::TriangularEast:
1409 return East;
1410 }
1411}
1412
1413bool QMacStylePrivate::verticalTabs(QMacStylePrivate::Direction direction)
1414{
1415 return (direction == QMacStylePrivate::East
1416 || direction == QMacStylePrivate::West);
1417}
1418
1419#endif // QT_CONFIG(tabbar)
1420
1421QStyleHelper::WidgetSizePolicy QMacStylePrivate::effectiveAquaSizeConstrain(const QStyleOption *option,
1422 const QWidget *widg,
1423 QStyle::ContentsType ct,
1424 QSize szHint, QSize *insz) const
1425{
1426 QStyleHelper::WidgetSizePolicy sz = aquaSizeConstrain(option, widg, ct, szHint, insz);
1427 if (sz == QStyleHelper::SizeDefault)
1428 return QStyleHelper::SizeLarge;
1429 return sz;
1430}
1431
1432QStyleHelper::WidgetSizePolicy QMacStylePrivate::aquaSizeConstrain(const QStyleOption *option, const QWidget *widg,
1433 QStyle::ContentsType ct, QSize szHint, QSize *insz) const
1434{
1435#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT)
1436 if (option) {
1437 if (option->state & QStyle::State_Small)
1438 return QStyleHelper::SizeSmall;
1439 if (option->state & QStyle::State_Mini)
1440 return QStyleHelper::SizeMini;
1441 }
1442
1443 if (!widg) {
1444 if (insz)
1445 *insz = QSize();
1446 if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL"))
1447 return QStyleHelper::SizeSmall;
1448 if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI"))
1449 return QStyleHelper::SizeMini;
1450 return QStyleHelper::SizeDefault;
1451 }
1452
1453 QSize large = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeLarge),
1454 small = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeSmall),
1455 mini = qt_aqua_get_known_size(ct, widg, szHint, QStyleHelper::SizeMini);
1456 bool guess_size = false;
1457 QStyleHelper::WidgetSizePolicy ret = QStyleHelper::SizeDefault;
1458 QStyleHelper::WidgetSizePolicy wsp = QStyleHelper::widgetSizePolicy(widg);
1459 if (wsp == QStyleHelper::SizeDefault)
1460 guess_size = true;
1461 else if (wsp == QStyleHelper::SizeMini)
1462 ret = QStyleHelper::SizeMini;
1463 else if (wsp == QStyleHelper::SizeSmall)
1464 ret = QStyleHelper::SizeSmall;
1465 else if (wsp == QStyleHelper::SizeLarge)
1466 ret = QStyleHelper::SizeLarge;
1467 if (guess_size)
1468 ret = qt_aqua_guess_size(widg, large, small, mini);
1469
1470 QSize *sz = 0;
1471 if (ret == QStyleHelper::SizeSmall)
1472 sz = &small;
1473 else if (ret == QStyleHelper::SizeLarge)
1474 sz = &large;
1475 else if (ret == QStyleHelper::SizeMini)
1476 sz = &mini;
1477 if (insz)
1478 *insz = sz ? *sz : QSize(-1, -1);
1479#ifdef DEBUG_SIZE_CONSTRAINT
1480 if (sz) {
1481 const char *size_desc = "Unknown";
1482 if (sz == &small)
1483 size_desc = "Small";
1484 else if (sz == &large)
1485 size_desc = "Large";
1486 else if (sz == &mini)
1487 size_desc = "Mini";
1488 qDebug("%s - %s: %s taken (%d, %d) [%d, %d]",
1489 widg ? widg->objectName().toLatin1().constData() : "*Unknown*",
1490 widg ? widg->metaObject()->className() : "*Unknown*", size_desc, widg->width(), widg->height(),
1491 sz->width(), sz->height());
1492 }
1493#endif
1494 return ret;
1495#else
1496 if (insz)
1497 *insz = QSize();
1498 Q_UNUSED(widg);
1499 Q_UNUSED(ct);
1500 Q_UNUSED(szHint);
1501 return QStyleHelper::SizeDefault;
1502#endif
1503}
1504
1505uint qHash(const QMacStylePrivate::CocoaControl &cw, uint seed = 0)
1506{
1507 return ((cw.type << 2) | cw.size) ^ seed;
1508}
1509
1510QMacStylePrivate::CocoaControl::CocoaControl()
1511 : type(NoControl), size(QStyleHelper::SizeDefault)
1512{
1513}
1514
1515QMacStylePrivate::CocoaControl::CocoaControl(CocoaControlType t, QStyleHelper::WidgetSizePolicy s)
1516 : type(t), size(s)
1517{
1518}
1519
1520bool QMacStylePrivate::CocoaControl::operator==(const CocoaControl &other) const
1521{
1522 return other.type == type && other.size == size;
1523}
1524
1525QSizeF QMacStylePrivate::CocoaControl::defaultFrameSize() const
1526{
1527 // We need this because things like NSView.alignmentRectInsets
1528 // or -[NSCell titleRectForBounds:] won't work unless the control
1529 // has a reasonable frame set. IOW, it's a chicken and egg problem.
1530 // These values are as observed in Xcode 9's Interface Builder.
1531
1532 if (type == Button_PushButton)
1533 return QSizeF(-1, pushButtonDefaultHeight[size]);
1534
1535 if (type == Button_PopupButton
1536 || type == Button_PullDown)
1537 return QSizeF(-1, popupButtonDefaultHeight[size]);
1538
1539 if (type == ComboBox)
1540 return QSizeF(-1, comboBoxDefaultHeight[size]);
1541
1542 return QSizeF();
1543}
1544
1545QRectF QMacStylePrivate::CocoaControl::adjustedControlFrame(const QRectF &rect) const
1546{
1547 QRectF frameRect;
1548 const auto frameSize = defaultFrameSize();
1549 if (type == QMacStylePrivate::Button_SquareButton) {
1550 frameRect = rect.adjusted(3, 1, -3, -1)
1551 .adjusted(focusRingWidth, focusRingWidth, -focusRingWidth, -focusRingWidth);
1552 } else if (type == QMacStylePrivate::Button_PushButton) {
1553 // Start from the style option's top-left corner.
1554 frameRect = QRectF(rect.topLeft(),
1555 QSizeF(rect.width(), frameSize.height()));
1556 if (size == QStyleHelper::SizeSmall)
1557 frameRect = frameRect.translated(0, 1.5);
1558 else if (size == QStyleHelper::SizeMini)
1559 frameRect = frameRect.adjusted(0, 0, -8, 0).translated(4, 4);
1560 } else {
1561 // Center in the style option's rect.
1562 frameRect = QRectF(QPointF(0, (rect.height() - frameSize.height()) / 2.0),
1563 QSizeF(rect.width(), frameSize.height()));
1564 frameRect = frameRect.translated(rect.topLeft());
1565 if (type == QMacStylePrivate::Button_PullDown || type == QMacStylePrivate::Button_PopupButton) {
1566 if (size == QStyleHelper::SizeLarge)
1567 frameRect = frameRect.adjusted(0, 0, -6, 0).translated(3, 0);
1568 else if (size == QStyleHelper::SizeSmall)
1569 frameRect = frameRect.adjusted(0, 0, -4, 0).translated(2, 1);
1570 else if (size == QStyleHelper::SizeMini)
1571 frameRect = frameRect.adjusted(0, 0, -9, 0).translated(5, 0);
1572 } else if (type == QMacStylePrivate::ComboBox) {
1573 frameRect = frameRect.adjusted(0, 0, -6, 0).translated(4, 0);
1574 }
1575 }
1576
1577 return frameRect;
1578}
1579
1580QMarginsF QMacStylePrivate::CocoaControl::titleMargins() const
1581{
1582 if (type == QMacStylePrivate::Button_PushButton) {
1583 if (size == QStyleHelper::SizeLarge)
1584 return QMarginsF(12, 5, 12, 9);
1585 if (size == QStyleHelper::SizeSmall)
1586 return QMarginsF(12, 4, 12, 9);
1587 if (size == QStyleHelper::SizeMini)
1588 return QMarginsF(10, 1, 10, 2);
1589 }
1590
1591 if (type == QMacStylePrivate::Button_PullDown) {
1592 if (size == QStyleHelper::SizeLarge)
1593 return QMarginsF(7.5, 2.5, 22.5, 5.5);
1594 if (size == QStyleHelper::SizeSmall)
1595 return QMarginsF(7.5, 2, 20.5, 4);
1596 if (size == QStyleHelper::SizeMini)
1597 return QMarginsF(4.5, 0, 16.5, 2);
1598 }
1599
1600 if (type == QMacStylePrivate::Button_SquareButton)
1601 return QMarginsF(6, 1, 6, 2);
1602
1603 return QMarginsF();
1604}
1605
1606bool QMacStylePrivate::CocoaControl::getCocoaButtonTypeAndBezelStyle(NSButtonType *buttonType, NSBezelStyle *bezelStyle) const
1607{
1608 switch (type) {
1609 case Button_CheckBox:
1610 *buttonType = NSSwitchButton;
1611 *bezelStyle = NSRegularSquareBezelStyle;
1612 break;
1613 case Button_Disclosure:
1614 *buttonType = NSOnOffButton;
1615 *bezelStyle = NSDisclosureBezelStyle;
1616 break;
1617 case Button_RadioButton:
1618 *buttonType = NSRadioButton;
1619 *bezelStyle = NSRegularSquareBezelStyle;
1620 break;
1621 case Button_SquareButton:
1622 *buttonType = NSPushOnPushOffButton;
1623 *bezelStyle = NSShadowlessSquareBezelStyle;
1624 break;
1625 case Button_PushButton:
1626 *buttonType = NSPushOnPushOffButton;
1627 *bezelStyle = NSRoundedBezelStyle;
1628 break;
1629 default:
1630 return false;
1631 }
1632
1633 return true;
1634}
1635
1636QMacStylePrivate::CocoaControlType cocoaControlType(const QStyleOption *opt, const QWidget *w)
1637{
1638 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1639 const bool hasMenu = btn->features & QStyleOptionButton::HasMenu;
1640 // When the contents won't fit in a large sized button,
1641 // and WA_MacNormalSize is not set, make the button square.
1642 // Threshold used to be at 34, not 32.
1643 const auto maxNonSquareHeight = pushButtonDefaultHeight[QStyleHelper::SizeLarge];
1644 const bool isSquare = (btn->features & QStyleOptionButton::Flat)
1645 || (btn->rect.height() > maxNonSquareHeight
1646 && !(w && w->testAttribute(Qt::WA_MacNormalSize)));
1647 return (isSquare? QMacStylePrivate::Button_SquareButton :
1648 hasMenu ? QMacStylePrivate::Button_PullDown :
1649 QMacStylePrivate::Button_PushButton);
1650 }
1651
1652 if (const auto *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
1653 if (combo->editable)
1654 return QMacStylePrivate::ComboBox;
1655 // TODO Me may support square, non-editable combo boxes, but not more than that
1656 return QMacStylePrivate::Button_PopupButton;
1657 }
1658
1659 return QMacStylePrivate::NoControl;
1660}
1661
1662/**
1663 Carbon draws comboboxes (and other views) outside the rect given as argument. Use this function to obtain
1664 the corresponding inner rect for drawing the same combobox so that it stays inside the given outerBounds.
1665*/
1666CGRect QMacStylePrivate::comboboxInnerBounds(const CGRect &outerBounds, const CocoaControl &cocoaWidget)
1667{
1668 CGRect innerBounds = outerBounds;
1669 // Carbon draw parts of the view outside the rect.
1670 // So make the rect a bit smaller to compensate
1671 // (I wish HIThemeGetButtonBackgroundBounds worked)
1672 if (cocoaWidget.type == Button_PopupButton) {
1673 switch (cocoaWidget.size) {
1674 case QStyleHelper::SizeSmall:
1675 innerBounds.origin.x += 3;
1676 innerBounds.origin.y += 3;
1677 innerBounds.size.width -= 6;
1678 innerBounds.size.height -= 7;
1679 break;
1680 case QStyleHelper::SizeMini:
1681 innerBounds.origin.x += 2;
1682 innerBounds.origin.y += 2;
1683 innerBounds.size.width -= 5;
1684 innerBounds.size.height -= 6;
1685 break;
1686 case QStyleHelper::SizeLarge:
1687 case QStyleHelper::SizeDefault:
1688 innerBounds.origin.x += 2;
1689 innerBounds.origin.y += 2;
1690 innerBounds.size.width -= 5;
1691 innerBounds.size.height -= 6;
1692 }
1693 } else if (cocoaWidget.type == ComboBox) {
1694 switch (cocoaWidget.size) {
1695 case QStyleHelper::SizeSmall:
1696 innerBounds.origin.x += 3;
1697 innerBounds.origin.y += 3;
1698 innerBounds.size.width -= 7;
1699 innerBounds.size.height -= 8;
1700 break;
1701 case QStyleHelper::SizeMini:
1702 innerBounds.origin.x += 3;
1703 innerBounds.origin.y += 3;
1704 innerBounds.size.width -= 4;
1705 innerBounds.size.height -= 8;
1706 break;
1707 case QStyleHelper::SizeLarge:
1708 case QStyleHelper::SizeDefault:
1709 innerBounds.origin.x += 3;
1710 innerBounds.origin.y += 2;
1711 innerBounds.size.width -= 6;
1712 innerBounds.size.height -= 8;
1713 }
1714 }
1715
1716 return innerBounds;
1717}
1718
1719/**
1720 Inside a combobox Qt places a line edit widget. The size of this widget should depend on the kind
1721 of combobox we choose to draw. This function calculates and returns this size.
1722*/
1723QRectF QMacStylePrivate::comboboxEditBounds(const QRectF &outerBounds, const CocoaControl &cw)
1724{
1725 QRectF ret = outerBounds;
1726 if (cw.type == ComboBox) {
1727 switch (cw.size) {
1728 case QStyleHelper::SizeLarge:
1729 ret = ret.adjusted(0, 0, -28, 0).translated(3, 4.5);
1730 ret.setHeight(16);
1731 break;
1732 case QStyleHelper::SizeSmall:
1733 ret = ret.adjusted(0, 0, -24, 0).translated(3, 2);
1734 ret.setHeight(14);
1735 break;
1736 case QStyleHelper::SizeMini:
1737 ret = ret.adjusted(0, 0, -21, 0).translated(2, 3);
1738 ret.setHeight(11);
1739 break;
1740 default:
1741 break;
1742 }
1743 } else if (cw.type == Button_PopupButton) {
1744 switch (cw.size) {
1745 case QStyleHelper::SizeLarge:
1746 ret.adjust(10, 1, -23, -4);
1747 break;
1748 case QStyleHelper::SizeSmall:
1749 ret.adjust(10, 4, -20, -3);
1750 break;
1751 case QStyleHelper::SizeMini:
1752 ret.adjust(9, 0, -19, 0);
1753 ret.setHeight(13);
1754 break;
1755 default:
1756 break;
1757 }
1758 }
1759 return ret;
1760}
1761
1762QMacStylePrivate::QMacStylePrivate()
1763 : backingStoreNSView(nil)
1764{
1765 if (auto *ssf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::SmallFont))
1766 smallSystemFont = *ssf;
1767 if (auto *msf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::MiniFont))
1768 miniSystemFont = *msf;
1769}
1770
1771QMacStylePrivate::~QMacStylePrivate()
1772{
1773 QMacAutoReleasePool pool;
1774 for (NSView *b : cocoaControls)
1775 [b release];
1776 for (NSCell *cell : cocoaCells)
1777 [cell release];
1778}
1779
1780NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const
1781{
1782 if (widget.type == QMacStylePrivate::NoControl
1783 || widget.size == QStyleHelper::SizeDefault)
1784 return nil;
1785
1786 if (widget.type == Box) {
1787 if (__builtin_available(macOS 10.14, *)) {
1788 if (qt_mac_applicationIsInDarkMode()) {
1789 // See render code in drawPrimitive(PE_FrameTabWidget)
1790 widget.type = Box_Dark;
1791 }
1792 }
1793 }
1794
1795 NSView *bv = cocoaControls.value(widget, nil);
1796 if (!bv) {
1797 switch (widget.type) {
1798 case Box: {
1799 NSBox *box = [[NSBox alloc] init];
1800 bv = box;
1801 box.title = @"";
1802 box.titlePosition = NSNoTitle;
1803 break;
1804 }
1805 case Box_Dark:
1806 bv = [[QDarkNSBox alloc] init];
1807 break;
1808 case Button_CheckBox:
1809 case Button_Disclosure:
1810 case Button_PushButton:
1811 case Button_RadioButton:
1812 case Button_SquareButton: {
1813 NSButton *bc = [[NSButton alloc] init];
1814 bc.title = @"";
1815 // See below for style and bezel setting.
1816 bv = bc;
1817 break;
1818 }
1819 case Button_PopupButton:
1820 case Button_PullDown: {
1821 NSPopUpButton *bc = [[NSPopUpButton alloc] init];
1822 bc.title = @"";
1823 if (widget.type == Button_PullDown)
1824 bc.pullsDown = YES;
1825 bv = bc;
1826 break;
1827 }
1828 case Button_WindowClose:
1829 case Button_WindowMiniaturize:
1830 case Button_WindowZoom: {
1831 const NSWindowButton button = [=] {
1832 switch (widget.type) {
1833 case Button_WindowClose:
1834 return NSWindowCloseButton;
1835 case Button_WindowMiniaturize:
1836 return NSWindowMiniaturizeButton;
1837 case Button_WindowZoom:
1838 return NSWindowZoomButton;
1839 default:
1840 break;
1841 }
1842 Q_UNREACHABLE();
1843 } ();
1844 const auto styleMask = NSWindowStyleMaskTitled
1845 | NSWindowStyleMaskClosable
1846 | NSWindowStyleMaskMiniaturizable
1847 | NSWindowStyleMaskResizable;
1848 bv = [NSWindow standardWindowButton:button forStyleMask:styleMask];
1849 [bv retain];
1850 break;
1851 }
1852 case ComboBox:
1853 bv = [[NSComboBox alloc] init];
1854 break;
1855 case ProgressIndicator_Determinate:
1856 bv = [[NSProgressIndicator alloc] init];
1857 break;
1858 case ProgressIndicator_Indeterminate:
1859 bv = [[QIndeterminateProgressIndicator alloc] init];
1860 break;
1861 case Scroller_Horizontal:
1862 bv = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 200, 20)];
1863 break;
1864 case Scroller_Vertical:
1865 // Cocoa sets the orientation from the view's frame
1866 // at construction time, and it cannot be changed later.
1867 bv = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 20, 200)];
1868 break;
1869 case Slider_Horizontal:
1870 bv = [[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 200, 20)];
1871 break;
1872 case Slider_Vertical:
1873 // Cocoa sets the orientation from the view's frame
1874 // at construction time, and it cannot be changed later.
1875 bv = [[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 20, 200)];
1876 break;
1877 case SplitView_Horizontal:
1878 bv = [[NSSplitView alloc] init];
1879 break;
1880 case SplitView_Vertical:
1881 bv = [[QVerticalSplitView alloc] init];
1882 break;
1883 case TextField:
1884 bv = [[NSTextField alloc] init];
1885 break;
1886 default:
1887 break;
1888 }
1889
1890 if ([bv isKindOfClass:[NSControl class]]) {
1891 auto *ctrl = static_cast<NSControl *>(bv);
1892 switch (widget.size) {
1893 case QStyleHelper::SizeSmall:
1894 ctrl.controlSize = NSControlSizeSmall;
1895 break;
1896 case QStyleHelper::SizeMini:
1897 ctrl.controlSize = NSControlSizeMini;
1898 break;
1899 default:
1900 break;
1901 }
1902 } else if (widget.type == ProgressIndicator_Determinate ||
1903 widget.type == ProgressIndicator_Indeterminate) {
1904 auto *pi = static_cast<NSProgressIndicator *>(bv);
1905 pi.indeterminate = (widget.type == ProgressIndicator_Indeterminate);
1906 switch (widget.size) {
1907 case QStyleHelper::SizeSmall:
1908 pi.controlSize = NSControlSizeSmall;
1909 break;
1910 case QStyleHelper::SizeMini:
1911 pi.controlSize = NSControlSizeMini;
1912 break;
1913 default:
1914 break;
1915 }
1916 }
1917
1918 cocoaControls.insert(widget, bv);
1919 }
1920
1921 NSButtonType buttonType;
1922 NSBezelStyle bezelStyle;
1923 if (widget.getCocoaButtonTypeAndBezelStyle(&buttonType, &bezelStyle)) {
1924 // FIXME We need to reset the button's type and
1925 // bezel style properties, even when cached.
1926 auto *button = static_cast<NSButton *>(bv);
1927 button.buttonType = buttonType;
1928 button.bezelStyle = bezelStyle;
1929 if (widget.type == Button_CheckBox)
1930 button.allowsMixedState = YES;
1931 }
1932
1933 return bv;
1934}
1935
1936NSCell *QMacStylePrivate::cocoaCell(CocoaControl widget) const
1937{
1938 NSCell *cell = cocoaCells[widget];
1939 if (!cell) {
1940 switch (widget.type) {
1941 case Stepper:
1942 cell = [[NSStepperCell alloc] init];
1943 break;
1944 case Button_Disclosure: {
1945 NSButtonCell *bc = [[NSButtonCell alloc] init];
1946 bc.buttonType = NSOnOffButton;
1947 bc.bezelStyle = NSDisclosureBezelStyle;
1948 cell = bc;
1949 break;
1950 }
1951 default:
1952 break;
1953 }
1954
1955 switch (widget.size) {
1956 case QStyleHelper::SizeSmall:
1957 cell.controlSize = NSControlSizeSmall;
1958 break;
1959 case QStyleHelper::SizeMini:
1960 cell.controlSize = NSControlSizeMini;
1961 break;
1962 default:
1963 break;
1964 }
1965
1966 cocoaCells.insert(widget, cell);
1967 }
1968
1969 return cell;
1970}
1971
1972void QMacStylePrivate::drawNSViewInRect(NSView *view, const QRectF &rect, QPainter *p,
1973 __attribute__((noescape)) DrawRectBlock drawRectBlock) const
1974{
1975 QMacCGContext ctx(p);
1976 setupNSGraphicsContext(ctx, YES);
1977
1978 // FIXME: The rect that we get in is relative to the widget that we're drawing
1979 // style on behalf of, and doesn't take into account the offset of that widget
1980 // to the widget that owns the backingstore, which we are placing the native
1981 // view into below. This means most of the views are placed in the upper left
1982 // corner of backingStoreNSView, which does not map to where the actual widget
1983 // is, and which may cause problems such as triggering a setNeedsDisplay of the
1984 // backingStoreNSView for the wrong rect. We work around this by making the view
1985 // layer-backed, which prevents triggering display of the backingStoreNSView, but
1986 // but there may be other issues lurking here due to the wrong position. QTBUG-68023
1987 view.wantsLayer = YES;
1988
1989 // FIXME: We are also setting the frame of the incoming view a lot at the call
1990 // sites of this function, making it unclear who's actually responsible for
1991 // maintaining the size and position of the view. In theory the call sites
1992 // should ensure the _size_ of the view is correct, and then let this code
1993 // take care of _positioning_ the view at the right place inside backingStoreNSView.
1994 // For now we pass on the rect as is, to prevent any regressions until this
1995 // can be investigated properly.
1996 view.frame = rect.toCGRect();
1997
1998 [backingStoreNSView addSubview:view];
1999
2000 // FIXME: Based on the code below, this method isn't drawing an NSView into
2001 // a rect, it's drawing _part of the NSView_, defined by the incoming clip
2002 // or dirty rect, into the current graphics context. We're doing some manual
2003 // translations at the call sites that would indicate that this relationship
2004 // is a bit fuzzy.
2005 const CGRect dirtyRect = rect.toCGRect();
2006
2007 if (drawRectBlock)
2008 drawRectBlock(ctx, dirtyRect);
2009 else
2010 [view drawRect:dirtyRect];
2011
2012 [view removeFromSuperviewWithoutNeedingDisplay];
2013
2014 restoreNSGraphicsContext(ctx);
2015}
2016
2017void QMacStylePrivate::resolveCurrentNSView(QWindow *window) const
2018{
2019 backingStoreNSView = window ? (NSView *)window->winId() : nil;
2020}
2021
2022QMacStyle::QMacStyle()
2023 : QCommonStyle(*new QMacStylePrivate)
2024{
2025 QMacAutoReleasePool pool;
2026
2027 static QMacNotificationObserver scrollbarStyleObserver(nil,
2028 NSPreferredScrollerStyleDidChangeNotification, []() {
2029 // Purge destroyed scroll bars
2030 QMacStylePrivate::scrollBars.removeAll(QPointer<QObject>());
2031
2032 QEvent event(QEvent::StyleChange);
2033 for (const auto &o : QMacStylePrivate::scrollBars)
2034 QCoreApplication::sendEvent(o, &event);
2035 });
2036
2037#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
2038 Q_D(QMacStyle);
2039 // FIXME: Tie this logic into theme change, or even polish/unpolish
2040 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) {
2041 d->appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] {
2042 Q_D(QMacStyle);
2043 for (NSView *b : d->cocoaControls)
2044 [b release];
2045 d->cocoaControls.clear();
2046 });
2047 }
2048#endif
2049}
2050
2051QMacStyle::~QMacStyle()
2052{
2053}
2054
2055void QMacStyle::polish(QPalette &)
2056{
2057}
2058
2059void QMacStyle::polish(QApplication *)
2060{
2061}
2062
2063void QMacStyle::unpolish(QApplication *)
2064{
2065}
2066
2067void QMacStyle::polish(QWidget* w)
2068{
2069 if (false
2070#if QT_CONFIG(menu)
2071 || qobject_cast<QMenu*>(w)
2072# if QT_CONFIG(combobox)
2073 || qobject_cast<QComboBoxPrivateContainer *>(w)
2074# endif
2075#endif
2076#if QT_CONFIG(mdiarea)
2077 || qobject_cast<QMdiSubWindow *>(w)
2078#endif
2079 ) {
2080 w->setAttribute(Qt::WA_TranslucentBackground, true);
2081 w->setAutoFillBackground(false);
2082 }
2083
2084#if QT_CONFIG(tabbar)
2085 if (QTabBar *tb = qobject_cast<QTabBar*>(w)) {
2086 if (tb->documentMode()) {
2087 w->setAttribute(Qt::WA_Hover);
2088 w->setFont(qt_app_fonts_hash()->value("QSmallFont", QFont()));
2089 QPalette p = w->palette();
2090 p.setColor(QPalette::WindowText, QColor(17, 17, 17));
2091 w->setPalette(p);
2092 w->setAttribute(Qt::WA_SetPalette, false);
2093 w->setAttribute(Qt::WA_SetFont, false);
2094 }
2095 }
2096#endif
2097
2098 QCommonStyle::polish(w);
2099
2100 if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) {
2101 rubber->setWindowOpacity(0.25);
2102 rubber->setAttribute(Qt::WA_PaintOnScreen, false);
2103 rubber->setAttribute(Qt::WA_NoSystemBackground, false);
2104 }
2105
2106 if (qobject_cast<QScrollBar*>(w)) {
2107 w->setAttribute(Qt::WA_OpaquePaintEvent, false);
2108 w->setAttribute(Qt::WA_Hover, true);
2109 w->setMouseTracking(true);
2110 }
2111}
2112
2113void QMacStyle::unpolish(QWidget* w)
2114{
2115 if (
2116#if QT_CONFIG(menu)
2117 qobject_cast<QMenu*>(w) &&
2118#endif
2119 !w->testAttribute(Qt::WA_SetPalette))
2120 {
2121 w->setPalette(QPalette());
2122 w->setWindowOpacity(1.0);
2123 }
2124
2125#if QT_CONFIG(combobox)
2126 if (QComboBox *combo = qobject_cast<QComboBox *>(w)) {
2127 if (!combo->isEditable()) {
2128 if (QWidget *widget = combo->findChild<QComboBoxPrivateContainer *>())
2129 widget->setWindowOpacity(1.0);
2130 }
2131 }
2132#endif
2133
2134#if QT_CONFIG(tabbar)
2135 if (qobject_cast<QTabBar*>(w)) {
2136 if (!w->testAttribute(Qt::WA_SetFont))
2137 w->setFont(QFont());
2138 if (!w->testAttribute(Qt::WA_SetPalette))
2139 w->setPalette(QPalette());
2140 }
2141#endif
2142
2143 if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) {
2144 rubber->setWindowOpacity(1.0);
2145 rubber->setAttribute(Qt::WA_PaintOnScreen, true);
2146 rubber->setAttribute(Qt::WA_NoSystemBackground, true);
2147 }
2148
2149 if (QFocusFrame *frame = qobject_cast<QFocusFrame *>(w))
2150 frame->setAttribute(Qt::WA_NoSystemBackground, true);
2151
2152 QCommonStyle::unpolish(w);
2153
2154 if (qobject_cast<QScrollBar*>(w)) {
2155 w->setAttribute(Qt::WA_OpaquePaintEvent, true);
2156 w->setAttribute(Qt::WA_Hover, false);
2157 w->setMouseTracking(false);
2158 }
2159}
2160
2161int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const
2162{
2163 Q_D(const QMacStyle);
2164 const int controlSize = getControlSize(opt, widget);
2165 int ret = 0;
2166
2167 switch (metric) {
2168#if QT_CONFIG(tabbar)
2169 case PM_TabCloseIndicatorWidth:
2170 case PM_TabCloseIndicatorHeight:
2171 ret = closeButtonSize;
2172 break;
2173#endif
2174 case PM_ToolBarIconSize:
2175 ret = proxy()->pixelMetric(PM_LargeIconSize);
2176 break;
2177 case PM_FocusFrameVMargin:
2178 case PM_FocusFrameHMargin:
2179 ret = qt_mac_aqua_get_metric(FocusRectOutset);
2180 break;
2181 case PM_DialogButtonsSeparator:
2182 ret = -5;
2183 break;
2184 case PM_DialogButtonsButtonHeight: {
2185 QSize sz;
2186 ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz);
2187 if (sz == QSize(-1, -1))
2188 ret = 32;
2189 else
2190 ret = sz.height();
2191 break; }
2192 case PM_DialogButtonsButtonWidth: {
2193 QSize sz;
2194 ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz);
2195 if (sz == QSize(-1, -1))
2196 ret = 70;
2197 else
2198 ret = sz.width();
2199 break; }
2200
2201 case PM_MenuBarHMargin:
2202 ret = 8;
2203 break;
2204
2205 case PM_MenuBarVMargin:
2206 ret = 0;
2207 break;
2208
2209 case PM_MenuBarPanelWidth:
2210 ret = 0;
2211 break;
2212
2213 case PM_MenuButtonIndicator:
2214 ret = toolButtonArrowSize;
2215 break;
2216
2217 case QStyle::PM_MenuDesktopFrameWidth:
2218 ret = 5;
2219 break;
2220
2221 case PM_CheckBoxLabelSpacing:
2222 case PM_RadioButtonLabelSpacing:
2223 ret = [=] {
2224 if (opt) {
2225 if (opt->state & State_Mini)
2226 return 4;
2227 if (opt->state & State_Small)
2228 return 3;
2229 }
2230 return 2;
2231 } ();
2232 break;
2233 case PM_MenuScrollerHeight:
2234 ret = 15; // I hate having magic numbers in here...
2235 break;
2236 case PM_DefaultFrameWidth:
2237#if QT_CONFIG(mainwindow)
2238 if (widget && (widget->isWindow() || !widget->parentWidget()
2239 || (qobject_cast<const QMainWindow*>(widget->parentWidget())
2240 && static_cast<QMainWindow *>(widget->parentWidget())->centralWidget() == widget))
2241 && qobject_cast<const QAbstractScrollArea *>(widget))
2242 ret = 0;
2243 else
2244#endif
2245 // The combo box popup has no frame.
2246 if (qstyleoption_cast<const QStyleOptionComboBox *>(opt) != 0)
2247 ret = 0;
2248 else
2249 ret = 1;
2250 break;
2251 case PM_MaximumDragDistance:
2252 ret = -1;
2253 break;
2254 case PM_ScrollBarSliderMin:
2255 ret = 24;
2256 break;
2257 case PM_SpinBoxFrameWidth:
2258 ret = qt_mac_aqua_get_metric(EditTextFrameOutset);
2259 break;
2260 case PM_ButtonShiftHorizontal:
2261 case PM_ButtonShiftVertical:
2262 ret = 0;
2263 break;
2264 case PM_SliderLength:
2265 ret = 17;
2266 break;
2267 // Returns the number of pixels to use for the business part of the
2268 // slider (i.e., the non-tickmark portion). The remaining space is shared
2269 // equally between the tickmark regions.
2270 case PM_SliderControlThickness:
2271 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2272 int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width();
2273 int ticks = sl->tickPosition;
2274 int n = 0;
2275 if (ticks & QSlider::TicksAbove)
2276 ++n;
2277 if (ticks & QSlider::TicksBelow)
2278 ++n;
2279 if (!n) {
2280 ret = space;
2281 break;
2282 }
2283
2284 int thick = 6; // Magic constant to get 5 + 16 + 5
2285 if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks)
2286 thick += proxy()->pixelMetric(PM_SliderLength, sl, widget) / 4;
2287
2288 space -= thick;
2289 if (space > 0)
2290 thick += (space * 2) / (n + 2);
2291 ret = thick;
2292 } else {
2293 ret = 0;
2294 }
2295 break;
2296 case PM_SmallIconSize:
2297 ret = int(QStyleHelper::dpiScaled(16.));
2298 break;
2299
2300 case PM_LargeIconSize:
2301 ret = int(QStyleHelper::dpiScaled(32.));
2302 break;
2303
2304 case PM_IconViewIconSize:
2305 ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget);
2306 break;
2307
2308 case PM_ButtonDefaultIndicator:
2309 ret = 0;
2310 break;
2311 case PM_TitleBarHeight: {
2312 NSUInteger style = NSWindowStyleMaskTitled;
2313 if (widget && ((widget->windowFlags() & Qt::Tool) == Qt::Tool))
2314 style |= NSWindowStyleMaskUtilityWindow;
2315 ret = int([NSWindow frameRectForContentRect:NSZeroRect
2316 styleMask:style].size.height);
2317 break; }
2318 case QStyle::PM_TabBarTabHSpace:
2319 switch (d->aquaSizeConstrain(opt, widget)) {
2320 case QStyleHelper::SizeLarge:
2321 ret = QCommonStyle::pixelMetric(metric, opt, widget);
2322 break;
2323 case QStyleHelper::SizeSmall:
2324 ret = 20;
2325 break;
2326 case QStyleHelper::SizeMini:
2327 ret = 16;
2328 break;
2329 case QStyleHelper::SizeDefault:
2330#if QT_CONFIG(tabbar)
2331 const QStyleOptionTab *tb = qstyleoption_cast<const QStyleOptionTab *>(opt);
2332 if (tb && tb->documentMode)
2333 ret = 30;
2334 else
2335#endif
2336 ret = QCommonStyle::pixelMetric(metric, opt, widget);
2337 break;
2338 }
2339 break;
2340 case PM_TabBarTabVSpace:
2341 ret = 4;
2342 break;
2343 case PM_TabBarTabShiftHorizontal:
2344 case PM_TabBarTabShiftVertical:
2345 ret = 0;
2346 break;
2347 case PM_TabBarBaseHeight:
2348 ret = 0;
2349 break;
2350 case PM_TabBarTabOverlap:
2351 ret = 1;
2352 break;
2353 case PM_TabBarBaseOverlap:
2354 switch (d->aquaSizeConstrain(opt, widget)) {
2355 case QStyleHelper::SizeDefault:
2356 case QStyleHelper::SizeLarge:
2357 ret = 11;
2358 break;
2359 case QStyleHelper::SizeSmall:
2360 ret = 8;
2361 break;
2362 case QStyleHelper::SizeMini:
2363 ret = 7;
2364 break;
2365 }
2366 break;
2367 case PM_ScrollBarExtent: {
2368 const QStyleHelper::WidgetSizePolicy size = d->effectiveAquaSizeConstrain(opt, widget);
2369 ret = static_cast<int>([NSScroller
2370 scrollerWidthForControlSize:static_cast<NSControlSize>(size)
2371 scrollerStyle:[NSScroller preferredScrollerStyle]]);
2372 break; }
2373 case PM_IndicatorHeight: {
2374 switch (d->aquaSizeConstrain(opt, widget)) {
2375 case QStyleHelper::SizeDefault:
2376 case QStyleHelper::SizeLarge:
2377 ret = qt_mac_aqua_get_metric(CheckBoxHeight);
2378 break;
2379 case QStyleHelper::SizeMini:
2380 ret = qt_mac_aqua_get_metric(MiniCheckBoxHeight);
2381 break;
2382 case QStyleHelper::SizeSmall:
2383 ret = qt_mac_aqua_get_metric(SmallCheckBoxHeight);
2384 break;
2385 }
2386 break; }
2387 case PM_IndicatorWidth: {
2388 switch (d->aquaSizeConstrain(opt, widget)) {
2389 case QStyleHelper::SizeDefault:
2390 case QStyleHelper::SizeLarge:
2391 ret = qt_mac_aqua_get_metric(CheckBoxWidth);
2392 break;
2393 case QStyleHelper::SizeMini:
2394 ret = qt_mac_aqua_get_metric(MiniCheckBoxWidth);
2395 break;
2396 case QStyleHelper::SizeSmall:
2397 ret = qt_mac_aqua_get_metric(SmallCheckBoxWidth);
2398 break;
2399 }
2400 ++ret;
2401 break; }
2402 case PM_ExclusiveIndicatorHeight: {
2403 switch (d->aquaSizeConstrain(opt, widget)) {
2404 case QStyleHelper::SizeDefault:
2405 case QStyleHelper::SizeLarge:
2406 ret = qt_mac_aqua_get_metric(RadioButtonHeight);
2407 break;
2408 case QStyleHelper::SizeMini:
2409 ret = qt_mac_aqua_get_metric(MiniRadioButtonHeight);
2410 break;
2411 case QStyleHelper::SizeSmall:
2412 ret = qt_mac_aqua_get_metric(SmallRadioButtonHeight);
2413 break;
2414 }
2415 break; }
2416 case PM_ExclusiveIndicatorWidth: {
2417 switch (d->aquaSizeConstrain(opt, widget)) {
2418 case QStyleHelper::SizeDefault:
2419 case QStyleHelper::SizeLarge:
2420 ret = qt_mac_aqua_get_metric(RadioButtonWidth);
2421 break;
2422 case QStyleHelper::SizeMini:
2423 ret = qt_mac_aqua_get_metric(MiniRadioButtonWidth);
2424 break;
2425 case QStyleHelper::SizeSmall:
2426 ret = qt_mac_aqua_get_metric(SmallRadioButtonWidth);
2427 break;
2428 }
2429 ++ret;
2430 break; }
2431 case PM_MenuVMargin:
2432 ret = 4;
2433 break;
2434 case PM_MenuPanelWidth:
2435 ret = 0;
2436 break;
2437 case PM_ToolTipLabelFrameWidth:
2438 ret = 0;
2439 break;
2440 case PM_SizeGripSize: {
2441 QStyleHelper::WidgetSizePolicy aSize;
2442 if (widget && widget->window()->windowType() == Qt::Tool)
2443 aSize = QStyleHelper::SizeSmall;
2444 else
2445 aSize = QStyleHelper::SizeLarge;
2446 const QSize size = qt_aqua_get_known_size(CT_SizeGrip, widget, QSize(), aSize);
2447 ret = size.width();
2448 break; }
2449 case PM_MdiSubWindowFrameWidth:
2450 ret = 1;
2451 break;
2452 case PM_DockWidgetFrameWidth:
2453 ret = 0;
2454 break;
2455 case PM_DockWidgetTitleMargin:
2456 ret = 0;
2457 break;
2458 case PM_DockWidgetSeparatorExtent:
2459 ret = 1;
2460 break;
2461 case PM_ToolBarHandleExtent:
2462 ret = 11;
2463 break;
2464 case PM_ToolBarItemMargin:
2465 ret = 0;
2466 break;
2467 case PM_ToolBarItemSpacing:
2468 ret = 4;
2469 break;
2470 case PM_SplitterWidth:
2471 ret = qMax(7, QApplication::globalStrut().width());
2472 break;
2473 case PM_LayoutLeftMargin:
2474 case PM_LayoutTopMargin:
2475 case PM_LayoutRightMargin:
2476 case PM_LayoutBottomMargin:
2477 {
2478 bool isWindow = false;
2479 if (opt) {
2480 isWindow = (opt->state & State_Window);
2481 } else if (widget) {
2482 isWindow = widget->isWindow();
2483 }
2484
2485 if (isWindow) {
2486 /*
2487 AHIG would have (20, 8, 10) here but that makes
2488 no sense. It would also have 14 for the top margin
2489 but this contradicts both Builder and most
2490 applications.
2491 */
2492 return_SIZE(20, 10, 10); // AHIG
2493 } else {
2494 // hack to detect QTabWidget
2495 if (widget && widget->parentWidget()
2496 && widget->parentWidget()->sizePolicy().controlType() == QSizePolicy::TabWidget) {
2497 if (metric == PM_LayoutTopMargin) {
2498 /*
2499 Builder would have 14 (= 20 - 6) instead of 12,
2500 but that makes the tab look disproportionate.
2501 */
2502 return_SIZE(12, 6, 6); // guess
2503 } else {
2504 return_SIZE(20 /* Builder */, 8 /* guess */, 8 /* guess */);
2505 }
2506 } else {
2507 /*
2508 Child margins are highly inconsistent in AHIG and Builder.
2509 */
2510 return_SIZE(12, 8, 6); // guess
2511 }
2512 }
2513 }
2514 case PM_LayoutHorizontalSpacing:
2515 case PM_LayoutVerticalSpacing:
2516 return -1;
2517 case PM_MenuHMargin:
2518 ret = 0;
2519 break;
2520 case PM_ToolBarExtensionExtent:
2521 ret = 21;
2522 break;
2523 case PM_ToolBarFrameWidth:
2524 ret = 1;
2525 break;
2526 case PM_ScrollView_ScrollBarOverlap:
2527 ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay ?
2528 pixelMetric(PM_ScrollBarExtent, opt, widget) : 0;
2529 break;
2530 default:
2531 ret = QCommonStyle::pixelMetric(metric, opt, widget);
2532 break;
2533 }
2534 return ret;
2535}
2536
2537QPalette QMacStyle::standardPalette() const
2538{
2539 auto platformTheme = QGuiApplicationPrivate::platformTheme();
2540 auto styleNames = platformTheme->themeHint(QPlatformTheme::StyleNames);
2541 if (styleNames.toStringList().contains("macintosh"))
2542 return *platformTheme->palette();
2543 else
2544 return QStyle::standardPalette();
2545}
2546
2547int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w,
2548 QStyleHintReturn *hret) const
2549{
2550 QMacAutoReleasePool pool;
2551
2552 int ret = 0;
2553 switch (sh) {
2554 case SH_Slider_SnapToValue:
2555 case SH_PrintDialog_RightAlignButtons:
2556 case SH_FontDialog_SelectAssociatedText:
2557 case SH_MenuBar_MouseTracking:
2558 case SH_Menu_MouseTracking:
2559 case SH_ComboBox_ListMouseTracking:
2560 case SH_MainWindow_SpaceBelowMenuBar:
2561 case SH_ItemView_ChangeHighlightOnFocus:
2562 ret = 1;
2563 break;
2564 case SH_ToolBox_SelectedPageTitleBold:
2565 ret = 0;
2566 break;
2567 case SH_DialogButtonBox_ButtonsHaveIcons:
2568 ret = 0;
2569 break;
2570 case SH_Menu_SelectionWrap:
2571 ret = false;
2572 break;
2573 case SH_Menu_KeyboardSearch:
2574 ret = true;
2575 break;
2576 case SH_Menu_SpaceActivatesItem:
2577 ret = true;
2578 break;
2579 case SH_Slider_AbsoluteSetButtons:
2580 ret = Qt::LeftButton|Qt::MidButton;
2581 break;
2582 case SH_Slider_PageSetButtons:
2583 ret = 0;
2584 break;
2585 case SH_ScrollBar_ContextMenu:
2586 ret = false;
2587 break;
2588 case SH_TitleBar_AutoRaise:
2589 ret = true;
2590 break;
2591 case SH_Menu_AllowActiveAndDisabled:
2592 ret = false;
2593 break;
2594 case SH_Menu_SubMenuPopupDelay:
2595 ret = 100;
2596 break;
2597 case SH_Menu_SubMenuUniDirection:
2598 ret = true;
2599 break;
2600 case SH_Menu_SubMenuSloppySelectOtherActions:
2601 ret = false;
2602 break;
2603 case SH_Menu_SubMenuResetWhenReenteringParent:
2604 ret = true;
2605 break;
2606 case SH_Menu_SubMenuDontStartSloppyOnLeave:
2607 ret = true;
2608 break;
2609
2610 case SH_ScrollBar_LeftClickAbsolutePosition: {
2611 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
2612 bool result = [defaults boolForKey:@"AppleScrollerPagingBehavior"];
2613 if(QApplication::keyboardModifiers() & Qt::AltModifier)
2614 ret = !result;
2615 else
2616 ret = result;
2617 break; }
2618 case SH_TabBar_PreferNoArrows:
2619 ret = true;
2620 break;
2621 /*
2622 case SH_DialogButtons_DefaultButton:
2623 ret = QDialogButtons::Reject;
2624 break;
2625 */
2626 case SH_GroupBox_TextLabelVerticalAlignment:
2627 ret = Qt::AlignTop;
2628 break;
2629 case SH_ScrollView_FrameOnlyAroundContents:
2630 ret = QCommonStyle::styleHint(sh, opt, w, hret);
2631 break;
2632 case SH_Menu_FillScreenWithScroll:
2633 ret = false;
2634 break;
2635 case SH_Menu_Scrollable:
2636 ret = true;
2637 break;
2638 case SH_RichText_FullWidthSelection:
2639 ret = true;
2640 break;
2641 case SH_BlinkCursorWhenTextSelected:
2642 ret = false;
2643 break;
2644 case SH_ScrollBar_StopMouseOverSlider:
2645 ret = true;
2646 break;
2647 case SH_ListViewExpand_SelectMouseType:
2648 ret = QEvent::MouseButtonRelease;
2649 break;
2650 case SH_TabBar_SelectMouseType:
2651#if QT_CONFIG(tabbar)
2652 if (const QStyleOptionTabBarBase *opt2 = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) {
2653 ret = opt2->documentMode ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease;
2654 } else
2655#endif
2656 {
2657 ret = QEvent::MouseButtonRelease;
2658 }
2659 break;
2660 case SH_ComboBox_Popup:
2661 if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt))
2662 ret = !cmb->editable;
2663 else
2664 ret = 0;
2665 break;
2666 case SH_Workspace_FillSpaceOnMaximize:
2667 ret = true;
2668 break;
2669 case SH_Widget_ShareActivation:
2670 ret = true;
2671 break;
2672 case SH_Header_ArrowAlignment:
2673 ret = Qt::AlignRight;
2674 break;
2675 case SH_TabBar_Alignment: {
2676#if QT_CONFIG(tabwidget)
2677 if (const QTabWidget *tab = qobject_cast<const QTabWidget*>(w)) {
2678 if (tab->documentMode()) {
2679 ret = Qt::AlignLeft;
2680 break;
2681 }
2682 }
2683#endif
2684#if QT_CONFIG(tabbar)
2685 if (const QTabBar *tab = qobject_cast<const QTabBar*>(w)) {
2686 if (tab->documentMode()) {
2687 ret = Qt::AlignLeft;
2688 break;
2689 }
2690 }
2691#endif
2692 ret = Qt::AlignCenter;
2693 } break;
2694 case SH_UnderlineShortcut:
2695 ret = false;
2696 break;
2697 case SH_ToolTipLabel_Opacity:
2698 ret = 242; // About 95%
2699 break;
2700 case SH_Button_FocusPolicy:
2701 ret = Qt::TabFocus;
2702 break;
2703 case SH_EtchDisabledText:
2704 ret = false;
2705 break;
2706 case SH_FocusFrame_Mask: {
2707 ret = true;
2708 if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) {
2709 const uchar fillR = 192, fillG = 191, fillB = 190;
2710 QImage img;
2711
2712 QSize pixmapSize = opt->rect.size();
2713 if (!pixmapSize.isEmpty()) {
2714 QPixmap pix(pixmapSize);
2715 pix.fill(QColor(fillR, fillG, fillB));
2716 QPainter pix_paint(&pix);
2717 proxy()->drawControl(CE_FocusFrame, opt, &pix_paint, w);
2718 pix_paint.end();
2719 img = pix.toImage();
2720 }
2721
2722 const QRgb *sptr = (QRgb*)img.bits(), *srow;
2723 const int sbpl = img.bytesPerLine();
2724 const int w = sbpl/4, h = img.height();
2725
2726 QImage img_mask(img.width(), img.height(), QImage::Format_ARGB32);
2727 QRgb *dptr = (QRgb*)img_mask.bits(), *drow;
2728 const int dbpl = img_mask.bytesPerLine();
2729
2730 for (int y = 0; y < h; ++y) {
2731 srow = sptr+((y*sbpl)/4);
2732 drow = dptr+((y*dbpl)/4);
2733 for (int x = 0; x < w; ++x) {
2734 const int redDiff = qRed(*srow) - fillR;
2735 const int greenDiff = qGreen(*srow) - fillG;
2736 const int blueDiff = qBlue(*srow) - fillB;
2737 const int diff = (redDiff * redDiff) + (greenDiff * greenDiff) + (blueDiff * blueDiff);
2738 (*drow++) = (diff < 10) ? 0xffffffff : 0xff000000;
2739 ++srow;
2740 }
2741 }
2742 QBitmap qmask = QBitmap::fromImage(img_mask);
2743 mask->region = QRegion(qmask);
2744 }
2745 break; }
2746 case SH_TitleBar_NoBorder:
2747 ret = 1;
2748 break;
2749 case SH_RubberBand_Mask:
2750 ret = 0;
2751 break;
2752 case SH_ComboBox_LayoutDirection:
2753 ret = Qt::LeftToRight;
2754 break;
2755 case SH_ItemView_EllipsisLocation:
2756 ret = Qt::AlignHCenter;
2757 break;
2758 case SH_ItemView_ShowDecorationSelected:
2759 ret = true;
2760 break;
2761 case SH_TitleBar_ModifyNotification:
2762 ret = false;
2763 break;
2764 case SH_ScrollBar_RollBetweenButtons:
2765 ret = true;
2766 break;
2767 case SH_WindowFrame_Mask:
2768 ret = false;
2769 break;
2770 case SH_TabBar_ElideMode:
2771 ret = Qt::ElideRight;
2772 break;
2773#if QT_CONFIG(dialogbuttonbox)
2774 case SH_DialogButtonLayout:
2775 ret = QDialogButtonBox::MacLayout;
2776 break;
2777#endif
2778 case SH_FormLayoutWrapPolicy:
2779 ret = QFormLayout::DontWrapRows;
2780 break;
2781 case SH_FormLayoutFieldGrowthPolicy:
2782 ret = QFormLayout::FieldsStayAtSizeHint;
2783 break;
2784 case SH_FormLayoutFormAlignment:
2785 ret = Qt::AlignHCenter | Qt::AlignTop;
2786 break;
2787 case SH_FormLayoutLabelAlignment:
2788 ret = Qt::AlignRight;
2789 break;
2790 case SH_ComboBox_PopupFrameStyle:
2791 ret = QFrame::NoFrame;
2792 break;
2793 case SH_MessageBox_TextInteractionFlags:
2794 ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard;
2795 break;
2796 case SH_SpellCheckUnderlineStyle:
2797 ret = QTextCharFormat::DashUnderline;
2798 break;
2799 case SH_MessageBox_CenterButtons:
2800 ret = false;
2801 break;
2802 case SH_MenuBar_AltKeyNavigation:
2803 ret = false;
2804 break;
2805 case SH_ItemView_MovementWithoutUpdatingSelection:
2806 ret = false;
2807 break;
2808 case SH_FocusFrame_AboveWidget:
2809 ret = true;
2810 break;
2811#if QT_CONFIG(wizard)
2812 case SH_WizardStyle:
2813 ret = QWizard::MacStyle;
2814 break;
2815#endif
2816 case SH_ItemView_ArrowKeysNavigateIntoChildren:
2817 ret = false;
2818 break;
2819 case SH_Menu_FlashTriggeredItem:
2820 ret = true;
2821 break;
2822 case SH_Menu_FadeOutOnHide:
2823 ret = true;
2824 break;
2825 case SH_ItemView_PaintAlternatingRowColorsForEmptyArea:
2826 ret = true;
2827 break;
2828#if QT_CONFIG(tabbar)
2829 case SH_TabBar_CloseButtonPosition:
2830 ret = QTabBar::LeftSide;
2831 break;
2832#endif
2833 case SH_DockWidget_ButtonsHaveFrame:
2834 ret = false;
2835 break;
2836 case SH_ScrollBar_Transient:
2837 if ((qobject_cast<const QScrollBar *>(w) && w->parent() &&
2838 qobject_cast<QAbstractScrollArea*>(w->parent()->parent()))
2839#ifndef QT_NO_ACCESSIBILITY
2840 || (opt && QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ScrollBar))
2841#endif
2842 ) {
2843 ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay;
2844 }
2845 break;
2846#if QT_CONFIG(itemviews)
2847 case SH_ItemView_ScrollMode:
2848 ret = QAbstractItemView::ScrollPerPixel;
2849 break;
2850#endif
2851 case SH_TitleBar_ShowToolTipsOnButtons:
2852 // min/max/close buttons on windows don't show tool tips
2853 ret = false;
2854 break;
2855 case SH_ComboBox_AllowWheelScrolling:
2856 ret = false;
2857 break;
2858 case SH_SpinBox_ButtonsInsideFrame:
2859 ret = false;
2860 break;
2861 case SH_Table_GridLineColor:
2862 ret = int(qt_mac_toQColor(NSColor.gridColor).rgb());
2863 break;
2864 default:
2865 ret = QCommonStyle::styleHint(sh, opt, w, hret);
2866 break;
2867 }
2868 return ret;
2869}
2870
2871QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap,
2872 const QStyleOption *opt) const
2873{
2874 switch (iconMode) {
2875 case QIcon::Disabled: {
2876 QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
2877 int imgh = img.height();
2878 int imgw = img.width();
2879 QRgb pixel;
2880 for (int y = 0; y < imgh; ++y) {
2881 for (int x = 0; x < imgw; ++x) {
2882 pixel = img.pixel(x, y);
2883 img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel),
2884 qAlpha(pixel) / 2));
2885 }
2886 }
2887 return QPixmap::fromImage(img);
2888 }
2889 default:
2890 ;
2891 }
2892 return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt);
2893}
2894
2895
2896QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt,
2897 const QWidget *widget) const
2898{
2899 // The default implementation of QStyle::standardIconImplementation() is to call standardPixmap()
2900 // I don't want infinite recursion so if we do get in that situation, just return the Window's
2901 // standard pixmap instead (since there is no mac-specific icon then). This should be fine until
2902 // someone changes how Windows standard
2903 // pixmap works.
2904 static bool recursionGuard = false;
2905
2906 if (recursionGuard)
2907 return QCommonStyle::standardPixmap(standardPixmap, opt, widget);
2908
2909 recursionGuard = true;
2910 QIcon icon = proxy()->standardIcon(standardPixmap, opt, widget);
2911 recursionGuard = false;
2912 int size;
2913 switch (standardPixmap) {
2914 default:
2915 size = 32;
2916 break;
2917 case SP_MessageBoxCritical:
2918 case SP_MessageBoxQuestion:
2919 case SP_MessageBoxInformation:
2920 case SP_MessageBoxWarning:
2921 size = 64;
2922 break;
2923 }
2924 return icon.pixmap(qt_getWindow(widget), QSize(size, size));
2925}
2926
2927void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p,
2928 const QWidget *w) const
2929{
2930 Q_D(const QMacStyle);
2931 const AppearanceSync appSync;
2932 QMacCGContext cg(p);
2933 QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr;
2934 d->resolveCurrentNSView(window);
2935 switch (pe) {
2936 case PE_IndicatorArrowUp:
2937 case PE_IndicatorArrowDown:
2938 case PE_IndicatorArrowRight:
2939 case PE_IndicatorArrowLeft: {
2940 p->save();
2941 p->setRenderHint(QPainter::Antialiasing);
2942 const int xOffset = 1; // FIXME: opt->direction == Qt::LeftToRight ? 2 : -1;
2943 qreal halfSize = 0.5 * qMin(opt->rect.width(), opt->rect.height());
2944 const qreal penWidth = qMax(halfSize / 3.0, 1.25);
2945#if QT_CONFIG(toolbutton)
2946 if (const QToolButton *tb = qobject_cast<const QToolButton *>(w)) {
2947 // When stroking the arrow, make sure it fits in the tool button
2948 if (tb->arrowType() != Qt::NoArrow
2949 || tb->popupMode() == QToolButton::MenuButtonPopup)
2950 halfSize -= penWidth;
2951 }
2952#endif
2953
2954 QMatrix matrix;
2955 matrix.translate(opt->rect.center().x() + xOffset, opt->rect.center().y() + 2);
2956 QPainterPath path;
2957 switch(pe) {
2958 default:
2959 case PE_IndicatorArrowDown:
2960 break;
2961 case PE_IndicatorArrowUp:
2962 matrix.rotate(180);
2963 break;
2964 case PE_IndicatorArrowLeft:
2965 matrix.rotate(90);
2966 break;
2967 case PE_IndicatorArrowRight:
2968 matrix.rotate(-90);
2969 break;
2970 }
2971 p->setMatrix(matrix);
2972
2973 path.moveTo(-halfSize, -halfSize * 0.5);
2974 path.lineTo(0.0, halfSize * 0.5);
2975 path.lineTo(halfSize, -halfSize * 0.5);
2976
2977 const QPen arrowPen(opt->palette.text(), penWidth,
2978 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
2979 p->strokePath(path, arrowPen);
2980 p->restore();
2981 break; }
2982#if QT_CONFIG(tabbar)
2983 case PE_FrameTabBarBase:
2984 if (const QStyleOptionTabBarBase *tbb
2985 = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) {
2986 if (tbb->documentMode) {
2987 p->save();
2988 drawTabBase(p, tbb, w);
2989 p->restore();
2990 return;
2991 }
2992#if QT_CONFIG(tabwidget)
2993 QRegion region(tbb->rect);
2994 region -= tbb->tabBarRect;
2995 p->save();
2996 p->setClipRegion(region);
2997 QStyleOptionTabWidgetFrame twf;
2998 twf.QStyleOption::operator=(*tbb);
2999 twf.shape = tbb->shape;
3000 switch (QMacStylePrivate::tabDirection(twf.shape)) {
3001 case QMacStylePrivate::North:
3002 twf.rect = twf.rect.adjusted(0, 0, 0, 10);
3003 break;
3004 case QMacStylePrivate::South:
3005 twf.rect = twf.rect.adjusted(0, -10, 0, 0);
3006 break;
3007 case QMacStylePrivate::West:
3008 twf.rect = twf.rect.adjusted(0, 0, 10, 0);
3009 break;
3010 case QMacStylePrivate::East:
3011 twf.rect = twf.rect.adjusted(0, -10, 0, 0);
3012 break;
3013 }
3014 proxy()->drawPrimitive(PE_FrameTabWidget, &twf, p, w);
3015 p->restore();
3016#endif
3017 }
3018 break;
3019#endif
3020 case PE_PanelTipLabel:
3021 p->fillRect(opt->rect, opt->palette.brush(QPalette::ToolTipBase));
3022 break;
3023 case PE_FrameGroupBox:
3024 if (const auto *groupBox = qstyleoption_cast<const QStyleOptionFrame *>(opt))
3025 if (groupBox->features & QStyleOptionFrame::Flat) {
3026 QCommonStyle::drawPrimitive(pe, groupBox, p, w);
3027 break;
3028 }
3029#if QT_CONFIG(tabwidget)
3030 Q_FALLTHROUGH();
3031 case PE_FrameTabWidget:
3032#endif
3033 {
3034 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Box, QStyleHelper::SizeLarge);
3035 auto *box = static_cast<NSBox *>(d->cocoaControl(cw));
3036 // FIXME Since macOS 10.14, simply calling drawRect: won't display anything anymore.
3037 // The AppKit team is aware of this and has proposed a couple of solutions.
3038 // The first solution was to call displayRectIgnoringOpacity:inContext: instead.
3039 // However, it doesn't seem to work on 10.13. More importantly, dark mode on 10.14
3040 // is extremely slow. Light mode works fine.
3041 // The second solution is to subclass NSBox and reimplement a trivial drawRect: which
3042 // would only call super. This works without any issue on 10.13, but a double border
3043 // shows on 10.14 in both light and dark modes.
3044 // The code below picks what works on each version and mode. On 10.13 and earlier, we
3045 // simply call drawRect: on a regular NSBox. On 10.14, we call displayRectIgnoringOpacity:
3046 // inContext:, but only in light mode. In dark mode, we use a custom NSBox subclass,
3047 // QDarkNSBox, of type NSBoxCustom. Its appearance is close enough to the real thing so
3048 // we can use this for now.
3049 auto adjustedRect = opt->rect;
3050 bool needTranslation = false;
3051 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave
3052 && !qt_mac_applicationIsInDarkMode()) {
3053 // Another surprise from AppKit (SDK 10.14) - -displayRectIgnoringOpacity:
3054 // is different from drawRect: for some Apple-known reason box is smaller
3055 // in height than we need, resulting in tab buttons sitting too high/not
3056 // centered. Attempts to play with insets etc did not work - the same wrong
3057 // height. Simple translation is not working (too much space "at bottom"),
3058 // so we make it bigger and translate (otherwise it's clipped at bottom btw).
3059 adjustedRect.adjust(0, 0, 0, 3);
3060 needTranslation = true;
3061 }
3062 d->drawNSViewInRect(box, adjustedRect, p, ^(CGContextRef ctx, const CGRect &rect) {
3063#if QT_CONFIG(tabwidget)
3064 if (QTabWidget *tabWidget = qobject_cast<QTabWidget *>(opt->styleObject))
3065 clipTabBarFrame(opt, this, ctx);
3066#endif
3067 CGContextTranslateCTM(ctx, 0, rect.origin.y + rect.size.height);
3068 CGContextScaleCTM(ctx, 1, -1);
3069 if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave
3070 || [box isMemberOfClass:QDarkNSBox.class]) {
3071 [box drawRect:rect];
3072 } else {
3073 if (needTranslation)
3074 CGContextTranslateCTM(ctx, 0.0, 4.0);
3075 [box displayRectIgnoringOpacity:box.bounds inContext:NSGraphicsContext.currentContext];
3076 }
3077 });
3078 break;
3079 }
3080 case PE_IndicatorToolBarSeparator: {
3081 QPainterPath path;
3082 if (opt->state & State_Horizontal) {
3083 int xpoint = opt->rect.center().x();
3084 path.moveTo(xpoint + 0.5, opt->rect.top() + 1);
3085 path.lineTo(xpoint + 0.5, opt->rect.bottom());
3086 } else {
3087 int ypoint = opt->rect.center().y();
3088 path.moveTo(opt->rect.left() + 2 , ypoint + 0.5);
3089 path.lineTo(opt->rect.right() + 1, ypoint + 0.5);
3090 }
3091 QPainterPathStroker theStroker;
3092 theStroker.setCapStyle(Qt::FlatCap);
3093 theStroker.setDashPattern(QVector<qreal>() << 1 << 2);
3094 path = theStroker.createStroke(path);
3095 const auto dark = qt_mac_applicationIsInDarkMode() ? opt->palette.dark().color().darker()
3096 : QColor(0, 0, 0, 119);
3097 p->fillPath(path, dark);
3098 }
3099 break;
3100 case PE_FrameWindow:
3101 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
3102 if (w && w->inherits("QMdiSubWindow")) {
3103 p->save();
3104 p->setPen(QPen(frame->palette.dark().color(), frame->lineWidth));
3105 p->setBrush(frame->palette.window());
3106 p->drawRect(frame->rect);
3107 p->restore();
3108 }
3109 }
3110 break;
3111 case PE_IndicatorDockWidgetResizeHandle: {
3112 // The docwidget resize handle is drawn as a one-pixel wide line.
3113 p->save();
3114 if (