diff --git a/README.md b/README.md index a5338ea..90580a5 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,83 @@ # OS X Integration Improved integration of Qt and KDE applications with the Mac OS X desktop ### KDEPlatformTheme The plugin Mac KDE platform theme plugin makes it possible to use KDE font specifications and colour palettes, so that themes like Breeze, Oxygen or QtCurve look like they should but above all that applications use the fonts and font roles for which they were designed. This plugin functions as a wrapper; any request it cannot handle authoritatively itself will be handed off to the platform plugin, which means things like native menubars and Dock menus continue to work. Newer versions of the plugin introduced build-time preferences for file dialog styles (native or KDE) and more generic convenience features like click-and-hold to trigger a contextmenu and emulation of a Menu key (right Command+Option key-combo). Originally a custom-built Qt with a simple patch was required that would let the plugin load automatically like in a KDE session (KDE_SESSION_VERSION set to 4 or 5). Setting QT_QPA_PLATFORMTHEME=kde will work with a stock Qt install, and the plugin can now also be built to override the native Cocoa QPA plugin (OVERRIDE_NATIVE_THEME CMake option). The Menu key emulation still requires a Qt patch (or a dedicated event handler) to do anything useful though. -Other useful env. variables: -QT_QPA_PLATFORMTHEME_VERBOSE : activate verbose mode (logging category +* Other useful env. variables: +- QT_QPA_PLATFORMTHEME_VERBOSE : activate verbose mode (logging category CocoaPlatformTheme or KDEPlatformTheme). -QT_QPA_PLATFORMTHEME_CONFIG_FILE : load a different file instead of "kdeglobals" +- QT_QPA_PLATFORMTHEME_CONFIG_FILE : load a different file instead of "kdeglobals" from ~/.config or ~/Library/Preferences -QT_QPA_PLATFORMTHEME_DISABLED : disable the plugin completely. +- QT_QPA_PLATFORMTHEME_DISABLED : disable the plugin completely. +- PREFER_KDE_DIALOGS : force KDE dialogs even when configured to prefer native + file dialogs. This component should still build against Qt 5.5.x; the other components need at least Qt 5.8 . ### QMacStyle A modified fork of the native macintosh style from Qt 5.9 (git) which doesn't impose the Mac standard font for QComboBox menu items and provides support for named menu sections in context menus and menus attached to a "non-native" menubar. Also builds against Qt 5.8.0 . A standalone build of this component can be done using the provided QMake file (qmacstyle/macstyle.pro). ### QCocoaQPA A modified fork of the Cocoa platform plugin from Qt 5.9 (git; builds against Qt 5.8.0) which provides support for named menu sections under the native menubar and also reintroduces a basic fullscreen mode that works consistently across Mission Control settings and platforms - i.e. it never blackens out other attached monitors but keeps their content visible and accessible. It's also a lot faster and supports opening new windows without side-effects when in fullscreen mode. Selecting the FreeType engine has been made easier via an env. variable (QT_MAC_USE_FREETYPE) as well as an integration function that can be called from application code (see kfontsettingsdatamac.mm). This plugin installs next to and will be loaded instead of the stock plugin; it will then give priority to the modified QMacStyle if that is installed. If the KDE platform theme plugin is built in override mode (see above) this plugin is loaded instead (and will then load the modified or the stock cocoa platform plugin). A standalone build of this component can be done using the provided QMake file (qcocoa-qpa/qcocoa-standalone.pro). ### Building The preferred way of building this project is using CMake, and requires KDE's Extra CMake Modules (http://projects.kde.org/projects/kdesupport/extra-cmake-modules) ; this is also the only way to build the KDE platform theme plugin component. * CMake options: - BUILD_KDE_THEME_PLUGIN : should the KDE platform theme plugin be built? - BUILD_QT_PLUGINS : should the Qt style and QPA plugin components be built? * CMake options for the KDE platform theme plugin: - DEFINE_ICONTHEME_SETTINGS : Should the theme plugin define a standard theme and add the standard locations for icon themes to the search path? - PREFER_NATIVE_DIALOGS : Should native dialogs be preferred over Qt's cross-platform dialogs? - NEVER_NATIVE_DIALOGS : Should native dialogs never be used (when not already preferred)? - OVERRIDE_NATIVE_THEME : see above. NB: the Macintosh/Aqua widget style remains the default style! - DISABLE_DBUS_SUPPORT : Don't build the D-Bus functionality. Experimental! - EMULATE_MENU_KEY : emulate a Menu key (right Command+Option key-combo); requires BUILD_QT_PLUGINS to be set in order for that keypress to open the context menu. diff --git a/src/platformtheme/kdemactheme.h b/src/platformtheme/kdemactheme.h index 43dc76b..dfea02d 100644 --- a/src/platformtheme/kdemactheme.h +++ b/src/platformtheme/kdemactheme.h @@ -1,81 +1,83 @@ /* This file is part of the KDE libraries * Copyright 2013 Kevin Ottens * Copyright 2015 René J.V. Bertin * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License or ( at * your option ) version 3 or, at the discretion of KDE e.V. ( which shall * act as a proxy as in section 14 of the GPLv3 ), any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDEMACTHEME_H #define KDEMACTHEME_H #include "kdeplatformtheme.h" #include "kfontsettingsdatamac.h" #ifdef ADD_MENU_KEY #include #endif class KHintsSettingsMac; class QIconEngine; class KdeMacThemeEventFilter; class QPlatformNativeInterface; class KdeMacTheme : public KdePlatformTheme { public: KdeMacTheme(); ~KdeMacTheme(); // KdeMacTheme must provide platform menu methods or else there will be no menus QPlatformMenuItem* createPlatformMenuItem() const override; QPlatformMenu* createPlatformMenu() const override; QPlatformMenuBar* createPlatformMenuBar() const override; QVariant themeHint(ThemeHint hint) const override; const QPalette *palette(Palette type = SystemPalette) const override; const QFont *font(Font type) const override; QList keyBindings(QKeySequence::StandardKey key) const override; QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; bool usePlatformNativeDialog(DialogType type) const override; QString standardButtonText(int button) const override; QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; QPlatformNativeInterface *nativeInterface(); typedef void * (*PlatformFunctionPtr)(); PlatformFunctionPtr platformFunction(const QByteArray &functionName); bool verbose; protected: void loadSettings(); KFontSettingsDataMac::FontTypes fontType(Font type) const; private: KHintsSettingsMac *m_hints; KFontSettingsDataMac *m_fontsData; // this will hold the instance of the native theme that will be used as a fallback QPlatformTheme *nativeTheme; // this will hold an instance of a class with Qt and/or native event filters: KdeMacThemeEventFilter *m_eventFilter; QPlatformNativeInterface *m_nativeInterface; + + bool m_isCocoa; }; #endif // KDEMACTHEME_H diff --git a/src/platformtheme/kdemactheme.mm b/src/platformtheme/kdemactheme.mm index ce5b51b..eb0f1ed 100644 --- a/src/platformtheme/kdemactheme.mm +++ b/src/platformtheme/kdemactheme.mm @@ -1,743 +1,746 @@ /* This file is part of the KDE libraries * Copyright 2013 Kevin Ottens * Copyright 2013 Aleix Pol Gonzalez * Copyright 2014 Lukáš Tinkl * Copyright 2015 René J.V. Bertin * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License or ( at * your option ) version 3 or, at the discretion of KDE e.V. ( which shall * act as a proxy as in section 14 of the GPLv3 ), any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // #define ADD_MENU_KEY #include "config-platformtheme.h" #include "kdemactheme.h" #include "kfontsettingsdatamac.h" #include "khintssettingsmac.h" #include "kdeplatformfiledialoghelper.h" #include "kdeplatformsystemtrayicon.h" #include "platformtheme_logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_GESTURES #include #include #include #include #include #include #include #include #include #include #include #include #endif // instantiating the native platform theme requires the use of private APIs #include #include #include #include #include #include #include #include #include #include #ifdef USE_PLCRASHREPORTER #include #endif // [NSEvent modifierFlags] keycodes: // LeftShift=131330 // RightShift=131332 // LeftAlt=524576 // RightAlt=524608 // LeftCommand=1048840 // RightCommand=1048848 // RightCommand+RightAlt=1573200 static QString platformName = QStringLiteral(""); // #define TAPANDHOLD_DEBUG class KdeMacThemeEventFilter : public QObject { Q_OBJECT public: KdeMacThemeEventFilter(QObject *parent=nullptr) : QObject(parent) { qtNativeFilter = new QNativeEventFilter; } virtual ~KdeMacThemeEventFilter() { delete qtNativeFilter; qtNativeFilter = nullptr; } class QNativeEventFilter : public QAbstractNativeEventFilter { public: virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; }; QNativeEventFilter *qtNativeFilter; #ifdef ADD_MENU_KEY const static int keyboardMonitorMask = NSKeyDownMask | NSKeyUpMask | NSFlagsChangedMask; NSEvent *nativeEventHandler(void *message); id m_keyboardMonitor; bool enabled; NSTimeInterval disableTime; #endif #ifndef QT_NO_GESTURES inline bool handleGestureForObject(const QObject *obj) const { // this function is called with an that is or inherits a QWidget const QPushButton *btn = qobject_cast(obj); const QToolButton *tbtn = qobject_cast(obj); if (tbtn) { return !tbtn->menu(); } else if (btn) { return !btn->menu(); } else { return (qobject_cast(obj) || qobject_cast(obj) // || obj->inherits("QTabBar") || obj->inherits("QTabWidget") || qobject_cast(obj) || qobject_cast(obj) || qobject_cast(obj) // this catches items in directory lists and the like || obj->objectName() == QStringLiteral("qt_scrollarea_viewport") || obj->inherits("KateViewInternal")); // Konsole windows can be found as obj->inherits("Konsole::TerminalDisplay") but // for some reason Konsole doesn't respond to synthetic ContextMenu events } } #endif int pressedMouseButtons() { return [NSEvent pressedMouseButtons]; } bool eventFilter(QObject *obj, QEvent *event) override { #ifndef QT_NO_GESTURES static QVariant qTrue(true), qFalse(false); // #ifdef TAPANDHOLD_DEBUG // if (qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { // QVariant isGrabbed = obj->property("OurTaHGestureActive"); // if (isGrabbed.isValid() && isGrabbed.toBool()) { // qCWarning(PLATFORMTHEME) << "event=" << event << "grabbed obj=" << obj; // } // } // #endif switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = dynamic_cast(event); if (me->button() == Qt::LeftButton && me->modifiers() == Qt::NoModifier) { QWidget *w = qobject_cast(obj); if (w && handleGestureForObject(obj)) { QVariant isGrabbed = obj->property("OurTaHGestureActive"); if (!(isGrabbed.isValid() && isGrabbed.toBool())) { // ideally we'd check first - if we could. // storing all grabbed QObjects is potentially dangerous since we won't // know when they go stale. w->grabGesture(Qt::TapAndHoldGesture); // accept this event but resend it so that the 1st mousepress // can also trigger a tap-and-hold! obj->setProperty("OurTaHGestureActive", qTrue); #ifdef TAPANDHOLD_DEBUG if (qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { qCWarning(PLATFORMTHEME) << "event=" << event << "grabbing obj=" << obj << "parent=" << obj->parent(); } #endif if (!m_grabbing.contains(obj)) { QMouseEvent relay(*me); me->accept(); m_grabbing.insert(obj); int ret = QCoreApplication::sendEvent(obj, &relay); m_grabbing.remove(obj); return ret; } } } #ifdef TAPANDHOLD_DEBUG else if (w && qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { qCWarning(PLATFORMTHEME) << "event=" << event << "obj=" << obj << "parent=" << obj->parent(); } #endif } // NB: don't "eat" the event if no action was taken! break; } // case QEvent::Paint: // if (pressedMouseButtons() == 1) { // // ignore QPaintEvents when the left mouse button (1<<0) is being held // break; // } else { // // not holding the left mouse button; fall through to check if // // maybe we should cancel a click-and-hold-opens-contextmenu process. // } case QEvent::MouseMove: case QEvent::MouseButtonRelease: { QVariant isGrabbed = obj->property("OurTaHGestureActive"); if (isGrabbed.isValid() && isGrabbed.toBool()) { #ifdef TAPANDHOLD_DEBUG qCWarning(PLATFORMTHEME) << "event=" << event << "obj=" << obj << "parent=" << obj->parent() << "grabbed=" << obj->property("OurTaHGestureActive"); #endif obj->setProperty("OurTaHGestureActive", qFalse); } break; } case QEvent::Gesture: { QGestureEvent *gEvent = static_cast(event); if (QTapAndHoldGesture *heldTap = static_cast(gEvent->gesture(Qt::TapAndHoldGesture))) { if (heldTap->state() == Qt::GestureFinished) { QVariant isGrabbed = obj->property("OurTaHGestureActive"); if (isGrabbed.isValid() && isGrabbed.toBool() && pressedMouseButtons() == 1) { QWidget *w = qobject_cast(obj); // user clicked and held a button, send it a simulated ContextMenuEvent // but send a simulated buttonrelease event first. QPoint localPos = w->mapFromGlobal(heldTap->position().toPoint()); QContextMenuEvent ce(QContextMenuEvent::Mouse, localPos, heldTap->hotSpot().toPoint()); // don't send a ButtonRelease event to Q*Buttons because we don't want to trigger them if (QPushButton *btn = qobject_cast(obj)) { btn->setDown(false); obj->setProperty("OurTaHGestureActive", qFalse); } else if (QToolButton *tbtn = qobject_cast(obj)) { tbtn->setDown(false); obj->setProperty("OurTaHGestureActive", qFalse); } else { QMouseEvent me(QEvent::MouseButtonRelease, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); #ifdef TAPANDHOLD_DEBUG qCWarning(PLATFORMTHEME) << "Sending" << &me; #endif // we'll be unsetting OurTaHGestureActive in the MouseButtonRelease handler above QCoreApplication::sendEvent(obj, &me); } qCWarning(PLATFORMTHEME) << "Sending" << &ce << "to" << obj << "because of" << gEvent << "isGrabbed=" << isGrabbed; bool ret = QCoreApplication::sendEvent(obj, &ce); gEvent->accept(); qCWarning(PLATFORMTHEME) << "\tsendEvent" << &ce << "returned" << ret; return true; } } } break; } #ifdef TAPANDHOLD_DEBUG case QEvent::ContextMenu: if (qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { qCWarning(PLATFORMTHEME) << "event=" << event << "obj=" << obj << "parent=" << obj->parent() << "grabbed=" << obj->property("OurTaHGestureActive"); } break; #endif default: break; } #endif return false; } #ifndef QT_NO_GESTURES QSet m_grabbing; #endif }; bool KdeMacThemeEventFilter::QNativeEventFilter::nativeEventFilter(const QByteArray&, void *message, long *) { NSEvent *event = static_cast(message); switch ([event type]) { #if defined(MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) case NSEventTypeSystemDefined: #else case NSSystemDefined: #endif { // borrowed with thanks from QMPlay2 const int keyCode = ([event data1] & 0xFFFF0000) >> 16; const int keyFlags = ([event data1] & 0x0000FFFF); const int keyState = (((keyFlags & 0xFF00) >> 8) == 0xA); // qCWarning(PLATFORMTHEME) << QStringLiteral("NSSystemDefined event keyCode=%1 keyFlags=%2 keyState=%3").arg(keyCode).arg(keyFlags).arg(keyState); if (keyState == 1) { int qtKey = 0; switch (keyCode) { case NX_KEYTYPE_PLAY: qtKey = Qt::Key_MediaTogglePlayPause; break; case NX_KEYTYPE_NEXT: case NX_KEYTYPE_FAST: qtKey = Qt::Key_MediaNext; break; case NX_KEYTYPE_PREVIOUS: case NX_KEYTYPE_REWIND: qtKey = Qt::Key_MediaPrevious; break; } if (qtKey) { QKeyEvent mediaKeyEvent(QEvent::KeyPress, qtKey, Qt::NoModifier); qCWarning(PLATFORMTHEME) << "Sending mediaKeyEvent" << &mediaKeyEvent; QCoreApplication::sendEvent(qApp, &mediaKeyEvent); return false; } } break; } } return false; } #ifdef ADD_MENU_KEY NSEvent *KdeMacThemeEventFilter::nativeEventHandler(void *message) { NSEvent *event = static_cast(message); switch ([event type]) { case NSFlagsChanged: { switch ([event modifierFlags]) { case 524608: case 1048848: enabled = false; disableTime = [event timestamp]; break; case 1573200: // simultaneous press (i.e. within <= 0.1s) of just the right Command and Option keys: if (enabled || [event timestamp] - disableTime <= 0.1) { enabled = true; // qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "event=" << QString::fromNSString([event description]) // << "modifierFlags=" << [event modifierFlags] << "keyCode=" << [event keyCode]; const unichar menuKeyCode = static_cast(NSMenuFunctionKey); NSString *menuKeyString = [NSString stringWithCharacters:&menuKeyCode length:1]; NSEvent *menuKeyEvent = [NSEvent keyEventWithType:NSKeyDown location:[event locationInWindow] modifierFlags:([event modifierFlags] & ~(NSCommandKeyMask|NSAlternateKeyMask)) timestamp:[event timestamp] windowNumber:[event windowNumber] context:nil characters:menuKeyString charactersIgnoringModifiers:menuKeyString isARepeat:NO // the keyCode must be an 8-bit value so not to be confounded with the Unicode value. // Judging from Carbon/Events.h 0x7f is unused. keyCode:0x7f]; // qCWarning(PLATFORMTHEME) << "new event:" << QString::fromNSString([menuKeyEvent description]); return menuKeyEvent; } // fall through! default: // any other flag change reenables the menukey emulation. enabled = true; break; } break; } // case NSKeyDown: { // qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "event=" << QString::fromNSString([event description]) // << "key=" << [event keyCode] // << "modifierFlags=" << [event modifierFlags] << "chars=" << QString::fromNSString([event characters]) // << "charsIgnMods=" << QString::fromNSString([event charactersIgnoringModifiers]); // break; // } } // standard event processing return event; } #endif //ADD_MENU_KEY static void warnNoNativeTheme() { // Make sure the warning appears somewhere. qCWarning(PLATFORMTHEME) isn't guaranteed to be of use when we're // not called from a terminal session and it's probably too early to try an alert dialog. // NSLog() will log to system.log, but also to the terminal. if (platformName.contains(QLatin1String("cocoa"))) { NSLog(@"The %s platform theme plugin is being used and the native theme for the %@ platform failed to load.\n" "Applications will function but lack functionality available only through the native theme,\n" "including the menu bar at the top of the screen(s).", PLATFORM_PLUGIN_THEME_NAME, platformName.toNSString()); } else { NSLog(@"The %s platform theme plugin is being used and the native theme for the %@ platform failed to load.\n" "Applications will function but lack functionality available only through the native theme.", PLATFORM_PLUGIN_THEME_NAME, platformName.toNSString()); } } /* ============ How we get here: (lldb) bt * thread #1: tid = 0x2e3a6be, 0x000000010a481454 KDEPlatformTheme.so`KdeMacTheme::KdeMacTheme(this=0x0000000103a3d830) + 4 at kdemactheme.mm:72, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2 * frame #0: 0x000000010a481454 KDEPlatformTheme.so`KdeMacTheme::KdeMacTheme(this=0x0000000103a3d830) + 4 at kdemactheme.mm:72 frame #1: 0x000000010a48686b KDEPlatformTheme.so`CocoaPlatformThemePlugin::create(this=, key=, paramList=) + 27 at main_mac.cpp:53 frame #2: 0x00000001008c85b8 QtGui`QPlatformThemeFactory::create(QString const&, QString const&) [inlined] QPlatformTheme* qLoadPlugin(loader=, key=0x0000000103a406b0, args=0x0000000103a3d710) + 60 at qfactoryloader_p.h:103 frame #3: 0x00000001008c857c QtGui`QPlatformThemeFactory::create(key=, platformPluginPath=) + 396 at qplatformthemefactory.cpp:73 frame #4: 0x00000001008d31bb QtGui`QGuiApplicationPrivate::createPlatformIntegration() [inlined] QLatin1String::QLatin1String(this=0x0000000103b17a00, pluginArgument=0x0000000103b17a00, this=0x0000000103b17a00, platformPluginPath=0x000000010134fa90, s=0x0000000103b19e50, platformThemeName=0x000000010134fa90, argc=, argv=) + 1357 at qguiapplication.cpp:1135 frame #5: 0x00000001008d2c6e QtGui`QGuiApplicationPrivate::createPlatformIntegration(this=0x0000000103c0a6a0) + 1950 at qguiapplication.cpp:1257 frame #6: 0x00000001008d3adb QtGui`QGuiApplicationPrivate::createEventDispatcher(this=) + 27 at qguiapplication.cpp:1274 frame #7: 0x00000001010e0098 QtCore`QCoreApplicationPrivate::init(this=0x0000000103c0a6a0) + 1832 at qcoreapplication.cpp:794 frame #8: 0x00000001008cfce1 QtGui`QGuiApplicationPrivate::init(this=0x0000000103c0a6a0) + 49 at qguiapplication.cpp:1297 frame #9: 0x000000010001e90e QtWidgets`QApplicationPrivate::init(this=0x0000000103c0a6a0) + 14 at qapplication.cpp:583 ============ */ KdeMacTheme::KdeMacTheme() : m_nativeInterface(nullptr) , verbose(qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { if (strcasecmp(QT_VERSION_STR, qVersion())) { NSLog(@"Warning: the %s platform theme plugin for Mac was built against Qt %s but is running with Qt %s!", PLATFORM_PLUGIN_THEME_NAME, QT_VERSION_STR, qVersion()); } // first things first: instruct Qt not to use the Mac-style toplevel menubar // if we are not using the Cocoa QPA plugin (but the XCB QPA instead). platformName = QGuiApplication::platformName(); if (!platformName.contains(QLatin1String("cocoa"))) { QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); QCoreApplication::setAttribute(Qt::AA_MacDontSwapCtrlAndMeta, true); + m_isCocoa = false; + } else { + m_isCocoa = true; } QPlatformIntegration *pi = QGuiApplicationPrivate::platformIntegration(); if (pi) { nativeTheme = pi->createPlatformTheme(platformName); } else { nativeTheme = Q_NULLPTR; } if (!nativeTheme) { warnNoNativeTheme(); } else if (verbose) { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "loading platform theme plugin" << QLatin1String(PLATFORM_PLUGIN_THEME_NAME) << "for platform" << platformName; } m_fontsData = Q_NULLPTR; m_hints = Q_NULLPTR; loadSettings(); m_eventFilter = new KdeMacThemeEventFilter; #ifndef QT_NO_GESTURES qApp->installEventFilter(m_eventFilter); #endif #ifdef ADD_MENU_KEY m_eventFilter->m_keyboardMonitor = 0; @autoreleasepool { // set up a keyboard event monitor m_eventFilter->m_keyboardMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:KdeMacThemeEventFilter::keyboardMonitorMask handler:^(NSEvent* event) { return m_eventFilter->nativeEventHandler(event); }]; } if (m_eventFilter->m_keyboardMonitor) { m_eventFilter->enabled = true; } else { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "Could not create a global keyboard monitor"; } #endif // for some reason our Qt native event filter is apparently never called. qApp->installNativeEventFilter(m_eventFilter->qtNativeFilter); #ifdef USE_PLCRASHREPORTER static PLCrashReporter *crashReporter = nil; if (!crashReporter) { crashReporter = [[PLCrashReporter alloc] initWithConfiguration:[PLCrashReporterConfig defaultConfiguration]]; } NSError *error; if ([crashReporter hasPendingCrashReport]) @autoreleasepool { NSData *crashData; PLCrashReport *report = nil; crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error]; if (crashData) { report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease]; } if (report) { // report could be sent to KAboutData::applicationData().bugAddress() // using QDesktopServices::openUrl("mailto:") qCWarning(PLATFORMTHEME) << qApp->applicationName() << "crashed on" << QString::fromNSString([report.systemInfo.timestamp description]); qCWarning(PLATFORMTHEME) << "\twith signal" << QString::fromNSString(report.signalInfo.name) << "code" << QString::fromNSString(report.signalInfo.code) << "at address" << report.signalInfo.address; } [crashReporter purgePendingCrashReport]; } if (![crashReporter enableCrashReporterAndReturnError: &error]) { NSLog(@"Warning: Could not enable crash reporter: %@", error); } #endif } KdeMacTheme::~KdeMacTheme() { delete nativeTheme; if (m_eventFilter) { qApp->removeNativeEventFilter(m_eventFilter->qtNativeFilter); #ifdef ADD_MENU_KEY m_eventFilter->enabled = false; if (m_eventFilter->m_keyboardMonitor) { @autoreleasepool { [NSEvent removeMonitor:m_eventFilter->m_keyboardMonitor]; } } #endif } delete m_eventFilter; m_eventFilter = 0; } QPlatformMenuItem* KdeMacTheme::createPlatformMenuItem() const { if (nativeTheme) { return nativeTheme->createPlatformMenuItem(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenuItem(); } } QPlatformMenu* KdeMacTheme::createPlatformMenu() const { if (nativeTheme) { return nativeTheme->createPlatformMenu(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenu(); } } QPlatformMenuBar* KdeMacTheme::createPlatformMenuBar() const { if (nativeTheme) { return nativeTheme->createPlatformMenuBar(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenuBar(); } } QVariant KdeMacTheme::themeHint(QPlatformTheme::ThemeHint hintType) const { QVariant hint = m_hints->hint(hintType); if (hint.isValid()) { if (verbose) { qCWarning(PLATFORMTHEME) << "themeHint" << hintType << ":" << hint; } return hint; } else { if (nativeTheme) { if (verbose) { qCWarning(PLATFORMTHEME) << "Using native theme for themeHint" << hintType << ":" << nativeTheme->themeHint(hintType); } return nativeTheme->themeHint(hintType); } return QPlatformTheme::themeHint(hintType); } } const QPalette *KdeMacTheme::palette(Palette type) const { QPalette *palette = m_hints->palette(type); if (palette) { return palette; } else { if (nativeTheme) { return nativeTheme->palette(type); } return QPlatformTheme::palette(type); } } KFontSettingsDataMac::FontTypes KdeMacTheme::fontType(QPlatformTheme::Font type) const { KFontSettingsDataMac::FontTypes ftype; switch (type) { default: ftype = KFontSettingsDataMac::FontTypes(KdePlatformTheme::fontType(type)); break; case MessageBoxFont: ftype = KFontSettingsDataMac::MessageBoxFont; break; } return ftype; } const QFont *KdeMacTheme::font(Font type) const { // when using the platform-default fonts, try returning a bold version of the // standard system font; it's the only one where Qt/OS X really deviates. const QFont *qf = m_fontsData->font(fontType(type)); if (!qf && nativeTheme) { qf = nativeTheme->font(type); // if (qf) { // qCWarning(PLATFORMTHEME) << "native font for type" << type << "=role" << fontType(type) << ":" << *qf; // } else { // qCWarning(PLATFORMTHEME) << "native font for type" << type << "=role" << fontType(type) << ": NULL"; // } } return qf; } void KdeMacTheme::loadSettings() { if (!m_fontsData) { m_fontsData = new KFontSettingsDataMac(this); } if (!m_hints) { m_hints = new KHintsSettingsMac(this); } } QList KdeMacTheme::keyBindings(QKeySequence::StandardKey key) const { // return a native keybinding if we can determine what that is if (nativeTheme) { return nativeTheme->keyBindings(key); } // or else we return whatever KDE applications expect elsewhere return KdePlatformTheme::keyBindings(key); } bool KdeMacTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const { #ifdef KDEMACTHEME_PREFER_NATIVE_DIALOGS if (nativeTheme) { return nativeTheme->usePlatformNativeDialog(type); } #endif #ifndef KDEMACTHEME_NEVER_NATIVE_DIALOGS return type == QPlatformTheme::FileDialog && qobject_cast(QCoreApplication::instance()); #else return false; #endif } QString KdeMacTheme::standardButtonText(int button) const { // assume that button text is a domain where cross-platform application // coherence primes over native platform look and feel. IOW, function over form. // It's impossible to use the parent's method since we use // the nativeTheme in the default case switch (static_cast(button)) { case QPlatformDialogHelper::NoButton: qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "Unsupported standard button:" << button; return QString(); case QPlatformDialogHelper::Ok: return KStandardGuiItem::ok().text(); case QPlatformDialogHelper::Save: return KStandardGuiItem::save().text(); case QPlatformDialogHelper::SaveAll: return i18nc("@action:button", "Save All"); case QPlatformDialogHelper::Open: return KStandardGuiItem::open().text(); case QPlatformDialogHelper::Yes: return KStandardGuiItem::yes().text(); case QPlatformDialogHelper::YesToAll: return i18nc("@action:button", "Yes to All"); case QPlatformDialogHelper::No: return KStandardGuiItem::no().text(); case QPlatformDialogHelper::NoToAll: return i18nc("@action:button", "No to All"); case QPlatformDialogHelper::Abort: // FIXME KStandardGuiItem::stop() doesn't seem right here return i18nc("@action:button", "Abort"); case QPlatformDialogHelper::Retry: return i18nc("@action:button", "Retry"); case QPlatformDialogHelper::Ignore: return i18nc("@action:button", "Ignore"); case QPlatformDialogHelper::Close: return KStandardGuiItem::close().text(); case QPlatformDialogHelper::Cancel: return KStandardGuiItem::cancel().text(); case QPlatformDialogHelper::Discard: return KStandardGuiItem::discard().text(); case QPlatformDialogHelper::Help: return KStandardGuiItem::help().text(); case QPlatformDialogHelper::Apply: return KStandardGuiItem::apply().text(); case QPlatformDialogHelper::Reset: return KStandardGuiItem::reset().text(); case QPlatformDialogHelper::RestoreDefaults: return KStandardGuiItem::defaults().text(); default: if (nativeTheme) { // something not foreseen by Qt/KDE: now see if OS X // has an opinion about the text. return nativeTheme->standardButtonText(button); } return QPlatformTheme::defaultStandardButtonText(button); } } QPlatformDialogHelper *KdeMacTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const { #ifdef KDEMACTHEME_PREFER_NATIVE_DIALOGS - // always prefer native dialogs + // always prefer native dialogs - when using the Cocoa QPA. // NOTE: somehow, the "don't use native dialog" option that Qt's example "standarddialogs" // provides does not modify our usePlatformNativeDialog() return value, but *does* cause // a Qt dialog to be created instead of the native one. Weird. - if (nativeTheme && !qEnvironmentVariableIsSet("PREFER_KDE_DIALOGS")) { + if (nativeTheme && m_isCocoa && !qEnvironmentVariableIsSet("PREFER_KDE_DIALOGS")) { return nativeTheme->createPlatformDialogHelper(type); } #endif QPlatformDialogHelper *helper = KdePlatformTheme::createPlatformDialogHelper(type); if (helper) { return helper; } else { if (nativeTheme) { helper = nativeTheme->createPlatformDialogHelper(type); } return helper ? helper : QPlatformTheme::createPlatformDialogHelper(type); } } QPlatformSystemTrayIcon *KdeMacTheme::createPlatformSystemTrayIcon() const { if (nativeTheme) { return nativeTheme->createPlatformSystemTrayIcon(); } // TODO: figure out if it makes sense to return something other than // nativeTheme->createPlatformSystemTrayIcon() or even NULL return KdePlatformTheme::createPlatformSystemTrayIcon(); } QPlatformNativeInterface *KdeMacTheme::nativeInterface() { if (!m_nativeInterface) { m_nativeInterface = QGuiApplication::platformNativeInterface(); } return m_nativeInterface; } KdeMacTheme::PlatformFunctionPtr KdeMacTheme::platformFunction(const QByteArray &functionName) { if (nativeInterface()) { return m_nativeInterface->nativeResourceFunctionForIntegration(functionName); } return nullptr; } #include "kdemactheme.moc" diff --git a/src/platformtheme/kdeplatformtheme.cpp b/src/platformtheme/kdeplatformtheme.cpp index 1377679..14f0e9e 100644 --- a/src/platformtheme/kdeplatformtheme.cpp +++ b/src/platformtheme/kdeplatformtheme.cpp @@ -1,337 +1,337 @@ /* This file is part of the KDE libraries * Copyright 2013 Kevin Ottens * Copyright 2013 Aleix Pol Gonzalez * Copyright 2014 Lukáš Tinkl * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License or ( at * your option ) version 3 or, at the discretion of KDE e.V. ( which shall * act as a proxy as in section 14 of the GPLv3 ), any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "kdeplatformtheme.h" #include "kfontsettingsdata.h" #include "khintssettings.h" #include "kdeplatformfiledialoghelper.h" #include "kdeplatformsystemtrayicon.h" #include "platformtheme_logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include KdePlatformTheme::KdePlatformTheme() { m_fontsData = Q_NULLPTR; m_hints = Q_NULLPTR; loadSettings(); QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false); setQtQuickControlsTheme(); } KdePlatformTheme::~KdePlatformTheme() { delete m_fontsData; delete m_hints; } QVariant KdePlatformTheme::themeHint(QPlatformTheme::ThemeHint hintType) const { QVariant hint = m_hints->hint(hintType); if (hint.isValid()) { return hint; } else { return QPlatformTheme::themeHint(hintType); } } QIcon KdePlatformTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const { if (iconOptions.testFlag(DontUseCustomDirectoryIcons) && fileInfo.isDir()) { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "icon \"inode-directory\""; return QIcon::fromTheme(QLatin1String("inode-directory")); } - qCWarning(PLATFORMTHEME) << Q_FUNC_INFO + qCDebug(PLATFORMTHEME) << Q_FUNC_INFO << "file:" << fileInfo.absoluteFilePath() << "icon:" << KIO::iconNameForUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); return QIcon::fromTheme(KIO::iconNameForUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath()))); } #if QT_VERSION < QT_VERSION_CHECK(5, 8, 0) QPixmap KdePlatformTheme::fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &size, QPlatformTheme::IconOptions iconOptions) const { return fileIcon(fileInfo, iconOptions).pixmap(size.toSize(), QIcon::Normal); } #endif const QPalette *KdePlatformTheme::palette(Palette type) const { QPalette *palette = m_hints->palette(type); if (palette) { return palette; } else { return QPlatformTheme::palette(type); } } KFontSettingsData::FontTypes KdePlatformTheme::fontType(Font type) const { KFontSettingsData::FontTypes fdtype; switch (type) { case SystemFont: fdtype = KFontSettingsData::GeneralFont; break; case MenuFont: case MenuBarFont: case MenuItemFont: fdtype = KFontSettingsData::MenuFont; break; case MessageBoxFont: case LabelFont: case TipLabelFont: case StatusBarFont: case PushButtonFont: case ItemViewFont: case ListViewFont: case HeaderViewFont: case ListBoxFont: case ComboMenuItemFont: case ComboLineEditFont: fdtype = KFontSettingsData::GeneralFont; break; case TitleBarFont: case MdiSubWindowTitleFont: case DockWidgetTitleFont: fdtype = KFontSettingsData::WindowTitleFont; break; case SmallFont: case MiniFont: fdtype = KFontSettingsData::SmallestReadableFont; break; case FixedFont: fdtype = KFontSettingsData::FixedFont; break; case ToolButtonFont: fdtype = KFontSettingsData::ToolbarFont; break; default: fdtype = KFontSettingsData::GeneralFont; break; } return fdtype; } const QFont *KdePlatformTheme::font(Font type) const { return m_fontsData->font(fontType(type)); } QIconEngine *KdePlatformTheme::createIconEngine(const QString &iconName) const { return new KIconEngine(iconName, KIconLoader::global()); } void KdePlatformTheme::loadSettings() { if (!m_fontsData) { m_fontsData = new KFontSettingsData; m_hints = new KHintsSettings; } } QList KdePlatformTheme::keyBindings(QKeySequence::StandardKey key) const { switch (key) { case QKeySequence::HelpContents: return KStandardShortcut::shortcut(KStandardShortcut::Help); case QKeySequence::WhatsThis: return KStandardShortcut::shortcut(KStandardShortcut::WhatsThis); case QKeySequence::Open: return KStandardShortcut::shortcut(KStandardShortcut::Open); case QKeySequence::Close: return KStandardShortcut::shortcut(KStandardShortcut::Close); case QKeySequence::Save: return KStandardShortcut::shortcut(KStandardShortcut::Save); case QKeySequence::New: return KStandardShortcut::shortcut(KStandardShortcut::New); case QKeySequence::Cut: return KStandardShortcut::shortcut(KStandardShortcut::Cut); case QKeySequence::Copy: return KStandardShortcut::shortcut(KStandardShortcut::Copy); case QKeySequence::Paste: return KStandardShortcut::shortcut(KStandardShortcut::Paste); case QKeySequence::Undo: return KStandardShortcut::shortcut(KStandardShortcut::Undo); case QKeySequence::Redo: return KStandardShortcut::shortcut(KStandardShortcut::Redo); case QKeySequence::Back: return KStandardShortcut::shortcut(KStandardShortcut::Back); case QKeySequence::Forward: return KStandardShortcut::shortcut(KStandardShortcut::Forward); case QKeySequence::Refresh: return KStandardShortcut::shortcut(KStandardShortcut::Reload); case QKeySequence::ZoomIn: return KStandardShortcut::shortcut(KStandardShortcut::ZoomIn); case QKeySequence::ZoomOut: return KStandardShortcut::shortcut(KStandardShortcut::ZoomOut); case QKeySequence::Print: return KStandardShortcut::shortcut(KStandardShortcut::Print); case QKeySequence::Find: return KStandardShortcut::shortcut(KStandardShortcut::Find); case QKeySequence::FindNext: return KStandardShortcut::shortcut(KStandardShortcut::FindNext); case QKeySequence::FindPrevious: return KStandardShortcut::shortcut(KStandardShortcut::FindPrev); case QKeySequence::Replace: return KStandardShortcut::shortcut(KStandardShortcut::Replace); case QKeySequence::SelectAll: return KStandardShortcut::shortcut(KStandardShortcut::SelectAll); case QKeySequence::MoveToNextWord: return KStandardShortcut::shortcut(KStandardShortcut::ForwardWord); case QKeySequence::MoveToPreviousWord: return KStandardShortcut::shortcut(KStandardShortcut::BackwardWord); case QKeySequence::MoveToNextPage: return KStandardShortcut::shortcut(KStandardShortcut::Next); case QKeySequence::MoveToPreviousPage: return KStandardShortcut::shortcut(KStandardShortcut::Prior); case QKeySequence::MoveToStartOfLine: return KStandardShortcut::shortcut(KStandardShortcut::BeginningOfLine); case QKeySequence::MoveToEndOfLine: return KStandardShortcut::shortcut(KStandardShortcut::EndOfLine); case QKeySequence::MoveToStartOfDocument: return KStandardShortcut::shortcut(KStandardShortcut::Begin); case QKeySequence::MoveToEndOfDocument: return KStandardShortcut::shortcut(KStandardShortcut::End); case QKeySequence::SaveAs: return KStandardShortcut::shortcut(KStandardShortcut::SaveAs); case QKeySequence::Preferences: return KStandardShortcut::shortcut(KStandardShortcut::Preferences); case QKeySequence::Quit: return KStandardShortcut::shortcut(KStandardShortcut::Quit); case QKeySequence::FullScreen: return KStandardShortcut::shortcut(KStandardShortcut::FullScreen); case QKeySequence::Deselect: return KStandardShortcut::shortcut(KStandardShortcut::Deselect); case QKeySequence::DeleteStartOfWord: return KStandardShortcut::shortcut(KStandardShortcut::DeleteWordBack); case QKeySequence::DeleteEndOfWord: return KStandardShortcut::shortcut(KStandardShortcut::DeleteWordForward); case QKeySequence::NextChild: return KStandardShortcut::shortcut(KStandardShortcut::TabNext); case QKeySequence::PreviousChild: return KStandardShortcut::shortcut(KStandardShortcut::TabPrev); default: return QPlatformTheme::keyBindings(key); } } bool KdePlatformTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const { return type == QPlatformTheme::FileDialog; } QString KdePlatformTheme::standardButtonText(int button) const { switch (static_cast(button)) { case QPlatformDialogHelper::NoButton: qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "Unsupported standard button:" << button; return QString(); case QPlatformDialogHelper::Ok: return KStandardGuiItem::ok().text(); case QPlatformDialogHelper::Save: return KStandardGuiItem::save().text(); case QPlatformDialogHelper::SaveAll: return i18nc("@action:button", "Save All"); case QPlatformDialogHelper::Open: return KStandardGuiItem::open().text(); case QPlatformDialogHelper::Yes: return KStandardGuiItem::yes().text(); case QPlatformDialogHelper::YesToAll: return i18nc("@action:button", "Yes to All"); case QPlatformDialogHelper::No: return KStandardGuiItem::no().text(); case QPlatformDialogHelper::NoToAll: return i18nc("@action:button", "No to All"); case QPlatformDialogHelper::Abort: // FIXME KStandardGuiItem::stop() doesn't seem right here return i18nc("@action:button", "Abort"); case QPlatformDialogHelper::Retry: return i18nc("@action:button", "Retry"); case QPlatformDialogHelper::Ignore: return i18nc("@action:button", "Ignore"); case QPlatformDialogHelper::Close: return KStandardGuiItem::close().text(); case QPlatformDialogHelper::Cancel: return KStandardGuiItem::cancel().text(); case QPlatformDialogHelper::Discard: return KStandardGuiItem::discard().text(); case QPlatformDialogHelper::Help: return KStandardGuiItem::help().text(); case QPlatformDialogHelper::Apply: return KStandardGuiItem::apply().text(); case QPlatformDialogHelper::Reset: return KStandardGuiItem::reset().text(); case QPlatformDialogHelper::RestoreDefaults: return KStandardGuiItem::defaults().text(); default: return QPlatformTheme::defaultStandardButtonText(button); } } QPlatformDialogHelper *KdePlatformTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const { switch (type) { case QPlatformTheme::FileDialog: return new KDEPlatformFileDialogHelper; case QPlatformTheme::FontDialog: case QPlatformTheme::ColorDialog: case QPlatformTheme::MessageDialog: default: return 0; } } QPlatformSystemTrayIcon *KdePlatformTheme::createPlatformSystemTrayIcon() const { return new KDEPlatformSystemTrayIcon; } //force QtQuickControls2 to use the desktop theme as default void KdePlatformTheme::setQtQuickControlsTheme() { //if the user is running only a QGuiApplication. Abort as this style is all about QWidgets and we know setting this will make it crash if (!qobject_cast(qApp)) { if (qgetenv("QT_QUICK_CONTROLS_1_STYLE").right(7) == "Desktop") { qunsetenv("QT_QUICK_CONTROLS_1_STYLE"); } return; } //if the user has explicitly set something else, don't meddle // Do this after potentially unsetting QT_QUICK_CONTROLS_1_STYLE! if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE")) { return; } // newer plasma-integration versions use QtQuickControls2 for this // I don't want that extra dependency. qputenv("QT_QUICK_CONTROLS_STYLE", "org.kde.desktop"); }