File indexing completed on 2024-05-05 14:23:15
0001 /***************************************************************************** 0002 * Copyright 2007 Thomas Luebking <thomas.luebking@web.de> * 0003 * Copyright 2007 - 2010 Craig Drummond <craig.p.drummond@gmail.com> * 0004 * Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU Lesser General Public License as * 0008 * published by the Free Software Foundation; either version 2.1 of the * 0009 * License, or (at your option) version 3, or any later version accepted * 0010 * by the membership of KDE e.V. (or its successor approved by the * 0011 * membership of KDE e.V.), which shall act as a proxy defined in * 0012 * Section 6 of version 3 of the license. * 0013 * * 0014 * This program is distributed in the hope that it will be useful, * 0015 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 0017 * Lesser General Public License for more details. * 0018 * * 0019 * You should have received a copy of the GNU Lesser General Public * 0020 * License along with this library. If not, * 0021 * see <http://www.gnu.org/licenses/>. * 0022 *****************************************************************************/ 0023 0024 #include <qtcurve-utils/utils.h> 0025 #include <qtcurve-utils/qtutils.h> 0026 0027 #include <QActionEvent> 0028 #include <QApplication> 0029 #include <QDBusConnectionInterface> 0030 #include <QDBusMessage> 0031 #include <QLayout> 0032 #include <QMenuBar> 0033 #include <QWindowStateChangeEvent> 0034 0035 #include "macmenu.h" 0036 #include "macmenu-dbus.h" 0037 0038 #include <QtDebug> 0039 0040 using namespace Bespin; 0041 0042 static MacMenu *instance = 0; 0043 #define MSG(_FNC_) QDBusMessage::createMethodCall( "org.kde.XBar", "/XBar", "org.kde.XBar", _FNC_ ) 0044 #define XBAR_SEND( _MSG_ ) QDBusConnection::sessionBus().send( _MSG_ ) 0045 0046 bool 0047 FullscreenWatcher::eventFilter(QObject *o, QEvent *ev) 0048 { 0049 QWidget *window = QtCurve::qtcToWidget(o); 0050 if (!(window && ev->type() == QEvent::WindowStateChange)) 0051 return false; 0052 if (window->windowState() & Qt::WindowFullScreen) 0053 instance->deactivate(window); 0054 else 0055 instance->activate(window); 0056 return false; 0057 } 0058 0059 static FullscreenWatcher *fullscreenWatcher = 0; 0060 0061 MacMenu::MacMenu() : QObject() 0062 { 0063 usingMacMenu = QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.XBar"); 0064 service = QString("org.kde.XBar-%1").arg(QCoreApplication::applicationPid()); 0065 // register me 0066 QDBusConnection::sessionBus().registerService(service); 0067 QDBusConnection::sessionBus().registerObject("/XBarClient", this); 0068 0069 connect (qApp, SIGNAL(aboutToQuit()), this, SLOT(deactivate())); 0070 } 0071 0072 0073 void 0074 MacMenu::manage(QMenuBar *menu) 0075 { 0076 if (!menu) // ... 0077 return; 0078 0079 // we only accept menus that are placed on a QMainWindow - for the moment, and probably ever 0080 QWidget *dad = menu->parentWidget(); 0081 if (!(dad && dad->isWindow() && dad->inherits("QMainWindow") && dad->layout() && dad->layout()->menuBar() == menu)) 0082 return; 0083 0084 // if ((dad = dad->parentWidget()) && dad->inherits("QMdiSubWindow")) 0085 // return; 0086 0087 0088 if (!instance) 0089 { 0090 instance = new MacMenu; 0091 /*MacMenuAdaptor *adapt = */new MacMenuAdaptor(instance); 0092 fullscreenWatcher = new FullscreenWatcher; 0093 } 0094 else if (instance->items.contains(menu)) 0095 return; // no double adds please! 0096 0097 if (instance->usingMacMenu) 0098 instance->activate(menu); 0099 0100 connect (menu, SIGNAL(destroyed(QObject *)), instance, SLOT(_release(QObject *))); 0101 0102 instance->items.append(menu); 0103 } 0104 0105 void 0106 MacMenu::release(QMenuBar *menu) 0107 { 0108 if (!instance) 0109 return; 0110 instance->_release(menu); 0111 } 0112 0113 bool 0114 MacMenu::isActive() 0115 { 0116 return instance && instance->usingMacMenu; 0117 } 0118 0119 void 0120 MacMenu::_release(QObject *o) 0121 { 0122 XBAR_SEND( MSG("unregisterMenu") << (qlonglong)o ); 0123 0124 QMenuBar *menu = qobject_cast<QMenuBar*>(o); 0125 if (!menu) return; 0126 0127 items.removeAll(menu); 0128 menu->removeEventFilter(this); 0129 QWidget *dad = menu->parentWidget(); 0130 if (dad && dad->layout()) 0131 dad->layout()->setMenuBar(menu); 0132 menu->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); 0133 menu->adjustSize(); 0134 // menu->updateGeometry(); 0135 } 0136 0137 void 0138 MacMenu::activate() 0139 { 0140 MenuList::iterator menu = items.begin(); 0141 while (menu != items.end()) 0142 { 0143 if (*menu) 0144 { activate(*menu); ++menu; } 0145 else 0146 { actions.remove(*menu); menu = items.erase(menu); } 0147 } 0148 usingMacMenu = true; 0149 } 0150 0151 void 0152 MacMenu::activate(QMenuBar *menu) 0153 { 0154 menu->removeEventFilter(this); 0155 0156 // and WOWWWW - no more per window menubars... 0157 menu->setFixedSize(0,0); 0158 //NOTICE i used to set the menu's parent->layout()->setMenuBar(0) to get rid of the free space 0159 // but this leeds to side effects (e.g. kcalc won't come up anymore...) 0160 // so now the stylehint for the free space below checks the menubar height and returns 0161 // a negative value so that final result will be 1 px heigh... 0162 menu->updateGeometry(); 0163 0164 // we need to hold a copy of this list to handle action removes 0165 // (as we get the event after the action has been removed from the widget...) 0166 actions[menu] = menu->actions(); 0167 0168 // find a nice header 0169 QString title = menu->window()->windowTitle(); 0170 const QStringList appArgs = QCoreApplication::arguments(); 0171 QString name = appArgs.isEmpty() ? "" : appArgs.at(0).section('/', -1); 0172 if (title.isEmpty()) 0173 title = name; 0174 else 0175 { 0176 int i = title.indexOf(name, 0, Qt::CaseInsensitive); 0177 if (i > -1) 0178 title = title.mid(i, name.length()); 0179 } 0180 title = title.section(" - ", -1); 0181 if (title.isEmpty()) 0182 { 0183 if (!menu->actions().isEmpty()) 0184 title = menu->actions().at(0)->text(); 0185 if (title.isEmpty()) 0186 title = "QApplication"; 0187 } 0188 // register the menu via dbus 0189 QStringList entries; 0190 foreach (QAction *action, menu->actions()) { 0191 if (action->isSeparator()) { 0192 entries << "<XBAR_SEPARATOR/>"; 0193 } else { 0194 entries << action->text(); 0195 } 0196 } 0197 XBAR_SEND( MSG("registerMenu") << service << (qlonglong)menu << title << entries ); 0198 // TODO cause of now async call, the following should - maybe - attached to the above?!! 0199 if (menu->isActiveWindow()) 0200 XBAR_SEND( MSG("requestFocus") << (qlonglong)menu ); 0201 // take care of several widget events! 0202 menu->installEventFilter(this); 0203 if (menu->window()) 0204 { 0205 menu->window()->removeEventFilter(fullscreenWatcher); 0206 menu->window()->installEventFilter(fullscreenWatcher); 0207 } 0208 } 0209 0210 void 0211 MacMenu::activate(QWidget *window) 0212 { 0213 MenuList::iterator menu = items.begin(); 0214 while (menu != items.end()) 0215 { 0216 if (*menu) 0217 { 0218 if ((*menu)->window() == window) 0219 { activate(*menu); return; } 0220 ++menu; 0221 } 0222 else 0223 { actions.remove(*menu); menu = items.erase(menu); } 0224 } 0225 } 0226 0227 void 0228 MacMenu::deactivate() 0229 { 0230 usingMacMenu = false; 0231 0232 MenuList::iterator i = items.begin(); 0233 QMenuBar *menu = 0; 0234 while (i != items.end()) 0235 { 0236 actions.remove(*i); 0237 if ((menu = *i)) 0238 { 0239 deactivate(menu); 0240 ++i; 0241 } 0242 else 0243 i = items.erase(i); 0244 } 0245 } 0246 0247 void 0248 MacMenu::deactivate(QMenuBar *menu) 0249 { 0250 menu->removeEventFilter(this); 0251 QWidget *dad = menu->parentWidget(); 0252 if (dad && dad->layout()) 0253 dad->layout()->setMenuBar(menu); 0254 menu->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); 0255 menu->adjustSize(); 0256 // menu->updateGeometry(); 0257 } 0258 0259 void 0260 MacMenu::deactivate(QWidget *window) 0261 { 0262 MenuList::iterator menu = items.begin(); 0263 while (menu != items.end()) 0264 { 0265 if (*menu) 0266 { 0267 if ((*menu)->window() == window) 0268 { deactivate(*menu); return; } 0269 ++menu; 0270 } 0271 else 0272 { actions.remove(*menu); menu = items.erase(menu); } 0273 } 0274 } 0275 0276 QMenuBar * 0277 MacMenu::menuBar(qlonglong key) 0278 { 0279 MenuList::iterator i = items.begin(); 0280 QMenuBar *menu; 0281 while (i != items.end()) 0282 { 0283 if (!(menu = *i)) 0284 { 0285 actions.remove(menu); 0286 i = items.erase(i); 0287 } 0288 else 0289 { 0290 if ((qlonglong)menu == key) 0291 return menu; 0292 else 0293 ++i; 0294 } 0295 } 0296 return nullptr; 0297 } 0298 0299 void 0300 MacMenu::popup(qlonglong key, int idx, int x, int y) 0301 { 0302 QMenuBar *menu = menuBar(key); 0303 if (!menu) return; 0304 0305 QMenu *pop; 0306 for (int i = 0; i < menu->actions().count(); ++i) 0307 { 0308 if (!(pop = menu->actions().at(i)->menu())) 0309 continue; 0310 0311 if (i == idx) { 0312 if (!pop->isVisible()) 0313 { 0314 connect (pop, SIGNAL(aboutToHide()), this, SLOT(menuClosed())); 0315 XBAR_SEND( MSG("setOpenPopup") << idx ); 0316 pop->popup(QPoint(x,y)); 0317 } 0318 else 0319 { 0320 XBAR_SEND( MSG("setOpenPopup") << -1000 ); 0321 pop->hide(); 0322 } 0323 } 0324 else 0325 pop->hide(); 0326 } 0327 } 0328 0329 void 0330 MacMenu::popDown(qlonglong key) 0331 { 0332 QMenuBar *menu = menuBar(key); 0333 if (!menu) return; 0334 0335 QWidget *pop; 0336 for (int i = 0; i < menu->actions().count(); ++i) 0337 { 0338 if (!(pop = menu->actions().at(i)->menu())) 0339 continue; 0340 disconnect (pop, SIGNAL(aboutToHide()), this, SLOT(menuClosed())); 0341 pop->hide(); 0342 // menu->activateWindow(); 0343 break; 0344 } 0345 } 0346 0347 static bool inHover = false; 0348 0349 void 0350 MacMenu::hover(qlonglong key, int idx, int x, int y) 0351 { 0352 QMenuBar *menu = menuBar(key); 0353 if (!menu) return; 0354 0355 QWidget *pop; 0356 for (int i = 0; i < menu->actions().count(); ++i) 0357 { 0358 if ((i == idx) || !(pop = menu->actions().at(i)->menu())) 0359 continue; 0360 if (pop->isVisible()) 0361 { 0362 inHover = true; 0363 popup(key, idx, x, y); // TODO: this means a useless second pass above... 0364 inHover = false; 0365 break; 0366 } 0367 } 0368 } 0369 0370 static QMenuBar* 0371 bar4menu(QMenu *menu) 0372 { 0373 if (!menu->menuAction()) 0374 return 0; 0375 if (menu->menuAction()->associatedWidgets().isEmpty()) 0376 return 0; 0377 foreach (QWidget *w, menu->menuAction()->associatedWidgets()) { 0378 if (qobject_cast<QMenuBar*>(w)) { 0379 return static_cast<QMenuBar*>(w); 0380 } 0381 } 0382 return 0; 0383 } 0384 0385 void 0386 MacMenu::menuClosed() 0387 { 0388 QObject * _sender = sender(); 0389 if (!_sender) 0390 return; 0391 0392 disconnect (sender(), SIGNAL(aboutToHide()), this, SLOT(menuClosed())); 0393 if (!inHover) 0394 { 0395 XBAR_SEND( MSG("setOpenPopup") << -500 ); 0396 0397 if (QMenu *menu = qobject_cast<QMenu*>(_sender)) 0398 if (QMenuBar *bar = bar4menu(menu)) 0399 bar->activateWindow(); 0400 } 0401 } 0402 0403 void 0404 MacMenu::changeAction(QMenuBar *menu, QActionEvent *ev) 0405 { 0406 int idx; 0407 const QString title = ev->action()->isSeparator() ? "<XBAR_SEPARATOR/>" : ev->action()->text(); 0408 if (ev->type() == QEvent::ActionAdded) 0409 { 0410 idx = ev->before() ? menu->actions().indexOf(ev->before())-1 : -1; 0411 XBAR_SEND( MSG("addEntry") << (qlonglong)menu << idx << title ); 0412 actions[menu].insert(idx, ev->action()); 0413 return; 0414 } 0415 if (ev->type() == QEvent::ActionChanged) 0416 { 0417 idx = menu->actions().indexOf(ev->action()); 0418 XBAR_SEND( MSG("changeEntry") << (qlonglong)menu << idx << title ); 0419 } 0420 else 0421 { // remove 0422 idx = actions[menu].indexOf(ev->action()); 0423 actions[menu].removeAt(idx); 0424 XBAR_SEND( MSG("removeEntry") << (qlonglong)menu << idx ); 0425 } 0426 } 0427 0428 void 0429 MacMenu::raise(qlonglong key) 0430 { 0431 if (QMenuBar *menu = menuBar(key)) 0432 { 0433 if (QWidget *win = menu->window()) 0434 { 0435 win->showNormal(); 0436 win->activateWindow(); 0437 win->raise(); 0438 } 0439 } 0440 } 0441 0442 bool 0443 MacMenu::eventFilter(QObject *o, QEvent *ev) 0444 { 0445 QMenuBar *menu = qobject_cast<QMenuBar*>(o); 0446 if (!menu) 0447 return false; 0448 0449 if (!usingMacMenu) 0450 return false; 0451 0452 QString func; 0453 switch (ev->type()) 0454 { 0455 case QEvent::Resize: 0456 // menu->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); 0457 if (menu->size() != QSize(0,0)) 0458 { 0459 menu->setFixedSize(0,0); 0460 menu->updateGeometry(); 0461 } 0462 break; 0463 case QEvent::ActionAdded: 0464 case QEvent::ActionChanged: 0465 case QEvent::ActionRemoved: 0466 changeAction(menu, static_cast<QActionEvent*>(ev)); 0467 break; 0468 // case QEvent::ParentChange: 0469 // qDebug() << o << ev; 0470 // return false; 0471 case QEvent::EnabledChange: 0472 if (static_cast<QWidget*>(o)->isEnabled()) 0473 XBAR_SEND( MSG("requestFocus") << (qlonglong)menu ); 0474 else 0475 XBAR_SEND( MSG("releaseFocus") << (qlonglong)menu ); 0476 break; 0477 0478 // TODO: test whether this is the only one and show it? (e.g. what about dialogs...?!) 0479 case QEvent::ApplicationActivate: 0480 // if (items.count() > 1) 0481 // break; 0482 case QEvent::WindowActivate: 0483 XBAR_SEND( MSG("requestFocus") << (qlonglong)menu ); 0484 break; 0485 0486 case QEvent::WindowDeactivate: 0487 // if (items.count() == 1) 0488 // break; 0489 case QEvent::WindowBlocked: 0490 case QEvent::ApplicationDeactivate: 0491 XBAR_SEND( MSG("releaseFocus") << (qlonglong)menu ); 0492 break; 0493 default: 0494 return false; 0495 0496 // maybe these need to be passed through...?! 0497 // QEvent::GrabKeyboard 0498 // QEvent::GrabMouse 0499 // QEvent::KeyPress 0500 // QEvent::KeyRelease 0501 // QEvent::UngrabKeyboard 0502 // QEvent::UngrabMouse 0503 // --- and what about these --- 0504 // QEvent::MenubarUpdated 0505 // QEvent::ParentChange 0506 // ------------------- 0507 } 0508 return false; 0509 } 0510 0511 #undef MSG 0512 #undef XBAR_SEND