Refactor the system tray's context menu
[quassel.git] / src / uisupport / graphicalui.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2010 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This contains code from KStatusNotifierItem, part of the KDE libs     *
6  *   Copyright (C) 2009 Marco Martin <notmart@gmail.com>                   *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) version 3.                                           *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
22  ***************************************************************************/
23
24 #include "graphicalui.h"
25
26 #include "actioncollection.h"
27 #include "contextmenuactionprovider.h"
28
29 #ifdef Q_WS_X11
30 #  include <QX11Info>
31 #endif
32 #ifdef HAVE_KDE
33 #  include <KWindowInfo>
34 #  include <KWindowSystem>
35 #endif
36
37 GraphicalUi *GraphicalUi::_instance = 0;
38 QWidget *GraphicalUi::_mainWidget = 0;
39 QHash<QString, ActionCollection *> GraphicalUi::_actionCollections;
40 ContextMenuActionProvider *GraphicalUi::_contextMenuActionProvider = 0;
41 ToolBarActionProvider *GraphicalUi::_toolBarActionProvider = 0;
42 UiStyle *GraphicalUi::_uiStyle = 0;
43 bool GraphicalUi::_onAllDesktops = false;
44
45 GraphicalUi::GraphicalUi(QObject *parent) : AbstractUi(parent) {
46   Q_ASSERT(!_instance);
47   _instance = this;
48
49 #ifdef Q_WS_WIN
50   _dwTickCount = 0;
51 #endif
52 }
53
54 void GraphicalUi::init() {
55 #ifdef Q_WS_WIN
56   mainWidget()->installEventFilter(this);
57 #endif
58 }
59
60 ActionCollection *GraphicalUi::actionCollection(const QString &category) {
61   if(_actionCollections.contains(category))
62     return _actionCollections.value(category);
63   ActionCollection *coll = new ActionCollection(_mainWidget);
64   if(_mainWidget)
65     coll->addAssociatedWidget(_mainWidget);
66   _actionCollections.insert(category, coll);
67   return coll;
68 }
69
70 void GraphicalUi::setMainWidget(QWidget *widget) {
71   _mainWidget = widget;
72 }
73
74 void GraphicalUi::setContextMenuActionProvider(ContextMenuActionProvider *provider) {
75   _contextMenuActionProvider = provider;
76 }
77
78 void GraphicalUi::setToolBarActionProvider(ToolBarActionProvider *provider) {
79   _toolBarActionProvider = provider;
80 }
81
82 void GraphicalUi::setUiStyle(UiStyle *style) {
83   _uiStyle = style;
84 }
85
86 bool GraphicalUi::eventFilter(QObject *obj, QEvent *event) {
87 #ifdef Q_WS_WIN
88   if(obj == mainWidget() && event->type() == QEvent::ActivationChange) {
89     _dwTickCount = GetTickCount();
90   }
91 #endif
92   return AbstractUi::eventFilter(obj, event);
93 }
94
95 // Code taken from KStatusNotifierItem for handling minimize/restore
96
97 bool GraphicalUi::checkMainWidgetVisibility(bool perform) {
98 #ifdef Q_WS_WIN
99   // the problem is that we lose focus when the systray icon is activated
100   // and we don't know the former active window
101   // therefore we watch for activation event and use our stopwatch :)
102   if(GetTickCount() - _dwTickCount < 300) {
103     // we were active in the last 300ms -> hide it
104     if(perform)
105       minimizeRestore(false);
106     return false;
107   } else {
108     if(perform)
109       minimizeRestore(true);
110     return true;
111   }
112
113 #elif defined(HAVE_KDE) && defined(Q_WS_X11)
114   KWindowInfo info1 = KWindowSystem::windowInfo(mainWidget()->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop);
115   // mapped = visible (but possibly obscured)
116   bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized();
117
118   //    - not mapped -> show, raise, focus
119   //    - mapped
120   //        - obscured -> raise, focus
121   //        - not obscured -> hide
122   //info1.mappingState() != NET::Visible -> window on another desktop?
123   if(!mapped) {
124     if(perform)
125       minimizeRestore(true);
126     return true;
127
128   } else {
129     QListIterator< WId > it (KWindowSystem::stackingOrder());
130     it.toBack();
131     while(it.hasPrevious()) {
132       WId id = it.previous();
133       if(id == mainWidget()->winId())
134         break;
135
136       KWindowInfo info2 = KWindowSystem::windowInfo(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType);
137
138       if(info2.mappingState() != NET::Visible)
139         continue; // not visible on current desktop -> ignore
140
141       if(!info2.geometry().intersects(mainWidget()->geometry()))
142         continue; // not obscuring the window -> ignore
143
144       if(!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove))
145         continue; // obscured by window kept above -> ignore
146
147       NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask
148                                               | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
149                                               | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
150
151       if(type == NET::Dock || type == NET::TopMenu)
152         continue; // obscured by dock or topmenu -> ignore
153
154       if(perform) {
155         KWindowSystem::raiseWindow(mainWidget()->winId());
156         KWindowSystem::activateWindow(mainWidget()->winId());
157       }
158       return true;
159     }
160
161     //not on current desktop?
162     if(!info1.isOnCurrentDesktop()) {
163       if(perform)
164         KWindowSystem::activateWindow(mainWidget()->winId());
165       return true;
166     }
167
168     if(perform)
169       minimizeRestore(false); // hide
170     return false;
171   }
172 #else
173
174   if(!mainWidget()->isVisible() || mainWidget()->isMinimized()) {
175     if(perform)
176       minimizeRestore(true);
177     return true;
178   } else {
179     if(perform)
180       minimizeRestore(false);
181     return false;
182   }
183
184 #endif
185
186   return true;
187 }
188
189 bool GraphicalUi::isMainWidgetVisible() {
190   return !instance()->checkMainWidgetVisibility(false);
191 }
192
193 void GraphicalUi::minimizeRestore(bool show) {
194   if(show)
195     activateMainWidget();
196   else
197     hideMainWidget();
198 }
199
200 void GraphicalUi::activateMainWidget() {
201 #ifdef HAVE_KDE
202 #  ifdef Q_WS_X11
203     KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
204     if(_onAllDesktops) {
205       KWindowSystem::setOnAllDesktops(mainWidget()->winId(), true);
206     } else {
207       KWindowSystem::setCurrentDesktop(info.desktop());
208     }
209
210     mainWidget()->move(info.frameGeometry().topLeft()); // avoid placement policies
211     mainWidget()->show();
212     mainWidget()->raise();
213     KWindowSystem::raiseWindow(mainWidget()->winId());
214     KWindowSystem::activateWindow(mainWidget()->winId());
215 #  else
216     mainWidget()->show();
217     KWindowSystem::raiseWindow(mainWidget()->winId());
218     KWindowSystem::forceActiveWindow(mainWidget()->winId());
219 #  endif
220
221 #else /* HAVE_KDE */
222
223 #ifdef Q_WS_X11
224   // Bypass focus stealing prevention
225   QX11Info::setAppUserTime(QX11Info::appTime());
226 #endif
227
228   if(mainWidget()->windowState() & Qt::WindowMinimized) {
229     // restore
230     mainWidget()->setWindowState((mainWidget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
231   }
232
233   // this does not actually work on all platforms... and causes more evil than good
234   // mainWidget()->move(mainWidget()->frameGeometry().topLeft()); // avoid placement policies
235   mainWidget()->show();
236   mainWidget()->raise();
237   mainWidget()->activateWindow();
238
239 #endif /* HAVE_KDE */
240 }
241
242 void GraphicalUi::hideMainWidget() {
243
244 #if defined(HAVE_KDE) && defined(Q_WS_X11)
245   KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
246   _onAllDesktops = info.onAllDesktops();
247 #endif
248
249   if(instance()->isHidingMainWidgetAllowed())
250     mainWidget()->hide();
251 }
252
253 void GraphicalUi::toggleMainWidget() {
254   instance()->checkMainWidgetVisibility(true);
255 }