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

source code of kxmlgui/src/kedittoolbar.cpp