Add macOS touchbar support

This commit is contained in:
Alex Spataru 2021-09-14 16:42:18 -05:00
parent 5f40442812
commit 198157eb96
45 changed files with 2044 additions and 3 deletions

View File

@ -138,6 +138,7 @@ HEADERS += \
src/JSON/Generator.h \
src/JSON/Group.h \
src/MQTT/Client.h \
src/Misc/MacExtras.h \
src/Misc/ModuleManager.h \
src/Misc/TimerEvents.h \
src/Misc/ThemeManager.h \
@ -162,6 +163,7 @@ SOURCES += \
src/JSON/Generator.cpp \
src/JSON/Group.cpp \
src/MQTT/Client.cpp \
src/Misc/MacExtras.cpp \
src/Misc/ModuleManager.cpp \
src/Misc/TimerEvents.cpp \
src/Misc/ThemeManager.cpp \

View File

@ -34,4 +34,5 @@ RESOURCES += \
$$PWD/messages/rcc_messages.qrc \
$$PWD/qml/rcc_qml.qrc \
$$PWD/themes/rcc_themes.qrc \
$$PWD/mac-icons/rcc_mac-icons.qrc \
$$PWD/translations/rcc_translations.qrc

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/mac-icons">
<file>console.png</file>
<file>dashboard.png</file>
<file>setup.png</file>
<file>widgets.png</file>
</qresource>
</RCC>

