diff --git a/src/platformtheme/kdemactheme.mm b/src/platformtheme/kdemactheme.mm index e7d3ec1..6e213b0 100644 --- a/src/platformtheme/kdemactheme.mm +++ b/src/platformtheme/kdemactheme.mm @@ -1,755 +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(platformThemeName); } else { nativeTheme = Q_NULLPTR; } if (!nativeTheme) { warnNoNativeTheme(); } else if (verbose) { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "loading platform theme plugin" << QLatin1String(PLATFORM_PLUGIN_THEME_NAME) << "for platform" << platformName; } m_fontsData = Q_NULLPTR; m_hints = Q_NULLPTR; loadSettings(); m_eventFilter = new KdeMacThemeEventFilter; #ifndef QT_NO_GESTURES qApp->installEventFilter(m_eventFilter); #endif #ifdef ADD_MENU_KEY m_eventFilter->m_keyboardMonitor = 0; @autoreleasepool { // set up a keyboard event monitor m_eventFilter->m_keyboardMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:KdeMacThemeEventFilter::keyboardMonitorMask handler:^(NSEvent* event) { return m_eventFilter->nativeEventHandler(event); }]; } if (m_eventFilter->m_keyboardMonitor) { m_eventFilter->enabled = true; } else { qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "Could not create a global keyboard monitor"; } #endif // for some reason our Qt native event filter is apparently never called. qApp->installNativeEventFilter(m_eventFilter->qtNativeFilter); #ifdef USE_PLCRASHREPORTER static PLCrashReporter *crashReporter = nil; if (!crashReporter) { crashReporter = [[PLCrashReporter alloc] initWithConfiguration:[PLCrashReporterConfig defaultConfiguration]]; } NSError *error; if ([crashReporter hasPendingCrashReport]) @autoreleasepool { NSData *crashData; PLCrashReport *report = nil; crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error]; if (crashData) { report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease]; } if (report) { // report could be sent to KAboutData::applicationData().bugAddress() // using QDesktopServices::openUrl("mailto:") qCWarning(PLATFORMTHEME) << qApp->applicationName() << "crashed on" << QString::fromNSString([report.systemInfo.timestamp description]); qCWarning(PLATFORMTHEME) << "\twith signal" << QString::fromNSString(report.signalInfo.name) << "code" << QString::fromNSString(report.signalInfo.code) << "at address" << report.signalInfo.address; } [crashReporter purgePendingCrashReport]; } if (![crashReporter enableCrashReporterAndReturnError: &error]) { NSLog(@"Warning: Could not enable crash reporter: %@", error); } #endif } KdeMacTheme::~KdeMacTheme() { delete nativeTheme; if (m_eventFilter) { qApp->removeNativeEventFilter(m_eventFilter->qtNativeFilter); #ifdef ADD_MENU_KEY m_eventFilter->enabled = false; if (m_eventFilter->m_keyboardMonitor) { @autoreleasepool { [NSEvent removeMonitor:m_eventFilter->m_keyboardMonitor]; } } #endif } delete m_eventFilter; m_eventFilter = 0; } QPlatformMenuItem* KdeMacTheme::createPlatformMenuItem() const { if (nativeTheme) { return nativeTheme->createPlatformMenuItem(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenuItem(); } } QPlatformMenu* KdeMacTheme::createPlatformMenu() const { if (nativeTheme) { return nativeTheme->createPlatformMenu(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenu(); } } QPlatformMenuBar* KdeMacTheme::createPlatformMenuBar() const { if (nativeTheme) { return nativeTheme->createPlatformMenuBar(); } else { warnNoNativeTheme(); return QPlatformTheme::createPlatformMenuBar(); } } QVariant KdeMacTheme::themeHint(QPlatformTheme::ThemeHint hintType) const { QVariant hint = m_hints->hint(hintType); if (hint.isValid()) { if (verbose) { qCWarning(PLATFORMTHEME) << "themeHint" << hintType << ":" << hint; } return hint; } else { if (nativeTheme) { if (verbose) { qCWarning(PLATFORMTHEME) << "Using native theme for themeHint" << hintType << ":" << nativeTheme->themeHint(hintType); } return nativeTheme->themeHint(hintType); } return QPlatformTheme::themeHint(hintType); } } const QPalette *KdeMacTheme::palette(Palette type) const { QPalette *palette = m_hints->palette(type); if (palette) { return palette; } else { if (nativeTheme) { return nativeTheme->palette(type); } return QPlatformTheme::palette(type); } } KFontSettingsDataMac::FontTypes KdeMacTheme::fontType(QPlatformTheme::Font type) const { KFontSettingsDataMac::FontTypes ftype; switch (type) { default: ftype = KFontSettingsDataMac::FontTypes(KdePlatformTheme::fontType(type)); break; case MessageBoxFont: ftype = KFontSettingsDataMac::MessageBoxFont; break; } return ftype; } const QFont *KdeMacTheme::font(Font type) const { // when using the platform-default fonts, try returning a bold version of the // standard system font; it's the only one where Qt/OS X really deviates. const QFont *qf = m_fontsData->font(fontType(type)); if (!qf && nativeTheme) { qf = nativeTheme->font(type); // if (qf) { // qCWarning(PLATFORMTHEME) << "native font for type" << type << "=role" << fontType(type) << ":" << *qf; // } else { // qCWarning(PLATFORMTHEME) << "native font for type" << type << "=role" << fontType(type) << ": NULL"; // } } return qf; } void KdeMacTheme::loadSettings() { if (!m_fontsData) { m_fontsData = new KFontSettingsDataMac(this); } if (!m_hints) { m_hints = new KHintsSettingsMac(this); } } QList KdeMacTheme::keyBindings(QKeySequence::StandardKey key) const { // return a native keybinding if we can determine what that is if (nativeTheme) { return nativeTheme->keyBindings(key); } // or else we return whatever KDE applications expect elsewhere return KdePlatformTheme::keyBindings(key); } bool KdeMacTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const { #ifdef KDEMACTHEME_PREFER_NATIVE_DIALOGS if (nativeTheme) { return nativeTheme->usePlatformNativeDialog(type); } #endif #ifndef KDEMACTHEME_NEVER_NATIVE_DIALOGS return type == QPlatformTheme::FileDialog && qobject_cast(QCoreApplication::instance()); #else return false; #endif } QString KdeMacTheme::standardButtonText(int button) const { // assume that button text is a domain where cross-platform application // coherence primes over native platform look and feel. IOW, function over form. // It's impossible to use the parent's method since we use // the nativeTheme in the default case switch (static_cast(button)) { case QPlatformDialogHelper::NoButton: qCWarning(PLATFORMTHEME) << Q_FUNC_INFO << "Unsupported standard button:" << button; return QString(); case QPlatformDialogHelper::Ok: return KStandardGuiItem::ok().text(); case QPlatformDialogHelper::Save: return KStandardGuiItem::save().text(); case QPlatformDialogHelper::SaveAll: return i18nc("@action:button", "Save All"); case QPlatformDialogHelper::Open: return KStandardGuiItem::open().text(); case QPlatformDialogHelper::Yes: return KStandardGuiItem::yes().text(); case QPlatformDialogHelper::YesToAll: return i18nc("@action:button", "Yes to All"); case QPlatformDialogHelper::No: return KStandardGuiItem::no().text(); case QPlatformDialogHelper::NoToAll: return i18nc("@action:button", "No to All"); case QPlatformDialogHelper::Abort: // FIXME KStandardGuiItem::stop() doesn't seem right here return i18nc("@action:button", "Abort"); case QPlatformDialogHelper::Retry: return i18nc("@action:button", "Retry"); case QPlatformDialogHelper::Ignore: return i18nc("@action:button", "Ignore"); case QPlatformDialogHelper::Close: return KStandardGuiItem::close().text(); case QPlatformDialogHelper::Cancel: return KStandardGuiItem::cancel().text(); case QPlatformDialogHelper::Discard: return KStandardGuiItem::discard().text(); case QPlatformDialogHelper::Help: return KStandardGuiItem::help().text(); case QPlatformDialogHelper::Apply: return KStandardGuiItem::apply().text(); case QPlatformDialogHelper::Reset: return KStandardGuiItem::reset().text(); case QPlatformDialogHelper::RestoreDefaults: return KStandardGuiItem::defaults().text(); default: if (nativeTheme) { // something not foreseen by Qt/KDE: now see if OS X // has an opinion about the text. return nativeTheme->standardButtonText(button); } return QPlatformTheme::defaultStandardButtonText(button); } } QPlatformDialogHelper *KdeMacTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const { #ifdef KDEMACTHEME_PREFER_NATIVE_DIALOGS // always prefer native dialogs - 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 && m_isCocoa && !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) { 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 19fad3c..ca670af 100644 --- a/src/platformtheme/kdeplatformsystemtrayicon.cpp +++ b/src/platformtheme/kdeplatformsystemtrayicon.cpp @@ -1,362 +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 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 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 14f0e9e..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")); } 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/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"