File indexing completed on 2024-04-14 15:37:20
0001 /* 0002 * Copyright 2018 Michail Vourlakos <mvourlakos@gmail.com> 0003 * 0004 * This file is part of Latte-Dock 0005 * 0006 * Latte-Dock is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU General Public License as 0008 * published by the Free Software Foundation; either version 2 of 0009 * the License, or (at your option) any later version. 0010 * 0011 * Latte-Dock is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "contextmenu.h" 0021 0022 // local 0023 #include "view.h" 0024 #include "visibilitymanager.h" 0025 #include "../lattecorona.h" 0026 0027 // Qt 0028 #include <QMouseEvent> 0029 #include <QVersionNumber> 0030 0031 // KDE 0032 #include <KActionCollection> 0033 #include <KAuthorized> 0034 #include <KLocalizedString> 0035 0036 // Plasma 0037 #include <Plasma/Applet> 0038 #include <Plasma/Containment> 0039 #include <Plasma/ContainmentActions> 0040 #include <Plasma/Corona> 0041 #include <PlasmaQuick/AppletQuickItem> 0042 0043 namespace Latte { 0044 namespace ViewPart { 0045 0046 ContextMenu::ContextMenu(Latte::View *view) : 0047 QObject(view), 0048 m_latteView(view) 0049 { 0050 } 0051 0052 ContextMenu::~ContextMenu() 0053 { 0054 } 0055 0056 QMenu *ContextMenu::menu() 0057 { 0058 return m_contextMenu; 0059 } 0060 0061 void ContextMenu::menuAboutToHide() 0062 { 0063 if (!m_latteView) { 0064 return; 0065 } 0066 0067 m_contextMenu = 0; 0068 0069 if (!m_latteView->containment()->isUserConfiguring()) { 0070 m_latteView->visibility()->setBlockHiding(false); 0071 } 0072 0073 emit menuChanged(); 0074 } 0075 0076 bool ContextMenu::mousePressEvent(QMouseEvent *event) 0077 { 0078 //qDebug() << "Step -1 ..."; 0079 0080 if (!event || !m_latteView->containment()) { 0081 return false; 0082 } 0083 0084 //qDebug() << "Step 0..."; 0085 0086 //even if the menu is executed synchronously, other events may be processed 0087 //by the qml incubator when plasma is loading, so we need to guard there 0088 if (m_contextMenu) { 0089 //qDebug() << "Step 0.5 ..."; 0090 m_contextMenu->close(); 0091 m_contextMenu = 0; 0092 emit menuChanged(); 0093 // PlasmaQuick::ContainmentView::mousePressEvent(event); 0094 return false; 0095 } 0096 0097 //qDebug() << "1 ..."; 0098 QString trigger = Plasma::ContainmentActions::eventToString(event); 0099 0100 if (trigger == "RightButton;NoModifier") { 0101 Plasma::ContainmentActions *plugin = m_latteView->containment()->containmentActions().value(trigger); 0102 0103 if (!plugin || plugin->contextualActions().isEmpty()) { 0104 event->setAccepted(false); 0105 return false; 0106 } 0107 0108 //qDebug() << "2 ..."; 0109 //the plugin can be a single action or a context menu 0110 //Don't have an action list? execute as single action 0111 //and set the event position as action data 0112 /*if (plugin->contextualActions().length() == 1) { 0113 QAction *action = plugin->contextualActions().at(0); 0114 action->setData(event->pos()); 0115 action->trigger(); 0116 event->accept(); 0117 return; 0118 }*/ 0119 //FIXME: very inefficient appletAt() implementation 0120 Plasma::Applet *applet = 0; 0121 bool inSystray = false; 0122 0123 //! initialize the appletContainsMethod on the first right click 0124 if (!m_appletContainsMethod.isValid()) { 0125 updateAppletContainsMethod(); 0126 } 0127 0128 for (const Plasma::Applet *appletTemp : m_latteView->containment()->applets()) { 0129 PlasmaQuick::AppletQuickItem *ai = appletTemp->property("_plasma_graphicObject").value<PlasmaQuick::AppletQuickItem *>(); 0130 0131 bool appletContainsMouse = false; 0132 0133 if (m_appletContainsMethod.isValid()) { 0134 QVariant retVal; 0135 m_appletContainsMethod.invoke(m_appletContainsMethodItem, Qt::DirectConnection, Q_RETURN_ARG(QVariant, retVal) 0136 , Q_ARG(QVariant, appletTemp->id()), Q_ARG(QVariant, event->pos())); 0137 appletContainsMouse = retVal.toBool(); 0138 } else { 0139 appletContainsMouse = ai->contains(ai->mapFromItem(m_latteView->contentItem(), event->pos())); 0140 } 0141 0142 if (ai && ai->isVisible() && appletContainsMouse) { 0143 applet = ai->applet(); 0144 0145 if (m_latteView && m_latteView->layout() && m_latteView->layout()->isInternalContainment(applet)) { 0146 Plasma::Containment *internalC = m_latteView->layout()->internalContainmentOf(applet); 0147 0148 if (internalC) { 0149 Plasma::Applet *internalApplet{nullptr}; 0150 0151 for (const Plasma::Applet *appletCont : internalC->applets()) { 0152 PlasmaQuick::AppletQuickItem *ai2 = appletCont->property("_plasma_graphicObject").value<PlasmaQuick::AppletQuickItem *>(); 0153 0154 if (ai2 && ai2->isVisible() && ai2->contains(ai2->mapFromItem(m_latteView->contentItem(), event->pos()))) { 0155 internalApplet = ai2->applet(); 0156 break; 0157 } 0158 } 0159 0160 if (!internalApplet) { 0161 return true; 0162 } else { 0163 applet = internalApplet; 0164 } 0165 } 0166 0167 break; 0168 } else { 0169 ai = 0; 0170 } 0171 } 0172 } 0173 0174 if (!applet && !inSystray) { 0175 applet = m_latteView->containment(); 0176 } 0177 0178 //qDebug() << "3 ..."; 0179 0180 if (applet) { 0181 const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); 0182 0183 //qDebug() << "3.5 ..."; 0184 0185 if (!provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { 0186 //qDebug() << "4..."; 0187 QMenu *desktopMenu = new QMenu; 0188 0189 //this is a workaround where Qt now creates the menu widget 0190 //in .exec before oxygen can polish it and set the following attribute 0191 desktopMenu->setAttribute(Qt::WA_TranslucentBackground); 0192 //end workaround 0193 0194 if (desktopMenu->winId()) { 0195 desktopMenu->windowHandle()->setTransientParent(m_latteView); 0196 } 0197 0198 desktopMenu->setAttribute(Qt::WA_DeleteOnClose); 0199 m_contextMenu = desktopMenu; 0200 0201 //! deprecated old code that can be removed if the following plasma approach doesn't 0202 //! create any issues with context menu creation in Latte 0203 /*if (m_latteView->mouseGrabberItem()) { 0204 //workaround, this fixes for me most of the right click menu behavior 0205 m_latteView->mouseGrabberItem()->ungrabMouse(); 0206 return; 0207 }*/ 0208 0209 //!plasma official code 0210 //this is a workaround where Qt will fail to realize a mouse has been released 0211 0212 // this happens if a window which does not accept focus spawns a new window that takes focus and X grab 0213 // whilst the mouse is depressed 0214 // https://bugreports.qt.io/browse/QTBUG-59044 0215 // this causes the next click to go missing 0216 0217 //by releasing manually we avoid that situation 0218 auto ungrabMouseHack = [this]() { 0219 if (m_latteView->mouseGrabberItem()) { 0220 m_latteView->mouseGrabberItem()->ungrabMouse(); 0221 } 0222 }; 0223 0224 //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" 0225 //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() 0226 if (QVersionNumber::fromString(qVersion()) > QVersionNumber(5, 8, 0)) { 0227 QTimer::singleShot(0, this, ungrabMouseHack); 0228 } else { 0229 ungrabMouseHack(); 0230 } 0231 0232 //end workaround 0233 //!end of plasma official code(workaround) 0234 0235 //qDebug() << "5 ..."; 0236 0237 if (applet && applet != m_latteView->containment()) { 0238 //qDebug() << "5.3 ..."; 0239 emit applet->contextualActionsAboutToShow(); 0240 addAppletActions(desktopMenu, applet, event); 0241 } else { 0242 //qDebug() << "5.6 ..."; 0243 emit m_latteView->containment()->contextualActionsAboutToShow(); 0244 addContainmentActions(desktopMenu, event); 0245 } 0246 0247 //this is a workaround where Qt now creates the menu widget 0248 //in .exec before oxygen can polish it and set the following attribute 0249 desktopMenu->setAttribute(Qt::WA_TranslucentBackground); 0250 //end workaround 0251 QPoint pos = event->globalPos(); 0252 0253 if (applet) { 0254 //qDebug() << "6 ..."; 0255 desktopMenu->adjustSize(); 0256 0257 if (m_latteView->screen()) { 0258 const QRect scr = m_latteView->screen()->geometry(); 0259 int smallStep = 3; 0260 int x = event->globalPos().x() + smallStep; 0261 int y = event->globalPos().y() + smallStep; 0262 0263 //qDebug()<<x << " - "<<y; 0264 0265 if (event->globalPos().x() > scr.center().x()) { 0266 x = event->globalPos().x() - desktopMenu->width() - smallStep; 0267 } 0268 0269 if (event->globalPos().y() > scr.center().y()) { 0270 y = event->globalPos().y() - desktopMenu->height() - smallStep; 0271 } 0272 0273 pos = QPoint(x, y); 0274 } 0275 } 0276 0277 //qDebug() << "7..."; 0278 0279 if (desktopMenu->isEmpty()) { 0280 //qDebug() << "7.5 ..."; 0281 delete desktopMenu; 0282 event->accept(); 0283 return false; 0284 } 0285 0286 connect(desktopMenu, SIGNAL(aboutToHide()), this, SLOT(menuAboutToHide())); 0287 m_latteView->visibility()->setBlockHiding(true); 0288 desktopMenu->popup(pos); 0289 event->setAccepted(true); 0290 emit menuChanged(); 0291 return false; 0292 } 0293 0294 //qDebug() << "8 ..."; 0295 } 0296 0297 //qDebug() << "9 ..."; 0298 } 0299 0300 //qDebug() << "10 ..."; 0301 emit menuChanged(); 0302 return true; 0303 // PlasmaQuick::ContainmentView::mousePressEvent(event); 0304 } 0305 0306 //! update the appletContainsPos method from Panel view 0307 void ContextMenu::updateAppletContainsMethod() 0308 { 0309 for (QQuickItem *item : m_latteView->contentItem()->childItems()) { 0310 if (auto *metaObject = item->metaObject()) { 0311 // not using QMetaObject::invokeMethod to avoid warnings when calling 0312 // this on applets that don't have it or other child items since this 0313 // is pretty much trial and error. 0314 // Also, "var" arguments are treated as QVariant in QMetaObject 0315 0316 int methodIndex = metaObject->indexOfMethod("appletContainsPos(QVariant,QVariant)"); 0317 0318 if (methodIndex == -1) { 0319 continue; 0320 } 0321 0322 m_appletContainsMethod = metaObject->method(methodIndex); 0323 m_appletContainsMethodItem = item; 0324 } 0325 } 0326 } 0327 0328 void ContextMenu::addAppletActions(QMenu *desktopMenu, Plasma::Applet *applet, QEvent *event) 0329 { 0330 if (!m_latteView->containment()) { 0331 return; 0332 } 0333 0334 for (QAction *action : applet->contextualActions()) { 0335 if (action) { 0336 desktopMenu->addAction(action); 0337 } 0338 } 0339 0340 if (!applet->failedToLaunch()) { 0341 QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); 0342 0343 if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { 0344 desktopMenu->addAction(runAssociatedApplication); 0345 } 0346 0347 QAction *configureApplet = applet->actions()->action(QStringLiteral("configure")); 0348 0349 if (configureApplet && configureApplet->isEnabled()) { 0350 desktopMenu->addAction(configureApplet); 0351 } 0352 0353 QAction *appletAlternatives = applet->actions()->action(QStringLiteral("alternatives")); 0354 0355 if (appletAlternatives && appletAlternatives->isEnabled() && m_latteView->containment()->isUserConfiguring()) { 0356 desktopMenu->addAction(appletAlternatives); 0357 } 0358 } 0359 0360 QAction *containmentAction = desktopMenu->menuAction(); 0361 containmentAction->setText(i18nc("%1 is the name of the containment", "%1 Options", m_latteView->containment()->title())); 0362 0363 addContainmentActions(containmentAction->menu(), event); 0364 0365 if (!containmentAction->menu()->isEmpty()) { 0366 int enabled = 0; 0367 //count number of real actions 0368 QListIterator<QAction *> actionsIt(containmentAction->menu()->actions()); 0369 0370 while (enabled < 3 && actionsIt.hasNext()) { 0371 QAction *action = actionsIt.next(); 0372 0373 if (action->isVisible() && !action->isSeparator()) { 0374 ++enabled; 0375 } 0376 } 0377 0378 desktopMenu->addSeparator(); 0379 0380 if (enabled) { 0381 //if there is only one, don't create a submenu 0382 // if (enabled < 2) { 0383 for (QAction *action : containmentAction->menu()->actions()) { 0384 if (action && action->isVisible()) { 0385 desktopMenu->addAction(action); 0386 } 0387 } 0388 0389 // } else { 0390 // desktopMenu->addMenu(containmentMenu); 0391 // } 0392 } 0393 } 0394 0395 if (m_latteView->containment()->immutability() == Plasma::Types::Mutable && 0396 (m_latteView->containment()->containmentType() != Plasma::Types::PanelContainment || m_latteView->containment()->isUserConfiguring())) { 0397 QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); 0398 0399 //qDebug() << "checking for removal" << closeApplet; 0400 if (closeApplet) { 0401 if (!desktopMenu->isEmpty()) { 0402 desktopMenu->addSeparator(); 0403 } 0404 0405 //qDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); 0406 desktopMenu->addAction(closeApplet); 0407 } 0408 } 0409 } 0410 0411 void ContextMenu::addContainmentActions(QMenu *desktopMenu, QEvent *event) 0412 { 0413 if (!m_latteView->containment()) { 0414 return; 0415 } 0416 0417 if (m_latteView->containment()->corona()->immutability() != Plasma::Types::Mutable && 0418 !KAuthorized::authorizeAction(QStringLiteral("plasma/containment_actions"))) { 0419 //qDebug() << "immutability"; 0420 return; 0421 } 0422 0423 //this is what ContainmentPrivate::prepareContainmentActions was 0424 const QString trigger = Plasma::ContainmentActions::eventToString(event); 0425 //"RightButton;NoModifier" 0426 Plasma::ContainmentActions *plugin = m_latteView->containment()->containmentActions().value(trigger); 0427 0428 if (!plugin) { 0429 return; 0430 } 0431 0432 if (plugin->containment() != m_latteView->containment()) { 0433 plugin->setContainment(m_latteView->containment()); 0434 // now configure it 0435 KConfigGroup cfg(m_latteView->containment()->corona()->config(), "ActionPlugins"); 0436 cfg = KConfigGroup(&cfg, QString::number(m_latteView->containment()->containmentType())); 0437 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); 0438 plugin->restore(pluginConfig); 0439 } 0440 0441 QList<QAction *> actions = plugin->contextualActions(); 0442 0443 for (const QAction *act : actions) { 0444 if (act->menu()) { 0445 //this is a workaround where Qt now creates the menu widget 0446 //in .exec before oxygen can polish it and set the following attribute 0447 act->menu()->setAttribute(Qt::WA_TranslucentBackground); 0448 //end workaround 0449 0450 if (act->menu()->winId()) { 0451 act->menu()->windowHandle()->setTransientParent(m_latteView); 0452 } 0453 } 0454 } 0455 0456 desktopMenu->addActions(actions); 0457 0458 return; 0459 } 0460 0461 Plasma::Containment *ContextMenu::containmentById(uint id) 0462 { 0463 for (const auto containment : m_latteView->corona()->containments()) { 0464 if (id == containment->id()) { 0465 return containment; 0466 } 0467 } 0468 0469 return 0; 0470 } 0471 0472 } 0473 }