BIN
assets/mac-icons/setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -44,11 +44,38 @@ Control {
//
// Aliases to button check status
//
property alias dataChecked: dataBt.checked
property alias dataChecked: dashboardBt.checked
property alias setupChecked: setupBt.checked
property alias consoleChecked: consoleBt.checked
property alias widgetsChecked: widgetsBt.checked
//
// Connections with mac touchbar
//
Connections {
target: Cpp_Misc_MacExtras
function onSetupClicked() {
setupBt.clicked()
Cpp_Misc_MacExtras.setSetupChecked(setupBt.checked)
}
function onConsoleClicked() {
consoleBt.clicked()
Cpp_Misc_MacExtras.setConsoleChecked(consoleBt.checked)
}
function onWidgetsClicked() {
widgetsBt.clicked()
Cpp_Misc_MacExtras.setWidgetsChecked(widgetsBt.checked)
}
function onDashboardClicked() {
dashboardBt.clicked()
Cpp_Misc_MacExtras.setDashboardChecked(dashboardBt.checked)
}
}
//
// Background gradient
//
@ -116,6 +143,7 @@ Control {
palette.buttonText: Cpp_ThemeManager.brightText
palette.button: Cpp_ThemeManager.toolbarGradient1
palette.window: Cpp_ThemeManager.toolbarGradient1
onCheckedChanged: Cpp_Misc_MacExtras.setSetupChecked(checked)
}
Button {
@ -128,15 +156,16 @@ Control {
onClicked: root.consoleClicked()
icon.source: "qrc:/icons/code.svg"
text: qsTr("Console") + _btSpacer
enabled: dataBt.enabled || widgetsBt.enabled
enabled: dashboardBt.enabled || widgetsBt.enabled
icon.color: Cpp_ThemeManager.brightText
palette.buttonText: Cpp_ThemeManager.brightText
palette.button: Cpp_ThemeManager.toolbarGradient1
palette.window: Cpp_ThemeManager.toolbarGradient1
onCheckedChanged: Cpp_Misc_MacExtras.setConsoleChecked(checked)
}
Button {
id: dataBt
id: dashboardBt
flat: true
icon.width: 24
@ -150,6 +179,8 @@ Control {
palette.buttonText: Cpp_ThemeManager.brightText
palette.button: Cpp_ThemeManager.toolbarGradient1
palette.window: Cpp_ThemeManager.toolbarGradient1
onCheckedChanged: Cpp_Misc_MacExtras.setDashboardChecked(checked)
onEnabledChanged: Cpp_Misc_MacExtras.setDashboardEnabled(enabled)
opacity: enabled ? 1 : 0.5
Behavior on opacity {NumberAnimation{}}
@ -170,6 +201,8 @@ Control {
palette.buttonText: Cpp_ThemeManager.brightText
palette.button: Cpp_ThemeManager.toolbarGradient1
palette.window: Cpp_ThemeManager.toolbarGradient1
onCheckedChanged: Cpp_Misc_MacExtras.setWidgetsChecked(checked)
onEnabledChanged: Cpp_Misc_MacExtras.setWidgetsEnabled(enabled)
opacity: enabled ? 1 : 0.5
Behavior on opacity {NumberAnimation{}}

Binary file not shown.

View File

@ -1267,6 +1267,25 @@
<translation>Drucken</translation>
</message>
</context>
<context>
<name>Misc::MacExtras</name>
<message>
<source>Setup</source>
<translation>Einstellungen</translation>
</message>
<message>
<source>Console</source>
<translation>Konsole</translation>
</message>
<message>
<source>Widgets</source>
<translation>Widgets</translation>
</message>
<message>
<source>Dashboard</source>
<translation>Dashboard</translation>
</message>
</context>
<context>
<name>Misc::ThemeManager</name>
<message>

Binary file not shown.

View File

@ -1125,6 +1125,25 @@
<translation></translation>
</message>
</context>
<context>
<name>Misc::MacExtras</name>
<message>
<source>Setup</source>
<translation></translation>
</message>
<message>
<source>Console</source>
<translation></translation>
</message>
<message>
<source>Widgets</source>
<translation></translation>
</message>
<message>
<source>Dashboard</source>
<translation></translation>
</message>
</context>
<context>
<name>Misc::ThemeManager</name>
<message>

Binary file not shown.

View File

@ -1334,6 +1334,25 @@
<translation>Imprimir</translation>
</message>
</context>
<context>
<name>Misc::MacExtras</name>
<message>
<source>Setup</source>
<translation>Configuración</translation>
</message>
<message>
<source>Console</source>
<translation>Consola</translation>
</message>
<message>
<source>Widgets</source>
<translation>Widgets</translation>
</message>
<message>
<source>Dashboard</source>
<translation>Tablero</translation>
</message>
</context>
<context>
<name>Misc::ThemeManager</name>
<message>

Binary file not shown.

View File

@ -1299,6 +1299,25 @@
<translation></translation>
</message>
</context>
<context>
<name>Misc::MacExtras</name>
<message>
<source>Setup</source>
<translation></translation>
</message>
<message>
<source>Console</source>
<translation></translation>
</message>
<message>
<source>Widgets</source>
<translation></translation>
</message>
<message>
<source>Dashboard</source>
<translation></translation>
</message>
</context>
<context>
<name>Misc::ThemeManager</name>
<message>

7
libs/KDMacTouchBar/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
Makefile
*.moc
*.o
lib
.qmake.cache
.qmake.stash
.DS_Store

16
libs/KDMacTouchBar/.krazy Normal file
View File

@ -0,0 +1,16 @@
CHECKSETS qt5,c++,foss
#KDAB-specific checks
EXTRA kdabcopyright,kdabcontactus
#additional checks
#EXTRA defines,null
#exclude checks now being done by clazy or clang-tools
EXCLUDE strings,explicit,normalize,passbyvalue,operators,nullstrcompare,nullstrassign,doublequote_chars,qobject,sigsandslots,staticobjects
#exclude more checks
EXCLUDE style
#skip the borrowed code in the cmake subdir
SKIP /cmake/Qt5Portability.cmake

View File

@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 2.8.12)
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")
install(FILES
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/KDMacTouchBarConfig.cmake"
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake")
add_subdirectory(src)
add_subdirectory(examples)

View File

@ -0,0 +1,175 @@
KDMacTouchBar is Copyright (C) 2019-2021 Klaralvdalens Datakonsult AB.
You may use, distribute and copy KDMacTouchBar under the terms of
GNU Lesser General Public License version 3, which is displayed below.
You may even contact us at info@kdab.com for different licensing options.
-------------------------------------------------------------------------
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,64 @@
# KDMacTouchBar
KDAB's Qt Widget for the Mac Touch Bar
## Introduction
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:
```
QMainWindow *mw = ...;
KDMacTouchBar *touchBar = new KDMacTouchBar(mw);
touchBar->addAction(actionNewFile);
touchBar->addSeparator();
touchBar->addAction(actionSaveFile);
```
## Licensing:
KD MacTouchBar is (C) 2019-2021, Klarälvdalens Datakonsult AB,
and is available under the terms of:
* the LGPL (see LICENSE.LGPL.txt for details)
* the KDAB commercial license, provided that you buy a license.
please contact info@kdab.com if you are interested in buying commercial licenses.
## Get Involved:
KDAB will happily accept external contributions; however, **all**
contributions will require a signed Contributor License Agreement
(see docs/KDMacTouchBar-CopyrightAssignmentForm.pdf).
Contact info@kdab.com for more information.
Please submit your contributions or issue reports from our GitHub space at
https://github.com/KDAB/KDMacTouchBar
## About KDAB
KD MacTouchBar is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).
The KDAB Group is the global No.1 software consultancy for Qt, C++ and
OpenGL applications across desktop, embedded and mobile platforms.
The KDAB Group provides consulting and mentoring for developing Qt applications
from scratch and in porting from all popular and legacy frameworks to Qt.
We continue to help develop parts of Qt and are one of the major contributors
to the Qt Project. We can give advanced or standard trainings anywhere
around the globe on Qt as well as C++, OpenGL, 3D and more.
Please visit https://www.kdab.com to meet the people who write code like this.
Stay up-to-date with KDAB product announcements:
* [KDAB Newsletter](https://news.kdab.com)
* [KDAB Blogs](https://www.kdab.com/category/blogs)
* [KDAB on Twitter](https://twitter.com/KDABQt)

View File

@ -0,0 +1,2 @@
set(KDMacTouchBar_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/../KDMacTouchBar.framework/Headers)
set(KDMacTouchBar_LIBRARIES ${CMAKE_CURRENT_LIST_DIR}/../KDMacTouchBar.framework/Versions/Current/KDMacTouchBar)

View File

@ -0,0 +1,32 @@
include_directories(${Qt5Widgets_INCLUDE_DIRS})
if(QT_USE_QTNETWORK)
find_package(Qt5Network REQUIRED)
list(APPEND QT_LIBRARIES Qt5::Network)
include_directories(${Qt5Network_INCLUDE_DIRS})
endif()
if(QT_USE_QTXML)
find_package(Qt5Xml REQUIRED)
list(APPEND QT_LIBRARIES Qt5::Xml)
include_directories(${Qt5Xml_INCLUDE_DIRS})
endif()
if(QT_USE_QTTEST)
find_package(Qt5Test REQUIRED)
list(APPEND QT_LIBRARIES Qt5::Test)
include_directories(${Qt5Test_INCLUDE_DIRS})
endif()
macro(qt4_wrap_ui)
qt5_wrap_ui(${ARGN})
endmacro()
macro(qt4_wrap_cpp)
qt5_wrap_cpp(${ARGN})
endmacro()
macro(qt4_add_resources)
qt5_add_resources(${ARGN})
endmacro()

View File

@ -0,0 +1 @@
add_subdirectory(mactouchbar)

View File

@ -0,0 +1,3 @@
TEMPLATE=subdirs
SUBDIRS=mactouchbar

View File

@ -0,0 +1,19 @@
include(${QT_USE_FILE})
set(CMAKE_AUTORCC ON)
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
add_executable(mactouchbar MACOSX_BUNDLE
main.cpp
mainwindow.cpp
mainwindow.h
mactouchbar.qrc)
include_directories(../../src)
install(TARGETS mactouchbar
DESTINATION "${CMAKE_INSTALL_PREFIX}/examples/mactouchbar")
target_link_libraries(mactouchbar KDMacTouchBar)

View File

@ -0,0 +1,12 @@
QT += widgets
HEADERS += mainwindow.h
SOURCES += mainwindow.cpp main.cpp
RESOURCES += mactouchbar.qrc
INCLUDEPATH = ../../src
LIBS += -F../../lib -framework KDMacTouchBar
QMAKE_LFLAGS = '-Wl,-rpath,\'$$OUT_PWD/../../lib\',-rpath,\'$$INSTALL_PREFIX/lib\''
target.path = "$${INSTALL_PREFIX}/examples/mactouchbar"
INSTALLS += target

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>qtlogo.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,38 @@
/****************************************************************************
** Copyright (C) 2019-2021 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 <QtWidgets/QApplication>
#include "kdmactouchbar.h"
#include "mainwindow.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// enable automatic message box on touchbar
KDMacTouchBar::setAutomaticallyCreateMessageBoxTouchBar(true);
MainWindow mw;
mw.show();
return app.exec();
}

View File

@ -0,0 +1,73 @@
/****************************************************************************
** Copyright (C) 2019-2021 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 "mainwindow.h"
#include <QtGui/QCloseEvent>
#include <QtWidgets/QAction>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QLabel>
#include "kdmactouchbar.h"
MainWindow::MainWindow()
{
setWindowTitle("KDMacTouchBar Example");
resize(400, 200);
// attach a touchbar to this window
KDMacTouchBar *touchBar = new KDMacTouchBar(this);
QIcon qtIcon(QStringLiteral(":qtlogo.png"));
// add item
QAction *action = new QAction(qtIcon, tr("Qt with touchbar"));
touchBar->addAction(action);
connect(action, &QAction::triggered, this, &MainWindow::activated);
// separator
touchBar->addSeparator();
touchBar->addSeparator()->setText(tr("More items:"));
// and more items
QAction *action1 = new QAction(tr("Item 1"));
touchBar->addAction(action1);
connect(action1, &QAction::triggered, this, &MainWindow::activated);
QAction *action2 = new QAction(tr("Item 2"));
touchBar->addAction(action2);
connect(action2, &QAction::triggered, this, &MainWindow::activated);
// make special escape button
QAction *quit = new QAction(tr("Quit"));
touchBar->setEscapeAction(quit);
connect(quit, &QAction::triggered, this, &QWidget::close);
}
void MainWindow::activated()
{
QMessageBox::information(this, tr("Activated"), tr("You activated the touchbar action!"));
}
void MainWindow::closeEvent(QCloseEvent *event)
{
event->setAccepted(QMessageBox::question(this, tr("Quit"), tr("Do yo really want to quit?")) == QMessageBox::Yes);
}

View File

@ -0,0 +1,39 @@
/****************************************************************************
** Copyright (C) 2019-2021 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 EX_MAINWINDOW_H
#define EX_MAINWINDOW_H
#include <QtWidgets/QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
public Q_SLOTS:
void activated();
protected:
void closeEvent(QCloseEvent *event) override;
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,8 @@
TEMPLATE = subdirs
features.path = $$INSTALL_PREFIX/share/mkspecs/features
features.files = *.prf
INSTALLS += features
OTHER_FILES = *.prf

View File

@ -0,0 +1,4 @@
LIBPATH=$$clean_path($$PWD/../../../lib)
LIBS += -F$$LIBPATH -framework KDMacTouchBar
QMAKE_LFLAGS += '-Wl,-rpath,\'$$LIBPATH\''
INCLUDEPATH += $$LIBPATH/KDMacTouchBar.framework/Headers

View File

@ -0,0 +1 @@
VERSION = 1.0

View File

@ -0,0 +1,33 @@
TEMPLATE = subdirs
include(kdmactouchbar.pri)
DEFAULT_INSTALL_PREFIX = /usr/local/KDAB/KDMacTouchBar-$$VERSION
isEmpty( KDMACTOUCHBAR_INSTALL_PREFIX ): KDMACTOUCHBAR_INSTALL_PREFIX=$$PREFIX
isEmpty( KDMACTOUCHBAR_INSTALL_PREFIX ): KDMACTOUCHBAR_INSTALL_PREFIX=$$DEFAULT_INSTALL_PREFIX
# if the default was either set by configure or set by the line above:
equals( KDMACTOUCHBAR_INSTALL_PREFIX, $$DEFAULT_INSTALL_PREFIX ){
INSTALL_PREFIX=$$DEFAULT_INSTALL_PREFIX
message( "No install prefix given, using default of" $$DEFAULT_INSTALL_PREFIX (use configure.sh -prefix DIR to specify))
} else {
INSTALL_PREFIX=$$KDMACTOUCHBAR_INSTALL_PREFIX
}
# This file is in the build directory (because "somecommand >> somefile" puts it there)
QMAKE_CACHE = "$${OUT_PWD}/.qmake.cache"
MESSAGE = '\\'$$LITERAL_HASH\\' KDAB qmake cache file: following lines autogenerated during qmake run'
system('echo $${MESSAGE} > $${QMAKE_CACHE}')
TMP_SOURCE_DIR = $${PWD}
TMP_BUILD_DIR = $${OUT_PWD}
system('echo TOP_SOURCE_DIR=$${TMP_SOURCE_DIR} >> $${QMAKE_CACHE}')
system('echo TOP_BUILD_DIR=$${TMP_BUILD_DIR} >> $${QMAKE_CACHE}')
system('echo INSTALL_PREFIX=$$INSTALL_PREFIX >> $${QMAKE_CACHE}')
SUBDIRS += src examples features
examples.depends = src

View File

@ -0,0 +1,24 @@
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
PUBLIC_HEADER "${HEADERS}"
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib"
MACOSX_RPATH TRUE
)

View File

@ -0,0 +1,87 @@
/****************************************************************************
** Copyright (C) 2019-2021 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

View File

@ -0,0 +1,980 @@
/****************************************************************************
** Copyright (C) 2019-2021 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

View File

@ -0,0 +1,33 @@
/****************************************************************************
** Copyright (C) 2019-2021 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
#elif defined(KDMACTOUCHBAR_BUILD_KDMACTOUCHBAR_SRC)
# define KDMACTOUCHBAR_EXPORT
#else
# define KDMACTOUCHBAR_EXPORT Q_DECL_IMPORT
#endif
#endif /* KDMACTOUCHBAR_GLOBAL_H */

View File

@ -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"

121
src/Misc/MacExtras.cpp Normal file
View File

@ -0,0 +1,121 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "MacExtras.h"
#include "Translator.h"
#include <Logger.h>
#include <ConsoleAppender.h>
#ifdef Q_OS_MAC
# include <kdmactouchbar.h>
#endif
using namespace Misc;
MacExtras *INSTANCE = nullptr;
MacExtras::MacExtras()
{
#ifdef Q_OS_MAC
// Configure action strings
updateButtonText();
// Configure action icons
m_setupAction.setIcon(QIcon("://mac-icons/setup.png"));
m_consoleAction.setIcon(QIcon("://mac-icons/console.png"));
m_widgetsAction.setIcon(QIcon("://mac-icons/widgets.png"));
m_dashboardAction.setIcon(QIcon("://mac-icons/dashboard.png"));
// Setup checkable items
m_setupAction.setCheckable(true);
m_consoleAction.setCheckable(true);
m_widgetsAction.setCheckable(true);
m_dashboardAction.setCheckable(true);
// Configure signals
connect(&m_setupAction, SIGNAL(triggered()), this, SIGNAL(setupClicked()));
connect(&m_consoleAction, SIGNAL(triggered()), this, SIGNAL(consoleClicked()));
connect(&m_widgetsAction, SIGNAL(triggered()), this, SIGNAL(widgetsClicked()));
connect(&m_dashboardAction, SIGNAL(triggered()), this, SIGNAL(dashboardClicked()));
// Create touchbar
KDMacTouchBar *bar = new KDMacTouchBar((QWidget *)nullptr);
bar->addAction(&m_setupAction);
bar->addAction(&m_consoleAction);
bar->addAction(&m_dashboardAction);
bar->addAction(&m_widgetsAction);
// Re-translate buttons when language is changed
connect(Translator::getInstance(), SIGNAL(languageChanged()), this,
SLOT(updateButtonText()));
// Logger
LOG_TRACE() << "Class initialized";
#endif
}
MacExtras *MacExtras::getInstance()
{
if (!INSTANCE)
INSTANCE = new MacExtras;
return INSTANCE;
}
void MacExtras::setSetupChecked(const bool checked)
{
m_setupAction.setChecked(checked);
}
void MacExtras::setConsoleChecked(const bool checked)
{
m_consoleAction.setChecked(checked);
}
void MacExtras::setWidgetsChecked(const bool checked)
{
m_widgetsAction.setChecked(checked);
}
void MacExtras::setWidgetsEnabled(const bool enabled)
{
m_widgetsAction.setEnabled(enabled);
}
void MacExtras::setDashboardChecked(const bool checked)
{
m_dashboardAction.setChecked(checked);
}
void MacExtras::setDashboardEnabled(const bool enabled)
{
m_dashboardAction.setEnabled(enabled);
}
void MacExtras::updateButtonText()
{
m_setupAction.setText(tr("Setup"));
m_consoleAction.setText(tr("Console"));
m_widgetsAction.setText(tr("Widgets"));
m_dashboardAction.setText(tr("Dashboard"));
}

67
src/Misc/MacExtras.h Normal file
View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MISC_MAC_EXTRAS_H
#define MISC_MAC_EXTRAS_H
#include <QAction>
#include <QObject>
namespace Misc
{
class MacExtras : public QObject
{
Q_OBJECT
signals:
void setupClicked();
void widgetsClicked();
void connectClicked();
void consoleClicked();
void dashboardClicked();
public:
static MacExtras *getInstance();
public slots:
void setSetupChecked(const bool checked);
void setConsoleChecked(const bool checked);
void setWidgetsChecked(const bool checked);
void setWidgetsEnabled(const bool enabled);
void setDashboardChecked(const bool checked);
void setDashboardEnabled(const bool enabled);
private slots:
void updateButtonText();
private:
MacExtras();
private:
QAction m_setupAction;
QAction m_consoleAction;
QAction m_widgetsAction;
QAction m_dashboardAction;
};
}
#endif

View File

@ -43,6 +43,7 @@
#include <IO/DataSources/Serial.h>
#include <IO/DataSources/Network.h>
#include <Misc/MacExtras.h>
#include <Misc/Utilities.h>
#include <Misc/Translator.h>
#include <Misc/TimerEvents.h>
@ -155,6 +156,7 @@ void ModuleManager::initializeQmlInterface()
auto themeManager = Misc::ThemeManager::getInstance();
auto mqttPublisher = MQTT::Client::getInstance();
auto pluginsBridge = Plugins::Bridge::getInstance();
auto macExtras = Misc::MacExtras::getInstance();
LOG_INFO() << "Finished initializing C++ modules";
// Retranslate the QML interface automagically
@ -180,6 +182,7 @@ void ModuleManager::initializeQmlInterface()
c->setContextProperty("Cpp_JSON_Generator", jsonGenerator);
c->setContextProperty("Cpp_MQTT_Client", mqttPublisher);
c->setContextProperty("Cpp_Plugins_Bridge", pluginsBridge);
c->setContextProperty("Cpp_Misc_MacExtras", macExtras);
// Register app info with QML
c->setContextProperty("Cpp_AppName", qApp->applicationName());