1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
4 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
5 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-only
8*/
9
10#include "kedittoolbar.h"
11#include "debug.h"
12#include "kedittoolbar_p.h"
13
14#include <QAction>
15#include <QApplication>
16#include <QCheckBox>
17#include <QComboBox>
18#include <QDialogButtonBox>
19#include <QDir>
20#include <QDomDocument>
21#include <QFile>
22#include <QGridLayout>
23#include <QLabel>
24#include <QLineEdit>
25#include <QMimeData>
26#include <QPushButton>
27#include <QShowEvent>
28#include <QStandardPaths>
29#include <QToolButton>
30
31#include <KIconDialog>
32#include <KListWidgetSearchLine>
33#include <KLocalizedString>
34#include <KMessageBox>
35#include <KSeparator>
36
37#include "kactioncollection.h"
38#include "ktoolbar.h"
39#include "kxmlguifactory.h"
40
41#include "ktoolbarhelper_p.h"
42#include <kxmlgui_version.h>
43
44// static const char *const s_XmlTypeToString[] = { "Shell", "Part", "Local", "Merged" };
45
46typedef QList<QDomElement> ToolBarList;
47
48namespace KDEPrivate
49{
50/**
51 * Return a list of toolbar elements given a toplevel element
52 */
53static ToolBarList findToolBars(const QDomElement &start)
54{
55 ToolBarList list;
56
57 for (QDomElement elem = start; !elem.isNull(); elem = elem.nextSiblingElement()) {
58 if (elem.tagName() == QLatin1String("ToolBar")) {
59 if (elem.attribute(QStringLiteral("noEdit")) != QLatin1String("true")) {
60 list.append(t: elem);
61 }
62 } else {
63 if (elem.tagName() != QLatin1String("MenuBar")) { // there are no toolbars inside the menubar :)
64 list += findToolBars(start: elem.firstChildElement()); // recursive
65 }
66 }
67 }
68
69 return list;
70}
71
72class XmlData
73{
74public:
75 enum XmlType { Shell = 0, Part, Local, Merged };
76
77 explicit XmlData(XmlType xmlType, const QString &xmlFile, KActionCollection *collection)
78 : m_isModified(false)
79 , m_xmlFile(xmlFile)
80 , m_type(xmlType)
81 , m_actionCollection(collection)
82 {
83 }
84 void dump() const
85 {
86#if 0
87 qDebug(240) << "XmlData" << this << "type" << s_XmlTypeToString[m_type] << "xmlFile:" << m_xmlFile;
88 foreach (const QDomElement &element, m_barList) {
89 qDebug(240) << " ToolBar:" << toolBarText(element);
90 }
91 if (m_actionCollection) {
92 qDebug(240) << " " << m_actionCollection->actions().count() << "actions in the collection.";
93 } else {
94 qDebug(240) << " no action collection.";
95 }
96#endif
97 }
98 QString xmlFile() const
99 {
100 return m_xmlFile;
101 }
102 XmlType type() const
103 {
104 return m_type;
105 }
106 KActionCollection *actionCollection() const
107 {
108 return m_actionCollection;
109 }
110 void setDomDocument(const QDomDocument &domDoc)
111 {
112 m_document = domDoc.cloneNode().toDocument();
113 m_barList = findToolBars(start: m_document.documentElement());
114 }
115 // Return reference, for e.g. actionPropertiesElement() to modify the document
116 QDomDocument &domDocument()
117 {
118 return m_document;
119 }
120 const QDomDocument &domDocument() const
121 {
122 return m_document;
123 }
124
125 /**
126 * Return the text (user-visible name) of a given toolbar
127 */
128 QString toolBarText(const QDomElement &it) const;
129
130 bool m_isModified = false;
131 ToolBarList &barList()
132 {
133 return m_barList;
134 }
135 const ToolBarList &barList() const
136 {
137 return m_barList;
138 }
139
140private:
141 ToolBarList m_barList;
142 QString m_xmlFile;
143 QDomDocument m_document;
144 XmlType m_type;
145 KActionCollection *m_actionCollection;
146};
147
148QString XmlData::toolBarText(const QDomElement &it) const
149{
150 QString name = KToolbarHelper::i18nToolBarName(element: it);
151
152 // the name of the toolbar might depend on whether or not
153 // it is in kparts
154 if ((m_type == XmlData::Shell) || (m_type == XmlData::Part)) {
155 QString doc_name(m_document.documentElement().attribute(QStringLiteral("name")));
156 name += QLatin1String(" <") + doc_name + QLatin1Char('>');
157 }
158 return name;
159}
160
161typedef QList<XmlData> XmlDataList;
162
163class ToolBarItem : public QListWidgetItem
164{
165public:
166 ToolBarItem(QListWidget *parent, const QString &tag = QString(), const QString &name = QString(), const QString &statusText = QString())
167 : QListWidgetItem(parent)
168 , m_internalTag(tag)
169 , m_internalName(name)
170 , m_statusText(statusText)
171 , m_isSeparator(false)
172 , m_isSpacer(false)
173 , m_isTextAlongsideIconHidden(false)
174 {
175 // Drop between items, not onto items
176 setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled);
177 }
178
179 void setInternalTag(const QString &tag)
180 {
181 m_internalTag = tag;
182 }
183 void setInternalName(const QString &name)
184 {
185 m_internalName = name;
186 }
187 void setStatusText(const QString &text)
188 {
189 m_statusText = text;
190 }
191 void setSeparator(bool sep)
192 {
193 m_isSeparator = sep;
194 }
195 void setSpacer(bool spacer)
196 {
197 m_isSpacer = spacer;
198 }
199 void setTextAlongsideIconHidden(bool hidden)
200 {
201 m_isTextAlongsideIconHidden = hidden;
202 }
203 QString internalTag() const
204 {
205 return m_internalTag;
206 }
207 QString internalName() const
208 {
209 return m_internalName;
210 }
211 QString statusText() const
212 {
213 return m_statusText;
214 }
215 bool isSeparator() const
216 {
217 return m_isSeparator;
218 }
219 bool isSpacer() const
220 {
221 return m_isSpacer;
222 }
223 bool isTextAlongsideIconHidden() const
224 {
225 return m_isTextAlongsideIconHidden;
226 }
227
228 int index() const
229 {
230 return listWidget()->row(item: const_cast<ToolBarItem *>(this));
231 }
232
233private:
234 QString m_internalTag;
235 QString m_internalName;
236 QString m_statusText;
237 bool m_isSeparator;
238 bool m_isSpacer;
239 bool m_isTextAlongsideIconHidden;
240};
241
242static QDataStream &operator<<(QDataStream &s, const ToolBarItem &item)
243{
244 s << item.internalTag();
245 s << item.internalName();
246 s << item.statusText();
247 s << item.isSeparator();
248 s << item.isSpacer();
249 s << item.isTextAlongsideIconHidden();
250 return s;
251}
252static QDataStream &operator>>(QDataStream &s, ToolBarItem &item)
253{
254 QString internalTag;
255 s >> internalTag;
256 item.setInternalTag(internalTag);
257 QString internalName;
258 s >> internalName;
259 item.setInternalName(internalName);
260 QString statusText;
261 s >> statusText;
262 item.setStatusText(statusText);
263 bool sep;
264 s >> sep;
265 item.setSeparator(sep);
266 bool spacer;
267 s >> spacer;
268 item.setSpacer(spacer);
269 bool hidden;
270 s >> hidden;
271 item.setTextAlongsideIconHidden(hidden);
272 return s;
273}
274
275////
276
277ToolBarListWidget::ToolBarListWidget(QWidget *parent)
278 : QListWidget(parent)
279 , m_activeList(true)
280{
281 setDragDropMode(QAbstractItemView::DragDrop); // no internal moves
282}
283QMimeData *ToolBarListWidget::mimeData(const QList<QListWidgetItem *> &items) const
284{
285 if (items.isEmpty()) {
286 return nullptr;
287 }
288 QMimeData *mimedata = new QMimeData();
289
290 QByteArray data;
291 {
292 QDataStream stream(&data, QIODevice::WriteOnly);
293 // we only support single selection
294 ToolBarItem *item = static_cast<ToolBarItem *>(items.first());
295 stream << *item;
296 }
297
298 mimedata->setData(QStringLiteral("application/x-kde-action-list"), data);
299 mimedata->setData(QStringLiteral("application/x-kde-source-treewidget"), data: m_activeList ? "active" : "inactive");
300
301 return mimedata;
302}
303
304bool ToolBarListWidget::dropMimeData(int index, const QMimeData *mimeData, Qt::DropAction action)
305{
306 Q_UNUSED(action)
307 const QByteArray data = mimeData->data(QStringLiteral("application/x-kde-action-list"));
308 if (data.isEmpty()) {
309 return false;
310 }
311 QDataStream stream(data);
312 const bool sourceIsActiveList = mimeData->data(QStringLiteral("application/x-kde-source-treewidget")) == "active";
313 ToolBarItem *item = new ToolBarItem(this); // needs parent, use this temporarily
314 stream >> *item;
315 Q_EMIT dropped(list: this, index, item, sourceIsActiveList);
316 return true;
317}
318
319ToolBarItem *ToolBarListWidget::currentItem() const
320{
321 return static_cast<ToolBarItem *>(QListWidget::currentItem());
322}
323
324IconTextEditDialog::IconTextEditDialog(QWidget *parent)
325 : QDialog(parent)
326{
327 setWindowTitle(i18nc("@title:window", "Change Text"));
328 setModal(true);
329
330 QVBoxLayout *layout = new QVBoxLayout(this);
331
332 QGridLayout *grid = new QGridLayout;
333 grid->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
334
335 m_lineEdit = new QLineEdit(this);
336 m_lineEdit->setClearButtonEnabled(true);
337 QLabel *label = new QLabel(i18n("Icon te&xt:"), this);
338 label->setBuddy(m_lineEdit);
339 grid->addWidget(label, row: 0, column: 0);
340 grid->addWidget(m_lineEdit, row: 0, column: 1);
341
342 m_cbHidden = new QCheckBox(i18nc("@option:check", "&Hide text when toolbar shows text alongside icons"), this);
343 grid->addWidget(m_cbHidden, row: 1, column: 1);
344
345 layout->addLayout(layout: grid);
346
347 m_buttonBox = new QDialogButtonBox(this);
348 m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
349 connect(sender: m_buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &QDialog::accept);
350 connect(sender: m_buttonBox, signal: &QDialogButtonBox::rejected, context: this, slot: &QDialog::reject);
351 layout->addWidget(m_buttonBox);
352
353 connect(sender: m_lineEdit, signal: &QLineEdit::textChanged, context: this, slot: &IconTextEditDialog::slotTextChanged);
354
355 m_lineEdit->setFocus();
356 setFixedHeight(sizeHint().height());
357}
358
359void IconTextEditDialog::setIconText(const QString &text)
360{
361 m_lineEdit->setText(text);
362}
363
364QString IconTextEditDialog::iconText() const
365{
366 return m_lineEdit->text().trimmed();
367}
368
369void IconTextEditDialog::setTextAlongsideIconHidden(bool hidden)
370{
371 m_cbHidden->setChecked(hidden);
372}
373
374bool IconTextEditDialog::textAlongsideIconHidden() const
375{
376 return m_cbHidden->isChecked();
377}
378
379void IconTextEditDialog::slotTextChanged(const QString &text)
380{
381 // Do not allow empty icon text
382 m_buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(!text.trimmed().isEmpty());
383}
384
385class KEditToolBarWidgetPrivate
386{
387public:
388 /**
389 *
390 * @param collection In the old-style constructor, this is the collection passed
391 * to the KEditToolBar constructor.
392 * In the xmlguifactory-based constructor, we let KXMLGUIClient create a dummy one,
393 * but it probably isn't used.
394 */
395 KEditToolBarWidgetPrivate(KEditToolBarWidget *widget, const QString &cName, KActionCollection *collection)
396 : m_collection(collection)
397 , m_widget(widget)
398 , m_factory(nullptr)
399 , m_componentName(cName)
400 , m_helpArea(nullptr)
401 , m_isPart(false)
402 , m_loadedOnce(false)
403 {
404 // We want items with an icon to align with items without icon
405 // So we use an empty QPixmap for that
406 const int iconSize = widget->style()->pixelMetric(metric: QStyle::PM_SmallIconSize);
407 m_emptyIcon = QPixmap(iconSize, iconSize);
408 m_emptyIcon.fill(fillColor: Qt::transparent);
409 }
410 ~KEditToolBarWidgetPrivate()
411 {
412 }
413
414 // private slots
415 void slotToolBarSelected(int index);
416
417 void slotInactiveSelectionChanged();
418 void slotActiveSelectionChanged();
419
420 void slotInsertButton();
421 void slotRemoveButton();
422 void slotUpButton();
423 void slotDownButton();
424
425 void selectActiveItem(const QString &);
426
427 void slotChangeIcon();
428 void slotChangeIconText();
429
430 void slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList);
431
432 void setupLayout();
433
434 void initOldStyle(const QString &file, bool global, const QString &defaultToolbar);
435 void initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolbar);
436 void loadToolBarCombo(const QString &defaultToolbar);
437 void loadActions(const QDomElement &elem);
438
439 QString xmlFile(const QString &xml_file) const
440 {
441 return xml_file.isEmpty() ? m_componentName + QLatin1String("ui.rc") : xml_file;
442 }
443
444 /**
445 * Load in the specified XML file and dump the raw xml
446 */
447 QString loadXMLFile(const QString &_xml_file)
448 {
449 QString raw_xml;
450 QString xml_file = xmlFile(xml_file: _xml_file);
451 // qCDebug(DEBUG_KXMLGUI) << "loadXMLFile xml_file=" << xml_file;
452
453 if (!QDir::isRelativePath(path: xml_file)) {
454 raw_xml = KXMLGUIFactory::readConfigFile(filename: xml_file);
455 } else {
456 raw_xml = KXMLGUIFactory::readConfigFile(filename: xml_file, componentName: m_componentName);
457 }
458
459 return raw_xml;
460 }
461
462 /**
463 * Look for a given item in the current toolbar
464 */
465 QDomElement findElementForToolBarItem(const ToolBarItem *item) const
466 {
467 // qDebug(240) << "looking for name=" << item->internalName() << "and tag=" << item->internalTag();
468 for (QDomNode n = m_currentToolBarElem.firstChild(); !n.isNull(); n = n.nextSibling()) {
469 QDomElement elem = n.toElement();
470 if ((elem.attribute(QStringLiteral("name")) == item->internalName()) && (elem.tagName() == item->internalTag())) {
471 return elem;
472 }
473 }
474 // qDebug(240) << "no item found in the DOM with name=" << item->internalName() << "and tag=" << item->internalTag();
475 return QDomElement();
476 }
477
478 void insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend = false);
479 void removeActive(ToolBarItem *item);
480 void moveActive(ToolBarItem *item, ToolBarItem *before);
481 void updateLocal(QDomElement &elem);
482
483#ifndef NDEBUG
484 void dump() const
485 {
486 for (const auto &xmlFile : m_xmlFiles) {
487 xmlFile.dump();
488 }
489 }
490#endif
491
492 QComboBox *m_toolbarCombo;
493
494 QToolButton *m_upAction;
495 QToolButton *m_removeAction;
496 QToolButton *m_insertAction;
497 QToolButton *m_downAction;
498
499 // QValueList<QAction*> m_actionList;
500 KActionCollection *m_collection;
501 KEditToolBarWidget *const m_widget;
502 KXMLGUIFactory *m_factory;
503 QString m_componentName;
504
505 QPixmap m_emptyIcon;
506
507 XmlData *m_currentXmlData;
508 QDomElement m_currentToolBarElem;
509
510 QString m_xmlFile;
511 QString m_globalFile;
512 QString m_rcFile;
513 QDomDocument m_localDoc;
514
515 ToolBarList m_barList;
516 ToolBarListWidget *m_inactiveList;
517 ToolBarListWidget *m_activeList;
518
519 XmlDataList m_xmlFiles;
520
521 QLabel *m_comboLabel;
522 KSeparator *m_comboSeparator;
523 QLabel *m_helpArea;
524 QPushButton *m_changeIcon;
525 QPushButton *m_changeIconText;
526 bool m_isPart : 1;
527 bool m_loadedOnce : 1;
528};
529
530}
531
532using namespace KDEPrivate;
533
534class KEditToolBarPrivate
535{
536public:
537 KEditToolBarPrivate(KEditToolBar *qq)
538 : q(qq)
539 {
540 }
541
542 void init();
543
544 void slotButtonClicked(QAbstractButton *button);
545 void acceptOK(bool);
546 void enableApply(bool);
547 void okClicked();
548 void applyClicked();
549 void defaultClicked();
550
551 KEditToolBar *const q;
552 bool m_accept = false;
553 // Save parameters for recreating widget after resetting toolbar
554 bool m_global = false;
555 KActionCollection *m_collection = nullptr;
556 QString m_file;
557 QString m_defaultToolBar;
558 KXMLGUIFactory *m_factory = nullptr;
559 KEditToolBarWidget *m_widget = nullptr;
560 QVBoxLayout *m_layout = nullptr;
561 QDialogButtonBox *m_buttonBox = nullptr;
562};
563
564Q_GLOBAL_STATIC(QString, s_defaultToolBarName)
565
566KEditToolBar::KEditToolBar(KActionCollection *collection, QWidget *parent)
567 : QDialog(parent)
568 , d(new KEditToolBarPrivate(this))
569{
570 d->m_widget = new KEditToolBarWidget(collection, this);
571 d->init();
572 d->m_collection = collection;
573}
574
575KEditToolBar::KEditToolBar(KXMLGUIFactory *factory, QWidget *parent)
576 : QDialog(parent)
577 , d(new KEditToolBarPrivate(this))
578{
579 d->m_widget = new KEditToolBarWidget(this);
580 d->init();
581 d->m_factory = factory;
582}
583
584void KEditToolBarPrivate::init()
585{
586 m_accept = false;
587 m_factory = nullptr;
588
589 q->setDefaultToolBar(QString());
590
591 q->setWindowTitle(i18nc("@title:window", "Configure Toolbars"));
592 q->setModal(false);
593
594 m_layout = new QVBoxLayout(q);
595 m_layout->addWidget(m_widget);
596
597 m_buttonBox = new QDialogButtonBox(q);
598 m_buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
599 KGuiItem::assign(button: m_buttonBox->button(which: QDialogButtonBox::Ok), item: KStandardGuiItem::ok());
600 KGuiItem::assign(button: m_buttonBox->button(which: QDialogButtonBox::Apply), item: KStandardGuiItem::apply());
601 KGuiItem::assign(button: m_buttonBox->button(which: QDialogButtonBox::Cancel), item: KStandardGuiItem::cancel());
602 KGuiItem::assign(button: m_buttonBox->button(which: QDialogButtonBox::RestoreDefaults), item: KStandardGuiItem::defaults());
603 q->connect(sender: m_buttonBox, signal: &QDialogButtonBox::clicked, context: q, slot: [this](QAbstractButton *button) {
604 slotButtonClicked(button);
605 });
606 QObject::connect(sender: m_buttonBox, signal: &QDialogButtonBox::rejected, context: q, slot: &QDialog::reject);
607 m_layout->addWidget(m_buttonBox);
608
609 q->connect(sender: m_widget, signal: &KEditToolBarWidget::enableOk, context: q, slot: [this](bool state) {
610 acceptOK(state);
611 enableApply(state);
612 });
613 enableApply(false);
614
615 q->setMinimumSize(q->sizeHint());
616}
617
618void KEditToolBar::setResourceFile(const QString &file, bool global)
619{
620 d->m_file = file;
621 d->m_global = global;
622 d->m_widget->load(resourceFile: d->m_file, global: d->m_global, defaultToolBar: d->m_defaultToolBar);
623}
624
625KEditToolBar::~KEditToolBar()
626{
627 s_defaultToolBarName()->clear();
628}
629
630void KEditToolBar::setDefaultToolBar(const QString &toolBarName)
631{
632 if (toolBarName.isEmpty()) {
633 d->m_defaultToolBar = *s_defaultToolBarName();
634 } else {
635 d->m_defaultToolBar = toolBarName;
636 }
637}
638
639void KEditToolBarPrivate::acceptOK(bool b)
640{
641 m_buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(b);
642 m_accept = b;
643}
644
645void KEditToolBarPrivate::enableApply(bool b)
646{
647 m_buttonBox->button(which: QDialogButtonBox::Apply)->setEnabled(b);
648}
649
650void KEditToolBarPrivate::defaultClicked()
651{
652 if (KMessageBox::warningContinueCancel(
653 parent: q,
654 i18n("Do you really want to reset all toolbars of this application to their default? The changes will be applied immediately."),
655 i18n("Reset Toolbars"),
656 buttonContinue: KGuiItem(i18n("Reset")))
657 != KMessageBox::Continue) {
658 return;
659 }
660
661 KEditToolBarWidget *oldWidget = m_widget;
662 m_widget = nullptr;
663 m_accept = false;
664
665 if (m_factory) {
666 const auto clients = m_factory->clients();
667 for (KXMLGUIClient *client : clients) {
668 const QString file = client->localXMLFile();
669 if (file.isEmpty()) {
670 continue;
671 }
672 // qDebug(240) << "Deleting local xml file" << file;
673 // << "for client" << client << typeid(*client).name();
674 if (QFile::exists(fileName: file)) {
675 if (!QFile::remove(fileName: file)) {
676 qCWarning(DEBUG_KXMLGUI) << "Could not delete" << file;
677 }
678 }
679 }
680
681 // Reload the xml files in all clients, now that the local files are gone
682 oldWidget->rebuildKXMLGUIClients();
683
684 m_widget = new KEditToolBarWidget(q);
685 m_widget->load(factory: m_factory, defaultToolBar: m_defaultToolBar);
686 } else {
687 int slash = m_file.lastIndexOf(c: QLatin1Char('/')) + 1;
688 if (slash) {
689 m_file.remove(i: 0, len: slash);
690 }
691 const QString xml_file = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/")
692 + QCoreApplication::instance()->applicationName() + QLatin1Char('/') + m_file;
693
694 if (QFile::exists(fileName: xml_file)) {
695 if (!QFile::remove(fileName: xml_file)) {
696 qCWarning(DEBUG_KXMLGUI) << "Could not delete " << xml_file;
697 }
698 }
699
700 m_widget = new KEditToolBarWidget(m_collection, q);
701 q->setResourceFile(file: m_file, global: m_global);
702 }
703
704 // Copy the geometry to minimize UI flicker
705 m_widget->setGeometry(oldWidget->geometry());
706 delete oldWidget;
707 m_layout->insertWidget(index: 0, widget: m_widget);
708
709 q->connect(sender: m_widget, signal: &KEditToolBarWidget::enableOk, context: q, slot: [this](bool state) {
710 acceptOK(b: state);
711 enableApply(b: state);
712 });
713 enableApply(b: false);
714
715 Q_EMIT q->newToolBarConfig();
716}
717
718void KEditToolBarPrivate::slotButtonClicked(QAbstractButton *button)
719{
720 QDialogButtonBox::StandardButton type = m_buttonBox->standardButton(button);
721
722 switch (type) {
723 case QDialogButtonBox::Ok:
724 okClicked();
725 break;
726 case QDialogButtonBox::Apply:
727 applyClicked();
728 break;
729 case QDialogButtonBox::RestoreDefaults:
730 defaultClicked();
731 break;
732 default:
733 break;
734 }
735}
736
737void KEditToolBarPrivate::okClicked()
738{
739 if (!m_accept) {
740 q->reject();
741 return;
742 }
743
744 // Do not rebuild GUI and emit the "newToolBarConfig" signal again here if the "Apply"
745 // button was already pressed and no further changes were made.
746 if (m_buttonBox->button(which: QDialogButtonBox::Apply)->isEnabled()) {
747 m_widget->save();
748 Q_EMIT q->newToolBarConfig();
749 }
750 q->accept();
751}
752
753void KEditToolBarPrivate::applyClicked()
754{
755 (void)m_widget->save();
756 enableApply(b: false);
757 Q_EMIT q->newToolBarConfig();
758}
759
760void KEditToolBar::setGlobalDefaultToolBar(const QString &toolBarName)
761{
762 *s_defaultToolBarName() = toolBarName;
763}
764
765KEditToolBarWidget::KEditToolBarWidget(KActionCollection *collection, QWidget *parent)
766 : QWidget(parent)
767 , d(new KEditToolBarWidgetPrivate(this, componentName(), collection))
768{
769 d->setupLayout();
770}
771
772KEditToolBarWidget::KEditToolBarWidget(QWidget *parent)
773 : QWidget(parent)
774 , d(new KEditToolBarWidgetPrivate(this, componentName(), KXMLGUIClient::actionCollection() /*create new one*/))
775{
776 d->setupLayout();
777}
778
779KEditToolBarWidget::~KEditToolBarWidget()
780{
781 delete d;
782}
783
784void KEditToolBarWidget::load(const QString &file, bool global, const QString &defaultToolBar)
785{
786 d->initOldStyle(file, global, defaultToolbar: defaultToolBar);
787}
788
789void KEditToolBarWidget::load(KXMLGUIFactory *factory, const QString &defaultToolBar)
790{
791 d->initFromFactory(factory, defaultToolbar: defaultToolBar);
792}
793
794void KEditToolBarWidgetPrivate::initOldStyle(const QString &resourceFile, bool global, const QString &defaultToolBar)
795{
796 // TODO: make sure we can call this multiple times?
797 if (m_loadedOnce) {
798 return;
799 }
800
801 m_loadedOnce = true;
802 // d->m_actionList = collection->actions();
803
804 // handle the merging
805 if (global) {
806 m_widget->loadStandardsXmlFile(); // ui_standards.rc
807 }
808 const QString localXML = loadXMLFile(xml_file: resourceFile);
809 m_widget->setXML(document: localXML, merge: global ? true /*merge*/ : false);
810
811 // first, get all of the necessary info for our local xml
812 XmlData local(XmlData::Local, xmlFile(xml_file: resourceFile), m_collection);
813 QDomDocument domDoc;
814 domDoc.setContent(data: localXML);
815 local.setDomDocument(domDoc);
816 m_xmlFiles.append(t: local);
817
818 // then, the merged one (ui_standards + local xml)
819 XmlData merge(XmlData::Merged, QString(), m_collection);
820 merge.setDomDocument(m_widget->domDocument());
821 m_xmlFiles.append(t: merge);
822
823#ifndef NDEBUG
824 dump();
825#endif
826
827 // now load in our toolbar combo box
828 loadToolBarCombo(defaultToolbar: defaultToolBar);
829 m_widget->adjustSize();
830 m_widget->setMinimumSize(m_widget->sizeHint());
831}
832
833void KEditToolBarWidgetPrivate::initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolBar)
834{
835 // TODO: make sure we can call this multiple times?
836 if (m_loadedOnce) {
837 return;
838 }
839
840 m_loadedOnce = true;
841
842 m_factory = factory;
843
844 // add all of the client data
845 bool first = true;
846 const auto clients = factory->clients();
847 for (KXMLGUIClient *client : clients) {
848 if (client->xmlFile().isEmpty()) {
849 continue;
850 }
851
852 XmlData::XmlType type = XmlData::Part;
853 if (first) {
854 type = XmlData::Shell;
855 first = false;
856 Q_ASSERT(!client->localXMLFile().isEmpty()); // where would we save changes??
857 }
858
859 XmlData data(type, client->localXMLFile(), client->actionCollection());
860 QDomDocument domDoc = client->domDocument();
861 data.setDomDocument(domDoc);
862 m_xmlFiles.append(t: data);
863
864 // d->m_actionList += client->actionCollection()->actions();
865 }
866
867#ifndef NDEBUG
868 // d->dump();
869#endif
870
871 // now load in our toolbar combo box
872 loadToolBarCombo(defaultToolbar: defaultToolBar);
873 m_widget->adjustSize();
874 m_widget->setMinimumSize(m_widget->sizeHint());
875
876 m_widget->actionCollection()->addAssociatedWidget(widget: m_widget);
877 const auto widgetActions = m_widget->actionCollection()->actions();
878 for (QAction *action : widgetActions) {
879 action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
880 }
881}
882
883void KEditToolBarWidget::save()
884{
885 // qDebug(240) << "KEditToolBarWidget::save";
886 for (const auto &xmlFile : std::as_const(t&: d->m_xmlFiles)) {
887 // let's not save non-modified files
888 if (!xmlFile.m_isModified) {
889 continue;
890 }
891
892 // let's also skip (non-existent) merged files
893 if (xmlFile.type() == XmlData::Merged) {
894 continue;
895 }
896
897 // Add noMerge="1" to all the menus since we are saving the merged data
898 QDomNodeList menuNodes = xmlFile.domDocument().elementsByTagName(QStringLiteral("Menu"));
899 for (int i = 0; i < menuNodes.length(); ++i) {
900 QDomNode menuNode = menuNodes.item(index: i);
901 QDomElement menuElement = menuNode.toElement();
902 if (menuElement.isNull()) {
903 continue;
904 }
905 menuElement.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
906 }
907
908 // qCDebug(DEBUG_KXMLGUI) << (*it).domDocument().toString();
909
910 // qDebug(240) << "Saving " << (*it).xmlFile();
911 // if we got this far, we might as well just save it
912 KXMLGUIFactory::saveConfigFile(doc: xmlFile.domDocument(), filename: xmlFile.xmlFile());
913 }
914
915 if (!d->m_factory) {
916 return;
917 }
918
919 rebuildKXMLGUIClients();
920}
921
922void KEditToolBarWidget::rebuildKXMLGUIClients()
923{
924 if (!d->m_factory) {
925 return;
926 }
927
928 const QList<KXMLGUIClient *> clients = d->m_factory->clients();
929 // qDebug() << "factory: " << clients.count() << " clients";
930
931 if (clients.isEmpty()) {
932 return;
933 }
934
935 // remove the elements starting from the last going to the first
936 for (auto it = clients.crbegin(); it != clients.crend(); ++it) {
937 // qDebug() << "factory->removeClient " << client;
938 d->m_factory->removeClient(client: *it);
939 }
940
941 KXMLGUIClient *firstClient = clients.first();
942
943 // now, rebuild the gui from the first to the last
944 // qDebug(240) << "rebuilding the gui";
945 for (KXMLGUIClient *client : clients) {
946 // qDebug(240) << "updating client " << client << " " << client->componentName() << " xmlFile=" << client->xmlFile();
947 QString file(client->xmlFile()); // before setting ui_standards!
948 if (!file.isEmpty()) {
949 // passing an empty stream forces the clients to reread the XML
950 client->setXMLGUIBuildDocument(QDomDocument());
951
952 // for the shell, merge in ui_standards.rc
953 if (client == firstClient) { // same assumption as in the ctor: first==shell
954 client->loadStandardsXmlFile();
955 }
956
957 // and this forces it to use the *new* XML file
958 client->setXMLFile(file, merge: client == firstClient /* merge if shell */);
959
960 // [we can't use reloadXML, it doesn't load ui_standards.rc]
961 }
962 }
963
964 // Now we can add the clients to the factory
965 // We don't do it in the loop above because adding a part automatically
966 // adds its plugins, so we must make sure the plugins were updated first.
967 for (KXMLGUIClient *client : clients) {
968 d->m_factory->addClient(client);
969 }
970}
971
972void KEditToolBarWidgetPrivate::setupLayout()
973{
974 // the toolbar name combo
975 m_comboLabel = new QLabel(i18n("&Toolbar:"), m_widget);
976 m_toolbarCombo = new QComboBox(m_widget);
977 m_comboLabel->setBuddy(m_toolbarCombo);
978 m_comboSeparator = new KSeparator(m_widget);
979 QObject::connect(sender: m_toolbarCombo, signal: qOverload<int>(&QComboBox::activated), context: m_widget, slot: [this](int index) {
980 slotToolBarSelected(index);
981 });
982
983 // QPushButton *new_toolbar = new QPushButton(i18n("&New"), this);
984 // new_toolbar->setPixmap(BarIcon("document-new", KIconLoader::SizeSmall));
985 // new_toolbar->setEnabled(false); // disabled until implemented
986 // QPushButton *del_toolbar = new QPushButton(i18n("&Delete"), this);
987 // del_toolbar->setPixmap(BarIcon("edit-delete", KIconLoader::SizeSmall));
988 // del_toolbar->setEnabled(false); // disabled until implemented
989
990 // our list of inactive actions
991 QLabel *inactive_label = new QLabel(i18n("A&vailable actions:"), m_widget);
992 m_inactiveList = new ToolBarListWidget(m_widget);
993 m_inactiveList->setDragEnabled(true);
994 m_inactiveList->setActiveList(false);
995 m_inactiveList->setMinimumSize(minw: 180, minh: 200);
996 m_inactiveList->setDropIndicatorShown(false); // #165663
997 inactive_label->setBuddy(m_inactiveList);
998 QObject::connect(sender: m_inactiveList, signal: &QListWidget::itemSelectionChanged, context: m_widget, slot: [this]() {
999 slotInactiveSelectionChanged();
1000 });
1001 QObject::connect(sender: m_inactiveList, signal: &QListWidget::itemDoubleClicked, context: m_widget, slot: [this]() {
1002 slotInsertButton();
1003 });
1004 QObject::connect(sender: m_inactiveList,
1005 signal: &ToolBarListWidget::dropped,
1006 context: m_widget,
1007 slot: [this](ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList) {
1008 slotDropped(list, index, item, sourceIsActiveList);
1009 });
1010
1011 KListWidgetSearchLine *inactiveListSearchLine = new KListWidgetSearchLine(m_widget, m_inactiveList);
1012 inactiveListSearchLine->setPlaceholderText(i18n("Filter"));
1013
1014 // our list of active actions
1015 QLabel *active_label = new QLabel(i18n("Curr&ent actions:"), m_widget);
1016 m_activeList = new ToolBarListWidget(m_widget);
1017 m_activeList->setDragEnabled(true);
1018 m_activeList->setActiveList(true);
1019 // With Qt-4.1 only setting MiniumWidth results in a 0-width icon column ...
1020 m_activeList->setMinimumSize(minw: m_inactiveList->minimumWidth(), minh: 100);
1021 active_label->setBuddy(m_activeList);
1022
1023 QObject::connect(sender: m_activeList, signal: &QListWidget::itemSelectionChanged, context: m_widget, slot: [this]() {
1024 slotActiveSelectionChanged();
1025 });
1026 QObject::connect(sender: m_activeList, signal: &QListWidget::itemDoubleClicked, context: m_widget, slot: [this]() {
1027 slotRemoveButton();
1028 });
1029 QObject::connect(sender: m_activeList,
1030 signal: &ToolBarListWidget::dropped,
1031 context: m_widget,
1032 slot: [this](ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList) {
1033 slotDropped(list, index, item, sourceIsActiveList);
1034 });
1035
1036 KListWidgetSearchLine *activeListSearchLine = new KListWidgetSearchLine(m_widget, m_activeList);
1037 activeListSearchLine->setPlaceholderText(i18n("Filter"));
1038
1039 // "change icon" button
1040 m_changeIcon = new QPushButton(i18nc("@action:button", "Change &Icon..."), m_widget);
1041 m_changeIcon->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-icons")));
1042 m_changeIcon->setEnabled(m_activeList->currentItem());
1043
1044 QObject::connect(sender: m_changeIcon, signal: &QPushButton::clicked, context: m_widget, slot: [this]() {
1045 slotChangeIcon();
1046 });
1047
1048 // "change icon text" button
1049 m_changeIconText = new QPushButton(i18nc("@action:button", "Change Te&xt..."), m_widget);
1050 m_changeIconText->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
1051 m_changeIconText->setEnabled(m_activeList->currentItem() != nullptr);
1052
1053 QObject::connect(sender: m_changeIconText, signal: &QPushButton::clicked, context: m_widget, slot: [this]() {
1054 slotChangeIconText();
1055 });
1056
1057 // The buttons in the middle
1058
1059 m_upAction = new QToolButton(m_widget);
1060 m_upAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1061 m_upAction->setEnabled(false);
1062 m_upAction->setAutoRepeat(true);
1063 QObject::connect(sender: m_upAction, signal: &QToolButton::clicked, context: m_widget, slot: [this]() {
1064 slotUpButton();
1065 });
1066
1067 m_insertAction = new QToolButton(m_widget);
1068 m_insertAction->setIcon(QIcon::fromTheme(name: QApplication::isRightToLeft() ? QStringLiteral("go-previous") : QStringLiteral("go-next")));
1069 m_insertAction->setEnabled(false);
1070 QObject::connect(sender: m_insertAction, signal: &QToolButton::clicked, context: m_widget, slot: [this]() {
1071 slotInsertButton();
1072 });
1073
1074 m_removeAction = new QToolButton(m_widget);
1075 m_removeAction->setIcon(QIcon::fromTheme(name: QApplication::isRightToLeft() ? QStringLiteral("go-next") : QStringLiteral("go-previous")));
1076 m_removeAction->setEnabled(false);
1077 QObject::connect(sender: m_removeAction, signal: &QToolButton::clicked, context: m_widget, slot: [this]() {
1078 slotRemoveButton();
1079 });
1080
1081 m_downAction = new QToolButton(m_widget);
1082 m_downAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1083 m_downAction->setEnabled(false);
1084 m_downAction->setAutoRepeat(true);
1085 QObject::connect(sender: m_downAction, signal: &QToolButton::clicked, context: m_widget, slot: [this]() {
1086 slotDownButton();
1087 });
1088
1089 m_helpArea = new QLabel(m_widget);
1090 m_helpArea->setWordWrap(true);
1091
1092 // now start with our layouts
1093 QVBoxLayout *top_layout = new QVBoxLayout(m_widget);
1094 top_layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
1095
1096 QVBoxLayout *name_layout = new QVBoxLayout();
1097 QHBoxLayout *list_layout = new QHBoxLayout();
1098
1099 QVBoxLayout *inactive_layout = new QVBoxLayout();
1100 QVBoxLayout *active_layout = new QVBoxLayout();
1101 QHBoxLayout *changeIcon_layout = new QHBoxLayout();
1102
1103 QGridLayout *button_layout = new QGridLayout();
1104
1105 name_layout->addWidget(m_comboLabel);
1106 name_layout->addWidget(m_toolbarCombo);
1107 // name_layout->addWidget(new_toolbar);
1108 // name_layout->addWidget(del_toolbar);
1109
1110 button_layout->setSpacing(0);
1111 button_layout->setRowStretch(row: 0, stretch: 10);
1112 button_layout->addWidget(m_upAction, row: 1, column: 1);
1113 button_layout->addWidget(m_removeAction, row: 2, column: 0);
1114 button_layout->addWidget(m_insertAction, row: 2, column: 2);
1115 button_layout->addWidget(m_downAction, row: 3, column: 1);
1116 button_layout->setRowStretch(row: 4, stretch: 10);
1117
1118 inactive_layout->addWidget(inactive_label);
1119 inactive_layout->addWidget(inactiveListSearchLine);
1120 inactive_layout->addWidget(m_inactiveList, stretch: 1);
1121
1122 active_layout->addWidget(active_label);
1123 active_layout->addWidget(activeListSearchLine);
1124 active_layout->addWidget(m_activeList, stretch: 1);
1125 active_layout->addLayout(layout: changeIcon_layout);
1126
1127 changeIcon_layout->addWidget(m_changeIcon);
1128 changeIcon_layout->addStretch(stretch: 1);
1129 changeIcon_layout->addWidget(m_changeIconText);
1130
1131 list_layout->addLayout(layout: inactive_layout);
1132 list_layout->addLayout(layout: button_layout);
1133 list_layout->addLayout(layout: active_layout);
1134
1135 top_layout->addLayout(layout: name_layout);
1136 top_layout->addWidget(m_comboSeparator);
1137 top_layout->addLayout(layout: list_layout, stretch: 10);
1138 top_layout->addWidget(m_helpArea);
1139 top_layout->addWidget(new KSeparator(m_widget));
1140}
1141
1142void KEditToolBarWidgetPrivate::loadToolBarCombo(const QString &defaultToolBar)
1143{
1144 const QLatin1String attrName("name");
1145 // just in case, we clear our combo
1146 m_toolbarCombo->clear();
1147
1148 int defaultToolBarId = -1;
1149 int count = 0;
1150 // load in all of the toolbar names into this combo box
1151 for (const auto &xmlFile : std::as_const(t&: m_xmlFiles)) {
1152 // skip the merged one in favor of the local one,
1153 // so that we can change icons
1154 // This also makes the app-defined named for "mainToolBar" appear rather than the ui_standards-defined name.
1155 if (xmlFile.type() == XmlData::Merged) {
1156 continue;
1157 }
1158
1159 // each xml file may have any number of toolbars
1160 for (const auto &bar : std::as_const(t: xmlFile.barList())) {
1161 const QString text = xmlFile.toolBarText(it: bar);
1162 m_toolbarCombo->addItem(atext: text);
1163 const QString name = bar.attribute(name: attrName);
1164 if (defaultToolBarId == -1 && name == defaultToolBar) {
1165 defaultToolBarId = count;
1166 }
1167 count++;
1168 }
1169 }
1170 const bool showCombo = (count > 1);
1171 m_comboLabel->setVisible(showCombo);
1172 m_comboSeparator->setVisible(showCombo);
1173 m_toolbarCombo->setVisible(showCombo);
1174 if (defaultToolBarId == -1) {
1175 defaultToolBarId = 0;
1176 }
1177 // we want to the specified item selected and its actions loaded
1178 m_toolbarCombo->setCurrentIndex(defaultToolBarId);
1179 slotToolBarSelected(index: m_toolbarCombo->currentIndex());
1180}
1181
1182void KEditToolBarWidgetPrivate::loadActions(const QDomElement &elem)
1183{
1184 const QLatin1String tagSeparator("Separator");
1185 const QLatin1String tagSpacer("Spacer");
1186 const QLatin1String tagMerge("Merge");
1187 const QLatin1String tagActionList("ActionList");
1188 const QLatin1String tagAction("Action");
1189 const QLatin1String attrName("name");
1190
1191 const QString separatorstring = i18n("--- separator ---");
1192 const QString spacerstring = i18n("--- expanding spacer ---");
1193
1194 int sep_num = 0;
1195 QString sep_name(QStringLiteral("separator_%1"));
1196 int spacer_num = 0;
1197 QString spacer_name(QStringLiteral("spacer_%1"));
1198
1199 // clear our lists
1200 m_inactiveList->clear();
1201 m_activeList->clear();
1202 m_insertAction->setEnabled(false);
1203 m_removeAction->setEnabled(false);
1204 m_upAction->setEnabled(false);
1205 m_downAction->setEnabled(false);
1206
1207 // We'll use this action collection
1208 KActionCollection *actionCollection = m_currentXmlData->actionCollection();
1209
1210 // store the names of our active actions
1211 QSet<QString> active_list;
1212
1213 // Filtering message requested by translators (scripting).
1214 KLocalizedString nameFilter = ki18nc("@item:intable Action name in toolbar editor", "%1");
1215
1216 // see if our current action is in this toolbar
1217 QDomNode n = elem.firstChild();
1218 for (; !n.isNull(); n = n.nextSibling()) {
1219 QDomElement it = n.toElement();
1220 if (it.isNull()) {
1221 continue;
1222 }
1223 if (it.tagName() == tagSeparator) {
1224 ToolBarItem *act = new ToolBarItem(m_activeList, tagSeparator, sep_name.arg(a: sep_num++), QString());
1225 act->setSeparator(true);
1226 act->setText(separatorstring);
1227 it.setAttribute(name: attrName, value: act->internalName());
1228 continue;
1229 }
1230 if (it.tagName() == tagSpacer) {
1231 ToolBarItem *act = new ToolBarItem(m_activeList, tagSpacer, spacer_name.arg(a: spacer_num++), QString());
1232 act->setSpacer(true);
1233 act->setText(spacerstring);
1234 it.setAttribute(name: attrName, value: act->internalName());
1235 continue;
1236 }
1237
1238 if (it.tagName() == tagMerge) {
1239 // Merge can be named or not - use the name if there is one
1240 QString name = it.attribute(name: attrName);
1241 ToolBarItem *act =
1242 new ToolBarItem(m_activeList, tagMerge, name, i18n("This element will be replaced with all the elements of an embedded component."));
1243 if (name.isEmpty()) {
1244 act->setText(i18n("<Merge>"));
1245 } else {
1246 act->setText(i18n("<Merge %1>", name));
1247 }
1248 continue;
1249 }
1250
1251 if (it.tagName() == tagActionList) {
1252 ToolBarItem *act =
1253 new ToolBarItem(m_activeList,
1254 tagActionList,
1255 it.attribute(name: attrName),
1256 i18n("This is a dynamic list of actions. You can move it, but if you remove it you will not be able to re-add it."));
1257 act->setText(i18n("ActionList: %1", it.attribute(attrName)));
1258 continue;
1259 }
1260
1261 // iterate through this client's actions
1262 // This used to iterate through _all_ actions, but we don't support
1263 // putting any action into any client...
1264 const auto actions = actionCollection->actions();
1265 for (QAction *action : actions) {
1266 // do we have a match?
1267 if (it.attribute(name: attrName) == action->objectName()) {
1268 // we have a match!
1269 ToolBarItem *act = new ToolBarItem(m_activeList, it.tagName(), action->objectName(), action->toolTip());
1270 act->setText(nameFilter.subs(a: KLocalizedString::removeAcceleratorMarker(label: action->iconText())).toString());
1271 act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon);
1272 act->setTextAlongsideIconHidden(action->priority() < QAction::NormalPriority);
1273
1274 active_list.insert(value: action->objectName());
1275 break;
1276 }
1277 }
1278 }
1279
1280 // go through the rest of the collection
1281 const auto actions = actionCollection->actions();
1282 for (QAction *action : actions) {
1283 // skip our active ones
1284 if (active_list.contains(value: action->objectName())) {
1285 continue;
1286 }
1287
1288 ToolBarItem *act = new ToolBarItem(m_inactiveList, tagAction, action->objectName(), action->toolTip());
1289 act->setText(nameFilter.subs(a: KLocalizedString::removeAcceleratorMarker(label: action->text())).toString());
1290 act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon);
1291 }
1292
1293 m_inactiveList->sortItems(order: Qt::AscendingOrder);
1294
1295 // finally, add default separators and spacers to the inactive list
1296 ToolBarItem *sep = new ToolBarItem(nullptr, tagSeparator, sep_name.arg(a: sep_num++), QString());
1297 sep->setSeparator(true);
1298 sep->setText(separatorstring);
1299 m_inactiveList->insertItem(row: 0, item: sep);
1300
1301 ToolBarItem *spacer = new ToolBarItem(nullptr, tagSpacer, spacer_name.arg(a: spacer_num++), QString());
1302 spacer->setSpacer(true);
1303 spacer->setText(spacerstring);
1304 m_inactiveList->insertItem(row: 1, item: spacer);
1305}
1306
1307KActionCollection *KEditToolBarWidget::actionCollection() const
1308{
1309 return d->m_collection;
1310}
1311
1312void KEditToolBarWidgetPrivate::slotToolBarSelected(int index)
1313{
1314 // We need to find the XmlData and toolbar element for this index
1315 // To do that, we do the same iteration as the one which filled in the combobox.
1316
1317 int toolbarNumber = 0;
1318 for (auto &xmlFile : m_xmlFiles) {
1319 // skip the merged one in favor of the local one,
1320 // so that we can change icons
1321 if (xmlFile.type() == XmlData::Merged) {
1322 continue;
1323 }
1324
1325 // each xml file may have any number of toolbars
1326 const auto &barList = xmlFile.barList();
1327 for (const auto &bar : barList) {
1328 // is this our toolbar?
1329 if (toolbarNumber == index) {
1330 // save our current settings
1331 m_currentXmlData = &xmlFile;
1332 m_currentToolBarElem = bar;
1333
1334 // qCDebug(DEBUG_KXMLGUI) << "found toolbar" << m_currentXmlData->toolBarText(*it) << "m_currentXmlData set to";
1335 m_currentXmlData->dump();
1336
1337 // If this is a Merged xmldata, clicking the "change icon" button would assert...
1338 Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1339
1340 // load in our values
1341 loadActions(elem: m_currentToolBarElem);
1342
1343 if (xmlFile.type() == XmlData::Part || xmlFile.type() == XmlData::Shell) {
1344 m_widget->setDOMDocument(document: xmlFile.domDocument());
1345 }
1346 return;
1347 }
1348 ++toolbarNumber;
1349 }
1350 }
1351}
1352
1353void KEditToolBarWidgetPrivate::slotInactiveSelectionChanged()
1354{
1355 if (!m_inactiveList->selectedItems().isEmpty()) {
1356 m_insertAction->setEnabled(true);
1357 QString statusText = static_cast<ToolBarItem *>(m_inactiveList->selectedItems().first())->statusText();
1358 m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText));
1359 } else {
1360 m_insertAction->setEnabled(false);
1361 m_helpArea->setText(QString());
1362 }
1363}
1364
1365void KEditToolBarWidgetPrivate::slotActiveSelectionChanged()
1366{
1367 ToolBarItem *toolitem = nullptr;
1368 if (!m_activeList->selectedItems().isEmpty()) {
1369 toolitem = static_cast<ToolBarItem *>(m_activeList->selectedItems().first());
1370 }
1371
1372 m_removeAction->setEnabled(toolitem);
1373
1374 m_changeIcon->setEnabled(toolitem && toolitem->internalTag() == QLatin1String("Action"));
1375
1376 m_changeIconText->setEnabled(toolitem && toolitem->internalTag() == QLatin1String("Action"));
1377
1378 if (toolitem) {
1379 m_upAction->setEnabled(toolitem->index() != 0);
1380 m_downAction->setEnabled(toolitem->index() != toolitem->listWidget()->count() - 1);
1381
1382 QString statusText = toolitem->statusText();
1383 m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText));
1384 } else {
1385 m_upAction->setEnabled(false);
1386 m_downAction->setEnabled(false);
1387 m_helpArea->setText(QString());
1388 }
1389}
1390
1391void KEditToolBarWidgetPrivate::slotInsertButton()
1392{
1393 QString internalName = static_cast<ToolBarItem *>(m_inactiveList->currentItem())->internalName();
1394
1395 insertActive(item: m_inactiveList->currentItem(), before: m_activeList->currentItem(), prepend: false);
1396 // we're modified, so let this change
1397 Q_EMIT m_widget->enableOk(true);
1398
1399 slotToolBarSelected(index: m_toolbarCombo->currentIndex());
1400
1401 selectActiveItem(internalName);
1402}
1403
1404void KEditToolBarWidgetPrivate::selectActiveItem(const QString &internalName)
1405{
1406 int activeItemCount = m_activeList->count();
1407 for (int i = 0; i < activeItemCount; i++) {
1408 ToolBarItem *item = static_cast<ToolBarItem *>(m_activeList->item(row: i));
1409 if (item->internalName() == internalName) {
1410 m_activeList->setCurrentItem(item);
1411 break;
1412 }
1413 }
1414}
1415
1416void KEditToolBarWidgetPrivate::slotRemoveButton()
1417{
1418 removeActive(item: m_activeList->currentItem());
1419
1420 slotToolBarSelected(index: m_toolbarCombo->currentIndex());
1421}
1422
1423void KEditToolBarWidgetPrivate::insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend)
1424{
1425 if (!item) {
1426 return;
1427 }
1428
1429 QDomElement new_item;
1430 // let's handle the separator and spacer specially
1431 if (item->isSeparator()) {
1432 new_item = m_widget->domDocument().createElement(QStringLiteral("Separator"));
1433 } else if (item->isSpacer()) {
1434 new_item = m_widget->domDocument().createElement(QStringLiteral("Spacer"));
1435 } else {
1436 new_item = m_widget->domDocument().createElement(QStringLiteral("Action"));
1437 }
1438
1439 new_item.setAttribute(QStringLiteral("name"), value: item->internalName());
1440
1441 Q_ASSERT(!m_currentToolBarElem.isNull());
1442
1443 if (before) {
1444 // we have the item in the active list which is before the new
1445 // item.. so let's try our best to add our new item right after it
1446 QDomElement elem = findElementForToolBarItem(item: before);
1447 Q_ASSERT(!elem.isNull());
1448 m_currentToolBarElem.insertAfter(newChild: new_item, refChild: elem);
1449 } else {
1450 // simply put it at the beginning or the end of the list.
1451 if (prepend) {
1452 m_currentToolBarElem.insertBefore(newChild: new_item, refChild: m_currentToolBarElem.firstChild());
1453 } else {
1454 m_currentToolBarElem.appendChild(newChild: new_item);
1455 }
1456 }
1457
1458 // and set this container as a noMerge
1459 m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1460
1461 // update the local doc
1462 updateLocal(elem&: m_currentToolBarElem);
1463}
1464
1465void KEditToolBarWidgetPrivate::removeActive(ToolBarItem *item)
1466{
1467 if (!item) {
1468 return;
1469 }
1470
1471 // we're modified, so let this change
1472 Q_EMIT m_widget->enableOk(true);
1473
1474 // now iterate through to find the child to nuke
1475 QDomElement elem = findElementForToolBarItem(item);
1476 if (!elem.isNull()) {
1477 // nuke myself!
1478 m_currentToolBarElem.removeChild(oldChild: elem);
1479
1480 // and set this container as a noMerge
1481 m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1482
1483 // update the local doc
1484 updateLocal(elem&: m_currentToolBarElem);
1485 }
1486}
1487
1488void KEditToolBarWidgetPrivate::slotUpButton()
1489{
1490 ToolBarItem *item = m_activeList->currentItem();
1491
1492 if (!item) {
1493 Q_ASSERT(false);
1494 return;
1495 }
1496
1497 int row = item->listWidget()->row(item) - 1;
1498 // make sure we're not the top item already
1499 if (row < 0) {
1500 Q_ASSERT(false);
1501 return;
1502 }
1503
1504 // we're modified, so let this change
1505 Q_EMIT m_widget->enableOk(true);
1506
1507 moveActive(item, before: static_cast<ToolBarItem *>(item->listWidget()->item(row: row - 1)));
1508}
1509
1510void KEditToolBarWidgetPrivate::moveActive(ToolBarItem *item, ToolBarItem *before)
1511{
1512 QDomElement e = findElementForToolBarItem(item);
1513
1514 if (e.isNull()) {
1515 return;
1516 }
1517
1518 // remove item
1519 m_activeList->takeItem(row: m_activeList->row(item));
1520
1521 // put it where it's supposed to go
1522 m_activeList->insertItem(row: m_activeList->row(item: before) + 1, item);
1523
1524 // make it selected again
1525 m_activeList->setCurrentItem(item);
1526
1527 // and do the real move in the DOM
1528 if (!before) {
1529 m_currentToolBarElem.insertBefore(newChild: e, refChild: m_currentToolBarElem.firstChild());
1530 } else {
1531 m_currentToolBarElem.insertAfter(newChild: e, refChild: findElementForToolBarItem(item: before));
1532 }
1533
1534 // and set this container as a noMerge
1535 m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1536
1537 // update the local doc
1538 updateLocal(elem&: m_currentToolBarElem);
1539}
1540
1541void KEditToolBarWidgetPrivate::slotDownButton()
1542{
1543 ToolBarItem *item = m_activeList->currentItem();
1544
1545 if (!item) {
1546 Q_ASSERT(false);
1547 return;
1548 }
1549
1550 // make sure we're not the bottom item already
1551 int newRow = item->listWidget()->row(item) + 1;
1552 if (newRow >= item->listWidget()->count()) {
1553 Q_ASSERT(false);
1554 return;
1555 }
1556
1557 // we're modified, so let this change
1558 Q_EMIT m_widget->enableOk(true);
1559
1560 moveActive(item, before: static_cast<ToolBarItem *>(item->listWidget()->item(row: newRow)));
1561}
1562
1563void KEditToolBarWidgetPrivate::updateLocal(QDomElement &elem)
1564{
1565 for (auto &xmlFile : m_xmlFiles) {
1566 if (xmlFile.type() == XmlData::Merged) {
1567 continue;
1568 }
1569
1570 if (xmlFile.type() == XmlData::Shell || xmlFile.type() == XmlData::Part) {
1571 if (m_currentXmlData->xmlFile() == xmlFile.xmlFile()) {
1572 xmlFile.m_isModified = true;
1573 return;
1574 }
1575
1576 continue;
1577 }
1578
1579 xmlFile.m_isModified = true;
1580 const QLatin1String attrName("name");
1581 for (const auto &bar : std::as_const(t&: xmlFile.barList())) {
1582 const QString name(bar.attribute(name: attrName));
1583 const QString tag(bar.tagName());
1584 if ((tag != elem.tagName()) || (name != elem.attribute(name: attrName))) {
1585 continue;
1586 }
1587
1588 QDomElement toolbar = xmlFile.domDocument().documentElement().toElement();
1589 toolbar.replaceChild(newChild: elem, oldChild: bar);
1590 return;
1591 }
1592
1593 // just append it
1594 QDomElement toolbar = xmlFile.domDocument().documentElement().toElement();
1595 Q_ASSERT(!toolbar.isNull());
1596 toolbar.appendChild(newChild: elem);
1597 }
1598}
1599
1600void KEditToolBarWidgetPrivate::slotChangeIcon()
1601{
1602 m_currentXmlData->dump();
1603 Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1604
1605 QString icon = KIconDialog::getIcon(group: KIconLoader::Toolbar,
1606 context: KIconLoader::Action,
1607 strictIconSize: false,
1608 iconSize: 0,
1609 user: false, // all defaults
1610 parent: m_widget,
1611 i18n("Change Icon"));
1612
1613 if (icon.isEmpty()) {
1614 return;
1615 }
1616
1617 ToolBarItem *item = m_activeList->currentItem();
1618 // qCDebug(DEBUG_KXMLGUI) << item;
1619 if (item) {
1620 item->setIcon(QIcon::fromTheme(name: icon));
1621
1622 m_currentXmlData->m_isModified = true;
1623
1624 // Get hold of ActionProperties tag
1625 QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc&: m_currentXmlData->domDocument());
1626 // Find or create an element for this action
1627 QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, sName: item->internalName(), create: true /*create*/);
1628 Q_ASSERT(!act_elem.isNull());
1629 act_elem.setAttribute(QStringLiteral("icon"), value: icon);
1630
1631 // we're modified, so let this change
1632 Q_EMIT m_widget->enableOk(true);
1633 }
1634}
1635
1636void KEditToolBarWidgetPrivate::slotChangeIconText()
1637{
1638 m_currentXmlData->dump();
1639 ToolBarItem *item = m_activeList->currentItem();
1640
1641 if (item) {
1642 QString iconText = item->text();
1643 bool hidden = item->isTextAlongsideIconHidden();
1644
1645 IconTextEditDialog dialog(m_widget);
1646 dialog.setIconText(iconText);
1647 dialog.setTextAlongsideIconHidden(hidden);
1648
1649 bool ok = dialog.exec() == QDialog::Accepted;
1650 iconText = dialog.iconText();
1651 hidden = dialog.textAlongsideIconHidden();
1652
1653 bool hiddenChanged = hidden != item->isTextAlongsideIconHidden();
1654 bool iconTextChanged = iconText != item->text();
1655
1656 if (!ok || (!hiddenChanged && !iconTextChanged)) {
1657 return;
1658 }
1659
1660 item->setText(iconText);
1661 item->setTextAlongsideIconHidden(hidden);
1662
1663 Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1664
1665 m_currentXmlData->m_isModified = true;
1666
1667 // Get hold of ActionProperties tag
1668 QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc&: m_currentXmlData->domDocument());
1669 // Find or create an element for this action
1670 QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, sName: item->internalName(), create: true /*create*/);
1671 Q_ASSERT(!act_elem.isNull());
1672 if (iconTextChanged) {
1673 act_elem.setAttribute(QStringLiteral("iconText"), value: iconText);
1674 }
1675 if (hiddenChanged) {
1676 act_elem.setAttribute(QStringLiteral("priority"), value: hidden ? QAction::LowPriority : QAction::NormalPriority);
1677 }
1678
1679 // we're modified, so let this change
1680 Q_EMIT m_widget->enableOk(true);
1681 }
1682}
1683
1684void KEditToolBarWidgetPrivate::slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList)
1685{
1686 // qCDebug(DEBUG_KXMLGUI) << "slotDropped list=" << (list==m_activeList?"activeList":"inactiveList")
1687 // << "index=" << index << "sourceIsActiveList=" << sourceIsActiveList;
1688 if (list == m_activeList) {
1689 ToolBarItem *after = index > 0 ? static_cast<ToolBarItem *>(list->item(row: index - 1)) : nullptr;
1690 // qCDebug(DEBUG_KXMLGUI) << "after" << after->text() << after->internalTag();
1691 if (sourceIsActiveList) {
1692 // has been dragged within the active list (moved).
1693 moveActive(item, before: after);
1694 } else {
1695 // dragged from the inactive list to the active list
1696 insertActive(item, before: after, prepend: true);
1697 }
1698 } else if (list == m_inactiveList) {
1699 // has been dragged to the inactive list -> remove from the active list.
1700 removeActive(item);
1701 }
1702
1703 delete item; // not needed anymore. must be deleted before slotToolBarSelected clears the lists
1704
1705 // we're modified, so let this change
1706 Q_EMIT m_widget->enableOk(true);
1707
1708 slotToolBarSelected(index: m_toolbarCombo->currentIndex());
1709}
1710
1711void KEditToolBar::showEvent(QShowEvent *event)
1712{
1713 if (!event->spontaneous()) {
1714 // The dialog has been shown, enable toolbar editing
1715 if (d->m_factory) {
1716 // call the xmlgui-factory version
1717 d->m_widget->load(factory: d->m_factory, defaultToolBar: d->m_defaultToolBar);
1718 } else {
1719 // call the action collection version
1720 d->m_widget->load(file: d->m_file, global: d->m_global, defaultToolBar: d->m_defaultToolBar);
1721 }
1722
1723 KToolBar::setToolBarsEditable(true);
1724 }
1725 QDialog::showEvent(event);
1726}
1727
1728void KEditToolBar::hideEvent(QHideEvent *event)
1729{
1730 // The dialog has been hidden, disable toolbar editing
1731 KToolBar::setToolBarsEditable(false);
1732
1733 QDialog::hideEvent(event);
1734}
1735
1736#include "moc_kedittoolbar.cpp"
1737#include "moc_kedittoolbar_p.cpp"
1738

source code of kxmlgui/src/kedittoolbar.cpp