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