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