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