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