fb4a2c7fdbfe7baee1102ce300e1040cb0d8efac
[quassel.git] / src / uisupport / graphicalui.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
22  ***************************************************************************/
23
24 #include "graphicalui.h"
25
26 #include "actioncollection.h"
27 #include "uisettings.h"
28 #include "contextmenuactionprovider.h"
29 #include "toolbaractionprovider.h"
30
31 #ifdef Q_WS_X11
32 #  include <QX11Info>
33 #endif
34 #ifdef HAVE_KDE4
35 #  include <KWindowInfo>
36 #  include <KWindowSystem>
37 #endif
38
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 namespace {
47
48 GraphicalUi *_instance{nullptr};
49
50 }
51
52
53 GraphicalUi *GraphicalUi::instance() {
54     return _instance;
55 }
56
57
58 GraphicalUi::GraphicalUi(QObject *parent) : AbstractUi(parent)
59 {
60     Q_ASSERT(!_instance);
61     _instance = this;
62
63 #ifdef Q_OS_WIN
64     _dwTickCount = 0;
65 #endif
66 #ifdef Q_OS_MAC
67     GetFrontProcess(&_procNum);
68 #endif
69 }
70
71
72 void GraphicalUi::init()
73 {
74 #ifdef Q_OS_WIN
75     mainWidget()->installEventFilter(this);
76 #endif
77 }
78
79
80 ActionCollection *GraphicalUi::actionCollection(const QString &category, const QString &translatedCategory)
81 {
82     if (_actionCollections.contains(category))
83         return _actionCollections.value(category);
84     ActionCollection *coll = new ActionCollection(_mainWidget);
85
86     if (!translatedCategory.isEmpty())
87         coll->setProperty("Category", translatedCategory);
88     else
89         coll->setProperty("Category", category);
90
91     if (_mainWidget)
92         coll->addAssociatedWidget(_mainWidget);
93     _actionCollections.insert(category, coll);
94     return coll;
95 }
96
97
98 QHash<QString, ActionCollection *> GraphicalUi::actionCollections()
99 {
100     return _actionCollections;
101 }
102
103
104 void GraphicalUi::loadShortcuts()
105 {
106     foreach(ActionCollection *coll, actionCollections())
107     coll->readSettings();
108 }
109
110
111 void GraphicalUi::saveShortcuts()
112 {
113     ShortcutSettings s;
114     s.clear();
115     foreach(ActionCollection *coll, actionCollections())
116     coll->writeSettings();
117 }
118
119
120 void GraphicalUi::setMainWidget(QWidget *widget)
121 {
122     _mainWidget = widget;
123 }
124
125
126 void GraphicalUi::setContextMenuActionProvider(ContextMenuActionProvider *provider)
127 {
128     _contextMenuActionProvider = provider;
129 }
130
131
132 void GraphicalUi::setToolBarActionProvider(ToolBarActionProvider *provider)
133 {
134     _toolBarActionProvider = provider;
135 }
136
137
138 void GraphicalUi::setUiStyle(UiStyle *style)
139 {
140     _uiStyle = style;
141 }
142
143
144 void GraphicalUi::disconnectedFromCore()
145 {
146     _contextMenuActionProvider->disconnectedFromCore();
147     _toolBarActionProvider->disconnectedFromCore();
148     AbstractUi::disconnectedFromCore();
149 }
150
151
152 bool GraphicalUi::eventFilter(QObject *obj, QEvent *event)
153 {
154 #ifdef Q_OS_WIN
155     if (obj == mainWidget() && event->type() == QEvent::ActivationChange) {
156         _dwTickCount = GetTickCount();
157     }
158 #endif
159     return AbstractUi::eventFilter(obj, event);
160 }
161
162
163 // NOTE: Window activation stuff seems to work just fine in Plasma 5 without requiring X11 hacks.
164 // TODO: Evaluate cleaning all this up once we can get rid of Qt4/KDE4
165
166 // Code taken from KStatusNotifierItem for handling minimize/restore
167
168 bool GraphicalUi::checkMainWidgetVisibility(bool perform)
169 {
170 #ifdef Q_OS_WIN
171     // the problem is that we lose focus when the systray icon is activated
172     // and we don't know the former active window
173     // therefore we watch for activation event and use our stopwatch :)
174     if (GetTickCount() - _dwTickCount < 300) {
175         // we were active in the last 300ms -> hide it
176         if (perform)
177             minimizeRestore(false);
178         return false;
179     }
180     else {
181         if (perform)
182             minimizeRestore(true);
183         return true;
184     }
185
186 #elif defined(HAVE_KDE4) && defined(Q_WS_X11)
187     KWindowInfo info1 = KWindowSystem::windowInfo(mainWidget()->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop);
188     // mapped = visible (but possibly obscured)
189     bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized();
190
191     //    - not mapped -> show, raise, focus
192     //    - mapped
193     //        - obscured -> raise, focus
194     //        - not obscured -> hide
195     //info1.mappingState() != NET::Visible -> window on another desktop?
196     if (!mapped) {
197         if (perform)
198             minimizeRestore(true);
199         return true;
200     }
201     else {
202         QListIterator<WId> it(KWindowSystem::stackingOrder());
203         it.toBack();
204         while (it.hasPrevious()) {
205             WId id = it.previous();
206             if (id == mainWidget()->winId())
207                 break;
208
209             KWindowInfo info2 = KWindowSystem::windowInfo(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType);
210
211             if (info2.mappingState() != NET::Visible)
212                 continue;  // not visible on current desktop -> ignore
213
214             if (!info2.geometry().intersects(mainWidget()->geometry()))
215                 continue;  // not obscuring the window -> ignore
216
217             if (!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove))
218                 continue;  // obscured by window kept above -> ignore
219
220             NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask
221                 | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
222                 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
223
224             if (type == NET::Dock || type == NET::TopMenu)
225                 continue;  // obscured by dock or topmenu -> ignore
226
227             if (perform) {
228                 KWindowSystem::raiseWindow(mainWidget()->winId());
229                 KWindowSystem::activateWindow(mainWidget()->winId());
230             }
231             return true;
232         }
233
234         //not on current desktop?
235         if (!info1.isOnCurrentDesktop()) {
236             if (perform)
237                 KWindowSystem::activateWindow(mainWidget()->winId());
238             return true;
239         }
240
241         if (perform)
242             minimizeRestore(false);  // hide
243         return false;
244     }
245 #else
246
247     if (!mainWidget()->isVisible() || mainWidget()->isMinimized() || !mainWidget()->isActiveWindow()) {
248         if (perform)
249             minimizeRestore(true);
250         return true;
251     }
252     else {
253         if (perform)
254             minimizeRestore(false);
255         return false;
256     }
257
258 #endif
259
260     return true;
261 }
262
263
264 bool GraphicalUi::isMainWidgetVisible()
265 {
266     return !instance()->checkMainWidgetVisibility(false);
267 }
268
269
270 void GraphicalUi::minimizeRestore(bool show)
271 {
272     if (show)
273         activateMainWidget();
274     else
275         hideMainWidget();
276 }
277
278
279 void GraphicalUi::activateMainWidget()
280 {
281 #ifdef HAVE_KDE4
282 #  ifdef Q_WS_X11
283     KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
284     if (_onAllDesktops) {
285         KWindowSystem::setOnAllDesktops(mainWidget()->winId(), true);
286     }
287     else {
288         KWindowSystem::setCurrentDesktop(info.desktop());
289     }
290
291     mainWidget()->move(info.frameGeometry().topLeft()); // avoid placement policies
292     mainWidget()->show();
293     mainWidget()->raise();
294     KWindowSystem::raiseWindow(mainWidget()->winId());
295     KWindowSystem::activateWindow(mainWidget()->winId());
296 #  else
297     mainWidget()->show();
298     KWindowSystem::raiseWindow(mainWidget()->winId());
299     KWindowSystem::forceActiveWindow(mainWidget()->winId());
300 #  endif
301
302 #else /* HAVE_KDE4 */
303
304 #ifdef Q_WS_X11
305     // Bypass focus stealing prevention
306     QX11Info::setAppUserTime(QX11Info::appTime());
307 #endif
308
309     if (mainWidget()->windowState() & Qt::WindowMinimized) {
310         // restore
311         mainWidget()->setWindowState((mainWidget()->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
312     }
313
314     // this does not actually work on all platforms... and causes more evil than good
315     // mainWidget()->move(mainWidget()->frameGeometry().topLeft()); // avoid placement policies
316 #ifdef Q_OS_MAC
317     SetFrontProcess(&instance()->_procNum);
318 #else
319     mainWidget()->show();
320     mainWidget()->raise();
321     mainWidget()->activateWindow();
322 #endif
323
324 #endif /* HAVE_KDE4 */
325 }
326
327
328 void GraphicalUi::hideMainWidget()
329 {
330 #if defined(HAVE_KDE4) && defined(Q_WS_X11)
331     KWindowInfo info = KWindowSystem::windowInfo(mainWidget()->winId(), NET::WMDesktop | NET::WMFrameExtents);
332     _onAllDesktops = info.onAllDesktops();
333 #endif
334
335     if (instance()->isHidingMainWidgetAllowed())
336 #ifdef Q_OS_MAC
337         ShowHideProcess(&instance()->_procNum, false);
338 #else
339         mainWidget()->hide();
340 #endif
341 }
342
343
344 void GraphicalUi::toggleMainWidget()
345 {
346     instance()->checkMainWidgetVisibility(true);
347 }