File indexing completed on 2024-12-01 12:40:38

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2006 Olivier Goffart <ogoffart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kmenumenuhandler_p.h"
0009 
0010 #include "debug.h"
0011 #include "kactioncollection.h"
0012 #include "kmainwindow.h"
0013 #include "kshortcutwidget.h"
0014 #include "ktoolbar.h"
0015 #include "kxmlguibuilder.h"
0016 #include "kxmlguiclient.h"
0017 #include "kxmlguifactory.h"
0018 
0019 #include <QAction>
0020 #include <QContextMenuEvent>
0021 #include <QDialog>
0022 #include <QDialogButtonBox>
0023 #include <QDomDocument>
0024 #include <QDomNode>
0025 #include <QMenu>
0026 #include <QVBoxLayout>
0027 
0028 #include <KLocalizedString>
0029 #include <KSelectAction>
0030 
0031 namespace KDEPrivate
0032 {
0033 KMenuMenuHandler::KMenuMenuHandler(KXMLGUIBuilder *builder)
0034     : QObject()
0035     , m_builder(builder)
0036 {
0037     m_toolbarAction = new KSelectAction(i18n("Add to Toolbar"), this);
0038     connect(m_toolbarAction, &KSelectAction::indexTriggered, this, &KMenuMenuHandler::slotAddToToolBar);
0039 }
0040 
0041 void KMenuMenuHandler::insertMenu(QMenu *popup)
0042 {
0043     popup->installEventFilter(this);
0044 }
0045 
0046 bool KMenuMenuHandler::eventFilter(QObject *watched, QEvent *event)
0047 {
0048     switch (event->type()) {
0049     case QEvent::MouseButtonPress:
0050         if (m_contextMenu && m_contextMenu->isVisible()) {
0051             m_contextMenu->hide();
0052             return true;
0053         }
0054         break;
0055 
0056     case QEvent::MouseButtonRelease:
0057         if (m_contextMenu && m_contextMenu->isVisible()) {
0058             return true;
0059         }
0060         break;
0061 
0062     case QEvent::ContextMenu: {
0063         QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event);
0064         QMenu *menu = static_cast<QMenu *>(watched);
0065         if (e->reason() == QContextMenuEvent::Mouse) {
0066             showContextMenu(menu, e->pos());
0067         } else if (menu->activeAction()) {
0068             showContextMenu(menu, menu->actionGeometry(menu->activeAction()).center());
0069         }
0070     }
0071         event->accept();
0072         return true;
0073 
0074     default:
0075         break;
0076     }
0077 
0078     return false;
0079 }
0080 
0081 void KMenuMenuHandler::buildToolbarAction()
0082 {
0083     KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
0084     if (!window) {
0085         return;
0086     }
0087     QStringList toolbarlist;
0088     const auto toolbars = window->toolBars();
0089     toolbarlist.reserve(toolbars.size());
0090     for (KToolBar *b : toolbars) {
0091         toolbarlist << (b->windowTitle().isEmpty() ? b->objectName() : b->windowTitle());
0092     }
0093     m_toolbarAction->setItems(toolbarlist);
0094 }
0095 
0096 static KActionCollection *findParentCollection(KXMLGUIFactory *factory, QAction *action)
0097 {
0098     const auto clients = factory->clients();
0099     for (KXMLGUIClient *client : clients) {
0100         KActionCollection *collection = client->actionCollection();
0101         // if the call to actions() is too slow, add KActionCollection::contains(QAction*).
0102         if (collection->actions().contains(action)) {
0103             return collection;
0104         }
0105     }
0106     return nullptr;
0107 }
0108 
0109 void KMenuMenuHandler::slotSetShortcut()
0110 {
0111     if (!m_popupMenu || !m_popupAction) {
0112         return;
0113     }
0114 
0115     QDialog dialog(m_builder->widget());
0116     auto *layout = new QVBoxLayout(&dialog);
0117 
0118     KShortcutWidget swidget(&dialog);
0119     swidget.setShortcut(m_popupAction->shortcuts());
0120     layout->addWidget(&swidget);
0121 
0122     QDialogButtonBox box(&dialog);
0123     box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0124     connect(&box, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0125     connect(&box, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0126     layout->addWidget(&box);
0127 
0128     KActionCollection *parentCollection = nullptr;
0129     if (dynamic_cast<KXMLGUIClient *>(m_builder)) {
0130         QList<KActionCollection *> checkCollections;
0131         KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
0132         parentCollection = findParentCollection(factory, m_popupAction);
0133         const auto clients = factory->clients();
0134         checkCollections.reserve(clients.size());
0135         for (KXMLGUIClient *client : clients) {
0136             checkCollections += client->actionCollection();
0137         }
0138         swidget.setCheckActionCollections(checkCollections);
0139     }
0140 
0141     if (dialog.exec()) {
0142         m_popupAction->setShortcuts(swidget.shortcut());
0143         swidget.applyStealShortcut();
0144         if (parentCollection) {
0145             parentCollection->writeSettings();
0146         }
0147     }
0148 }
0149 
0150 void KMenuMenuHandler::slotAddToToolBar(int tb)
0151 {
0152     KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
0153     if (!window) {
0154         return;
0155     }
0156 
0157     if (!m_popupMenu || !m_popupAction) {
0158         return;
0159     }
0160 
0161     KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
0162     QString actionName = m_popupAction->objectName(); // set by KActionCollection::addAction
0163     KActionCollection *collection = nullptr;
0164     if (factory) {
0165         collection = findParentCollection(factory, m_popupAction);
0166     }
0167     if (!collection) {
0168         qCWarning(DEBUG_KXMLGUI) << "Cannot find the action collection for action " << actionName;
0169         return;
0170     }
0171 
0172     KToolBar *toolbar = window->toolBars().at(tb);
0173     toolbar->addAction(m_popupAction);
0174 
0175     const KXMLGUIClient *client = collection->parentGUIClient();
0176     QString xmlFile = client->localXMLFile();
0177     QDomDocument document;
0178     document.setContent(KXMLGUIFactory::readConfigFile(client->xmlFile(), client->componentName()));
0179     QDomElement elem = document.documentElement().toElement();
0180 
0181     const QLatin1String tagToolBar("ToolBar");
0182     const QLatin1String attrNoEdit("noEdit");
0183     const QLatin1String attrName("name");
0184 
0185     QDomElement toolbarElem;
0186     QDomNode n = elem.firstChild();
0187     for (; !n.isNull(); n = n.nextSibling()) {
0188         QDomElement elem = n.toElement();
0189         if (!elem.isNull() && elem.tagName() == tagToolBar && elem.attribute(attrName) == toolbar->objectName()) {
0190             if (elem.attribute(attrNoEdit) == QLatin1String("true")) {
0191                 qCWarning(DEBUG_KXMLGUI) << "The toolbar is not editable";
0192                 return;
0193             }
0194             toolbarElem = elem;
0195             break;
0196         }
0197     }
0198     if (toolbarElem.isNull()) {
0199         toolbarElem = document.createElement(tagToolBar);
0200         toolbarElem.setAttribute(attrName, toolbar->objectName());
0201         elem.appendChild(toolbarElem);
0202     }
0203 
0204     KXMLGUIFactory::findActionByName(toolbarElem, actionName, true);
0205     KXMLGUIFactory::saveConfigFile(document, xmlFile);
0206 }
0207 
0208 void KMenuMenuHandler::showContextMenu(QMenu *menu, const QPoint &pos)
0209 {
0210     Q_ASSERT(!m_popupMenu);
0211     Q_ASSERT(!m_popupAction);
0212     Q_ASSERT(!m_contextMenu);
0213 
0214     auto *action = menu->actionAt(pos);
0215     if (!action || action->isSeparator()) {
0216         return;
0217     }
0218 
0219     m_popupMenu = menu;
0220     m_popupAction = action;
0221 
0222     m_contextMenu = new QMenu;
0223     m_contextMenu->addAction(i18nc("@action:inmenu", "Configure Shortcut..."), this, &KMenuMenuHandler::slotSetShortcut);
0224 
0225     KMainWindow *window = qobject_cast<KMainWindow *>(m_builder->widget());
0226     if (window) {
0227         m_contextMenu->addAction(m_toolbarAction);
0228         buildToolbarAction();
0229     }
0230 
0231     m_contextMenu->exec(menu->mapToGlobal(pos));
0232     delete m_contextMenu;
0233     m_contextMenu = nullptr;
0234 
0235     m_popupAction = nullptr;
0236     m_popupMenu = nullptr;
0237 }
0238 
0239 } // END namespace KDEPrivate
0240 
0241 #include "moc_kmenumenuhandler_p.cpp"