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