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