From: Manuel Nickschas Date: Tue, 16 Feb 2010 00:30:04 +0000 (+0100) Subject: More systray refactoring X-Git-Tag: 0.6-beta1~22 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=2e9492d9ef198bde37da1f858602ab9624c0a12a More systray refactoring * Move the window activation stuff into GraphicalUI (because that's where it really belongs) * Make hiding the tray icon work better (i.e. without a restart) * Ensure that the main window can't be hidden if we don't have a systray --- diff --git a/src/client/abstractui.h b/src/client/abstractui.h index 2ca395a0..00ec65a2 100644 --- a/src/client/abstractui.h +++ b/src/client/abstractui.h @@ -18,8 +18,8 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#ifndef QUASSELUI_H -#define QUASSELUI_H +#ifndef ABSTRACTUI_H +#define ABSTRACTUI_H #include #include diff --git a/src/qtui/mainwin.cpp b/src/qtui/mainwin.cpp index 3ea5d90c..ea361d96 100644 --- a/src/qtui/mainwin.cpp +++ b/src/qtui/mainwin.cpp @@ -275,8 +275,8 @@ void MainWin::restoreStateFromSettings(UiSettings &s) { move(_normalPos); #endif - if(s.value("MainWinHidden").toBool() && systemTray()->isSystemTrayAvailable()) - systemTray()->hideMainWidget(); + if(s.value("MainWinHidden").toBool() && QtUi::haveSystemTray()) + QtUi::hideMainWidget(); else if(s.value("MainWinMinimized").toBool()) showMinimized(); else if(maximized) @@ -1008,9 +1008,8 @@ void MainWin::closeEvent(QCloseEvent *event) { QtUiSettings s; QtUiApplication* app = qobject_cast qApp; Q_ASSERT(app); - if(!app->isAboutToQuit() - && s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnClose").toBool() && systemTray()->isSystemTrayAvailable()) { - systemTray()->hideMainWidget(); + if(!app->isAboutToQuit() && QtUi::haveSystemTray() && s.value("MinimizeOnClose").toBool()) { + QtUi::hideMainWidget(); event->ignore(); } else { event->accept(); diff --git a/src/qtui/qtui.cpp b/src/qtui/qtui.cpp index 30064d67..2a088360 100644 --- a/src/qtui/qtui.cpp +++ b/src/qtui/qtui.cpp @@ -71,6 +71,8 @@ QtUi::~QtUi() { void QtUi::init() { _mainWin->init(); + QtUiSettings uiSettings; + uiSettings.initAndNotify("UseSystemTrayIcon", this, SLOT(useSystemTrayChanged(QVariant)), true); } MessageModel *QtUi::createMessageModel(QObject *parent) { @@ -89,12 +91,36 @@ void QtUi::disconnectedFromCore() { _mainWin->disconnectedFromCore(); } +void QtUi::useSystemTrayChanged(const QVariant &v) { + _useSystemTray = v.toBool(); + SystemTray *tray = mainWindow()->systemTray(); + if(_useSystemTray) { + if(tray->isSystemTrayAvailable()) + tray->setVisible(true); + } else { + if(tray->isSystemTrayAvailable() && mainWindow()->isVisible()) + tray->setVisible(false); + } +} + bool QtUi::haveSystemTray() { -#ifdef QT_NO_SYSTEMTRAYICON - return false; -#else - return mainWindow()->systemTray()->isSystemTrayAvailable(); -#endif + return mainWindow()->systemTray()->isSystemTrayAvailable() && instance()->_useSystemTray; +} + +bool QtUi::isHidingMainWidgetAllowed() const { + return haveSystemTray(); +} + +void QtUi::minimizeRestore(bool show) { + SystemTray *tray = mainWindow()->systemTray(); + if(show) { + if(tray && !_useSystemTray) + tray->setVisible(false); + } else { + if(tray && _useSystemTray) + tray->setVisible(true); + } + GraphicalUi::minimizeRestore(show); } void QtUi::registerNotificationBackend(AbstractNotificationBackend *backend) { diff --git a/src/qtui/qtui.h b/src/qtui/qtui.h index 33a84620..1eb038f2 100644 --- a/src/qtui/qtui.h +++ b/src/qtui/qtui.h @@ -70,11 +70,20 @@ protected slots: void disconnectedFromCore(); void notificationActivated(uint notificationId); +protected: + virtual void minimizeRestore(bool show); + virtual bool isHidingMainWidgetAllowed() const; + +private slots: + void useSystemTrayChanged(const QVariant &); + private: static QPointer _instance; static QPointer _mainWin; static QList _notificationBackends; static QList _notifications; + + bool _useSystemTray; }; QtUi *QtUi::instance() { return _instance ? _instance.data() : new QtUi(); } diff --git a/src/qtui/systemtray.cpp b/src/qtui/systemtray.cpp index 27aeb086..91258230 100644 --- a/src/qtui/systemtray.cpp +++ b/src/qtui/systemtray.cpp @@ -2,9 +2,6 @@ * Copyright (C) 2005-2010 by the Quassel Project * * devel@quassel-irc.org * * * - * This contains code from KStatusNotifierItem, part of the KDE libs * - * Copyright (C) 2009 Marco Martin * - * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * @@ -47,19 +44,10 @@ SystemTray::SystemTray(QWidget *parent) { Q_ASSERT(parent); -#ifdef Q_WS_WIN - _dwTickCount = 0; - associatedWidget()->installEventFilter(this); -#endif - qApp->installEventFilter(this); } SystemTray::~SystemTray() { -#ifdef Q_WS_WIN - associatedWidget()->removeEventFilter(this); -#endif - _trayMenu->deleteLater(); } @@ -146,7 +134,7 @@ void SystemTray::activate(SystemTray::ActivationReason reason) { emit activated(reason); if(reason == Trigger && !isActivationInhibited()) { - toggleMainWidget(); + GraphicalUi::toggleMainWidget(); } } @@ -154,120 +142,5 @@ bool SystemTray::eventFilter(QObject *obj, QEvent *event) { if(event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease) { _inhibitActivation = false; } -#ifdef Q_WS_WIN - if(obj == associatedWidget() && event->type() == QEvent::ActivationChange) { - _dwTickCount = GetTickCount(); - } -#endif return QObject::eventFilter(obj, event); } - -// Code taken from KStatusNotifierItem for handling minimize/restore - -bool SystemTray::checkVisibility(bool perform) { -#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 :) - if(GetTickCount() - _dwTickCount < 300) { - // we were active in the last 300ms -> hide it - minimizeRestore(false); - } else { - minimizeRestore(true); - } - -#elif defined(HAVE_KDE) && defined(Q_WS_X11) - KWindowInfo info1 = KWindowSystem::windowInfo(associatedWidget()->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop); - // mapped = visible (but possibly obscured) - bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized(); - - // - not mapped -> show, raise, focus - // - mapped - // - obscured -> raise, focus - // - not obscured -> hide - //info1.mappingState() != NET::Visible -> window on another desktop? - if(!mapped) { - if(perform) - minimizeRestore(true); - return true; - - } else { - QListIterator< WId > it (KWindowSystem::stackingOrder()); - it.toBack(); - while(it.hasPrevious()) { - WId id = it.previous(); - if(id == associatedWidget()->winId()) - break; - - KWindowInfo info2 = KWindowSystem::windowInfo(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType); - - if(info2.mappingState() != NET::Visible) - continue; // not visible on current desktop -> ignore - - if(!info2.geometry().intersects(associatedWidget()->geometry())) - continue; // not obscuring the window -> ignore - - if(!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove)) - continue; // obscured by window kept above -> ignore - - NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask - | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask - | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); - - if(type == NET::Dock || type == NET::TopMenu) - continue; // obscured by dock or topmenu -> ignore - - if(perform) { - KWindowSystem::raiseWindow(associatedWidget()->winId()); - KWindowSystem::activateWindow(associatedWidget()->winId()); - } - return true; - } - - //not on current desktop? - if(!info1.isOnCurrentDesktop()) { - if(perform) - KWindowSystem::activateWindow(associatedWidget()->winId()); - return true; - } - - if(perform) - minimizeRestore(false); // hide - return false; - } -#else - - if(!associatedWidget()->isVisible() || associatedWidget()->isMinimized()) { - if(perform) - minimizeRestore(true); - return true; - } else { - if(perform) - minimizeRestore(false); - return false; - } - -#endif - - return true; -} - -void SystemTray::minimizeRestore(bool show) { - if(show) - GraphicalUi::activateMainWidget(); - else { - if(isSystemTrayAvailable()) { - if(!isVisible()) - setVisible(); - GraphicalUi::hideMainWidget(); - } - } -} - -void SystemTray::hideMainWidget() { - minimizeRestore(false); -} - -void SystemTray::toggleMainWidget() { - checkVisibility(true); -} diff --git a/src/qtui/systemtray.h b/src/qtui/systemtray.h index d610c48e..7192b3fe 100644 --- a/src/qtui/systemtray.h +++ b/src/qtui/systemtray.h @@ -23,10 +23,6 @@ #include "icon.h" -#ifdef Q_WS_WIN -# include -#endif - class QMenu; class SystemTray : public QObject { @@ -72,8 +68,8 @@ public: virtual inline bool isSystemTrayAvailable() const; void setAlert(bool alerted); - inline void setInhibitActivation(); - inline virtual bool isVisible() const { return false; } + virtual inline void setInhibitActivation(); + virtual inline bool isVisible() const { return false; } inline bool isActivationInhibited() const; QWidget *associatedWidget() const; @@ -83,8 +79,6 @@ public slots: virtual void setVisible(bool visible = true); virtual void setToolTip(const QString &title, const QString &subtitle); virtual void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); - void toggleMainWidget(); - void hideMainWidget(); signals: void activated(SystemTray::ActivationReason); @@ -95,8 +89,6 @@ signals: protected slots: virtual void activate(SystemTray::ActivationReason = Trigger); - void minimizeRestore(bool restore); - protected: virtual void setMode(Mode mode); inline Mode mode() const; @@ -113,8 +105,6 @@ protected: private slots: private: - bool checkVisibility(bool performToggle = true); - Mode _mode; State _state; @@ -125,11 +115,6 @@ private: QMenu *_trayMenu; QWidget *_associatedWidget; - -#ifdef Q_WS_WIN - DWORD _dwTickCount; -#endif - }; // inlines diff --git a/src/uisupport/graphicalui.cpp b/src/uisupport/graphicalui.cpp index 40921cff..5d9a4198 100644 --- a/src/uisupport/graphicalui.cpp +++ b/src/uisupport/graphicalui.cpp @@ -1,7 +1,10 @@ /*************************************************************************** - * Copyright (C) 2005-09 by the Quassel Project * + * Copyright (C) 2005-2010 by the Quassel Project * * devel@quassel-irc.org * * * + * This contains code from KStatusNotifierItem, part of the KDE libs * + * Copyright (C) 2009 Marco Martin * + * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * @@ -31,6 +34,7 @@ # include #endif +GraphicalUi *GraphicalUi::_instance = 0; QWidget *GraphicalUi::_mainWidget = 0; QHash GraphicalUi::_actionCollections; ContextMenuActionProvider *GraphicalUi::_contextMenuActionProvider = 0; @@ -38,11 +42,23 @@ ToolBarActionProvider *GraphicalUi::_toolBarActionProvider = 0; UiStyle *GraphicalUi::_uiStyle = 0; bool GraphicalUi::_onAllDesktops = false; -GraphicalUi::GraphicalUi(QObject *parent) : AbstractUi(parent) -{ +GraphicalUi::GraphicalUi(QObject *parent) : AbstractUi(parent) { + Q_ASSERT(!_instance); + _instance = this; + +#ifdef Q_WS_WIN + _dwTickCount = 0; + mainWidget()->installEventFilter(this); +#endif } +GraphicalUi::~GraphicalUi() { +#ifdef Q_WS_WIN + mainWidget()->removeEventFilter(this); +#endif +} + ActionCollection *GraphicalUi::actionCollection(const QString &category) { if(_actionCollections.contains(category)) return _actionCollections.value(category); @@ -69,6 +85,112 @@ void GraphicalUi::setUiStyle(UiStyle *style) { _uiStyle = style; } +bool GraphicalUi::eventFilter(QObject *obj, QEvent *event) { +#ifdef Q_WS_WIN + if(obj == mainWidget() && event->type() == QEvent::ActivationChange) { + _dwTickCount = GetTickCount(); + } +#endif + return AbstractUi::eventFilter(obj, event); +} + +// Code taken from KStatusNotifierItem for handling minimize/restore + +bool GraphicalUi::checkMainWidgetVisibility(bool perform) { +#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 :) + if(GetTickCount() - _dwTickCount < 300) { + // we were active in the last 300ms -> hide it + minimizeRestore(false); + } else { + minimizeRestore(true); + } + +#elif defined(HAVE_KDE) && defined(Q_WS_X11) + KWindowInfo info1 = KWindowSystem::windowInfo(mainWidget()->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop); + // mapped = visible (but possibly obscured) + bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized(); + + // - not mapped -> show, raise, focus + // - mapped + // - obscured -> raise, focus + // - not obscured -> hide + //info1.mappingState() != NET::Visible -> window on another desktop? + if(!mapped) { + if(perform) + minimizeRestore(true); + return true; + + } else { + QListIterator< WId > it (KWindowSystem::stackingOrder()); + it.toBack(); + while(it.hasPrevious()) { + WId id = it.previous(); + if(id == mainWidget()->winId()) + break; + + KWindowInfo info2 = KWindowSystem::windowInfo(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType); + + if(info2.mappingState() != NET::Visible) + continue; // not visible on current desktop -> ignore + + if(!info2.geometry().intersects(mainWidget()->geometry())) + continue; // not obscuring the window -> ignore + + if(!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove)) + continue; // obscured by window kept above -> ignore + + NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask + | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask + | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); + + if(type == NET::Dock || type == NET::TopMenu) + continue; // obscured by dock or topmenu -> ignore + + if(perform) { + KWindowSystem::raiseWindow(mainWidget()->winId()); + KWindowSystem::activateWindow(mainWidget()->winId()); + } + return true; + } + + //not on current desktop? + if(!info1.isOnCurrentDesktop()) { + if(perform) + KWindowSystem::activateWindow(mainWidget()->winId()); + return true; + } + + if(perform) + minimizeRestore(false); // hide + return false; + } +#else + + if(!mainWidget()->isVisible() || mainWidget()->isMinimized()) { + if(perform) + minimizeRestore(true); + return true; + } else { + if(perform) + minimizeRestore(false); + return false; + } + +#endif + + return true; +} + +void GraphicalUi::minimizeRestore(bool show) { + if(show) + activateMainWidget(); + else + hideMainWidget(); +} + void GraphicalUi::activateMainWidget() { #ifdef HAVE_KDE # ifdef Q_WS_X11 @@ -118,5 +240,10 @@ void GraphicalUi::hideMainWidget() { _onAllDesktops = info.onAllDesktops(); #endif - mainWidget()->hide(); + if(instance()->isHidingMainWidgetAllowed()) + mainWidget()->hide(); +} + +void GraphicalUi::toggleMainWidget() { + instance()->checkMainWidgetVisibility(true); } diff --git a/src/uisupport/graphicalui.h b/src/uisupport/graphicalui.h index 9832d104..1ed55b60 100644 --- a/src/uisupport/graphicalui.h +++ b/src/uisupport/graphicalui.h @@ -28,11 +28,16 @@ class ContextMenuActionProvider; class ToolBarActionProvider; class UiStyle; +#ifdef Q_WS_WIN +# include +#endif + class GraphicalUi : public AbstractUi { Q_OBJECT public: GraphicalUi(QObject *parent = 0); + virtual ~GraphicalUi(); //! Access global ActionCollections. /** These ActionCollections are associated with the main window, i.e. they contain global @@ -53,37 +58,61 @@ public: //! Hide main widget (storing the current desktop if possible) static void hideMainWidget(); + //! Toggle main widget + static void toggleMainWidget(); + protected: //! This is the widget we associate global actions with, typically the main window void setMainWidget(QWidget *); + //! Check if the mainWidget is visible and optionally toggle its visibility + /** With KDE integration, we check if the mainWidget is (partially) obscured in order to determine if + * it should be activated or hidden. Without KDE, we need to resort to checking the current state + * as Qt knows it, ignoring windows covering it. + * @param performToggle If true, toggle the window's state in addition to checking visibility + * @return True, if the window is currently visible + */ + bool checkMainWidgetVisibility(bool performToggle); + + //! Minimize to or restore main widget + virtual void minimizeRestore(bool show); + + //! Whether it is allowed to hide the mainWidget + /** The default implementation returns false, meaning that we won't hide the mainWidget even + * if requested. This is to prevent hiding in case we don't have a tray icon to restore from. + */ + virtual inline bool isHidingMainWidgetAllowed() const; + void setContextMenuActionProvider(ContextMenuActionProvider *); void setToolBarActionProvider(ToolBarActionProvider *); void setUiStyle(UiStyle *); + virtual bool eventFilter(QObject *obj, QEvent *event); + private: + static inline GraphicalUi *instance(); + + static GraphicalUi *_instance; static QWidget *_mainWidget; static QHash _actionCollections; static ContextMenuActionProvider *_contextMenuActionProvider; static ToolBarActionProvider *_toolBarActionProvider; static UiStyle *_uiStyle; static bool _onAllDesktops; -}; -ContextMenuActionProvider *GraphicalUi::contextMenuActionProvider() { - return _contextMenuActionProvider; -} +#ifdef Q_WS_WIN + DWORD _dwTickCount; +#endif -ToolBarActionProvider *GraphicalUi::toolBarActionProvider() { - return _toolBarActionProvider; -} +}; -UiStyle *GraphicalUi::uiStyle() { - return _uiStyle; -} +// inlines -QWidget *GraphicalUi::mainWidget() { - return _mainWidget; -} +GraphicalUi *GraphicalUi::instance() { return _instance; } +ContextMenuActionProvider *GraphicalUi::contextMenuActionProvider() { return _contextMenuActionProvider; } +ToolBarActionProvider *GraphicalUi::toolBarActionProvider() { return _toolBarActionProvider; } +UiStyle *GraphicalUi::uiStyle() { return _uiStyle; } +QWidget *GraphicalUi::mainWidget() { return _mainWidget; } +bool GraphicalUi::isHidingMainWidgetAllowed() const { return false; } #endif