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 tf.bezeled = YES;
3561 static_cast<NSTextFieldCell *>(tf.cell).bezelStyle = isRounded ? NSTextFieldRoundedBezel : NSTextFieldSquareBezel;
3562 tf.frame = opt->rect.toCGRect();
3563 d->drawNSViewInRect(tf, opt->rect, p, ^(CGContextRef, const CGRect &rect) {
3564 if (!isDarkMode()) {
3565 // In 'Dark' mode controls are transparent, so we do not
3566 // over-paint the (potentially custom) color in the background.
3567 // In 'Light' mode we have to care about the correct
3568 // background color. See the comments below for PE_PanelLineEdit.
3569 CGContextRef cgContext = NSGraphicsContext.currentContext.CGContext;
3570 // See QMacCGContext, here we expect bitmap context created with
3571 // color space 'kCGColorSpaceSRGB', if it's something else - we
3572 // give up.
3573 if (cgContext ? bool(CGBitmapContextGetColorSpace(cgContext)) : false) {
3574 tf.drawsBackground = YES;
3575 const QColor bgColor = frame->palette.brush(QPalette::Base).color();
3576 tf.backgroundColor = [NSColor colorWithSRGBRed:bgColor.redF()
3577 green:bgColor.greenF()
3578 blue:bgColor.blueF()
3579 alpha:bgColor.alphaF()];
3580 if (bgColor.alpha() != 255) {
3581 // No way we can have it bezeled and transparent ...
3582 tf.bordered = YES;
3583 }
3584 }
3585 }
3586
3587 CGRect fixedRect = rect;
3588 if (qt_apple_runningWithLiquidGlass()) {
3589 // The text edit cell is drawn with a little offset to the left and
3590 // the size increase compared to the 'rect' we want it to be drawn in. As a
3591 // result, the cell's 'outline' is clipped away. Adjusting the rectangle
3592 // for this, so that it's inside the clip rect, as it was before Tahoe.
3593 fixedRect = CGRectInset(rect, 1., 1.);
3594 }
3595 [tf.cell drawWithFrame:fixedRect inView:tf];
3596 });
3597 } else {
3598 QCommonStyle::drawPrimitive(pe, opt, p, w);
3599 }
3600 }
3601 break;
3602 case PE_PanelLineEdit:
3603 {
3604 const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(opt);
3605 if (isDarkMode() || (panel && panel->lineWidth <= 0)) {
3606 // QCommonStyle::drawPrimitive(PE_PanelLineEdit) fill the background with
3607 // a proper color, defined in opt->palette and then, if lineWidth > 0, it
3608 // calls QMacStyle::drawPrimitive(PE_FrameLineEdit). We use NSTextFieldCell
3609 // to handle PE_FrameLineEdit, which will use system-default background.
3610 // In 'Dark' mode it's transparent and thus it's not over-painted.
3611 QCommonStyle::drawPrimitive(pe, opt, p, w);
3612 } else {
3613 // In 'Light' mode, if panel->lineWidth > 0, we have to use the correct
3614 // background color when drawing PE_FrameLineEdit, so let's call it
3615 // directly and set the proper color there.
3616 drawPrimitive(PE_FrameLineEdit, opt, p, w);
3617 }
3618
3619 // Draw the focus frame for widgets other than QLineEdit (e.g. for line edits in Webkit).
3620 // Focus frame is drawn outside the rectangle passed in the option-rect.
3621 if (panel) {
3622#if QT_CONFIG(lineedit)
3623 if ((opt->state & State_HasFocus) && !qobject_cast<const QLineEdit*>(w)) {
3624 int vmargin = pixelMetric(QStyle::PM_FocusFrameVMargin);
3625 int hmargin = pixelMetric(QStyle::PM_FocusFrameHMargin);
3626 QStyleOptionFrame focusFrame = *panel;
3627 focusFrame.rect = panel->rect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
3628 drawControl(CE_FocusFrame, &focusFrame, p, w);
3629 }
3630#endif
3631 }
3632 }
3633 break;
3634 case PE_PanelScrollAreaCorner: {
3635 const QBrush brush(opt->palette.brush(QPalette::Base));
3636 p->fillRect(opt->rect, brush);
3637 p->setPen(QPen(QColor(217, 217, 217)));
3638 p->drawLine(opt->rect.topLeft(), opt->rect.topRight());
3639 p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft());
3640 } break;
3641 case PE_FrameStatusBarItem:
3642 break;
3643#if QT_CONFIG(tabbar)
3644 case PE_IndicatorTabClose: {
3645 // Make close button visible only on the hovered tab.
3646 QTabBar *tabBar = qobject_cast<QTabBar*>(w->parentWidget());
3647 const QWidget *closeBtn = w;
3648 if (!tabBar) {
3649 // QStyleSheetStyle instead of CloseButton (which has
3650 // a QTabBar as a parent widget) uses the QTabBar itself:
3651 tabBar = qobject_cast<QTabBar *>(const_cast<QWidget*>(w));
3652 closeBtn = decltype(closeBtn)(property("_q_styleSheetRealCloseButton").value<void *>());
3653 }
3654 if (tabBar) {
3655 const bool documentMode = tabBar->documentMode();
3656 const QTabBarPrivate *tabBarPrivate = static_cast<QTabBarPrivate *>(QObjectPrivate::get(tabBar));
3657 const int hoveredTabIndex = tabBarPrivate->hoveredTabIndex();
3658 if (!documentMode ||
3659 (hoveredTabIndex != -1 && ((closeBtn == tabBar->tabButton(hoveredTabIndex, QTabBar::LeftSide)) ||
3660 (closeBtn == tabBar->tabButton(hoveredTabIndex, QTabBar::RightSide))))) {
3661 const bool hover = (opt->state & State_MouseOver);
3662 const bool selected = (opt->state & State_Selected);
3663 const bool pressed = (opt->state & State_Sunken);
3664 drawTabCloseButton(p, hover, selected, pressed, documentMode);
3665 }
3666 }
3667 } break;
3668#endif // QT_CONFIG(tabbar)
3669 case PE_PanelStatusBar: {
3670 p->fillRect(opt->rect, opt->palette.window());
3671
3672 // Draw the black separator line at the top of the status bar.
3673 if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active))
3674 p->setPen(titlebarSeparatorLineActive);
3675 else
3676 p->setPen(titlebarSeparatorLineInactive);
3677 p->drawLine(opt->rect.left(), opt->rect.top(), opt->rect.right(), opt->rect.top());
3678
3679 break;
3680 }
3681 case PE_PanelMenu: {
3682 p->save();
3683 p->fillRect(opt->rect, Qt::transparent);
3684 p->setPen(Qt::transparent);
3685 p->setBrush(opt->palette.window());
3686 p->setRenderHint(QPainter::Antialiasing, true);
3687 const QPainterPath path = d->windowPanelPath(opt->rect);
3688 p->drawPath(path);
3689 p->restore();
3690 } break;
3691
3692 default:
3693 QCommonStyle::drawPrimitive(pe, opt, p, w);
3694 break;
3695 }
3696}
3697
3698static QPixmap darkenPixmap(const QPixmap &pixmap)
3699{
3700 QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
3701 int imgh = img.height();
3702 int imgw = img.width();
3703 int h, s, v, a;
3704 QRgb pixel;
3705 for (int y = 0; y < imgh; ++y) {
3706 for (int x = 0; x < imgw; ++x) {
3707 pixel = img.pixel(x, y);
3708 a = qAlpha(pixel);
3709 QColor hsvColor(pixel);
3710 hsvColor.getHsv(&h, &s, &v);
3711 s = qMin(100, s * 2);
3712 v = v / 2;
3713 hsvColor.setHsv(h, s, v);
3714 pixel = hsvColor.rgb();
3715 img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), a));
3716 }
3717 }
3718 return QPixmap::fromImage(img);
3719}
3720
3721void QMacStylePrivate::setupVerticalInvertedXform(CGContextRef cg, bool reverse, bool vertical, const CGRect &rect) const
3722{
3723 if (vertical) {
3724 CGContextTranslateCTM(cg, rect.size.height, 0);
3725 CGContextRotateCTM(cg, M_PI_2);
3726 }
3727 if (vertical != reverse) {
3728 CGContextTranslateCTM(cg, rect.size.width, 0);
3729 CGContextScaleCTM(cg, -1, 1);
3730 }
3731}
3732
3733void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p,
3734 const QWidget *w) const
3735{
3736 Q_D(const QMacStyle);
3737 const QMacAutoReleasePool pool;
3738 QMacCGContext cg(p);
3739 if (!cg)
3740 qCWarning(lcMacStyle) << "drawControl:" << ce << "invalid (nullptr) graphics context";
3741
3742 QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr;
3743 d->resolveCurrentNSView(window);
3744 switch (ce) {
3745 case CE_HeaderSection:
3746 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) {
3747 State flags = header->state;
3748 QRect ir = header->rect;
3749
3750
3751#if 0 // FIXME: What's this solving exactly?
3752 bool noVerticalHeader = true;
3753#if QT_CONFIG(tableview)
3754 if (w)
3755 if (const QTableView *table = qobject_cast<const QTableView *>(w->parentWidget()))
3756 noVerticalHeader = !table->verticalHeader()->isVisible();
3757#endif
3758
3759 const bool drawLeftBorder = header->orientation == Qt::Vertical
3760 || header->position == QStyleOptionHeader::OnlyOneSection
3761 || (header->position == QStyleOptionHeader::Beginning && noVerticalHeader);
3762#endif
3763
3764 const bool pressed = (flags & State_Sunken) && !(flags & State_On);
3765 p->fillRect(ir, pressed ? header->palette.dark() : header->palette.button());
3766 p->setPen(QPen(header->palette.dark(), 1.0));
3767 if (header->orientation == Qt::Horizontal)
3768 p->drawLine(QLineF(ir.right() + 0.5, ir.top() + headerSectionSeparatorInset,
3769 ir.right() + 0.5, ir.bottom() - headerSectionSeparatorInset));
3770 else
3771 p->drawLine(QLineF(ir.left() + headerSectionSeparatorInset, ir.bottom(),
3772 ir.right() - headerSectionSeparatorInset, ir.bottom()));
3773 }
3774
3775 break;
3776 case CE_ToolButtonLabel:
3777 if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
3778 QStyleOptionToolButton myTb = *tb;
3779 myTb.state &= ~State_AutoRaise;
3780#if QT_CONFIG(accessibility)
3781 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) {
3782 QRect cr = tb->rect;
3783 int shiftX = 0;
3784 int shiftY = 0;
3785 bool needText = false;
3786 int alignment = 0;
3787 bool down = tb->state & (State_Sunken | State_On);
3788 if (down) {
3789 shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, w);
3790 shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, tb, w);
3791 }
3792 // The down state is special for QToolButtons in a toolbar on the Mac
3793 // The text is a bit bolder and gets a drop shadow and the icons are also darkened.
3794 // This doesn't really fit into any particular case in QIcon, so we
3795 // do the majority of the work ourselves.
3796 if (!(tb->features & QStyleOptionToolButton::Arrow)) {
3797 Qt::ToolButtonStyle tbstyle = tb->toolButtonStyle;
3798 if (tb->icon.isNull() && !tb->text.isEmpty())
3799 tbstyle = Qt::ToolButtonTextOnly;
3800
3801 switch (tbstyle) {
3802 case Qt::ToolButtonTextOnly: {
3803 needText = true;
3804 alignment = Qt::AlignCenter;
3805 break; }
3806 case Qt::ToolButtonIconOnly:
3807 case Qt::ToolButtonTextBesideIcon:
3808 case Qt::ToolButtonTextUnderIcon: {
3809 QRect pr = cr;
3810 QIcon::Mode iconMode = (tb->state & State_Enabled) ? QIcon::Normal
3811 : QIcon::Disabled;
3812 QIcon::State iconState = (tb->state & State_On) ? QIcon::On
3813 : QIcon::Off;
3814 QPixmap pixmap = tb->icon.pixmap(tb->rect.size().boundedTo(tb->iconSize), p->device()->devicePixelRatio(),
3815 iconMode, iconState);
3816
3817 // Draw the text if it's needed.
3818 if (tb->toolButtonStyle != Qt::ToolButtonIconOnly) {
3819 needText = true;
3820 QSizeF size = pixmap.deviceIndependentSize();
3821 if (tb->toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
3822 pr.setHeight(size.height() + 6);
3823 cr.adjust(0, pr.bottom(), 0, -3);
3824 alignment |= Qt::AlignCenter;
3825 } else {
3826 pr.setWidth(size.width() + 8);
3827 cr.adjust(pr.right(), 0, 0, 0);
3828 alignment |= Qt::AlignLeft | Qt::AlignVCenter;
3829 }
3830 }
3831 if (opt->state & State_Sunken) {
3832 pr.translate(shiftX, shiftY);
3833 pixmap = darkenPixmap(pixmap);
3834 }
3835 proxy()->drawItemPixmap(p, pr, Qt::AlignCenter, pixmap);
3836 break; }
3837 default:
3838 Q_ASSERT(false);
3839 break;
3840 }
3841
3842 if (needText) {
3843 QPalette pal = tb->palette;
3844 QPalette::ColorRole role = QPalette::NoRole;
3845 if (!proxy()->styleHint(SH_UnderlineShortcut, tb, w))
3846 alignment |= Qt::TextHideMnemonic;
3847 if (down)
3848 cr.translate(shiftX, shiftY);
3849 if (tbstyle == Qt::ToolButtonTextOnly
3850 || (tbstyle != Qt::ToolButtonTextOnly && !down)) {
3851 QPen pen = p->pen();
3852 QColor light = down || isDarkMode() ? Qt::black : Qt::white;
3853 light.setAlphaF(0.375f);
3854 p->setPen(light);
3855 p->drawText(cr.adjusted(0, 1, 0, 1), alignment, tb->text);
3856 p->setPen(pen);
3857 if (down && tbstyle == Qt::ToolButtonTextOnly) {
3858 pal = QApplication::palette("QMenu");
3859 pal.setCurrentColorGroup(tb->palette.currentColorGroup());
3860 role = QPalette::HighlightedText;
3861 }
3862 }
3863 proxy()->drawItemText(p, cr, alignment, pal,
3864 tb->state & State_Enabled, tb->text, role);
3865 }
3866 } else {
3867 QCommonStyle::drawControl(ce, &myTb, p, w);
3868 }
3869 } else
3870#endif // QT_CONFIG(accessibility)
3871 {
3872 QCommonStyle::drawControl(ce, &myTb, p, w);
3873 }
3874 }
3875 break;
3876 case CE_ToolBoxTabShape:
3877 QCommonStyle::drawControl(ce, opt, p, w);
3878 break;
3879 case CE_PushButtonBevel:
3880 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
3881 if (!(btn->state & (State_Raised | State_Sunken | State_On)))
3882 break;
3883
3884 if (btn->features & QStyleOptionButton::CommandLinkButton) {
3885 QCommonStyle::drawControl(ce, opt, p, w);
3886 break;
3887 }
3888
3889 const bool hasFocus = btn->state & State_HasFocus;
3890 const bool isActive = btn->state & State_Active;
3891
3892 // a focused auto-default button within an active window
3893 // takes precedence over a normal default button
3894 if ((btn->features & QStyleOptionButton::AutoDefaultButton)
3895 && isActive && hasFocus)
3896 d->autoDefaultButton = btn->styleObject;
3897 else if (d->autoDefaultButton == btn->styleObject)
3898 d->autoDefaultButton = nullptr;
3899
3900 const bool isEnabled = btn->state & State_Enabled;
3901 const bool isPressed = btn->state & State_Sunken;
3902 const bool isHighlighted = isActive &&
3903 ((btn->state & State_On)
3904 || (btn->features & QStyleOptionButton::DefaultButton)
3905 || (btn->features & QStyleOptionButton::AutoDefaultButton
3906 && d->autoDefaultButton == btn->styleObject));
3907 const bool hasMenu = btn->features & QStyleOptionButton::HasMenu;
3908 const auto ct = cocoaControlType(btn, w);
3909 const auto cs = d->effectiveAquaSizeConstrain(btn, w);
3910 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
3911 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
3912 // Ensure same size and location as we used to have with HITheme.
3913 // This is more convoluted than we initially thought. See for example
3914 // differences between plain and menu button frames.
3915 const QRectF frameRect = cw.adjustedControlFrame(btn->rect);
3916 pb.frame = frameRect.toCGRect();
3917
3918 pb.enabled = isEnabled;
3919
3920 // With the 'momentary push in' type this gives an impression of a
3921 // button in a 'pressed' state (the 'momentary push in' does
3922 // not show its state otherwise):
3923 [pb highlight:isPressed];
3924
3925
3926 if (cw.type == QMacStylePrivate::Button_SquareButton) {
3927 pb.state = isHighlighted && !isPressed ? NSControlStateValueOn : NSControlStateValueOff;
3928 } else {
3929 // For default/checked button this will give the required
3930 // button accent color:
3931 pb.keyEquivalent = isHighlighted ? @"\r" : @"";
3932 }
3933
3934 d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) {
3935 [pb.cell drawBezelWithFrame:r inView:pb.superview];
3936 });
3937 [pb highlight:NO];
3938
3939 if (hasMenu && cw.type == QMacStylePrivate::Button_SquareButton) {
3940 // Using -[NSPopuButtonCell drawWithFrame:inView:] above won't do
3941 // it right because we don't set the text in the native button.
3942 const int mbi = proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, w);
3943 const auto ir = frameRect.toRect();
3944 int arrowYOffset = 0;
3945#if 0
3946 // FIXME What's this for again?
3947 if (!w) {
3948 // adjustment for Qt Quick Controls
3949 arrowYOffset -= ir.top();
3950 if (cw.second == QStyleHelper::SizeSmall)
3951 arrowYOffset += 1;
3952 }
3953#endif
3954 const auto ar = visualRect(btn->direction, ir, QRect(ir.right() - mbi - 6, ir.height() / 2 - arrowYOffset, mbi, mbi));
3955
3956 QStyleOption arrowOpt = *opt;
3957 arrowOpt.rect = ar;
3958 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, w);
3959 }
3960 }
3961 break;
3962 case CE_PushButtonLabel:
3963 if (const QStyleOptionButton *b = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
3964 QStyleOptionButton btn(*b);
3965 // We really don't want the label to be drawn the same as on
3966 // windows style if it has an icon and text, then it should be more like a
3967 // tab. So, cheat a little here. However, if it *is* only an icon
3968 // the windows style works great, so just use that implementation.
3969 const bool isEnabled = btn.state & State_Enabled;
3970 const bool hasMenu = btn.features & QStyleOptionButton::HasMenu;
3971 const bool hasIcon = !btn.icon.isNull();
3972 const bool hasText = !btn.text.isEmpty();
3973 const bool isActive = btn.state & State_Active;
3974 const bool isPressed = btn.state & State_Sunken;
3975 const bool isDefault = (btn.features & QStyleOptionButton::DefaultButton && !d->autoDefaultButton)
3976 || d->autoDefaultButton == btn.styleObject;
3977
3978 // cocoaControlType evaluates the type based on the control's geometry, not on the
3979 // label's geometry
3980 const QRect oldRect = btn.rect;
3981 if (w)
3982 btn.rect = w->rect();
3983 const auto ct = cocoaControlType(&btn, w);
3984 btn.rect = oldRect;
3985
3986 if (!hasMenu && ct != QMacStylePrivate::Button_SquareButton) {
3987 if (isPressed || (isActive && isEnabled && ((btn.state & State_On) || isDefault)))
3988 btn.palette.setColor(QPalette::ButtonText, Qt::white);
3989 }
3990
3991 if (isEnabled && !isDarkMode()) {
3992 if (!isDefault && !(btn.state & State_On)) {
3993 // It's a gray button, white text color (if set in the
3994 // previous statement) would be almost invisible.
3995 btn.palette.setColor(QPalette::ButtonText, Qt::black);
3996 }
3997 }
3998
3999 if ((!hasIcon && !hasMenu) || (hasIcon && !hasText)) {
4000 QCommonStyle::drawControl(ce, &btn, p, w);
4001 } else {
4002 QRect freeContentRect = btn.rect;
4003 QRect textRect = itemTextRect(
4004 btn.fontMetrics, freeContentRect, Qt::AlignCenter, isEnabled, btn.text);
4005 if (hasMenu) {
4006 if (ct == QMacStylePrivate::Button_SquareButton)
4007 textRect.moveTo(w ? 8 : 11, textRect.top());
4008 else
4009 textRect.moveTo(w ? (15 - pushButtonBevelRectOffsets[d->effectiveAquaSizeConstrain(b, w)])
4010 : 11, textRect.top()); // Supports Qt Quick Controls
4011 }
4012 // Draw the icon:
4013 if (hasIcon) {
4014 int contentW = textRect.width();
4015 if (hasMenu)
4016 contentW += proxy()->pixelMetric(PM_MenuButtonIndicator) + 4;
4017 QIcon::Mode mode = isEnabled ? QIcon::Normal : QIcon::Disabled;
4018 if (mode == QIcon::Normal && btn.state & State_HasFocus)
4019 mode = QIcon::Active;
4020 // Decide if the icon is should be on or off:
4021 QIcon::State state = QIcon::Off;
4022 if (btn.state & State_On)
4023 state = QIcon::On;
4024 QPixmap pixmap = btn.icon.pixmap(btn.iconSize, p->device()->devicePixelRatio(), mode, state);
4025 QSizeF pixmapSize = pixmap.deviceIndependentSize();
4026 contentW += pixmapSize.width() + QMacStylePrivate::PushButtonContentPadding;
4027 int iconLeftOffset = freeContentRect.x() + (freeContentRect.width() - contentW) / 2;
4028 int iconTopOffset = freeContentRect.y() + (freeContentRect.height() - pixmapSize.height()) / 2;
4029 QRect iconDestRect(iconLeftOffset, iconTopOffset, pixmapSize.width(), pixmapSize.height());
4030 QRect visualIconDestRect = visualRect(btn.direction, freeContentRect, iconDestRect);
4031 proxy()->drawItemPixmap(p, visualIconDestRect, Qt::AlignLeft | Qt::AlignVCenter, pixmap);
4032 int newOffset = iconDestRect.x() + iconDestRect.width()
4033 + QMacStylePrivate::PushButtonContentPadding - textRect.x();
4034 textRect.adjust(newOffset, 0, newOffset, 0);
4035 }
4036 // Draw the text:
4037 if (hasText) {
4038 textRect = visualRect(btn.direction, freeContentRect, textRect);
4039 proxy()->drawItemText(p, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, btn.palette,
4040 isEnabled, btn.text, QPalette::ButtonText);
4041 }
4042 }
4043 }
4044 break;
4045#if QT_CONFIG(combobox)
4046 case CE_ComboBoxLabel:
4047 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
4048 auto comboCopy = *cb;
4049 comboCopy.direction = Qt::LeftToRight;
4050 // The rectangle will be adjusted to SC_ComboBoxEditField with comboboxEditBounds()
4051 QCommonStyle::drawControl(CE_ComboBoxLabel, &comboCopy, p, w);
4052 }
4053 break;
4054#endif // #if QT_CONFIG(combobox)
4055#if QT_CONFIG(tabbar)
4056 case CE_TabBarTabShape:
4057 if (const auto *tabOpt = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
4058 if (tabOpt->documentMode) {
4059 p->save();
4060 bool isUnified = false;
4061 if (w) {
4062 QRect tabRect = tabOpt->rect;
4063 QPoint windowTabStart = w->mapTo(w->window(), tabRect.topLeft());
4064 isUnified = isInMacUnifiedToolbarArea(w->window(), windowTabStart.y());
4065 }
4066
4067 const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, opt, w);
4068 drawTabShape(p, tabOpt, isUnified, tabOverlap);
4069
4070 p->restore();
4071 return;
4072 }
4073
4074 // We use NSButton and NSPopupButton (their 'cells') to draw bezels,
4075 // imitating tabs in a tab widget (looking similar to native segmented
4076 // control). Since native buttons are horizontally aligned, we have to
4077 // apply rotations and translations for the 'West' and 'East' tab positions
4078 // - which are also 'vertical' tabs (rotation by pi/2 and -pi/2).
4079 // 'needInactiveHack' means a tab is selected but its top-level window
4080 // is inactive (not key). In this state the tab must be visibly
4081 // different from other tabs (text color and bezel color).
4082 // NSButton is used if:
4083 // - the tab is a single tab (it's also selected) and window is active;
4084 // - the tab is selected and its window is active/key.
4085 // NSPopUpButton is used if:
4086 // - the tab is not a signle tab, and not selected
4087 // - the tab is a single tab and selected, but its window is not key
4088 // (aka needsInactiveHack).
4089 //
4090 // On macOS Tahoe button bezels are rendered within the rect we pass to
4091 // -drawWithFrame:inView:, so a left/right adjustment is not required.
4092 // To keep things simple we apply transformations as if
4093 // tabOpt->rect is ideal for our tabs (+ we have to increase a width
4094 // shift origin.x in case additional clipping required for 'Middle',
4095 // 'Beginning' and 'End' tabs).
4096
4097 const bool isActive = tabOpt->state & State_Active;
4098 const bool isEnabled = tabOpt->state & State_Enabled;
4099 const bool isPressed = tabOpt->state & State_Sunken;
4100 const bool isSelected = tabOpt->state & State_Selected;
4101 const auto tabDirection = QMacStylePrivate::tabDirection(tabOpt->shape);
4102 const bool verticalTabs = tabDirection == QMacStylePrivate::East
4103 || tabDirection == QMacStylePrivate::West;
4104
4105 QStyleOptionTab::TabPosition tp = tabOpt->position;
4106 QStyleOptionTab::SelectedPosition sp = tabOpt->selectedPosition;
4107 if (tabOpt->direction == Qt::RightToLeft && !verticalTabs) {
4108 if (tp == QStyleOptionTab::Beginning)
4109 tp = QStyleOptionTab::End;
4110 else if (tp == QStyleOptionTab::End)
4111 tp = QStyleOptionTab::Beginning;
4112
4113 if (sp == QStyleOptionTab::NextIsSelected)
4114 sp = QStyleOptionTab::PreviousIsSelected;
4115 else if (sp == QStyleOptionTab::PreviousIsSelected)
4116 sp = QStyleOptionTab::NextIsSelected;
4117 }
4118
4119 // Alas, NSSegmentedControl and NSSegmentedCell are letting us down.
4120 // We're not able to draw it at will, either calling -[drawSegment:
4121 // inFrame:withView:], -[drawRect:] or anything in between. Besides,
4122 // there's no public API do draw the pressed state, AFAICS. We'll use
4123 // a push NSButton instead and clip the CGContext.
4124
4125 const auto cs = d->effectiveAquaSizeConstrain(opt, w);
4126 // Extra hacks to get the proper pressed appreance when not selected or selected and inactive
4127 const bool needsInactiveHack = !isActive && isSelected;
4128 const auto ct = !needsInactiveHack && (isSelected || tp == QStyleOptionTab::OnlyOneTab) ?
4129 QMacStylePrivate::Button_PushButton :
4130 QMacStylePrivate::Button_PopupButton;
4131 const bool isPopupButton = ct == QMacStylePrivate::Button_PopupButton;
4132 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
4133 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
4134 auto vOffset = 1;
4135
4136 if (tabDirection == QMacStylePrivate::East)
4137 vOffset -= 1;
4138 const auto outerAdjust = isPopupButton ? 1 : 4;
4139 const auto innerAdjust = isPopupButton ? 20 : 10;
4140 QRectF frameRect = tabOpt->rect;
4141 if (verticalTabs)
4142 frameRect = QRectF(frameRect.y(), frameRect.x(), frameRect.height(), frameRect.width());
4143 // Adjust before clipping
4144 frameRect = frameRect.translated(0, vOffset);
4145 switch (tp) {
4146 case QStyleOptionTab::Beginning:
4147 // Pressed state hack: tweak adjustments in preparation for flip below
4148 if (!isSelected && tabDirection == QMacStylePrivate::West)
4149 frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0);
4150 else
4151 frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0);
4152
4153 if (isSelected) {
4154 // 1 pixel of 'roundness' is still visible on the right
4155 // (the left is OK, it's rounded).
4156 frameRect = frameRect.adjusted(0, 0, 1, 0);
4157 }
4158 break;
4159 case QStyleOptionTab::Middle:
4160 frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0);
4161
4162 if (isSelected) {
4163 // 1 pixel of 'roundness' is still visible on both
4164 // sides - left and right.
4165 frameRect = frameRect.adjusted(-1, 0, 1, 0);
4166 }
4167 break;
4168 case QStyleOptionTab::Moving: // Moving tab treated like End
4169 case QStyleOptionTab::End:
4170 // Pressed state hack: tweak adjustments in preparation for flip below
4171 if (isSelected || tabDirection == QMacStylePrivate::West)
4172 frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0);
4173 else
4174 frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0);
4175
4176 if (isSelected) {
4177 // 1 pixel of 'roundness' is still visible on the left.
4178 frameRect = frameRect.adjusted(-1, 0, 0, 0);
4179 }
4180 break;
4181 case QStyleOptionTab::OnlyOneTab:
4182 frameRect = frameRect.adjusted(-outerAdjust, 0, outerAdjust, 0);
4183 break;
4184 }
4185 pb.frame = frameRect.toCGRect();
4186
4187 if (!isPopupButton) {
4188 // Note: these days we use 'momentary push in' for Button_PushButton,
4189 // but tabs are also rendered using NSButton/ButtonPushButton, and
4190 // here we need 'push on/off' to make it work (tab highlight colors).
4191 pb.buttonType = NSButtonTypePushOnPushOff;
4192 }
4193
4194 pb.enabled = isEnabled;
4195 [pb highlight:isPressed];
4196
4197 pb.state = (isActive && isSelected) ? NSControlStateValueOn : NSControlStateValueOff;
4198
4199 const auto drawBezelBlock = ^(CGContextRef ctx, const CGRect &r) {
4200 CGContextClipToRect(ctx, opt->rect.toCGRect());
4201 if (!isSelected || needsInactiveHack) {
4202 // Final stage of the pressed state hack: flip NSPopupButton rendering
4203 if (!verticalTabs && tp == QStyleOptionTab::End) {
4204 CGContextTranslateCTM(ctx, opt->rect.right(), 0);
4205 CGContextScaleCTM(ctx, -1, 1);
4206 CGContextTranslateCTM(ctx, -frameRect.left(), 0);
4207 } else if (tabDirection == QMacStylePrivate::West && tp == QStyleOptionTab::Beginning) {
4208 CGContextTranslateCTM(ctx, 0, opt->rect.top());
4209 CGContextScaleCTM(ctx, 1, -1);
4210 CGContextTranslateCTM(ctx, 0, -frameRect.right());
4211 } else if (tabDirection == QMacStylePrivate::East && tp == QStyleOptionTab::End) {
4212 CGContextTranslateCTM(ctx, 0, opt->rect.bottom());
4213 CGContextScaleCTM(ctx, 1, -1);
4214 CGContextTranslateCTM(ctx, 0, -frameRect.left());
4215 }
4216 }
4217
4218 // Rotate and translate CTM when vertical
4219 // On macOS: positive angle is CW, negative is CCW
4220 if (tabDirection == QMacStylePrivate::West) {
4221 CGContextTranslateCTM(ctx, 0, frameRect.right());
4222 CGContextRotateCTM(ctx, -M_PI_2);
4223 CGContextTranslateCTM(ctx, -frameRect.left(), 0);
4224 } else if (tabDirection == QMacStylePrivate::East) {
4225 CGContextTranslateCTM(ctx, opt->rect.right(), 0);
4226 CGContextRotateCTM(ctx, M_PI_2);
4227 }
4228
4229 // Now, if it's a trick with a popup button, it has an arrow
4230 // which makes no sense on tabs.
4231 NSPopUpArrowPosition oldPosition = NSPopUpArrowAtCenter;
4232 NSPopUpButtonCell *pbCell = nil;
4233 auto rAdjusted = r;
4234 if (isPopupButton) {
4235 // Note: starting from macOS BigSur NSPopupButton has this
4236 // arrow 'button' in a different place and it became
4237 // quite visible 'in between' inactive tabs.
4238 pbCell = static_cast<NSPopUpButtonCell *>(pb.cell);
4239 oldPosition = pbCell.arrowPosition;
4240 pbCell.arrowPosition = NSPopUpNoArrow;
4241 if (pb.state == NSControlStateValueOff) {
4242 // NSPopUpButton in this state is smaller.
4243 rAdjusted.origin.x -= 3;
4244 rAdjusted.size.width += 6;
4245 if (tp == QStyleOptionTab::End)
4246 rAdjusted.origin.x -= 2;
4247 }
4248 }
4249 if (qt_apple_runningWithLiquidGlass()) {
4250 // All Tahoe adjustments are here, in one place. We preserve
4251 // the previous rotations and translations applied in this lambda
4252 // and we know exactly where our button must be - inside the clip
4253 // bounding box (with cliiped off left/right ends if needed).
4254 // 'deltaW' is an arbitrary number here, but it's enough to move
4255 // one of the button's ends beyond clipping bounds (or even both ends -
4256 // for the option 'Middle'). A single tab does not require
4257 // any adjustment.
4258 const CGFloat deltaW = 20.0;
4259 rAdjusted = CGContextGetClipBoundingBox(ctx);
4260 if (tp == QStyleOptionTab::Beginning) {
4261 // Note: the 'Beginning' tab is on the left for horizontal tabs
4262 // and the first on the top for vertical tabs.
4263
4264 // This cuts a part from the left or right of the button:
4265 rAdjusted.size.width += deltaW;
4266
4267 if (tabDirection == QMacStylePrivate::West && isSelected && isActive) {
4268 // NSPopUpButton for non-selected or 'inactive hack' already
4269 // has the required transformations, for other cases, shift
4270 // the left end:
4271 rAdjusted.origin.x -= deltaW;
4272 }
4273 } else if (tp == QStyleOptionTab::Middle) {
4274 // Both left/right ends must be clipped:
4275 rAdjusted.origin.x -= deltaW;
4276 rAdjusted.size.width += deltaW * 2;
4277 } else if (tp == QStyleOptionTab::End) {
4278 if (isSelected && isActive && tabDirection != QMacStylePrivate::West)
4279 rAdjusted.origin.x -= deltaW;
4280 rAdjusted.size.width += deltaW;
4281 }
4282 }
4283
4284 [pb.cell drawBezelWithFrame:rAdjusted inView:pb.superview];
4285
4286 if (pbCell) // Restore, we may reuse it for a ComboBox.
4287 pbCell.arrowPosition = oldPosition;
4288 };
4289
4290 if (needsInactiveHack) {
4291 // First, render tab as non-selected tab on a pixamp
4292 const qreal pixelRatio = p->device()->devicePixelRatio();
4293 QImage tabPixmap(opt->rect.size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
4294 tabPixmap.setDevicePixelRatio(pixelRatio);
4295 tabPixmap.fill(Qt::transparent);
4296 QPainter tabPainter(&tabPixmap);
4297 d->drawNSViewInRect(pb, frameRect, &tabPainter, ^(CGContextRef ctx, const CGRect &r) {
4298 CGContextTranslateCTM(ctx, -opt->rect.left(), -opt->rect.top());
4299 drawBezelBlock(ctx, r);
4300 });
4301 tabPainter.end();
4302
4303 // Then, darken it with the proper shade of gray
4304 const qreal inactiveGray = 0.898; // As measured
4305 const int inactiveGray8 = qRound(inactiveGray * 255.0);
4306 const QRgb inactiveGrayRGB = qRgb(inactiveGray8, inactiveGray8, inactiveGray8);
4307 for (int l = 0; l < tabPixmap.height(); ++l) {
4308 auto *line = reinterpret_cast<QRgb*>(tabPixmap.scanLine(l));
4309 for (int i = 0; i < tabPixmap.width(); ++i) {
4310 if (qAlpha(line[i]) == 255) {
4311 line[i] = inactiveGrayRGB;
4312 } else if (qAlpha(line[i]) > 128) {
4313 const int g = qRound(inactiveGray * qRed(line[i]));
4314 line[i] = qRgba(g, g, g, qAlpha(line[i]));
4315 }
4316 }
4317 }
4318
4319 // Finally, draw the tab pixmap on the current painter
4320 p->drawImage(opt->rect, tabPixmap);
4321 } else {
4322 d->drawNSViewInRect(pb, frameRect, p, drawBezelBlock);
4323 }
4324
4325 if (!isSelected && sp != QStyleOptionTab::NextIsSelected
4326 && tp != QStyleOptionTab::End
4327 && tp != QStyleOptionTab::OnlyOneTab) {
4328 static const QPen separatorPen(Qt::black, 1.0);
4329 p->save();
4330 p->setOpacity(isEnabled ? 0.105 : 0.06); // As measured
4331 p->setPen(separatorPen);
4332 if (tabDirection == QMacStylePrivate::West) {
4333 p->drawLine(QLineF(opt->rect.left() + 1.5, opt->rect.bottom(),
4334 opt->rect.right() - 0.5, opt->rect.bottom()));
4335 } else if (tabDirection == QMacStylePrivate::East) {
4336 p->drawLine(QLineF(opt->rect.left(), opt->rect.bottom(),
4337 opt->rect.right() - 0.5, opt->rect.bottom()));
4338 } else {
4339 p->drawLine(QLineF(opt->rect.right(), opt->rect.top() + 1.0,
4340 opt->rect.right(), opt->rect.bottom() - 0.5));
4341 }
4342 p->restore();
4343 }
4344 }
4345 break;
4346 case CE_TabBarTabLabel:
4347 if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
4348 QStyleOptionTab myTab = *tab;
4349 const auto foregroundRole = w ? w->foregroundRole() : QPalette::WindowText;
4350 const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape);
4351 const bool verticalTabs = tabDirection == QMacStylePrivate::East
4352 || tabDirection == QMacStylePrivate::West;
4353
4354 // Check to see if we use have the same as the system font
4355 // (QComboMenuItem is internal and should never be seen by the
4356 // outside world, unless they read the source, in which case, it's
4357 // their own fault).
4358 const bool nonDefaultFont = p->font() != qt_app_fonts_hash()->value("QComboMenuItem");
4359
4360 if (!myTab.documentMode && (myTab.state & State_Selected) && (myTab.state & State_Active))
4361 if (const auto *tabBar = qobject_cast<const QTabBar *>(w))
4362 if (!tabBar->tabTextColor(tabBar->currentIndex()).isValid())
4363 myTab.palette.setColor(foregroundRole, Qt::white);
4364
4365 if (myTab.documentMode && isDarkMode()) {
4366 if (const auto *tabBar = qobject_cast<const QTabBar *>(w)) {
4367 if (!tabBar->tabTextColor(myTab.tabIndex).isValid()) {
4368 bool active = (myTab.state & State_Selected) && (myTab.state & State_Active);
4369 myTab.palette.setColor(foregroundRole, active ? Qt::white : Qt::gray);
4370 }
4371 }
4372 }
4373
4374 int heightOffset = 0;
4375 if (verticalTabs) {
4376 heightOffset = -1;
4377 } else if (nonDefaultFont) {
4378 if (p->fontMetrics().height() == myTab.rect.height())
4379 heightOffset = 2;
4380 }
4381 myTab.rect.setHeight(myTab.rect.height() + heightOffset);
4382
4383 QCommonStyle::drawControl(ce, &myTab, p, w);
4384 }
4385 break;
4386#endif
4387#if QT_CONFIG(dockwidget)
4388 case CE_DockWidgetTitle:
4389 if (const auto *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) {
4390 const bool isVertical = dwOpt->verticalTitleBar;
4391 const auto effectiveRect = isVertical ? opt->rect.transposed() : opt->rect;
4392 p->save();
4393 if (isVertical) {
4394 p->translate(effectiveRect.left(), effectiveRect.top() + effectiveRect.width());
4395 p->rotate(-90);
4396 p->translate(-effectiveRect.left(), -effectiveRect.top());
4397 }
4398
4399 // fill title bar background
4400 p->fillRect(effectiveRect, opt->palette.window());
4401
4402 // draw horizontal line at bottom
4403 p->setPen(opt->palette.dark().color());
4404 p->drawLine(effectiveRect.bottomLeft(), effectiveRect.bottomRight());
4405
4406 if (!dwOpt->title.isEmpty()) {
4407 auto titleRect = proxy()->subElementRect(SE_DockWidgetTitleBarText, opt, w);
4408 if (isVertical)
4409 titleRect = QRect(effectiveRect.left() + opt->rect.bottom() - titleRect.bottom(),
4410 effectiveRect.top() + titleRect.left() - opt->rect.left(),
4411 titleRect.height(),
4412 titleRect.width());
4413
4414 const auto text = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
4415 proxy()->drawItemText(p, titleRect, Qt::AlignCenter | Qt::TextHideMnemonic, dwOpt->palette,
4416 dwOpt->state & State_Enabled, text, QPalette::WindowText);
4417 }
4418 p->restore();
4419 }
4420 break;
4421#endif
4422 case CE_FocusFrame: {
4423 const auto *ff = qobject_cast<const QFocusFrame *>(w);
4424 const auto *ffw = ff ? ff->widget() : nullptr;
4425 const auto ct = [=] {
4426 if (ffw) {
4427 if (qobject_cast<const QCheckBox*>(ffw))
4428 return QMacStylePrivate::Button_CheckBox;
4429 if (qobject_cast<const QRadioButton*>(ffw))
4430 return QMacStylePrivate::Button_RadioButton;
4431 if (qobject_cast<const QLineEdit*>(ffw) || qobject_cast<const QTextEdit*>(ffw))
4432 return QMacStylePrivate::TextField;
4433 if (const auto *pb = qobject_cast<const QPushButton *>(ffw)) {
4434 // keep in sync with cocoaControlType
4435 auto sizePolicy = QStyleHelper::widgetSizePolicy(ffw, opt);
4436 if (sizePolicy == QStyleHelper::SizeDefault)
4437 sizePolicy = QStyleHelper::SizeLarge;
4438 if (pb->isFlat()
4439 || (pb->rect().height() != pushButtonDefaultHeight[sizePolicy])) {
4440 return QMacStylePrivate::Button_SquareButton;
4441 }
4442 if (pb->menu() != nullptr)
4443 return QMacStylePrivate::Button_PullDown;
4444 return QMacStylePrivate::Button_PushButton;
4445 }
4446 }
4447
4448 return QMacStylePrivate::Box; // Not really, just make it the default
4449 } ();
4450 auto cs = QStyleHelper::widgetSizePolicy(ffw, opt);
4451 if (cs == QStyleHelper::SizeDefault)
4452 cs = QStyleHelper::SizeLarge;
4453 const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, w);
4454 const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, opt, w);
4455 d->drawFocusRing(p, opt->rect, hMargin, vMargin, QMacStylePrivate::CocoaControl(ct, cs));
4456 break; }
4457 case CE_MenuEmptyArea:
4458 // for QComboBoxListView
4459 if (qobject_cast<const QAbstractItemView *>(w))
4460 proxy()->drawPrimitive(PE_PanelMenu, opt, p, w);
4461 // otherwise, PE_PanelMenu has already drawn everything
4462 break;
4463 case CE_MenuItem:
4464 case CE_MenuHMargin:
4465 case CE_MenuVMargin:
4466 case CE_MenuTearoff:
4467 case CE_MenuScroller:
4468 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
4469 const bool active = mi->state & State_Selected;
4470 if (active)
4471 p->fillRect(mi->rect, mi->palette.highlight());
4472
4473 const QStyleHelper::WidgetSizePolicy widgetSize = d->aquaSizeConstrain(opt, w);
4474
4475 if (ce == CE_MenuTearoff) {
4476 p->setPen(QPen(mi->palette.dark().color(), 1, Qt::DashLine));
4477 p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2 - 1,
4478 mi->rect.x() + mi->rect.width() - 4,
4479 mi->rect.y() + mi->rect.height() / 2 - 1);
4480 p->setPen(QPen(mi->palette.light().color(), 1, Qt::DashLine));
4481 p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2,
4482 mi->rect.x() + mi->rect.width() - 4,
4483 mi->rect.y() + mi->rect.height() / 2);
4484 } else if (ce == CE_MenuScroller) {
4485 const QSize scrollerSize = QSize(10, 8);
4486 const int scrollerVOffset = 5;
4487 const int left = mi->rect.x() + (mi->rect.width() - scrollerSize.width()) / 2;
4488 const int right = left + scrollerSize.width();
4489 int top;
4490 int bottom;
4491 if (opt->state & State_DownArrow) {
4492 bottom = mi->rect.y() + scrollerVOffset;
4493 top = bottom + scrollerSize.height();
4494 } else {
4495 bottom = mi->rect.bottom() - scrollerVOffset;
4496 top = bottom - scrollerSize.height();
4497 }
4498 p->save();
4499 p->setRenderHint(QPainter::Antialiasing);
4500 QPainterPath path;
4501 path.moveTo(left, bottom);
4502 path.lineTo(right, bottom);
4503 path.lineTo((left + right) / 2, top);
4504 p->fillPath(path, opt->palette.buttonText());
4505 p->restore();
4506 } else if (ce != CE_MenuItem) {
4507 break;
4508 }
4509
4510 if (mi->menuItemType == QStyleOptionMenuItem::Separator) {
4511 CGColorRef separatorColor = [NSColor quaternaryLabelColor].CGColor;
4512 const QRect separatorRect = QRect(mi->rect.left(), mi->rect.center().y(), mi->rect.width(), 2);
4513 p->fillRect(separatorRect, qt_mac_toQColor(separatorColor));
4514 break;
4515 }
4516
4517 const int maxpmw = mi->maxIconWidth;
4518 const bool enabled = mi->state & State_Enabled;
4519
4520 int xpos = mi->rect.x() + 18;
4521 int checkcol = maxpmw;
4522 if (!enabled)
4523 p->setPen(mi->palette.text().color());
4524 else if (active)
4525 p->setPen(mi->palette.highlightedText().color());
4526 else
4527 p->setPen(mi->palette.buttonText().color());
4528
4529 if (mi->checked) {
4530 QStyleOption checkmarkOpt;
4531 checkmarkOpt.initFrom(w);
4532
4533 const int mw = checkcol + macItemFrame;
4534 const int mh = mi->rect.height() + macItemFrame;
4535 const int xp = mi->rect.x() + macItemFrame;
4536 checkmarkOpt.rect = QRect(xp, mi->rect.y() - checkmarkOpt.fontMetrics.descent(), mw, mh);
4537
4538 checkmarkOpt.state.setFlag(State_On, active);
4539 checkmarkOpt.state.setFlag(State_Enabled, enabled);
4540 if (widgetSize == QStyleHelper::SizeMini)
4541 checkmarkOpt.state |= State_Mini;
4542 else if (widgetSize == QStyleHelper::SizeSmall)
4543 checkmarkOpt.state |= State_Small;
4544
4545 // We let drawPrimitive(PE_IndicatorMenuCheckMark) pick the right color
4546 checkmarkOpt.palette.setColor(QPalette::HighlightedText, p->pen().color());
4547 checkmarkOpt.palette.setColor(QPalette::Text, p->pen().color());
4548
4549 proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &checkmarkOpt, p, w);
4550 }
4551 if (!mi->icon.isNull()) {
4552 QIcon::Mode mode = (mi->state & State_Enabled) ? QIcon::Normal
4553 : QIcon::Disabled;
4554 // Always be normal or disabled to follow the Mac style.
4555 int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize);
4556 QSize iconSize(smallIconSize, smallIconSize);
4557#if QT_CONFIG(combobox)
4558 if (const QComboBox *comboBox = qobject_cast<const QComboBox *>(w)) {
4559 iconSize = comboBox->iconSize();
4560 }
4561#endif
4562 QPixmap pixmap = mi->icon.pixmap(iconSize, p->device()->devicePixelRatio(), mode);
4563 QRect cr(xpos, mi->rect.y(), checkcol, mi->rect.height());
4564 QSize size = pixmap.deviceIndependentSize().toSize();
4565 QRect pmr(QPoint(0, 0), size);
4566 pmr.moveCenter(cr.center());
4567 p->drawPixmap(pmr.topLeft(), pixmap);
4568 xpos += size.width() + 6;
4569 }
4570
4571 QString s = mi->text;
4572 const auto text_flags = Qt::AlignVCenter | Qt::TextHideMnemonic
4573 | Qt::TextSingleLine | Qt::AlignAbsolute;
4574 int yPos = mi->rect.y();
4575 if (widgetSize == QStyleHelper::SizeMini)
4576 yPos += 1;
4577
4578 const bool isSubMenu = mi->menuItemType == QStyleOptionMenuItem::SubMenu;
4579 const int tabwidth = isSubMenu ? 9 : mi->reservedShortcutWidth;
4580
4581 QString rightMarginText;
4582 if (isSubMenu)
4583 rightMarginText = QStringLiteral("\u25b6\ufe0e"); // U+25B6 U+FE0E: BLACK RIGHT-POINTING TRIANGLE
4584
4585 // If present, save and remove embedded shorcut from text
4586 const int tabIndex = s.indexOf(QLatin1Char('\t'));
4587 if (tabIndex >= 0) {
4588 if (!isSubMenu) // ... but ignore it if it's a submenu.
4589 rightMarginText = s.mid(tabIndex + 1);
4590 s = s.left(tabIndex);
4591 }
4592
4593 p->save();
4594 if (!rightMarginText.isEmpty()) {
4595 p->setFont(qt_app_fonts_hash()->value("QMenuItem", p->font()));
4596 int xp = mi->rect.right() - tabwidth - macRightBorder + 2;
4597 if (isSubMenu) {
4598 p->drawText(xp, yPos, tabwidth, mi->rect.height(), text_flags | Qt::AlignRight, rightMarginText);
4599 } else {
4600 xp -= macItemHMargin + macItemFrame + 3; // Adjust for shortcut
4601 // try to render modifier part of shortcut string right aligned, key part left aligned
4602 const QKeySequence seq = QKeySequence::fromString(rightMarginText, QKeySequence::NativeText);
4603 if (seq.count() == 1) { // one-combo sequence, the right most character is the key
4604 // we don't know which key of all menu items is the widest, so use the widest possible
4605 const int maxKeyWidth = p->fontMetrics().maxWidth();
4606 const QChar key = rightMarginText.at(rightMarginText.length() - 1);
4607 const QString modifiers = rightMarginText.left(rightMarginText.size() - 1);
4608 p->drawText(xp + tabwidth - maxKeyWidth, yPos, maxKeyWidth, mi->rect.height(), text_flags, key);
4609 // don't clip the shortcuts; maxKeyWidth might be more than what we have been allotted by the menu
4610 p->drawText(xp, yPos, tabwidth - maxKeyWidth, mi->rect.height(),
4611 text_flags | Qt::AlignRight | Qt::TextDontClip, modifiers);
4612 } else { // draw the whole thing left-aligned for complex or unparsable cases
4613 p->drawText(xp, yPos, tabwidth, mi->rect.height(), text_flags, rightMarginText);
4614 }
4615 }
4616 }
4617
4618 if (!s.isEmpty()) {
4619 const int xm = macItemFrame + maxpmw + macItemHMargin;
4620 QFont myFont = mi->font;
4621 // myFont may not have any "hard" flags set. We override
4622 // the point size so that when it is resolved against the device, this font will win.
4623 // This is mainly to handle cases where someone sets the font on the window
4624 // and then the combo inherits it and passes it onward. At that point the resolve mask
4625 // is very, very weak. This makes it stonger.
4626 myFont.setPointSizeF(QFontInfo(mi->font).pointSizeF());
4627
4628 // QTBUG-65653: Our own text rendering doesn't look good enough, especially on non-retina
4629 // displays. Worked around here while waiting for a proper fix in QCoreTextFontEngine.
4630 // Only if we're not using QCoreTextFontEngine we do fallback to our own text rendering.
4631 const auto *fontEngine = QFontPrivate::get(myFont)->engineForScript(QChar::Script_Common);
4632 Q_ASSERT(fontEngine);
4633 if (fontEngine->type() == QFontEngine::Multi) {
4634 fontEngine = static_cast<const QFontEngineMulti *>(fontEngine)->engine(0);
4635 Q_ASSERT(fontEngine);
4636 }
4637 if (fontEngine->type() == QFontEngine::Mac) {
4638 NSFont *f = (NSFont *)(CTFontRef)fontEngine->handle();
4639
4640 // Respect the menu item palette as set in the style option.
4641 const auto pc = p->pen().color();
4642 NSColor *c = [NSColor colorWithSRGBRed:pc.redF()
4643 green:pc.greenF()
4644 blue:pc.blueF()
4645 alpha:pc.alphaF()];
4646
4647 s = qt_mac_removeMnemonics(s);
4648
4649 QMacCGContext cgCtx(p);
4650 d->setupNSGraphicsContext(cgCtx, YES);
4651
4652 // Draw at point instead of in rect, as the rect we've computed for the menu item
4653 // is based on the font metrics we got from HarfBuzz, so we may risk having CoreText
4654 // line-break the string if it doesn't fit the given rect. It's better to draw outside
4655 // the rect and possibly overlap something than to have part of the text disappear.
4656 [s.toNSString() drawAtPoint:CGPointMake(xpos, yPos)
4657 withAttributes:@{ NSFontAttributeName:f, NSForegroundColorAttributeName:c,
4658 NSObliquenessAttributeName: [NSNumber numberWithDouble: myFont.italic() ? 0.3 : 0.0],
4659 NSUnderlineStyleAttributeName: [NSNumber numberWithInt: myFont.underline() ? NSUnderlineStyleSingle
4660 : NSUnderlineStyleNone],
4661 NSStrikethroughStyleAttributeName: [NSNumber numberWithInt: myFont.strikeOut() ? NSUnderlineStyleSingle
4662 : NSUnderlineStyleNone]}];
4663
4664 d->restoreNSGraphicsContext(cgCtx);
4665 } else {
4666 p->setFont(myFont);
4667 p->drawText(xpos, yPos, mi->rect.width() - xm - tabwidth + 1,
4668 mi->rect.height(), text_flags, s);
4669 }
4670 }
4671 p->restore();
4672 }
4673 break;
4674 case CE_MenuBarItem:
4675 case CE_MenuBarEmptyArea:
4676 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
4677 const bool selected = (opt->state & State_Selected) && (opt->state & State_Enabled) && (opt->state & State_Sunken);
4678 const QBrush bg = selected ? mi->palette.highlight() : mi->palette.window();
4679 p->fillRect(mi->rect, bg);
4680
4681 if (ce != CE_MenuBarItem)
4682 break;
4683
4684 if (!mi->icon.isNull()) {
4685 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize);
4686 drawItemPixmap(p, mi->rect,
4687 Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip
4688 | Qt::TextSingleLine,
4689 mi->icon.pixmap(QSize(iconExtent, iconExtent), p->device()->devicePixelRatio(),
4690 (mi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled));
4691 } else {
4692 drawItemText(p, mi->rect,
4693 Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip
4694 | Qt::TextSingleLine,
4695 mi->palette, mi->state & State_Enabled,
4696 mi->text, selected ? QPalette::HighlightedText : QPalette::ButtonText);
4697 }
4698 }
4699 break;
4700 case CE_ProgressBarLabel:
4701 case CE_ProgressBarGroove:
4702 // Do nothing. All done in CE_ProgressBarContents. Only keep these for proxy style overrides.
4703 break;
4704 case CE_ProgressBarContents:
4705 if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
4706 const bool isIndeterminate = (pb->minimum == 0 && pb->maximum == 0);
4707 const bool vertical = !(pb->state & QStyle::State_Horizontal);
4708 const bool inverted = pb->invertedAppearance;
4709 bool reverse = (!vertical && (pb->direction == Qt::RightToLeft));
4710 if (inverted)
4711 reverse = !reverse;
4712 QRect rect = pb->rect;
4713
4714 const auto aquaSize = d->effectiveAquaSizeConstrain(opt, w);
4715 const QProgressStyleAnimation *animation = qobject_cast<QProgressStyleAnimation*>(d->animation(opt->styleObject));
4716 if (isIndeterminate) {
4717 // QIndeterminateProgressIndicator derives from NSProgressIndicator. We use a single
4718 // instance that we start animating as soon as one of the progress bars is indeterminate.
4719 // Since they will be in sync (as it's the case in Cocoa), we just need to draw it with
4720 // the right geometry when the animation triggers an update. However, we can't hide it
4721 // entirely between frames since that would stop the animation, so we just set its alpha
4722 // value to 0. Same if we remove it from its superview. See QIndeterminateProgressIndicator
4723 // implementation for details.
4724 if (!animation && opt->styleObject) {
4725 auto *animation = new QProgressStyleAnimation(d->animateSpeed(QMacStylePrivate::AquaProgressBar), opt->styleObject);
4726 // NSProgressIndicator is heavier to draw than the HITheme API, so we reduce the frame rate a couple notches.
4727 animation->setFrameRate(QStyleAnimation::FifteenFps);
4728 d->startAnimation(animation);
4729 }
4730
4731 if (qt_apple_runningWithLiquidGlass()) {
4732 d->drawProgressBar(p, pb);
4733 } else if (cg) {
4734 if (vertical)
4735 rect = rect.transposed();
4736 d->setupNSGraphicsContext(cg, NO);
4737 d->setupVerticalInvertedXform(cg, reverse, vertical, rect.toCGRect());
4738 if (auto *ipi
4739 = static_cast<QIndeterminateProgressIndicator *>(
4740 d->cocoaControl({ QMacStylePrivate::ProgressIndicator_Indeterminate, aquaSize }))) {
4741 [ipi startAnimation];
4742 [ipi drawWithFrame:rect.toCGRect() inView:d->backingStoreNSView];
4743 }
4744 d->restoreNSGraphicsContext(cg);
4745 }
4746 } else {
4747 if (animation) {
4748 d->stopAnimation(opt->styleObject);
4749 if (auto *ipi
4750 = static_cast<QIndeterminateProgressIndicator *>(
4751 d->cocoaControl({ QMacStylePrivate::ProgressIndicator_Indeterminate, aquaSize })))
4752 [ipi stopAnimation];
4753 }
4754 if (qt_apple_runningWithLiquidGlass()) {
4755 d->drawProgressBar(p, pb);
4756 } else {
4757 if (vertical)
4758 rect = rect.transposed();
4759 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::ProgressIndicator_Determinate, aquaSize);
4760 auto *pi = static_cast<NSProgressIndicator *>(d->cocoaControl(cw));
4761 d->drawNSViewInRect(pi, rect, p, ^(CGContextRef ctx, const CGRect &cgrect) {
4762 d->setupVerticalInvertedXform(ctx, reverse, vertical, cgrect);
4763 pi.minValue = pb->minimum;
4764 pi.maxValue = pb->maximum;
4765 pi.doubleValue = pb->progress;
4766 [pi drawRect:cgrect]; });
4767 }
4768 }
4769 }
4770 break;
4771 case CE_SizeGrip: {
4772 // This is not HIG kosher: Fall back to the old stuff until we decide what to do.
4773#ifndef QT_NO_MDIAREA
4774 if (!w || !qobject_cast<QMdiSubWindow *>(w->parentWidget()))
4775#endif
4776 break;
4777
4778 if (w->testAttribute(Qt::WA_MacOpaqueSizeGrip))
4779 p->fillRect(opt->rect, opt->palette.window());
4780
4781 QPen lineColor = QColor(82, 82, 82, 192);
4782 lineColor.setWidth(1);
4783 p->save();
4784 p->setRenderHint(QPainter::Antialiasing);
4785 p->setPen(lineColor);
4786 const Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : qApp->layoutDirection();
4787 const int NumLines = 3;
4788 for (int l = 0; l < NumLines; ++l) {
4789 const int offset = (l * 4 + 3);
4790 QPoint start, end;
4791 if (layoutDirection == Qt::LeftToRight) {
4792 start = QPoint(opt->rect.width() - offset, opt->rect.height() - 1);
4793 end = QPoint(opt->rect.width() - 1, opt->rect.height() - offset);
4794 } else {
4795 start = QPoint(offset, opt->rect.height() - 1);
4796 end = QPoint(1, opt->rect.height() - offset);
4797 }
4798 p->drawLine(start, end);
4799 }
4800 p->restore();
4801 break;
4802 }
4803 case CE_Splitter:
4804 if (opt->rect.width() > 1 && opt->rect.height() > 1) {
4805 const bool isVertical = !(opt->state & QStyle::State_Horizontal);
4806 // Qt refers to the layout orientation, while Cocoa refers to the divider's.
4807 const auto ct = isVertical ? QMacStylePrivate::SplitView_Horizontal : QMacStylePrivate::SplitView_Vertical;
4808 const auto cw = QMacStylePrivate::CocoaControl(ct, QStyleHelper::SizeLarge);
4809 auto *sv = static_cast<NSSplitView *>(d->cocoaControl(cw));
4810 sv.frame = opt->rect.toCGRect();
4811 d->drawNSViewInRect(sv, opt->rect, p, ^(CGContextRef, const CGRect &rect) {
4812 [sv drawDividerInRect:rect];
4813 });
4814 } else {
4815 QPen oldPen = p->pen();
4816 p->setPen(opt->palette.dark().color());
4817 if (opt->state & QStyle::State_Horizontal)
4818 p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft());
4819 else
4820 p->drawLine(opt->rect.topLeft(), opt->rect.topRight());
4821 p->setPen(oldPen);
4822 }
4823 break;
4824 case CE_RubberBand:
4825 if (const QStyleOptionRubberBand *rubber = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) {
4826 QColor fillColor(opt->palette.color(QPalette::Disabled, QPalette::Highlight));
4827 if (!rubber->opaque) {
4828 QColor strokeColor;
4829 // I retrieved these colors from the Carbon-Dev mailing list
4830 strokeColor.setHsvF(0, 0, 0.86, 1.0);
4831 fillColor.setHsvF(0, 0, 0.53, 0.25);
4832 if (opt->rect.width() * opt->rect.height() <= 3) {
4833 p->fillRect(opt->rect, strokeColor);
4834 } else {
4835 QPen oldPen = p->pen();
4836 QBrush oldBrush = p->brush();
4837 QPen pen(strokeColor);
4838 p->setPen(pen);
4839 p->setBrush(fillColor);
4840 QRect adjusted = opt->rect.adjusted(1, 1, -1, -1);
4841 if (adjusted.isValid())
4842 p->drawRect(adjusted);
4843 p->setPen(oldPen);
4844 p->setBrush(oldBrush);
4845 }
4846 } else {
4847 p->fillRect(opt->rect, fillColor);
4848 }
4849 }
4850 break;
4851#ifndef QT_NO_TOOLBAR
4852 case CE_ToolBar: {
4853 const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(opt);
4854 const bool darkMode = isDarkMode();
4855 const QColor separatorColor = darkMode ? darkModeSeparatorLine : opt->palette.dark().color();
4856
4857 // Unified title and toolbar drawing. In this mode the cocoa platform plugin will
4858 // fill the top toolbar area part with a background gradient that "unifies" with
4859 // the title bar. The following code fills the toolBar area with transparent pixels
4860 // to make that gradient visible.
4861 if (w) {
4862#if QT_CONFIG(mainwindow)
4863 if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(w->window())) {
4864 if (toolBar && toolBar->toolBarArea == Qt::TopToolBarArea && mainWindow->unifiedTitleAndToolBarOnMac()) {
4865 // fill with transparent pixels.
4866 p->save();
4867 p->setCompositionMode(QPainter::CompositionMode_Source);
4868 p->fillRect(opt->rect, Qt::transparent);
4869 p->restore();
4870
4871 // Draw a horizontal separator line at the toolBar bottom if the "unified" area ends here.
4872 // There might be additional toolbars or other widgets such as tab bars in document
4873 // mode below. Determine this by making a unified toolbar area test for the row below
4874 // this toolbar.
4875 const QPoint windowToolbarEnd = w->mapTo(w->window(), opt->rect.bottomLeft());
4876 const bool isEndOfUnifiedArea = !isInMacUnifiedToolbarArea(w->window(), windowToolbarEnd.y() + 1);
4877 if (isEndOfUnifiedArea) {
4878 const int margin = qt_mac_aqua_get_metric(SeparatorSize);
4879 const auto separatorRect = QRect(opt->rect.left(), opt->rect.bottom(), opt->rect.width(), margin);
4880 p->fillRect(separatorRect, separatorColor);
4881 }
4882 break;
4883 }
4884 }
4885#endif
4886 }
4887
4888 p->fillRect(opt->rect, opt->palette.window());
4889
4890 p->save();
4891 p->setPen(separatorColor);
4892 QRect toolbarRect = darkMode ? opt->rect.adjusted(0, 0, 0, 1) : opt->rect;
4893 if (opt->state & State_Horizontal) {
4894 p->drawLine(toolbarRect.topLeft(), toolbarRect.topRight());
4895 p->drawLine(toolbarRect.bottomLeft(), toolbarRect.bottomRight());
4896 } else {
4897 p->drawLine(toolbarRect.topLeft(), toolbarRect.bottomLeft());
4898 p->drawLine(toolbarRect.topRight(), toolbarRect.bottomRight());
4899 }
4900 p->restore();
4901
4902
4903 } break;
4904#endif
4905 default:
4906 QCommonStyle::drawControl(ce, opt, p, w);
4907 break;
4908 }
4909}
4910
4911static void setLayoutItemMargins(int left, int top, int right, int bottom, QRect *rect, Qt::LayoutDirection dir)
4912{
4913 if (dir == Qt::RightToLeft) {
4914 rect->adjust(-right, top, -left, bottom);
4915 } else {
4916 rect->adjust(left, top, right, bottom);
4917 }
4918}
4919
4920QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt,
4921 const QWidget *widget) const
4922{
4923 Q_D(const QMacStyle);
4924 QRect rect;
4925 const int controlSize = getControlSize(opt, widget);
4926
4927 switch (sr) {
4928#if QT_CONFIG(itemviews)
4929 case SE_ItemViewItemText:
4930 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
4931 int fw = proxy()->pixelMetric(PM_FocusFrameHMargin, opt, widget);
4932 // We add the focusframeargin between icon and text in commonstyle
4933 rect = QCommonStyle::subElementRect(sr, opt, widget);
4934 if (vopt->features & QStyleOptionViewItem::HasDecoration)
4935 rect.adjust(-fw, 0, 0, 0);
4936 }
4937 break;
4938#endif
4939 case SE_ToolBoxTabContents:
4940 rect = QCommonStyle::subElementRect(sr, opt, widget);
4941 break;
4942 case SE_PushButtonBevel:
4943 case SE_PushButtonContents:
4944 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
4945 // Comment from the old HITheme days:
4946 // "Unlike Carbon, we want the button to always be drawn inside its bounds.
4947 // Therefore, the button is a bit smaller, so that even if it got focus,
4948 // the focus 'shadow' will be inside. Adjust the content rect likewise."
4949 // In the future, we should consider using -[NSCell titleRectForBounds:].
4950 // Since it requires configuring the NSButton fully, i.e. frame, image,
4951 // title and font, we keep things more manual until we are more familiar
4952 // with side effects when changing NSButton state.
4953 const auto ct = cocoaControlType(btn, widget);
4954 const auto cs = d->effectiveAquaSizeConstrain(btn, widget);
4955 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
4956 auto frameRect = cw.adjustedControlFrame(btn->rect);
4957 if (sr == SE_PushButtonContents) {
4958 frameRect -= cw.titleMargins();
4959 } else if (cw.type != QMacStylePrivate::Button_SquareButton) {
4960 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
4961 frameRect = QRectF::fromCGRect([pb alignmentRectForFrame:frameRect.toCGRect()]);
4962 if (cw.type == QMacStylePrivate::Button_PushButton)
4963 frameRect -= pushButtonShadowMargins[cw.size];
4964 else if (cw.type == QMacStylePrivate::Button_PullDown)
4965 frameRect -= pullDownButtonShadowMargins[cw.size];
4966 }
4967 rect = frameRect.toRect();
4968 }
4969 break;
4970 case SE_HeaderLabel: {
4971 int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget);
4972 rect.setRect(opt->rect.x() + margin, opt->rect.y(),
4973 opt->rect.width() - margin * 2, opt->rect.height() - 2);
4974 if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) {
4975 // Subtract width needed for arrow, if there is one
4976 if (header->sortIndicator != QStyleOptionHeader::None) {
4977 if (opt->state & State_Horizontal)
4978 rect.setWidth(rect.width() - (headerSectionArrowHeight) - (margin * 2));
4979 else
4980 rect.setHeight(rect.height() - (headerSectionArrowHeight) - (margin * 2));
4981 }
4982 }
4983 rect = visualRect(opt->direction, opt->rect, rect);
4984 break;
4985 }
4986 case SE_HeaderArrow: {
4987 int h = opt->rect.height();
4988 int w = opt->rect.width();
4989 int x = opt->rect.x();
4990 int y = opt->rect.y();
4991 int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget);
4992
4993 if (opt->state & State_Horizontal) {
4994 rect.setRect(x + w - margin * 2 - headerSectionArrowHeight, y + 5,
4995 headerSectionArrowHeight, h - margin * 2 - 5);
4996 } else {
4997 rect.setRect(x + 5, y + h - margin * 2 - headerSectionArrowHeight,
4998 w - margin * 2 - 5, headerSectionArrowHeight);
4999 }
5000 rect = visualRect(opt->direction, opt->rect, rect);
5001 break;
5002 }
5003 case SE_ProgressBarGroove:
5004 // Wrong in the secondary dimension, but accurate enough in the main dimension.
5005 rect = opt->rect;
5006 break;
5007 case SE_ProgressBarLabel:
5008 break;
5009 case SE_ProgressBarContents:
5010 rect = opt->rect;
5011 break;
5012 case SE_TreeViewDisclosureItem: {
5013 rect = opt->rect;
5014 // As previously returned by HIThemeGetButtonContentBounds
5015 rect.setLeft(rect.left() + 2 + DisclosureOffset);
5016 break;
5017 }
5018#if QT_CONFIG(tabwidget)
5019 case SE_TabWidgetLeftCorner:
5020 if (const QStyleOptionTabWidgetFrame *twf
5021 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
5022 switch (twf->shape) {
5023 case QTabBar::RoundedNorth:
5024 case QTabBar::TriangularNorth:
5025 rect = QRect(QPoint(0, 0), twf->leftCornerWidgetSize);
5026 break;
5027 case QTabBar::RoundedSouth:
5028 case QTabBar::TriangularSouth:
5029 rect = QRect(QPoint(0, twf->rect.height() - twf->leftCornerWidgetSize.height()),
5030 twf->leftCornerWidgetSize);
5031 break;
5032 default:
5033 break;
5034 }
5035 rect = visualRect(twf->direction, twf->rect, rect);
5036 }
5037 break;
5038 case SE_TabWidgetRightCorner:
5039 if (const QStyleOptionTabWidgetFrame *twf
5040 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
5041 switch (twf->shape) {
5042 case QTabBar::RoundedNorth:
5043 case QTabBar::TriangularNorth:
5044 rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), 0),
5045 twf->rightCornerWidgetSize);
5046 break;
5047 case QTabBar::RoundedSouth:
5048 case QTabBar::TriangularSouth:
5049 rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(),
5050 twf->rect.height() - twf->rightCornerWidgetSize.height()),
5051 twf->rightCornerWidgetSize);
5052 break;
5053 default:
5054 break;
5055 }
5056 rect = visualRect(twf->direction, twf->rect, rect);
5057 }
5058 break;
5059 case SE_TabWidgetTabContents:
5060 rect = QCommonStyle::subElementRect(sr, opt, widget);
5061 if (const auto *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
5062 if (twf->lineWidth != 0) {
5063 switch (QMacStylePrivate::tabDirection(twf->shape)) {
5064 case QMacStylePrivate::North:
5065 rect.adjust(+1, +14, -1, -1);
5066 break;
5067 case QMacStylePrivate::South:
5068 rect.adjust(+1, +1, -1, -14);
5069 break;
5070 case QMacStylePrivate::West:
5071 rect.adjust(+14, +1, -1, -1);
5072 break;
5073 case QMacStylePrivate::East:
5074 rect.adjust(+1, +1, -14, -1);
5075 }
5076 }
5077 }
5078 break;
5079 case SE_TabBarTabText:
5080 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
5081 QRect dummyIconRect;
5082 d->tabLayout(tab, widget, &rect, &dummyIconRect);
5083 }
5084 break;
5085 case SE_TabBarTabLeftButton:
5086 case SE_TabBarTabRightButton:
5087 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
5088 bool selected = tab->state & State_Selected;
5089 int verticalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, tab, widget);
5090 int horizontalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, tab, widget);
5091 int hpadding = 5;
5092
5093 bool verticalTabs = tab->shape == QTabBar::RoundedEast
5094 || tab->shape == QTabBar::RoundedWest
5095 || tab->shape == QTabBar::TriangularEast
5096 || tab->shape == QTabBar::TriangularWest;
5097
5098 QRect tr = tab->rect;
5099 if (tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::TriangularSouth)
5100 verticalShift = -verticalShift;
5101 if (verticalTabs) {
5102 qSwap(horizontalShift, verticalShift);
5103 horizontalShift *= -1;
5104 verticalShift *= -1;
5105 }
5106 if (tab->shape == QTabBar::RoundedWest || tab->shape == QTabBar::TriangularWest)
5107 horizontalShift = -horizontalShift;
5108
5109 tr.adjust(0, 0, horizontalShift, verticalShift);
5110 if (selected)
5111 {
5112 tr.setBottom(tr.bottom() - verticalShift);
5113 tr.setRight(tr.right() - horizontalShift);
5114 }
5115
5116 QSize size = (sr == SE_TabBarTabLeftButton) ? tab->leftButtonSize : tab->rightButtonSize;
5117 int w = size.width();
5118 int h = size.height();
5119 int midHeight = static_cast<int>(qCeil(float(tr.height() - h) / 2));
5120 int midWidth = ((tr.width() - w) / 2);
5121
5122 bool atTheTop = true;
5123 switch (tab->shape) {
5124 case QTabBar::RoundedWest:
5125 case QTabBar::TriangularWest:
5126 atTheTop = (sr == SE_TabBarTabLeftButton);
5127 break;
5128 case QTabBar::RoundedEast:
5129 case QTabBar::TriangularEast:
5130 atTheTop = (sr == SE_TabBarTabRightButton);
5131 break;
5132 default:
5133 if (sr == SE_TabBarTabLeftButton)
5134 rect = QRect(tab->rect.x() + hpadding, midHeight, w, h);
5135 else
5136 rect = QRect(tab->rect.right() - w - hpadding, midHeight, w, h);
5137 rect = visualRect(tab->direction, tab->rect, rect);
5138 }
5139 if (verticalTabs) {
5140 if (atTheTop)
5141 rect = QRect(midWidth, tr.y() + tab->rect.height() - hpadding - h, w, h);
5142 else
5143 rect = QRect(midWidth, tr.y() + hpadding, w, h);
5144 }
5145 }
5146 break;
5147#endif
5148 case SE_LineEditContents:
5149 rect = QCommonStyle::subElementRect(sr, opt, widget);
5150#if QT_CONFIG(combobox)
5151 if (widget && qobject_cast<const QComboBox*>(widget->parentWidget()))
5152 rect.adjust(-1, -2, 0, 0);
5153 else
5154#endif
5155 rect.adjust(-1, -1, 0, +1);
5156 break;
5157 case SE_CheckBoxLayoutItem:
5158 rect = opt->rect;
5159 if (controlSize == QStyleHelper::SizeLarge) {
5160 setLayoutItemMargins(+2, +3, -9, -4, &rect, opt->direction);
5161 } else if (controlSize == QStyleHelper::SizeSmall) {
5162 setLayoutItemMargins(+1, +5, 0 /* fix */, -6, &rect, opt->direction);
5163 } else {
5164 setLayoutItemMargins(0, +7, 0 /* fix */, -6, &rect, opt->direction);
5165 }
5166 break;
5167 case SE_ComboBoxLayoutItem:
5168#ifndef QT_NO_TOOLBAR
5169 if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) {
5170 // Do nothing, because QToolbar needs the entire widget rect.
5171 // Otherwise it will be clipped. Equivalent to
5172 // widget->setAttribute(Qt::WA_LayoutUsesWidgetRect), but without
5173 // all the hassle.
5174 } else
5175#endif
5176 {
5177 rect = opt->rect;
5178 if (controlSize == QStyleHelper::SizeLarge) {
5179 rect.adjust(+3, +2, -3, -4);
5180 } else if (controlSize == QStyleHelper::SizeSmall) {
5181 setLayoutItemMargins(+2, +1, -3, -4, &rect, opt->direction);
5182 } else {
5183 setLayoutItemMargins(+1, 0, -2, 0, &rect, opt->direction);
5184 }
5185 }
5186 break;
5187 case SE_LabelLayoutItem:
5188 rect = opt->rect;
5189 setLayoutItemMargins(+1, 0 /* SHOULD be -1, done for alignment */, 0, 0 /* SHOULD be -1, done for alignment */, &rect, opt->direction);
5190 break;
5191 case SE_ProgressBarLayoutItem: {
5192 rect = opt->rect;
5193 int bottom = SIZE(3, 8, 8);
5194 if (opt->state & State_Horizontal) {
5195 rect.adjust(0, +1, 0, -bottom);
5196 } else {
5197 setLayoutItemMargins(+1, 0, -bottom, 0, &rect, opt->direction);
5198 }
5199 break;
5200 }
5201 case SE_PushButtonLayoutItem:
5202 if (const QStyleOptionButton *buttonOpt
5203 = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
5204 if ((buttonOpt->features & QStyleOptionButton::Flat))
5205 break; // leave rect alone
5206 if ((buttonOpt->features & QStyleOptionButton::CommandLinkButton)) {
5207 rect = opt->rect;
5208 if (controlSize == QStyleHelper::SizeLarge)
5209 rect.adjust(+6, +4, -6, -8);
5210 else if (controlSize == QStyleHelper::SizeSmall)
5211 rect.adjust(+5, +4, -5, -6);
5212 else
5213 rect.adjust(+1, 0, -1, -2);
5214 break;
5215 }
5216 }
5217 rect = opt->rect;
5218 if (controlSize == QStyleHelper::SizeLarge) {
5219 rect.adjust(0, +4, 0, -8);
5220 } else if (controlSize == QStyleHelper::SizeSmall) {
5221 rect.adjust(0, +4, 0, -6);
5222 } else {
5223 rect.adjust(0, 0, 0, -2);
5224 }
5225 break;
5226 case SE_RadioButtonLayoutItem:
5227 rect = opt->rect;
5228 if (controlSize == QStyleHelper::SizeLarge) {
5229 setLayoutItemMargins(+2, +2 /* SHOULD BE +3, done for alignment */,
5230 0, -4 /* SHOULD BE -3, done for alignment */, &rect, opt->direction);
5231 } else if (controlSize == QStyleHelper::SizeSmall) {
5232 rect.adjust(0, +6, 0 /* fix */, -5);
5233 } else {
5234 rect.adjust(0, +6, 0 /* fix */, -7);
5235 }
5236 break;
5237 case SE_SliderLayoutItem:
5238 if (const QStyleOptionSlider *sliderOpt
5239 = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5240 rect = opt->rect;
5241 if (sliderOpt->tickPosition == QSlider::NoTicks) {
5242 int above = SIZE(3, 0, 2);
5243 int below = SIZE(4, 3, 0);
5244 if (sliderOpt->orientation == Qt::Horizontal) {
5245 rect.adjust(0, +above, 0, -below);
5246 } else {
5247 rect.adjust(+above, 0, -below, 0); //### Seems that QSlider flip the position of the ticks in reverse mode.
5248 }
5249 } else if (sliderOpt->tickPosition == QSlider::TicksAbove) {
5250 int below = SIZE(3, 2, 0);
5251 if (sliderOpt->orientation == Qt::Horizontal) {
5252 rect.setHeight(rect.height() - below);
5253 } else {
5254 rect.setWidth(rect.width() - below);
5255 }
5256 } else if (sliderOpt->tickPosition == QSlider::TicksBelow) {
5257 int above = SIZE(3, 2, 0);
5258 if (sliderOpt->orientation == Qt::Horizontal) {
5259 rect.setTop(rect.top() + above);
5260 } else {
5261 rect.setLeft(rect.left() + above);
5262 }
5263 }
5264 }
5265 break;
5266 case SE_FrameLayoutItem:
5267 // hack because QStyleOptionFrame doesn't have a frameStyle member
5268 if (const QFrame *frame = qobject_cast<const QFrame *>(widget)) {
5269 rect = opt->rect;
5270 switch (frame->frameStyle() & QFrame::Shape_Mask) {
5271 case QFrame::HLine:
5272 rect.adjust(0, +1, 0, -1);
5273 break;
5274 case QFrame::VLine:
5275 rect.adjust(+1, 0, -1, 0);
5276 break;
5277 default:
5278 ;
5279 }
5280 }
5281 break;
5282 case SE_GroupBoxLayoutItem:
5283 rect = opt->rect;
5284 if (const QStyleOptionGroupBox *groupBoxOpt =
5285 qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
5286 /*
5287 AHIG is very inconsistent when it comes to group boxes.
5288 Basically, we make sure that (non-checkable) group boxes
5289 and tab widgets look good when laid out side by side.
5290 */
5291 if (groupBoxOpt->subControls & (QStyle::SC_GroupBoxCheckBox
5292 | QStyle::SC_GroupBoxLabel)) {
5293 int delta;
5294 if (groupBoxOpt->subControls & QStyle::SC_GroupBoxCheckBox) {
5295 delta = SIZE(8, 4, 4); // guess
5296 } else {
5297 delta = SIZE(15, 12, 12); // guess
5298 }
5299 rect.setTop(rect.top() + delta);
5300 }
5301 }
5302 rect.setBottom(rect.bottom() - 1);
5303 break;
5304#if QT_CONFIG(tabwidget)
5305 case SE_TabWidgetLayoutItem:
5306 if (const QStyleOptionTabWidgetFrame *tabWidgetOpt =
5307 qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
5308 /*
5309 AHIG specifies "12 or 14" as the distance from the window
5310 edge. We choose 14 and since the default top margin is 20,
5311 the overlap is 6.
5312 */
5313 rect = tabWidgetOpt->rect;
5314 if (tabWidgetOpt->shape == QTabBar::RoundedNorth)
5315 rect.setTop(rect.top() + SIZE(6 /* AHIG */, 3 /* guess */, 2 /* AHIG */));
5316 }
5317 break;
5318#endif
5319#if QT_CONFIG(dockwidget)
5320 case SE_DockWidgetCloseButton:
5321 case SE_DockWidgetFloatButton:
5322 case SE_DockWidgetTitleBarText:
5323 case SE_DockWidgetIcon: {
5324 int iconSize = proxy()->pixelMetric(PM_SmallIconSize, opt, widget);
5325 int buttonMargin = proxy()->pixelMetric(PM_DockWidgetTitleBarButtonMargin, opt, widget);
5326 QRect srect = opt->rect;
5327
5328 const QStyleOptionDockWidget *dwOpt
5329 = qstyleoption_cast<const QStyleOptionDockWidget*>(opt);
5330 bool canClose = dwOpt == 0 ? true : dwOpt->closable;
5331 bool canFloat = dwOpt == 0 ? false : dwOpt->floatable;
5332
5333 const bool verticalTitleBar = dwOpt->verticalTitleBar;
5334
5335 // If this is a vertical titlebar, we transpose and work as if it was
5336 // horizontal, then transpose again.
5337 if (verticalTitleBar)
5338 srect = srect.transposed();
5339
5340 do {
5341 int right = srect.right();
5342 int left = srect.left();
5343
5344 QRect closeRect;
5345 if (canClose) {
5346 QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton,
5347 opt, widget).actualSize(QSize(iconSize, iconSize));
5348 sz += QSize(buttonMargin, buttonMargin);
5349 if (verticalTitleBar)
5350 sz = sz.transposed();
5351 closeRect = QRect(left,
5352 srect.center().y() - sz.height()/2,
5353 sz.width(), sz.height());
5354 left = closeRect.right() + 1;
5355 }
5356 if (sr == SE_DockWidgetCloseButton) {
5357 rect = closeRect;
5358 break;
5359 }
5360
5361 QRect floatRect;
5362 if (canFloat) {
5363 QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarNormalButton,
5364 opt, widget).actualSize(QSize(iconSize, iconSize));
5365 sz += QSize(buttonMargin, buttonMargin);
5366 if (verticalTitleBar)
5367 sz = sz.transposed();
5368 floatRect = QRect(left,
5369 srect.center().y() - sz.height()/2,
5370 sz.width(), sz.height());
5371 left = floatRect.right() + 1;
5372 }
5373 if (sr == SE_DockWidgetFloatButton) {
5374 rect = floatRect;
5375 break;
5376 }
5377
5378 QRect iconRect;
5379 if (const QDockWidget *dw = qobject_cast<const QDockWidget*>(widget)) {
5380 QIcon icon;
5381 if (dw->isFloating())
5382 icon = dw->windowIcon();
5383 if (!icon.isNull()
5384 && icon.cacheKey() != QApplication::windowIcon().cacheKey()) {
5385 QSize sz = icon.actualSize(QSize(rect.height(), rect.height()));
5386 if (verticalTitleBar)
5387 sz = sz.transposed();
5388 iconRect = QRect(right - sz.width(), srect.center().y() - sz.height()/2,
5389 sz.width(), sz.height());
5390 right = iconRect.left() - 1;
5391 }
5392 }
5393 if (sr == SE_DockWidgetIcon) {
5394 rect = iconRect;
5395 break;
5396 }
5397
5398 QRect textRect = QRect(left, srect.top(),
5399 right - left, srect.height());
5400 if (sr == SE_DockWidgetTitleBarText) {
5401 rect = textRect;
5402 break;
5403 }
5404 } while (false);
5405
5406 if (verticalTitleBar) {
5407 rect = QRect(srect.left() + rect.top() - srect.top(),
5408 srect.top() + srect.right() - rect.right(),
5409 rect.height(), rect.width());
5410 } else {
5411 rect = visualRect(opt->direction, srect, rect);
5412 }
5413 break;
5414 }
5415#endif
5416 default:
5417 rect = QCommonStyle::subElementRect(sr, opt, widget);
5418 break;
5419 }
5420 return rect;
5421}
5422
5423void QMacStylePrivate::drawToolbarButtonArrow(const QStyleOption *opt, QPainter *p) const
5424{
5425 Q_Q(const QMacStyle);
5426 QStyleOption arrowOpt = *opt;
5427 arrowOpt.rect = QRect(opt->rect.right() - (toolButtonArrowSize + toolButtonArrowMargin),
5428 opt->rect.bottom() - (toolButtonArrowSize + toolButtonArrowMargin),
5431 q->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &arrowOpt, p);
5432}
5433
5434void QMacStylePrivate::setupNSGraphicsContext(CGContextRef cg, bool flipped) const
5435{
5436 CGContextSaveGState(cg);
5437 [NSGraphicsContext saveGraphicsState];
5438
5439 [NSGraphicsContext setCurrentContext:
5440 [NSGraphicsContext graphicsContextWithCGContext:cg flipped:flipped]];
5441}
5442
5443void QMacStylePrivate::restoreNSGraphicsContext(CGContextRef cg) const
5444{
5445 [NSGraphicsContext restoreGraphicsState];
5446 CGContextRestoreGState(cg);
5447}
5448
5449void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p,
5450 const QWidget *widget) const
5451{
5452 Q_D(const QMacStyle);
5453 QMacCGContext cg(p);
5454 if (!cg)
5455 qCWarning(lcMacStyle) << "drawComplexControl:" << cc << "invalid (nullptr) graphics context";
5456 QWindow *window = widget && widget->window() ? widget->window()->windowHandle() : nullptr;
5457 d->resolveCurrentNSView(window);
5458 switch (cc) {
5459 case CC_ScrollBar:
5460 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5461 if (!cg) // Scroll bar rendering is either using NSControl or CoreGraphics directly ...
5462 break;
5463 const bool drawTrack = sb->subControls & SC_ScrollBarGroove;
5464 const bool drawKnob = sb->subControls & SC_ScrollBarSlider && sb->minimum != sb->maximum;
5465 if (!drawTrack && !drawKnob)
5466 break;
5467
5468 const bool isHorizontal = sb->orientation == Qt::Horizontal;
5469
5470 if (opt && opt->styleObject && !QMacStylePrivate::scrollBars.contains(opt->styleObject))
5471 QMacStylePrivate::scrollBars.append(QPointer<QObject>(opt->styleObject));
5472
5473 static const CGFloat knobWidths[] = { 7.0, 5.0, 5.0 };
5474 static const CGFloat expandedKnobWidths[] = { 11.0, 9.0, 9.0 };
5475 const auto cocoaSize = d->effectiveAquaSizeConstrain(opt, widget);
5476 const CGFloat maxExpandScale = expandedKnobWidths[cocoaSize] / knobWidths[cocoaSize];
5477
5478 const QStyle *realStyle = widget ? widget->style() : proxy();
5479 const bool isTransient = realStyle->styleHint(SH_ScrollBar_Transient, opt, widget);
5480 if (!isTransient)
5481 d->stopAnimation(opt->styleObject);
5482 bool wasActive = false;
5483 CGFloat opacity = 0.0;
5484 CGFloat expandScale = 1.0;
5485 CGFloat expandOffset = 0.0;
5486 bool shouldExpand = false;
5487
5488 if (QObject *styleObject = opt->styleObject) {
5489 const int oldPos = styleObject->property("_q_stylepos").toInt();
5490 const int oldMin = styleObject->property("_q_stylemin").toInt();
5491 const int oldMax = styleObject->property("_q_stylemax").toInt();
5492 const QRect oldRect = styleObject->property("_q_stylerect").toRect();
5493 const QStyle::State oldState = static_cast<QStyle::State>(styleObject->property("_q_stylestate").value<QStyle::State::Int>());
5494 const uint oldActiveControls = styleObject->property("_q_stylecontrols").toUInt();
5495
5496 // a scrollbar is transient when the scrollbar itself and
5497 // its sibling are both inactive (ie. not pressed/hovered/moved)
5498 const bool transient = isTransient && !opt->activeSubControls && !(sb->state & State_On);
5499
5500 if (!transient ||
5501 oldPos != sb->sliderPosition ||
5502 oldMin != sb->minimum ||
5503 oldMax != sb->maximum ||
5504 oldRect != sb->rect ||
5505 oldState != sb->state ||
5506 oldActiveControls != sb->activeSubControls) {
5507
5508 // if the scrollbar is transient or its attributes, geometry or
5509 // state has changed, the opacity is reset back to 100% opaque
5510 opacity = 1.0;
5511
5512 styleObject->setProperty("_q_stylepos", sb->sliderPosition);
5513 styleObject->setProperty("_q_stylemin", sb->minimum);
5514 styleObject->setProperty("_q_stylemax", sb->maximum);
5515 styleObject->setProperty("_q_stylerect", sb->rect);
5516 styleObject->setProperty("_q_stylestate", static_cast<QStyle::State::Int>(sb->state));
5517 styleObject->setProperty("_q_stylecontrols", static_cast<uint>(sb->activeSubControls));
5518
5519 QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject));
5520 if (transient) {
5521 if (!anim) {
5522 anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Deactivating, styleObject);
5523 d->startAnimation(anim);
5524 } else if (anim->mode() == QScrollbarStyleAnimation::Deactivating) {
5525 // the scrollbar was already fading out while the
5526 // state changed -> restart the fade out animation
5527 anim->setCurrentTime(0);
5528 }
5529 } else if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) {
5530 d->stopAnimation(styleObject);
5531 }
5532 }
5533
5534 QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject));
5535 if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) {
5536 // once a scrollbar was active (hovered/pressed), it retains
5537 // the active look even if it's no longer active while fading out
5538 if (oldActiveControls)
5539 anim->setActive(true);
5540
5541 wasActive = anim->wasActive();
5542 opacity = anim->currentValue();
5543 }
5544
5545 shouldExpand = isTransient && (opt->activeSubControls || wasActive);
5546 if (shouldExpand) {
5547 if (!anim && !oldActiveControls) {
5548 // Start expand animation only once and when entering
5549 anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Activating, styleObject);
5550 d->startAnimation(anim);
5551 }
5552 if (anim && anim->mode() == QScrollbarStyleAnimation::Activating) {
5553 expandScale = 1.0 + (maxExpandScale - 1.0) * anim->currentValue();
5554 expandOffset = 5.5 * (1.0 - anim->currentValue());
5555 } else {
5556 // Keep expanded state after the animation ends, and when fading out
5557 expandScale = maxExpandScale;
5558 expandOffset = 0.0;
5559 }
5560 }
5561 }
5562
5563 d->setupNSGraphicsContext(cg, NO /* flipped */);
5564
5565 const auto controlType = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical;
5566 const auto cw = QMacStylePrivate::CocoaControl(controlType, cocoaSize);
5567 NSScroller *scroller = static_cast<NSScroller *>(d->cocoaControl(cw));
5568
5569 const QColor bgColor = QStyleHelper::backgroundColor(opt->palette, widget);
5570 const bool hasDarkBg = bgColor.red() < 128 && bgColor.green() < 128 && bgColor.blue() < 128;
5571 if (isTransient) {
5572 // macOS behavior: as soon as one color channel is >= 128,
5573 // the background is considered bright, scroller is dark.
5574 scroller.knobStyle = hasDarkBg? NSScrollerKnobStyleLight : NSScrollerKnobStyleDark;
5575 } else {
5576 scroller.knobStyle = NSScrollerKnobStyleDefault;
5577 }
5578
5579 scroller.scrollerStyle = isTransient ? NSScrollerStyleOverlay : NSScrollerStyleLegacy;
5580
5581 if (!setupScroller(scroller, sb))
5582 break;
5583
5584 if (isTransient) {
5585 CGContextBeginTransparencyLayerWithRect(cg, scroller.frame, nullptr);
5586 CGContextSetAlpha(cg, opacity);
5587 }
5588
5589 if (drawTrack) {
5590 // Draw the track when hovering. Expand by shifting the track rect.
5591 if (!isTransient || opt->activeSubControls || wasActive) {
5592 CGRect trackRect = scroller.bounds;
5593 if (isHorizontal)
5594 trackRect.origin.y += expandOffset;
5595 else
5596 trackRect.origin.x += expandOffset;
5597 [scroller drawKnobSlotInRect:trackRect highlight:NO];
5598 }
5599 }
5600
5601 if (drawKnob) {
5602 if (shouldExpand) {
5603 // -[NSScroller drawKnob] is not useful here because any scaling applied
5604 // will only be used to draw the hi-DPI artwork. And even if did scale,
5605 // the stretched knob would look wrong, actually. So we need to draw the
5606 // scroller manually when it's being hovered.
5607 const CGFloat scrollerWidth = [NSScroller scrollerWidthForControlSize:scroller.controlSize scrollerStyle:scroller.scrollerStyle];
5608 const CGFloat knobWidth = knobWidths[cocoaSize] * expandScale;
5609 // Cocoa can help get the exact knob length in the current orientation
5610 const CGRect scrollerKnobRect = CGRectInset([scroller rectForPart:NSScrollerKnob], 1, 1);
5611 const CGFloat knobLength = isHorizontal ? scrollerKnobRect.size.width : scrollerKnobRect.size.height;
5612 const CGFloat knobPos = isHorizontal ? scrollerKnobRect.origin.x : scrollerKnobRect.origin.y;
5613 const CGFloat knobOffset = qRound((scrollerWidth + expandOffset - knobWidth) / 2.0);
5614 const CGFloat knobRadius = knobWidth / 2.0;
5615 CGRect knobRect;
5616 if (isHorizontal)
5617 knobRect = CGRectMake(knobPos, knobOffset, knobLength, knobWidth);
5618 else
5619 knobRect = CGRectMake(knobOffset, knobPos, knobWidth, knobLength);
5620 QCFType<CGPathRef> knobPath = CGPathCreateWithRoundedRect(knobRect, knobRadius, knobRadius, nullptr);
5621 CGContextAddPath(cg, knobPath);
5622 CGContextSetAlpha(cg, 0.5);
5623 CGColorRef knobColor = hasDarkBg ? NSColor.whiteColor.CGColor : NSColor.blackColor.CGColor;
5624 CGContextSetFillColorWithColor(cg, knobColor);
5625 CGContextFillPath(cg);
5626 } else {
5627 [scroller drawKnob];
5628
5629 if (!isTransient && opt->activeSubControls) {
5630 // The knob should appear darker (going from 0.76 down to 0.49).
5631 // But no blending mode can help darken enough in a single pass,
5632 // so we resort to drawing the knob twice with a small help from
5633 // blending. This brings the gray level to a close enough 0.53.
5634 CGContextSetBlendMode(cg, kCGBlendModePlusDarker);
5635 [scroller drawKnob];
5636 }
5637 }
5638 }
5639
5640 if (isTransient)
5641 CGContextEndTransparencyLayer(cg);
5642
5643 d->restoreNSGraphicsContext(cg);
5644 }
5645 break;
5646 case CC_Slider:
5647 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5648 const bool isHorizontal = sl->orientation == Qt::Horizontal;
5649 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical;
5650 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
5651 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5652 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw));
5653 if (!setupSlider(slider, sl))
5654 break;
5655
5656 const bool hasTicks = sl->tickPosition != QSlider::NoTicks;
5657 const bool hasDoubleTicks = sl->tickPosition == QSlider::TicksBothSides;
5658 const bool drawKnob = sl->subControls & SC_SliderHandle;
5659 const bool drawBar = sl->subControls & SC_SliderGroove;
5660 const bool drawTicks = sl->subControls & SC_SliderTickmarks;
5661 const bool isPressed = qt_apple_runningWithLiquidGlass() ? false : sl->state & State_Sunken;
5662
5663 CGPoint pressPoint;
5664 if (isPressed) {
5665 const CGRect knobRect = [slider.cell knobRectFlipped:slider.isFlipped];
5666 pressPoint.x = CGRectGetMidX(knobRect);
5667 pressPoint.y = CGRectGetMidY(knobRect);
5668
5669 // The only way to tell a NSSlider/NSSliderCell to render as pressed
5670 // is to start tracking. But this API has some weird behaviors that
5671 // we have to account for. First of all, the pressed state will not
5672 // be visually reflected unless we start tracking twice. And secondly
5673 // if we don't track twice, the state of one render-pass will affect
5674 // the render pass of other sliders, even if we set up the shared
5675 // NSSlider with a new slider value.
5676 //
5677 // Note: with the 'Liquid Glass' enabled, this trick is not working anymore (instead of pressed
5678 // state macOS has a nice 'lensing' effect for a pressed thumb, which we don't see anyway)
5679 // but potentially creates a problem for our baseline test, where the 'knob' is incorrectly
5680 // placed related to the slider's groove (its filled area).
5681
5682 [slider.cell startTrackingAt:pressPoint inView:slider];
5683 [slider.cell startTrackingAt:pressPoint inView:slider];
5684 }
5685
5686 d->drawNSViewInRect(slider, opt->rect, p, ^(CGContextRef ctx, const CGRect &rect) {
5687
5688 // Since the GC is flipped, upsideDown means *not* inverted when vertical.
5689 const bool verticalFlip = !isHorizontal && !sl->upsideDown; // FIXME: && !isSierraOrLater
5690
5691 if (isHorizontal) {
5692 if (sl->upsideDown) {
5693 CGContextTranslateCTM(ctx, rect.size.width, rect.origin.y);
5694 CGContextScaleCTM(ctx, -1, 1);
5695 } else {
5696 CGContextTranslateCTM(ctx, 0, rect.origin.y);
5697 }
5698 } else if (verticalFlip) {
5699 CGContextTranslateCTM(ctx, rect.origin.x, rect.size.height);
5700 CGContextScaleCTM(ctx, 1, -1);
5701 }
5702
5703 if (hasDoubleTicks) {
5704 // This ain't HIG kosher: eye-proved constants
5705 if (isHorizontal)
5706 CGContextTranslateCTM(ctx, 0, 4);
5707 else
5708 CGContextTranslateCTM(ctx, 1, 0);
5709 }
5710
5711#if 0
5712 // FIXME: Sadly, this part doesn't work. It seems to somehow pollute the
5713 // NSSlider's internal state and, when we need to use the "else" part,
5714 // the slider's frame is not in sync with its cell dimensions.
5715 const bool drawAllParts = drawKnob && drawBar && (!hasTicks || drawTicks);
5716 if (drawAllParts && !hasDoubleTicks && (!verticalFlip || drawTicks)) {
5717 // Draw eveything at once if we're going to, except for inverted vertical
5718 // sliders which need to be drawn part by part because of the shadow below
5719 // the knob. Same for two-sided tickmarks.
5720 if (verticalFlip && drawTicks) {
5721 // Since tickmarks are always rendered symmetrically, a vertically
5722 // flipped slider with tickmarks only needs to get its value flipped.
5723 slider.intValue = slider.maxValue - slider.intValue + slider.minValue;
5724 }
5725 [slider drawRect:CGRectZero];
5726 } else
5727#endif
5728 {
5729 NSSliderCell *cell = slider.cell;
5730
5731 const int numberOfTickMarks = slider.numberOfTickMarks;
5732 // This ain't HIG kosher: force tick-less bar position.
5733 if (hasDoubleTicks)
5734 slider.numberOfTickMarks = 0;
5735
5736 const CGRect barRect = [cell barRectFlipped:slider.isFlipped];
5737 if (drawBar) {
5738 if (!isHorizontal && !sl->upsideDown && (hasDoubleTicks || !hasTicks)) {
5739 // The logic behind verticalFlip and upsideDown is the twisted one.
5740 // Bar is the only part of the cell affected by this 'flipped'
5741 // parameter in the call below, all other parts (knob, etc.) 'fixed'
5742 // by scaling/translating. With ticks on one side it's not a problem
5743 // at all - the bar is gray anyway. Without ticks or with ticks on
5744 // the both sides, for inverted appearance and vertical orientation -
5745 // we must flip so that knob and blue filling work in accordance.
5746 [cell drawBarInside:barRect flipped:true];
5747 } else {
5748 [cell drawBarInside:barRect flipped:!verticalFlip];
5749 }
5750 // This ain't HIG kosher: force unfilled bar look.
5751 if (hasDoubleTicks)
5752 slider.numberOfTickMarks = numberOfTickMarks;
5753 }
5754
5755 if (hasTicks && drawTicks) {
5756 if (!drawBar && hasDoubleTicks)
5757 slider.numberOfTickMarks = numberOfTickMarks;
5758
5759 if (qt_apple_runningWithLiquidGlass())
5760 drawTickMarks(ctx, slider, sl);
5761 else
5762 [cell drawTickMarks];
5763
5764 if (hasDoubleTicks) {
5765 // This ain't HIG kosher: just slap a set of tickmarks on each side, like we used to.
5766 CGAffineTransform tickMarksFlip;
5767 const CGRect tickMarkRect = [cell rectOfTickMarkAtIndex:0];
5768 if (isHorizontal) {
5769 tickMarksFlip = CGAffineTransformMakeTranslation(0, rect.size.height - tickMarkRect.size.height - 3);
5770 tickMarksFlip = CGAffineTransformScale(tickMarksFlip, 1, -1);
5771 } else {
5772 tickMarksFlip = CGAffineTransformMakeTranslation(rect.size.width - tickMarkRect.size.width / 2, 0);
5773 tickMarksFlip = CGAffineTransformScale(tickMarksFlip, -1, 1);
5774 }
5775 CGContextConcatCTM(ctx, tickMarksFlip);
5776 [cell drawTickMarks];
5777 CGContextConcatCTM(ctx, CGAffineTransformInvert(tickMarksFlip));
5778 }
5779 }
5780
5781 if (drawKnob) {
5782 // This ain't HIG kosher: force round knob look.
5783 if (hasDoubleTicks)
5784 slider.numberOfTickMarks = 0;
5785 if (qt_apple_runningWithLiquidGlass())
5786 drawSliderKnob(ctx, slider);
5787 else
5788 [cell drawKnob];
5789 }
5790 }
5791 });
5792
5793 if (isPressed) {
5794 // We stop twice to be on the safe side, even if one seems to be enough.
5795 // See startTracking above for why we do this.
5796 [slider.cell stopTracking:pressPoint at:pressPoint inView:slider mouseIsUp:NO];
5797 [slider.cell stopTracking:pressPoint at:pressPoint inView:slider mouseIsUp:NO];
5798 }
5799 }
5800 break;
5801#if QT_CONFIG(spinbox)
5802 case CC_SpinBox:
5803 if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
5804 if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) {
5805 const auto lineEditRect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxEditField, widget);
5806 QStyleOptionFrame frame;
5807 static_cast<QStyleOption &>(frame) = *opt;
5808 frame.rect = lineEditRect;
5809 frame.state |= State_Sunken;
5810 frame.lineWidth = 1;
5811 frame.midLineWidth = 0;
5812 frame.features = QStyleOptionFrame::None;
5813 frame.frameShape = QFrame::Box;
5814 drawPrimitive(PE_FrameLineEdit, &frame, p, widget);
5815 }
5816 if (sb->subControls & (SC_SpinBoxUp | SC_SpinBoxDown)) {
5817 const QRect updown = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget)
5818 | proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget);
5819 if (!cg) // Using controls or CoreGraphics, needs a valid context.
5820 break;
5821 d->setupNSGraphicsContext(cg, NO);
5822
5823 const auto aquaSize = d->effectiveAquaSizeConstrain(opt, widget);
5824 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Stepper, aquaSize);
5825 NSStepperCell *cell = static_cast<NSStepperCell *>(d->cocoaCell(cw));
5826 const auto controlSize = cell.controlSize;
5827 if (qt_apple_runningWithLiquidGlass())
5828 cell.controlSize = NSControlSizeMini;
5829 cell.enabled = (sb->state & State_Enabled);
5830
5831 const CGRect newRect = [cell drawingRectForBounds:updown.toCGRect()];
5832
5833 const bool upPressed = sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken);
5834 const bool downPressed = sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken);
5835 const CGFloat x = CGRectGetMidX(newRect);
5836 const CGFloat y = upPressed ? -3 : 3; // Weird coordinate shift going on. Verified with Hopper
5837 const CGPoint pressPoint = CGPointMake(x, y);
5838 // Pretend we're pressing the mouse on the right button. Unfortunately, NSStepperCell has no
5839 // API to highlight a specific button. The highlighted property works only on the down button.
5840 if (upPressed || downPressed)
5841 [cell startTrackingAt:pressPoint inView:d->backingStoreNSView];
5842
5843 [cell drawWithFrame:newRect inView:d->backingStoreNSView];
5844
5845 if (upPressed || downPressed)
5846 [cell stopTracking:pressPoint at:pressPoint inView:d->backingStoreNSView mouseIsUp:NO];
5847
5848 d->restoreNSGraphicsContext(cg);
5849 if (qt_apple_runningWithLiquidGlass())
5850 cell.controlSize = controlSize;
5851 }
5852 }
5853 break;
5854#endif
5855#if QT_CONFIG(combobox)
5856 case CC_ComboBox:
5857 if (const auto *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
5858 const bool isEnabled = combo->state & State_Enabled;
5859 const bool isPressed = combo->state & State_Sunken;
5860
5861 const auto ct = cocoaControlType(combo, widget);
5862 const auto cs = d->effectiveAquaSizeConstrain(combo, widget);
5863 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5864 auto *cc = static_cast<NSControl *>(d->cocoaControl(cw));
5865 cc.enabled = isEnabled;
5866 QRectF frameRect = cw.adjustedControlFrame(combo->rect);
5867 if (cw.type == QMacStylePrivate::Button_PopupButton) {
5868 // Non-editable QComboBox
5869 auto *pb = static_cast<NSPopUpButton *>(cc);
5870 // FIXME Old offsets. Try to move to adjustedControlFrame()
5871 if (cw.size == QStyleHelper::SizeSmall) {
5872 frameRect = frameRect.translated(0, 1);
5873 } else if (cw.size == QStyleHelper::SizeMini) {
5874 // Same 0.5 pt misalignment as AppKit and fit the focus ring
5875 frameRect = frameRect.translated(2, -0.5);
5876 }
5877 pb.frame = frameRect.toCGRect();
5878 [pb highlight:isPressed];
5879 d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) {
5880 [pb.cell drawBezelWithFrame:r inView:pb.superview];
5881 });
5882 } else if (cw.type == QMacStylePrivate::ComboBox) {
5883 // Editable QComboBox
5884 auto *cb = static_cast<NSComboBox *>(cc);
5885 const auto frameRect = cw.adjustedControlFrame(combo->rect);
5886 cb.frame = frameRect.toCGRect();
5887
5888 // This API was requested to Apple in rdar #36197888. We know it's safe to use up to macOS 10.13.3
5889 if (NSButtonCell *cell = static_cast<NSButtonCell *>([cc.cell qt_valueForPrivateKey:@"_buttonCell"])) {
5890 cell.highlighted = isPressed;
5891 } else {
5892 // TODO Render to pixmap and darken the button manually
5893 }
5894
5895 d->drawNSViewInRect(cb, frameRect, p, ^(CGContextRef, const CGRect &r) {
5896 // FIXME This is usually drawn in the control's superview, but we wouldn't get inactive look in this case
5897 [cb.cell drawWithFrame:r inView:cb];
5898 });
5899 }
5900
5901 if (combo->state & State_HasFocus) {
5902 // TODO Remove and use QFocusFrame instead.
5903 const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, combo, widget);
5904 const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, combo, widget);
5905 QRectF focusRect;
5906 if (cw.type == QMacStylePrivate::Button_PopupButton) {
5907 if (qt_apple_runningWithLiquidGlass())
5908 focusRect = QRectF::fromCGRect(qt_alignmentRectForFrame(cc.frame, cw.size, cw.type));
5909 else
5910 focusRect = QRectF::fromCGRect([cc alignmentRectForFrame:cc.frame]);
5911 focusRect -= pullDownButtonShadowMargins[cw.size];
5912 if (cw.size == QStyleHelper::SizeSmall)
5913 focusRect = focusRect.translated(0, 1);
5914 else if (cw.size == QStyleHelper::SizeMini)
5915 focusRect = focusRect.translated(2, -1);
5916 } else if (cw.type == QMacStylePrivate::ComboBox) {
5917 focusRect = frameRect - comboBoxFocusRingMargins[cw.size];
5918 }
5919 d->drawFocusRing(p, focusRect, hMargin, vMargin, cw);
5920 }
5921 }
5922 break;
5923#endif // QT_CONFIG(combobox)
5924 case CC_TitleBar:
5925 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) {
5926 const bool isActive = (titlebar->state & State_Active)
5927 && (titlebar->titleBarState & State_Active);
5928
5929 p->fillRect(opt->rect, Qt::transparent);
5930 p->setRenderHint(QPainter::Antialiasing);
5931 p->setClipRect(opt->rect, Qt::IntersectClip);
5932
5933 // FIXME A single drawPath() with 0-sized pen
5934 // doesn't look as good as this double fillPath().
5935 const auto outerFrameRect = QRectF(opt->rect.adjusted(0, 0, 0, opt->rect.height()));
5936 QPainterPath outerFramePath = d->windowPanelPath(outerFrameRect);
5937 p->fillPath(outerFramePath, opt->palette.dark());
5938
5939 const auto frameAdjust = 1.0 / p->device()->devicePixelRatio();
5940 const auto innerFrameRect = outerFrameRect.adjusted(frameAdjust, frameAdjust, -frameAdjust, 0);
5941 QPainterPath innerFramePath = d->windowPanelPath(innerFrameRect);
5942 p->fillPath(innerFramePath, opt->palette.button());
5943
5944 if (titlebar->subControls & (SC_TitleBarCloseButton
5945 | SC_TitleBarMaxButton
5946 | SC_TitleBarMinButton
5947 | SC_TitleBarNormalButton)) {
5948 const bool isHovered = (titlebar->state & State_MouseOver);
5949 static const SubControl buttons[] = {
5950 SC_TitleBarCloseButton, SC_TitleBarMinButton, SC_TitleBarMaxButton
5951 };
5952 for (const auto sc : buttons) {
5953 const auto ct = d->windowButtonCocoaControl(sc);
5954 const auto cw = QMacStylePrivate::CocoaControl(ct, QStyleHelper::SizeLarge);
5955 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw));
5956 wb.enabled = (sc & titlebar->subControls) && isActive;
5957 [wb highlight:(titlebar->state & State_Sunken) && (sc & titlebar->activeSubControls)];
5958 Q_UNUSED(isHovered); // FIXME No public API for this
5959
5960 const auto buttonRect = proxy()->subControlRect(CC_TitleBar, titlebar, sc, widget);
5961 d->drawNSViewInRect(wb, buttonRect, p, ^(CGContextRef, const CGRect &rect) {
5962 auto *wbCell = static_cast<NSButtonCell *>(wb.cell);
5963 [wbCell drawWithFrame:rect inView:wb];
5964 });
5965 }
5966 }
5967
5968 if (titlebar->subControls & SC_TitleBarLabel) {
5969 const auto tr = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarLabel, widget);
5970 if (!titlebar->icon.isNull()) {
5971 const auto iconExtent = proxy()->pixelMetric(PM_SmallIconSize);
5972 const auto iconSize = QSize(iconExtent, iconExtent);
5973 const auto iconPos = tr.x() - titlebar->icon.actualSize(iconSize).width() - qRound(titleBarIconTitleSpacing);
5974 // Only render the icon if it'll be fully visible
5975 if (iconPos < tr.right() - titleBarIconTitleSpacing)
5976 p->drawPixmap(iconPos, tr.y(), titlebar->icon.pixmap(iconSize, QIcon::Normal));
5977 }
5978
5979 if (!titlebar->text.isEmpty())
5980 drawItemText(p, tr, Qt::AlignCenter, opt->palette, isActive, titlebar->text, QPalette::Text);
5981 }
5982 }
5983 break;
5984 case CC_GroupBox:
5985 if (const QStyleOptionGroupBox *gb
5986 = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
5987
5988 QStyleOptionGroupBox groupBox(*gb);
5989 const bool flat = groupBox.features & QStyleOptionFrame::Flat;
5990 if (!flat)
5991 groupBox.state |= QStyle::State_Mini; // Force mini-sized checkbox to go with small-sized label
5992 else
5993 groupBox.subControls = groupBox.subControls & ~SC_GroupBoxFrame; // We don't like frames and ugly lines
5994
5995 const bool didSetFont = widget && widget->testAttribute(Qt::WA_SetFont);
5996 const bool didModifySubControls = !didSetFont && QApplication::desktopSettingsAware();
5997 if (didModifySubControls)
5998 groupBox.subControls = groupBox.subControls & ~SC_GroupBoxLabel;
5999 QCommonStyle::drawComplexControl(cc, &groupBox, p, widget);
6000 if (didModifySubControls) {
6001 const QRect rect = proxy()->subControlRect(CC_GroupBox, &groupBox, SC_GroupBoxLabel, widget);
6002 const bool rtl = groupBox.direction == Qt::RightToLeft;
6003 const int alignment = Qt::TextHideMnemonic | (rtl ? Qt::AlignRight : Qt::AlignLeft);
6004 const QFont savedFont = p->font();
6005 if (!flat && d->smallSystemFont)
6006 p->setFont(*d->smallSystemFont);
6007 proxy()->drawItemText(p, rect, alignment, groupBox.palette, groupBox.state & State_Enabled, groupBox.text, QPalette::WindowText);
6008 if (!flat)
6009 p->setFont(savedFont);
6010 }
6011 }
6012 break;
6013 case CC_ToolButton:
6014 if (const QStyleOptionToolButton *tb
6015 = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
6016#if QT_CONFIG(accessibility)
6017 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) {
6018 if (tb->subControls & SC_ToolButtonMenu) {
6019 QStyleOption arrowOpt = *tb;
6020 arrowOpt.rect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget);
6021 arrowOpt.rect.setY(arrowOpt.rect.y() + arrowOpt.rect.height() / 2);
6022 arrowOpt.rect.setHeight(arrowOpt.rect.height() / 2);
6023 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget);
6024 } else if ((tb->features & QStyleOptionToolButton::HasMenu)
6025 && (tb->toolButtonStyle != Qt::ToolButtonTextOnly && !tb->icon.isNull())) {
6026 d->drawToolbarButtonArrow(tb, p);
6027 }
6028 if (tb->state & State_On) {
6029 NSView *view = window ? (NSView *)window->winId() : nil;
6030 bool isKey = false;
6031 if (view)
6032 isKey = [view.window isKeyWindow];
6033
6034 QBrush brush(brushForToolButton(isKey));
6035 QPainterPath path;
6036 path.addRoundedRect(QRectF(tb->rect.x(), tb->rect.y(), tb->rect.width(), tb->rect.height() + 4), 4, 4);
6037 p->setRenderHint(QPainter::Antialiasing);
6038 p->fillPath(path, brush);
6039 }
6040 proxy()->drawControl(CE_ToolButtonLabel, opt, p, widget);
6041 } else
6042#endif // QT_CONFIG(accessibility)
6043 {
6044 auto bflags = tb->state;
6045 if (tb->subControls & SC_ToolButton)
6046 bflags |= State_Sunken;
6047 auto mflags = tb->state;
6048 if (tb->subControls & SC_ToolButtonMenu)
6049 mflags |= State_Sunken;
6050
6051 if (tb->subControls & SC_ToolButton) {
6052 if (bflags & (State_Sunken | State_On | State_Raised)) {
6053 const bool isEnabled = tb->state & State_Enabled;
6054 const bool isPressed = tb->state & State_Sunken;
6055 const bool isHighlighted = (tb->state & State_Active) && (tb->state & State_On);
6056 const auto ct = QMacStylePrivate::Button_PushButton;
6057 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6058 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6059 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
6060 pb.bezelStyle = NSBezelStyleShadowlessSquare; // TODO Use NSTexturedRoundedBezelStyle in the future.
6061 pb.frame = opt->rect.toCGRect();
6062 pb.buttonType = NSButtonTypePushOnPushOff;
6063 pb.enabled = isEnabled;
6064 [pb highlight:isPressed];
6065 pb.state = isHighlighted && !isPressed ? NSControlStateValueOn : NSControlStateValueOff;
6066 const auto buttonRect = proxy()->subControlRect(cc, tb, SC_ToolButton, widget);
6067 d->drawNSViewInRect(pb, buttonRect, p, ^(CGContextRef, const CGRect &rect) {
6068 [pb.cell drawBezelWithFrame:rect inView:pb];
6069 });
6070 }
6071 }
6072
6073 if (tb->subControls & SC_ToolButtonMenu) {
6074 const auto menuRect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget);
6075 QStyleOption arrowOpt = *tb;
6076 arrowOpt.rect = QRect(menuRect.x() + ((menuRect.width() - toolButtonArrowSize) / 2),
6077 menuRect.height() - (toolButtonArrowSize + toolButtonArrowMargin),
6080 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget);
6081 } else if (tb->features & QStyleOptionToolButton::HasMenu) {
6082 d->drawToolbarButtonArrow(tb, p);
6083 }
6084 QRect buttonRect = proxy()->subControlRect(CC_ToolButton, tb, SC_ToolButton, widget);
6085 int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget);
6086 QStyleOptionToolButton label = *tb;
6087 label.rect = buttonRect.adjusted(fw, fw, -fw, -fw);
6088 proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget);
6089 }
6090 }
6091 break;
6092#if QT_CONFIG(dial)
6093 case CC_Dial:
6094 if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt))
6095 QStyleHelper::drawDial(dial, p);
6096 break;
6097#endif
6098 default:
6099 QCommonStyle::drawComplexControl(cc, opt, p, widget);
6100 break;
6101 }
6102}
6103
6105 const QStyleOptionComplex *opt,
6106 const QPoint &pt, const QWidget *widget) const
6107{
6108 Q_D(const QMacStyle);
6109 SubControl sc = QStyle::SC_None;
6110 switch (cc) {
6111 case CC_ComboBox:
6112 if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
6113 sc = QCommonStyle::hitTestComplexControl(cc, cmb, pt, widget);
6114 if (!cmb->editable && sc != QStyle::SC_None)
6115 sc = SC_ComboBoxArrow; // A bit of a lie, but what we want
6116 }
6117 break;
6118 case CC_Slider:
6119 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
6120 if (!sl->rect.contains(pt))
6121 break;
6122
6123 const bool hasTicks = sl->tickPosition != QSlider::NoTicks;
6124 const bool isHorizontal = sl->orientation == Qt::Horizontal;
6125 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical;
6126 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6127 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6128 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw));
6129 if (!setupSlider(slider, sl))
6130 break;
6131
6132 NSSliderCell *cell = slider.cell;
6133 const auto barRect = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]);
6134 const auto knobRect = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]);
6135 if (knobRect.contains(pt)) {
6136 sc = SC_SliderHandle;
6137 } else if (barRect.contains(pt)) {
6138 sc = SC_SliderGroove;
6139 } else if (hasTicks) {
6140 sc = SC_SliderTickmarks;
6141 }
6142 }
6143 break;
6144 case CC_ScrollBar:
6145 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
6146 if (!sb->rect.contains(pt)) {
6147 sc = SC_None;
6148 break;
6149 }
6150
6151 const bool isHorizontal = sb->orientation == Qt::Horizontal;
6152 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical;
6153 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6154 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6155 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw));
6156 if (!setupScroller(scroller, sb)) {
6157 sc = SC_None;
6158 break;
6159 }
6160
6161 // Since -[NSScroller testPart:] doesn't want to cooperate, we do it the
6162 // straightforward way. In any case, macOS doesn't return line-sized changes
6163 // with NSScroller since 10.7, according to the aforementioned method's doc.
6164 const auto knobRect = QRectF::fromCGRect([scroller rectForPart:NSScrollerKnob]);
6165 if (isHorizontal) {
6166 const bool isReverse = sb->direction == Qt::RightToLeft;
6167 if (pt.x() < knobRect.left())
6168 sc = isReverse ? SC_ScrollBarAddPage : SC_ScrollBarSubPage;
6169 else if (pt.x() > knobRect.right())
6170 sc = isReverse ? SC_ScrollBarSubPage : SC_ScrollBarAddPage;
6171 else
6172 sc = SC_ScrollBarSlider;
6173 } else {
6174 if (pt.y() < knobRect.top())
6175 sc = SC_ScrollBarSubPage;
6176 else if (pt.y() > knobRect.bottom())
6177 sc = SC_ScrollBarAddPage;
6178 else
6179 sc = SC_ScrollBarSlider;
6180 }
6181 }
6182 break;
6183 default:
6184 sc = QCommonStyle::hitTestComplexControl(cc, opt, pt, widget);
6185 break;
6186 }
6187 return sc;
6188}
6189
6190QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc,
6191 const QWidget *widget) const
6192{
6193 Q_D(const QMacStyle);
6194 QRect ret;
6195 switch (cc) {
6196 case CC_ScrollBar:
6197 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
6198 const bool isHorizontal = sb->orientation == Qt::Horizontal;
6199 const bool isReverseHorizontal = isHorizontal && (sb->direction == Qt::RightToLeft);
6200
6201 NSScrollerPart part = NSScrollerNoPart;
6202 if (sc == SC_ScrollBarSlider) {
6203 part = NSScrollerKnob;
6204 } else if (sc == SC_ScrollBarGroove) {
6205 part = NSScrollerKnobSlot;
6206 } else if (sc == SC_ScrollBarSubPage || sc == SC_ScrollBarAddPage) {
6207 if ((!isReverseHorizontal && sc == SC_ScrollBarSubPage)
6208 || (isReverseHorizontal && sc == SC_ScrollBarAddPage))
6209 part = NSScrollerDecrementPage;
6210 else
6211 part = NSScrollerIncrementPage;
6212 }
6213 // And nothing else since 10.7
6214
6215 if (part != NSScrollerNoPart) {
6216 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical;
6217 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6218 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6219 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw));
6220 if (setupScroller(scroller, sb))
6221 ret = QRectF::fromCGRect([scroller rectForPart:part]).toRect();
6222 }
6223 }
6224 break;
6225 case CC_Slider:
6226 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
6227 const bool hasTicks = sl->tickPosition != QSlider::NoTicks;
6228 const bool isHorizontal = sl->orientation == Qt::Horizontal;
6229 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical;
6230 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6231 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6232 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw));
6233 if (!setupSlider(slider, sl))
6234 break;
6235
6236 NSSliderCell *cell = slider.cell;
6237 if (sc == SC_SliderHandle) {
6238 ret = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]).toRect();
6239 if (isHorizontal) {
6240 ret.setTop(sl->rect.top());
6241 ret.setBottom(sl->rect.bottom());
6242 } else {
6243 ret.setLeft(sl->rect.left());
6244 ret.setRight(sl->rect.right());
6245 }
6246 } else if (sc == SC_SliderGroove) {
6247 ret = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]).toRect();
6248 } else if (hasTicks && sc == SC_SliderTickmarks) {
6249 const auto tickMarkRect = QRectF::fromCGRect([cell rectOfTickMarkAtIndex:0]);
6250 if (isHorizontal)
6251 ret = QRect(sl->rect.left(), tickMarkRect.top(), sl->rect.width(), tickMarkRect.height());
6252 else
6253 ret = QRect(tickMarkRect.left(), sl->rect.top(), tickMarkRect.width(), sl->rect.height());
6254 }
6255
6256 // Invert if needed and extend to the actual bounds of the slider
6257 if (isHorizontal) {
6258 if (sl->upsideDown) {
6259 ret = QRect(sl->rect.right() - ret.right(), sl->rect.top(), ret.width(), sl->rect.height());
6260 } else {
6261 ret.setTop(sl->rect.top());
6262 ret.setBottom(sl->rect.bottom());
6263 }
6264 } else {
6265 if (!sl->upsideDown) {
6266 ret = QRect(sl->rect.left(), sl->rect.bottom() - ret.bottom(), sl->rect.width(), ret.height());
6267 } else {
6268 ret.setLeft(sl->rect.left());
6269 ret.setRight(sl->rect.right());
6270 }
6271 }
6272 }
6273 break;
6274 case CC_TitleBar:
6275 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) {
6276 // The title bar layout is as follows: close, min, zoom, icon, title
6277 // [ x _ + @ Window Title ]
6278 // Center the icon and title until it starts to overlap with the buttons.
6279 // The icon doesn't count towards SC_TitleBarLabel, but it's still rendered
6280 // next to the title text. See drawComplexControl().
6281 if (sc == SC_TitleBarLabel) {
6282 qreal labelWidth = titlebar->fontMetrics.horizontalAdvance(titlebar->text) + 1; // FIXME Rounding error?
6283 qreal labelHeight = titlebar->fontMetrics.height();
6284
6285 const auto lastButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarMaxButton, widget);
6286 qreal controlsSpacing = lastButtonRect.right() + titleBarButtonSpacing;
6287 if (!titlebar->icon.isNull()) {
6288 const auto iconSize = proxy()->pixelMetric(PM_SmallIconSize);
6289 const auto actualIconSize = titlebar->icon.actualSize(QSize(iconSize, iconSize)).width();
6290 controlsSpacing += actualIconSize + titleBarIconTitleSpacing;
6291 }
6292
6293 const qreal labelPos = qMax(controlsSpacing, (opt->rect.width() - labelWidth) / 2.0);
6294 labelWidth = qMin(labelWidth, opt->rect.width() - (labelPos + titleBarTitleRightMargin));
6295 ret = QRect(labelPos, (opt->rect.height() - labelHeight) / 2,
6296 labelWidth, labelHeight);
6297 } else {
6298 const auto currentButton = d->windowButtonCocoaControl(sc);
6299 if (currentButton == QMacStylePrivate::NoControl)
6300 break;
6301
6302 QPointF buttonPos = titlebar->rect.topLeft() + QPointF(titleBarButtonSpacing, 0);
6303 QSizeF buttonSize;
6304 for (int ct = QMacStylePrivate::Button_WindowClose; ct <= currentButton; ct++) {
6305 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::CocoaControlType(ct),
6306 QStyleHelper::SizeLarge);
6307 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw));
6308 if (ct == currentButton)
6309 buttonSize = QSizeF::fromCGSize(wb.frame.size);
6310 else
6311 buttonPos.rx() += wb.frame.size.width + titleBarButtonSpacing;
6312 }
6313
6314 const auto vOffset = (opt->rect.height() - buttonSize.height()) / 2.0;
6315 ret = QRectF(buttonPos, buttonSize).translated(0, vOffset).toRect();
6316 }
6317 }
6318 break;
6319 case CC_ComboBox:
6320 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
6321 const auto ct = cocoaControlType(combo, widget);
6322 const auto cs = d->effectiveAquaSizeConstrain(combo, widget);
6323 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
6324 const auto editRect = QMacStylePrivate::comboboxEditBounds(cw.adjustedControlFrame(combo->rect), cw);
6325
6326 switch (sc) {
6327 case SC_ComboBoxEditField:{
6328 ret = editRect.toAlignedRect();
6329 break; }
6330 case SC_ComboBoxArrow:{
6331 ret = editRect.toAlignedRect();
6332 ret.setX(ret.x() + ret.width());
6333 ret.setWidth(combo->rect.right() - ret.right());
6334 break; }
6335 case SC_ComboBoxListBoxPopup:{
6336 if (combo->editable) {
6337 const CGRect inner = QMacStylePrivate::comboboxInnerBounds(combo->rect.toCGRect(), cw);
6338 const int comboTop = combo->rect.top();
6339 ret = QRect(qRound(inner.origin.x),
6340 comboTop,
6341 qRound(inner.origin.x - combo->rect.left() + inner.size.width),
6342 editRect.bottom() - comboTop + 2);
6343 } else {
6344 ret = QRect(combo->rect.x() + 4 - 11,
6345 combo->rect.y() + 1,
6346 editRect.width() + 10 + 11,
6347 1);
6348 }
6349 break; }
6350 default:
6351 break;
6352 }
6353 }
6354 break;
6355 case CC_GroupBox:
6356 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
6357 bool checkable = groupBox->subControls & SC_GroupBoxCheckBox;
6358 const bool flat = groupBox->features & QStyleOptionFrame::Flat;
6359 bool hasNoText = !checkable && groupBox->text.isEmpty();
6360 switch (sc) {
6361 case SC_GroupBoxLabel:
6362 case SC_GroupBoxCheckBox: {
6363 // Cheat and use the smaller font if we need to
6364 const bool checkable = groupBox->subControls & SC_GroupBoxCheckBox;
6365 const bool fontIsSet = (widget && widget->testAttribute(Qt::WA_SetFont))
6366 || !QApplication::desktopSettingsAware();
6367 const int margin = flat || hasNoText ? 0 : 9;
6368 ret = groupBox->rect.adjusted(margin, 0, -margin, 0);
6369
6370 const QFontMetricsF fm = flat || fontIsSet || !d->smallSystemFont
6371 ? QFontMetricsF(groupBox->fontMetrics)
6372 : QFontMetricsF(*d->smallSystemFont);
6373 const QSizeF s = fm.size(Qt::AlignHCenter | Qt::AlignVCenter, qt_mac_removeMnemonics(groupBox->text), 0, nullptr);
6374 const int tw = qCeil(s.width());
6375 const int h = qCeil(fm.height());
6376 ret.setHeight(h);
6377
6378 QRect labelRect = alignedRect(groupBox->direction, groupBox->textAlignment,
6379 QSize(tw, h), ret);
6380 if (flat && checkable)
6381 labelRect.moveLeft(labelRect.left() + 4);
6382 int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt, widget);
6383 bool rtl = groupBox->direction == Qt::RightToLeft;
6384 if (sc == SC_GroupBoxLabel) {
6385 if (checkable) {
6386 int newSum = indicatorWidth + 1;
6387 int newLeft = labelRect.left() + (rtl ? -newSum : newSum);
6388 labelRect.moveLeft(newLeft);
6389 if (flat)
6390 labelRect.moveTop(labelRect.top() + 3);
6391 else
6392 labelRect.moveTop(labelRect.top() + 4);
6393 } else if (flat) {
6394 int newLeft = labelRect.left() - (rtl ? 3 : -3);
6395 labelRect.moveLeft(newLeft);
6396 labelRect.moveTop(labelRect.top() + 3);
6397 } else {
6398 int newLeft = labelRect.left() - (rtl ? 3 : 2);
6399 labelRect.moveLeft(newLeft);
6400 labelRect.moveTop(labelRect.top() + 4);
6401 }
6402 ret = labelRect;
6403 }
6404
6405 if (sc == SC_GroupBoxCheckBox) {
6406 int left = rtl ? labelRect.right() - indicatorWidth : labelRect.left() - 1;
6407 int top = flat ? ret.top() + 1 : ret.top() + 5;
6408 ret.setRect(left, top,
6409 indicatorWidth, proxy()->pixelMetric(PM_IndicatorHeight, opt, widget));
6410 }
6411 break;
6412 }
6413 case SC_GroupBoxContents:
6414 case SC_GroupBoxFrame: {
6415 QFontMetrics fm = groupBox->fontMetrics;
6416 int yOffset = 3;
6417 if (!flat) {
6418 if (widget && !widget->testAttribute(Qt::WA_SetFont)
6419 && QApplication::desktopSettingsAware())
6420 fm = QFontMetrics(qt_app_fonts_hash()->value("QSmallFont", QFont()));
6421 yOffset = 5;
6422 }
6423
6424 if (hasNoText)
6425 yOffset = -qCeil(QFontMetricsF(fm).height());
6426 ret = opt->rect.adjusted(0, qCeil(QFontMetricsF(fm).height()) + yOffset, 0, 0);
6427 if (sc == SC_GroupBoxContents) {
6428 if (flat)
6429 ret.adjust(3, -5, -3, -4); // guess too
6430 else
6431 ret.adjust(3, 3, -3, -4); // guess
6432 }
6433 }
6434 break;
6435 default:
6436 ret = QCommonStyle::subControlRect(cc, groupBox, sc, widget);
6437 break;
6438 }
6439 }
6440 break;
6441#if QT_CONFIG(spinbox)
6442 case CC_SpinBox:
6443 if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
6444 QStyleHelper::WidgetSizePolicy aquaSize = d->effectiveAquaSizeConstrain(spin, widget);
6445 const auto fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, spin, widget);
6446 int spinner_w;
6447 int spinBoxSep;
6448 switch (aquaSize) {
6449 case QStyleHelper::SizeLarge:
6450 spinner_w = 14;
6451 spinBoxSep = 2;
6452 break;
6453 case QStyleHelper::SizeSmall:
6454 spinner_w = 12;
6455 spinBoxSep = 2;
6456 break;
6457 case QStyleHelper::SizeMini:
6458 spinner_w = 10;
6459 spinBoxSep = 1;
6460 break;
6461 default:
6462 Q_UNREACHABLE();
6463 }
6464
6465 switch (sc) {
6466 case SC_SpinBoxUp:
6467 case SC_SpinBoxDown: {
6468 if (spin->buttonSymbols == QAbstractSpinBox::NoButtons)
6469 break;
6470
6471 const int y = fw;
6472 const int x = spin->rect.width() - spinner_w;
6473 ret.setRect(x + spin->rect.x(), y + spin->rect.y(), spinner_w, spin->rect.height() - y * 2);
6474 int hackTranslateX;
6475 switch (aquaSize) {
6476 case QStyleHelper::SizeLarge:
6477 hackTranslateX = 0;
6478 break;
6479 case QStyleHelper::SizeSmall:
6480 hackTranslateX = -2;
6481 break;
6482 case QStyleHelper::SizeMini:
6483 hackTranslateX = -1;
6484 break;
6485 default:
6486 Q_UNREACHABLE();
6487 }
6488
6489 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Stepper, aquaSize);
6490 NSStepperCell *cell = static_cast<NSStepperCell *>(d->cocoaCell(cw));
6491 const CGRect outRect = [cell drawingRectForBounds:ret.toCGRect()];
6492 ret = QRectF::fromCGRect(outRect).toRect();
6493
6494 switch (sc) {
6495 case SC_SpinBoxUp:
6496 ret.setHeight(ret.height() / 2);
6497 break;
6498 case SC_SpinBoxDown:
6499 ret.setY(ret.y() + ret.height() / 2);
6500 break;
6501 default:
6502 Q_ASSERT(0);
6503 break;
6504 }
6505 ret.translate(hackTranslateX, 0); // hack: position the buttons correctly (weird that we need this)
6506 ret = visualRect(spin->direction, spin->rect, ret);
6507 break;
6508 }
6509 case SC_SpinBoxEditField:
6510 ret = spin->rect.adjusted(fw, fw, -fw, -fw);
6511 if (spin->buttonSymbols != QAbstractSpinBox::NoButtons) {
6512 ret.setWidth(spin->rect.width() - spinBoxSep - spinner_w);
6513 ret = visualRect(spin->direction, spin->rect, ret);
6514 }
6515 break;
6516 default:
6517 ret = QCommonStyle::subControlRect(cc, spin, sc, widget);
6518 break;
6519 }
6520 }
6521 break;
6522#endif
6523 case CC_ToolButton:
6524 ret = QCommonStyle::subControlRect(cc, opt, sc, widget);
6525 if (sc == SC_ToolButtonMenu) {
6526#if QT_CONFIG(accessibility)
6527 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar))
6528 ret.adjust(-toolButtonArrowMargin, 0, 0, 0);
6529#endif
6530 ret.adjust(-1, 0, 0, 0);
6531 }
6532 break;
6533 default:
6534 ret = QCommonStyle::subControlRect(cc, opt, sc, widget);
6535 break;
6536 }
6537 return ret;
6538}
6539
6540QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt,
6541 const QSize &csz, const QWidget *widget) const
6542{
6543 Q_D(const QMacStyle);
6544 QSize sz(csz);
6545 bool useAquaGuideline = true;
6546
6547 switch (ct) {
6548#if QT_CONFIG(spinbox)
6549 case CT_SpinBox:
6550 if (const QStyleOptionSpinBox *vopt = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
6551 const bool hasButtons = (vopt->buttonSymbols != QAbstractSpinBox::NoButtons);
6552 const int buttonWidth = hasButtons ? proxy()->subControlRect(CC_SpinBox, vopt, SC_SpinBoxUp, widget).width() : 0;
6553 sz += QSize(buttonWidth, 0);
6554 }
6555 break;
6556#endif
6557 case QStyle::CT_TabWidget:
6558 // the size between the pane and the "contentsRect" (+4,+4)
6559 // (the "contentsRect" is on the inside of the pane)
6560 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget);
6561 /**
6562 This is supposed to show the relationship between the tabBar and
6563 the stack widget of a QTabWidget.
6564 Unfortunately ascii is not a good way of representing graphics.....
6565 PS: The '=' line is the painted frame.
6566
6567 top ---+
6568 |
6569 |
6570 |
6571 | vvv just outside the painted frame is the "pane"
6572 - -|- - - - - - - - - - <-+
6573 TAB BAR +=====^============ | +2 pixels
6574 - - -|- - -|- - - - - - - <-+
6575 | | ^ ^^^ just inside the painted frame is the "contentsRect"
6576 | | |
6577 | overlap |
6578 | | |
6579 bottom ------+ <-+ +14 pixels
6580 |
6581 v
6582 ------------------------------ <- top of stack widget
6583
6584
6585 To summarize:
6586 * 2 is the distance between the pane and the contentsRect
6587 * The 14 and the 1's are the distance from the contentsRect to the stack widget.
6588 (same value as used in SE_TabWidgetTabContents)
6589 * overlap is how much the pane should overlap the tab bar
6590 */
6591 // then add the size between the stackwidget and the "contentsRect"
6592#if QT_CONFIG(tabwidget)
6593 if (const QStyleOptionTabWidgetFrame *twf
6594 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
6595 QSize extra(0,0);
6596 const int overlap = pixelMetric(PM_TabBarBaseOverlap, opt, widget);
6597 const int gapBetweenTabbarAndStackWidget = 2 + 14 - overlap;
6598
6599 const auto tabDirection = QMacStylePrivate::tabDirection(twf->shape);
6600 if (tabDirection == QMacStylePrivate::North
6601 || tabDirection == QMacStylePrivate::South) {
6602 extra = QSize(2, gapBetweenTabbarAndStackWidget + 1);
6603 } else {
6604 extra = QSize(gapBetweenTabbarAndStackWidget + 1, 2);
6605 }
6606 sz+= extra;
6607 }
6608#endif
6609 break;
6610#if QT_CONFIG(tabbar)
6611 case QStyle::CT_TabBarTab:
6612 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
6613 const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont))
6614 || !QApplication::desktopSettingsAware();
6615 const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape);
6616 const bool verticalTabs = tabDirection == QMacStylePrivate::East
6617 || tabDirection == QMacStylePrivate::West;
6618 if (verticalTabs)
6619 sz = sz.transposed();
6620
6621 int defaultTabHeight;
6622 const auto cs = d->effectiveAquaSizeConstrain(opt, widget);
6623 switch (cs) {
6624 case QStyleHelper::SizeLarge:
6625 if (tab->documentMode)
6626 defaultTabHeight = 24;
6627 else
6628 defaultTabHeight = 21;
6629
6630 if (qt_apple_runningWithLiquidGlass() && !tab->documentMode) {
6631 // We render tabs using NSButton and NSPopupButton. The control size
6632 // we use for tabs by default NSControlSizeRegular. With height 21
6633 // the button's bezel is partially clipped (by the clip rect with height 21),
6634 // which does not look good. Switching to NSControlSizeSmall has another
6635 // problem - different heights for NSButton vs. NSPopupButton, which is
6636 // quite visible (-2 pixels). 23 seems to work fine with the regular control
6637 // size. Document mode is not using native controls and thus stays unchanged.
6638 defaultTabHeight = 23;
6639 }
6640 break;
6641 case QStyleHelper::SizeSmall:
6642 defaultTabHeight = 18;
6643 break;
6644 case QStyleHelper::SizeMini:
6645 defaultTabHeight = 16;
6646 break;
6647 default:
6648 break;
6649 }
6650
6651 const bool widthSet = !differentFont && tab->icon.isNull();
6652 if (widthSet) {
6653 const auto textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, tab->text);
6654 sz.rwidth() = textSize.width();
6655 sz.rheight() = qMax(defaultTabHeight, textSize.height());
6656 } else {
6657 sz.rheight() = qMax(defaultTabHeight, sz.height());
6658 }
6659 sz.rwidth() += proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget);
6660
6661 if (verticalTabs)
6662 sz = sz.transposed();
6663
6664 int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height());
6665 int maxWidgetWidth = qMax(tab->leftButtonSize.width(), tab->rightButtonSize.width());
6666
6667 int widgetWidth = 0;
6668 int widgetHeight = 0;
6669 int padding = 0;
6670 if (tab->leftButtonSize.isValid()) {
6671 padding += 8;
6672 widgetWidth += tab->leftButtonSize.width();
6673 widgetHeight += tab->leftButtonSize.height();
6674 }
6675 if (tab->rightButtonSize.isValid()) {
6676 padding += 8;
6677 widgetWidth += tab->rightButtonSize.width();
6678 widgetHeight += tab->rightButtonSize.height();
6679 }
6680
6681 if (verticalTabs) {
6682 sz.setWidth(qMax(sz.width(), maxWidgetWidth));
6683 sz.setHeight(sz.height() + widgetHeight + padding);
6684 } else {
6685 if (widthSet)
6686 sz.setWidth(sz.width() + widgetWidth + padding);
6687 sz.setHeight(qMax(sz.height(), maxWidgetHeight));
6688 }
6689 }
6690 break;
6691#endif
6692 case QStyle::CT_PushButton: {
6693 bool isFlat = false;
6694 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
6695 if (btn->features & QStyleOptionButton::CommandLinkButton)
6696 return QCommonStyle::sizeFromContents(ct, opt, sz, widget);
6697 isFlat = btn->features & QStyleOptionButton::Flat;
6698 }
6699
6700 // By default, we fit the contents inside a normal rounded push button.
6701 // Do this by add enough space around the contents so that rounded
6702 // borders (including highlighting when active) will show.
6703 QSize macsz;
6704 const auto controlSize = d->effectiveAquaSizeConstrain(opt, widget, CT_PushButton, sz, &macsz);
6705 // FIXME See comment in CT_PushButton case in qt_aqua_get_known_size().
6706 if (macsz.width() != -1)
6707 sz.setWidth(macsz.width());
6708 else
6709 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12;
6710 // All values as measured from HIThemeGetButtonBackgroundBounds()
6711 if (controlSize != QStyleHelper::SizeMini)
6712 sz.rwidth() += 12; // We like 12 over here.
6713
6714 if (controlSize == QStyleHelper::SizeLarge) {
6715 if (!isFlat)
6716 sz.rwidth() -= 12;
6717 if (sz.height() > 16)
6718 sz.rheight() += pushButtonDefaultHeight[QStyleHelper::SizeLarge] - 16;
6719 else
6720 sz.setHeight(pushButtonDefaultHeight[QStyleHelper::SizeLarge]);
6721 } else {
6722 if (!isFlat && !qt_apple_runningWithLiquidGlass())
6723 sz.rwidth() -= 10;
6724 if (controlSize == QStyleHelper::SizeMini)
6725 sz.setHeight(24);
6726 else
6727 sz.setHeight(pushButtonDefaultHeight[QStyleHelper::SizeSmall]);
6728 }
6729 if (isFlat)
6730 sz.rwidth() -= 13;
6731 break;
6732 }
6733 case QStyle::CT_MenuItem:
6734 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
6735 int maxpmw = mi->maxIconWidth;
6736#if QT_CONFIG(combobox)
6737 const QComboBox *comboBox = qobject_cast<const QComboBox *>(widget);
6738#endif
6739 int w = sz.width(),
6740 h = sz.height();
6741 if (mi->menuItemType == QStyleOptionMenuItem::Separator) {
6742 w = 10;
6744 } else {
6745 h = mi->fontMetrics.height() + 2;
6746 if (!mi->icon.isNull()) {
6747#if QT_CONFIG(combobox)
6748 if (comboBox) {
6749 const QSize &iconSize = comboBox->iconSize();
6750 h = qMax(h, iconSize.height() + 4);
6751 maxpmw = qMax(maxpmw, iconSize.width());
6752 } else
6753#endif
6754 {
6755 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize);
6756 h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4);
6757 }
6758 }
6759 }
6760 if (mi->text.contains(QLatin1Char('\t')))
6761 w += 12;
6762 else if (mi->menuItemType == QStyleOptionMenuItem::SubMenu)
6763 w += 35; // Not quite exactly as it seems to depend on other factors
6764 if (maxpmw)
6765 w += maxpmw + 6;
6766 // add space for a check. All items have place for a check too.
6767 w += 20;
6768#if QT_CONFIG(combobox)
6769 if (comboBox && comboBox->isVisible()) {
6770 QStyleOptionComboBox cmb;
6771 cmb.initFrom(comboBox);
6772 cmb.editable = false;
6773 cmb.subControls = QStyle::SC_ComboBoxEditField;
6774 cmb.activeSubControls = QStyle::SC_None;
6775 w = qMax(w, subControlRect(QStyle::CC_ComboBox, &cmb,
6776 QStyle::SC_ComboBoxEditField,
6777 comboBox).width());
6778 } else
6779#endif
6780 {
6781 w += 12;
6782 }
6783 sz = QSize(w, h);
6784 }
6785 break;
6786 case CT_MenuBarItem:
6787 if (!sz.isEmpty())
6788 sz += QSize(12, 4); // Constants from QWindowsStyle
6789 break;
6790 case CT_ToolButton:
6791 sz.rwidth() += 10;
6792 sz.rheight() += 10;
6793 if (const auto *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt))
6794 if (tb->features & QStyleOptionToolButton::Menu)
6795 sz.rwidth() += toolButtonArrowMargin;
6796 return sz;
6797 case CT_ComboBox:
6798 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
6799 const auto controlSize = d->effectiveAquaSizeConstrain(opt, widget);
6800 if (!cb->editable) {
6801 // See CT_PushButton; we have to fit the focus
6802 // ring and a non-editable combo box is a NSPopUpButton.
6803 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 24;
6804 // All values as measured from HIThemeGetButtonBackgroundBounds()
6805 if (controlSize != QStyleHelper::SizeMini)
6806 sz.rwidth() += 12; // We like 12 over here.
6807#if 0
6808 // TODO Maybe support square combo boxes
6809 if (controlSize == QStyleHelper::SizeLarge && sz.height() > 16)
6810 sz.rheight() += popupButtonDefaultHeight[QStyleHelper::SizeLarge] - 16;
6811 else
6812#endif
6813 } else {
6814 sz.rwidth() += 50; // FIXME Double check this
6815 }
6816
6817 // This should be enough to fit the focus ring
6818 if (controlSize == QStyleHelper::SizeMini)
6819 sz.setHeight(24); // FIXME Our previous HITheme-based logic returned this for CT_PushButton.
6820 else
6821 sz.setHeight(pushButtonDefaultHeight[controlSize]);
6822
6823 return sz;
6824 }
6825 break;
6826 case CT_Menu: {
6827 if (proxy() == this) {
6828 sz = csz;
6829 } else {
6830 QStyleHintReturnMask menuMask;
6831 QStyleOption myOption = *opt;
6832 myOption.rect.setSize(sz);
6833 if (proxy()->styleHint(SH_Menu_Mask, &myOption, widget, &menuMask))
6834 sz = menuMask.region.boundingRect().size();
6835 }
6836 break; }
6837 case CT_HeaderSection:{
6838 const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt);
6839 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget);
6840 if (header->text.contains(QLatin1Char('\n')))
6841 useAquaGuideline = false;
6842 break; }
6843 case CT_ScrollBar :
6844 // Make sure that the scroll bar is large enough to display the thumb indicator.
6845 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
6846 const int minimumSize = 24; // Smallest knob size, but Cocoa doesn't seem to care
6847 if (slider->orientation == Qt::Horizontal)
6848 sz = sz.expandedTo(QSize(minimumSize, sz.height()));
6849 else
6850 sz = sz.expandedTo(QSize(sz.width(), minimumSize));
6851 }
6852 break;
6853#if QT_CONFIG(itemviews)
6854 case CT_ItemViewItem:
6855 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
6856 sz = QCommonStyle::sizeFromContents(ct, vopt, csz, widget);
6857 sz.setHeight(sz.height() + 2);
6858 }
6859 break;
6860#endif
6861
6862 default:
6863 sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget);
6864 }
6865
6866 if (useAquaGuideline && ct != CT_PushButton) {
6867 // TODO Probably going away at some point
6868 QSize macsz;
6869 if (d->aquaSizeConstrain(opt, widget, ct, sz, &macsz) != QStyleHelper::SizeDefault) {
6870 if (macsz.width() != -1)
6871 sz.setWidth(macsz.width());
6872 if (macsz.height() != -1)
6873 sz.setHeight(macsz.height());
6874 }
6875 }
6876
6877 // The sizes that Carbon and the guidelines gives us excludes the focus frame.
6878 // We compensate for this by adding some extra space here to make room for the frame when drawing:
6879 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){
6880 if (combo->editable) {
6881 const auto widgetSize = d->aquaSizeConstrain(opt, widget);
6882 QMacStylePrivate::CocoaControl cw;
6883 cw.type = combo->editable ? QMacStylePrivate::ComboBox : QMacStylePrivate::Button_PopupButton;
6884 cw.size = widgetSize;
6885 const CGRect diffRect = QMacStylePrivate::comboboxInnerBounds(CGRectZero, cw);
6886 sz.rwidth() -= qRound(diffRect.size.width);
6887 sz.rheight() -= qRound(diffRect.size.height);
6888 }
6889 }
6890 return sz;
6891}
6892
6893void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal,
6894 bool enabled, const QString &text, QPalette::ColorRole textRole) const
6895{
6896 if (flags & Qt::TextShowMnemonic)
6897 flags |= Qt::TextHideMnemonic;
6898 QCommonStyle::drawItemText(p, r, flags, pal, enabled, text, textRole);
6899}
6900
6901bool QMacStyle::event(QEvent *e)
6902{
6903 Q_D(QMacStyle);
6904 if (e->type() == QEvent::FocusIn) {
6905 QWidget *f = nullptr;
6906 QWidget *focusWidget = QApplication::focusWidget();
6907#if QT_CONFIG(graphicsview)
6908 if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) {
6909 QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : 0;
6910 if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) {
6911 QGraphicsProxyWidget *proxy = static_cast<QGraphicsProxyWidget *>(focusItem);
6912 if (proxy->widget())
6913 focusWidget = proxy->widget()->focusWidget();
6914 }
6915 }
6916#endif
6917
6918 if (focusWidget && focusWidget->testAttribute(Qt::WA_MacShowFocusRect)) {
6919#if QT_CONFIG(spinbox)
6920 if (const auto sb = qobject_cast<QAbstractSpinBox *>(focusWidget))
6921 f = sb->property("_q_spinbox_lineedit").value<QWidget *>();
6922 else
6923#endif
6924 f = focusWidget;
6925 }
6926 if (f) {
6927 if (!d->focusWidget)
6928 d->focusWidget = new QFocusFrame(f);
6929 d->focusWidget->setWidget(f);
6930 } else if (d->focusWidget) {
6931 d->focusWidget->setWidget(0);
6932 }
6933 } else if (e->type() == QEvent::FocusOut) {
6934 if (d->focusWidget)
6935 d->focusWidget->setWidget(0);
6936 }
6937 return false;
6938}
6939
6940int QMacStyle::layoutSpacing(QSizePolicy::ControlType control1,
6941 QSizePolicy::ControlType control2,
6942 Qt::Orientation orientation,
6943 const QStyleOption *option,
6944 const QWidget *widget) const
6945{
6946 const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton;
6947 const int controlSize = getControlSize(option, widget);
6948
6949 if (control2 == QSizePolicy::ButtonBox) {
6950 /*
6951 AHIG seems to prefer a 12-pixel margin between group
6952 boxes and the row of buttons. The 20 pixel comes from
6953 Builder.
6954 */
6955 if (control1 & (QSizePolicy::Frame // guess
6956 | QSizePolicy::GroupBox // (AHIG, guess, guess)
6957 | QSizePolicy::TabWidget // guess
6958 | ButtonMask)) { // AHIG
6959 return_SIZE(14, 8, 8);
6960 } else if (control1 == QSizePolicy::LineEdit) {
6961 return_SIZE(8, 8, 8); // Interface Builder
6962 } else {
6963 return_SIZE(20, 7, 7); // Interface Builder
6964 }
6965 }
6966
6967 if ((control1 | control2) & ButtonMask) {
6968 if (control1 == QSizePolicy::LineEdit)
6969 return_SIZE(8, 8, 8); // Interface Builder
6970 else if (control2 == QSizePolicy::LineEdit) {
6971 if (orientation == Qt::Vertical)
6972 return_SIZE(20, 7, 7); // Interface Builder
6973 else
6974 return_SIZE(20, 8, 8);
6975 }
6976 return_SIZE(14, 8, 8); // Interface Builder
6977 }
6978
6979 switch (CT2(control1, control2)) {
6980 case CT1(QSizePolicy::Label): // guess
6981 case CT2(QSizePolicy::Label, QSizePolicy::DefaultType): // guess
6982 case CT2(QSizePolicy::Label, QSizePolicy::CheckBox): // AHIG
6983 case CT2(QSizePolicy::Label, QSizePolicy::ComboBox): // AHIG
6984 case CT2(QSizePolicy::Label, QSizePolicy::LineEdit): // guess
6985 case CT2(QSizePolicy::Label, QSizePolicy::RadioButton): // AHIG
6986 case CT2(QSizePolicy::Label, QSizePolicy::Slider): // guess
6987 case CT2(QSizePolicy::Label, QSizePolicy::SpinBox): // guess
6988 case CT2(QSizePolicy::Label, QSizePolicy::ToolButton): // guess
6989 return_SIZE(8, 6, 5);
6990 case CT1(QSizePolicy::ToolButton):
6991 return 8; // AHIG
6992 case CT1(QSizePolicy::CheckBox):
6993 case CT2(QSizePolicy::CheckBox, QSizePolicy::RadioButton):
6994 case CT2(QSizePolicy::RadioButton, QSizePolicy::CheckBox):
6995 if (orientation == Qt::Vertical)
6996 return_SIZE(8, 8, 7); // AHIG and Builder
6997 break;
6998 case CT1(QSizePolicy::RadioButton):
6999 if (orientation == Qt::Vertical)
7000 return 5; // (Builder, guess, AHIG)
7001 }
7002
7003 if (orientation == Qt::Horizontal
7004 && (control2 & (QSizePolicy::CheckBox | QSizePolicy::RadioButton)))
7005 return_SIZE(12, 10, 8); // guess
7006
7007 if ((control1 | control2) & (QSizePolicy::Frame
7008 | QSizePolicy::GroupBox
7009 | QSizePolicy::TabWidget)) {
7010 /*
7011 These values were chosen so that nested container widgets
7012 look good side by side. Builder uses 8, which looks way
7013 too small, and AHIG doesn't say anything.
7014 */
7015 return_SIZE(16, 10, 10); // guess
7016 }
7017
7018 if ((control1 | control2) & (QSizePolicy::Line | QSizePolicy::Slider))
7019 return_SIZE(12, 10, 8); // AHIG
7020
7021 if ((control1 | control2) & QSizePolicy::LineEdit)
7022 return_SIZE(10, 8, 8); // AHIG
7023
7024 /*
7025 AHIG and Builder differ by up to 4 pixels for stacked editable
7026 comboboxes. We use some values that work fairly well in all
7027 cases.
7028 */
7029 if ((control1 | control2) & QSizePolicy::ComboBox)
7030 return_SIZE(10, 8, 7); // guess
7031
7032 /*
7033 Builder defaults to 8, 6, 5 in lots of cases, but most of the time the
7034 result looks too cramped.
7035 */
7036 return_SIZE(10, 8, 6); // guess
7037}
7038
7039QT_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