diff --git a/CMakeLists.txt b/CMakeLists.txt index 47708ff..6158082 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,121 +1,146 @@ cmake_minimum_required(VERSION 3.0) project(OSXIntegration) option(BUILD_KDE_THEME_PLUGIN "Should the KDE theme plugin be built?" ON) option(BUILD_QT_PLUGINS - "Should the Qt QPA and style plugins be built (they don't depend on KDE but become optional when the KDE theme plugin is built)" + "Should the Qt Cocoa QPA and Aqua style plugins be built (they don't depend on KDE but become optional when the KDE theme plugin is built)" ON) option(DEFINE_ICONTHEME_SETTINGS "Should the theme plugin define a standard theme and add the standard locations for icon themes to the search path?" OFF) option(PREFER_NATIVE_DIALOGS "Should native dialogs be preferred?" ON) option(NEVER_NATIVE_DIALOGS "Should native dialogs never be used (when not already preferred)?" OFF) option(OVERRIDE_NATIVE_THEME - "Should the platform theme plugin replace the native theme? If OFF it installs as the \"kde\" theme." + "Should the platform theme plugin override the native theme? If ON it installs as the \"Cocoa\" instead of as the \"kde\" theme." OFF) option(DISABLE_DBUS_SUPPORT "Don't build the D-Bus functionality. Experimental!" OFF) option(EMULATE_MENU_KEY "Emulate the presence of a Menu key as on PC keyboards when the right Command+Option keys are pressed. BUILD_QT_PLUGINS must be set for this to have any effect." OFF) +option(HAVE_INFINALITY + "The Infinality+Ultimate patch set has been applied to FreeType and FontConfig. + (the FontConfig patches are determining). See https://github.com/archfan/infinality_bundle" + OFF) option(USE_PLCRASHREPORTER "Use the Plausible CrashReporter (https://www.plcrashreporter.org)" OFF) include(FeatureSummary) +add_feature_info("KDE platform theme plugin" BUILD_KDE_THEME_PLUGIN "") +add_feature_info("Build the Qt plugins" BUILD_QT_PLUGINS + "Should the Qt Cocoa QPA and Aqua style plugins be built") +add_feature_info("Override the Cocoa platform" OVERRIDE_NATIVE_THEME + "Install the platform theme plugin so it overrides the native \"Cocoa\" platform") +add_feature_info("Icon theme settings" DEFINE_ICONTHEME_SETTINGS + "Should the theme plugin define a standard theme and add the standard locations for icon themes to the search path?") +add_feature_info("Prefer native dialogs" PREFER_NATIVE_DIALOGS "") +add_feature_info("Never use native dialogs" NEVER_NATIVE_DIALOGS "") +add_feature_info("No DBus" DISABLE_DBUS_SUPPORT + "Don't build the D-Bus functionality. Experimental!") +add_feature_info("Emulate a Menu key" EMULATE_MENU_KEY + "Emulate the presence of a Menu key as on PC keyboards when the right Command+Option keys are pressed.") +add_feature_info("Build for use with the Infinality+Ultimate patch-set" HAVE_INFINALITY "") + find_package(ECM 5.20.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") # feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMQtDeclareLoggingCategory) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) # for the optional Qt QPA and style plugins # (note that they have not yet been tested against Qt 5.7) set(QT_MIN_VERSION 5.7.0) # minimum required version: if(BUILD_KDE_THEME_PLUGIN) # require the minimum version for the KF5 theme plugin and # only build the Qt plugins if the install is recent enough set(REQUIRED_QT_VERSION 5.5.0) else() # when we only build the Qt plugins we require their minimum version set(REQUIRED_QT_VERSION ${QT_MIN_VERSION}) endif() include(FindQt5Components) execute_process(COMMAND cmake/git_version.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE KF5_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) message(STATUS "KF5_VERSION=${KF5_VERSION}") if(BUILD_KDE_THEME_PLUGIN) # 20170119: v5.30.0 is synced with plasma-integration 5.8.95 set(KF5_DEP_VERSION "5.33.0") # handled by release scripts ecm_setup_version(${KF5_VERSION} VARIABLE_PREFIX OSXINTEGRATION VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/osxintegration_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5OSXIntegrationConfigVersion.cmake" SOVERSION 5) find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Config ConfigWidgets I18n IconThemes KIO Notifications WidgetsAddons) if (NOT DISABLE_DBUS_SUPPORT) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus) add_definitions(-DDBUS_SUPPORT_ENABLED) endif() add_definitions(-DTRANSLATION_DOMAIN=\"osxintegration5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() endif() add_subdirectory(src) if(BUILD_KDE_THEME_PLUGIN) add_subdirectory(autotests) # add_subdirectory(tests) # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5OSXIntegration") configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5OSXIntegrationConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5OSXIntegrationConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5OSXIntegrationConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5OSXIntegrationConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/osxintegration_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) install(FILES macosx_workspace.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR}) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) +if(CMAKE_OSX_DEPLOYMENT_TARGET) + message(STATUS "OS X Deployment target: ${CMAKE_OSX_DEPLOYMENT_TARGET}") +endif() +if(CMAKE_OSX_SYSROOT) + message(STATUS "OS X SDK sysroot: ${CMAKE_OSX_SYSROOT}") +endif() diff --git a/README.md b/README.md index 1933a9d..7b580bb 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,92 @@ # 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 + CocoaPlatformTheme or KDEPlatformTheme). +- 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. +- 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. +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). There's also support +for activating the FontConfig fontengine/database (QT_MAC_USE_FONTCONFIG) but +this requires a patched QtBase configured to use FontConfig. Both use a font +gamma setting that determines font darkness and can be set via the env. var +QT_MAC_FREETYPE_FONT_GAMMA . 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. + +* CMake options for QCocoaQPA : +- HAVE_INFINALITY : should be enabled when you have the Infinality+Ultimate patch-set + applied to FreeType *and* FontConfig. Without this option fonts will probably look + washed out. diff --git a/qtplugins.pro b/qtplugins.pro index 25cf7f4..4571366 100644 --- a/qtplugins.pro +++ b/qtplugins.pro @@ -1,4 +1,9 @@ TEMPLATE = subdirs +contains(QT_VERSION, ^5\\.[0-7]\\..*) { + message("Need at least Qt 5.8.0 to build QAltCocoa and QAltMacStyle") + error("Qt 5.8.0 or higher is required") +} + SUBDIRS += src/qcocoa-qpa/qcocoa-standalone.pro \ src/qmacstyle/macstyle.pro diff --git a/src/platformtheme/cocoaplatformtheme.json b/src/platformtheme/cocoaplatformtheme.json index 520c4f5..ef2a3b1 100644 --- a/src/platformtheme/cocoaplatformtheme.json +++ b/src/platformtheme/cocoaplatformtheme.json @@ -1,3 +1,11 @@ { - "Keys": [ "cocoa" ] + "Keys": [ "cocoa" ], + "Name": "Kokoa", + "Copyright": "(C) 2015-17 René J.V. Bertin", + "Description": [ + "KDE platform integration plugin for Mac OS X/macOS", + "Based on the Plasma Integration plugin.", + "This version replaces and proxies the original Qt Cocoa and/or RJVB's QAltCocoa QPA plugins" + ], + "Url": [ "https://github.com/RJVB/osx-integration" ] } diff --git a/src/platformtheme/config-platformtheme.h.cmake b/src/platformtheme/config-platformtheme.h.cmake index aee7975..f302837 100644 --- a/src/platformtheme/config-platformtheme.h.cmake +++ b/src/platformtheme/config-platformtheme.h.cmake @@ -1,4 +1,5 @@ #cmakedefine01 HAVE_X11 #define BREEZE_STYLE_NAME "${BREEZE_STYLE_NAME}" #define PLATFORM_PLUGIN_THEME_NAME "${PLATFORM_PLUGIN_THEME_NAME}" +#define PLATFORM_PLUGIN_FILE_NAME "${PLUGIN_FILENAME}" diff --git a/src/platformtheme/kdemactheme.h b/src/platformtheme/kdemactheme.h index 08fafa4..dfea02d 100644 --- a/src/platformtheme/kdemactheme.h +++ b/src/platformtheme/kdemactheme.h @@ -1,73 +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 98d9195..6e213b0 100644 --- a/src/platformtheme/kdemactheme.mm +++ b/src/platformtheme/kdemactheme.mm @@ -1,717 +1,756 @@ /* 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(); + QString platformThemeName; if (!platformName.contains(QLatin1String("cocoa"))) { QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); QCoreApplication::setAttribute(Qt::AA_MacDontSwapCtrlAndMeta, true); + m_isCocoa = false; + // we will almost certainly be using the xcb QPA ("X11"). We'll proxy + // the generic Unix theme, *not* the KDE theme. That'd be redundant. + platformThemeName = QStringLiteral("generic"); + } else { + m_isCocoa = true; + platformThemeName = platformName; } QPlatformIntegration *pi = QGuiApplicationPrivate::platformIntegration(); if (pi) { - nativeTheme = pi->createPlatformTheme(platformName); + nativeTheme = pi->createPlatformTheme(platformThemeName); } else { nativeTheme = Q_NULLPTR; } if (!nativeTheme) { warnNoNativeTheme(); - } else if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + } 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; + m_fontsData = new KFontSettingsDataMac(this); } if (!m_hints) { - m_hints = new KHintsSettingsMac; + 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") || qEnvironmentVariableIsEmpty("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(); + const auto systray = nativeTheme->createPlatformSystemTrayIcon(); + if (!m_isCocoa && verbose) { + qCWarning(PLATFORMTHEME) << "Created native systray icon" << systray << "for platform" << platformName; + } + return systray; } // 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/kdeplatformfiledialoghelper.cpp b/src/platformtheme/kdeplatformfiledialoghelper.cpp index 21fe7ff..4c924dc 100644 --- a/src/platformtheme/kdeplatformfiledialoghelper.cpp +++ b/src/platformtheme/kdeplatformfiledialoghelper.cpp @@ -1,440 +1,452 @@ /* This file is part of the KDE libraries * Copyright 2013 Aleix Pol Gonzalez * Copyright 2014 Martin Klapetek * * 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 "kdeplatformfiledialoghelper.h" #include "kdeplatformfiledialogbase_p.h" #include "kdirselectdialog_p.h" +#include "platformtheme_logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { /* * Map a Qt filter string into a KDE one. */ static QString qt2KdeFilter(const QStringList &f) { QString filter; QTextStream str(&filter, QIODevice::WriteOnly); QStringList list(f); list.replaceInStrings(QStringLiteral("/"), QStringLiteral("\\/")); QStringList::const_iterator it(list.constBegin()), end(list.constEnd()); bool first = true; for (; it != end; ++it) { int ob = it->lastIndexOf(QLatin1Char('(')), cb = it->lastIndexOf(QLatin1Char(')')); if (-1 != cb && ob < cb) { if (first) { first = false; } else { str << '\n'; } str << it->mid(ob + 1, (cb - ob) - 1) << '|' << it->mid(0, ob); } } return filter; } /* * Map a KDE filter string into a Qt one. */ static QString kde2QtFilter(const QStringList &list, const QString &kde) { QStringList::const_iterator it(list.constBegin()), end(list.constEnd()); int pos; for (; it != end; ++it) { if (-1 != (pos = it->indexOf(kde)) && pos > 0 && (QLatin1Char('(') == (*it)[pos - 1] || QLatin1Char(' ') == (*it)[pos - 1]) && it->length() >= kde.length() + pos && (QLatin1Char(')') == (*it)[pos + kde.length()] || QLatin1Char(' ') == (*it)[pos + kde.length()])) { return *it; } } return QString(); } } KDEPlatformFileDialog::KDEPlatformFileDialog() : KDEPlatformFileDialogBase() , m_fileWidget(new KFileWidget(QUrl(), this)) { setLayout(new QVBoxLayout); connect(m_fileWidget, SIGNAL(filterChanged(QString)), SIGNAL(filterSelected(QString))); layout()->addWidget(m_fileWidget); m_buttons = new QDialogButtonBox(this); m_buttons->addButton(m_fileWidget->okButton(), QDialogButtonBox::AcceptRole); m_buttons->addButton(m_fileWidget->cancelButton(), QDialogButtonBox::RejectRole); connect(m_buttons, SIGNAL(rejected()), m_fileWidget, SLOT(slotCancel())); connect(m_fileWidget->okButton(), SIGNAL(clicked(bool)), m_fileWidget, SLOT(slotOk())); connect(m_fileWidget, SIGNAL(accepted()), m_fileWidget, SLOT(accept())); connect(m_fileWidget, SIGNAL(accepted()), SLOT(accept())); connect(m_fileWidget->cancelButton(), SIGNAL(clicked(bool)), SLOT(reject())); layout()->addWidget(m_buttons); } +KDEPlatformFileDialog::~KDEPlatformFileDialog() +{ + delete m_fileWidget; + delete m_buttons; +} + QUrl KDEPlatformFileDialog::directory() { return m_fileWidget->baseUrl(); } QList KDEPlatformFileDialog::selectedFiles() { return m_fileWidget->selectedUrls(); } void KDEPlatformFileDialog::selectFile(const QUrl &filename) { QUrl dirUrl = filename.adjusted(QUrl::RemoveFilename); m_fileWidget->setUrl(dirUrl); m_fileWidget->setSelectedUrl(filename); } void KDEPlatformFileDialog::setViewMode(QFileDialogOptions::ViewMode view) { switch (view) { case QFileDialogOptions::ViewMode::Detail: m_fileWidget->setViewMode(KFile::FileView::Detail); break; case QFileDialogOptions::ViewMode::List: m_fileWidget->setViewMode(KFile::FileView::Simple); break; default: m_fileWidget->setViewMode(KFile::FileView::Default); break; } } void KDEPlatformFileDialog::setFileMode(QFileDialogOptions::FileMode mode) { switch (mode) { case QFileDialogOptions::FileMode::AnyFile: m_fileWidget->setMode(KFile::File); break; case QFileDialogOptions::FileMode::ExistingFile: m_fileWidget->setMode(KFile::Mode::File | KFile::Mode::ExistingOnly); break; case QFileDialogOptions::FileMode::Directory: m_fileWidget->setMode(KFile::Mode::Directory | KFile::Mode::ExistingOnly); break; case QFileDialogOptions::FileMode::ExistingFiles: m_fileWidget->setMode(KFile::Mode::Files | KFile::Mode::ExistingOnly); break; default: m_fileWidget->setMode(KFile::File); break; } } void KDEPlatformFileDialog::setCustomLabel(QFileDialogOptions::DialogLabel label, const QString &text) { if (label == QFileDialogOptions::Accept) { // OK button m_fileWidget->okButton()->setText(text); } else if (label == QFileDialogOptions::Reject) { // Cancel button m_fileWidget->cancelButton()->setText(text); } else if (label == QFileDialogOptions::LookIn) { // Location label m_fileWidget->setLocationLabel(text); } } QString KDEPlatformFileDialog::selectedMimeTypeFilter() { if (m_fileWidget->filterWidget()->isMimeFilter()) { const auto mimeTypeFromFilter = QMimeDatabase().mimeTypeForName(m_fileWidget->filterWidget()->currentFilter()); // If one does not call selectMimeTypeFilter(), KFileFilterCombo::currentFilter() returns invalid mimeTypes, // such as "application/json application/zip". if (mimeTypeFromFilter.isValid()) { return mimeTypeFromFilter.name(); } } if (selectedFiles().isEmpty()) { return QString(); } // Works for both KFile::File and KFile::Files modes. return QMimeDatabase().mimeTypeForUrl(selectedFiles().at(0)).name(); } QString KDEPlatformFileDialog::selectedNameFilter() { return m_fileWidget->filterWidget()->currentFilter(); } void KDEPlatformFileDialog::selectMimeTypeFilter(const QString &filter) { m_fileWidget->filterWidget()->setCurrentFilter(filter); } void KDEPlatformFileDialog::selectNameFilter(const QString &filter) { m_fileWidget->filterWidget()->setCurrentFilter(filter); } void KDEPlatformFileDialog::setDirectory(const QUrl &directory) { if (!directory.isLocalFile()) { // Qt can not determine if the remote URL points to a file or a // directory, that is why options()->initialDirectory() always returns // the full URL. KIO::StatJob *job = KIO::stat(directory); KJobWidgets::setWindow(job, this); if (job->exec()) { KIO::UDSEntry entry = job->statResult(); if (!entry.isDir()) { // this is probably a file remove the file part m_fileWidget->setUrl(directory.adjusted(QUrl::RemoveFilename)); m_fileWidget->setSelectedUrl(directory); } else { m_fileWidget->setUrl(directory); } } } else { m_fileWidget->setUrl(directory); } } bool KDEPlatformFileDialogHelper::isSupportedUrl(const QUrl& url) const { return KProtocolInfo::protocols().contains(url.scheme()); } //////////////////////////////////////////////// KDEPlatformFileDialogHelper::KDEPlatformFileDialogHelper() : QPlatformFileDialogHelper() , m_dialog(new KDEPlatformFileDialog) { connect(m_dialog, SIGNAL(closed()), SLOT(saveSize())); connect(m_dialog, SIGNAL(finished(int)), SLOT(saveSize())); connect(m_dialog, SIGNAL(currentChanged(QUrl)), SIGNAL(currentChanged(QUrl))); connect(m_dialog, SIGNAL(directoryEntered(QUrl)), SIGNAL(directoryEntered(QUrl))); connect(m_dialog, SIGNAL(fileSelected(QUrl)), SIGNAL(fileSelected(QUrl))); connect(m_dialog, SIGNAL(filesSelected(QList)), SIGNAL(filesSelected(QList))); connect(m_dialog, SIGNAL(filterSelected(QString)), SIGNAL(filterSelected(QString))); connect(m_dialog, SIGNAL(accepted()), SIGNAL(accept())); connect(m_dialog, SIGNAL(rejected()), SIGNAL(reject())); } KDEPlatformFileDialogHelper::~KDEPlatformFileDialogHelper() { saveSize(); delete m_dialog; } void KDEPlatformFileDialogHelper::initializeDialog() { if (options()->testOption(QFileDialogOptions::ShowDirsOnly)) { m_dialog->deleteLater(); m_dialog = new KDirSelectDialog(options()->initialDirectory()); connect(m_dialog, SIGNAL(accepted()), SIGNAL(accept())); connect(m_dialog, SIGNAL(rejected()), SIGNAL(reject())); if (!options()->windowTitle().isEmpty()) m_dialog->setWindowTitle(options()->windowTitle()); } else { // needed for accessing m_fileWidget KDEPlatformFileDialog *dialog = qobject_cast(m_dialog); dialog->m_fileWidget->setOperationMode(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? KFileWidget::Opening : KFileWidget::Saving); if (options()->windowTitle().isEmpty()) { dialog->setWindowTitle(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? i18nc("@title:window", "Open File") : i18nc("@title:window", "Save File")); } else { dialog->setWindowTitle(options()->windowTitle()); } setDirectory(options()->initialDirectory()); //dialog->setViewMode(options()->viewMode()); // don't override our options, fixes remembering the chosen view mode and sizes! dialog->setFileMode(options()->fileMode()); // custom labels if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) { // OK button dialog->setCustomLabel(QFileDialogOptions::Accept, options()->labelText(QFileDialogOptions::Accept)); } else if (options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) { // Cancel button dialog->setCustomLabel(QFileDialogOptions::Reject, options()->labelText(QFileDialogOptions::Reject)); } else if (options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) { // Location label dialog->setCustomLabel(QFileDialogOptions::LookIn, options()->labelText(QFileDialogOptions::LookIn)); } const QStringList mimeFilters = options()->mimeTypeFilters(); const QStringList nameFilters = options()->nameFilters(); if (!mimeFilters.isEmpty()) { QString defaultMimeFilter; if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) defaultMimeFilter = options()->initiallySelectedMimeTypeFilter(); #endif if (defaultMimeFilter.isEmpty()) { defaultMimeFilter = mimeFilters.at(0); } } dialog->m_fileWidget->setMimeFilter(mimeFilters, defaultMimeFilter); if ( mimeFilters.contains( QStringLiteral("inode/directory") ) ) dialog->m_fileWidget->setMode( dialog->m_fileWidget->mode() | KFile::Directory ); } else if (!nameFilters.isEmpty()) { dialog->m_fileWidget->setFilter(qt2KdeFilter(nameFilters)); } #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) if (!options()->initiallySelectedMimeTypeFilter().isEmpty()) { selectMimeTypeFilter(options()->initiallySelectedMimeTypeFilter()); } else if (!options()->initiallySelectedNameFilter().isEmpty()) { selectNameFilter(options()->initiallySelectedNameFilter()); } #else if (!options()->initiallySelectedNameFilter().isEmpty()) { selectNameFilter(options()->initiallySelectedNameFilter()); } #endif // overwrite option // TODO: figure out how to avoid a native "OK to overwrite" request followed by one from KDE (mod in KIO??) if (options()->testOption(QFileDialogOptions::FileDialogOption::DontConfirmOverwrite)) { dialog->m_fileWidget->setConfirmOverwrite(false); } else if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { dialog->m_fileWidget->setConfirmOverwrite(true); } } } void KDEPlatformFileDialogHelper::exec() { restoreSize(); // KDEPlatformFileDialog::show() will always be called during QFileDialog::exec() // discard the delayed show() it and use exec() and it will call show() for us. // We can't hide and show it here because of https://bugreports.qt.io/browse/QTBUG-48248 m_dialog->discardDelayedShow(); m_dialog->exec(); } void KDEPlatformFileDialogHelper::hide() { m_dialog->discardDelayedShow(); m_dialog->hide(); } void KDEPlatformFileDialogHelper::saveSize() { KSharedConfig::Ptr conf = KSharedConfig::openConfig(); KConfigGroup group = conf->group("FileDialogSize"); KWindowConfig::saveWindowSize(m_dialog->windowHandle(), group); } void KDEPlatformFileDialogHelper::restoreSize() { m_dialog->winId(); // ensure there's a window created KSharedConfig::Ptr conf = KSharedConfig::openConfig(); KWindowConfig::restoreWindowSize(m_dialog->windowHandle(), conf->group("FileDialogSize")); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA m_dialog->resize(m_dialog->windowHandle()->size()); } bool KDEPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) { initializeDialog(); m_dialog->setWindowFlags(windowFlags); m_dialog->setWindowModality(windowModality); restoreSize(); m_dialog->windowHandle()->setTransientParent(parent); // Use a delayed show here to delay show() after the internal Qt invisible QDialog. // The delayed call shouldn't matter, because for other "real" native QPlatformDialog // implementation like Mac and Windows, the native dialog is not necessarily // show up immediately. m_dialog->delayedShow(); return true; } QList KDEPlatformFileDialogHelper::selectedFiles() const { return m_dialog->selectedFiles(); } #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) QString KDEPlatformFileDialogHelper::selectedMimeTypeFilter() const { return m_dialog->selectedMimeTypeFilter(); } void KDEPlatformFileDialogHelper::selectMimeTypeFilter(const QString &filter) { m_dialog->selectMimeTypeFilter(filter); } #endif QString KDEPlatformFileDialogHelper::selectedNameFilter() const { return kde2QtFilter(options()->nameFilters(), m_dialog->selectedNameFilter()); } QUrl KDEPlatformFileDialogHelper::directory() const { return m_dialog->directory(); } void KDEPlatformFileDialogHelper::selectFile(const QUrl &filename) { m_dialog->selectFile(filename); + + // Qt 5 at least <= 5.8.0 does not derive the directory from the passed url + // and set the initialDirectory option accordingly, also not for known schemes + // like file://, so we have to do it ourselves + options()->setInitialDirectory(m_dialog->directory()); } void KDEPlatformFileDialogHelper::setDirectory(const QUrl &directory) { if (!directory.isEmpty()) { m_dialog->setDirectory(directory); } } void KDEPlatformFileDialogHelper::selectNameFilter(const QString &filter) { m_dialog->selectNameFilter(qt2KdeFilter(QStringList(filter))); } void KDEPlatformFileDialogHelper::setFilter() { } bool KDEPlatformFileDialogHelper::defaultNameFilterDisables() const { return false; } diff --git a/src/platformtheme/kdeplatformfiledialoghelper.h b/src/platformtheme/kdeplatformfiledialoghelper.h index 24cf561..c5420bb 100644 --- a/src/platformtheme/kdeplatformfiledialoghelper.h +++ b/src/platformtheme/kdeplatformfiledialoghelper.h @@ -1,87 +1,88 @@ /* This file is part of the KDE libraries * Copyright 2013 Aleix Pol Gonzalez * * 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 KDEPLATFORMFILEDIALOGHELPER_H #define KDEPLATFORMFILEDIALOGHELPER_H #include #include "kdeplatformfiledialogbase_p.h" class KFileWidget; class QDialogButtonBox; class KDEPlatformFileDialog : public KDEPlatformFileDialogBase { Q_OBJECT public: friend class KDEPlatformFileDialogHelper; explicit KDEPlatformFileDialog(); + virtual ~KDEPlatformFileDialog(); QUrl directory() override; void selectMimeTypeFilter(const QString &filter) override; void selectNameFilter(const QString &filter) override; void setDirectory(const QUrl &directory) override; void selectFile(const QUrl &filename) override; void setViewMode(QFileDialogOptions::ViewMode view); void setFileMode(QFileDialogOptions::FileMode mode); void setCustomLabel(QFileDialogOptions::DialogLabel label, const QString & text); QString selectedMimeTypeFilter() override; QString selectedNameFilter() override; QList selectedFiles() override; protected: KFileWidget *m_fileWidget; }; class KDEPlatformFileDialogHelper : public QPlatformFileDialogHelper { Q_OBJECT public: KDEPlatformFileDialogHelper(); virtual ~KDEPlatformFileDialogHelper(); void initializeDialog(); bool defaultNameFilterDisables() const override; QUrl directory() const override; QList selectedFiles() const override; #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) QString selectedMimeTypeFilter() const override; void selectMimeTypeFilter(const QString &filter) override; #endif QString selectedNameFilter() const override; void selectNameFilter(const QString &filter) override; void selectFile(const QUrl &filename) override; void setFilter() override; void setDirectory(const QUrl &directory) override; bool isSupportedUrl(const QUrl& url) const override; void exec() override; void hide() override; bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; private Q_SLOTS: void saveSize(); private: void restoreSize(); KDEPlatformFileDialogBase *m_dialog; }; #endif // KDEPLATFORMFILEDIALOGHELPER_H diff --git a/src/platformtheme/kdeplatformsystemtrayicon.cpp b/src/platformtheme/kdeplatformsystemtrayicon.cpp index 3c0b215..ca670af 100644 --- a/src/platformtheme/kdeplatformsystemtrayicon.cpp +++ b/src/platformtheme/kdeplatformsystemtrayicon.cpp @@ -1,359 +1,382 @@ /* This file is part of the KDE libraries - * Copyright 2014 Martin Gräßlin + * Copyright 2014 Martin Gräßlin * * 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 "kdeplatformsystemtrayicon.h" #include #include #include #include #include #include #ifdef DBUS_SUPPORT_ENABLED #include #endif +#include SystemTrayMenu::SystemTrayMenu() : QPlatformMenu() , m_tag(0) , m_menu(new QMenu()) { connect(m_menu.data(), &QMenu::aboutToShow, this, &QPlatformMenu::aboutToShow); connect(m_menu.data(), &QMenu::aboutToHide, this, &QPlatformMenu::aboutToHide); } SystemTrayMenu::~SystemTrayMenu() { if (m_menu) { m_menu->deleteLater(); } } QPlatformMenuItem *SystemTrayMenu::createMenuItem() const { return new SystemTrayMenuItem(); } +QPlatformMenu *SystemTrayMenu::createSubMenu() const +{ + return new SystemTrayMenu(); +} + void SystemTrayMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) { if (SystemTrayMenuItem *ours = qobject_cast(menuItem)) { bool inserted = false; if (SystemTrayMenuItem *oursBefore = qobject_cast(before)) { for (auto it = m_items.begin(); it != m_items.end(); ++it) { if (*it == oursBefore) { m_items.insert(it, ours); if (m_menu) { m_menu->insertAction(oursBefore->action(), ours->action()); } inserted = true; break; } } } if (!inserted) { m_items.append(ours); if (m_menu) { m_menu->addAction(ours->action()); } } } } QPlatformMenuItem *SystemTrayMenu::menuItemAt(int position) const { if (position < m_items.size()) { return m_items.at(position); } return Q_NULLPTR; } QPlatformMenuItem *SystemTrayMenu::menuItemForTag(quintptr tag) const { auto it = std::find_if(m_items.constBegin(), m_items.constEnd(), [tag](SystemTrayMenuItem *item) { return item->tag() == tag; }); if (it != m_items.constEnd()) { return *it; } return Q_NULLPTR; } void SystemTrayMenu::removeMenuItem(QPlatformMenuItem *menuItem) { if (SystemTrayMenuItem *ours = qobject_cast(menuItem)) { m_items.removeOne(ours); if (ours->action() && m_menu) { m_menu->removeAction(ours->action()); } } } void SystemTrayMenu::setEnabled(bool enabled) { if (!m_menu) { return; } m_menu->setEnabled(enabled); } void SystemTrayMenu::setIcon(const QIcon &icon) { if (!m_menu) { return; } m_menu->setIcon(icon); } void SystemTrayMenu::setTag(quintptr tag) { m_tag = tag; } void SystemTrayMenu::setText(const QString &text) { if (!m_menu) { return; } m_menu->setTitle(text); } void SystemTrayMenu::setVisible(bool visible) { if (!m_menu) { return; } m_menu->setVisible(visible); } void SystemTrayMenu::syncMenuItem(QPlatformMenuItem *menuItem) { Q_UNUSED(menuItem) // nothing to do } void SystemTrayMenu::syncSeparatorsCollapsible(bool enable) { if (!m_menu) { return; } m_menu->setSeparatorsCollapsible(enable); } quintptr SystemTrayMenu::tag() const { return m_tag; } QMenu *SystemTrayMenu::menu() const { return m_menu.data(); } SystemTrayMenuItem::SystemTrayMenuItem() : QPlatformMenuItem() , m_tag(0) , m_action(new QAction(this)) { connect(m_action, &QAction::triggered, this, &QPlatformMenuItem::activated); connect(m_action, &QAction::hovered, this, &QPlatformMenuItem::hovered); } SystemTrayMenuItem::~SystemTrayMenuItem() { } void SystemTrayMenuItem::setCheckable(bool checkable) { m_action->setCheckable(checkable); } void SystemTrayMenuItem::setChecked(bool isChecked) { m_action->setChecked(isChecked); } void SystemTrayMenuItem::setEnabled(bool enabled) { m_action->setEnabled(enabled); } void SystemTrayMenuItem::setFont(const QFont &font) { m_action->setFont(font); } void SystemTrayMenuItem::setIcon(const QIcon &icon) { m_action->setIcon(icon); } void SystemTrayMenuItem::setIsSeparator(bool isSeparator) { m_action->setSeparator(isSeparator); } void SystemTrayMenuItem::setMenu(QPlatformMenu *menu) { if (SystemTrayMenu *ourMenu = qobject_cast(menu)) { m_action->setMenu(ourMenu->menu()); } } void SystemTrayMenuItem::setRole(QPlatformMenuItem::MenuRole role) { Q_UNUSED(role) } void SystemTrayMenuItem::setShortcut(const QKeySequence &shortcut) { m_action->setShortcut(shortcut); } void SystemTrayMenuItem::setTag(quintptr tag) { m_tag = tag; } void SystemTrayMenuItem::setText(const QString &text) { m_action->setText(text); } void SystemTrayMenuItem::setVisible(bool isVisible) { m_action->setVisible(isVisible); } void SystemTrayMenuItem::setIconSize(int size) { Q_UNUSED(size); } +void SystemTrayMenuItem::setHasExclusiveGroup(bool hasExclusiveGroup) +{ + if (hasExclusiveGroup) { + if (!m_action->actionGroup()) { + m_action->setActionGroup(new QActionGroup(m_action)); + } + } else { + QActionGroup *actionGroup = m_action->actionGroup(); + if (actionGroup) { + m_action->setActionGroup(nullptr); + delete actionGroup; + } + } +} + quintptr SystemTrayMenuItem::tag() const { return m_tag; } QAction *SystemTrayMenuItem::action() const { return m_action; } KDEPlatformSystemTrayIcon::KDEPlatformSystemTrayIcon() : QPlatformSystemTrayIcon() , m_sni(Q_NULLPTR) { } KDEPlatformSystemTrayIcon::~KDEPlatformSystemTrayIcon() { } void KDEPlatformSystemTrayIcon::init() { if (!m_sni) { m_sni = new KStatusNotifierItem(); m_sni->setStandardActionsEnabled(false); m_sni->setTitle(QApplication::applicationDisplayName()); connect(m_sni, &KStatusNotifierItem::activateRequested, [this](bool active, const QPoint &pos) { Q_UNUSED(active) Q_UNUSED(pos) emit activated(QPlatformSystemTrayIcon::Trigger); }); connect(m_sni, &KStatusNotifierItem::secondaryActivateRequested, [this](const QPoint &pos) { Q_UNUSED(pos) emit activated(QPlatformSystemTrayIcon::MiddleClick); }); } } void KDEPlatformSystemTrayIcon::cleanup() { delete m_sni; m_sni = Q_NULLPTR; } void KDEPlatformSystemTrayIcon::updateIcon(const QIcon &icon) { if (!m_sni) { return; } if (icon.name().isEmpty()) { m_sni->setIconByPixmap(icon); m_sni->setToolTipIconByPixmap(icon); } else { m_sni->setIconByName(icon.name()); m_sni->setToolTipIconByName(icon.name()); } } void KDEPlatformSystemTrayIcon::updateToolTip(const QString &tooltip) { if (!m_sni) { return; } m_sni->setToolTipTitle(tooltip); } void KDEPlatformSystemTrayIcon::updateMenu(QPlatformMenu *menu) { if (!m_sni) { return; } if (SystemTrayMenu *ourMenu = qobject_cast(menu)) { m_sni->setContextMenu(ourMenu->menu()); } } QPlatformMenu *KDEPlatformSystemTrayIcon::createMenu() const { return new SystemTrayMenu(); } QRect KDEPlatformSystemTrayIcon::geometry() const { // StatusNotifierItem doesn't provide the geometry return QRect(); } void KDEPlatformSystemTrayIcon::showMessage(const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int secs) { Q_UNUSED(iconType) if (!m_sni) { return; } m_sni->showMessage(title, msg, icon.name(), secs); } bool KDEPlatformSystemTrayIcon::isSystemTrayAvailable() const { #ifdef DBUS_SUPPORT_ENABLED - QDBusInterface systrayHost(QStringLiteral("org.kde.StatusNotifierWatcher"), QStringLiteral("/StatusNotifierWatcher"), QStringLiteral("org.kde.StatusNotifierWatcher")); - if (systrayHost.isValid()) { - return systrayHost.property("IsStatusNotifierHostRegistered").toBool(); + if (QGuiApplication::platformName().compare(QStringLiteral("cocoa"), Qt::CaseInsensitive)) { + QDBusInterface systrayHost(QStringLiteral("org.kde.StatusNotifierWatcher"), QStringLiteral("/StatusNotifierWatcher"), QStringLiteral("org.kde.StatusNotifierWatcher")); + if (systrayHost.isValid()) { + return systrayHost.property("IsStatusNotifierHostRegistered").toBool(); + } } #endif - return false; + return QSystemTrayIcon::isSystemTrayAvailable(); } bool KDEPlatformSystemTrayIcon::supportsMessages() const { return true; } diff --git a/src/platformtheme/kdeplatformsystemtrayicon.h b/src/platformtheme/kdeplatformsystemtrayicon.h index c8a7b96..8847fa1 100644 --- a/src/platformtheme/kdeplatformsystemtrayicon.h +++ b/src/platformtheme/kdeplatformsystemtrayicon.h @@ -1,111 +1,113 @@ /* This file is part of the KDE libraries - * Copyright 2014 Martin Gräßlin + * Copyright 2014 Martin Gräßlin * * 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 KDEPLATFORMSYSTEMTRAYICON_H #define KDEPLATFORMSYSTEMTRAYICON_H #include #include class KStatusNotifierItem; class SystemTrayMenuItem; class QAction; class QMenu; class SystemTrayMenu : public QPlatformMenu { Q_OBJECT public: SystemTrayMenu(); ~SystemTrayMenu() override; void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; QPlatformMenuItem *menuItemAt(int position) const override; QPlatformMenuItem *menuItemForTag(quintptr tag) const override; void removeMenuItem(QPlatformMenuItem *menuItem) override; void setEnabled(bool enabled) override; void setIcon(const QIcon &icon) override; void setTag(quintptr tag) override; void setText(const QString &text) override; void setVisible(bool visible) override; void syncMenuItem(QPlatformMenuItem *menuItem) override; void syncSeparatorsCollapsible(bool enable) override; quintptr tag() const override; QPlatformMenuItem *createMenuItem() const override; + QPlatformMenu *createSubMenu() const override; QMenu *menu() const; private: quintptr m_tag; QPointer m_menu; QList m_items; }; class SystemTrayMenuItem : public QPlatformMenuItem { Q_OBJECT public: SystemTrayMenuItem(); ~SystemTrayMenuItem() override; void setCheckable(bool checkable) override; void setChecked(bool isChecked) override; void setEnabled(bool enabled) override; void setFont(const QFont &font) override; void setIcon(const QIcon &icon) override; void setIsSeparator(bool isSeparator) override; void setMenu(QPlatformMenu *menu) override; void setRole(MenuRole role) override; void setShortcut(const QKeySequence &shortcut) override; void setTag(quintptr tag) override; void setText(const QString &text) override; void setVisible(bool isVisible) override; quintptr tag() const override; void setIconSize(int size) override; + void setHasExclusiveGroup(bool hasExclusiveGroup) override; QAction *action() const; private: quintptr m_tag; QAction *m_action; }; class KDEPlatformSystemTrayIcon : public QPlatformSystemTrayIcon { public: KDEPlatformSystemTrayIcon(); ~KDEPlatformSystemTrayIcon() override; void init() override; void cleanup() override; void updateIcon(const QIcon &icon) override; void updateToolTip(const QString &tooltip) override; void updateMenu(QPlatformMenu *menu) override; QRect geometry() const override; void showMessage(const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int secs) override; bool isSystemTrayAvailable() const override; bool supportsMessages() const override; QPlatformMenu *createMenu() const override; private: KStatusNotifierItem *m_sni; }; #endif diff --git a/src/platformtheme/kdeplatformtheme.cpp b/src/platformtheme/kdeplatformtheme.cpp index 1377679..5069b76 100644 --- a/src/platformtheme/kdeplatformtheme.cpp +++ b/src/platformtheme/kdeplatformtheme.cpp @@ -1,337 +1,338 @@ /* 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; + return type == QPlatformTheme::FileDialog && qobject_cast(QCoreApplication::instance()); } 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 the user is running only a QGuiApplication, explicitely unset the QQC1 desktop style and 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"); } diff --git a/src/platformtheme/kdeplatformtheme.json b/src/platformtheme/kdeplatformtheme.json index 5d250cc..0e48a74 100644 --- a/src/platformtheme/kdeplatformtheme.json +++ b/src/platformtheme/kdeplatformtheme.json @@ -1,3 +1,12 @@ { - "Keys": [ "kde" ] + "Keys": [ "kde" ], + "Name": "KDEPlatformTheme", + "Copyright": "(C) 2015-17 René J.V. Bertin", + "Description": [ + "KDE platform integration plugin for Mac OS X/macOS", + "Based on the Plasma Integration plugin.", + "This version only proxies the original Qt Cocoa and/or RJVB's QAltCocoa QPA plugins;", + "Set QT_QPA_PLATFORMTHEME=kde to use it instead of the Cocoa plugin." + ], + "Url": [ "https://github.com/RJVB/osx-integration" ] } diff --git a/src/platformtheme/kdirselectdialog.cpp b/src/platformtheme/kdirselectdialog.cpp index 9d13ec4..3bf1b78 100644 --- a/src/platformtheme/kdirselectdialog.cpp +++ b/src/platformtheme/kdirselectdialog.cpp @@ -1,588 +1,591 @@ /* Copyright (C) 2001,2002 Carsten Pfeiffer Copyright (C) 2001 Michael Jarrett Copyright (C) 2009 Shaun Reich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 Library 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 "kdirselectdialog_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kfiletreeview_p.h" #include #include // ### add mutator for treeview! class KDirSelectDialog::Private { public: Private(bool localOnly, KDirSelectDialog *parent) : m_parent(parent), m_localOnly(localOnly), m_comboLocked(false), m_urlCombo(0) { } void readConfig(const KSharedConfigPtr &config, const QString &group); void saveConfig(KSharedConfigPtr config, const QString &group); void slotMkdir(); void slotCurrentChanged(); void slotExpand(const QModelIndex &); void slotUrlActivated(const QString &); void slotComboTextChanged(const QString &); void slotContextMenuRequested(const QPoint &); void slotNewFolder(); void slotMoveToTrash(); void slotDelete(); void slotProperties(); KDirSelectDialog *m_parent; bool m_localOnly : 1; bool m_comboLocked : 1; QUrl m_rootUrl; QUrl m_startDir; KFileTreeView *m_treeView; QMenu *m_contextMenu; KActionCollection *m_actions; KFilePlacesView *m_placesView; KHistoryComboBox *m_urlCombo; QString m_recentDirClass; QUrl m_startURL; QAction *moveToTrash; QAction *deleteAction; QAction *showHiddenFoldersAction; }; void KDirSelectDialog::Private::readConfig(const KSharedConfig::Ptr &config, const QString &group) { m_urlCombo->clear(); KConfigGroup conf(config, group); m_urlCombo->setHistoryItems(conf.readPathEntry("History Items", QStringList())); const QSize size = conf.readEntry("DirSelectDialog Size", QSize()); if (size.isValid()) { m_parent->resize(size); } } void KDirSelectDialog::Private::saveConfig(KSharedConfig::Ptr config, const QString &group) { KConfigGroup conf(config, group); KConfigGroup::WriteConfigFlags flags(KConfigGroup::Persistent | KConfigGroup::Global); conf.writePathEntry("History Items", m_urlCombo->historyItems(), flags); conf.writeEntry("DirSelectDialog Size", m_parent->size(), flags); config->sync(); } void KDirSelectDialog::Private::slotMkdir() { bool ok; QString where = m_parent->url().toDisplayString(QUrl::PreferLocalFile); QString name = i18nc("folder name", "New Folder"); if (m_parent->url().isLocalFile() && QFileInfo::exists(m_parent->url().toLocalFile() + QLatin1Char('/') + name)) { name = KIO::suggestName(m_parent->url(), name); } QString directory = KIO::encodeFileName(QInputDialog::getText(m_parent, i18nc("@title:window", "New Folder"), i18nc("@label:textbox", "Create new folder in:\n%1", where), QLineEdit::Normal, name, &ok)); if (!ok) { return; } bool selectDirectory = true; bool writeOk = false; bool exists = false; QUrl folderurl(m_parent->url()); const QStringList dirs = directory.split(QLatin1Char('/'), QString::SkipEmptyParts); QStringList::ConstIterator it = dirs.begin(); for (; it != dirs.end(); ++it) { folderurl.setPath(folderurl.path() + QLatin1Char('/') + *it); KIO::StatJob *job = KIO::stat(folderurl); KJobWidgets::setWindow(job, m_parent); job->setDetails(0); //We only want to know if it exists, 0 == that. job->setSide(KIO::StatJob::DestinationSide); exists = job->exec(); if (!exists) { KIO::MkdirJob *job = KIO::mkdir(folderurl); KJobWidgets::setWindow(job, m_parent); writeOk = job->exec(); } } if (exists) { // url was already existent QString which = folderurl.toDisplayString(QUrl::PreferLocalFile); KMessageBox::sorry(m_parent, i18n("A file or folder named %1 already exists.", which)); selectDirectory = false; } else if (!writeOk) { KMessageBox::sorry(m_parent, i18n("You do not have permission to create that folder.")); } else if (selectDirectory) { m_parent->setCurrentUrl(folderurl); } } void KDirSelectDialog::Private::slotCurrentChanged() { if (m_comboLocked) { return; } const QUrl u = m_treeView->currentUrl(); if (u.isValid()) { m_urlCombo->setEditText(u.toDisplayString(QUrl::PreferLocalFile)); } else { m_urlCombo->setEditText(QString()); } } void KDirSelectDialog::Private::slotUrlActivated(const QString &text) { if (text.isEmpty()) { return; } const QUrl url = QUrl::fromUserInput(text); m_urlCombo->addToHistory(url.toDisplayString()); if (m_parent->localOnly() && !url.isLocalFile()) { return; //FIXME: messagebox for the user } QUrl oldUrl = m_treeView->currentUrl(); if (oldUrl.isEmpty()) { oldUrl = m_startDir; } m_parent->setCurrentUrl(oldUrl); } void KDirSelectDialog::Private::slotComboTextChanged(const QString &text) { m_treeView->blockSignals(true); QUrl url = QUrl::fromUserInput(text); #ifdef Q_OS_WIN QUrl rootUrl(m_treeView->rootUrl()); if (url.isLocalFile() && !rootUrl.isParentOf(url) && !rootUrl.matches(url, QUrl::StripTrailingSlash)) { QUrl tmp = KIO::upUrl(url); while (tmp.path().length() > 1) { url = tmp; tmp = KIO::upUrl(url); } m_treeView->setRootUrl(url); } #endif m_treeView->setCurrentUrl(url); m_treeView->blockSignals(false); } void KDirSelectDialog::Private::slotContextMenuRequested(const QPoint &pos) { m_contextMenu->popup(m_treeView->viewport()->mapToGlobal(pos)); } void KDirSelectDialog::Private::slotExpand(const QModelIndex &index) { m_treeView->setExpanded(index, !m_treeView->isExpanded(index)); } void KDirSelectDialog::Private::slotNewFolder() { slotMkdir(); } void KDirSelectDialog::Private::slotMoveToTrash() { const QUrl url = m_treeView->selectedUrl(); KIO::JobUiDelegate job; if (job.askDeleteConfirmation(QList() << url, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::CopyJob *copyJob = KIO::trash(url); KJobWidgets::setWindow(copyJob, m_parent); copyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void KDirSelectDialog::Private::slotDelete() { const QUrl url = m_treeView->selectedUrl(); KIO::JobUiDelegate job; if (job.askDeleteConfirmation(QList() << url, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::DeleteJob *deleteJob = KIO::del(url); KJobWidgets::setWindow(deleteJob, m_parent); deleteJob->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void KDirSelectDialog::Private::slotProperties() { KPropertiesDialog *dialog = 0; dialog = new KPropertiesDialog(m_treeView->selectedUrl(), this->m_parent); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } KDirSelectDialog::KDirSelectDialog(const QUrl &startDir, bool localOnly, QWidget *parent) // #ifdef Q_OS_WIN // : QDialog(parent, Qt::WindowMinMaxButtonsHint), // #else // : QDialog(parent), // #endif : d(new Private(localOnly, this)) { setWindowTitle(i18nc("@title:window", "Select Folder")); QVBoxLayout *topLayout = new QVBoxLayout; setLayout(topLayout); QFrame *page = new QFrame(this); topLayout->addWidget(page); QPushButton *folderButton = new QPushButton; KGuiItem::assign(folderButton, KGuiItem(i18nc("@action:button", "New Folder..."), QStringLiteral("folder-new"))); connect(folderButton, SIGNAL(clicked()), this, SLOT(slotNewFolder())); m_buttons = new QDialogButtonBox(this); m_buttons->addButton(folderButton, QDialogButtonBox::ActionRole); m_buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_buttons, SIGNAL(accepted()), this, SLOT(accept())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(reject())); topLayout->addWidget(m_buttons); QHBoxLayout *hlay = new QHBoxLayout(page); hlay->setMargin(0); QVBoxLayout *mainLayout = new QVBoxLayout(); d->m_actions = new KActionCollection(this); d->m_actions->addAssociatedWidget(this); d->m_placesView = new KFilePlacesView(page); d->m_placesView->setModel(new KFilePlacesModel(d->m_placesView)); d->m_placesView->setObjectName(QStringLiteral("speedbar")); d->m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->m_placesView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); connect(d->m_placesView, SIGNAL(urlChanged(QUrl)), SLOT(setCurrentUrl(QUrl))); hlay->addWidget(d->m_placesView); hlay->addLayout(mainLayout); d->m_treeView = new KFileTreeView(page); d->m_treeView->setDirOnlyMode(true); d->m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); for (int i = 1; i < d->m_treeView->model()->columnCount(); ++i) { d->m_treeView->hideColumn(i); } d->m_urlCombo = new KHistoryComboBox(page); d->m_urlCombo->setLayoutDirection(Qt::LeftToRight); d->m_urlCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); d->m_urlCombo->setTrapReturnKey(true); d->m_urlCombo->setPixmapProvider(new KUrlPixmapProvider()); KUrlCompletion *comp = new KUrlCompletion(); comp->setMode(KUrlCompletion::DirCompletion); d->m_urlCombo->setCompletionObject(comp, true); d->m_urlCombo->setAutoDeleteCompletionObject(true); d->m_urlCombo->setDuplicatesEnabled(false); d->m_contextMenu = new QMenu(this); QAction *newFolder = new QAction(i18nc("@action:inmenu", "New Folder..."), this); d->m_actions->addAction(newFolder->objectName(), newFolder); newFolder->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); newFolder->setShortcut(Qt::Key_F10); connect(newFolder, SIGNAL(triggered(bool)), this, SLOT(slotNewFolder())); d->m_contextMenu->addAction(newFolder); d->moveToTrash = new QAction(i18nc("@action:inmenu", "Move to Trash"), this); d->m_actions->addAction(d->moveToTrash->objectName(), d->moveToTrash); d->moveToTrash->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); d->moveToTrash->setShortcut(Qt::Key_Delete); connect(d->moveToTrash, SIGNAL(triggered(bool)), this, SLOT(slotMoveToTrash())); d->m_contextMenu->addAction(d->moveToTrash); d->deleteAction = new QAction(i18nc("@action:inmenu", "Delete"), this); d->m_actions->addAction(d->deleteAction->objectName(), d->deleteAction); d->deleteAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); d->deleteAction->setShortcut(Qt::SHIFT + Qt::Key_Delete); connect(d->deleteAction, SIGNAL(triggered(bool)), this, SLOT(slotDelete())); d->m_contextMenu->addAction(d->deleteAction); d->m_contextMenu->addSeparator(); d->showHiddenFoldersAction = new KToggleAction(i18nc("@option:check", "Show Hidden Folders"), this); d->m_actions->addAction(d->showHiddenFoldersAction->objectName(), d->showHiddenFoldersAction); d->showHiddenFoldersAction->setShortcut(Qt::Key_F8); connect(d->showHiddenFoldersAction, SIGNAL(triggered(bool)), d->m_treeView, SLOT(setShowHiddenFiles(bool))); d->m_contextMenu->addAction(d->showHiddenFoldersAction); d->m_contextMenu->addSeparator(); QAction *propertiesAction = new QAction(i18nc("@action:inmenu", "Properties"), this); d->m_actions->addAction(propertiesAction->objectName(), propertiesAction); propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); propertiesAction->setShortcut(Qt::ALT + Qt::Key_Return); connect(propertiesAction, SIGNAL(triggered(bool)), this, SLOT(slotProperties())); d->m_contextMenu->addAction(propertiesAction); d->m_startURL = KFileWidget::getStartUrl(startDir, d->m_recentDirClass); if (localOnly && !d->m_startURL.isLocalFile()) { QString docPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); if (QDir(docPath).exists()) { d->m_startURL = QUrl::fromLocalFile(docPath); } else { d->m_startURL = QUrl::fromLocalFile(QDir::homePath()); } } d->m_startDir = d->m_startURL; d->m_rootUrl = d->m_treeView->rootUrl(); d->readConfig(KSharedConfig::openConfig(), QStringLiteral("DirSelect Dialog")); mainLayout->addWidget(d->m_treeView, 1); mainLayout->addWidget(d->m_urlCombo, 0); connect(d->m_treeView, SIGNAL(currentChanged(QUrl)), SLOT(slotCurrentChanged())); connect(d->m_treeView, SIGNAL(activated(QModelIndex)), SLOT(slotExpand(QModelIndex))); connect(d->m_treeView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotContextMenuRequested(QPoint))); connect(d->m_urlCombo, SIGNAL(editTextChanged(QString)), SLOT(slotComboTextChanged(QString))); connect(d->m_urlCombo, SIGNAL(activated(QString)), SLOT(slotUrlActivated(QString))); connect(d->m_urlCombo, SIGNAL(returnPressed(QString)), SLOT(slotUrlActivated(QString))); setCurrentUrl(d->m_startURL); } KDirSelectDialog::~KDirSelectDialog() { + delete d->m_placesView; + delete d->m_treeView; + delete d->m_urlCombo; delete d; } QUrl KDirSelectDialog::url() const { QUrl comboUrl = QUrl::fromUserInput(d->m_urlCombo->currentText()); if (comboUrl.isValid()) { KIO::StatJob *statJob = KIO::stat(comboUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, d->m_parent); const bool ok = statJob->exec(); if (ok && statJob->statResult().isDir()) { return comboUrl; } } // qDebug() << comboUrl.path() << " is not an accessible directory"; return d->m_treeView->currentUrl(); } QUrl KDirSelectDialog::rootUrl() const { return d->m_rootUrl; } QAbstractItemView *KDirSelectDialog::view() const { return d->m_treeView; } bool KDirSelectDialog::localOnly() const { return d->m_localOnly; } QUrl KDirSelectDialog::startDir() const { return d->m_startDir; } void KDirSelectDialog::setCurrentUrl(const QUrl &url) { if (!url.isValid()) { return; } if (url.scheme() != d->m_rootUrl.scheme()) { QUrl u(url); //We need the url to end with / because some code ahead (kdirmodel) is expecting //to find the / separator. It can happen that a valid url like smb: does not have //one so we should add it. if (!u.toString().endsWith(QLatin1Char('/'))) { u.setPath(QStringLiteral("/")); } d->m_treeView->setRootUrl(u); d->m_rootUrl = u; } //Check if url represents a hidden folder and enable showing them QString fileName = url.fileName(); //TODO a better hidden file check? bool isHidden = fileName.length() > 1 && fileName[0] == QLatin1Char('.') && (fileName.length() > 2 ? fileName[1] != QLatin1Char('.') : true); bool showHiddenFiles = isHidden && !d->m_treeView->showHiddenFiles(); if (showHiddenFiles) { d->showHiddenFoldersAction->setChecked(true); d->m_treeView->setShowHiddenFiles(true); } d->m_treeView->setCurrentUrl(url); } void KDirSelectDialog::accept() { QUrl selectedUrl = url(); if (!selectedUrl.isValid()) { return; } if (!d->m_recentDirClass.isEmpty()) { KRecentDirs::add(d->m_recentDirClass, selectedUrl.toString()); } d->m_urlCombo->addToHistory(selectedUrl.toDisplayString()); KFileWidget::setStartDir(url()); QDialog::accept(); } void KDirSelectDialog::hideEvent(QHideEvent *event) { d->saveConfig(KSharedConfig::openConfig(), QStringLiteral("DirSelect Dialog")); QDialog::hideEvent(event); } // static QUrl KDirSelectDialog::selectDirectory(const QUrl &startDir, bool localOnly, QWidget *parent, const QString &caption) { KDirSelectDialog myDialog(startDir, localOnly, parent); if (!caption.isNull()) { myDialog.setWindowTitle(caption); } if (myDialog.exec() == QDialog::Accepted) { QUrl url = myDialog.url(); //Returning the most local url if (url.isLocalFile()) { return url; } KIO::StatJob *job = KIO::stat(url); KJobWidgets::setWindow(job, parent); if (!job->exec()) { return url; } KIO::UDSEntry entry = job->statResult(); const QString path = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); return path.isEmpty() ? url : QUrl::fromLocalFile(path); } else { return QUrl(); } } QUrl KDirSelectDialog::directory() { return url(); } QList KDirSelectDialog::selectedFiles() { return QList() << url(); } void KDirSelectDialog::setDirectory(const QUrl &directory) { setCurrentUrl(directory); } QString KDirSelectDialog::selectedMimeTypeFilter() { return QString(); } QString KDirSelectDialog::selectedNameFilter() { return QString(); } void KDirSelectDialog::selectFile(const QUrl &filename) { Q_UNUSED(filename) } void KDirSelectDialog::selectMimeTypeFilter(const QString &filter) { Q_UNUSED(filter) } void KDirSelectDialog::selectNameFilter(const QString &filter) { Q_UNUSED(filter) } #include "moc_kdirselectdialog_p.cpp" diff --git a/src/platformtheme/kfiletreeview.cpp b/src/platformtheme/kfiletreeview.cpp index 77b128d..69ba3bb 100644 --- a/src/platformtheme/kfiletreeview.cpp +++ b/src/platformtheme/kfiletreeview.cpp @@ -1,212 +1,215 @@ /* This file is part of the KDE project Copyright (C) 2007 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) 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 Library 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 "kfiletreeview_p.h" #include #include #include #include #include #include #include #include #include #include class KFileTreeView::Private { public: Private(KFileTreeView *parent) : q(parent) { } QUrl urlForProxyIndex(const QModelIndex &index) const; void _k_activated(const QModelIndex &); void _k_currentChanged(const QModelIndex &, const QModelIndex &); void _k_expanded(const QModelIndex &); KFileTreeView *q; KDirModel *mSourceModel; KDirSortFilterProxyModel *mProxyModel; }; QUrl KFileTreeView::Private::urlForProxyIndex(const QModelIndex &index) const { const KFileItem item = mSourceModel->itemForIndex(mProxyModel->mapToSource(index)); return !item.isNull() ? item.url() : QUrl(); } void KFileTreeView::Private::_k_activated(const QModelIndex &index) { const QUrl url = urlForProxyIndex(index); if (url.isValid()) { emit q->activated(url); } } void KFileTreeView::Private::_k_currentChanged(const QModelIndex ¤tIndex, const QModelIndex &) { const QUrl url = urlForProxyIndex(currentIndex); if (url.isValid()) { emit q->currentChanged(url); } } void KFileTreeView::Private::_k_expanded(const QModelIndex &baseIndex) { QModelIndex index = mProxyModel->mapFromSource(baseIndex); + q->setExpanded(index, true); q->selectionModel()->clearSelection(); q->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); q->scrollTo(index); } KFileTreeView::KFileTreeView(QWidget *parent) : QTreeView(parent), d(new Private(this)) { d->mSourceModel = new KDirModel(this); d->mProxyModel = new KDirSortFilterProxyModel(this); d->mProxyModel->setSourceModel(d->mSourceModel); setModel(d->mProxyModel); setItemDelegate(new KFileItemDelegate(this)); setLayoutDirection(Qt::LeftToRight); d->mSourceModel->dirLister()->openUrl(QUrl::fromLocalFile(QDir::root().absolutePath()), KDirLister::Keep); connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(_k_activated(QModelIndex))); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(_k_currentChanged(QModelIndex,QModelIndex))); connect(d->mSourceModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_expanded(QModelIndex))); } KFileTreeView::~KFileTreeView() { + delete d->mSourceModel; + delete d->mProxyModel; delete d; } QUrl KFileTreeView::currentUrl() const { return d->urlForProxyIndex(currentIndex()); } QUrl KFileTreeView::selectedUrl() const { if (!selectionModel()->hasSelection()) { return QUrl(); } const QItemSelection selection = selectionModel()->selection(); const QModelIndex firstIndex = selection.indexes().first(); return d->urlForProxyIndex(firstIndex); } QList KFileTreeView::selectedUrls() const { QList urls; if (!selectionModel()->hasSelection()) { return urls; } const QModelIndexList indexes = selectionModel()->selection().indexes(); foreach (const QModelIndex &index, indexes) { const QUrl url = d->urlForProxyIndex(index); if (url.isValid()) { urls.append(url); } } return urls; } QUrl KFileTreeView::rootUrl() const { return d->mSourceModel->dirLister()->url(); } void KFileTreeView::setDirOnlyMode(bool enabled) { d->mSourceModel->dirLister()->setDirOnlyMode(enabled); d->mSourceModel->dirLister()->openUrl(d->mSourceModel->dirLister()->url()); } void KFileTreeView::setShowHiddenFiles(bool enabled) { QUrl url = currentUrl(); d->mSourceModel->dirLister()->setShowingDotFiles(enabled); d->mSourceModel->dirLister()->openUrl(d->mSourceModel->dirLister()->url()); setCurrentUrl(url); } void KFileTreeView::setCurrentUrl(const QUrl &url) { QModelIndex baseIndex = d->mSourceModel->indexForUrl(url); if (!baseIndex.isValid()) { d->mSourceModel->expandToUrl(url); return; } QModelIndex proxyIndex = d->mProxyModel->mapFromSource(baseIndex); selectionModel()->clearSelection(); selectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::SelectCurrent); scrollTo(proxyIndex); } void KFileTreeView::setRootUrl(const QUrl &url) { d->mSourceModel->dirLister()->openUrl(url); } void KFileTreeView::contextMenuEvent(QContextMenuEvent *event) { QMenu menu; KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Folders"), &menu); showHiddenAction->setChecked(d->mSourceModel->dirLister()->showingDotFiles()); connect(showHiddenAction, SIGNAL(toggled(bool)), this, SLOT(setShowHiddenFiles(bool))); menu.addAction(showHiddenAction); menu.exec(event->globalPos()); } bool KFileTreeView::showHiddenFiles() const { return d->mSourceModel->dirLister()->showingDotFiles(); } QSize KFileTreeView::sizeHint() const { // This size makes KDirSelectDialog pop up just under 800x600 by default :-) return QSize(680, 500); } #include "moc_kfiletreeview_p.cpp" diff --git a/src/platformtheme/kfontsettingsdata.cpp b/src/platformtheme/kfontsettingsdata.cpp index c2eec44..6e43edd 100644 --- a/src/platformtheme/kfontsettingsdata.cpp +++ b/src/platformtheme/kfontsettingsdata.cpp @@ -1,133 +1,141 @@ /* This file is part of the KDE libraries Copyright (C) 2000, 2006 David Faure Copyright 2008 Friedrich W. H. Kossebau Copyright 2013 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 Library 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. */ #undef QT_NO_CAST_FROM_ASCII #include "kfontsettingsdata.h" +#include "platformtheme_logging.h" + #include #include #include #include #ifdef DBUS_SUPPORT_ENABLED #include #include #endif #include #include #include KFontSettingsData::KFontSettingsData() - : QObject(0) + : QObject(nullptr) { #ifdef DBUS_SUPPORT_ENABLED QMetaObject::invokeMethod(this, "delayedDBusConnects", Qt::QueuedConnection); #endif for (int i = 0; i < FontTypesCount; ++i) { mFonts[i] = 0; } } KFontSettingsData::~KFontSettingsData() { for (int i = 0; i < FontTypesCount; ++i) { delete mFonts[i]; } } // NOTE: keep in sync with plasma-desktop/kcms/fonts/fonts.cpp static const char GeneralId[] = "General"; static const char DefaultFont[] = "Lucida Grande"; static const KFontData DefaultFontData[KFontSettingsData::FontTypesCount] = { { GeneralId, "font", DefaultFont, 10, -1, QFont::SansSerif, "Regular" }, { GeneralId, "fixed", "Monaco", 9, -1, QFont::Monospace, "Regular" }, { GeneralId, "toolBarFont", DefaultFont, 9, -1, QFont::SansSerif, "Regular" }, { GeneralId, "menuFont", DefaultFont, 10, -1, QFont::SansSerif, "Regular" }, { "WM", "activeFont", DefaultFont, 10, -1, QFont::SansSerif, "Regular" }, { GeneralId, "taskbarFont", DefaultFont, 10, -1, QFont::SansSerif, "Regular" }, { GeneralId, "smallestReadableFont", DefaultFont, 8, -1, QFont::SansSerif, "Regular" } }; KSharedConfigPtr &KFontSettingsData::kdeGlobals() { if (!mKdeGlobals) { if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_CONFIG_FILE")) { - mKdeGlobals = KSharedConfig::openConfig(qgetenv("QT_QPA_PLATFORMTHEME_CONFIG_FILE"), KConfig::NoGlobals); + const auto fname(qgetenv("QT_QPA_PLATFORMTHEME_CONFIG_FILE")); + mKdeGlobals = KSharedConfig::openConfig(fname, KConfig::NoGlobals); + const auto foundFile = QStandardPaths::locate(mKdeGlobals->locationType(), mKdeGlobals->name()); + if (foundFile.isEmpty()) { + qCWarning(PLATFORMTHEME) << "WARNING: could not open config file" << fname + << "in" << QStandardPaths::standardLocations(mKdeGlobals->locationType()); + } } else { mKdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); } } return mKdeGlobals; } QFont *KFontSettingsData::font(FontTypes fontType) { QFont *cachedFont = mFonts[fontType]; if (!cachedFont) { const KFontData &fontData = DefaultFontData[fontType]; cachedFont = new QFont(QLatin1String(fontData.FontName), fontData.Size, fontData.Weight); cachedFont->setStyleHint(fontData.StyleHint); const KConfigGroup configGroup(kdeGlobals(), fontData.ConfigGroupKey); QString fontInfo = configGroup.readEntry(fontData.ConfigKey, QString()); //If we have serialized information for this font, restore it //NOTE: We are not using KConfig directly because we can't call QFont::QFont from here if (!fontInfo.isEmpty()) { cachedFont->fromString(fontInfo); } else { cachedFont->setStyleName(QLatin1String(fontData.StyleName)); } mFonts[fontType] = cachedFont; } return cachedFont; } void KFontSettingsData::dropFontSettingsCache() { if (mKdeGlobals) { mKdeGlobals->reparseConfiguration(); } for (int i = 0; i < FontTypesCount; ++i) { delete mFonts[i]; mFonts[i] = 0; } QWindowSystemInterface::handleThemeChange(0); if (qobject_cast(QCoreApplication::instance())) { QApplication::setFont(*font(KFontSettingsData::GeneralFont)); } else { QGuiApplication::setFont(*font(KFontSettingsData::GeneralFont)); } } void KFontSettingsData::delayedDBusConnects() { #ifdef DBUS_SUPPORT_ENABLED QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(dropFontSettingsCache())); #endif } diff --git a/src/platformtheme/kfontsettingsdatamac.h b/src/platformtheme/kfontsettingsdatamac.h index ee99919..91e4450 100644 --- a/src/platformtheme/kfontsettingsdatamac.h +++ b/src/platformtheme/kfontsettingsdatamac.h @@ -1,59 +1,70 @@ /* This file is part of the KDE libraries Copyright (C) 2000, 2006 David Faure Copyright 2008 Friedrich W. H. Kossebau Copyright 2013 Aleix Pol Gonzalez 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 Library General Public License version 2 as published by the Free Software Foundation. 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 Library 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 KFONTSETTINGSDATAMAC_H #define KFONTSETTINGSDATAMAC_H #include "kfontsettingsdata.h" +#include + +class KdeMacTheme; class KFontSettingsDataMac : public KFontSettingsData { Q_OBJECT public: // if adding a new type here also add an entry to DefaultFontData enum FontTypes { GeneralFont = 0, FixedFont, ToolbarFont, MenuFont, WindowTitleFont, TaskbarFont, SmallestReadableFont, MessageBoxFont, FontTypesCount }; - KFontSettingsDataMac(); + KFontSettingsDataMac(KdeMacTheme *theme); ~KFontSettingsDataMac(); + const char *fontNameFor(QFontDatabase::SystemFont role) const; + public Q_SLOTS: void dropFontSettingsCache(); protected Q_SLOTS: void delayedDBusConnects(); public: QFont *font(FontTypes fontType); + bool usingCoreText(); + private: + KFontSettingsDataMac(); + QFont *mFonts[FontTypesCount]; + KdeMacTheme *mTheme; + bool mUseCoreText; }; #endif // KFONTSETTINGSDATAMAC_H diff --git a/src/platformtheme/kfontsettingsdatamac.mm b/src/platformtheme/kfontsettingsdatamac.mm index ebbd1c1..3c7606c 100644 --- a/src/platformtheme/kfontsettingsdatamac.mm +++ b/src/platformtheme/kfontsettingsdatamac.mm @@ -1,253 +1,327 @@ /* This file is part of the KDE libraries Copyright (C) 2000, 2006 David Faure Copyright 2008 Friedrich W. H. Kossebau Copyright 2013 Aleix Pol Gonzalez 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 Library General Public License version 2 as published by the Free Software Foundation. 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 Library 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 "kfontsettingsdatamac.h" +#include "kdemactheme.h" #include "platformtheme_logging.h" #include #include #include #include #include #include #include #ifdef DBUS_SUPPORT_ENABLED #include #include #endif #include #include #include // NOTE: keep in sync with plasma-desktop/kcms/fonts/fonts.cpp static const char GeneralId[] = "General"; // NOTE: the default system font changed with OS X 10.11, from Lucida Grande to // San Francisco. With luck this will be caught by QFontDatabase::GeneralFont const char DefaultFont[] = "Lucida Grande"; static const char DefaultFixedFont[] = "Monaco"; static const char *LocalDefaultFont = NULL; // See README.fonts.txt for information and thoughts about native/default fonts KFontData DefaultFontData[KFontSettingsDataMac::FontTypesCount] = { { GeneralId, "font", DefaultFont, 12, -1, QFont::SansSerif, "Medium" }, { GeneralId, "fixed", DefaultFixedFont, 10, -1, QFont::Monospace, "Regular" }, { GeneralId, "toolBarFont", DefaultFont, 10, -1, QFont::SansSerif, "Medium" }, { GeneralId, "menuFont", DefaultFont, 14, -1, QFont::SansSerif, "Medium" }, // applications don't control the window titlebar fonts { "WM", "activeFont", DefaultFont, 13, -1, QFont::SansSerif, "Medium" }, { GeneralId, "taskbarFont", DefaultFont, 9, -1, QFont::SansSerif, "Medium" }, { GeneralId, "smallestReadableFont", DefaultFont, 9, -1, QFont::SansSerif, "Medium" }, // this one is to accomodate for the MessageBoxFont which should be bold on OS X // when using the native theme fonts. { GeneralId, "messageBoxFont", DefaultFont, 13, QFont::Bold, QFont::SansSerif, "Bold" } }; -static const char *fontNameFor(QFontDatabase::SystemFont role) +const char *KFontSettingsDataMac::fontNameFor(QFontDatabase::SystemFont role) const { QFont qf = QFontDatabase::systemFont(role); if (!qf.defaultFamily().isEmpty()) { char *fn; if (role == QFontDatabase::FixedFont && !qf.fixedPitch()) { fn = strdup("Monaco"); } else if (qf.defaultFamily() == QStringLiteral(".Lucida Grande UI")) { fn = strdup("Lucida Grande"); } else { fn = strdup(qf.defaultFamily().toLocal8Bit().data()); } - if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + if (mTheme->verbose) { qCWarning(PLATFORMTHEME) << "fontNameFor" << role << "font:" << qf << "name:" << fn; } return fn; } else { return NULL; } } -void initDefaultFonts() +void initDefaultFonts(KFontSettingsDataMac *instance) { const char *fn; static bool active = false; // we must protect ourselves from being called recursively if (active) { return; } active = true; if (!LocalDefaultFont) { - fn = fontNameFor(QFontDatabase::GeneralFont); + fn = instance->fontNameFor(QFontDatabase::GeneralFont); LocalDefaultFont = fn; } for (int i = 0 ; i < KFontSettingsDataMac::FontTypesCount ; ++i) { switch(i) { case KFontSettingsDataMac::FixedFont: - fn = fontNameFor(QFontDatabase::FixedFont); + fn = instance->fontNameFor(QFontDatabase::FixedFont); break; case KFontSettingsDataMac::WindowTitleFont: - fn = fontNameFor(QFontDatabase::TitleFont); + fn = instance->fontNameFor(QFontDatabase::TitleFont); break; case KFontSettingsDataMac::SmallestReadableFont: - fn = fontNameFor(QFontDatabase::SmallestReadableFont); + fn = instance->fontNameFor(QFontDatabase::SmallestReadableFont); break; default: fn = LocalDefaultFont; break; } if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { qCWarning(PLATFORMTHEME) << "Default font for type" << i << ":" << fn << "; currently:" << DefaultFontData[i].FontName; } if (fn) { if (DefaultFontData[i].FontName != DefaultFont && DefaultFontData[i].FontName != DefaultFixedFont && DefaultFontData[i].FontName != LocalDefaultFont) { free((void*)DefaultFontData[i].FontName); } DefaultFontData[i].FontName = fn; } } active = false; } -KFontSettingsDataMac::KFontSettingsDataMac() +KFontSettingsDataMac::KFontSettingsDataMac(KdeMacTheme *theme) + : mTheme(theme) { #ifdef DBUS_SUPPORT_ENABLED QMetaObject::invokeMethod(this, "delayedDBusConnects", Qt::QueuedConnection); #endif for (int i = 0; i < FontTypesCount; ++i) { // remove any information that already have been cached by our parent // IFF we don't have our own mFonts copy // delete mFonts[i]; mFonts[i] = 0; } + + if (QGuiApplication::platformName().contains(QLatin1String("cocoa"))) { + KConfigGroup general(kdeGlobals(), "General"); + const QString fontEngine = general.readEntry("fontEngine", QString()); + // don't do anything if no instructions are given in kdeglobals or the environment + bool useFreeType = false, useFontConfig = false; + mUseCoreText = false; + if (!fontEngine.isEmpty()) { + useFreeType = fontEngine.compare(QLatin1String("FreeType"), Qt::CaseInsensitive) == 0; + useFontConfig = fontEngine.compare(QLatin1String("FontConfig"), Qt::CaseInsensitive) == 0; + // fontEngine=CoreText is the default and only handled so we can warn appropriately + // when the user tries to activate another, unknown font engine. + mUseCoreText = fontEngine.compare(QLatin1String("CoreText"), Qt::CaseInsensitive) == 0; + } + if (qgetenv("QT_MAC_FONTENGINE").toLower() == "freetype") { + useFontConfig = false; + useFreeType = true; + } + if (qgetenv("QT_MAC_FONTENGINE").toLower() == "fontconfig") { + useFreeType = false; + useFontConfig = true; + } + if (qgetenv("QT_MAC_FONTENGINE").toLower() == "coretext") { + // CoreText overrides all + mUseCoreText = true; + } + QString desired; + bool result = false; + const auto ftptr = mTheme->platformFunction("qt_mac_use_freetype"); + const auto fcptr = mTheme->platformFunction("qt_mac_use_fontconfig"); + typedef bool (*fontengineEnabler)(bool enabled); + if (mUseCoreText) { + desired = QStringLiteral("CoreText"); + if (fcptr) { + reinterpret_cast(fcptr)(false); + } + if (ftptr) { + result = reinterpret_cast(ftptr)(false); + if (!result) { + // at this point failure *probably* means that: + qCWarning(PLATFORMTHEME) << "The" << desired << "fontengine was probably still enabled"; + } + } + } else if (useFontConfig) { + desired = QStringLiteral("FontConfig"); + if (fcptr) { + result = reinterpret_cast(fcptr)(useFontConfig); + } else { + qCWarning(PLATFORMTHEME) << "Cannot use the FontConfig fontengine/fontdatabase:\n" + "\tthis probably means Qt was built without FontConfig support or\n" + "\tthat you're not using the QAltCocoa QPA plugin."; + } + } else if (useFreeType) { + desired = QStringLiteral("FreeType"); + if (ftptr) { + result = reinterpret_cast(ftptr)(useFreeType); + } else { + qCWarning(PLATFORMTHEME) << "Cannot use the FreeType fontdatabase:\n" + "\tthis probably means Qt was built without FreeType support or\n" + "\tthat you're not using the QAltCocoa QPA plugin."; + } + } + } else { + mUseCoreText = false; + } + } KFontSettingsDataMac::~KFontSettingsDataMac() { for (int i = 0 ; i < KFontSettingsDataMac::FontTypesCount ; ++i) { if (DefaultFontData[i].FontName != DefaultFont && DefaultFontData[i].FontName != DefaultFixedFont) { if (DefaultFontData[i].FontName && DefaultFontData[i].FontName != LocalDefaultFont) { free((void*)(DefaultFontData[i].FontName)); } DefaultFontData[i].FontName = (i == FixedFont)? DefaultFixedFont : DefaultFont; } } if (LocalDefaultFont) { free((void*)(LocalDefaultFont)); } LocalDefaultFont = NULL; } QFont *KFontSettingsDataMac::font(FontTypes fontType) { QFont *cachedFont = mFonts[fontType]; if (!cachedFont) { // check if we have already initialised our local database mapping font types to fonts // if not, we do it here, at the latest possible moment. Doing it in the KFontSettingsDataMac // ctor is bound for failure as our instance is likely to be created before Qt's own // font database has been populated. That's expectable: the font database also represents // platform (theme) specific fonts for various roles, and our ctor is called as part of the // platform theme creation procedure. if (!LocalDefaultFont) { static bool active = false; // NB: initDefaultFonts() queries Qt's font database which in turn can call us // again. Protection against this is built into initDefaultFonts(), but in practice // we prefer to return NULL if called through recursively. if (!active) { active = true; - initDefaultFonts(); + initDefaultFonts(this); active = false; } else { // our caller must handle NULL, preferably by relaying the font request // to the native platform theme (see KdeMacTheme::font()). return NULL; } } const KConfigGroup configGroup(kdeGlobals(), DefaultFontData[fontType].ConfigGroupKey); QString fontInfo; bool forceBold = false; if (fontType == MessageBoxFont) { // OS X special: the MessageBoxFont is by default a bold version of the GeneralFont // and that's what is cached in DefaultFontData[MessageBoxFont]. // NB: we can use a single configGroup for this hack as long as MessageBoxFont and // GeneralFont share the same ConfigGroupKey (or MessageBoxFont cannot be configured). fontInfo = configGroup.readEntry(DefaultFontData[GeneralFont].ConfigKey, QString()); if (!fontInfo.isEmpty()) { // However, if the user has configured a GeneralFont (MessageBoxFont cannot be configured), // we respect his/her choice but maintain the bold aspect dictated by the platform. fontType = GeneralFont; forceBold = true; } } const KFontData &fontData = DefaultFontData[fontType]; cachedFont = new QFont(QLatin1String(fontData.FontName), fontData.Size, forceBold? QFont::Bold : fontData.Weight); - cachedFont->setStyleHint(fontData.StyleHint); // ignore the default stylehint; works better converting medium -> bold // cachedFont->setStyleName(QLatin1String(fontData.StyleName)); // if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { // qCWarning(PLATFORMTHEME) << "Requested font type" << fontType << "name=" << fontData.FontName << "forceBold=" << forceBold << "styleHint=" << fontData.StyleHint; // qCWarning(PLATFORMTHEME) << "\t->" << *cachedFont; // } fontInfo = configGroup.readEntry(fontData.ConfigKey, QString()); if (!fontInfo.isEmpty()) { cachedFont->fromString(fontInfo); // if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { // qCWarning(PLATFORMTHEME) << "\tfontInfo=" << fontInfo << "->" << *cachedFont; // } } else { QString fName = cachedFont->toString(); cachedFont->setStyleName(QLatin1String(fontData.StyleName)); if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { qCWarning(PLATFORMTHEME) << "\t" << fName << "+ styleName" << fontData.StyleName << "->" << *cachedFont; } } + // experimental: force outline mode when not using CoreText. This should prevent the FreeType + // font engine from picking up and using X11 bitmap fonts, should those be installed. + if (mUseCoreText) { + cachedFont->setStyleHint(fontData.StyleHint); + } else { + cachedFont->setStyleHint(fontData.StyleHint, QFont::ForceOutline); + } mFonts[fontType] = cachedFont; } return cachedFont; } void KFontSettingsDataMac::dropFontSettingsCache() { if (qobject_cast(QCoreApplication::instance())) { QApplication::setFont(*font(KFontSettingsDataMac::GeneralFont)); } else { QGuiApplication::setFont(*font(KFontSettingsDataMac::GeneralFont)); } } void KFontSettingsDataMac::delayedDBusConnects() { #ifdef DBUS_SUPPORT_ENABLED QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(dropFontSettingsCache())); #endif } diff --git a/src/platformtheme/khintssettings.cpp b/src/platformtheme/khintssettings.cpp index ae63e83..d450476 100644 --- a/src/platformtheme/khintssettings.cpp +++ b/src/platformtheme/khintssettings.cpp @@ -1,434 +1,457 @@ /* This file is part of the KDE libraries * Copyright 2013 Kevin Ottens * Copyright 2013 Aleix Pol Gonzalez * Copyright 2013 Alejandro Fiestas Olivares * * 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. */ #undef QT_NO_CAST_FROM_ASCII #include "khintssettings.h" #include "platformtheme_logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DBUS_SUPPORT_ENABLED #include #include #endif #include #include #include #include #include #ifdef UNIT_TEST #undef HAVE_X11 #define HAVE_X11 0 #endif #if HAVE_X11 #include #include #endif -static const QString defaultLookAndFeelPackage = QStringLiteral("org.kde.breeze.desktop"); +static const QString defaultLookAndFeelPackage = QStringLiteral("com.apple.macintosh.desktop"); KSharedConfigPtr &KHintsSettings::kdeGlobals() { if (!mKdeGlobals) { + verbose = qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE"); if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_CONFIG_FILE")) { - mKdeGlobals = KSharedConfig::openConfig(qgetenv("QT_QPA_PLATFORMTHEME_CONFIG_FILE"), KConfig::NoGlobals); + const auto fname(qgetenv("QT_QPA_PLATFORMTHEME_CONFIG_FILE")); + mKdeGlobals = KSharedConfig::openConfig(fname, KConfig::NoGlobals); + const auto foundFile = QStandardPaths::locate(mKdeGlobals->locationType(), mKdeGlobals->name()); + if (foundFile.isEmpty()) { + qCWarning(PLATFORMTHEME) << "WARNING: could not open config file" << fname + << "in" << QStandardPaths::standardLocations(mKdeGlobals->locationType()); + } } else { mKdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); } } return mKdeGlobals; } +KSharedConfigPtr &KHintsSettings::LnfConfig() +{ + return mLnfConfig; +} + +KSharedConfigPtr &KHintsSettings::DefaultLnfConfig() +{ + return mDefaultLnfConfig; +} + KHintsSettings::KHintsSettings() : QObject(0) { kdeGlobals(); KConfigGroup cg(mKdeGlobals, "KDE"); // try to extract the proper defaults file from a lookandfeel package const QString looknfeel = cg.readEntry("LookAndFeelPackage", defaultLookAndFeelPackage); mDefaultLnfConfig = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/look-and-feel/") + looknfeel + QStringLiteral("/contents/defaults"))); if (looknfeel != defaultLookAndFeelPackage) { mLnfConfig = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/look-and-feel/") + defaultLookAndFeelPackage + QStringLiteral("/contents/defaults"))); + if (verbose) { + qCWarning(PLATFORMTHEME) << "User-selected look-and-feel:" << looknfeel << mDefaultLnfConfig->name(); + qCWarning(PLATFORMTHEME) << "Default look-and-feel:" << defaultLookAndFeelPackage << mLnfConfig->name(); + } } const auto cursorBlinkRate = cg.readEntry("CursorBlinkRate", 1000); m_hints[QPlatformTheme::CursorFlashTime] = cursorBlinkRate > 0 ? qBound(200, cursorBlinkRate, 2000) : 0; // 0 => no blinking m_hints[QPlatformTheme::MouseDoubleClickInterval] = cg.readEntry("DoubleClickInterval", 400); m_hints[QPlatformTheme::StartDragDistance] = cg.readEntry("StartDragDist", 10); m_hints[QPlatformTheme::StartDragTime] = cg.readEntry("StartDragTime", 500); KConfigGroup cgToolbar(mKdeGlobals, "Toolbar style"); m_hints[QPlatformTheme::ToolButtonStyle] = toolButtonStyle(cgToolbar); KConfigGroup cgToolbarIcon(mKdeGlobals, "MainToolbarIcons"); m_hints[QPlatformTheme::ToolBarIconSize] = cgToolbarIcon.readEntry("Size", 22); m_hints[QPlatformTheme::ItemViewActivateItemOnSingleClick] = cg.readEntry("SingleClick", true); m_hints[QPlatformTheme::SystemIconThemeName] = readConfigValue(QStringLiteral("Icons"), QStringLiteral("Theme"), QStringLiteral("oxygen")); m_hints[QPlatformTheme::SystemIconFallbackThemeName] = QStringLiteral("hicolor"); m_hints[QPlatformTheme::IconThemeSearchPaths] = xdgIconThemePaths(); QStringList styleNames; styleNames << QStringLiteral("oxygen") << QStringLiteral("fusion") << QStringLiteral("windows"); const QString configuredStyle = cg.readEntry("widgetStyle", QString()); if (!configuredStyle.isEmpty()) { styleNames.removeOne(configuredStyle); styleNames.prepend(configuredStyle); } const QString lnfStyle = readConfigValue(QStringLiteral("KDE"), QStringLiteral("widgetStyle"), QString()).toString(); if (!lnfStyle.isEmpty()) { styleNames.removeOne(lnfStyle); styleNames.prepend(lnfStyle); } m_hints[QPlatformTheme::StyleNames] = styleNames; m_hints[QPlatformTheme::DialogButtonBoxLayout] = QDialogButtonBox::KdeLayout; m_hints[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = cg.readEntry("ShowIconsOnPushButtons", true); m_hints[QPlatformTheme::UseFullScreenForPopupMenu] = true; m_hints[QPlatformTheme::KeyboardScheme] = QPlatformTheme::KdeKeyboardScheme; m_hints[QPlatformTheme::UiEffects] = cg.readEntry("GraphicEffectsLevel", 0) != 0 ? QPlatformTheme::GeneralUiEffect : 0; m_hints[QPlatformTheme::IconPixmapSizes] = QVariant::fromValue(QList() << 512 << 256 << 128 << 64 << 32 << 22 << 16 << 8); m_hints[QPlatformTheme::WheelScrollLines] = cg.readEntry("WheelScrollLines", 3); if (qobject_cast(QCoreApplication::instance())) { QApplication::setWheelScrollLines(cg.readEntry("WheelScrollLines", 3)); } updateShowIconsInMenuItems(cg); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) m_hints[QPlatformTheme::ShowShortcutsInContextMenus] = true; #endif #ifdef DBUS_SUPPORT_ENABLED QMetaObject::invokeMethod(this, "delayedDBusConnects", Qt::QueuedConnection); #endif QMetaObject::invokeMethod(this, "setupIconLoader", Qt::QueuedConnection); loadPalettes(); } KHintsSettings::~KHintsSettings() { qDeleteAll(m_palettes); } QVariant KHintsSettings::readConfigValue(const QString &group, const QString &key, const QVariant &defaultValue) { KConfigGroup userCg(mKdeGlobals, group); QVariant value = userCg.readEntry(key, QString()); if (!value.isNull()) { return value; } if (mLnfConfig) { KConfigGroup lnfCg(mLnfConfig, "kdeglobals"); lnfCg = KConfigGroup(&lnfCg, group); if (lnfCg.isValid()) { value = lnfCg.readEntry(key, defaultValue); if (!value.isNull()) { return value; } } } +#ifndef Q_OS_MACOS KConfigGroup lnfCg(mDefaultLnfConfig, group); if (lnfCg.isValid()) { return lnfCg.readEntry(key, defaultValue); } +#endif return defaultValue; } QStringList KHintsSettings::xdgIconThemePaths() const { QStringList paths; // make sure we have ~/.local/share/icons in paths if it exists paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory); const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons")); if (homeIconDir.isDir()) { paths << homeIconDir.absoluteFilePath(); } return paths; } void KHintsSettings::delayedDBusConnects() { #ifdef DBUS_SUPPORT_ENABLED QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), this, SLOT(toolbarStyleChanged())); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"), this, SLOT(slotNotifyChange(int,int))); #endif } void KHintsSettings::setupIconLoader() { connect(KIconLoader::global(), &KIconLoader::iconChanged, this, &KHintsSettings::iconChanged); } void KHintsSettings::toolbarStyleChanged() { mKdeGlobals->reparseConfiguration(); KConfigGroup cg(mKdeGlobals, "Toolbar style"); m_hints[QPlatformTheme::ToolButtonStyle] = toolButtonStyle(cg); //from gtksymbol.cpp QWidgetList widgets = QApplication::allWidgets(); for (int i = 0; i < widgets.size(); ++i) { QWidget *widget = widgets.at(i); if (qobject_cast(widget)) { QEvent event(QEvent::StyleChange); QApplication::sendEvent(widget, &event); } } } void KHintsSettings::slotNotifyChange(int type, int arg) { mKdeGlobals->reparseConfiguration(); KConfigGroup cg(mKdeGlobals, "KDE"); switch (type) { case PaletteChanged: { loadPalettes(); //QApplication::setPalette and QGuiApplication::setPalette are different functions //and non virtual. Call the correct one if (qobject_cast(QCoreApplication::instance())) { QPalette palette = *m_palettes[QPlatformTheme::SystemPalette]; QApplication::setPalette(palette); // QTBUG QGuiApplication::paletteChanged() signal is only emitted by QGuiApplication // so things like SystemPalette QtQuick item that use it won't notice a palette // change when a QApplication which causes e.g. QML System Settings modules to not update emit qApp->paletteChanged(palette); } else if (qobject_cast(QCoreApplication::instance())) { QGuiApplication::setPalette(*m_palettes[QPlatformTheme::SystemPalette]); } break; } case SettingsChanged: { SettingsCategory category = static_cast(arg); if (category == SETTINGS_QT || category == SETTINGS_MOUSE) { updateQtSettings(cg); } else if (category == SETTINGS_STYLE) { m_hints[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = cg.readEntry("ShowIconsOnPushButtons", true); m_hints[QPlatformTheme::UiEffects] = cg.readEntry("GraphicEffectsLevel", 0) != 0 ? QPlatformTheme::GeneralUiEffect : 0; updateShowIconsInMenuItems(cg); } break; } case ToolbarStyleChanged: { toolbarStyleChanged(); break; } case IconChanged: iconChanged(arg); //Once the KCM is ported to use IconChanged, this should not be needed break; case CursorChanged: updateCursorTheme(); break; case StyleChanged: { QApplication *app = qobject_cast(QCoreApplication::instance()); if (!app) { return; } const QString theme = cg.readEntry("widgetStyle", QString()); if (theme.isEmpty()) { return; } QStringList styleNames; styleNames << cg.readEntry("widgetStyle", QString()) << QStringLiteral("oxygen") << QStringLiteral("fusion") << QStringLiteral("windows"); const QString lnfStyle = readConfigValue(QStringLiteral("KDE"), QStringLiteral("widgetStyle"), QString()).toString(); if (!lnfStyle.isEmpty() && !styleNames.contains(lnfStyle)) { styleNames.prepend(lnfStyle); } m_hints[QPlatformTheme::StyleNames] = styleNames; app->setStyle(theme); loadPalettes(); break; } default: qCWarning(PLATFORMTHEME) << "Unknown type of change in KGlobalSettings::slotNotifyChange: " << type; } } void KHintsSettings::iconChanged(int group) { KIconLoader::Group iconGroup = (KIconLoader::Group) group; if (iconGroup != KIconLoader::MainToolbar) { m_hints[QPlatformTheme::SystemIconThemeName] = readConfigValue(QStringLiteral("Icons"), QStringLiteral("Theme"), QStringLiteral("oxygen")); return; } const int currentSize = KIconLoader::global()->currentSize(KIconLoader::MainToolbar); if (m_hints[QPlatformTheme::ToolBarIconSize] == currentSize) { return; } m_hints[QPlatformTheme::ToolBarIconSize] = currentSize; //If we are not a QApplication, means that we are a QGuiApplication, then we do nothing. if (!qobject_cast(QCoreApplication::instance())) { return; } QWidgetList widgets = QApplication::allWidgets(); Q_FOREACH (QWidget *widget, widgets) { if (qobject_cast(widget) || qobject_cast(widget)) { QEvent event(QEvent::StyleChange); QApplication::sendEvent(widget, &event); } } } void KHintsSettings::updateQtSettings(KConfigGroup &cg) { int flash = qBound(200, cg.readEntry("CursorBlinkRate", 1000), 2000); m_hints[QPlatformTheme::CursorFlashTime] = flash; int doubleClickInterval = cg.readEntry("DoubleClickInterval", 400); m_hints[QPlatformTheme::MouseDoubleClickInterval] = doubleClickInterval; int startDragDistance = cg.readEntry("StartDragDist", 10); m_hints[QPlatformTheme::StartDragDistance] = startDragDistance; int startDragTime = cg.readEntry("StartDragTime", 10); m_hints[QPlatformTheme::StartDragTime] = startDragTime; m_hints[QPlatformTheme::ItemViewActivateItemOnSingleClick] = cg.readEntry("SingleClick", true); updateShowIconsInMenuItems(cg); int wheelScrollLines = cg.readEntry("WheelScrollLines", 3); m_hints[QPlatformTheme::WheelScrollLines] = wheelScrollLines; QApplication *app = qobject_cast(QCoreApplication::instance()); if (app) { QApplication::setWheelScrollLines(cg.readEntry("WheelScrollLines", 3)); } } void KHintsSettings::updateShowIconsInMenuItems(KConfigGroup &cg) { bool showIcons = cg.readEntry("ShowIconsInMenuItems", !QApplication::testAttribute(Qt::AA_DontShowIconsInMenus)); QCoreApplication::setAttribute(Qt::AA_DontShowIconsInMenus, !showIcons); } Qt::ToolButtonStyle KHintsSettings::toolButtonStyle(const KConfigGroup &cg) const { const QString buttonStyle = cg.readEntry("ToolButtonStyle", "TextBesideIcon").toLower(); return buttonStyle == QLatin1String("textbesideicon") ? Qt::ToolButtonTextBesideIcon : buttonStyle == QLatin1String("icontextright") ? Qt::ToolButtonTextBesideIcon : buttonStyle == QLatin1String("textundericon") ? Qt::ToolButtonTextUnderIcon : buttonStyle == QLatin1String("icontextbottom") ? Qt::ToolButtonTextUnderIcon : buttonStyle == QLatin1String("textonly") ? Qt::ToolButtonTextOnly : Qt::ToolButtonIconOnly; } void KHintsSettings::loadPalettes() { qDeleteAll(m_palettes); m_palettes.clear(); if (mKdeGlobals->hasGroup("Colors:View")) { m_palettes[QPlatformTheme::SystemPalette] = new QPalette(KColorScheme::createApplicationPalette(mKdeGlobals)); } else { KConfigGroup cg(mKdeGlobals, "KDE"); const QString looknfeel = cg.readEntry("LookAndFeelPackage", defaultLookAndFeelPackage); QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/look-and-feel/") + looknfeel + QStringLiteral("/contents/colors")); if (!path.isEmpty()) { m_palettes[QPlatformTheme::SystemPalette] = new QPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(path))); return; } const QString scheme = readConfigValue(QStringLiteral("General"), QStringLiteral("ColorScheme"), QStringLiteral("Oxygen")).toString(); path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/") + scheme + QStringLiteral(".colors")); if (!path.isEmpty()) { m_palettes[QPlatformTheme::SystemPalette] = new QPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(path))); } } } void KHintsSettings::updateCursorTheme() { KConfig config(QStringLiteral("kcminputrc")); KConfigGroup g(&config, "Mouse"); int size = g.readEntry("cursorSize", -1); // Default cursor size is 16 points if (size == -1) { if (QScreen *s = QGuiApplication::primaryScreen()) { size = s->logicalDotsPerInchY() * 16 / 72; } else { size = 0; } } #if HAVE_X11 if (QX11Info::isPlatformX11()) { const QString theme = g.readEntry("cursorTheme", QString()); // Note that in X11R7.1 and earlier, calling XcursorSetTheme() // with a NULL theme would cause Xcursor to use "default", but // in 7.2 and later it will cause it to revert to the theme that // was configured when the application was started. XcursorSetTheme(QX11Info::display(), theme.isNull() ? "default" : QFile::encodeName(theme).constData()); XcursorSetDefaultSize(QX11Info::display(), size); } #endif } diff --git a/src/platformtheme/khintssettings.h b/src/platformtheme/khintssettings.h index 8cc58dd..bdd2ec6 100644 --- a/src/platformtheme/khintssettings.h +++ b/src/platformtheme/khintssettings.h @@ -1,102 +1,106 @@ /* This file is part of the KDE libraries * Copyright 2013 Alejandro Fiestas Olivares * * 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 KHINTS_SETTINGS_H #define KHINTS_SETTINGS_H #include #include #include #include class KConfigGroup; class QPalette; class KHintsSettings : public QObject { Q_OBJECT public: /** * An identifier for change signals. * @note Copied from KGlobalSettings */ enum ChangeType { PaletteChanged = 0, FontChanged, StyleChanged, SettingsChanged, IconChanged, CursorChanged, ToolbarStyleChanged, ClipboardConfigChanged, BlockShortcuts, NaturalSortingChanged }; /** * Valid values for the settingsChanged signal * @note Copied from KGlobalSettings */ enum SettingsCategory { SETTINGS_MOUSE, SETTINGS_COMPLETION, SETTINGS_PATHS, SETTINGS_POPUPMENU, SETTINGS_QT, SETTINGS_SHORTCUTS, SETTINGS_LOCALE, SETTINGS_STYLE }; explicit KHintsSettings(); virtual ~KHintsSettings(); inline QVariant hint(QPlatformTheme::ThemeHint hint) const { return m_hints[hint]; } inline QPalette *palette(QPlatformTheme::Palette type) const { return m_palettes[type]; } QStringList xdgIconThemePaths() const; protected Q_SLOTS: void delayedDBusConnects(); void setupIconLoader(); void toolbarStyleChanged(); void slotNotifyChange(int type, int arg); protected: KSharedConfigPtr &kdeGlobals(); + KSharedConfigPtr &LnfConfig(); + KSharedConfigPtr &DefaultLnfConfig(); + QVariant readConfigValue(const QString &group, const QString &key, const QVariant &defaultValue); void loadPalettes(); void iconChanged(int group); void updateQtSettings(KConfigGroup &cg); void updateShowIconsInMenuItems(KConfigGroup &cg); Qt::ToolButtonStyle toolButtonStyle(const KConfigGroup &cg) const; void updateCursorTheme(); inline QHash &palettes() { return m_palettes; } inline QHash &hints() { return m_hints; } private: QHash m_palettes; QHash m_hints; KSharedConfigPtr mKdeGlobals; KSharedConfigPtr mDefaultLnfConfig; KSharedConfigPtr mLnfConfig; + bool verbose; }; #endif //KHINTS_SETTINGS_H diff --git a/src/platformtheme/khintssettingsmac.h b/src/platformtheme/khintssettingsmac.h index 57f00ff..71e17c0 100644 --- a/src/platformtheme/khintssettingsmac.h +++ b/src/platformtheme/khintssettingsmac.h @@ -1,55 +1,59 @@ /* This file is part of the KDE libraries * Copyright 2013 Alejandro Fiestas Olivares * Copyright 2015 René J.V. Bertin * Copyright 2013 Aleix Pol Gonzalez * Copyright 2013 Alejandro Fiestas Olivares * Copyright 2015 René J.V. Bertin #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DBUS_SUPPORT_ENABLED #include #include #endif #include #include #include #include #include class KdeProxyStyle : public QProxyStyle { public: KdeProxyStyle(const QString &styleName) : QProxyStyle(styleName) { ; } int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption *option = 0, const QWidget *widget = 0) const { int spacing = QProxyStyle::layoutSpacing(control1, control2, orientation, option, widget); qCWarning(PLATFORMTHEME) << "layoutSpacing=" << spacing; if (spacing > 2) { spacing /= 2; } return spacing; } }; -KHintsSettingsMac::KHintsSettingsMac() - : styleProxy(0) +KHintsSettingsMac::KHintsSettingsMac(KdeMacTheme *theme) + : mTheme(theme) + , styleProxy(0) { KSharedConfigPtr mKdeGlobals = kdeGlobals(); - if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + if (mTheme->verbose) { if (!mKdeGlobals->name().isEmpty()) { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "config file:" << mKdeGlobals->name() << "(" << QStandardPaths::locate(mKdeGlobals->locationType(), mKdeGlobals->name()) << ")"; + } else { + qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "config file:" << mKdeGlobals << "has no known name"; } } KConfigGroup cg(mKdeGlobals, "KDE"); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "config group" << mKdeGlobals->name() << "." << cg.name() + << "exists=" << cg.exists() + << "valid=" << cg.isValid() + << "groups=" << cg.groupList() + << "keys=" << cg.keyList(); + } // we're overriding whatever the parent class configured hints().clear(); KConfigGroup cgToolbar(mKdeGlobals, "Toolbar style"); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "config group" << mKdeGlobals->name() << "." << cgToolbar.name() + << "exists=" << cgToolbar.exists() + << "valid=" << cgToolbar.isValid() + << "groups=" << cgToolbar.groupList() + << "keys=" << cgToolbar.keyList(); + } hints()[QPlatformTheme::ToolButtonStyle] = toolButtonStyle(cgToolbar); KConfigGroup cgToolbarIcon(mKdeGlobals, "MainToolbarIcons"); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "config group" << mKdeGlobals->name() << "." << cgToolbarIcon.name() + << "exists=" << cgToolbarIcon.exists() + << "valid=" << cgToolbarIcon.isValid() + << "groups=" << cgToolbarIcon.groupList() + << "keys=" << cgToolbarIcon.keyList(); + } hints()[QPlatformTheme::ToolBarIconSize] = cgToolbarIcon.readEntry("Size", 22); hints()[QPlatformTheme::ItemViewActivateItemOnSingleClick] = cg.readEntry("SingleClick", true); #ifdef KDEMACTHEME_ADD_ICONTHEMESETTINGS // The new default Breeze icon theme is svg based and looks more out of place than the older Oxygen theme // which is PNG-based, and thus easier to use with/in the Finder. hints()[QPlatformTheme::SystemIconThemeName] = readConfigValue(QStringLiteral("Icons"), QStringLiteral("Theme"), QStringLiteral("oxygen")); hints()[QPlatformTheme::IconThemeSearchPaths] = xdgIconThemePaths(); #endif QStringList styleNames; styleNames << QStringLiteral("aqua") << QStringLiteral("macintosh") << QStringLiteral("fusion") << QStringLiteral("windows"); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "initial widget style list:" << styleNames; + } const QString configuredStyle = cg.readEntry("widgetStyle", QString()); if (!configuredStyle.isEmpty()) { styleNames.removeOne(configuredStyle); styleNames.prepend(configuredStyle); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "Found widgetStyle" << configuredStyle << "in config file"; + } } const QString lnfStyle = readConfigValue(QStringLiteral("KDE"), QStringLiteral("widgetStyle"), QString()).toString(); - if (!lnfStyle.isEmpty()) { + if (!lnfStyle.isEmpty() && lnfStyle != configuredStyle) { styleNames.removeOne(lnfStyle); styleNames.prepend(lnfStyle); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "Found widgetStyle" << lnfStyle << "look-and-feel definition" + << (LnfConfig() ? LnfConfig()->name() : QStringLiteral("???")); + } + } + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "final widget style list:" << styleNames; } hints()[QPlatformTheme::StyleNames] = styleNames; checkNativeTheme(configuredStyle); hints()[QPlatformTheme::DialogButtonBoxLayout] = QDialogButtonBox::MacLayout; hints()[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = cg.readEntry("ShowIconsOnPushButtons", false); hints()[QPlatformTheme::UseFullScreenForPopupMenu] = true; hints()[QPlatformTheme::KeyboardScheme] = QPlatformTheme::MacKeyboardScheme; hints()[QPlatformTheme::UiEffects] = cg.readEntry("GraphicEffectsLevel", 0) != 0 ? QPlatformTheme::GeneralUiEffect : 0; // this would be what we should return for IconPixmapSizes if we wanted to copy the system defaults: // qreal devicePixelRatio = qGuiApp->devicePixelRatio(); // QList sizes; // sizes << 16 * devicePixelRatio // << 32 * devicePixelRatio // << 64 * devicePixelRatio // << 128 * devicePixelRatio; // hints()[QPlatformTheme::IconPixmapSizes] = QVariant::fromValue(sizes); hints()[QPlatformTheme::WheelScrollLines] = cg.readEntry("WheelScrollLines", 3); if (qobject_cast(QCoreApplication::instance())) { QApplication::setWheelScrollLines(cg.readEntry("WheelScrollLines", 3)); } updateShowIconsInMenuItems(cg); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) m_hints[QPlatformTheme::ShowShortcutsInContextMenus] = true; #endif #ifdef DBUS_SUPPORT_ENABLED QMetaObject::invokeMethod(this, "delayedDBusConnects", Qt::QueuedConnection); #endif loadPalettes(); } KHintsSettingsMac::~KHintsSettingsMac() { } // adapted from QGenericUnixTheme::xdgIconThemePaths() QStringList KHintsSettingsMac::xdgIconThemePaths() const { QStringList paths; // make sure we have ~/.local/share/icons in paths if it exists paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory); // Add home directory first in search path const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons")); if (homeIconDir.isDir()) { paths.prepend(homeIconDir.absoluteFilePath()); } QStringList xdgDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); if (xdgDirs.isEmpty()) { xdgDirs << QStringLiteral("/opt/local/share") << QStringLiteral("/usr/local/share") << QStringLiteral("/usr/share"); } foreach (const QString &xdgDir, xdgDirs) { const QFileInfo xdgIconsDir(xdgDir + QStringLiteral("/icons")); if (xdgIconsDir.isDir()) { paths.append(xdgIconsDir.absoluteFilePath()); } const QFileInfo pixmapsIconsDir(xdgDir + QStringLiteral("/pixmaps")); if (pixmapsIconsDir.isDir()) { paths.append(pixmapsIconsDir.absoluteFilePath()); } } return paths; } void KHintsSettingsMac::delayedDBusConnects() { #ifdef DBUS_SUPPORT_ENABLED QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), this, SLOT(toolbarStyleChanged())); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"), this, SLOT(slotNotifyChange(int,int))); #endif } void KHintsSettingsMac::checkNativeTheme(const QString &theme) { #if 0 // using a QStyleProxy messes up the colour palette for some reason, so this feature is deactivated for now if (theme.isEmpty() || theme.compare(QStringLiteral("macintosh"), Qt::CaseInsensitive) == 0) { if (qApp) { if (!styleProxy) { styleProxy = new KdeProxyStyle(QStringLiteral("macintosh")); } // styleProxy will be owned by QApplication after this, so no point deleting it qApp->setStyle(styleProxy); loadPalettes(); } } #endif // do this only when certain that there's a QApplication instance: // QApplication *app = qobject_cast(QCoreApplication::instance()); // if (app) { // qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "platform theme:" << app->style()->objectName(); // } } void KHintsSettingsMac::slotNotifyChange(int type, int arg) { KHintsSettings::slotNotifyChange(type,arg); KSharedConfigPtr mKdeGlobals = kdeGlobals(); KConfigGroup cg(mKdeGlobals, "KDE"); switch (type) { case SettingsChanged: { SettingsCategory category = static_cast(arg); if (category == SETTINGS_STYLE) { hints()[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = cg.readEntry("ShowIconsOnPushButtons", false); updateShowIconsInMenuItems(cg); } break; } case StyleChanged: { QApplication *app = qobject_cast(QCoreApplication::instance()); if (!app) { return; } const QString theme = cg.readEntry("widgetStyle", QString()); checkNativeTheme(theme); if (theme.isEmpty()) { return; } QStringList styleNames; styleNames << cg.readEntry("widgetStyle", QString()) << QStringLiteral("aqua") << QStringLiteral("macintosh") << QStringLiteral("fusion") << QStringLiteral("windows"); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "initial widget style list:" << styleNames; + } const QString lnfStyle = readConfigValue(QStringLiteral("KDE"), QStringLiteral("widgetStyle"), QString()).toString(); if (!lnfStyle.isEmpty() && !styleNames.contains(lnfStyle)) { styleNames.prepend(lnfStyle); + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "Found widgetStyle" << lnfStyle << "look-and-feel definition" + << (LnfConfig() ? LnfConfig()->name() : QStringLiteral("???")); + } + } + if (mTheme->verbose) { + qCWarning(PLATFORMTHEME) << "final widget style list:" << styleNames; } hints()[QPlatformTheme::StyleNames] = styleNames; break; } } } void KHintsSettingsMac::iconChanged(int group) { KIconLoader::Group iconGroup = (KIconLoader::Group) group; if (iconGroup != KIconLoader::MainToolbar) { hints()[QPlatformTheme::SystemIconThemeName] = readConfigValue(QStringLiteral("Icons"), QStringLiteral("Theme"), QStringLiteral("oxygen")); return; } return KHintsSettings::iconChanged(group); } Qt::ToolButtonStyle KHintsSettingsMac::toolButtonStyle(const KConfigGroup &cg) const { const QString buttonStyle = cg.readEntry("ToolButtonStyle", "TextUnderIcon").toLower(); return buttonStyle == QLatin1String("textbesideicon") ? Qt::ToolButtonTextBesideIcon : buttonStyle == QLatin1String("icontextright") ? Qt::ToolButtonTextBesideIcon : buttonStyle == QLatin1String("textundericon") ? Qt::ToolButtonTextUnderIcon : buttonStyle == QLatin1String("icontextbottom") ? Qt::ToolButtonTextUnderIcon : buttonStyle == QLatin1String("textonly") ? Qt::ToolButtonTextOnly : Qt::ToolButtonIconOnly; } void KHintsSettingsMac::loadPalettes() { qDeleteAll(palettes()); palettes().clear(); KSharedConfigPtr mKdeGlobals = kdeGlobals(); if (mKdeGlobals->hasGroup("Colors:View")) { palettes()[QPlatformTheme::SystemPalette] = new QPalette(KColorScheme::createApplicationPalette(mKdeGlobals)); } else { const QString scheme = readConfigValue(QStringLiteral("General"), QStringLiteral("ColorScheme"), QStringLiteral("Mac OSX Graphite")).toString(); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/") + scheme + QStringLiteral(".colors")); if (!path.isEmpty()) { palettes()[QPlatformTheme::SystemPalette] = new QPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(path))); } } } void KHintsSettingsMac::updateCursorTheme() { } diff --git a/src/platformtheme/main_cocoa.cpp b/src/platformtheme/main_cocoa.cpp index 41c4084..ac1ff0e 100644 --- a/src/platformtheme/main_cocoa.cpp +++ b/src/platformtheme/main_cocoa.cpp @@ -1,77 +1,110 @@ /* 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. */ #include #include #include #include #include +#include #include "kdemactheme.h" +#include "platformtheme_logging.h" #include // We call ourselves the "cocoa" platform theme plugin. Doing that means we replace // the native platform theme. That is not a problem because the KdeMacTheme class proxies // the native theme. It just means that the native theme inherits a number of KDE // extensions, which are loaded automatically without user intervention like setting // an env. variable. // NB NB // This file should be kept in sync with main_kde.cpp !! // NB NB +static QPluginLoader unloadProtection; + class CocoaPlatformThemePlugin : public QPlatformThemePlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPA.QPlatformThemeFactoryInterface.5.1" FILE "cocoaplatformtheme.json") public: CocoaPlatformThemePlugin(QObject *parent = Q_NULLPTR) : QPlatformThemePlugin(parent) { + if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_DISABLED")) { + qCWarning(PLATFORMTHEME) << "The Cocoa platform theme plugin has been disabled because of QT_QPA_PLATFORMTHEME_DISABLED"; + } if (qEnvironmentVariableIsSet("KDE_LAYOUT_USES_WIDGET_RECT")) { qApp->installEventFilter(this); } } + ~CocoaPlatformThemePlugin() + { + if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + qCWarning(PLATFORMTHEME) << Q_FUNC_INFO; + } + } QPlatformTheme *create(const QString &key, const QStringList ¶mList) override { Q_UNUSED(key) Q_UNUSED(paramList) - return new KdeMacTheme; + if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_DISABLED")) { + if (unloadProtection.fileName().isEmpty()) { + unloadProtection.setFileName(QStringLiteral("platformthemes/" PLATFORM_PLUGIN_FILE_NAME)); + if (unloadProtection.fileName().isEmpty()) { + // try with the non-standard extension - probably redundant but I won't + // count on Qt to try the .so extension on Mac forever. + unloadProtection.setFileName(QStringLiteral("platformthemes/" PLATFORM_PLUGIN_FILE_NAME ".so")); + } + // using a global static loader instance should already prevent us from being unloaded + // too early; add an additional layer of protection: + unloadProtection.setLoadHints(QLibrary::PreventUnloadHint); + bool success = unloadProtection.load(); + if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + qCWarning(PLATFORMTHEME) << "loaded from:" + << unloadProtection.fileName() << "unload protection:" << success; + } + } + return new KdeMacTheme; + } else { + return nullptr; + } } protected: bool eventFilter(QObject *object, QEvent *event) override { switch (event->type()) { case QEvent::ChildAdded: { QChildEvent *childEvent = static_cast(event); if (childEvent->child()->isWidgetType()) { QWidget* theChildWidget = qobject_cast(childEvent->child()); theChildWidget->setAttribute(Qt::WA_LayoutUsesWidgetRect, true); } } } return qApp->eventFilter(object, event); } }; #include "main_cocoa.moc" diff --git a/src/platformtheme/main_kde.cpp b/src/platformtheme/main_kde.cpp index 3de5d18..4ff3dcc 100644 --- a/src/platformtheme/main_kde.cpp +++ b/src/platformtheme/main_kde.cpp @@ -1,75 +1,102 @@ /* 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. */ #include #include #include #include #include +#include #include "kdemactheme.h" +#include "platformtheme_logging.h" #include // We use a different internal class name for the Mac KDE platform theme plugin, but it will still // identify itself as "KDE" to Qt. This ensures that it will also be picked up when using // the XCB platform plugin (without patching it). // NB NB // This file should be kept in sync with main_kde.cpp !! // NB NB +static QPluginLoader unloadProtection; + class KdePlatformThemePlugin : public QPlatformThemePlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QPA.QPlatformThemeFactoryInterface.5.1" FILE "kdeplatformtheme.json") public: KdePlatformThemePlugin(QObject *parent = Q_NULLPTR) : QPlatformThemePlugin(parent) { + if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_DISABLED")) { + qCWarning(PLATFORMTHEME) << "The KDE platform theme plugin has been disabled because of QT_QPA_PLATFORMTHEME_DISABLED"; + return; + } if (qEnvironmentVariableIsSet("KDE_LAYOUT_USES_WIDGET_RECT")) { qApp->installEventFilter(this); } } QPlatformTheme *create(const QString &key, const QStringList ¶mList) override { Q_UNUSED(key) Q_UNUSED(paramList) - return new KdeMacTheme; + if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_DISABLED")) { + if (unloadProtection.fileName().isEmpty()) { + unloadProtection.setFileName(QStringLiteral("platformthemes/" PLATFORM_PLUGIN_FILE_NAME)); + if (unloadProtection.fileName().isEmpty()) { + // try with the non-standard extension + unloadProtection.setFileName(QStringLiteral("platformthemes/" PLATFORM_PLUGIN_FILE_NAME ".so")); + } + // using a global static loader instance should already prevent us from being unloaded + // too early; add an additional layer of protection: + unloadProtection.setLoadHints(QLibrary::PreventUnloadHint); + bool success = unloadProtection.load(); + if (qEnvironmentVariableIsSet("QT_QPA_PLATFORMTHEME_VERBOSE")) { + qCWarning(PLATFORMTHEME) << "loaded from:" + << unloadProtection.fileName() << "unload protection:" << success; + } + } + return new KdeMacTheme; + } else { + return nullptr; + } } protected: bool eventFilter(QObject *object, QEvent *event) { switch (event->type()) { case QEvent::ChildAdded: { QChildEvent *childEvent = static_cast(event); if (childEvent->child()->isWidgetType()) { QWidget* theChildWidget = qobject_cast(childEvent->child()); theChildWidget->setAttribute(Qt::WA_LayoutUsesWidgetRect, true); } } } return qApp->eventFilter(object, event); } }; #include "main_kde.moc" diff --git a/plintegration-cumpatch1.diff b/src/platformtheme/plintegration-cumpatch1.diff similarity index 100% rename from plintegration-cumpatch1.diff rename to src/platformtheme/plintegration-cumpatch1.diff