MacOS: add touchbar support

mrdeveloper 4 years ago
parent 3d5dae1f42
commit 0f366831a0

@ -12,6 +12,10 @@ add_subdirectory(model)
add_subdirectory(utils)
add_subdirectory(openpgp)
if(APPLE)
add_subdirectory(kdmactouchbar)
endif()
qt5_add_resources(RESOURCES assets.qrc)
# Compile source files (.h/.cpp)
@ -201,6 +205,12 @@ target_link_libraries(feather
${QRENCODE_LIBRARY}
)
if(APPLE)
target_link_libraries(feather
KDMacTouchBar
)
endif()
if(NOT APPLE)
target_link_libraries(feather
Qt5::QSvgIconPlugin

@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 2.8.7)
if("${CMAKE_INSTALL_PREFIX}" STREQUAL "")
set(USE_DEFAULT_INSTALL_LOCATION True)
else()
set(USE_DEFAULT_INSTALL_LOCATION False)
endif()
project(KDMacTouchBar CXX)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "" FORCE)
endif()
set(${PROJECT_NAME}_VERSION_MAJOR 1)
set(${PROJECT_NAME}_VERSION_MINOR 0)
set(${PROJECT_NAME}_VERSION_PATCH 0)
set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH})
if(CMAKE_VERSION VERSION_LESS "3.1")
if(CMAKE_COMPILER_IS_GNUCXX)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif ()
else()
set(CMAKE_CXX_STANDARD 11)
endif()
if(USE_DEFAULT_INSTALL_LOCATION)
set(CMAKE_INSTALL_PREFIX "/usr/local/KDAB/${PROJECT_NAME}-${${PROJECT_NAME}_VERSION}" CACHE STRING "" FORCE)
endif()
message(STATUS "Building ${PROJECT_NAME} ${${PROJECT_NAME}_VERSION} in ${CMAKE_BUILD_TYPE} mode. Installing to ${CMAKE_INSTALL_PREFIX}")
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
set(CMAKE_AUTOMOC TRUE)
set(QT_LIBRARIES Qt5::Widgets)
set(QT_USE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Qt5Portability.cmake")
# not generated
#install(FILES
# "${CMAKE_CURRENT_SOURCE_DIR}/cmake/KDMacTouchBarConfig.cmake"
# DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake")
add_subdirectory(src)

@ -0,0 +1,25 @@
#include(${QT_USE_FILE})
set(HEADERS kdmactouchbar.h
kdmactouchbar_global.h)
add_definitions(-DKDMACTOUCHBAR_BUILD_KDMACTOUCHBAR_LIB -DQT_NO_CAST_TO_ASCII -DQT_ASCII_CAST_WARNING)
add_library(KDMacTouchBar SHARED
kdmactouchbar.mm
${HEADERS})
install(TARGETS KDMacTouchBar
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib")
target_link_libraries(KDMacTouchBar ${QT_LIBRARIES} "-framework Cocoa")
set_target_properties(KDMacTouchBar PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION 1
MACOSX_FRAMEWORK_IDENTIFIER com.kdab.KDMacTouchBar
# fails building package on binary-factory
# PUBLIC_HEADER "${HEADERS}"
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib"
MACOSX_RPATH TRUE
)

@ -0,0 +1,87 @@
/****************************************************************************
** Copyright (C) 2019-2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
** All rights reserved.
**
** This file is part of the KD MacTouchBar library.
**
** This file may be distributed and/or modified under the terms of the
** GNU Lesser General Public License version 3 as published by the
** Free Software Foundation and appearing in the file LICENSE.LGPL.txt included.
**
** You may even contact us at info@kdab.com for different licensing options.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@kdab.com if any conditions of this licensing are not
** clear to you.
**
**********************************************************************/
#ifndef KDMACTOUCHBAR_H
#define KDMACTOUCHBAR_H
#include "kdmactouchbar_global.h"
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class QDialogButtonBox;
class QMessageBox;
class QTabBar;
class KDMACTOUCHBAR_EXPORT KDMacTouchBar : public QWidget
{
Q_OBJECT
Q_PROPERTY(QAction *principialAction READ principialAction WRITE setPrincipialAction)
Q_PROPERTY(QAction *escapeAction READ escapeAction WRITE setEscapeAction)
Q_PROPERTY(TouchButtonStyle touchButtonStyle READ touchButtonStyle WRITE setTouchButtonStyle)
public:
explicit KDMacTouchBar(QWidget *parent = nullptr);
explicit KDMacTouchBar(QMessageBox *messageBox);
~KDMacTouchBar();
enum TouchButtonStyle
{
IconOnly,
TextOnly,
TextBesideIcon
};
static void setAutomaticallyCreateMessageBoxTouchBar(bool automatic);
static bool isAutomacicallyCreatingMessageBoxTouchBar();
QAction *addSeparator();
QAction *addTabBar(QTabBar *tabBar);
void removeTabBar(QTabBar *tabBar);
QAction *addMessageBox(QMessageBox *messageBox);
void removeMessageBox(QMessageBox *messageBox);
QAction *addDialogButtonBox(QDialogButtonBox *buttonBox);
void removeDialogButtonBox(QDialogButtonBox *buttonBox);
void setPrincipialAction(QAction *action);
QAction *principialAction() const;
void setEscapeAction(QAction *action);
QAction *escapeAction() const;
void setTouchButtonStyle(TouchButtonStyle touchButtonStyle);
TouchButtonStyle touchButtonStyle() const;
void clear();
protected:
bool event(QEvent *event);
private:
class Private;
Private *const d;
};
QT_END_NAMESPACE
#endif

@ -0,0 +1,980 @@
/****************************************************************************
** Copyright (C) 2019-2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
** All rights reserved.
**
** This file is part of the KD MacTouchBar library.
**
** This file may be distributed and/or modified under the terms of the
** GNU Lesser General Public License version 3 as published by the
** Free Software Foundation and appearing in the file LICENSE.LGPL.txt included.
**
** You may even contact us at info@kdab.com for different licensing options.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@kdab.com if any conditions of this licensing are not
** clear to you.
**
**********************************************************************/
#include "kdmactouchbar.h"
#include <QActionEvent>
#include <QApplication>
#include <QDialogButtonBox>
#include <QLayout>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QPushButton>
#include <QProxyStyle>
#include <QStack>
#include <QTabBar>
#include <QTimer>
#include <QWidgetAction>
#import <AppKit/AppKit.h>
QT_BEGIN_NAMESPACE
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
NSImage *qt_mac_create_nsimage(const QIcon &icon, int defaultSize = 0);
#else
// defined in gui/painting/qcoregraphics.mm
@interface NSImage (QtExtras)
+ (instancetype)imageFromQIcon:(const QIcon &)icon;
@end
static NSImage *qt_mac_create_nsimage(const QIcon &icon)
{
return [NSImage imageFromQIcon:icon];
}
#endif
static QString identifierForAction(QObject *action)
{
return QStringLiteral("0x%1 %2")
.arg((quintptr)action, QT_POINTER_SIZE * 2, 16, QLatin1Char('0'))
.arg(action->objectName());
}
static QString removeMnemonics(const QString &original)
{
QString returnText(original.size(), 0);
int finalDest = 0;
int currPos = 0;
int l = original.length();
while (l) {
if (original.at(currPos) == QLatin1Char('&')) {
++currPos;
--l;
if (l == 0)
break;
} else if (original.at(currPos) == QLatin1Char('(') && l >= 4 &&
original.at(currPos + 1) == QLatin1Char('&') &&
original.at(currPos + 2) != QLatin1Char('&') &&
original.at(currPos + 3) == QLatin1Char(')')) {
/* remove mnemonics its format is "\s*(&X)" */
int n = 0;
while (finalDest > n && returnText.at(finalDest - n - 1).isSpace())
++n;
finalDest -= n;
currPos += 4;
l -= 4;
continue;
}
returnText[finalDest] = original.at(currPos);
++currPos;
++finalDest;
--l;
}
returnText.truncate(finalDest);
return returnText;
}
class TabBarAction : public QAction
{
public:
TabBarAction(QTabBar *tabBar, QObject *parent)
: QAction(parent)
{
setData(QVariant::fromValue<QObject *>(tabBar));
connect(tabBar, &QTabBar::currentChanged, this, &TabBarAction::sendDataChanged);
connect(tabBar, &QObject::destroyed, this, &QObject::deleteLater);
}
void sendDataChanged()
{
QActionEvent e(QEvent::ActionChanged, this);
for (auto w : associatedWidgets())
QApplication::sendEvent(w, &e);
}
};
class ButtonBoxAction : public QAction
{
public:
ButtonBoxAction(QDialogButtonBox *buttonBox, QObject *parent)
: QAction(parent)
, mButtonBox(buttonBox)
{
setData(QVariant::fromValue<QObject *>(buttonBox));
checkStandardButtonAndTexts();
auto timer = new QTimer(buttonBox);
timer->start(200);
connect(timer, &QTimer::timeout, this, &ButtonBoxAction::checkStandardButtonAndTexts);
connect(buttonBox, &QObject::destroyed, this, &QObject::deleteLater);
}
void checkStandardButtonAndTexts()
{
QStringList buttonTexts;
QPushButton *defaultButton = nullptr;
for (auto b : mButtonBox->buttons()) {
buttonTexts.append(b->text());
if (auto pb = qobject_cast<QPushButton *>(b))
if (pb->isDefault())
defaultButton = pb;
}
if (buttonTexts != mButtonTexts || defaultButton != mDefaultButton) {
sendDataChanged();
mButtonTexts = buttonTexts;
mDefaultButton = defaultButton;
}
}
void sendDataChanged()
{
QActionEvent e(QEvent::ActionChanged, this);
for (auto w : associatedWidgets())
QApplication::sendEvent(w, &e);
}
QDialogButtonBox *mButtonBox;
QList<QAbstractButton *> mButtons;
QPushButton *mDefaultButton = nullptr;
QStringList mButtonTexts;
};
@interface QObjectPointer : NSObject {
}
@property (readonly) QObject *qobject;
@end
@implementation QObjectPointer
QObject *_qobject;
@synthesize qobject = _qobject;
- (id)initWithQObject:(QObject *)aQObject
{
self = [super init];
_qobject = aQObject;
return self;
}
@end
class WidgetActionContainerWidget : public QWidget
{
public:
WidgetActionContainerWidget(QWidget *widget, NSView *view)
: w(widget)
, v(view)
{
widget->setParent(this);
widget->move(0, 0);
if (!widget->testAttribute(Qt::WA_Resized))
widget->resize(widget->sizeHint()
.boundedTo(QSize(widget->maximumWidth(), 30))
.expandedTo(widget->minimumSize()));
setAttribute(Qt::WA_DontShowOnScreen);
setAttribute(Qt::WA_QuitOnClose, false);
ensurePolished();
setVisible(true);
QPixmap pm(1, 1);
render(&pm, QPoint(), QRegion(),
widget->autoFillBackground()
? (QWidget::DrawWindowBackground | QWidget::DrawChildren)
: QWidget::DrawChildren);
}
QSize sizeHint() const { return w->size(); }
void resizeEvent(QResizeEvent *event) { w->resize(event->size()); }
bool event(QEvent *event)
{
if (event->type() == QEvent::UpdateRequest)
[v setNeedsDisplay:YES];
return QWidget::event(event);
}
QWidget *w;
NSView *v;
};
@interface WidgetActionView : NSView
@property WidgetActionContainerWidget *widget;
@property QWidget *touchTarget;
@property QWidgetAction *action;
@property (readonly) NSSize intrinsicContentSize;
@end
@implementation WidgetActionView
@synthesize action;
@synthesize widget;
@synthesize touchTarget;
- (NSSize)intrinsicContentSize
{
return NSMakeSize(widget->width(), widget->height());
}
- (id)initWithWidgetAction:(QWidgetAction *)wa
{
self = [super init];
action = wa;
widget = new WidgetActionContainerWidget(action->requestWidget(nullptr), self);
if (!widget->testAttribute(Qt::WA_Resized))
widget->resize(widget->sizeHint()
.boundedTo(QSize(widget->maximumWidth(), 30))
.expandedTo(widget->minimumSize()));
[self setNeedsDisplay:YES];
return self;
}
- (void)dealloc
{
widget->w->setParent(0);
[super dealloc];
}
- (void)drawRect:(NSRect)frame
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
widget->w->resize(frame.size.width, frame.size.height);
QPixmap pm(widget->w->size() * 2);
pm.setDevicePixelRatio(2);
pm.fill(Qt::transparent);
widget->w->render(&pm, QPoint(), QRegion(),
widget->w->autoFillBackground()
? (QWidget::DrawWindowBackground | QWidget::DrawChildren)
: QWidget::DrawChildren);
CGImageRef cgImage = pm.toImage().toCGImage();
NSImage *image = [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
[image drawInRect:frame];
[pool release];
}
- (void)touchesBeganWithEvent:(NSEvent *)event
{
NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseBegan inView:self] anyObject];
const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
touchTarget = widget->childAt(point);
if (touchTarget == nullptr)
touchTarget = widget;
QMouseEvent e(QEvent::MouseButtonPress, touchTarget->mapFrom(widget, point), Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(touchTarget, &e);
}
- (void)touchesMovedWithEvent:(NSEvent *)event
{
NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseMoved inView:self] anyObject];
const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
QMouseEvent e(QEvent::MouseButtonPress, touchTarget->mapFrom(widget, point), Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(touchTarget, &e);
}
- (void)touchesEndedWithEvent:(NSEvent *)event
{
NSTouch *touch = [[event touchesMatchingPhase:NSTouchPhaseEnded inView:self] anyObject];
const QPoint point = QPointF::fromCGPoint([touch locationInView:self]).toPoint();
QMouseEvent e(QEvent::MouseButtonRelease, touchTarget->mapFrom(widget, point), Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
qApp->sendEvent(touchTarget, &e);
}
@end
@interface DynamicTouchBarProviderDelegate : NSResponder <NSTouchBarDelegate>
@property (strong) NSMutableDictionary *items;
@end
@implementation DynamicTouchBarProviderDelegate
@synthesize items;
- (id)initWithItems:(NSMutableDictionary *)i
{
self = [super init];
self.items = i;
return self;
}
- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
Q_UNUSED(touchBar);
if ([self.items objectForKey:identifier] != nil)
return [self.items objectForKey:identifier];
return nil;
}
@end
@interface DynamicTouchBarProvider
: NSResponder <NSTouchBarProvider, NSApplicationDelegate, NSWindowDelegate>
@property (strong) NSMutableDictionary *items;
@property (strong) NSMutableDictionary *commands;
@property (strong) NSObject *qtDelegate;
@property (strong, readonly) NSTouchBar *touchBar;
@property (strong) DynamicTouchBarProviderDelegate *delegate;
@property KDMacTouchBar *qMacTouchBar;
@property QStack<NSPopoverTouchBarItem *> openedPopOvers;
@end
@implementation DynamicTouchBarProvider
@synthesize items;
@synthesize commands;
@synthesize touchBar;
@synthesize delegate;
@synthesize qMacTouchBar;
@synthesize openedPopOvers;
- (id)initWithKDMacTouchBar:(KDMacTouchBar *)bar
{
self = [super init];
items = [[NSMutableDictionary alloc] init];
commands = [[NSMutableDictionary alloc] init];
qMacTouchBar = bar;
delegate = [[DynamicTouchBarProviderDelegate alloc] initWithItems:items];
return self;
}
- (void)addItem:(QAction *)action
{
// Create custom button item
NSString *identifier = identifierForAction(action).toNSString();
NSTouchBarItem *item = nil;
NSView *view = nil;
if (auto wa = qobject_cast<QWidgetAction *>(action)) {
NSPopoverTouchBarItem *i =
[[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
item = i;
view = [[WidgetActionView alloc] initWithWidgetAction:wa];
i.collapsedRepresentation = view;
} else if (auto tb = qobject_cast<QTabBar *>(action->data().value<QObject *>())) {
NSCustomTouchBarItem *i =
[[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
item = i;
NSMutableArray *labels = [[NSMutableArray alloc] init];
view =
[[NSSegmentedControl segmentedControlWithLabels:labels
trackingMode:NSSegmentSwitchTrackingSelectOne
target:self
action:@selector(tabBarAction:)] autorelease];
i.view = view;
} else if (auto bb = qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>())) {
NSMutableArray *buttonItems = [[NSMutableArray alloc] init];
for (int i = 0; i < bb->layout()->count(); ++i) {
auto layoutItem = bb->layout()->itemAt(i);
if (auto b = qobject_cast<QPushButton *>(layoutItem->widget())) {
auto buttonIdentifier = identifierForAction(b).toNSString();
NSCustomTouchBarItem *buttonItem = nil;
NSButton *button = nil;
if ([[items allKeys] containsObject:buttonIdentifier]) {
buttonItem = [items objectForKey:buttonIdentifier];
button = buttonItem.view;
} else {
buttonItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:buttonIdentifier];
button = [NSButton buttonWithTitle:removeMnemonics(b->text()).toNSString()
target:self
action:@selector(buttonAction:)];
}
if (b->isDefault())
[button setKeyEquivalent:@"\r"];
button.title = removeMnemonics(b->text()).toNSString();
buttonItem.view = button;
[buttonItems addObject:buttonItem];
[commands setObject:[[QObjectPointer alloc] initWithQObject:b]
forKey:[NSValue valueWithPointer:button]];
[items setObject:buttonItem forKey:buttonIdentifier];
}
}
item = [[NSGroupTouchBarItem groupItemWithIdentifier:identifier items:buttonItems]
autorelease];
} else if (action->isSeparator()) {
NSPopoverTouchBarItem *i =
[[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
item = i;
view = [NSTextField labelWithString:action->text().toNSString()];
i.collapsedRepresentation = view;
} else {
NSPopoverTouchBarItem *i =
[[[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier] autorelease];
item = i;
view = [[NSButton buttonWithTitle:removeMnemonics(action->text()).toNSString()
target:self
action:@selector(itemAction:)] autorelease];
i.collapsedRepresentation = view;
}
if (view)
[view retain];
[item retain];
[items setObject:item forKey:identifier];
[commands setObject:[[QObjectPointer alloc] initWithQObject:action]
forKey:[NSValue valueWithPointer:view]];
if (!qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>()))
[self changeItem:action];
else
[self makeTouchBar];
}
- (void)changeItem:(QAction *)action
{
NSString *identifier = identifierForAction(action).toNSString();
if (auto wa = qobject_cast<QWidgetAction *>(action)) {
} else if (auto tb = qobject_cast<QTabBar *>(action->data().value<QObject *>())) {
NSCustomTouchBarItem *item = [items objectForKey:identifier];
NSSegmentedControl *control = item.view;
control.segmentCount = tb->count();
for (int i = 0; i < tb->count(); ++i) {
[control setLabel:tb->tabText(i).toNSString() forSegment:i];
}
control.selectedSegment = tb->currentIndex();
} else if (auto bb = qobject_cast<QDialogButtonBox *>(action->data().value<QObject *>())) {
// unfortunately we cannot modify a NSGroupTouchBarItem, so we
// have to recreate it from scratch :-(
int index = qMacTouchBar->actions().indexOf(action);
if (index == qMacTouchBar->actions().count() - 1) {
qMacTouchBar->removeAction(action);
qMacTouchBar->addAction(action);
} else {
QAction *before = qMacTouchBar->actions().at(index + 1);
qMacTouchBar->removeAction(action);
qMacTouchBar->insertAction(before, action);
}
} else if (action->isSeparator()) {
NSPopoverTouchBarItem *item = [items objectForKey:identifier];
NSTextField *field = item.collapsedRepresentation;
field.stringValue = action->text().toNSString();
} else {
NSPopoverTouchBarItem *item = [items objectForKey:identifier];
NSButton *button = item.collapsedRepresentation;
button.imagePosition = NSImageLeft;
button.enabled = action->isEnabled();
button.buttonType =
action->isCheckable() ? NSButtonTypePushOnPushOff : NSButtonTypeAccelerator;
button.bordered = action->isSeparator() && !action->text().isEmpty() ? NO : YES;
button.highlighted = !button.bordered;
button.state = action->isChecked();
button.hidden = !action->isVisible();
button.image = qt_mac_create_nsimage(action->icon());
button.title = removeMnemonics(action->text()).toNSString();
switch (qMacTouchBar->touchButtonStyle())
{
case KDMacTouchBar::IconOnly:
button.imagePosition = NSImageOnly;
break;
case KDMacTouchBar::TextOnly:
button.imagePosition = NSNoImage;
break;
case KDMacTouchBar::TextBesideIcon:
button.imagePosition = NSImageLeft;
break;
}
item.showsCloseButton = action->menu() != nullptr;
if (action->menu()) {
item.popoverTouchBar = [[NSTouchBar alloc] init];
item.popoverTouchBar.delegate = delegate;
// Add ordered items array
NSMutableArray *array = [[NSMutableArray alloc] init];
for (auto action : action->menu()->actions()) {
if (action->isVisible()) {
[self addItem:action];
if (action->isSeparator() && action->text().isEmpty())
[array addObject:NSTouchBarItemIdentifierFixedSpaceLarge];
else
[array addObject:identifierForAction(action).toNSString()];
}
}
item.popoverTouchBar.defaultItemIdentifiers = array;
}
}
[self makeTouchBar];
}
- (void)removeItem:(QAction *)action
{
NSString *identifier = identifierForAction(action).toNSString();
NSPopoverTouchBarItem *item = [items objectForKey:identifier];
if (item == nil)
return;
QObjectPointer *command = [commands objectForKey:[NSValue valueWithPointer:item.view]];
[commands removeObjectForKey:[NSValue valueWithPointer:item.view]];
[items removeObjectForKey:identifier];
[command release];
[self makeTouchBar];
}
- (void)buttonAction:(id)sender
{
QObjectPointer *qobjectPointer =
(QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
// Missing entry in commands dict. Should not really happen, but does
if (!qobjectPointer)
return;
QPushButton *button = qobject_cast<QPushButton *>(qobjectPointer.qobject);
if (!button)
return;
button->click();
}
- (void)tabBarAction:(id)sender
{
QObjectPointer *qobjectPointer =
(QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
// Missing entry in commands dict. Should not really happen, but does
if (!qobjectPointer)
return;
// Check for deleted QObject
if (!qobjectPointer.qobject)
return;
QAction *action = static_cast<QAction *>(qobjectPointer.qobject);
if (!action)
return;
QTabBar *tabBar = qobject_cast<QTabBar *>(action->data().value<QObject *>());
if (!tabBar)
return;
NSString *identifier = identifierForAction(action).toNSString();
NSCustomTouchBarItem *item = [items objectForKey:identifier];
NSSegmentedControl *control = item.view;
tabBar->setCurrentIndex(control.selectedSegment);
}
- (void)itemAction:(id)sender
{
QObjectPointer *qobjectPointer =
(QObjectPointer *)[commands objectForKey:[NSValue valueWithPointer:sender]];
// Missing entry in commands dict. Should not really happen, but does
if (!qobjectPointer)
return;
// Check for deleted QObject
if (!qobjectPointer.qobject)
return;
QAction *action = static_cast<QAction *>(qobjectPointer.qobject);
if (!action || action->isSeparator())
return;
if (!action->isEnabled())
return;
action->activate(QAction::Trigger);
if (action->menu()) {
NSString *identifier = identifierForAction(action).toNSString();
NSPopoverTouchBarItem *item = [items objectForKey:identifier];
[item showPopover:item];
openedPopOvers.push(item);
} else {
while (!openedPopOvers.isEmpty()) {
auto poppedItem = openedPopOvers.pop();
[poppedItem dismissPopover:poppedItem];
}
}
}
- (void)clearItems
{
[items removeAllObjects];
[commands removeAllObjects];
}
- (NSTouchBar *)makeTouchBar
{
// Create the touch bar with this instance as its delegate
if (touchBar == nil) {
touchBar = [[NSTouchBar alloc] init];
touchBar.delegate = delegate;
}
// Add ordered items array
NSMutableArray *array = [[NSMutableArray alloc] init];
for (auto action : qMacTouchBar->actions()) {
if (action->isVisible()) {
if (action->isSeparator() && action->text().isEmpty())
[array addObject:NSTouchBarItemIdentifierFixedSpaceLarge];
else
[array addObject:identifierForAction(action).toNSString()];
}
}
touchBar.defaultItemIdentifiers = array;
return touchBar;
}
- (void)installAsDelegateForWindow:(NSWindow *)window
{
_qtDelegate = window.delegate; // Save current delegate for forwarding
window.delegate = self;
}
- (void)installAsDelegateForApplication:(NSApplication *)application
{
_qtDelegate = application.delegate; // Save current delegate for forwarding
application.delegate = self;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
// We want to forward to the qt delegate. Respond to selectors it
// responds to in addition to selectors this instance resonds to.
return [_qtDelegate respondsToSelector:aSelector] || [super respondsToSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// Forward to the existing delegate. This function is only called for selectors
// this instance does not responds to, which means that the qt delegate
// must respond to it (due to the respondsToSelector implementation above).
[anInvocation invokeWithTarget:_qtDelegate];
}
@end
class KDMacTouchBar::Private
{
public:
DynamicTouchBarProvider *touchBarProvider = nil;
QAction *principialAction = nullptr;
QAction *escapeAction = nullptr;
KDMacTouchBar::TouchButtonStyle touchButtonStyle = TextBesideIcon;
static bool automaticallyCreateMessageBoxTouchBar;
};
bool KDMacTouchBar::Private::automaticallyCreateMessageBoxTouchBar = false;
class AutomaticMessageBoxTouchBarStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
void polish(QWidget *w) override
{
if (auto mb = qobject_cast<QMessageBox *>(w)) {
if (KDMacTouchBar::isAutomacicallyCreatingMessageBoxTouchBar())
new KDMacTouchBar(mb);
}
QProxyStyle::polish(w);
}
};
/*!
\class KDMacTouchBar
\brief The KDMacTouchBar class wraps the native NSTouchBar class.
KDMacTouchBar provides a Qt-based API for NSTouchBar. The touchbar displays
a number of QActions. Each QAction can have a text and an icon. Alternatively,
the QActions might be separators (with or without text) or QWidgetActions.
Add actions by calling addAction(). Alternatively, you can use one of the
convenience methods like addDialogButtonBox() or addTabBar() which provide
common use cases.
If an action with a associated menu is added, its menu items are added as
sub-touchbar. Showing sub-menus of this menu is not supported, due to macOS
system restrictions.
Usage: (QtWidgets)
\code
QMainWindow *mw = ...;
KDMacTouchBar *touchBar = new KDMacTouchBar(mw);
touchBar->addAction(actionNewFile);
touchBar->addSeparator();
touchBar->addAction(actionSaveFile);
\endcode
*/
/*!
\enum KDMacTouchBar::TouchButtonStyle
\value IconOnly Only display the icon.
\value TextOnly Only display the text.
\value TextBesideIcon The text appears beside the icon.
*/
/*!
Constructs a KDMacTouchBar for the window of \a parent. If \a parent is
nullptr, the KDMacTouchBar is shown as soon as this QApplication has focus,
if no other window with an own KDMacTouchBar has focus.
*/
KDMacTouchBar::KDMacTouchBar(QWidget *parent)
: QWidget(parent)
, d(new Private)
{
d->touchBarProvider = [[DynamicTouchBarProvider alloc] initWithKDMacTouchBar:this];
if (parent) {
NSView *view = reinterpret_cast<NSView *>(parent->window()->winId());
[d->touchBarProvider installAsDelegateForWindow:[view window]];
} else {
[d->touchBarProvider installAsDelegateForApplication:[NSApplication sharedApplication]];
}
}
/*!
Constructs a KDMacTouchBar containing the QDialogButtonBox from inside of
\a messageBox.
*/
KDMacTouchBar::KDMacTouchBar(QMessageBox *messageBox)
: QWidget( messageBox)
, d(new Private)
{
d->touchBarProvider = [[DynamicTouchBarProvider alloc] initWithKDMacTouchBar:this];
NSView *view = reinterpret_cast<NSView *>(messageBox->window()->winId());
[d->touchBarProvider installAsDelegateForWindow:[view window]];
setPrincipialAction(addMessageBox(messageBox));
}
/*!
Destroys the touch bar.
*/
KDMacTouchBar::~KDMacTouchBar()
{
[d->touchBarProvider release];
delete d;
}
/*!
This static convenience method controls, whether KDMacTouchBar will
automatically create a touchbar for QMessageBox instances. The
created touchbar contains the buttons of the message box.
This enables to use the static QMessageBox method and still having
a touchbar for them.
\note When you enable this setting for the first time, KDMacTouchBar will
install a QProxyStyle into the QApplication object to be able to create
the KDMacTouchBar in its polish method. The installed QProxyStyle will
use the existing application style as base style.
*/
void KDMacTouchBar::setAutomaticallyCreateMessageBoxTouchBar(bool automatic)
{
static AutomaticMessageBoxTouchBarStyle *touchStyle = nullptr;
if (automatic && !touchStyle) {
qApp->setStyle(touchStyle = new AutomaticMessageBoxTouchBarStyle(qApp->style()));
}
Private::automaticallyCreateMessageBoxTouchBar = automatic;
}
/*!
Returns whether KDMacTouchBar automatically creates a touchbar for
QMessageBox instances.
*/
bool KDMacTouchBar::isAutomacicallyCreatingMessageBoxTouchBar()
{
return Private::automaticallyCreateMessageBoxTouchBar;
}
/*!
Adds a separator item to the end of the touch bar.
*/
QAction *KDMacTouchBar::addSeparator()
{
auto action = new QAction(this);
action->setSeparator(true);
addAction(action);
return action;
}
/*! \reimp */
bool KDMacTouchBar::event(QEvent *event)
{
switch (event->type()) {
case QEvent::ActionAdded:
[d->touchBarProvider addItem:static_cast<QActionEvent *>(event)->action()];
break;
case QEvent::ActionChanged:
[d->touchBarProvider changeItem:static_cast<QActionEvent *>(event)->action()];
break;
case QEvent::ActionRemoved:
[d->touchBarProvider removeItem:static_cast<QActionEvent *>(event)->action()];
break;
default:
break;
}
return QWidget::event(event);
}
/*!
Adds a button group controlling a \a tabBar item to the end of the touch bar.
*/
QAction *KDMacTouchBar::addTabBar(QTabBar *tabBar)
{
auto a = new TabBarAction(tabBar, this);
addAction(a);
return a;
}
/*!
Removes \a tabBar from the touch bar.
*/
void KDMacTouchBar::removeTabBar(QTabBar *tabBar)
{
for (auto a : actions()) {
if (a->data().value<QObject *>() == tabBar) {
removeAction(a);
return;
}
}
}
/*!
Adds the QDialogButtonBox of \a messageBox to the end of the touch bar.
*/
QAction *KDMacTouchBar::addMessageBox(QMessageBox *messageBox)
{
return addDialogButtonBox(messageBox->findChild<QDialogButtonBox *>(QStringLiteral("qt_msgbox_buttonbox")));
}
/*!
Removes the QDialogButtonBox of \a messageBox from the touch bar.
*/
void KDMacTouchBar::removeMessageBox(QMessageBox *messageBox)
{
return removeDialogButtonBox(messageBox->findChild<QDialogButtonBox *>(QStringLiteral("qt_msgbox_buttonbox")));
}
/*!
Adds a button group controlling \a buttonBox to the end of the touch bar.
*/
QAction *KDMacTouchBar::addDialogButtonBox(QDialogButtonBox *buttonBox)
{
auto a = new ButtonBoxAction(buttonBox, this);
addAction(a);
return a;
}
/*!
Removes the \a buttonBox from the touch bar.
*/
void KDMacTouchBar::removeDialogButtonBox(QDialogButtonBox *buttonBox)
{
for (auto a : actions()) {
if (a->data().value<QObject *>() == buttonBox) {
removeAction(a);
return;
}
}
}
/*!
\property KDMacTouchBar::principialAction
\brief the principial action of the touch bar
The principial action of the touch bar is the QAction you want the system
to center in the touch bar.
You need to add the action to the touch bar before you can set it as principial
action.
*/
void KDMacTouchBar::setPrincipialAction(QAction *action)
{
d->principialAction = action;
d->touchBarProvider.touchBar.principalItemIdentifier = action ? identifierForAction(action).toNSString() : nil;
}
QAction *KDMacTouchBar::principialAction() const
{
return d->principialAction;
}
/*!
\property KDMacTouchBar::escapeAction
\brief the action used as system escape key action
By setting a QAction as escapeAction, it is possible to replace the system
escape key with a random action.
You don't need to add the action to the touch bar before you can set it as
escape action.
*/
void KDMacTouchBar::setEscapeAction(QAction *action)
{
if (d->escapeAction == action)
return;
if (d->escapeAction)
[d->touchBarProvider removeItem:d->escapeAction];
d->escapeAction = action;
if (d->escapeAction) {
[d->touchBarProvider addItem:d->escapeAction];
d->touchBarProvider.touchBar.escapeKeyReplacementItemIdentifier =
identifierForAction(action).toNSString();
} else
d->touchBarProvider.touchBar.escapeKeyReplacementItemIdentifier = nil;
}
QAction *KDMacTouchBar::escapeAction() const
{
return d->escapeAction;
}
/*!
\property KDMacTouchBar::touchButtonStyle
This property holds the style of touch bar buttons.
This property defines the style of all touch buttons that are added as QActions.
Added tab widgets, dialog button boxes, message boxes or QWidgetActions won't
follow this style.
The default is KDMacTouchBar::TextBesideIcon
*/
void KDMacTouchBar::setTouchButtonStyle(TouchButtonStyle touchButtonStyle)
{
if (d->touchButtonStyle == touchButtonStyle)
return;
d->touchButtonStyle = touchButtonStyle;
for (auto* action : actions())
[d->touchBarProvider changeItem:action];
if (d->escapeAction)
[d->touchBarProvider changeItem:d->escapeAction];
}
KDMacTouchBar::TouchButtonStyle KDMacTouchBar::touchButtonStyle() const
{
return d->touchButtonStyle;
}
/*!
Removes all actions from the touch bar. Removes even the escapeAction.
The principialAction is cleared.
*/
void KDMacTouchBar::clear()
{
setEscapeAction(nullptr);
setPrincipialAction(nullptr);
for (auto* action : actions())
removeAction(action);
}
QT_END_NAMESPACE

@ -0,0 +1,31 @@
/****************************************************************************
** Copyright (C) 2019-2020 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com.
** All rights reserved.
**
** This file is part of the KD MacTouchBar library.
**
** This file may be distributed and/or modified under the terms of the
** GNU Lesser General Public License version 3 as published by the
** Free Software Foundation and appearing in the file LICENSE.LGPL.txt included.
**
** You may even contact us at info@kdab.com for different licensing options.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@kdab.com if any conditions of this licensing are not
** clear to you.
**
**********************************************************************/
#ifndef KDMACTOUCHBAR_GLOBAL_H
#define KDMACTOUCHBAR_GLOBAL_H
#include <QtCore/QtGlobal>
#ifdef KDMACTOUCHBAR_BUILD_KDMACTOUCHBAR_LIB
# define KDMACTOUCHBAR_EXPORT Q_DECL_EXPORT
#else
# define KDMACTOUCHBAR_EXPORT Q_DECL_IMPORT
#endif
#endif /* KDMACTOUCHBAR_GLOBAL_H */

@ -0,0 +1,28 @@
TEMPLATE = lib
TARGET = KDMacTouchBar
QT += widgets
include($${TOP_SOURCE_DIR}/kdmactouchbar.pri)
CONFIG += lib_bundle
DESTDIR = ../lib
SOURCES = kdmactouchbar.mm
HEADERS = kdmactouchbar.h kdmactouchbar_global.h
LIBS += -framework Cocoa
QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/
DEFINES += KDMACTOUCHBAR_BUILD_KDMACTOUCHBAR_LIB QT_NO_CAST_TO_ASCII QT_ASCII_CAST_WARNING
target.path = "$${INSTALL_PREFIX}/lib"
INSTALLS += target
FRAMEWORK_HEADERS.version = Versions
FRAMEWORK_HEADERS.files = $$HEADERS
FRAMEWORK_HEADERS.path = Headers
QMAKE_BUNDLE_DATA += FRAMEWORK_HEADERS
QMAKE_TARGET_BUNDLE_PREFIX = "com.kdab"

@ -9,6 +9,10 @@
#include <QFileDialog>
#include <QInputDialog>
#ifdef Q_OS_MAC
#include "kdmactouchbar/src/kdmactouchbar.h"
#endif
#include "mainwindow.h"
#include "widgets/ccswidget.h"
#include "widgets/redditwidget.h"
@ -355,6 +359,15 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
this->initMain();
this->initWidgets();
this->initMenu();
#ifdef Q_OS_MAC
KDMacTouchBar *touchBar = new KDMacTouchBar(this);
touchBar->addAction(ui->actionCalculator);
touchBar->addAction(ui->actionKeys);
touchBar->addAction(ui->actionDonate_to_Feather);
touchBar->addAction(ui->actionReport_bug);
touchBar->addAction(ui->actionSettings);
#endif
}
void MainWindow::initMain() {

Loading…
Cancel
Save