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