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
4708 const CGRect newRect = [cell drawingRectForBounds:updown.toCGRect()];
4709
4710 const bool upPressed = sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken);
4711 const bool downPressed = sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken);
4712 const CGFloat x = CGRectGetMidX(newRect);
4713 const CGFloat y = upPressed ? -3 : 3; // Weird coordinate shift going on. Verified with Hopper
4714 const CGPoint pressPoint = CGPointMake(x, y);
4715 // Pretend we're pressing the mouse on the right button. Unfortunately, NSStepperCell has no
4716 // API to highlight a specific button. The highlighted property works only on the down button.
4717 if (upPressed || downPressed)
4718 [cell startTrackingAt:pressPoint inView:d->backingStoreNSView];
4719
4720 [cell drawWithFrame:newRect inView:d->backingStoreNSView];
4721
4722 if (upPressed || downPressed)
4723 [cell stopTracking:pressPoint at:pressPoint inView:d->backingStoreNSView mouseIsUp:NO];
4724
4725 d->restoreNSGraphicsContext(cg);
4726 }
4727 }
4728 break;
4729 case CC_ComboBox:
4730 if (const auto *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
4731 const bool isEnabled = combo->state & State_Enabled;
4732 const bool isPressed = combo->state & State_Sunken;
4733
4734 const auto ct = cocoaControlType(combo);
4735 const auto cs = d->effectiveAquaSizeConstrain(combo);
4736 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
4737 auto *cc = static_cast<NSControl *>(d->cocoaControl(cw));
4738 cc.enabled = isEnabled;
4739 QRectF frameRect = cw.adjustedControlFrame(combo->rect);;
4740 if (cw.type == QMacStylePrivate::Button_PopupButton) {
4741 // Non-editable QComboBox
4742 auto *pb = static_cast<NSPopUpButton *>(cc);
4743 // FIXME Old offsets. Try to move to adjustedControlFrame()
4744 if (cw.size == QStyleHelper::SizeSmall) {
4745 frameRect = frameRect.translated(0, 1);
4746 } else if (cw.size == QStyleHelper::SizeMini) {
4747 // Same 0.5 pt misalignment as AppKit and fit the focus ring
4748 frameRect = frameRect.translated(2, -0.5);
4749 }
4750 pb.frame = frameRect.toCGRect();
4751 [pb highlight:isPressed];
4752 d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) {
4753 QMacAutoReleasePool pool;
4754 [pb.cell drawBezelWithFrame:r inView:pb.superview];
4755 });
4756 } else if (cw.type == QMacStylePrivate::ComboBox) {
4757 // Editable QComboBox
4758 auto *cb = static_cast<NSComboBox *>(cc);
4759 const auto frameRect = cw.adjustedControlFrame(combo->rect);
4760 cb.frame = frameRect.toCGRect();
4761
4762 // This API was requested to Apple in rdar #36197888. We know it's safe to use up to macOS 10.13.3
4763 if (NSButtonCell *cell = static_cast<NSButtonCell *>([cc.cell qt_valueForPrivateKey:@"_buttonCell"])) {
4764 cell.highlighted = isPressed;
4765 } else {
4766 // TODO Render to pixmap and darken the button manually
4767 }
4768
4769 d->drawNSViewInRect(cb, frameRect, p, ^(CGContextRef, const CGRect &r) {
4770 // FIXME This is usually drawn in the control's superview, but we wouldn't get inactive look in this case
4771 QMacAutoReleasePool pool;
4772 [cb.cell drawWithFrame:r inView:cb];
4773 });
4774 }
4775 }
4776 break;
4777 case CC_SearchField:
4778 if (const auto *sf = qstyleoption_cast<const QStyleOptionSearchField *>(opt)) {
4779 const bool isEnabled = sf->state & State_Enabled;
4780
4781 const auto cs = d->effectiveAquaSizeConstrain(sf);
4782 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::SearchField, cs);
4783 auto *searchField = static_cast<NSSearchField *>(d->cocoaControl(cw));
4784 auto *cell = static_cast<NSSearchFieldCell *>(searchField.cell);
4785
4786 searchField.enabled = isEnabled;
4787
4788 if (sf->subControls == QStyle::SC_SearchFieldSearch) {
4789 // Draw only the search icon
4790 CGRect rect = [cell searchButtonRectForBounds:searchField.bounds];
4791 [cell drawWithFrame:rect inView:searchField];
4792 } else if (sf->subControls == QStyle::SC_SearchFieldClear) {
4793 // Draw only the clear icon
4794 CGRect rect = [cell cancelButtonRectForBounds:searchField.bounds];
4795 [cell drawWithFrame:rect inView:searchField];
4796 } else {
4797 // Draw the frame
4798 QRectF frameRect = cw.adjustedControlFrame(sf->rect);
4799 searchField.frame = frameRect.toCGRect();
4800 [cell setStringValue:sf->text.toNSString()];
4801 d->drawNSViewInRect(searchField, frameRect, p, ^(CGContextRef, const CGRect &r) {
4802 [cell drawWithFrame:r inView:searchField];
4803 });
4804 }
4805 }
4806 break;
4807 case CC_TitleBar:
4808 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) {
4809 const bool isActive = (titlebar->state & State_Active)
4810 && (titlebar->titleBarState & State_Active);
4811
4812 p->fillRect(opt->rect, Qt::transparent);
4813 p->setRenderHint(QPainter::Antialiasing);
4814 p->setClipRect(opt->rect, Qt::IntersectClip);
4815
4816 // FIXME A single drawPath() with 0-sized pen
4817 // doesn't look as good as this double fillPath().
4818 const auto outerFrameRect = QRectF(opt->rect.adjusted(0, 0, 0, opt->rect.height()));
4819 QPainterPath outerFramePath = d->windowPanelPath(outerFrameRect);
4820 p->fillPath(outerFramePath, opt->palette.dark());
4821
4822 const auto frameAdjust = 1.0 / p->device()->devicePixelRatioF();
4823 const auto innerFrameRect = outerFrameRect.adjusted(frameAdjust, frameAdjust, -frameAdjust, 0);
4824 QPainterPath innerFramePath = d->windowPanelPath(innerFrameRect);
4825 p->fillPath(innerFramePath, opt->palette.button());
4826
4827 if (titlebar->subControls & (SC_TitleBarCloseButton
4828 | SC_TitleBarMaxButton
4829 | SC_TitleBarMinButton
4830 | SC_TitleBarNormalButton)) {
4831 const bool isHovered = (titlebar->state & State_MouseOver);
4832 static const SubControl buttons[] = {
4833 SC_TitleBarCloseButton, SC_TitleBarMinButton, SC_TitleBarMaxButton
4834 };
4835 for (const auto sc : buttons) {
4836 const auto ct = d->windowButtonCocoaControl(sc);
4837 const auto cw = QMacStylePrivate::CocoaControl(ct, QStyleHelper::SizeLarge);
4838 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw));
4839 wb.enabled = (sc & titlebar->subControls) && isActive;
4840 [wb highlight:(titlebar->state & State_Sunken) && (sc & titlebar->activeSubControls)];
4841 Q_UNUSED(isHovered); // FIXME No public API for this
4842
4843 const auto buttonRect = proxy()->subControlRect(CC_TitleBar, titlebar, sc);
4844 d->drawNSViewInRect(wb, buttonRect, p, ^(CGContextRef, const CGRect &rect) {
4845 QMacAutoReleasePool pool;
4846 auto *wbCell = static_cast<NSButtonCell *>(wb.cell);
4847 [wbCell drawWithFrame:rect inView:wb];
4848 });
4849 }
4850 }
4851
4852 if (titlebar->subControls & SC_TitleBarLabel) {
4853 const auto tr = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarLabel);
4854 if (!titlebar->icon.isNull()) {
4855 const auto iconExtent = proxy()->pixelMetric(PM_SmallIconSize);
4856 const auto iconSize = QSize(iconExtent, iconExtent);
4857 const auto iconPos = tr.x() - titlebar->icon.actualSize(iconSize).width() - qRound(titleBarIconTitleSpacing);
4858 // Only render the icon if it'll be fully visible
4859 if (iconPos < tr.right() - titleBarIconTitleSpacing)
4860 p->drawPixmap(iconPos, tr.y(),
4861 titlebar->icon.pixmap(iconSize,
4862 opt->window->devicePixelRatio(),
4863 QIcon::Normal));
4864 }
4865
4866 if (!titlebar->text.isEmpty())
4867 drawItemText(p, tr, Qt::AlignCenter, opt->palette, isActive, titlebar->text, QPalette::Text);
4868 }
4869 }
4870 break;
4871 case CC_GroupBox:
4872 if (const QStyleOptionGroupBox *gb
4873 = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
4874
4875 QStyleOptionGroupBox groupBox(*gb);
4876 const bool flat = groupBox.features & QStyleOptionFrame::Flat;
4877 if (!flat)
4878 groupBox.state |= QStyle::State_Mini; // Force mini-sized checkbox to go with small-sized label
4879 else
4880 groupBox.subControls = groupBox.subControls & ~SC_GroupBoxFrame; // We don't like frames and ugly lines
4881
4882// const bool didSetFont = widget && widget->testAttribute(Qt::WA_SetFont);
4883// const bool didModifySubControls = !didSetFont && QApplication::desktopSettingsAware();
4884// if (didModifySubControls)
4885// groupBox.subControls = groupBox.subControls & ~SC_GroupBoxLabel;
4886 QCommonStyle::drawComplexControl(cc, &groupBox, p);
4887// if (didModifySubControls) {
4888// const QRect rect = proxy()->subControlRect(CC_GroupBox, &groupBox, SC_GroupBoxLabel);
4889// const bool rtl = groupBox.direction == Qt::RightToLeft;
4890// const int alignment = Qt::TextHideMnemonic | (rtl ? Qt::AlignRight : Qt::AlignLeft);
4891// const QFont savedFont = p->font();
4892// if (!flat)
4893// p->setFont(d->smallSystemFont);
4894// proxy()->drawItemText(p, rect, alignment, groupBox.palette, groupBox.state & State_Enabled, groupBox.text, QPalette::WindowText);
4895// if (!flat)
4896// p->setFont(savedFont);
4897// }
4898 }
4899 break;
4900 case CC_ToolButton:
4901 if (const QStyleOptionToolButton *tb
4902 = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
4903#ifndef QT_NO_ACCESSIBILITY
4904 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) {
4905 if (tb->subControls & SC_ToolButtonMenu) {
4906 QStyleOption arrowOpt = *tb;
4907 arrowOpt.rect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu);
4908 arrowOpt.rect.setY(arrowOpt.rect.y() + arrowOpt.rect.height() / 2);
4909 arrowOpt.rect.setHeight(arrowOpt.rect.height() / 2);
4910 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p);
4911 } else if ((tb->features & QStyleOptionToolButton::HasMenu)
4912 && (tb->toolButtonStyle != Qt::ToolButtonTextOnly && !tb->icon.isNull())) {
4913 d->drawToolbarButtonArrow(tb, p);
4914 }
4915 if (tb->state & State_On) {
4916 NSView *view = reinterpret_cast<NSView *>(opt->window->winId());
4917 bool isKey = false;
4918 if (view)
4919 isKey = [view.window isKeyWindow];
4920
4921 QBrush brush(brushForToolButton(isKey));
4922 QPainterPath path;
4923 path.addRoundedRect(QRectF(tb->rect.x(), tb->rect.y(), tb->rect.width(), tb->rect.height() + 4), 4, 4);
4924 p->setRenderHint(QPainter::Antialiasing);
4925 p->fillPath(path, brush);
4926 }
4927 proxy()->drawControl(CE_ToolButtonLabel, opt, p);
4928 } else
4929#endif // QT_NO_ACCESSIBILITY
4930 {
4931 auto bflags = tb->state;
4932 if (tb->subControls & SC_ToolButton)
4933 bflags |= State_Sunken;
4934 auto mflags = tb->state;
4935 if (tb->subControls & SC_ToolButtonMenu)
4936 mflags |= State_Sunken;
4937
4938 if (tb->subControls & SC_ToolButton) {
4939 if (bflags & (State_Sunken | State_On | State_Raised)) {
4940 const bool isEnabled = tb->state & State_Enabled;
4941 const bool isPressed = tb->state & State_Sunken;
4942 const bool isHighlighted = (tb->state & State_Active) && (tb->state & State_On);
4943 const auto ct = QMacStylePrivate::Button_PushButton;
4944 const auto cs = d->effectiveAquaSizeConstrain(opt);
4945 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
4946 auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
4947 pb.bezelStyle = NSBezelStyleShadowlessSquare; // TODO Use NSTexturedRoundedBezelStyle in the future.
4948 pb.frame = opt->rect.toCGRect();
4949 pb.buttonType = NSButtonTypePushOnPushOff;
4950 pb.enabled = isEnabled;
4951 [pb highlight:isPressed];
4952 pb.state = isHighlighted && !isPressed ? NSControlStateValueOn : NSControlStateValueOff;
4953 const auto buttonRect = proxy()->subControlRect(cc, tb, SC_ToolButton);
4954 d->drawNSViewInRect(pb, buttonRect, p, ^(CGContextRef, const CGRect &rect) {
4955 QMacAutoReleasePool pool;
4956 [pb.cell drawBezelWithFrame:rect inView:pb];
4957 });
4958 }
4959 }
4960
4961 if (tb->subControls & SC_ToolButtonMenu) {
4962 const auto menuRect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu);
4963 QStyleOption arrowOpt = *tb;
4964 arrowOpt.rect = QRect(menuRect.x() + ((menuRect.width() - toolButtonArrowSize) / 2),
4965 menuRect.height() - (toolButtonArrowSize + toolButtonArrowMargin),
4966 toolButtonArrowSize,
4967 toolButtonArrowSize);
4968 proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p);
4969 } else if (tb->features & QStyleOptionToolButton::HasMenu) {
4970 d->drawToolbarButtonArrow(tb, p);
4971 }
4972 QRect buttonRect = proxy()->subControlRect(CC_ToolButton, tb, SC_ToolButton);
4973 int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt);
4974 QStyleOptionToolButton label = *tb;
4975 label.rect = buttonRect.adjusted(fw, fw, -fw, -fw);
4976 proxy()->drawControl(CE_ToolButtonLabel, &label, p);
4977 }
4978 }
4979 break;
4980 case CC_Dial:
4981 if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt))
4982 QStyleHelper::drawDial(dial, p);
4983 break;
4984 default:
4985 QCommonStyle::drawComplexControl(cc, opt, p);
4986 break;
4987 }
4988}
4989
4990QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, const QPoint &pt) const
4991{
4992 Q_D(const QMacStyle);
4993
4994 SubControl sc = QStyle::SC_None;
4995
4996 switch (cc) {
4997 case CC_ComboBox:
4998 if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
4999 sc = QCommonStyle::hitTestComplexControl(cc, cmb, pt);
5000 if (!cmb->editable && sc != QStyle::SC_None)
5001 sc = SC_ComboBoxArrow; // A bit of a lie, but what we want
5002 }
5003 break;
5004 case CC_Slider:
5005 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5006 if (!sl->rect.contains(pt))
5007 break;
5008
5009 const bool hasTicks = sl->tickPosition != QStyleOptionSlider::NoTicks;
5010 const bool isHorizontal = sl->orientation == Qt::Horizontal;
5011 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical;
5012 const auto cs = d->effectiveAquaSizeConstrain(opt);
5013 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5014 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw));
5015 if (!setupSlider(slider, sl))
5016 break;
5017
5018 NSSliderCell *cell = slider.cell;
5019 const auto barRect = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]);
5020 const auto knobRect = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]);
5021 if (knobRect.contains(pt)) {
5022 sc = SC_SliderHandle;
5023 } else if (barRect.contains(pt)) {
5024 sc = SC_SliderGroove;
5025 } else if (hasTicks) {
5026 sc = SC_SliderTickmarks;
5027 }
5028 }
5029 break;
5030 case CC_ScrollBar:
5031 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5032 if (!sb->rect.contains(pt)) {
5033 sc = SC_None;
5034 break;
5035 }
5036
5037 const bool isHorizontal = sb->orientation == Qt::Horizontal;
5038 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical;
5039 const auto cs = d->effectiveAquaSizeConstrain(opt);
5040 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5041 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw));
5042 if (!setupScroller(scroller, sb)) {
5043 sc = SC_None;
5044 break;
5045 }
5046
5047 // Since -[NSScroller testPart:] doesn't want to cooperate, we do it the
5048 // straightforward way. In any case, macOS doesn't return line-sized changes
5049 // with NSScroller since 10.7, according to the aforementioned method's doc.
5050 const auto knobRect = QRectF::fromCGRect([scroller rectForPart:NSScrollerKnob]);
5051 if (isHorizontal) {
5052 const bool isReverse = sb->direction == Qt::RightToLeft;
5053 if (pt.x() < knobRect.left())
5054 sc = isReverse ? SC_ScrollBarAddPage : SC_ScrollBarSubPage;
5055 else if (pt.x() > knobRect.right())
5056 sc = isReverse ? SC_ScrollBarSubPage : SC_ScrollBarAddPage;
5057 else
5058 sc = SC_ScrollBarSlider;
5059 } else {
5060 if (pt.y() < knobRect.top())
5061 sc = SC_ScrollBarSubPage;
5062 else if (pt.y() > knobRect.bottom())
5063 sc = SC_ScrollBarAddPage;
5064 else
5065 sc = SC_ScrollBarSlider;
5066 }
5067 }
5068 break;
5069 case CC_SearchField:
5070 if (const auto *sf = qstyleoption_cast<const QStyleOptionSearchField *>(opt)) {
5071 if (!sf->rect.contains(pt))
5072 break;
5073
5074 const auto cs = d->effectiveAquaSizeConstrain(sf);
5075 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::SearchField, cs);
5076 auto *searchField = static_cast<NSSearchField *>(d->cocoaControl(cw));
5077 searchField.frame = cw.adjustedControlFrame(sf->rect).toCGRect();
5078
5079 auto *cell = static_cast<NSSearchFieldCell *>(searchField.cell);
5080 const CGRect bounds = searchField.bounds;
5081
5082 const QRectF cancelRect = QRectF::fromCGRect([cell cancelButtonRectForBounds:bounds]);
5083 const QRectF searchIconRect = QRectF::fromCGRect([cell searchButtonRectForBounds:bounds]);
5084 const QRectF textFieldRect = QRectF::fromCGRect([cell searchTextRectForBounds:bounds]);
5085
5086 const QPointF localPt = pt - sf->rect.topLeft();
5087
5088 if (cancelRect.contains(localPt))
5089 sc = SC_SearchFieldClear;
5090 else if (searchIconRect.contains(localPt))
5091 sc = SC_SearchFieldSearch;
5092 else if (textFieldRect.contains(localPt))
5093 sc = SC_SearchFieldEditField;
5094 else
5095 sc = SC_SearchFieldPopup;
5096
5097 break;
5098 }
5099 break;
5100 default:
5101 sc = QCommonStyle::hitTestComplexControl(cc, opt, pt);
5102 break;
5103 }
5104 return sc;
5105}
5106
5107QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc) const
5108{
5109 Q_D(const QMacStyle);
5110
5111 QRect ret;
5112
5113 switch (cc) {
5114 case CC_ScrollBar:
5115 if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5116 const bool isHorizontal = sb->orientation == Qt::Horizontal;
5117 const bool isReverseHorizontal = isHorizontal && (sb->direction == Qt::RightToLeft);
5118
5119 NSScrollerPart part = NSScrollerNoPart;
5120 if (sc == SC_ScrollBarSlider) {
5121 part = NSScrollerKnob;
5122 } else if (sc == SC_ScrollBarGroove) {
5123 part = NSScrollerKnobSlot;
5124 } else if (sc == SC_ScrollBarSubPage || sc == SC_ScrollBarAddPage) {
5125 if ((!isReverseHorizontal && sc == SC_ScrollBarSubPage)
5126 || (isReverseHorizontal && sc == SC_ScrollBarAddPage))
5127 part = NSScrollerDecrementPage;
5128 else
5129 part = NSScrollerIncrementPage;
5130 }
5131 // And nothing else since 10.7
5132
5133 if (part != NSScrollerNoPart) {
5134 const auto ct = isHorizontal ? QMacStylePrivate::Scroller_Horizontal : QMacStylePrivate::Scroller_Vertical;
5135 const auto cs = d->effectiveAquaSizeConstrain(opt);
5136 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5137 auto *scroller = static_cast<NSScroller *>(d->cocoaControl(cw));
5138 if (setupScroller(scroller, sb))
5139 ret = QRectF::fromCGRect([scroller rectForPart:part]).toRect();
5140 }
5141 }
5142 break;
5143 case CC_Slider:
5144 if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5145 const bool hasTicks = sl->tickPosition != QStyleOptionSlider::NoTicks;
5146 const bool isHorizontal = sl->orientation == Qt::Horizontal;
5147 const auto ct = isHorizontal ? QMacStylePrivate::Slider_Horizontal : QMacStylePrivate::Slider_Vertical;
5148 const auto cs = d->effectiveAquaSizeConstrain(opt);
5149 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5150 auto *slider = static_cast<NSSlider *>(d->cocoaControl(cw));
5151 if (!setupSlider(slider, sl))
5152 break;
5153
5154 NSSliderCell *cell = slider.cell;
5155 if (sc == SC_SliderHandle) {
5156 ret = QRectF::fromCGRect([cell knobRectFlipped:slider.isFlipped]).toRect();
5157 } else if (sc == SC_SliderGroove) {
5158 ret = QRectF::fromCGRect([cell barRectFlipped:slider.isFlipped]).toRect();
5159 } else if (hasTicks && sc == SC_SliderTickmarks) {
5160 const auto tickMarkRect = QRectF::fromCGRect([cell rectOfTickMarkAtIndex:0]);
5161 if (isHorizontal)
5162 ret = QRect(sl->rect.left(), tickMarkRect.top(), sl->rect.width(), tickMarkRect.height());
5163 else
5164 ret = QRect(tickMarkRect.left(), sl->rect.top(), tickMarkRect.width(), sl->rect.height());
5165 }
5166
5167// if (sl->upsideDown) {
5168// if isHorizontal) {
5169// } else {
5170// }
5171// }
5172 }
5173 break;
5174 case CC_TitleBar:
5175 if (const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) {
5176 // The title bar layout is as follows: close, min, zoom, icon, title
5177 // [ x _ + @ Window Title ]
5178 // Center the icon and title until it starts to overlap with the buttons.
5179 // The icon doesn't count towards SC_TitleBarLabel, but it's still rendered
5180 // next to the title text. See drawComplexControl().
5181 if (sc == SC_TitleBarLabel) {
5182 qreal labelWidth = titlebar->fontMetrics.horizontalAdvance(titlebar->text) + 1; // FIXME Rounding error?
5183 qreal labelHeight = titlebar->fontMetrics.height();
5184
5185 const auto lastButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarMaxButton);
5186 qreal controlsSpacing = lastButtonRect.right() + titleBarButtonSpacing;
5187 if (!titlebar->icon.isNull()) {
5188 const auto iconSize = proxy()->pixelMetric(PM_SmallIconSize);
5189 const auto actualIconSize = titlebar->icon.actualSize(QSize(iconSize, iconSize)).width();;
5190 controlsSpacing += actualIconSize + titleBarIconTitleSpacing;
5191 }
5192
5193 const qreal labelPos = qMax(controlsSpacing, (opt->rect.width() - labelWidth) / 2.0);
5194 labelWidth = qMin(labelWidth, opt->rect.width() - (labelPos + titleBarTitleRightMargin));
5195 ret = QRect(labelPos, (opt->rect.height() - labelHeight) / 2,
5196 labelWidth, labelHeight);
5197 } else {
5198 const auto currentButton = d->windowButtonCocoaControl(sc);
5199 if (currentButton == QMacStylePrivate::NoControl)
5200 break;
5201
5202 QPointF buttonPos = titlebar->rect.topLeft() + QPointF(titleBarButtonSpacing, 0);
5203 QSizeF buttonSize;
5204 for (int ct = QMacStylePrivate::Button_WindowClose; ct <= currentButton; ct++) {
5205 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::CocoaControlType(ct),
5206 QStyleHelper::SizeLarge);
5207 auto *wb = static_cast<NSButton *>(d->cocoaControl(cw));
5208 if (ct == currentButton)
5209 buttonSize = QSizeF::fromCGSize(wb.frame.size);
5210 else
5211 buttonPos.rx() += wb.frame.size.width + titleBarButtonSpacing;
5212 }
5213
5214 const auto vOffset = (opt->rect.height() - buttonSize.height()) / 2.0;
5215 ret = QRectF(buttonPos, buttonSize).translated(0, vOffset).toRect();
5216 }
5217 }
5218 break;
5219 case CC_ComboBox:
5220 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
5221 const auto ct = cocoaControlType(combo);
5222 const auto cs = d->effectiveAquaSizeConstrain(combo);
5223 const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
5224
5225 // Old widget path. Current not understood why it's needed:
5226 //const auto editRect = QMacStylePrivate::comboboxEditBounds(cw.adjustedControlFrame(combo->rect), cw);
5227
5228 // New path:
5229 QRectF editRect;
5230 switch (cs) {
5231 case QStyleHelper::SizeLarge:
5232 editRect = combo->rect.adjusted(15, 7, -25, -9);
5233 break;
5234 case QStyleHelper::SizeSmall:
5235 if (combo->editable)
5236 editRect = combo->rect.adjusted(15, 6, -22, -9);
5237 else
5238 editRect = combo->rect.adjusted(15, 8, -22, -6);
5239 break;
5240 default:
5241 if (combo->editable)
5242 editRect = combo->rect.adjusted(15, 6, -20, -7);
5243 else
5244 editRect = combo->rect.adjusted(15, 5, -22, -6);
5245 break;
5246 }
5247
5248 switch (sc) {
5249 case SC_ComboBoxEditField:{
5250 ret = editRect.toAlignedRect();
5251 break; }
5252 case SC_ComboBoxArrow:{
5253 ret = editRect.toAlignedRect();
5254 ret.setX(ret.x() + ret.width());
5255 ret.setWidth(combo->rect.right() - ret.right());
5256 break; }
5257 case SC_ComboBoxListBoxPopup:{
5258 if (combo->editable) {
5259 const CGRect inner = QMacStylePrivate::comboboxInnerBounds(combo->rect.toCGRect(), cw);
5260 const int comboTop = combo->rect.top();
5261 ret = QRect(qRound(inner.origin.x),
5262 comboTop,
5263 qRound(inner.origin.x - combo->rect.left() + inner.size.width),
5264 editRect.bottom() - comboTop + 2);
5265 } else {
5266 ret = QRect(combo->rect.x() + 4 - 11,
5267 combo->rect.y() + 1,
5268 editRect.width() + 10 + 11,
5269 1);
5270 }
5271 break; }
5272 default:
5273 break;
5274 }
5275 }
5276 break;
5277 case CC_GroupBox:
5278 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
5279 bool checkable = groupBox->subControls & SC_GroupBoxCheckBox;
5280 const bool flat = groupBox->features & QStyleOptionFrame::Flat;
5281 bool hasNoText = !checkable && groupBox->text.isEmpty();
5282 switch (sc) {
5283 case SC_GroupBoxLabel:
5284 case SC_GroupBoxCheckBox: {
5285 // Cheat and use the smaller font if we need to
5286 const bool checkable = groupBox->subControls & SC_GroupBoxCheckBox;
5287 const bool fontIsSet = false;
5288// const bool fontIsSet = (widget && widget->testAttribute(Qt::WA_SetFont))
5289// || !QApplication::desktopSettingsAware();
5290 const int margin = flat || hasNoText ? 0 : 9;
5291 ret = groupBox->rect.adjusted(margin, 0, -margin, 0);
5292
5293 const QFontMetricsF fm = flat || fontIsSet ? QFontMetricsF(groupBox->fontMetrics) : QFontMetricsF(d->smallSystemFont);
5294 const QSizeF s = fm.size(Qt::AlignHCenter | Qt::AlignVCenter, qt_mac_removeMnemonics(groupBox->text), 0, nullptr);
5295 const int tw = qCeil(s.width());
5296 const int h = qCeil(fm.height());
5297 ret.setHeight(h);
5298
5299 QRect labelRect = alignedRect(groupBox->direction, groupBox->textAlignment,
5300 QSize(tw, h), ret);
5301 if (flat && checkable)
5302 labelRect.moveLeft(labelRect.left() + 4);
5303 int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt);
5304 bool rtl = groupBox->direction == Qt::RightToLeft;
5305 if (sc == SC_GroupBoxLabel) {
5306 if (checkable) {
5307 int newSum = indicatorWidth + 1;
5308 int newLeft = labelRect.left() + (rtl ? -newSum : newSum);
5309 labelRect.moveLeft(newLeft);
5310 if (flat)
5311 labelRect.moveTop(labelRect.top() + 3);
5312 else
5313 labelRect.moveTop(labelRect.top() + 4);
5314 } else if (flat) {
5315 int newLeft = labelRect.left() - (rtl ? 3 : -3);
5316 labelRect.moveLeft(newLeft);
5317 labelRect.moveTop(labelRect.top() + 3);
5318 } else {
5319 int newLeft = labelRect.left() - (rtl ? 3 : 2);
5320 labelRect.moveLeft(newLeft);
5321 labelRect.moveTop(labelRect.top() + 4);
5322 }
5323 ret = labelRect;
5324 }
5325
5326 if (sc == SC_GroupBoxCheckBox) {
5327 int left = rtl ? labelRect.right() - indicatorWidth : labelRect.left() - 1;
5328 int top = flat ? ret.top() + 1 : ret.top() + 5;
5329 ret.setRect(left, top,
5330 indicatorWidth, proxy()->pixelMetric(PM_IndicatorHeight, opt));
5331 }
5332 break;
5333 }
5334 case SC_GroupBoxContents:
5335 case SC_GroupBoxFrame: {
5336 QFontMetrics fm = groupBox->fontMetrics;
5337 int yOffset = 3;
5338 if (!flat)
5339 yOffset = 5;
5340
5341 if (hasNoText)
5342 yOffset = -qCeil(QFontMetricsF(fm).height());
5343 ret = opt->rect.adjusted(0, qCeil(QFontMetricsF(fm).height()) + yOffset, 0, 0);
5344 if (sc == SC_GroupBoxContents) {
5345 if (flat)
5346 ret.adjust(3, -5, -3, -4); // guess too
5347 else
5348 ret.adjust(3, 3, -3, -4); // guess
5349 }
5350 }
5351 break;
5352 default:
5353 ret = QCommonStyle::subControlRect(cc, groupBox, sc);
5354 break;
5355 }
5356 }
5357 break;
5358 case CC_SpinBox:
5359 if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
5360 QStyleHelper::WidgetSizePolicy aquaSize = d->effectiveAquaSizeConstrain(spin);
5361 const auto fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, spin);
5362 int spinner_w;
5363 int spinner_h;
5364 int adjust_y;
5365 int spinBoxSep;
5366 switch (aquaSize) {
5367 case QStyleHelper::SizeLarge:
5368 spinner_w = 14;
5369 spinner_h = 24;
5370 adjust_y = -1;
5371 spinBoxSep = 2;
5372 break;
5373 case QStyleHelper::SizeSmall:
5374 spinner_w = 12;
5375 spinner_h = 20;
5376 adjust_y = -1;
5377 spinBoxSep = 2;
5378 break;
5379 case QStyleHelper::SizeMini:
5380 spinner_w = 10;
5381 spinner_h = 16;
5382 adjust_y = -1;
5383 spinBoxSep = 1;
5384 break;
5385 default:
5386 Q_UNREACHABLE();
5387 }
5388
5389 switch (sc) {
5390 case SC_SpinBoxUp:
5391 case SC_SpinBoxDown: {
5392 if (spin->buttonSymbols == QStyleOptionSpinBox::NoButtons)
5393 break;
5394
5395 const int y = fw;
5396 const int x = spin->rect.width() - spinner_w;
5397 ret.setRect(x + spin->rect.x(), y + spin->rect.y(), spinner_w, spinner_h);
5398
5399 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::Stepper, aquaSize);
5400 NSStepperCell *cell = static_cast<NSStepperCell *>(d->cocoaCell(cw));
5401 const CGRect outRect = [cell drawingRectForBounds:ret.toCGRect()];
5402 ret = QRectF::fromCGRect(outRect).toRect();
5403
5404 switch (sc) {
5405 case SC_SpinBoxUp:
5406 ret.setHeight(ret.height() / 2);
5407 break;
5408 case SC_SpinBoxDown:
5409 ret.setY(ret.y() + ret.height() / 2);
5410 break;
5411 default:
5412 Q_ASSERT(0);
5413 break;
5414 }
5415 // The buttons are drawn with a top-margin (for some reason) into
5416 // the rect. So undo that margin here:
5417 ret.translate(0, adjust_y);
5418 ret = visualRect(spin->direction, spin->rect, ret);
5419 break;
5420 }
5421 case SC_SpinBoxEditField:
5422 ret = spin->rect.adjusted(fw, fw, -fw, -fw);
5423 if (spin->subControls & SC_SpinBoxUp || spin->subControls & SC_SpinBoxDown) {
5424 ret.setWidth(spin->rect.width() - spinBoxSep - spinner_w);
5425 ret = visualRect(spin->direction, spin->rect, ret);
5426 }
5427 break;
5428 default:
5429 ret = QCommonStyle::subControlRect(cc, spin, sc);
5430 break;
5431 }
5432 }
5433 break;
5434 case CC_ToolButton:
5435 ret = QCommonStyle::subControlRect(cc, opt, sc);
5436 if (sc == SC_ToolButtonMenu) {
5437#ifndef QT_NO_ACCESSIBILITY
5438 if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar))
5439 ret.adjust(-toolButtonArrowMargin, 0, 0, 0);
5440#endif
5441 ret.adjust(-1, 0, 0, 0);
5442 }
5443 break;
5444 case CC_SearchField:
5445 if (const QStyleOptionSearchField *sf = qstyleoption_cast<const QStyleOptionSearchField *>(opt)) {
5446 const auto cs = d->effectiveAquaSizeConstrain(sf);
5447 const auto cw = QMacStylePrivate::CocoaControl(QMacStylePrivate::SearchField, cs);
5448
5449 QRectF editRect;
5450 switch (cs) {
5451 case QStyleHelper::SizeLarge:
5452 editRect = sf->rect.adjusted(16, 7, -22, -6);
5453 break;
5454 case QStyleHelper::SizeSmall:
5455 editRect = sf->rect.adjusted(16, 5, -22, -7);
5456 break;
5457 default:
5458 editRect = sf->rect.adjusted(16, 5, -18, -7);
5459 break;
5460 }
5461
5462 auto *searchField = static_cast<NSSearchField *>(d->cocoaControl(cw));
5463 auto *cell = static_cast<NSSearchFieldCell *>(searchField.cell);
5464 switch (sc) {
5465 case SC_SearchFieldEditField:{
5466 ret = editRect.toAlignedRect();
5467 ret.setX(ret.x() + QMacStylePrivate::PushButtonContentPadding);
5468 break;
5469 }
5470 case SC_SearchFieldClear: {
5471 ret = QRectF::fromCGRect([cell cancelButtonRectForBounds:searchField.bounds]).toAlignedRect();
5472 break;
5473 }
5474 case SC_SearchFieldSearch: {
5475 ret = QRectF::fromCGRect([cell searchButtonRectForBounds:searchField.bounds]).toAlignedRect();
5476 break;
5477 }
5478 case SC_SearchFieldPopup: {
5479 const CGRect inner = QMacStylePrivate::comboboxInnerBounds(sf->rect.toCGRect(), cw);
5480 const int searchTop = sf->rect.top();
5481 ret = QRect(qRound(inner.origin.x),
5482 searchTop,
5483 qRound(inner.origin.x - sf->rect.left() + inner.size.width),
5484 editRect.bottom() - searchTop + 2);
5485 break;
5486 }
5487 default:
5488 break;
5489 }
5490 }
5491 break;
5492 default:
5493 ret = QCommonStyle::subControlRect(cc, opt, sc);
5494 break;
5495 }
5496 return ret;
5497}
5498
5499QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &csz) const
5500{
5501 Q_D(const QMacStyle);
5502
5503 QSize sz(csz);
5504 bool useAquaGuideline = true;
5505
5506 switch (ct) {
5507 case CT_SpinBox:
5508 if (const QStyleOptionSpinBox *vopt = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
5509 if (vopt->subControls == SC_SpinBoxFrame) {
5510 const QSize minimumSize(20, 24);
5511 if (sz.width() < minimumSize.width())
5512 sz.setWidth(minimumSize.width());
5513 if (sz.height() < minimumSize.height())
5514 sz.setHeight(minimumSize.height());
5515 } else {
5516 const QSize buttonSize = proxy()->subControlRect(CC_SpinBox, vopt, SC_SpinBoxUp).size();
5517 const int upAndDownTogetherHeight = buttonSize.height() * 2;
5518 sz += QSize(buttonSize.width(), upAndDownTogetherHeight);
5519 }
5520 }
5521 break;
5522 case QStyle::CT_TabWidget:
5523 // the size between the pane and the "contentsRect" (+4,+4)
5524 // (the "contentsRect" is on the inside of the pane)
5525 sz = QCommonStyle::sizeFromContents(ct, opt, csz);
5526 /**
5527 This is supposed to show the relationship between the tabBar and
5528 the stack widget of a QTabWidget.
5529 Unfortunately ascii is not a good way of representing graphics.....
5530 PS: The '=' line is the painted frame.
5531
5532 top ---+
5533 |
5534 |
5535 |
5536 | vvv just outside the painted frame is the "pane"
5537 - -|- - - - - - - - - - <-+
5538 TAB BAR +=====^============ | +2 pixels
5539 - - -|- - -|- - - - - - - <-+
5540 | | ^ ^^^ just inside the painted frame is the "contentsRect"
5541 | | |
5542 | overlap |
5543 | | |
5544 bottom ------+ <-+ +14 pixels
5545 |
5546 v
5547 ------------------------------ <- top of stack widget
5548
5549
5550 To summarize:
5551 * 2 is the distance between the pane and the contentsRect
5552 * The 14 and the 1's are the distance from the contentsRect to the stack widget.
5553 (same value as used in SE_TabWidgetTabContents)
5554 * overlap is how much the pane should overlap the tab bar
5555 */
5556 // then add the size between the stackwidget and the "contentsRect"
5557 if (const QStyleOptionTabWidgetFrame *twf
5558 = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
5559 QSize extra(0,0);
5560 const int overlap = pixelMetric(PM_TabBarBaseOverlap, opt);
5561 const int gapBetweenTabbarAndStackWidget = 2 + 14 - overlap;
5562
5563 const auto tabDirection = QMacStylePrivate::tabDirection(twf->shape);
5564 if (tabDirection == QMacStylePrivate::North
5565 || tabDirection == QMacStylePrivate::South) {
5566 extra = QSize(2, gapBetweenTabbarAndStackWidget + 1);
5567 } else {
5568 extra = QSize(gapBetweenTabbarAndStackWidget + 1, 2);
5569 }
5570 sz+= extra;
5571 }
5572 break;
5573 case QStyle::CT_TabBarTab:
5574 if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
5575// const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont))
5576// || !QApplication::desktopSettingsAware();
5577 const bool differentFont = false;
5578 const auto tabDirection = QMacStylePrivate::tabDirection(tab->shape);
5579 const bool verticalTabs = tabDirection == QMacStylePrivate::East
5580 || tabDirection == QMacStylePrivate::West;
5581 if (verticalTabs)
5582 sz = sz.transposed();
5583
5584 int defaultTabHeight;
5585 const auto cs = d->effectiveAquaSizeConstrain(opt);
5586 switch (cs) {
5587 case QStyleHelper::SizeLarge:
5588 if (tab->documentMode)
5589 defaultTabHeight = 24;
5590 else
5591 defaultTabHeight = 21;
5592 break;
5593 case QStyleHelper::SizeSmall:
5594 defaultTabHeight = 18;
5595 break;
5596 case QStyleHelper::SizeMini:
5597 defaultTabHeight = 16;
5598 break;
5599 default:
5600 break;
5601 }
5602
5603 const bool widthSet = !differentFont && tab->icon.isNull();
5604 if (widthSet) {
5605 const auto textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, tab->text);
5606 sz.rwidth() = textSize.width();
5607 sz.rheight() = qMax(defaultTabHeight, textSize.height());
5608 } else {
5609 sz.rheight() = qMax(defaultTabHeight, sz.height());
5610 }
5611 sz.rwidth() += proxy()->pixelMetric(PM_TabBarTabHSpace, tab);
5612
5613 if (verticalTabs)
5614 sz = sz.transposed();
5615
5616 int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height());
5617 int maxWidgetWidth = qMax(tab->leftButtonSize.width(), tab->rightButtonSize.width());
5618
5619 int widgetWidth = 0;
5620 int widgetHeight = 0;
5621 int padding = 0;
5622 if (tab->leftButtonSize.isValid()) {
5623 padding += 8;
5624 widgetWidth += tab->leftButtonSize.width();
5625 widgetHeight += tab->leftButtonSize.height();
5626 }
5627 if (tab->rightButtonSize.isValid()) {
5628 padding += 8;
5629 widgetWidth += tab->rightButtonSize.width();
5630 widgetHeight += tab->rightButtonSize.height();
5631 }
5632
5633 if (verticalTabs) {
5634 sz.setWidth(qMax(sz.width(), maxWidgetWidth));
5635 sz.setHeight(sz.height() + widgetHeight + padding);
5636 } else {
5637 if (widthSet)
5638 sz.setWidth(sz.width() + widgetWidth + padding);
5639 sz.setHeight(qMax(sz.height(), maxWidgetHeight));
5640 }
5641 }
5642 break;
5643 case CT_LineEdit:
5644 if (qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
5645 // Minimum size (with padding: 18x24)
5646 if (sz.width() < 10)
5647 sz.setWidth(10);
5648 if (sz.height() < 20)
5649 sz.setHeight(20);
5650
5651 // From using pixelTool with XCode/NSTextTextField
5652 int leftPadding = 4;
5653 int rightPadding = 4;
5654 int topPadding = 4;
5655 int bottomPadding = 0;
5656
5657 if (opt->state & QStyle::State_Small) {
5658 topPadding = 3;
5659 } else if (opt->state & QStyle::State_Mini) {
5660 topPadding = 2;
5661 }
5662
5663 sz.rwidth() += leftPadding + rightPadding;
5664 sz.rheight() += topPadding + bottomPadding;
5665 }
5666 break;
5667 case QStyle::CT_PushButton: {
5668 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt))
5669 if (btn->features & QStyleOptionButton::CommandLinkButton)
5670 return QCommonStyle::sizeFromContents(ct, opt, sz);
5671
5672 // By default, we fit the contents inside a normal rounded push button.
5673 // Do this by add enough space around the contents so that rounded
5674 // borders (including highlighting when active) will show.
5675 // TODO Use QFocusFrame and get rid of these horrors.
5676 QSize macsz;
5677 const auto controlSize = d->effectiveAquaSizeConstrain(opt, CT_PushButton, sz, &macsz);
5678 // FIXME See comment in CT_PushButton case in qt_aqua_get_known_size().
5679 if (macsz.width() != -1)
5680 sz.setWidth(macsz.width());
5681 else
5682 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12;
5683 // All values as measured from HIThemeGetButtonBackgroundBounds()
5684 if (controlSize != QStyleHelper::SizeMini)
5685 sz.rwidth() += 12; // We like 12 over here.
5686 if (controlSize == QStyleHelper::SizeLarge && sz.height() > 16)
5687 sz.rheight() += pushButtonDefaultHeight[QStyleHelper::SizeLarge] - 16;
5688 else if (controlSize == QStyleHelper::SizeMini)
5689 sz.setHeight(24); // FIXME Our previous HITheme-based logic returned this.
5690 else
5691 sz.setHeight(pushButtonDefaultHeight[controlSize]);
5692 break;
5693 }
5694 case QStyle::CT_MenuItem:
5695 if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
5696 int maxpmw = mi->maxIconWidth;
5697 int w = sz.width();
5698 int h = sz.height();
5699
5700//#if QT_CONFIG(combobox)
5701// const QComboBox *comboBox = qobject_cast<const QComboBox *>(widget);
5702//#endif
5703
5704 if (mi->menuItemType == QStyleOptionMenuItem::Separator) {
5705 w = 10;
5706 h = qt_mac_aqua_get_metric(MenuSeparatorHeight);
5707 } else {
5708 h = mi->fontMetrics.height() + 2;
5709 if (!mi->icon.isNull()) {
5710//#if QT_CONFIG(combobox)
5711// if (comboBox) {
5712// const QSize &iconSize = comboBox->iconSize();
5713// h = qMax(h, iconSize.height() + 4);
5714// maxpmw = qMax(maxpmw, iconSize.width());
5715// } else
5716//#endif
5717 {
5718 int iconExtent = proxy()->pixelMetric(PM_SmallIconSize);
5719 h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4);
5720 }
5721 }
5722 }
5723 if (mi->text.contains(QLatin1Char('\t')))
5724 w += 12;
5725 else if (mi->menuItemType == QStyleOptionMenuItem::SubMenu)
5726 w += 35; // Not quite exactly as it seems to depend on other factors
5727 if (maxpmw)
5728 w += maxpmw + 6;
5729 // add space for a check. All items have place for a check too.
5730 w += 20;
5731// if (comboBox && comboBox->isVisible()) {
5732// QStyleOptionComboBox cmb;
5733// cmb.initFrom(comboBox);
5734// cmb.editable = false;
5735// cmb.subControls = QStyle::SC_ComboBoxEditField;
5736// cmb.activeSubControls = QStyle::SC_None;
5737// w = qMax(w, subControlRect(QStyle::CC_ComboBox, &cmb,
5738// QStyle::SC_ComboBoxEditField,
5739// comboBox).width());
5740// } else {
5741// w += 12;
5742 sz = QSize(w, h);
5743 } break;
5744 case CT_MenuBarItem:
5745 if (!sz.isEmpty())
5746 sz += QSize(12, 4); // Constants from QWindowsStyle
5747 break;
5748 case CT_ToolButton:
5749 sz.rwidth() += 10;
5750 sz.rheight() += 10;
5751 if (const auto *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt))
5752 if (tb->features & QStyleOptionToolButton::Menu)
5753 sz.rwidth() += toolButtonArrowMargin;
5754 return sz;
5755 case CT_ComboBox:
5756 if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
5757 const int controlSize = getControlSize(opt);
5758
5759 // Set a sensible minimum width
5760 if (sz.width() < 10)
5761 sz.setWidth(10);
5762
5763 if (!cb->editable) {
5764 // Same as CT_PushButton, because we have to fit the focus
5765 // ring and a non-editable combo box is a NSPopUpButton.
5766 sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset;
5767
5768 if (controlSize == QStyleHelper::SizeLarge) {
5769 sz.rwidth() += 30;
5770 } else if (controlSize == QStyleHelper::SizeSmall) {
5771 sz.rwidth() += 26;
5772 } else {
5773 sz.rwidth() += 21;
5774 }
5775 } else {
5776 sz.rwidth() += 50; // FIXME Double check this
5777 }
5778
5779 // This should be enough to fit the focus ring
5780 if (controlSize == QStyleHelper::SizeMini)
5781 sz.setHeight(24); // FIXME Our previous HITheme-based logic returned this for CT_PushButton.
5782 else
5783 sz.setHeight(pushButtonDefaultHeight[controlSize]);
5784
5785 return sz;
5786 }
5787 break;
5788 case CT_SearchField:
5789 if (const QStyleOptionSearchField *sf = qstyleoption_cast<const QStyleOptionSearchField *>(opt)) {
5790 const QSize clearButton = proxy()->subControlRect(CC_SearchField, sf, SC_SearchFieldClear).size();
5791 const QSize searchButton = proxy()->subControlRect(CC_SearchField, sf, SC_SearchFieldSearch).size();
5792 if (sf->subControls == SC_SearchFieldFrame) {
5793 const int controlSize = getControlSize(opt);
5794 int padding;
5795 int iconSpacing;
5796
5797 if (controlSize == QStyleHelper::SizeLarge) {
5798 padding = 6;
5799 iconSpacing = 6;
5800 sz.setHeight(32);
5801 } else if (controlSize == QStyleHelper::SizeSmall) {
5802 padding = 5;
5803 iconSpacing = 5;
5804 sz.setHeight(28);
5805 } else {
5806 padding = 4;
5807 iconSpacing = 4;
5808 sz.setHeight(22);
5809 }
5810
5811 // minimum width
5812 if (sz.width() < 60)
5813 sz.setWidth(60);
5814
5815 const int totalIconsSize = clearButton.width() + searchButton.width() + (padding + iconSpacing) * 2;
5816 sz.rwidth() += totalIconsSize;
5817
5818 return sz;
5819 } else if (sf->subControls == SC_SearchFieldClear) {
5820 return clearButton;
5821 } else if (sf->subControls == SC_SearchFieldSearch) {
5822 return searchButton;
5823 }
5824 }
5825 break;
5826 case CT_Menu: {
5827 if (proxy() == this) {
5828 sz = csz;
5829 } else {
5830 QStyleHintReturnMask menuMask;
5831 QStyleOption myOption = *opt;
5832 myOption.rect.setSize(sz);
5833 if (proxy()->styleHint(SH_Menu_Mask, &myOption, &menuMask))
5834 sz = menuMask.region.boundingRect().size();
5835 }
5836 break; }
5837 case CT_HeaderSection:{
5838 const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt);
5839 sz = QCommonStyle::sizeFromContents(ct, opt, csz);
5840 if (header->text.contains(QLatin1Char('\n')))
5841 useAquaGuideline = false;
5842 break; }
5843 case CT_ScrollBar :
5844 // Make sure that the scroll bar is large enough to display the thumb indicator.
5845 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
5846 const int minimumWidth = 24;
5847 const int absoluteHeight = 14;
5848 if (slider->orientation == Qt::Horizontal) {
5849 sz = sz.expandedTo(QSize(minimumWidth, sz.height()));
5850 sz.setHeight(absoluteHeight);
5851 } else {
5852 sz = sz.expandedTo(QSize(sz.width(), minimumWidth));
5853 sz.setWidth(absoluteHeight);
5854 }
5855 }
5856 break;
5857 case CT_ItemViewItem:
5858 if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
5859 sz = QCommonStyle::sizeFromContents(ct, vopt, csz);
5860 sz.setHeight(sz.height() + 2);
5861 }
5862 break;
5863 default:
5864 sz = QCommonStyle::sizeFromContents(ct, opt, csz);
5865 }
5866
5867 if (useAquaGuideline && ct != CT_PushButton) {
5868 // TODO Probably going away at some point
5869 QSize macsz;
5870 if (d->aquaSizeConstrain(opt, ct, sz, &macsz) != QStyleHelper::SizeDefault) {
5871 if (macsz.width() != -1)
5872 sz.setWidth(macsz.width());
5873 if (macsz.height() != -1)
5874 sz.setHeight(macsz.height());
5875 }
5876 }
5877
5878 // The sizes that Carbon and the guidelines gives us excludes the focus frame.
5879 // We compensate for this by adding some extra space here to make room for the frame when drawing:
5880 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){
5881 if (combo->editable) {
5882 const auto widgetSize = d->aquaSizeConstrain(opt);
5883 QMacStylePrivate::CocoaControl cw;
5884 cw.type = combo->editable ? QMacStylePrivate::ComboBox : QMacStylePrivate::Button_PopupButton;
5885 cw.size = widgetSize;
5886 const CGRect diffRect = QMacStylePrivate::comboboxInnerBounds(CGRectZero, cw);
5887 sz.rwidth() -= qRound(diffRect.size.width);
5888 sz.rheight() -= qRound(diffRect.size.height);
5889 }
5890 }
5891 return sz;
5892}
5893
5894QFont QMacStyle::font(QStyle::ControlElement element, const QStyle::State state) const
5895{
5896 QFont font = QCommonStyle::font(element, state);
5897
5898 if (state & QStyle::State_Small) {
5899 font.setPixelSize(11);
5900 } else if (state & QStyle::State_Mini) {
5901 font.setPixelSize(9);
5902 }
5903
5904 return font;
5905}
5906
5907QMargins QMacStyle::ninePatchMargins(QStyle::ComplexControl cc, const QStyleOptionComplex *opt, const QSize &imageSize) const
5908{
5909 QMargins margins;
5910
5911 switch (cc) {
5912 case CC_ComboBox: {
5913 const QRect arrow = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow);
5914 margins = QMargins(10, 0, arrow.width() + 1, -1);
5915 break; }
5916 default:
5917 margins = QCommonStyle::ninePatchMargins(cc, opt, imageSize);
5918 break;
5919 }
5920
5921 return margins;
5922}
5923
5924void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal,
5925 bool enabled, const QString &text, QPalette::ColorRole textRole) const
5926{
5927 if(flags & Qt::TextShowMnemonic)
5928 flags |= Qt::TextHideMnemonic;
5929 QCommonStyle::drawItemText(p, r, flags, pal, enabled, text, textRole);
5930}
5931
5932QIcon QMacStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *opt) const
5933{
5934 switch (standardIcon) {
5935 default:
5936 return QCommonStyle::standardIcon(standardIcon, opt);
5937 case SP_ToolBarHorizontalExtensionButton:
5938 case SP_ToolBarVerticalExtensionButton: {
5939 QPixmap pixmap(QLatin1String(":/qt-project.org/styles/macstyle/images/toolbar-ext.png"));
5940 if (standardIcon == SP_ToolBarVerticalExtensionButton) {
5941 QPixmap pix2(pixmap.height(), pixmap.width());
5942 pix2.setDevicePixelRatio(pixmap.devicePixelRatio());
5943 pix2.fill(Qt::transparent);
5944 QPainter p(&pix2);
5945 p.translate(pix2.width(), 0);
5946 p.rotate(90);
5947 p.drawPixmap(0, 0, pixmap);
5948 return pix2;
5949 }
5950 return pixmap;
5951 }
5952 }
5953}
5954
5955} // QQC2_NAMESPACE
5956
5957QT_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