Systray icon improvements
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 27 Apr 2009 18:38:19 +0000 (20:38 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Mon, 27 Apr 2009 19:36:38 +0000 (21:36 +0200)
* 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
src/qtui/knotificationbackend.h
src/qtui/mainwin.cpp
src/qtui/mainwin.h
src/qtui/qtui.cpp
src/qtui/systemtray.cpp
src/qtui/systemtray.h
src/qtui/systraynotificationbackend.cpp
src/qtui/systraynotificationbackend.h

index cc84978..f9f0eb6 100644 (file)
@@ -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<KNotification *, uint>::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() {
index 04723d3..5c93ed5 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef KNOTIFICATIONBACKEND_H_
 #define KNOTIFICATIONBACKEND_H_
 
+#include <KSystemTrayIcon>
+
 #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:
index 2b758d5..6867d06 100644 (file)
 #  include <KStatusBar>
 #endif
 
+#ifdef Q_WS_X11
+#  include <QX11Info>
+#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);
index 000e570..845d378 100644 (file)
 #  include <QMainWindow>
 #endif
 
+#ifdef Q_WS_WIN
+#  include <windows.h>
+#endif
+
 #include <QSystemTrayIcon>
 
 #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<BufferViewDock *> _bufferViews;
@@ -170,6 +178,10 @@ class MainWin
 
     QWidget *_awayLog;
     friend class QtUi;
+
+#ifdef Q_WS_WIN
+    DWORD dwTickCount;
+#endif
 };
 
 SystemTray *MainWin::systemTray() const {
index d19692c..66e07d0 100644 (file)
@@ -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();
 }
index acb68b1..140f9e2 100644 (file)
@@ -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
+
+  }
+}
index 1e811cd..d98bf46 100644 (file)
 #ifndef SYSTEMTRAY_H_
 #define SYSTEMTRAY_H_
 
-#include <QSystemTrayIcon>
+#ifdef HAVE_KDE
+#  include <KSystemTrayIcon>
+#else
+#  include <QSystemTrayIcon>
+#endif
+
 #include <QTimer>
 
 #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
index 6b67b20..ec66bdd 100644 (file)
@@ -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 &notification) {
@@ -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) {
index f4e2c6e..0895cbe 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef SYSTRAYNOTIFICATIONBACKEND_H_
 #define SYSTRAYNOTIFICATIONBACKEND_H_
 
+#include <QSystemTrayIcon>
+
 #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 &);