From 6f442c275cc5a2d5f1084ac2ceca5f1ffce1d024 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Mon, 27 Apr 2009 20:38:19 +0200 Subject: [PATCH] Systray icon improvements * Hide rather than minimize the MainWin on click (this is standard behavior) * Bring window to front if it's obscured by other windows, rather than minimize it This works with KDE integration and on Windows only. Sorry pure Qt users, but I don't feel like reimplementing the X11 voodoo required for finding out our state :/ * Properly restore the old position after unhiding the window * Select highlighted buffer if a highlight is pending * Fix some corner cases where minimize/restore failed before Kudos to the KSystemTrayIcon developers for showing me how to stab some manners into window managers :) --- src/qtui/knotificationbackend.cpp | 28 ++++++- src/qtui/knotificationbackend.h | 4 + src/qtui/mainwin.cpp | 97 ++++++++++++++++--------- src/qtui/mainwin.h | 22 ++++-- src/qtui/qtui.cpp | 15 +--- src/qtui/systemtray.cpp | 40 +++++++++- src/qtui/systemtray.h | 25 ++++++- src/qtui/systraynotificationbackend.cpp | 17 ++++- src/qtui/systraynotificationbackend.h | 3 + 9 files changed, 191 insertions(+), 60 deletions(-) diff --git a/src/qtui/knotificationbackend.cpp b/src/qtui/knotificationbackend.cpp index cc849784..f9f0eb6e 100644 --- a/src/qtui/knotificationbackend.cpp +++ b/src/qtui/knotificationbackend.cpp @@ -34,7 +34,8 @@ #include "systemtray.h" KNotificationBackend::KNotificationBackend(QObject *parent) : AbstractNotificationBackend(parent) { - + connect(QtUi::mainWindow()->systemTray(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + SLOT(notificationActivated(QSystemTrayIcon::ActivationReason))); } void KNotificationBackend::notify(const Notification &n) { @@ -62,7 +63,30 @@ void KNotificationBackend::notificationActivated() { if(n && _notificationIds.contains(n)) id = _notificationIds.value(n); - emit activated(id); + notificationActivated(id); +} + +void KNotificationBackend::notificationActivated(QSystemTrayIcon::ActivationReason reason) { + if(reason == QSystemTrayIcon::Trigger && _notificationIds.count() > 0) { + notificationActivated(_notificationIds.values().at(0)); // we choose a random one for now + } +} + +void KNotificationBackend::notificationActivated(uint notificationId) { + QHash::iterator i = _notificationIds.begin(); + while(i != _notificationIds.end()) { + if(i.value() == notificationId) + i = _notificationIds.erase(i); + else + ++i; + } + + QtUi::mainWindow()->systemTray()->setInhibitActivation(); + emit activated(notificationId); + + if(!_notificationIds.count()) + QtUi::mainWindow()->systemTray()->setAlert(false); + } void KNotificationBackend::notificationClosed() { diff --git a/src/qtui/knotificationbackend.h b/src/qtui/knotificationbackend.h index 04723d3a..5c93ed5f 100644 --- a/src/qtui/knotificationbackend.h +++ b/src/qtui/knotificationbackend.h @@ -21,6 +21,8 @@ #ifndef KNOTIFICATIONBACKEND_H_ #define KNOTIFICATIONBACKEND_H_ +#include + #include "abstractnotificationbackend.h" #include "settingspage.h" @@ -39,6 +41,8 @@ public: private slots: void notificationActivated(); + void notificationActivated(QSystemTrayIcon::ActivationReason); + void notificationActivated(uint notificationId); void notificationClosed(); private: diff --git a/src/qtui/mainwin.cpp b/src/qtui/mainwin.cpp index 2b758d50..6867d060 100644 --- a/src/qtui/mainwin.cpp +++ b/src/qtui/mainwin.cpp @@ -28,6 +28,10 @@ # include #endif +#ifdef Q_WS_X11 +# include +#endif + #include "aboutdlg.h" #include "awaylogfilter.h" #include "awaylogview.h" @@ -108,6 +112,10 @@ MainWin::MainWin(QWidget *parent) _titleSetter(this), _awayLog(0) { +#ifdef Q_WS_WIN + dwTickCount = 0; +#endif + QtUiSettings uiSettings; QString style = uiSettings.value("Style", QString()).toString(); if(!style.isEmpty()) { @@ -516,11 +524,6 @@ void MainWin::saveStatusBarStatus(bool enabled) { void MainWin::setupSystray() { _systemTray = new SystemTray(this); - -#ifndef Q_WS_MAC - connect(systemTray(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systrayActivated(QSystemTrayIcon::ActivationReason))); -#endif - } void MainWin::setupToolBars() { @@ -548,18 +551,6 @@ void MainWin::setupToolBars() { #endif } -void MainWin::changeEvent(QEvent *event) { - if(event->type() == QEvent::WindowStateChange) { - if(windowState() & Qt::WindowMinimized) { - QtUiSettings s; - if(s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnMinimize").toBool()) { - hideToTray(); - event->accept(); - } - } - } -} - void MainWin::connectedToCore() { Q_CHECK_PTR(Client::bufferViewManager()); connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigAdded(int)), this, SLOT(addBufferView(int))); @@ -761,10 +752,30 @@ void MainWin::closeEvent(QCloseEvent *event) { } } -void MainWin::systrayActivated(QSystemTrayIcon::ActivationReason activationReason) { - if(activationReason == QSystemTrayIcon::Trigger) { - toggleMinimizedToTray(); +bool MainWin::event(QEvent *event) { + if(event->type() == QEvent::WindowActivate) + QtUi::closeNotifications(); + return QMainWindow::event(event); +} + +void MainWin::changeEvent(QEvent *event) { + if(event->type() == QEvent::WindowStateChange) { + if(windowState() & Qt::WindowMinimized) { + QtUiSettings s; + if(s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnMinimize").toBool()) { + hideToTray(); + event->accept(); + return; + } + } } + +#ifdef Q_WS_WIN + else if(event->type() == QEvent::ActivationChange) + dwTickCount = GetTickCount(); // needed for toggleMinimizedToTray() +#endif + + event->ignore(); } void MainWin::hideToTray() { @@ -772,22 +783,48 @@ void MainWin::hideToTray() { qWarning() << Q_FUNC_INFO << "was called with no SystemTray available!"; return; } - clearFocus(); hide(); systemTray()->setIconVisible(); } void MainWin::toggleMinimizedToTray() { +#ifdef Q_WS_WIN + // the problem is that we lose focus when the systray icon is activated + // and we don't know the former active window + // therefore we watch for activation event and use our stopwatch :) + // courtesy: KSystemTrayIcon + if(GetTickCount() - dwTickCount >= 300) + // we weren't active in the last 300ms -> activate + forceActivated(); + else + hideToTray(); + +#else + + if(!isVisible() || isMinimized()) + // restore + forceActivated(); + else + hideToTray(); + +#endif +} + +void MainWin::forceActivated() { +#ifdef Q_WS_X11 + // Bypass focus stealing prevention + QX11Info::setAppUserTime(QX11Info::appTime()); +#endif + if(windowState() & Qt::WindowMinimized) { // restore setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - show(); - activateWindow(); - raise(); - } else { - setWindowState((windowState() & ~Qt::WindowActive) | Qt::WindowMinimized); - hideToTray(); } + + move(frameGeometry().topLeft()); // avoid placement policies + show(); + raise(); + activateWindow(); } void MainWin::messagesInserted(const QModelIndex &parent, int start, int end) { @@ -819,12 +856,6 @@ void MainWin::messagesInserted(const QModelIndex &parent, int start, int end) { } } -bool MainWin::event(QEvent *event) { - if(event->type() == QEvent::WindowActivate) - QtUi::closeNotifications(); - return QMainWindow::event(event); -} - void MainWin::clientNetworkCreated(NetworkId id) { const Network *net = Client::network(id); QAction *act = new QAction(net->networkName(), this); diff --git a/src/qtui/mainwin.h b/src/qtui/mainwin.h index 000e5704..845d378c 100644 --- a/src/qtui/mainwin.h +++ b/src/qtui/mainwin.h @@ -27,6 +27,10 @@ # include #endif +#ifdef Q_WS_WIN +# include +#endif + #include #include "qtui.h" @@ -70,7 +74,7 @@ class MainWin inline SystemTray *systemTray() const; - virtual bool event(QEvent *event); + bool event(QEvent *event); static void flagRemoteCoreOnly(QObject *object) { object->setProperty("REMOTE_CORE_ONLY", true); } static bool isRemoteCoreOnly(QObject *object) { return object->property("REMOTE_CORE_ONLY").toBool(); } @@ -80,9 +84,14 @@ class MainWin void saveStateToSessionSettings(SessionSettings &s); void showStatusBarMessage(const QString &message); + void toggleMinimizedToTray(); + + //! Bring window to front and focus it + void forceActivated(); + protected: void closeEvent(QCloseEvent *event); - virtual void changeEvent(QEvent *event); + void changeEvent(QEvent *event); protected slots: void connectedToCore(); @@ -90,7 +99,6 @@ class MainWin void updateLagIndicator(int lag = -1); void disconnectedFromCore(); void setDisconnectedState(); - void systrayActivated(QSystemTrayIcon::ActivationReason); private slots: void addBufferView(int bufferViewConfigId); @@ -153,10 +161,10 @@ class MainWin void setupToolBars(); void updateIcon(); - void hideToTray(); - void toggleMinimizedToTray(); void enableMenus(); + void hideToTray(); + SystemTray *_systemTray; QList _bufferViews; @@ -170,6 +178,10 @@ class MainWin QWidget *_awayLog; friend class QtUi; + +#ifdef Q_WS_WIN + DWORD dwTickCount; +#endif }; SystemTray *MainWin::systemTray() const { diff --git a/src/qtui/qtui.cpp b/src/qtui/qtui.cpp index d19692ca..66e07d08 100644 --- a/src/qtui/qtui.cpp +++ b/src/qtui/qtui.cpp @@ -164,22 +164,13 @@ void QtUi::notificationActivated(uint notificationId) { BufferId bufId = (*i).bufferId; if(bufId.isValid()) Client::bufferModel()->switchToBuffer(bufId); + foreach(AbstractNotificationBackend *backend, _notificationBackends) + backend->close(notificationId); _notifications.erase(i); break; } else ++i; } } -#ifdef Q_WS_X11 - // Bypass focus stealing prevention - QX11Info::setAppUserTime(QX11Info::appTime()); -#endif - - if(_mainWin->windowState() & Qt::WindowMinimized) { - // restore - _mainWin->setWindowState((_mainWin->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - _mainWin->show(); - } - _mainWin->raise(); - _mainWin->activateWindow(); + mainWindow()->forceActivated(); } diff --git a/src/qtui/systemtray.cpp b/src/qtui/systemtray.cpp index acb68b19..140f9e27 100644 --- a/src/qtui/systemtray.cpp +++ b/src/qtui/systemtray.cpp @@ -31,11 +31,20 @@ SystemTray::SystemTray(QObject *parent) : QObject(parent), _state(Inactive), _alert(false), + _inhibitActivation(false), _currentIdx(0) { loadAnimations(); _currentIdx = _idxOffEnd; - _trayIcon = new QSystemTrayIcon(_phases.at(_currentIdx), this); + +#ifndef HAVE_KDE + _trayIcon = new QSystemTrayIcon(_phases.at(_currentIdx), QtUi::mainWindow()); +#else + _trayIcon = new KSystemTrayIcon(_phases.at(_currentIdx), QtUi::mainWindow()); + // We don't want to trigger a minimize if a highlight is pending, so we brutally remove the internal connection for that + disconnect(_trayIcon, SIGNAL(activated( QSystemTrayIcon::ActivationReason)), + _trayIcon, SLOT(activateOrHide(QSystemTrayIcon::ActivationReason))); +#endif _animationTimer.setInterval(150); _animationTimer.setSingleShot(false); @@ -56,7 +65,11 @@ SystemTray::SystemTray(QObject *parent) _trayIcon->show(); } - connect(_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SIGNAL(activated(QSystemTrayIcon::ActivationReason))); + qApp->installEventFilter(this); + +#ifndef Q_WS_MAC + connect(_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(on_activated(QSystemTrayIcon::ActivationReason))); +#endif connect(_trayIcon, SIGNAL(messageClicked()), SIGNAL(messageClicked())); } @@ -153,3 +166,26 @@ void SystemTray::setToolTip(const QString &tip) { void SystemTray::showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon icon, int millisecondsTimeoutHint) { _trayIcon->showMessage(title, message, icon, millisecondsTimeoutHint); } + +bool SystemTray::eventFilter(QObject *obj, QEvent *event) { + Q_UNUSED(obj); + if(event->type() == QEvent::MouseButtonRelease) { + _inhibitActivation = false; + } + return false; +} + +void SystemTray::on_activated(QSystemTrayIcon::ActivationReason reason) { + emit activated(reason); + + if(reason == QSystemTrayIcon::Trigger && !_inhibitActivation) { + +# ifdef HAVE_KDE + // the slot is private, but meh, who cares :) + QMetaObject::invokeMethod(_trayIcon, "activateOrHide", Q_ARG(QSystemTrayIcon::ActivationReason, QSystemTrayIcon::Trigger)); +# else + QtUi::mainWindow()->toggleMinimizedToTray(); +# endif + + } +} diff --git a/src/qtui/systemtray.h b/src/qtui/systemtray.h index 1e811cd8..d98bf46e 100644 --- a/src/qtui/systemtray.h +++ b/src/qtui/systemtray.h @@ -21,7 +21,12 @@ #ifndef SYSTEMTRAY_H_ #define SYSTEMTRAY_H_ -#include +#ifdef HAVE_KDE +# include +#else +# include +#endif + #include #include "icon.h" @@ -39,8 +44,9 @@ public: ~SystemTray(); inline bool isSystemTrayAvailable() const; - Icon icon() const; - QString toolTip() const; + inline bool isAlerted() const; + + inline void setInhibitActivation(); public slots: void setState(State); @@ -50,22 +56,31 @@ public slots: void showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information, int millisecondsTimeoutHint = 10000); - signals: void activated(QSystemTrayIcon::ActivationReason); void iconChanged(const Icon &); void messageClicked(); +protected: + bool eventFilter(QObject *obj, QEvent *event); + private slots: void nextPhase(); + void on_activated(QSystemTrayIcon::ActivationReason); private: void loadAnimations(); +#ifdef HAVE_KDE + KSystemTrayIcon *_trayIcon; +#else QSystemTrayIcon *_trayIcon; +#endif + QMenu *_trayMenu; State _state; bool _alert; + bool _inhibitActivation; int _idxOffStart, _idxOffEnd, _idxOnStart, _idxOnEnd, _idxAlertStart; int _currentIdx; @@ -77,5 +92,7 @@ private: // inlines bool SystemTray::isSystemTrayAvailable() const { return QSystemTrayIcon::isSystemTrayAvailable(); } +bool SystemTray::isAlerted() const { return _alert; } +void SystemTray::setInhibitActivation() { _inhibitActivation = true; } #endif diff --git a/src/qtui/systraynotificationbackend.cpp b/src/qtui/systraynotificationbackend.cpp index 6b67b202..ec66bddb 100644 --- a/src/qtui/systraynotificationbackend.cpp +++ b/src/qtui/systraynotificationbackend.cpp @@ -41,6 +41,8 @@ SystrayNotificationBackend::SystrayNotificationBackend(QObject *parent) notificationSettings.notify("Systray/Animate", this, SLOT(animateChanged(const QVariant &))); connect(QtUi::mainWindow()->systemTray(), SIGNAL(messageClicked()), SLOT(notificationActivated())); + connect(QtUi::mainWindow()->systemTray(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + SLOT(notificationActivated(QSystemTrayIcon::ActivationReason))); } void SystrayNotificationBackend::notify(const Notification ¬ification) { @@ -67,6 +69,7 @@ void SystrayNotificationBackend::close(uint notificationId) { if(_notifications.isEmpty()) { */ _notifications.clear(); + _activeId = 0; closeBubble(); QtUi::mainWindow()->systemTray()->setAlert(false); } @@ -74,7 +77,8 @@ void SystrayNotificationBackend::close(uint notificationId) { void SystrayNotificationBackend::showBubble() { // fancy stuff later: show messages in order // for now, we just show the last message - if(_notifications.isEmpty()) return; + if(_notifications.isEmpty()) + return; Notification n = _notifications.takeLast(); _activeId = n.notificationId; QString title = Client::networkModel()->networkName(n.bufferId) + " - " + Client::networkModel()->bufferName(n.bufferId); @@ -90,7 +94,16 @@ void SystrayNotificationBackend::closeBubble() { } void SystrayNotificationBackend::notificationActivated() { - emit activated(_activeId); + if(QtUi::mainWindow()->systemTray()->isAlerted()) { + QtUi::mainWindow()->systemTray()->setInhibitActivation(); + emit activated(_activeId); + } +} + +void SystrayNotificationBackend::notificationActivated(QSystemTrayIcon::ActivationReason reason) { + if(reason == QSystemTrayIcon::Trigger) { + notificationActivated(); + } } void SystrayNotificationBackend::showBubbleChanged(const QVariant &v) { diff --git a/src/qtui/systraynotificationbackend.h b/src/qtui/systraynotificationbackend.h index f4e2c6e5..0895cbef 100644 --- a/src/qtui/systraynotificationbackend.h +++ b/src/qtui/systraynotificationbackend.h @@ -21,6 +21,8 @@ #ifndef SYSTRAYNOTIFICATIONBACKEND_H_ #define SYSTRAYNOTIFICATIONBACKEND_H_ +#include + #include "abstractnotificationbackend.h" #include "settingspage.h" @@ -41,6 +43,7 @@ private slots: void showBubble(); void closeBubble(); void notificationActivated(); + void notificationActivated(QSystemTrayIcon::ActivationReason); void animateChanged(const QVariant &); void showBubbleChanged(const QVariant &); -- 2.20.1