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