Warning, /education/labplot/src/3rdparty/kdmactouchbar/src/kdmactouchbar.mm is written in an unsupported language. File is not indexed.

0001 /****************************************************************************
0002 ** Copyright (C) 2019-2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
0003 ** All rights reserved.
0004 **
0005 ** This file is part of the KD MacTouchBar library.
0006 **
0007 ** This file may be distributed and/or modified under the terms of the
0008 ** GNU Lesser General Public License version 3 as published by the
0009 ** Free Software Foundation and appearing in the file LICENSE.LGPL.txt included.
0010 **
0011 ** You may even contact us at info@kdab.com for different licensing options.
0012 **
0013 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
0014 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
0015 **
0016 ** Contact info@kdab.com if any conditions of this licensing are not
0017 ** clear to you.
0018 **
0019 **********************************************************************/
0020 #include "kdmactouchbar.h"
0021 
0022 #include <QActionEvent>
0023 #include <QApplication>
0024 #include <QDialogButtonBox>
0025 #include <QLayout>
0026 #include <QMenu>
0027 #include <QMessageBox>
0028 #include <QPainter>
0029 #include <QPushButton>
0030 #include <QProxyStyle>
0031 #include <QStack>
0032 #include <QTabBar>
0033 #include <QTimer>
0034 #include <QWidgetAction>
0035 
0036 #import <AppKit/AppKit.h>
0037 
0038 QT_BEGIN_NAMESPACE
0039 
0040 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
0041 NSImage *qt_mac_create_nsimage(const QIcon &icon, int defaultSize = 0);
0042 #else
0043 //  defined in gui/painting/qcoregraphics.mm
0044 @interface NSImage (QtExtras)
0045 + (instancetype)imageFromQIcon:(const QIcon &)icon;
0046 @end
0047 static NSImage *qt_mac_create_nsimage(const QIcon &icon)
0048 {
0049     return [NSImage imageFromQIcon:icon];
0050 }
0051 #endif
0052 
0053 static QString identifierForAction(QObject *action)
0054 {
0055     return QStringLiteral("0x%1 %2")
0056         .arg((quintptr)action, QT_POINTER_SIZE * 2, 16, QLatin1Char('0'))
0057         .arg(action->objectName());
0058 }
0059 
0060 
0061 static QString removeMnemonics(const QString &original)
0062 {
0063     QString returnText(original.size(), 0);
0064     int finalDest = 0;
0065     int currPos = 0;
0066     int l = original.length();
0067     while (l) {
0068         if (original.at(currPos) == QLatin1Char('&')) {
0069             ++currPos;
0070             --l;
0071             if (l == 0)
0072                 break;
0073         } else if (original.at(currPos) == QLatin1Char('(') && l >= 4 &&
0074                    original.at(currPos + 1) == QLatin1Char('&') &&
0075                    original.at(currPos + 2) != QLatin1Char('&') &&
0076                    original.at(currPos + 3) == QLatin1Char(')')) {
0077             /* remove mnemonics its format is "\s*(&X)" */
0078             int n = 0;
0079             while (finalDest > n && returnText.at(finalDest - n - 1).isSpace())
0080                 ++n;
0081             finalDest -= n;
0082             currPos += 4;
0083             l -= 4;
0084             continue;
0085         }
0086         returnText[finalDest] = original.at(currPos);
0087         ++currPos;
0088         ++finalDest;
0089         --l;
0090     }
0091     returnText.truncate(finalDest);
0092     return returnText;
0093 }
0094 
0095 class TabBarAction : public QAction
0096 {
0097 public:
0098     TabBarAction(QTabBar *tabBar, QObject *parent)
0099         : QAction(parent)
0100     {
0101         setData(QVariant::fromValue<QObject *>(tabBar));
0102         connect(tabBar, &QTabBar::currentChanged, this, &TabBarAction::sendDataChanged);
0103         connect(tabBar, &QObject::destroyed, this, &QObject::deleteLater);
0104     }
0105 
0106     void sendDataChanged()
0107     {
0108         QActionEvent e(QEvent::ActionChanged, this);
0109         for (auto w : associatedWidgets())
0110             QApplication::sendEvent(w, &e);
0111     }
0112 };
0113 
0114 class ButtonBoxAction : public QAction
0115 {
0116 public:
0117     ButtonBoxAction(QDialogButtonBox *buttonBox, QObject *parent)
0118         : QAction(parent)
0119         , mButtonBox(buttonBox)
0120     {
0121         setData(QVariant::fromValue<QObject *>(buttonBox));
0122 
0123         checkStandardButtonAndTexts();
0124 
0125         auto timer = new QTimer(buttonBox);
0126         timer->start(200);
0127         connect(timer, &QTimer::timeout, this, &ButtonBoxAction::checkStandardButtonAndTexts);
0128         connect(buttonBox, &QObject::destroyed, this, &QObject::deleteLater);
0129     }
0130 
0131     void checkStandardButtonAndTexts()
0132     {
0133         QStringList buttonTexts;
0134         QPushButton *defaultButton = nullptr;
0135         for (auto b : mButtonBox->buttons()) {
0136             buttonTexts.append(b->text());
0137             if (auto pb = qobject_cast<QPushButton *>(b))
0138                 if (pb->isDefault())
0139                     defaultButton = pb;
0140         }
0141 
0142         if (buttonTexts != mButtonTexts || defaultButton != mDefaultButton) {
0143             sendDataChanged();
0144             mButtonTexts = buttonTexts;
0145             mDefaultButton = defaultButton;
0146         }
0147     }
0148 
0149     void sendDataChanged()
0150     {
0151         QActionEvent e(QEvent::ActionChanged, this);
0152         for (auto w : associatedWidgets())
0153             QApplication::sendEvent(w, &e);
0154     }
0155 
0156     QDialogButtonBox *mButtonBox;
0157     QList<QAbstractButton *> mButtons;
0158     QPushButton *mDefaultButton = nullptr;
0159     QStringList mButtonTexts;
0160 };
0161 
0162 @interface QObjectPointer : NSObject {
0163 }
0164 @property (readonly) QObject *qobject;
0165 @end
0166 
0167 @implementation QObjectPointer
0168 QObject *_qobject;
0169 @synthesize qobject = _qobject;
0170 
0171 - (id)initWithQObject:(QObject *)aQObject
0172 {
0173     self = [super init];
0174     _qobject = aQObject;
0175     return self;
0176 }
0177 
0178 @end
0179 
0180 class WidgetActionContainerWidget : public QWidget
0181 {
0182 public:
0183     WidgetActionContainerWidget(QWidget *widget, NSView *view)
0184         : w(widget)
0185         , v(view)
0186     {
0187         widget->setParent(this);
0188         widget->move(0, 0);
0189         if (!widget->testAttribute(Qt::WA_Resized))
0190             widget->resize(widget->sizeHint()
0191                                .boundedTo(QSize(widget->maximumWidth(), 30))
0192                                .expandedTo(widget->minimumSize()));
0193         setAttribute(Qt::WA_DontShowOnScreen);
0194         setAttribute(Qt::WA_QuitOnClose, false);
0195         ensurePolished();
0196         setVisible(true);
0197         QPixmap pm(1, 1);
0198         render(&pm, QPoint(), QRegion(),
0199                widget->autoFillBackground()
0200                    ? (QWidget::DrawWindowBackground | QWidget::DrawChildren)
0201                    : QWidget::DrawChildren);
0202     }
0203 
0204     QSize sizeHint() const { return w->size(); }
0205 
0206     void resizeEvent(QResizeEvent *event) { w->resize(event->size()); }
0207 
0208     bool event(QEvent *event)
0209     {
0210         if (event->type() == QEvent::UpdateRequest)
0211             [v setNeedsDisplay:YES];
0212         return QWidget::event(event);
0213     }
0214 
0215     QWidget *w;
0216     NSView *v;
0217 };
0218 
0219 @interface WidgetActionView : NSView
0220 @property WidgetActionContainerWidget *widget;
0221 @property QWidget *touchTarget;
0222 @property QWidgetAction *action;
0223 @property (readonly) NSSize intrinsicContentSize;
0224 @end
0225 
0226 @implementation WidgetActionView
0227 @synthesize action;
0228 @synthesize widget;
0229 @synthesize touchTarget;
0230 
0231 - (NSSize)intrinsicContentSize
0232 {
0233     return NSMakeSize(widget->width(), widget->height());
0234 }
0235 
0236 - (id)initWithWidgetAction:(QWidgetAction *)wa
0237 {
0238     self = [super init];
0239     action = wa;
0240     widget = new WidgetActionContainerWidget(action->requestWidget(nullptr), self);
0241     if (!widget->testAttribute(Qt::WA_Resized))
0242         widget->resize(widget->sizeHint()
0243                            .boundedTo(QSize(widget->maximumWidth(), 30))
0244                            .expandedTo(widget->minimumSize()));
0245     [self setNeedsDisplay:YES];
0246     return self;
0247 }
0248 
0249 - (void)dealloc
0250 {
0251     widget->w->setParent(0);
0252     [super dealloc];
0253 }
0254 
0255 - (void)drawRect:(NSRect)frame
0256 {
0257     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
0258     widget->w->resize(frame.size.width, frame.size.height);
0259     QPixmap pm(widget->w->size() * 2);
0260     pm.setDevicePixelRatio(2);
0261     pm.fill(Qt::transparent);
0262     widget->w->render(&pm, QPoint(), QRegion(),
0263                       widget->w->autoFillBackground()
0264                           ? (QWidget::DrawWindowBackground | QWidget::DrawChildren)
0265                           : QWidget::DrawChildren);
0266     CGImageRef cgImage = pm.toImage().toCGImage();
0267     NSImage *image = [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
0268     [image drawInRect:frame];
0269     [pool release];
0270 }
0271 
0272 - (void)touchesBeganWithEvent:(NSEvent *)event
0273 {
0274     NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseBegan inView:self] anyObject];
0275     const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
0276     touchTarget = widget->childAt(point);
0277     if (touchTarget == nullptr)
0278         touchTarget = widget;
0279     QMouseEvent e(QEvent::MouseButtonPress, touchTarget->mapFrom(widget, point), Qt::LeftButton,
0280                   Qt::LeftButton, Qt::NoModifier);
0281     qApp->sendEvent(touchTarget, &e);
0282 }
0283 - (void)touchesMovedWithEvent:(NSEvent *)event
0284 {
0285     NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseMoved inView:self] anyObject];
0286     const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
0287     QMouseEvent e(QEvent::MouseButtonPress, touchTarget->mapFrom(widget, point), Qt::LeftButton,
0288                   Qt::LeftButton, Qt::NoModifier);
0289     qApp->sendEvent(touchTarget, &e);
0290 }
0291 - (void)touchesEndedWithEvent:(NSEvent *)event
0292 {
0293     NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseEnded inView:self] anyObject];
0294     const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
0295     QMouseEvent e(QEvent::MouseButtonRelease, touchTarget->mapFrom(widget, point), Qt::LeftButton,
0296                   Qt::LeftButton, Qt::NoModifier);
0297     qApp->sendEvent(touchTarget, &e);
0298 }
0299 
0300 @end
0301 
0302 @interface DynamicTouchBarProviderDelegate : NSResponder <NSTouchBarDelegate>
0303 @property (strong) NSMutableDictionary *items;
0304 @end
0305 
0306 @implementation DynamicTouchBarProviderDelegate
0307 @synthesize items;
0308 
0309 - (id)initWithItems:(NSMutableDictionary *)i
0310 {
0311     self = [super init];
0312     self.items = i;
0313     return self;
0314 }
0315 
0316 - (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
0317        makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
0318 {
0319     Q_UNUSED(touchBar);
0320 
0321     if ([self.items objectForKey:identifier] != nil)
0322         return [self.items objectForKey:identifier];
0323 
0324     return nil;
0325 }
0326 @end
0327 
0328 @interface DynamicTouchBarProvider
0329     : NSResponder <NSTouchBarProvider, NSApplicationDelegate, NSWindowDelegate>
0330 @property (strong) NSMutableDictionary *items;
0331 @property (strong) NSMutableDictionary *commands;
0332 @property (strong) NSObject *qtDelegate;
0333 @property (strong, readonly) NSTouchBar *touchBar;
0334 @property (strong) DynamicTouchBarProviderDelegate *delegate;
0335 @property KDMacTouchBar *qMacTouchBar;
0336 @property QStack<NSPopoverTouchBarItem *> openedPopOvers;
0337 @end
0338 
0339 @implementation DynamicTouchBarProvider
0340 @synthesize items;
0341 @synthesize commands;
0342 @synthesize touchBar;
0343 @synthesize delegate;
0344 @synthesize qMacTouchBar;
0345 @synthesize openedPopOvers;
0346 
0347 - (id)initWithKDMacTouchBar:(KDMacTouchBar *)bar
0348 {
0349     self = [super init];
0350     items = [[NSMutableDictionary alloc] init];
0351     commands = [[NSMutableDictionary alloc] init];
0352     qMacTouchBar = bar;
0353     delegate = [[DynamicTouchBarProviderDelegate alloc] initWithItems:items];
0354     return self;
0355 }
0356 
0357 - (void)addItem:(QAction *)action
0358 {
0359     // Create custom button item
0360     NSString *identifier = identifierForAction(action).toNSString();
0361     NSTouchBarItem *item = nil;
0362     NSView *view = nil;
0363 
0364     if (auto wa = qobject_cast<QWidgetAction *>(action)) {
0365         NSPopoverTouchBarItem *i =
0366             [[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
0367         item = i;
0368         view = [[WidgetActionView alloc] initWithWidgetAction:wa];
0369         i.collapsedRepresentation = view;
0370     } else if (auto tb = qobject_cast<QTabBar *>(action->data().value<QObject *>())) {
0371         NSCustomTouchBarItem *i =
0372             [[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
0373         item = i;
0374         NSMutableArray *labels = [[NSMutableArray alloc] init];
0375         view =
0376             [[NSSegmentedControl segmentedControlWithLabels:labels
0377                                                trackingMode:NSSegmentSwitchTrackingSelectOne
0378                                                      target:self
0379                                                      action:@selector(tabBarAction:)] autorelease];
0380         i.view = view;
0381     } else if (auto bb = qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>())) {
0382         NSMutableArray *buttonItems = [[NSMutableArray alloc] init];
0383 
0384         for (int i = 0; i < bb->layout()->count(); ++i) {
0385             auto layoutItem = bb->layout()->itemAt(i);
0386             if (auto b = qobject_cast<QPushButton *>(layoutItem->widget())) {
0387                 auto buttonIdentifier = identifierForAction(b).toNSString();
0388                 NSCustomTouchBarItem *buttonItem = nil;
0389                 NSButton *button = nil;
0390                 if ([[items allKeys] containsObject:buttonIdentifier]) {
0391                     buttonItem = [items objectForKey:buttonIdentifier];
0392                     button = buttonItem.view;
0393                 } else {
0394                     buttonItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:buttonIdentifier];
0395                     button = [NSButton buttonWithTitle:removeMnemonics(b->text()).toNSString()
0396                                                 target:self
0397                                                 action:@selector(buttonAction:)];
0398                 }
0399                 if (b->isDefault())
0400                     [button setKeyEquivalent:@"\r"];
0401                 button.title = removeMnemonics(b->text()).toNSString();
0402                 buttonItem.view = button;
0403                 [buttonItems addObject:buttonItem];
0404                 [commands setObject:[[QObjectPointer alloc] initWithQObject:b]
0405                              forKey:[NSValue valueWithPointer:button]];
0406                 [items setObject:buttonItem forKey:buttonIdentifier];
0407             }
0408         }
0409 
0410         item = [[NSGroupTouchBarItem groupItemWithIdentifier:identifier items:buttonItems]
0411             autorelease];
0412     } else if (action->isSeparator()) {
0413         NSPopoverTouchBarItem *i =
0414             [[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
0415         item = i;
0416         view = [NSTextField labelWithString:action->text().toNSString()];
0417         i.collapsedRepresentation = view;
0418     } else {
0419         NSPopoverTouchBarItem *i =
0420             [[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
0421         item = i;
0422         view = [[NSButton buttonWithTitle:removeMnemonics(action->text()).toNSString()
0423                                    target:self
0424                                    action:@selector(itemAction:)] autorelease];
0425         i.collapsedRepresentation = view;
0426     }
0427 
0428     if (view)
0429         [view retain];
0430     [item retain];
0431 
0432     [items setObject:item forKey:identifier];
0433     [commands setObject:[[QObjectPointer alloc] initWithQObject:action]
0434                  forKey:[NSValue valueWithPointer:view]];
0435 
0436     if (!qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>()))
0437         [self changeItem:action];
0438     else
0439         [self makeTouchBar];
0440 }
0441 
0442 - (void)changeItem:(QAction *)action
0443 {
0444     NSString *identifier = identifierForAction(action).toNSString();
0445 
0446     if (auto wa = qobject_cast<QWidgetAction *>(action)) {
0447 
0448     } else if (auto tb = qobject_cast<QTabBar *>(action->data().value<QObject *>())) {
0449         NSCustomTouchBarItem *item = [items objectForKey:identifier];
0450         NSSegmentedControl *control = item.view;
0451         control.segmentCount = tb->count();
0452         for (int i = 0; i < tb->count(); ++i) {
0453             [control setLabel:tb->tabText(i).toNSString() forSegment:i];
0454         }
0455         control.selectedSegment = tb->currentIndex();
0456     } else if (auto bb = qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>())) {
0457         // unfortunately we cannot modify a NSGroupTouchBarItem, so we
0458         // have to recreate it from scratch :-(
0459         int index = qMacTouchBar->actions().indexOf(action);
0460         if (index == qMacTouchBar->actions().count() - 1) {
0461             qMacTouchBar->removeAction(action);
0462             qMacTouchBar->addAction(action);
0463         } else {
0464             QAction *before = qMacTouchBar->actions().at(index + 1);
0465             qMacTouchBar->removeAction(action);
0466             qMacTouchBar->insertAction(before, action);
0467         }
0468     } else if (action->isSeparator()) {
0469         NSPopoverTouchBarItem *item = [items objectForKey:identifier];
0470         NSTextField *field = item.collapsedRepresentation;
0471         field.stringValue = action->text().toNSString();
0472     } else {
0473         NSPopoverTouchBarItem *item = [items objectForKey:identifier];
0474         NSButton *button = item.collapsedRepresentation;
0475         button.imagePosition = NSImageLeft;
0476         button.enabled = action->isEnabled();
0477         button.buttonType =
0478             action->isCheckable() ? NSButtonTypePushOnPushOff : NSButtonTypeAccelerator;
0479         button.bordered = action->isSeparator() && !action->text().isEmpty() ? NO : YES;
0480         button.highlighted = !button.bordered;
0481         button.state = action->isChecked();
0482         button.hidden = !action->isVisible();
0483 
0484                 //TODO: since we're using dark breeze icons, we need to invert the pixels
0485                 //to get white icons for the dark background of the touch bar.
0486                 //this logic needs to be adjusted or avoide once we start supporting dark mode on mac
0487                 QImage image = action->icon().pixmap(QSize(24,24)).toImage();
0488                 image.invertPixels(QImage::InvertRgb);
0489                 button.image = qt_mac_create_nsimage(QIcon(QPixmap::fromImage(image)));
0490 
0491                 //button.image = qt_mac_create_nsimage(action->icon());
0492         button.title = removeMnemonics(action->text()).toNSString();
0493         switch (qMacTouchBar->touchButtonStyle())
0494         {
0495         case KDMacTouchBar::IconOnly:
0496             button.imagePosition = NSImageOnly;
0497             break;
0498         case KDMacTouchBar::TextOnly:
0499             button.imagePosition = NSNoImage;
0500             break;
0501         case KDMacTouchBar::TextBesideIcon:
0502             button.imagePosition = NSImageLeft;
0503             break;
0504         }
0505         item.showsCloseButton = action->menu() != nullptr;
0506         if (action->menu()) {
0507             item.popoverTouchBar = [[NSTouchBar alloc] init];
0508             item.popoverTouchBar.delegate = delegate;
0509             // Add ordered items array
0510             NSMutableArray *array = [[NSMutableArray alloc] init];
0511             for (auto action : action->menu()->actions()) {
0512                 if (action->isVisible()) {
0513                     [self addItem:action];
0514                     if (action->isSeparator() && action->text().isEmpty())
0515                         [array addObject:NSTouchBarItemIdentifierFixedSpaceLarge];
0516                     else
0517                         [array addObject:identifierForAction(action).toNSString()];
0518                 }
0519             }
0520             item.popoverTouchBar.defaultItemIdentifiers = array;
0521         }
0522     }
0523 
0524     [self makeTouchBar];
0525 }
0526 
0527 - (void)removeItem:(QAction *)action
0528 {
0529     NSString *identifier = identifierForAction(action).toNSString();
0530     NSPopoverTouchBarItem *item = [items objectForKey:identifier];
0531     if (item == nil)
0532         return;
0533     QObjectPointer *command = [commands objectForKey:[NSValue valueWithPointer:item.view]];
0534     [commands removeObjectForKey:[NSValue valueWithPointer:item.view]];
0535     [items removeObjectForKey:identifier];
0536     [command release];
0537 
0538     [self makeTouchBar];
0539 }
0540 
0541 - (void)buttonAction:(id)sender
0542 {
0543     QObjectPointer *qobjectPointer =
0544         (QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
0545     // Missing entry in commands dict. Should not really happen, but does
0546     if (!qobjectPointer)
0547         return;
0548 
0549     QPushButton *button = qobject_cast<QPushButton *>(qobjectPointer.qobject);
0550     if (!button)
0551         return;
0552 
0553     button->click();
0554 }
0555 
0556 - (void)tabBarAction:(id)sender
0557 {
0558     QObjectPointer *qobjectPointer =
0559         (QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
0560     // Missing entry in commands dict. Should not really happen, but does
0561     if (!qobjectPointer)
0562         return;
0563 
0564     // Check for deleted QObject
0565     if (!qobjectPointer.qobject)
0566         return;
0567 
0568     QAction *action = static_cast<QAction *>(qobjectPointer.qobject);
0569     if (!action)
0570         return;
0571     QTabBar *tabBar = qobject_cast<QTabBar *>(action->data().value<QObject *>());
0572     if (!tabBar)
0573         return;
0574 
0575     NSString *identifier = identifierForAction(action).toNSString();
0576     NSCustomTouchBarItem *item = [items objectForKey:identifier];
0577     NSSegmentedControl *control = item.view;
0578 
0579     tabBar->setCurrentIndex(control.selectedSegment);
0580 }
0581 
0582 - (void)itemAction:(id)sender
0583 {
0584     QObjectPointer *qobjectPointer =
0585         (QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
0586     // Missing entry in commands dict. Should not really happen, but does
0587     if (!qobjectPointer)
0588         return;
0589 
0590     // Check for deleted QObject
0591     if (!qobjectPointer.qobject)
0592         return;
0593 
0594     QAction *action = static_cast<QAction *>(qobjectPointer.qobject);
0595     if (!action || action->isSeparator())
0596         return;
0597     if (!action->isEnabled())
0598         return;
0599     action->activate(QAction::Trigger);
0600 
0601     if (action->menu()) {
0602         NSString *identifier = identifierForAction(action).toNSString();
0603         NSPopoverTouchBarItem *item = [items objectForKey:identifier];
0604         [item showPopover:item];
0605         openedPopOvers.push(item);
0606     } else {
0607         while (!openedPopOvers.isEmpty()) {
0608             auto poppedItem = openedPopOvers.pop();
0609             [poppedItem dismissPopover:poppedItem];
0610         }
0611     }
0612 }
0613 
0614 - (void)clearItems
0615 {
0616     [items removeAllObjects];
0617     [commands removeAllObjects];
0618 }
0619 
0620 - (NSTouchBar *)makeTouchBar
0621 {
0622     // Create the touch bar with this instance as its delegate
0623     if (touchBar == nil) {
0624         touchBar = [[NSTouchBar alloc] init];
0625         touchBar.delegate = delegate;
0626     }
0627 
0628     // Add ordered items array
0629     NSMutableArray *array = [[NSMutableArray alloc] init];
0630     for (auto action : qMacTouchBar->actions()) {
0631         if (action->isVisible()) {
0632             if (action->isSeparator() && action->text().isEmpty())
0633                 [array addObject:NSTouchBarItemIdentifierFixedSpaceLarge];
0634             else
0635                 [array addObject:identifierForAction(action).toNSString()];
0636         }
0637     }
0638     touchBar.defaultItemIdentifiers = array;
0639 
0640     return touchBar;
0641 }
0642 
0643 - (void)installAsDelegateForWindow:(NSWindow *)window
0644 {
0645     _qtDelegate = window.delegate; // Save current delegate for forwarding
0646     window.delegate = self;
0647 }
0648 
0649 - (void)installAsDelegateForApplication:(NSApplication *)application
0650 {
0651     _qtDelegate = application.delegate; // Save current delegate for forwarding
0652     application.delegate = self;
0653 }
0654 
0655 - (BOOL)respondsToSelector:(SEL)aSelector
0656 {
0657     // We want to forward to the qt delegate. Respond to selectors it
0658     // responds to in addition to selectors this instance resonds to.
0659     return [_qtDelegate respondsToSelector:aSelector] || [super respondsToSelector:aSelector];
0660 }
0661 
0662 - (void)forwardInvocation:(NSInvocation *)anInvocation
0663 {
0664     // Forward to the existing delegate. This function is only called for selectors
0665     // this instance does not responds to, which means that the qt delegate
0666     // must respond to it (due to the respondsToSelector implementation above).
0667     [anInvocation invokeWithTarget:_qtDelegate];
0668 }
0669 
0670 @end
0671 
0672 class KDMacTouchBar::Private
0673 {
0674 public:
0675     DynamicTouchBarProvider *touchBarProvider = nil;
0676     QAction *principialAction = nullptr;
0677     QAction *escapeAction = nullptr;
0678     KDMacTouchBar::TouchButtonStyle touchButtonStyle = TextBesideIcon;
0679 
0680     static bool automaticallyCreateMessageBoxTouchBar;
0681 };
0682 
0683 bool KDMacTouchBar::Private::automaticallyCreateMessageBoxTouchBar = false;
0684 
0685 class AutomaticMessageBoxTouchBarStyle : public QProxyStyle
0686 {
0687 public:
0688     using QProxyStyle::QProxyStyle;
0689 
0690     void polish(QWidget *w) override
0691     {
0692         if (auto mb = qobject_cast<QMessageBox *>(w)) {
0693             if (KDMacTouchBar::isAutomacicallyCreatingMessageBoxTouchBar())
0694                 new KDMacTouchBar(mb);
0695         }
0696         QProxyStyle::polish(w);
0697     }
0698 };
0699 
0700 /*!
0701   \class KDMacTouchBar
0702   \brief The KDMacTouchBar class wraps the native NSTouchBar class.
0703 
0704   KDMacTouchBar provides a Qt-based API for NSTouchBar. The touchbar displays
0705   a number of QActions. Each QAction can have a text and an icon. Alternatively,
0706   the QActions might be separators (with or without text) or QWidgetActions.
0707 
0708   Add actions by calling addAction(). Alternatively, you can use one of the
0709   convenience methods like addDialogButtonBox() or addTabBar() which provide
0710   common use cases.
0711 
0712   If an action with a associated menu is added, its menu items are added as
0713   sub-touchbar. Showing sub-menus of this menu is not supported, due to macOS
0714   system restrictions.
0715 
0716   Usage: (QtWidgets)
0717   \code
0718     QMainWindow *mw = ...;
0719     KDMacTouchBar *touchBar = new KDMacTouchBar(mw);
0720     touchBar->addAction(actionNewFile);
0721     touchBar->addSeparator();
0722     touchBar->addAction(actionSaveFile);
0723   \endcode
0724 */
0725 
0726 /*!
0727 \enum KDMacTouchBar::TouchButtonStyle
0728 \value IconOnly Only display the icon.
0729 \value TextOnly Only display the text.
0730 \value TextBesideIcon The text appears beside the icon.
0731 */
0732 
0733 /*!
0734     Constructs a KDMacTouchBar for the window of \a parent. If \a parent is
0735     nullptr, the KDMacTouchBar is shown as soon as this QApplication has focus,
0736     if no other window with an own KDMacTouchBar has focus.
0737 */
0738 KDMacTouchBar::KDMacTouchBar(QWidget *parent)
0739     : QWidget(parent)
0740     , d(new Private)
0741 {
0742     d->touchBarProvider = [[DynamicTouchBarProvider alloc] initWithKDMacTouchBar:this];
0743     if (parent) {
0744         NSView *view = reinterpret_cast<NSView *>(parent->window()->winId());
0745         [d->touchBarProvider installAsDelegateForWindow:[view window]];
0746     } else {
0747         [d->touchBarProvider installAsDelegateForApplication:[NSApplication sharedApplication]];
0748     }
0749 }
0750 
0751 /*!
0752     Constructs a KDMacTouchBar containing the QDialogButtonBox from inside of
0753     \a messageBox.
0754 */
0755 KDMacTouchBar::KDMacTouchBar(QMessageBox *messageBox)
0756     : QWidget( messageBox)
0757     , d(new Private)
0758 {
0759     d->touchBarProvider = [[DynamicTouchBarProvider alloc] initWithKDMacTouchBar:this];
0760     NSView *view = reinterpret_cast<NSView *>(messageBox->window()->winId());
0761     [d->touchBarProvider installAsDelegateForWindow:[view window]];
0762     setPrincipialAction(addMessageBox(messageBox));
0763 }
0764 
0765 /*!
0766     Destroys the touch bar.
0767 */
0768 KDMacTouchBar::~KDMacTouchBar()
0769 {
0770     [d->touchBarProvider release];
0771     delete d;
0772 }
0773 
0774 /*!
0775    This static convenience method controls, whether KDMacTouchBar will
0776    automatically create a touchbar for QMessageBox instances. The
0777    created touchbar contains the buttons of the message box.
0778    This enables to use the static QMessageBox method and still having
0779    a touchbar for them.
0780 
0781    \note When you enable this setting for the first time, KDMacTouchBar will
0782          install a QProxyStyle into the QApplication object to be able to create
0783          the KDMacTouchBar in its polish method. The installed QProxyStyle will
0784          use the existing application style as base style.
0785 */
0786 void KDMacTouchBar::setAutomaticallyCreateMessageBoxTouchBar(bool automatic)
0787 {
0788     static AutomaticMessageBoxTouchBarStyle *touchStyle = nullptr;
0789     if (automatic && !touchStyle) {
0790         qApp->setStyle(touchStyle = new AutomaticMessageBoxTouchBarStyle(qApp->style()));
0791     }
0792     Private::automaticallyCreateMessageBoxTouchBar = automatic;
0793 }
0794 
0795 /*!
0796    Returns whether KDMacTouchBar automatically creates a touchbar for
0797    QMessageBox instances.
0798 */
0799 bool KDMacTouchBar::isAutomacicallyCreatingMessageBoxTouchBar()
0800 {
0801     return Private::automaticallyCreateMessageBoxTouchBar;
0802 }
0803 
0804 /*!
0805     Adds a separator item to the end of the touch bar.
0806 */
0807 QAction *KDMacTouchBar::addSeparator()
0808 {
0809     auto action = new QAction(this);
0810     action->setSeparator(true);
0811     addAction(action);
0812     return action;
0813 }
0814 
0815 /*! \reimp */
0816 bool KDMacTouchBar::event(QEvent *event)
0817 {
0818     switch (event->type()) {
0819     case QEvent::ActionAdded:
0820         [d->touchBarProvider addItem:static_cast<QActionEvent *>(event)->action()];
0821         break;
0822     case QEvent::ActionChanged:
0823         [d->touchBarProvider changeItem:static_cast<QActionEvent *>(event)->action()];
0824         break;
0825     case QEvent::ActionRemoved:
0826         [d->touchBarProvider removeItem:static_cast<QActionEvent *>(event)->action()];
0827         break;
0828     default:
0829         break;
0830     }
0831 
0832     return QWidget::event(event);
0833 }
0834 
0835 /*!
0836     Adds a button group controlling a \a tabBar item to the end of the touch bar.
0837 */
0838 QAction *KDMacTouchBar::addTabBar(QTabBar *tabBar)
0839 {
0840     auto a = new TabBarAction(tabBar, this);
0841     addAction(a);
0842     return a;
0843 }
0844 
0845 /*!
0846    Removes \a tabBar from the touch bar.
0847 */
0848 void KDMacTouchBar::removeTabBar(QTabBar *tabBar)
0849 {
0850     for (auto a : actions()) {
0851         if (a->data().value<QObject *>() == tabBar) {
0852             removeAction(a);
0853             return;
0854         }
0855     }
0856 }
0857 
0858 /*!
0859    Adds the QDialogButtonBox of \a messageBox to the end of the touch bar.
0860 */
0861 QAction *KDMacTouchBar::addMessageBox(QMessageBox *messageBox)
0862 {
0863     return addDialogButtonBox(messageBox->findChild<QDialogButtonBox *>(QStringLiteral("qt_msgbox_buttonbox")));
0864 }
0865 
0866 /*!
0867    Removes the QDialogButtonBox of \a messageBox from the touch bar.
0868 */
0869 void KDMacTouchBar::removeMessageBox(QMessageBox *messageBox)
0870 {
0871     return removeDialogButtonBox(messageBox->findChild<QDialogButtonBox *>(QStringLiteral("qt_msgbox_buttonbox")));
0872 }
0873 
0874 /*!
0875    Adds a button group controlling \a buttonBox to the end of the touch bar.
0876 */
0877 QAction *KDMacTouchBar::addDialogButtonBox(QDialogButtonBox *buttonBox)
0878 {
0879     auto a = new ButtonBoxAction(buttonBox, this);
0880     addAction(a);
0881     return a;
0882 }
0883 
0884 /*!
0885    Removes the \a buttonBox from the touch bar.
0886 */
0887 void KDMacTouchBar::removeDialogButtonBox(QDialogButtonBox *buttonBox)
0888 {
0889     for (auto a : actions()) {
0890         if (a->data().value<QObject *>() == buttonBox) {
0891             removeAction(a);
0892             return;
0893         }
0894     }
0895 }
0896 
0897 /*!
0898    \property KDMacTouchBar::principialAction
0899    \brief the principial action of the touch bar
0900 
0901    The principial action of the touch bar is the QAction you want the system
0902    to center in the touch bar.
0903 
0904    You need to add the action to the touch bar before you can set it as principial
0905    action.
0906 */
0907 void KDMacTouchBar::setPrincipialAction(QAction *action)
0908 {
0909     d->principialAction = action;
0910     d->touchBarProvider.touchBar.principalItemIdentifier = action ? identifierForAction(action).toNSString() : nil;
0911 }
0912 
0913 QAction *KDMacTouchBar::principialAction() const
0914 {
0915     return d->principialAction;
0916 }
0917 
0918 /*!
0919    \property KDMacTouchBar::escapeAction
0920    \brief the action used as system escape key action
0921 
0922    By setting a QAction as escapeAction, it is possible to replace the system
0923    escape key with a random action.
0924 
0925    You don't need to add the action to the touch bar before you can set it as
0926    escape action.
0927 */
0928 void KDMacTouchBar::setEscapeAction(QAction *action)
0929 {
0930     if (d->escapeAction == action)
0931         return;
0932     if (d->escapeAction)
0933         [d->touchBarProvider removeItem:d->escapeAction];
0934     d->escapeAction = action;
0935     if (d->escapeAction) {
0936         [d->touchBarProvider addItem:d->escapeAction];
0937         d->touchBarProvider.touchBar.escapeKeyReplacementItemIdentifier =
0938             identifierForAction(action).toNSString();
0939     } else
0940         d->touchBarProvider.touchBar.escapeKeyReplacementItemIdentifier = nil;
0941 }
0942 
0943 QAction *KDMacTouchBar::escapeAction() const
0944 {
0945     return d->escapeAction;
0946 }
0947 
0948 /*!
0949    \property KDMacTouchBar::touchButtonStyle
0950    This property holds the style of touch bar buttons.
0951 
0952    This property defines the style of all touch buttons that are added as QActions.
0953    Added tab widgets, dialog button boxes, message boxes or QWidgetActions won't
0954    follow this style.
0955 
0956    The default is KDMacTouchBar::TextBesideIcon
0957    */
0958 void KDMacTouchBar::setTouchButtonStyle(TouchButtonStyle touchButtonStyle)
0959 {
0960     if (d->touchButtonStyle == touchButtonStyle)
0961         return;
0962 
0963     d->touchButtonStyle = touchButtonStyle;
0964 
0965     for (auto* action : actions())
0966         [d->touchBarProvider changeItem:action];
0967     if (d->escapeAction)
0968         [d->touchBarProvider changeItem:d->escapeAction];
0969 }
0970 
0971 KDMacTouchBar::TouchButtonStyle KDMacTouchBar::touchButtonStyle() const
0972 {
0973     return d->touchButtonStyle;
0974 }
0975 
0976 /*!
0977    Removes all actions from the touch bar. Removes even the escapeAction.
0978    The principialAction is cleared.
0979 */
0980 void KDMacTouchBar::clear()
0981 {
0982     setEscapeAction(nullptr);
0983     setPrincipialAction(nullptr);
0984     for (auto* action : actions())
0985         removeAction(action);
0986 }
0987 
0988 QT_END_NAMESPACE