1/*
2 This file is part of the KDE project.
3
4 Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
5 Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
6
7 Permission is hereby granted, free of charge, to any person obtaining a
8 copy of this software and associated documentation files (the "Software"),
9 to deal in the Software without restriction, including without limitation
10 the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 and/or sell copies of the Software, and to permit persons to whom the
12 Software is furnished to do so, subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be included in
15 all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 DEALINGS IN THE SOFTWARE.
24*/
25
26#include "appmenu.h"
27#include "kdbusimporter.h"
28#include "menuimporteradaptor.h"
29#include "appmenuadaptor.h"
30#include "appmenu_dbus.h"
31#include "topmenubar.h"
32#include "verticalmenu.h"
33
34#include <QDBusInterface>
35#include <QDBusReply>
36#include <QDBusPendingCallWatcher>
37#include <QMenu>
38#include <QApplication>
39#include <QDesktopWidget>
40
41#include <KDebug>
42#include <KWindowSystem>
43#include <KWindowInfo>
44#include <KConfig>
45#include <KConfigGroup>
46#include <kpluginfactory.h>
47#include <kpluginloader.h>
48#include <netwm.h>
49
50K_PLUGIN_FACTORY(AppMenuFactory,
51 registerPlugin<AppMenuModule>();
52 )
53K_EXPORT_PLUGIN(AppMenuFactory("appmenu"))
54
55AppMenuModule::AppMenuModule(QObject* parent, const QList<QVariant>&)
56 : KDEDModule(parent),
57 m_parent(parent),
58 m_menuImporter(0),
59 m_appmenuDBus(new AppmenuDBus(parent)),
60 m_menubar(0),
61 m_menu(0),
62 m_screenTimer(new QTimer(this)),
63 m_waitingAction(0),
64 m_currentScreen(-1)
65{
66 reconfigure();
67
68 m_appmenuDBus->connectToBus();
69
70 m_currentScreen = currentScreen();
71
72 connect(m_appmenuDBus, SIGNAL(appShowMenu(int, int, WId)), SLOT(slotShowMenu(int, int, WId)));
73 connect(m_appmenuDBus, SIGNAL(moduleReconfigure()), SLOT(reconfigure()));
74
75 // transfer our signals to dbus
76 connect(this, SIGNAL(showRequest(qulonglong)), m_appmenuDBus, SIGNAL(showRequest(qulonglong)));
77 connect(this, SIGNAL(menuAvailable(qulonglong)), m_appmenuDBus, SIGNAL(menuAvailable(qulonglong)));
78 connect(this, SIGNAL(clearMenus()), m_appmenuDBus, SIGNAL(clearMenus()));
79 connect(this, SIGNAL(menuHidden(qulonglong)), m_appmenuDBus, SIGNAL(menuHidden(qulonglong)));
80 connect(this, SIGNAL(WindowRegistered(qulonglong, const QString&, const QDBusObjectPath&)),
81 m_appmenuDBus, SIGNAL(WindowRegistered(qulonglong, const QString&, const QDBusObjectPath&)));
82 connect(this, SIGNAL(WindowUnregistered(qulonglong)), m_appmenuDBus, SIGNAL(WindowUnregistered(qulonglong)));
83}
84
85AppMenuModule::~AppMenuModule()
86{
87 emit clearMenus();
88 hideMenubar();
89 if (m_menubar) {
90 delete m_menubar;
91 }
92 delete m_menuImporter;
93 delete m_appmenuDBus;
94}
95
96void AppMenuModule::slotShowMenu(int x, int y, WId id)
97{
98 static KDBusMenuImporter *importer = 0;
99
100 if (!m_menuImporter) {
101 return;
102 }
103
104 // If menu visible, hide it
105 if (m_menu && m_menu->isVisible()) {
106 m_menu->hide();
107 return;
108 }
109
110 //dbus call by user (for khotkey shortcut)
111 if (x == -1 || y == -1) {
112 // We do not know kwin button position, so tell kwin to show menu
113 emit showRequest(KWindowSystem::self()->activeWindow());
114 return;
115 }
116
117 importer = getImporter(id);
118
119 if (!importer) {
120 return;
121 }
122
123 QMenu *menu = importer->menu();
124
125 // Window do not have menu
126 if (!menu) {
127 return;
128 }
129
130 m_menu = new VerticalMenu();
131 m_menu->setParentWid(id);
132 // Populate menu
133 foreach (QAction *action, menu->actions()) {
134 m_menu->addAction(action);
135 }
136 m_menu->popup(QPoint(x, y));
137 // Activate waiting action if exist
138 if (m_waitingAction) {
139 m_menu->setActiveAction(m_waitingAction);
140 m_waitingAction = 0;
141 }
142 connect(m_menu, SIGNAL(aboutToHide()), this, SLOT(slotAboutToHide()));
143}
144
145void AppMenuModule::slotAboutToHide()
146{
147 if (m_menu) {
148 emit menuHidden(m_menu->parentWid());
149 m_menu->deleteLater();
150 m_menu = 0;
151 }
152}
153
154// New window registered
155void AppMenuModule::slotWindowRegistered(WId id, const QString& service, const QDBusObjectPath& path)
156{
157 KDBusMenuImporter* importer = m_importers.take(id);
158 if (importer) {
159 delete importer;
160 }
161
162 // Application already active so check if we need create menubar
163 if ( m_menuStyle == "TopMenuBar" && id == KWindowSystem::self()->activeWindow()) {
164 slotActiveWindowChanged(id);
165 } else if (m_menuStyle == "ButtonVertical") {
166 KWindowInfo info = KWindowSystem::windowInfo(id, 0, NET::WM2WindowClass);
167 // Tell Kwin menu is available
168 emit menuAvailable(id);
169 // FIXME: https://bugs.kde.org/show_bug.cgi?id=317926
170 if (info.windowClassName() != "kmix") {
171 getImporter(id);
172 }
173 }
174
175 // Send a signal on bus for others dbus interface registrars
176 emit WindowRegistered(id, service, path);
177}
178
179// Window unregistered
180void AppMenuModule::slotWindowUnregistered(WId id)
181{
182 KDBusMenuImporter* importer = m_importers.take(id);
183
184 // Send a signal on bus for others dbus interface registrars
185 emit WindowUnregistered(id);
186
187 if (importer) {
188 delete importer;
189 }
190
191 if (m_menubar && m_menubar->parentWid() == id) {
192 hideMenubar();
193 }
194}
195
196// Keyboard activation requested, transmit it to menu
197void AppMenuModule::slotActionActivationRequested(QAction* a)
198{
199 // If we have a topmenubar, activate action
200 if (m_menubar) {
201 m_menubar->setActiveAction(a);
202 m_menubar->show();
203 } else { // else send request to kwin or others dbus interface registrars
204 m_waitingAction = a;
205 emit showRequest(KWindowSystem::self()->activeWindow());
206 }
207}
208
209// Current window change, update menubar
210// See comments in slotWindowRegistered() for why we get importers here
211void AppMenuModule::slotActiveWindowChanged(WId id)
212{
213 KWindowInfo info = KWindowSystem::windowInfo(id, NET::WMWindowType);
214 unsigned long mask = NET::AllTypesMask;
215
216 m_currentScreen = currentScreen();
217
218 if (id == 0) {// Ignore root window
219 return;
220 } else if (info.windowType(mask) & NET::Dock) { // Hide immediatly menubar for docks (krunner)
221 hideMenubar();
222 return;
223 }
224
225 if (!m_menuImporter->serviceExist(id)) { // No menu exist, check for another menu for application
226 WId recursiveId = m_menuImporter->recursiveMenuId(id);
227 if (recursiveId) {
228 id = recursiveId;
229 }
230 }
231
232 KDBusMenuImporter *importer = getImporter(id);
233 if (!importer) {
234 hideMenubar();
235 return;
236 }
237
238 QMenu *menu = importer->menu();
239
240 if(menu) {
241 showMenuBar(menu);
242 m_menubar->setParentWid(id);
243 } else {
244 hideMenubar();
245 }
246}
247
248void AppMenuModule::slotShowCurrentWindowMenu()
249{
250 slotActiveWindowChanged(KWindowSystem::self()->activeWindow());
251}
252
253void AppMenuModule::slotCurrentScreenChanged()
254{
255 if (m_currentScreen != currentScreen()) {
256 if (m_menubar) {
257 m_menubar->setParentWid(0);
258 }
259 slotActiveWindowChanged(KWindowSystem::self()->activeWindow());
260 }
261}
262
263void AppMenuModule::slotBarNeedResize()
264{
265 if (m_menubar) {
266 m_menubar->updateSize();
267 m_menubar->move(centeredMenubarPos());
268 }
269}
270
271// reload settings
272void AppMenuModule::reconfigure()
273{
274 KConfig config( "kdeglobals", KConfig::FullConfig );
275 KConfigGroup configGroup = config.group("Appmenu Style");
276 m_menuStyle = configGroup.readEntry("Style", "InApplication");
277
278 m_waitingAction = 0;
279
280 // hide menubar if exist
281 hideMenubar();
282 if (m_menubar) {
283 delete m_menubar;
284 m_menubar = 0;
285 }
286
287 slotAboutToHide(); // hide vertical menu if exist
288
289 // Disconnect all options specifics signals
290 disconnect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(slotActiveWindowChanged(WId)));
291 disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(slotShowCurrentWindowMenu()));
292 disconnect(m_screenTimer, SIGNAL(timeout()), this, SLOT(slotCurrentScreenChanged()));
293
294 m_screenTimer->stop();
295
296 // Tell kwin to clean its titlebar
297 emit clearMenus();
298
299 if (m_menuStyle == "InApplication") {
300 if (m_menuImporter) {
301 delete m_menuImporter;
302 m_menuImporter = 0;
303 }
304 return;
305 }
306
307 // Setup a menu importer if needed
308 if (!m_menuImporter) {
309 m_menuImporter = new MenuImporter(m_parent);
310 connect(m_menuImporter, SIGNAL(WindowRegistered(WId, const QString&, const QDBusObjectPath&)),
311 SLOT(slotWindowRegistered(WId, const QString&, const QDBusObjectPath&)));
312 connect(m_menuImporter, SIGNAL(WindowUnregistered(WId)),
313 SLOT(slotWindowUnregistered(WId)));
314 m_menuImporter->connectToBus();
315 }
316
317 if( m_menuStyle == "ButtonVertical" ) {
318 foreach(WId id, m_menuImporter->ids()) {
319 emit menuAvailable(id);
320 }
321 }
322
323 // Setup top menubar if needed
324 if (m_menuStyle == "TopMenuBar") {
325 m_menubar = new TopMenuBar();
326 connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(slotActiveWindowChanged(WId)));
327 connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(slotShowCurrentWindowMenu()));
328 connect(m_screenTimer, SIGNAL(timeout()), this, SLOT(slotCurrentScreenChanged()));
329 connect(m_menubar, SIGNAL(needResize()), SLOT(slotBarNeedResize()));
330 m_screenTimer->start(1000);
331 slotShowCurrentWindowMenu();
332 }
333}
334
335KDBusMenuImporter* AppMenuModule::getImporter(WId id)
336{
337 KDBusMenuImporter* importer = 0;
338 if (m_importers.contains(id)) { // importer already exist
339 importer = m_importers.value(id);
340 } else if (m_menuImporter->serviceExist(id)) { // get importer
341 importer = new KDBusMenuImporter(id, m_menuImporter->serviceForWindow(id), &m_icons,
342 m_menuImporter->pathForWindow(id), this);
343 if (importer) {
344 QMetaObject::invokeMethod(importer, "updateMenu", Qt::DirectConnection);
345 connect(importer, SIGNAL(actionActivationRequested(QAction*)),
346 SLOT(slotActionActivationRequested(QAction*)));
347 m_importers.insert(id, importer);
348 }
349 }
350 return importer;
351}
352
353void AppMenuModule::showMenuBar(QMenu *menu)
354{
355 if (!menu) {
356 return;
357 }
358
359 m_menubar->setMenu(menu);
360 if (menu->actions().length()) {
361 m_menubar->enableMouseTracking();
362 }
363}
364
365void AppMenuModule::hideMenubar()
366{
367 if (!m_menubar) {
368 return;
369 }
370
371 m_menubar->enableMouseTracking(false);
372 if (m_menubar->isVisible()) {
373 m_menubar->hide();
374 }
375}
376
377int AppMenuModule::currentScreen()
378{
379 KWindowInfo info = KWindowSystem::windowInfo(KWindowSystem::self()->activeWindow(),
380 NET::WMGeometry);
381 int x = info.geometry().x();
382 int y = info.geometry().y();
383
384 QDesktopWidget *desktop = QApplication::desktop();
385 return desktop->screenNumber(QPoint(x,y));
386}
387
388
389QPoint AppMenuModule::centeredMenubarPos()
390{
391 QDesktopWidget *desktop = QApplication::desktop();
392 m_currentScreen = currentScreen();
393 QRect screen = desktop->availableGeometry(m_currentScreen);
394 int x = screen.center().x() - m_menubar->sizeHint().width()/2;
395 return QPoint(x, screen.topLeft().y());
396}
397
398
399#include "appmenu.moc"
400