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 | |
31 | namespace KDEPrivate |
32 | { |
33 | KMenuMenuHandler::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 | |
41 | void KMenuMenuHandler::insertMenu(QMenu *) |
42 | { |
43 | popup->installEventFilter(filterObj: this); |
44 | } |
45 | |
46 | bool 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 * = 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 | |
81 | void 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 | |
96 | static 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 | |
109 | void 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 | |
150 | void 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 | |
209 | void KMenuMenuHandler::showContextMenu(QMenu *, 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 | |