1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org> |
4 | SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "kxmlguibuilder.h" |
10 | |
11 | #include "debug.h" |
12 | #include "kmainwindow.h" |
13 | #include "kmenumenuhandler_p.h" |
14 | #include "ktoolbar.h" |
15 | #include "kxmlguiclient.h" |
16 | #include "kxmlguiwindow.h" |
17 | |
18 | #include <KAuthorized> |
19 | #include <KLocalizedString> |
20 | |
21 | #include <QAction> |
22 | #include <QDomElement> |
23 | #include <QMenu> |
24 | #include <QMenuBar> |
25 | #include <QObject> |
26 | #include <QStatusBar> |
27 | |
28 | using namespace KDEPrivate; |
29 | |
30 | class KXMLGUIBuilderPrivate |
31 | { |
32 | public: |
33 | KXMLGUIBuilderPrivate() |
34 | { |
35 | } |
36 | ~KXMLGUIBuilderPrivate() |
37 | { |
38 | } |
39 | |
40 | QWidget *m_widget = nullptr; |
41 | |
42 | QString tagMainWindow; |
43 | QString ; |
44 | QString ; |
45 | QString tagToolBar; |
46 | QString tagStatusBar; |
47 | |
48 | QString tagSeparator; |
49 | QString tagSpacer; |
50 | QString tagTearOffHandle; |
51 | QString ; |
52 | |
53 | QString attrName; |
54 | QString attrLineSeparator; |
55 | |
56 | QString attrDomain; |
57 | QString attrText1; |
58 | QString attrText2; |
59 | QString attrContext; |
60 | |
61 | QString attrIcon; |
62 | |
63 | KXMLGUIClient *m_client = nullptr; |
64 | |
65 | KMenuMenuHandler *m_menumenuhandler = nullptr; |
66 | }; |
67 | |
68 | KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget) |
69 | : d(new KXMLGUIBuilderPrivate) |
70 | { |
71 | d->m_widget = widget; |
72 | |
73 | d->tagMainWindow = QStringLiteral("mainwindow" ); |
74 | d->tagMenuBar = QStringLiteral("menubar" ); |
75 | d->tagMenu = QStringLiteral("menu" ); |
76 | d->tagToolBar = QStringLiteral("toolbar" ); |
77 | d->tagStatusBar = QStringLiteral("statusbar" ); |
78 | |
79 | d->tagSeparator = QStringLiteral("separator" ); |
80 | d->tagSpacer = QStringLiteral("spacer" ); |
81 | d->tagTearOffHandle = QStringLiteral("tearoffhandle" ); |
82 | d->tagMenuTitle = QStringLiteral("title" ); |
83 | |
84 | d->attrName = QStringLiteral("name" ); |
85 | d->attrLineSeparator = QStringLiteral("lineseparator" ); |
86 | |
87 | d->attrDomain = QStringLiteral("translationDomain" ); |
88 | d->attrText1 = QStringLiteral("text" ); |
89 | d->attrText2 = QStringLiteral("Text" ); |
90 | d->attrContext = QStringLiteral("context" ); |
91 | |
92 | d->attrIcon = QStringLiteral("icon" ); |
93 | |
94 | d->m_menumenuhandler = new KMenuMenuHandler(this); |
95 | } |
96 | |
97 | KXMLGUIBuilder::~KXMLGUIBuilder() |
98 | { |
99 | delete d->m_menumenuhandler; |
100 | } |
101 | |
102 | QWidget *KXMLGUIBuilder::widget() |
103 | { |
104 | return d->m_widget; |
105 | } |
106 | |
107 | QStringList KXMLGUIBuilder::containerTags() const |
108 | { |
109 | QStringList res; |
110 | res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar; |
111 | |
112 | return res; |
113 | } |
114 | |
115 | QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) |
116 | { |
117 | containerAction = nullptr; |
118 | |
119 | if (element.attribute(QStringLiteral("deleted" )).toLower() == QLatin1String("true" )) { |
120 | return nullptr; |
121 | } |
122 | |
123 | const QString tagName = element.tagName().toLower(); |
124 | if (tagName == d->tagMainWindow) { |
125 | KMainWindow *mainwindow = qobject_cast<KMainWindow *>(object: d->m_widget); // could be 0 |
126 | return mainwindow; |
127 | } |
128 | |
129 | if (tagName == d->tagMenuBar) { |
130 | KMainWindow *mainWin = qobject_cast<KMainWindow *>(object: d->m_widget); |
131 | QMenuBar *bar = nullptr; |
132 | if (mainWin) { |
133 | bar = mainWin->menuBar(); |
134 | } |
135 | if (!bar) { |
136 | bar = new QMenuBar(d->m_widget); |
137 | } |
138 | bar->show(); |
139 | return bar; |
140 | } |
141 | |
142 | if (tagName == d->tagMenu) { |
143 | // Look up to see if we are inside a mainwindow. If yes, then |
144 | // use it as parent widget (to get kaction to plug itself into the |
145 | // mainwindow). Don't use a popupmenu as parent widget, otherwise |
146 | // the popup won't be hidden if it is used as a standalone menu as well. |
147 | // Note: menus with a parent of 0, coming from child clients, can be |
148 | // leaked if the child client is deleted without a proper removeClient call, though. |
149 | QWidget *p = parent; |
150 | |
151 | if (!p && qobject_cast<QMainWindow *>(object: d->m_widget)) { |
152 | p = d->m_widget; |
153 | } |
154 | |
155 | while (p && !qobject_cast<QMainWindow *>(object: p)) { |
156 | p = p->parentWidget(); |
157 | } |
158 | |
159 | QString name = element.attribute(name: d->attrName); |
160 | |
161 | if (!KAuthorized::authorizeAction(action: name)) { |
162 | return nullptr; |
163 | } |
164 | |
165 | QMenu * = new QMenu(p); |
166 | popup->setObjectName(name); |
167 | |
168 | d->m_menumenuhandler->insertMenu(menu: popup); |
169 | |
170 | QString i18nText; |
171 | QDomElement textElem = element.namedItem(name: d->attrText1).toElement(); |
172 | if (textElem.isNull()) { // try with capital T |
173 | textElem = element.namedItem(name: d->attrText2).toElement(); |
174 | } |
175 | const QString text = textElem.text(); |
176 | const QString context = textElem.attribute(name: d->attrContext); |
177 | |
178 | // qCDebug(DEBUG_KXMLGUI) << "DOMAIN" << KLocalizedString::applicationDomain(); |
179 | // qCDebug(DEBUG_KXMLGUI) << "ELEMENT TEXT:" << text; |
180 | |
181 | if (text.isEmpty()) { // still no luck |
182 | i18nText = i18n("No text" ); |
183 | } else { |
184 | QByteArray domain = textElem.attribute(name: d->attrDomain).toUtf8(); |
185 | if (domain.isEmpty()) { |
186 | domain = element.ownerDocument().documentElement().attribute(name: d->attrDomain).toUtf8(); |
187 | if (domain.isEmpty()) { |
188 | domain = KLocalizedString::applicationDomain(); |
189 | } |
190 | } |
191 | if (context.isEmpty()) { |
192 | i18nText = i18nd(domain: domain.constData(), text: text.toUtf8().constData()); |
193 | } else { |
194 | i18nText = i18ndc(domain: domain.constData(), context: context.toUtf8().constData(), text: text.toUtf8().constData()); |
195 | } |
196 | } |
197 | |
198 | // qCDebug(DEBUG_KXMLGUI) << "ELEMENT i18n TEXT:" << i18nText; |
199 | |
200 | const QString icon = element.attribute(name: d->attrIcon); |
201 | QIcon pix; |
202 | if (!icon.isEmpty()) { |
203 | pix = QIcon::fromTheme(name: icon); |
204 | } |
205 | |
206 | if (parent) { |
207 | QAction *act = popup->menuAction(); |
208 | if (!icon.isEmpty()) { |
209 | act->setIcon(pix); |
210 | } |
211 | act->setText(i18nText); |
212 | if (index == -1 || index >= parent->actions().count()) { |
213 | parent->addAction(action: act); |
214 | } else { |
215 | parent->insertAction(before: parent->actions().value(i: index), action: act); |
216 | } |
217 | containerAction = act; |
218 | containerAction->setObjectName(name); |
219 | } |
220 | |
221 | return popup; |
222 | } |
223 | |
224 | if (tagName == d->tagToolBar) { |
225 | QString name = element.attribute(name: d->attrName); |
226 | |
227 | KToolBar *bar = static_cast<KToolBar *>(d->m_widget->findChild<KToolBar *>(aName: name)); |
228 | if (!bar) { |
229 | bar = new KToolBar(name, d->m_widget, false); |
230 | } |
231 | |
232 | if (qobject_cast<KMainWindow *>(object: d->m_widget)) { |
233 | if (d->m_client && !d->m_client->xmlFile().isEmpty()) { |
234 | bar->addXMLGUIClient(client: d->m_client); |
235 | } |
236 | } |
237 | if (!bar->mainWindow()) { |
238 | bar->show(); |
239 | } |
240 | |
241 | bar->loadState(element); |
242 | |
243 | return bar; |
244 | } |
245 | |
246 | if (tagName == d->tagStatusBar) { |
247 | KMainWindow *mainWin = qobject_cast<KMainWindow *>(object: d->m_widget); |
248 | if (mainWin) { |
249 | mainWin->statusBar()->show(); |
250 | return mainWin->statusBar(); |
251 | } |
252 | QStatusBar *bar = new QStatusBar(d->m_widget); |
253 | return bar; |
254 | } |
255 | |
256 | return nullptr; |
257 | } |
258 | |
259 | void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) |
260 | { |
261 | // Warning parent can be 0L |
262 | |
263 | if (qobject_cast<QMenu *>(object: container)) { |
264 | if (parent) { |
265 | parent->removeAction(action: containerAction); |
266 | } |
267 | |
268 | delete container; |
269 | } else if (qobject_cast<KToolBar *>(object: container)) { |
270 | KToolBar *tb = static_cast<KToolBar *>(container); |
271 | |
272 | tb->saveState(element); |
273 | if (tb->mainWindow()) { |
274 | delete tb; |
275 | } else { |
276 | tb->clear(); |
277 | tb->hide(); |
278 | } |
279 | } else if (qobject_cast<QMenuBar *>(object: container)) { |
280 | QMenuBar *mb = static_cast<QMenuBar *>(container); |
281 | mb->hide(); |
282 | // Don't delete menubar - it can be reused by createContainer. |
283 | // If you decide that you do need to delete the menubar, make |
284 | // sure that QMainWindow::d->mb does not point to a deleted |
285 | // menubar object. |
286 | } else if (qobject_cast<QStatusBar *>(object: container)) { |
287 | if (qobject_cast<KMainWindow *>(object: d->m_widget)) { |
288 | container->hide(); |
289 | } else { |
290 | delete static_cast<QStatusBar *>(container); |
291 | } |
292 | } else { |
293 | qCWarning(DEBUG_KXMLGUI) << "Unhandled container to remove : " << container->metaObject()->className(); |
294 | } |
295 | } |
296 | |
297 | QStringList KXMLGUIBuilder::customTags() const |
298 | { |
299 | QStringList res; |
300 | res << d->tagSeparator << d->tagSpacer << d->tagTearOffHandle << d->tagMenuTitle; |
301 | return res; |
302 | } |
303 | |
304 | QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element) |
305 | { |
306 | QAction *before = nullptr; |
307 | if (index > 0 && index < parent->actions().count()) { |
308 | before = parent->actions().at(i: index); |
309 | } |
310 | |
311 | const QString tagName = element.tagName().toLower(); |
312 | if (tagName == d->tagSeparator) { |
313 | if (QMenu * = qobject_cast<QMenu *>(object: parent)) { |
314 | // QMenu already cares for leading/trailing/repeated separators |
315 | // no need to check anything |
316 | return menu->insertSeparator(before); |
317 | } else if (QMenuBar *bar = qobject_cast<QMenuBar *>(object: parent)) { |
318 | QAction *separatorAction = new QAction(bar); |
319 | separatorAction->setSeparator(true); |
320 | bar->insertAction(before, action: separatorAction); |
321 | return separatorAction; |
322 | } else if (KToolBar *bar = qobject_cast<KToolBar *>(object: parent)) { |
323 | /* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator? |
324 | bool isLineSep = true; |
325 | |
326 | QDomNamedNodeMap attributes = element.attributes(); |
327 | unsigned int i = 0; |
328 | for (; i < attributes.length(); i++ ) |
329 | { |
330 | QDomAttr attr = attributes.item( i ).toAttr(); |
331 | |
332 | if ( attr.name().toLower() == d->attrLineSeparator && |
333 | attr.value().toLower() == QLatin1String("false") ) |
334 | { |
335 | isLineSep = false; |
336 | break; |
337 | } |
338 | } |
339 | |
340 | if ( isLineSep ) |
341 | return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L ); |
342 | else*/ |
343 | |
344 | return bar->insertSeparator(before); |
345 | } |
346 | } else if (tagName == d->tagSpacer) { |
347 | if (QToolBar *bar = qobject_cast<QToolBar *>(object: parent)) { |
348 | // Create the simple spacer widget |
349 | QWidget *spacer = new QWidget(parent); |
350 | spacer->setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::MinimumExpanding); |
351 | return bar->insertWidget(before, widget: spacer); |
352 | } |
353 | } else if (tagName == d->tagTearOffHandle) { |
354 | static_cast<QMenu *>(parent)->setTearOffEnabled(true); |
355 | } else if (tagName == d->tagMenuTitle) { |
356 | if (QMenu *m = qobject_cast<QMenu *>(object: parent)) { |
357 | QString i18nText; |
358 | const QString text = element.text(); |
359 | |
360 | if (text.isEmpty()) { |
361 | i18nText = i18n("No text" ); |
362 | } else { |
363 | QByteArray domain = element.attribute(name: d->attrDomain).toUtf8(); |
364 | if (domain.isEmpty()) { |
365 | domain = element.ownerDocument().documentElement().attribute(name: d->attrDomain).toUtf8(); |
366 | if (domain.isEmpty()) { |
367 | domain = KLocalizedString::applicationDomain(); |
368 | } |
369 | } |
370 | i18nText = i18nd(domain: domain.constData(), qPrintable(text)); |
371 | } |
372 | |
373 | QString icon = element.attribute(name: d->attrIcon); |
374 | QIcon pix; |
375 | |
376 | if (!icon.isEmpty()) { |
377 | pix = QIcon::fromTheme(name: icon); |
378 | } |
379 | |
380 | if (!icon.isEmpty()) { |
381 | return m->insertSection(before, icon: pix, text: i18nText); |
382 | } else { |
383 | return m->insertSection(before, text: i18nText); |
384 | } |
385 | } |
386 | } |
387 | |
388 | QAction *blank = new QAction(parent); |
389 | blank->setVisible(false); |
390 | parent->insertAction(before, action: blank); |
391 | return blank; |
392 | } |
393 | |
394 | KXMLGUIClient *KXMLGUIBuilder::builderClient() const |
395 | { |
396 | return d->m_client; |
397 | } |
398 | |
399 | void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client) |
400 | { |
401 | d->m_client = client; |
402 | } |
403 | |
404 | void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *) |
405 | { |
406 | KXmlGuiWindow *window = qobject_cast<KXmlGuiWindow *>(object: d->m_widget); |
407 | if (window) { |
408 | window->finalizeGUI(force: false); |
409 | } |
410 | } |
411 | |
412 | void KXMLGUIBuilder::virtual_hook(int, void *) |
413 | { |
414 | /*BASE::virtual_hook( id, data );*/ |
415 | } |
416 | |