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