Properly clean up after disconnecting
[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 #include "toolbaractionprovider.h"
29
30 #ifdef Q_WS_X11
31 #  include <QX11Info>
32 #endif
33 #ifdef HAVE_KDE
34 #  include <KWindowInfo>
35 #  include <KWindowSystem>
36 #endif
37
38 GraphicalUi *GraphicalUi::_instance = 0;
39 QWidget *GraphicalUi::_mainWidget = 0;
40 QHash<QString, ActionCollection *> GraphicalUi::_actionCollections;
41 ContextMenuActionProvider *GraphicalUi::_contextMenuActionProvider = 0;
42 ToolBarActionProvider *GraphicalUi::_toolBarActionProvider = 0;
43 UiStyle *GraphicalUi::_uiStyle = 0;
44 bool GraphicalUi::_onAllDesktops = false;
45
46 GraphicalUi::GraphicalUi(QObject *parent) : AbstractUi(parent) {
47   Q_ASSERT(!_instance);
48   _instance = this;
49
50 #ifdef Q_WS_WIN
51   _dwTickCount = 0;
52 #endif
53 }
54
55 void GraphicalUi::init() {
56 #ifdef Q_WS_WIN
57   mainWidget()->installEventFilter(this);
58 #endif
59 }
60
61 ActionCollection *GraphicalUi::actionCollection(const QString &category) {
62   if(_actionCollections.contains(category))
63     return _actionCollections.value(category);
64   ActionCollection *coll = new ActionCollection(_mainWidget);
65   if(_mainWidget)
66     coll->addAssociatedWidget(_mainWidget);
67   _actionCollections.insert(category, coll);
68   return coll;
69 }
70
71 void GraphicalUi::setMainWidget(QWidget *widget) {
72   _mainWidget = widget;
73 }
74
75 void GraphicalUi::setContextMenuActionProvider(ContextMenuActionProvider *provider) {
76   _contextMenuActionProvider = provider;
77 }
78
79 void GraphicalUi::setToolBarActionProvider(ToolBarActionProvider *provider) {
80   _toolBarActionProvider = provider;
81 }
82
83 void GraphicalUi::setUiStyle(UiStyle *style) {
84   _uiStyle = style;
85 }
86
87 void GraphicalUi::disconnectedFromCore() {
88   _contextMenuActionProvider->disconnectedFromCore();
89   _toolBarActionProvider->disconnectedFromCore();
90   AbstractUi::disconnectedFromCore();
91 }
92
93 bool GraphicalUi::eventFilter(QObject *obj, QEvent *event) {
94 #ifdef Q_WS_WIN
95   if(obj == mainWidget() && event->type() == QEvent::ActivationChange) {
96     _dwTickCount = GetTickCount();
97   }
98 #endif
99   return AbstractUi::eventFilter(obj, event);
100 }
101
102 // Code taken from KStatusNotifierItem for handling minimize/restore
103
104 bool GraphicalUi::checkMainWidgetVisibility(bool perform) {
105 #ifdef Q_WS_WIN
106   // the problem is that we lose focus when the systray icon is activated
107   // and we don't know the former active window
108   // therefore we watch for activation event and use our stopwatch :)
109   if(GetTickCount() - _dwTickCount < 300) {
110     // we were active in the last 300ms -> hide it
111     if(perform)
112       minimizeRestore(false);
113     return false;
114   } else {
115     if(perform)
116       minimizeRestore(true);
117     return true;
118   }
119
120 #elif defined(HAVE_KDE) && defined(Q_WS_X11)
121   KWindowInfo info1 = KWindowSystem::windowInfo(mainWidget()->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop);
122   // mapped = visible (but possibly obscured)
123   bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized();
124
125   //    - not mapped -> show, raise, focus
126   //    - mapped
127   //        - obscured -> raise, focus
128   //        - not obscured -> hide
129   //info1.mappingState() != NET::Visible -> window on another desktop?
130   if(!mapped) {
131     if(perform)
132       minimizeRestore(true);
133     return true;
134
135   } else {
136     QListIterator< WId > it (KWindowSystem::stackingOrder());
137     it.toBack();
138     while(it.hasPrevious()) {
139       WId id = it.previous();
140       if(id == mainWidget()->winId())
141         break;
142
143       KWindowInfo info2 = KWindowSystem::windowInfo(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType);
144
145       if(info2.mappingState() != NET::Visible)
146         continue; // not visible on current desktop -> ignore
147
148       if(!info2.geometry().intersects(mainWidget()->geometry()))
149         continue; // not obscuring the window -> ignore
150
151       if(!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove))
152         continue; // obscured by window kept above -> ignore
153
154       NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask
155                                               | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
156                                               | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
157
158       if(type == NET::Dock || type == NET::TopMenu)
159         continue; // obscured by dock or topmenu -> ignore
160
161       if(perform) {
162         KWindowSystem::raiseWindow(mainWidget()->winId());
163         KWindowSystem::activateWindow(mainWidget()->winId());
164       }
165       return true;
166     }
167
168     //not on current desktop?
169     if(!info1.isOnCurrentDesktop()) {
170       if(perform)
171         KWindowSystem::activateWindow(mainWidget()->winId());
172       return true;
173     }
174
175     if(perform)
176       minimizeRestore(false); // hide
177     return false;
178   }
179 #else
180
181   if(!mainWidget()->isVisible() || mainWidget()->isMinimized()) {
182     if(perform)
183       minimizeRestore(true);
184     return true;
185   } else {
186     if(perform)
187       minimizeRestore(false);
188     return false;
189   }
190
191 #endif
192
193   return true;
194 }
195
196 bool GraphicalUi::isMainWidgetVisible() {
197   return !instance()->checkMainWidgetVisibility(false);
198 }
199
200 void GraphicalUi::minimizeRestore(bool show) {
201   if(show)
202     activateMainWidget();
203   else
204     hideMainWidget();
205 }
206
207 void GraphicalUi::activateMainWidget() {
208 #ifdef HAVE_KDE
209 #  ifdef Q_WS_X11
210     KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
211     if(_onAllDesktops) {
212       KWindowSystem::setOnAllDesktops(mainWidget()->winId(), true);
213     } else {
214       KWindowSystem::setCurrentDesktop(info.desktop());
215     }
216
217     mainWidget()->move(info.frameGeometry().topLeft()); // avoid placement policies
218     mainWidget()->show();
219     mainWidget()->raise();
220     KWindowSystem::raiseWindow(mainWidget()->winId());
221     KWindowSystem::activateWindow(mainWidget()->winId());
222 #  else
223     mainWidget()->show();
224     KWindowSystem::raiseWindow(mainWidget()->winId());
225     KWindowSystem::forceActiveWindow(mainWidget()->winId());
226 #  endif
227
228 #else /* HAVE_KDE */
229
230 #ifdef Q_WS_X11
231   // Bypass focus stealing prevention
232   QX11Info::setAppUserTime(QX11Info::appTime());
233 #endif
234
235   if(mainWidget()->windowState() & Qt::WindowMinimized) {
236     // restore
237     mainWidget()->setWindowState((mainWidget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
238   }
239
240   // this does not actually work on all platforms... and causes more evil than good
241   // mainWidget()->move(mainWidget()->frameGeometry().topLeft()); // avoid placement policies
242   mainWidget()->show();
243   mainWidget()->raise();
244   mainWidget()->activateWindow();
245
246 #endif /* HAVE_KDE */
247 }
248
249 void GraphicalUi::hideMainWidget() {
250
251 #if defined(HAVE_KDE) && defined(Q_WS_X11)
252   KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
253   _onAllDesktops = info.onAllDesktops();
254 #endif
255
256   if(instance()->isHidingMainWidgetAllowed())
257     mainWidget()->hide();
258 }
259
260 void GraphicalUi::toggleMainWidget() {
261   instance()->checkMainWidgetVisibility(true);
262 }