1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2006 Olivier Goffart <ogoffart@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kmenumenuhandler_p.h"
9
10#include "debug.h"
11#include "kactioncollection.h"
12#include "kmainwindow.h"
13#include "kshortcutwidget.h"
14#include "ktoolbar.h"
15#include "kxmlguibuilder.h"
16#include "kxmlguiclient.h"
17#include "kxmlguifactory.h"
18
19#include <QAction>
20#include <QContextMenuEvent>
21#include <QDialog>
22#include <QDialogButtonBox>
23#include <QDomDocument>
24#include <QDomNode>
25#include <QMenu>
26#include <QVBoxLayout>
27
28#include <KLocalizedString>
29#include <KSelectAction>
30
31namespace KDEPrivate
32{
33KMenuMenuHandler::KMenuMenuHandler(KXMLGUIBuilder *builder)
34 : QObject()
35 , m_builder(builder)
36{
37 m_toolbarAction = new KSelectAction(i18n("Add to Toolbar"), this);
38 connect(sender: m_toolbarAction, signal: &KSelectAction::indexTriggered, context: this, slot: &KMenuMenuHandler::slotAddToToolBar);
39}
40
41void KMenuMenuHandler::insertMenu(QMenu *popup)
42{
43 popup->installEventFilter(filterObj: this);
44}
45
46bool KMenuMenuHandler::eventFilter(QObject *watched, QEvent *event)
47{
48 switch (event->type()) {
49 case QEvent::MouseButtonPress:
50 if (m_contextMenu && m_contextMenu->isVisible()) {
51 m_contextMenu->hide();
52 return true;
53 }
54 break;
55
56 case QEvent::MouseButtonRelease:
57 if (m_contextMenu && m_contextMenu->isVisible()) {
58 return true;
59 }
60 break;
61
62 case QEvent::ContextMenu: {
63 QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event);
64 QMenu *menu = static_cast<QMenu *>(watched);
65 if (e->reason() == QContextMenuEvent::Mouse) {
66 showContextMenu(menu, pos: e->pos());
67 } else if (menu->activeAction()) {
68 showContextMenu(menu, pos: menu->actionGeometry(menu->activeAction()).center());
69 }
70 }
71 event->accept();
72 return true;
73
74 default:
75 break;
76 }
77
78 return false;
79}
80
81void KMenuMenuHandler::buildToolbarAction()
82{
83 KMainWindow *window = qobject_cast<KMainWindow *>(object: m_builder->widget());
84 if (!window) {
85 return;
86 }
87 QStringList toolbarlist;
88 const auto toolbars = window->toolBars();
89 toolbarlist.reserve(asize: toolbars.size());
90 for (KToolBar *b : toolbars) {
91 toolbarlist << (b->windowTitle().isEmpty() ? b->objectName() : b->windowTitle());
92 }
93 m_toolbarAction->setItems(toolbarlist);
94}
95
96static KActionCollection *findParentCollection(KXMLGUIFactory *factory, QAction *action)
97{
98 const auto clients = factory->clients();
99 for (KXMLGUIClient *client : clients) {
100 KActionCollection *collection = client->actionCollection();
101 // if the call to actions() is too slow, add KActionCollection::contains(QAction*).
102 if (collection->actions().contains(t: action)) {
103 return collection;
104 }
105 }
106 return nullptr;
107}
108
109void KMenuMenuHandler::slotSetShortcut()
110{
111 if (!m_popupMenu || !m_popupAction) {
112 return;
113 }
114
115 QDialog dialog(m_builder->widget());
116 auto *layout = new QVBoxLayout(&dialog);
117
118 KShortcutWidget swidget(&dialog);
119 swidget.setShortcut(m_popupAction->shortcuts());
120 layout->addWidget(&swidget);
121
122 QDialogButtonBox box(&dialog);
123 box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
124 connect(sender: &box, signal: &QDialogButtonBox::accepted, context: &dialog, slot: &QDialog::accept);
125 connect(sender: &box, signal: &QDialogButtonBox::rejected, context: &dialog, slot: &QDialog::reject);
126 layout->addWidget(&box);
127
128 KActionCollection *parentCollection = nullptr;
129 if (dynamic_cast<KXMLGUIClient *>(m_builder)) {
130 QList<KActionCollection *> checkCollections;
131 KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
132 parentCollection = findParentCollection(factory, action: m_popupAction);
133 const auto clients = factory->clients();
134 checkCollections.reserve(asize: clients.size());
135 for (KXMLGUIClient *client : clients) {
136 checkCollections += client->actionCollection();
137 }
138 swidget.setCheckActionCollections(checkCollections);
139 }
140
141 if (dialog.exec()) {
142 m_popupAction->setShortcuts(swidget.shortcut());
143 swidget.applyStealShortcut();
144 if (parentCollection) {
145 parentCollection->writeSettings();
146 }
147 }
148}
149
150void KMenuMenuHandler::slotAddToToolBar(int tb)
151{
152 KMainWindow *window = qobject_cast<KMainWindow *>(object: m_builder->widget());
153 if (!window) {
154 return;
155 }
156
157 if (!m_popupMenu || !m_popupAction) {
158 return;
159 }
160
161 KXMLGUIFactory *factory = dynamic_cast<KXMLGUIClient *>(m_builder)->factory();
162 QString actionName = m_popupAction->objectName(); // set by KActionCollection::addAction
163 KActionCollection *collection = nullptr;
164 if (factory) {
165 collection = findParentCollection(factory, action: m_popupAction);
166 }
167 if (!collection) {
168 qCWarning(DEBUG_KXMLGUI) << "Cannot find the action collection for action " << actionName;
169 return;
170 }
171
172 KToolBar *toolbar = window->toolBars().at(i: tb);
173 toolbar->addAction(action: m_popupAction);
174
175 const KXMLGUIClient *client = collection->parentGUIClient();
176 QString xmlFile = client->localXMLFile();
177 QDomDocument document;
178 document.setContent(data: KXMLGUIFactory::readConfigFile(filename: client->xmlFile(), componentName: client->componentName()));
179 QDomElement elem = document.documentElement().toElement();
180
181 const QLatin1String tagToolBar("ToolBar");
182 const QLatin1String attrNoEdit("noEdit");
183 const QLatin1String attrName("name");
184
185 QDomElement toolbarElem;
186 QDomNode n = elem.firstChild();
187 for (; !n.isNull(); n = n.nextSibling()) {
188 QDomElement elem = n.toElement();
189 if (!elem.isNull() && elem.tagName() == tagToolBar && elem.attribute(name: attrName) == toolbar->objectName()) {
190 if (elem.attribute(name: attrNoEdit) == QLatin1String("true")) {
191 qCWarning(DEBUG_KXMLGUI) << "The toolbar is not editable";
192 return;
193 }
194 toolbarElem = elem;
195 break;
196 }
197 }
198 if (toolbarElem.isNull()) {
199 toolbarElem = document.createElement(tagName: tagToolBar);
200 toolbarElem.setAttribute(name: attrName, value: toolbar->objectName());
201 elem.appendChild(newChild: toolbarElem);
202 }
203
204 KXMLGUIFactory::findActionByName(elem&: toolbarElem, sName: actionName, create: true);
205 KXMLGUIFactory::saveConfigFile(doc: document, filename: xmlFile);
206 factory->refreshActionProperties();
207}
208
209void KMenuMenuHandler::showContextMenu(QMenu *menu, const QPoint &pos)
210{
211 Q_ASSERT(!m_popupMenu);
212 Q_ASSERT(!m_popupAction);
213 Q_ASSERT(!m_contextMenu);
214
215 auto *action = menu->actionAt(pos);
216 if (!action || action->isSeparator()) {
217 return;
218 }
219
220 m_popupMenu = menu;
221 m_popupAction = action;
222
223 m_contextMenu = new QMenu;
224 m_contextMenu->addAction(i18nc("@action:inmenu", "Configure Shortcut..."), args: this, args: &KMenuMenuHandler::slotSetShortcut);
225
226 KMainWindow *window = qobject_cast<KMainWindow *>(object: m_builder->widget());
227 if (window) {
228 m_contextMenu->addAction(action: m_toolbarAction);
229 buildToolbarAction();
230 }
231
232 m_contextMenu->exec(pos: menu->mapToGlobal(pos));
233 delete m_contextMenu;
234 m_contextMenu = nullptr;
235
236 m_popupAction = nullptr;
237 m_popupMenu = nullptr;
238}
239
240} // END namespace KDEPrivate
241
242#include "moc_kmenumenuhandler_p.cpp"
243

source code of kxmlgui/src/kmenumenuhandler_p.cpp