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