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 | |
50 | K_PLUGIN_FACTORY(, |
51 | registerPlugin<AppMenuModule>(); |
52 | ) |
53 | K_EXPORT_PLUGIN(AppMenuFactory("appmenu" )) |
54 | |
55 | 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 | |
85 | 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 | |
96 | void AppMenuModule::(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 * = 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 | |
145 | void AppMenuModule::() |
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 |
155 | void AppMenuModule::(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 |
180 | void AppMenuModule::(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 |
197 | void AppMenuModule::(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 |
211 | void AppMenuModule::(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 * = importer->menu(); |
239 | |
240 | if(menu) { |
241 | showMenuBar(menu); |
242 | m_menubar->setParentWid(id); |
243 | } else { |
244 | hideMenubar(); |
245 | } |
246 | } |
247 | |
248 | void AppMenuModule::() |
249 | { |
250 | slotActiveWindowChanged(KWindowSystem::self()->activeWindow()); |
251 | } |
252 | |
253 | void AppMenuModule::() |
254 | { |
255 | if (m_currentScreen != currentScreen()) { |
256 | if (m_menubar) { |
257 | m_menubar->setParentWid(0); |
258 | } |
259 | slotActiveWindowChanged(KWindowSystem::self()->activeWindow()); |
260 | } |
261 | } |
262 | |
263 | void AppMenuModule::() |
264 | { |
265 | if (m_menubar) { |
266 | m_menubar->updateSize(); |
267 | m_menubar->move(centeredMenubarPos()); |
268 | } |
269 | } |
270 | |
271 | // reload settings |
272 | void AppMenuModule::() |
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 | |
335 | KDBusMenuImporter* AppMenuModule::(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 | |
353 | void AppMenuModule::(QMenu *) |
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 | |
365 | void AppMenuModule::() |
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 | |
377 | int AppMenuModule::() |
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 | |
389 | QPoint AppMenuModule::() |
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 | |