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