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