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