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