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
28using namespace KDEPrivate;
29
30class KXMLGUIBuilderPrivate
31{
32public:
33 KXMLGUIBuilderPrivate()
34 {
35 }
36 ~KXMLGUIBuilderPrivate()
37 {
38 }
39
40 QWidget *m_widget = nullptr;
41
42 QString tagMainWindow;
43 QString tagMenuBar;
44 QString tagMenu;
45 QString tagToolBar;
46 QString tagStatusBar;
47
48 QString tagSeparator;
49 QString tagSpacer;
50 QString tagTearOffHandle;
51 QString tagMenuTitle;
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
68KXMLGUIBuilder::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
97KXMLGUIBuilder::~KXMLGUIBuilder()
98{
99 delete d->m_menumenuhandler;
100}
101
102QWidget *KXMLGUIBuilder::widget()
103{
104 return d->m_widget;
105}
106
107QStringList 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
115QWidget *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 *popup = 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
259void 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
297QStringList KXMLGUIBuilder::customTags() const
298{
299 QStringList res;
300 res << d->tagSeparator << d->tagSpacer << d->tagTearOffHandle << d->tagMenuTitle;
301 return res;
302}
303
304QAction *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 *menu = 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
394KXMLGUIClient *KXMLGUIBuilder::builderClient() const
395{
396 return d->m_client;
397}
398
399void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client)
400{
401 d->m_client = client;
402}
403
404void 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
412void KXMLGUIBuilder::virtual_hook(int, void *)
413{
414 /*BASE::virtual_hook( id, data );*/
415}
416

source code of kxmlgui/src/kxmlguibuilder.cpp