File indexing completed on 2024-04-21 15:05:48

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0004     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0005     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "kedittoolbar.h"
0011 #include "debug.h"
0012 #include "kedittoolbar_p.h"
0013 
0014 #include <QAction>
0015 #include <QApplication>
0016 #include <QCheckBox>
0017 #include <QComboBox>
0018 #include <QDialogButtonBox>
0019 #include <QDir>
0020 #include <QDomDocument>
0021 #include <QFile>
0022 #include <QGridLayout>
0023 #include <QLabel>
0024 #include <QLineEdit>
0025 #include <QMimeData>
0026 #include <QPushButton>
0027 #include <QShowEvent>
0028 #include <QStandardPaths>
0029 #include <QToolButton>
0030 
0031 #include <KIconDialog>
0032 #include <KListWidgetSearchLine>
0033 #include <KLocalizedString>
0034 #include <KMessageBox>
0035 #include <KSeparator>
0036 
0037 #include "kactioncollection.h"
0038 #include "ktoolbar.h"
0039 #include "kxmlguifactory.h"
0040 
0041 #include "../kxmlgui_version.h"
0042 #include "ktoolbarhelper_p.h"
0043 
0044 // static const char *const s_XmlTypeToString[] = { "Shell", "Part", "Local", "Merged" };
0045 
0046 typedef QList<QDomElement> ToolBarList;
0047 
0048 namespace KDEPrivate
0049 {
0050 /**
0051  * Return a list of toolbar elements given a toplevel element
0052  */
0053 static ToolBarList findToolBars(const QDomElement &start)
0054 {
0055     ToolBarList list;
0056 
0057     for (QDomElement elem = start; !elem.isNull(); elem = elem.nextSiblingElement()) {
0058         if (elem.tagName() == QLatin1String("ToolBar")) {
0059             if (elem.attribute(QStringLiteral("noEdit")) != QLatin1String("true")) {
0060                 list.append(elem);
0061             }
0062         } else {
0063             if (elem.tagName() != QLatin1String("MenuBar")) { // there are no toolbars inside the menubar :)
0064                 list += findToolBars(elem.firstChildElement()); // recursive
0065             }
0066         }
0067     }
0068 
0069     return list;
0070 }
0071 
0072 class XmlData
0073 {
0074 public:
0075     enum XmlType { Shell = 0, Part, Local, Merged };
0076 
0077     explicit XmlData(XmlType xmlType, const QString &xmlFile, KActionCollection *collection)
0078         : m_isModified(false)
0079         , m_xmlFile(xmlFile)
0080         , m_type(xmlType)
0081         , m_actionCollection(collection)
0082     {
0083     }
0084     void dump() const
0085     {
0086 #if 0
0087         qDebug(240) << "XmlData" << this << "type" << s_XmlTypeToString[m_type] << "xmlFile:" << m_xmlFile;
0088         foreach (const QDomElement &element, m_barList) {
0089             qDebug(240) << "    ToolBar:" << toolBarText(element);
0090         }
0091         if (m_actionCollection) {
0092             qDebug(240) << "    " << m_actionCollection->actions().count() << "actions in the collection.";
0093         } else {
0094             qDebug(240) << "    no action collection.";
0095         }
0096 #endif
0097     }
0098     QString xmlFile() const
0099     {
0100         return m_xmlFile;
0101     }
0102     XmlType type() const
0103     {
0104         return m_type;
0105     }
0106     KActionCollection *actionCollection() const
0107     {
0108         return m_actionCollection;
0109     }
0110     void setDomDocument(const QDomDocument &domDoc)
0111     {
0112         m_document = domDoc.cloneNode().toDocument();
0113         m_barList = findToolBars(m_document.documentElement());
0114     }
0115     // Return reference, for e.g. actionPropertiesElement() to modify the document
0116     QDomDocument &domDocument()
0117     {
0118         return m_document;
0119     }
0120     const QDomDocument &domDocument() const
0121     {
0122         return m_document;
0123     }
0124 
0125     /**
0126      * Return the text (user-visible name) of a given toolbar
0127      */
0128     QString toolBarText(const QDomElement &it) const;
0129 
0130     bool m_isModified = false;
0131     ToolBarList &barList()
0132     {
0133         return m_barList;
0134     }
0135     const ToolBarList &barList() const
0136     {
0137         return m_barList;
0138     }
0139 
0140 private:
0141     ToolBarList m_barList;
0142     QString m_xmlFile;
0143     QDomDocument m_document;
0144     XmlType m_type;
0145     KActionCollection *m_actionCollection;
0146 };
0147 
0148 QString XmlData::toolBarText(const QDomElement &it) const
0149 {
0150     QString name = KToolbarHelper::i18nToolBarName(it);
0151 
0152     // the name of the toolbar might depend on whether or not
0153     // it is in kparts
0154     if ((m_type == XmlData::Shell) || (m_type == XmlData::Part)) {
0155         QString doc_name(m_document.documentElement().attribute(QStringLiteral("name")));
0156         name += QLatin1String(" <") + doc_name + QLatin1Char('>');
0157     }
0158     return name;
0159 }
0160 
0161 typedef QList<XmlData> XmlDataList;
0162 
0163 class ToolBarItem : public QListWidgetItem
0164 {
0165 public:
0166     ToolBarItem(QListWidget *parent, const QString &tag = QString(), const QString &name = QString(), const QString &statusText = QString())
0167         : QListWidgetItem(parent)
0168         , m_internalTag(tag)
0169         , m_internalName(name)
0170         , m_statusText(statusText)
0171         , m_isSeparator(false)
0172         , m_isSpacer(false)
0173         , m_isTextAlongsideIconHidden(false)
0174     {
0175         // Drop between items, not onto items
0176         setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled);
0177     }
0178 
0179     void setInternalTag(const QString &tag)
0180     {
0181         m_internalTag = tag;
0182     }
0183     void setInternalName(const QString &name)
0184     {
0185         m_internalName = name;
0186     }
0187     void setStatusText(const QString &text)
0188     {
0189         m_statusText = text;
0190     }
0191     void setSeparator(bool sep)
0192     {
0193         m_isSeparator = sep;
0194     }
0195     void setSpacer(bool spacer)
0196     {
0197         m_isSpacer = spacer;
0198     }
0199     void setTextAlongsideIconHidden(bool hidden)
0200     {
0201         m_isTextAlongsideIconHidden = hidden;
0202     }
0203     QString internalTag() const
0204     {
0205         return m_internalTag;
0206     }
0207     QString internalName() const
0208     {
0209         return m_internalName;
0210     }
0211     QString statusText() const
0212     {
0213         return m_statusText;
0214     }
0215     bool isSeparator() const
0216     {
0217         return m_isSeparator;
0218     }
0219     bool isSpacer() const
0220     {
0221         return m_isSpacer;
0222     }
0223     bool isTextAlongsideIconHidden() const
0224     {
0225         return m_isTextAlongsideIconHidden;
0226     }
0227 
0228     int index() const
0229     {
0230         return listWidget()->row(const_cast<ToolBarItem *>(this));
0231     }
0232 
0233 private:
0234     QString m_internalTag;
0235     QString m_internalName;
0236     QString m_statusText;
0237     bool m_isSeparator;
0238     bool m_isSpacer;
0239     bool m_isTextAlongsideIconHidden;
0240 };
0241 
0242 static QDataStream &operator<<(QDataStream &s, const ToolBarItem &item)
0243 {
0244     s << item.internalTag();
0245     s << item.internalName();
0246     s << item.statusText();
0247     s << item.isSeparator();
0248     s << item.isSpacer();
0249     s << item.isTextAlongsideIconHidden();
0250     return s;
0251 }
0252 static QDataStream &operator>>(QDataStream &s, ToolBarItem &item)
0253 {
0254     QString internalTag;
0255     s >> internalTag;
0256     item.setInternalTag(internalTag);
0257     QString internalName;
0258     s >> internalName;
0259     item.setInternalName(internalName);
0260     QString statusText;
0261     s >> statusText;
0262     item.setStatusText(statusText);
0263     bool sep;
0264     s >> sep;
0265     item.setSeparator(sep);
0266     bool spacer;
0267     s >> spacer;
0268     item.setSpacer(spacer);
0269     bool hidden;
0270     s >> hidden;
0271     item.setTextAlongsideIconHidden(hidden);
0272     return s;
0273 }
0274 
0275 ////
0276 
0277 ToolBarListWidget::ToolBarListWidget(QWidget *parent)
0278     : QListWidget(parent)
0279     , m_activeList(true)
0280 {
0281     setDragDropMode(QAbstractItemView::DragDrop); // no internal moves
0282 }
0283 
0284 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0285 QMimeData *ToolBarListWidget::mimeData(const QList<QListWidgetItem *> items) const
0286 #else
0287 QMimeData *ToolBarListWidget::mimeData(const QList<QListWidgetItem *> &items) const
0288 #endif
0289 {
0290     if (items.isEmpty()) {
0291         return nullptr;
0292     }
0293     QMimeData *mimedata = new QMimeData();
0294 
0295     QByteArray data;
0296     {
0297         QDataStream stream(&data, QIODevice::WriteOnly);
0298         // we only support single selection
0299         ToolBarItem *item = static_cast<ToolBarItem *>(items.first());
0300         stream << *item;
0301     }
0302 
0303     mimedata->setData(QStringLiteral("application/x-kde-action-list"), data);
0304     mimedata->setData(QStringLiteral("application/x-kde-source-treewidget"), m_activeList ? "active" : "inactive");
0305 
0306     return mimedata;
0307 }
0308 
0309 bool ToolBarListWidget::dropMimeData(int index, const QMimeData *mimeData, Qt::DropAction action)
0310 {
0311     Q_UNUSED(action)
0312     const QByteArray data = mimeData->data(QStringLiteral("application/x-kde-action-list"));
0313     if (data.isEmpty()) {
0314         return false;
0315     }
0316     QDataStream stream(data);
0317     const bool sourceIsActiveList = mimeData->data(QStringLiteral("application/x-kde-source-treewidget")) == "active";
0318     ToolBarItem *item = new ToolBarItem(this); // needs parent, use this temporarily
0319     stream >> *item;
0320     Q_EMIT dropped(this, index, item, sourceIsActiveList);
0321     return true;
0322 }
0323 
0324 ToolBarItem *ToolBarListWidget::currentItem() const
0325 {
0326     return static_cast<ToolBarItem *>(QListWidget::currentItem());
0327 }
0328 
0329 IconTextEditDialog::IconTextEditDialog(QWidget *parent)
0330     : QDialog(parent)
0331 {
0332     setWindowTitle(i18nc("@title:window", "Change Text"));
0333     setModal(true);
0334 
0335     QVBoxLayout *layout = new QVBoxLayout(this);
0336 
0337     QGridLayout *grid = new QGridLayout;
0338     grid->setContentsMargins(0, 0, 0, 0);
0339 
0340     m_lineEdit = new QLineEdit(this);
0341     m_lineEdit->setClearButtonEnabled(true);
0342     QLabel *label = new QLabel(i18n("Icon te&xt:"), this);
0343     label->setBuddy(m_lineEdit);
0344     grid->addWidget(label, 0, 0);
0345     grid->addWidget(m_lineEdit, 0, 1);
0346 
0347     m_cbHidden = new QCheckBox(i18nc("@option:check", "&Hide text when toolbar shows text alongside icons"), this);
0348     grid->addWidget(m_cbHidden, 1, 1);
0349 
0350     layout->addLayout(grid);
0351 
0352     m_buttonBox = new QDialogButtonBox(this);
0353     m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0354     connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0355     connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0356     layout->addWidget(m_buttonBox);
0357 
0358     connect(m_lineEdit, &QLineEdit::textChanged, this, &IconTextEditDialog::slotTextChanged);
0359 
0360     m_lineEdit->setFocus();
0361     setFixedHeight(sizeHint().height());
0362 }
0363 
0364 void IconTextEditDialog::setIconText(const QString &text)
0365 {
0366     m_lineEdit->setText(text);
0367 }
0368 
0369 QString IconTextEditDialog::iconText() const
0370 {
0371     return m_lineEdit->text().trimmed();
0372 }
0373 
0374 void IconTextEditDialog::setTextAlongsideIconHidden(bool hidden)
0375 {
0376     m_cbHidden->setChecked(hidden);
0377 }
0378 
0379 bool IconTextEditDialog::textAlongsideIconHidden() const
0380 {
0381     return m_cbHidden->isChecked();
0382 }
0383 
0384 void IconTextEditDialog::slotTextChanged(const QString &text)
0385 {
0386     // Do not allow empty icon text
0387     m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.trimmed().isEmpty());
0388 }
0389 
0390 class KEditToolBarWidgetPrivate
0391 {
0392 public:
0393     /**
0394      *
0395      * @param collection In the old-style constructor, this is the collection passed
0396      * to the KEditToolBar constructor.
0397      * In the xmlguifactory-based constructor, we let KXMLGUIClient create a dummy one,
0398      * but it probably isn't used.
0399      */
0400     KEditToolBarWidgetPrivate(KEditToolBarWidget *widget, const QString &cName, KActionCollection *collection)
0401         : m_collection(collection)
0402         , m_widget(widget)
0403         , m_factory(nullptr)
0404         , m_componentName(cName)
0405         , m_helpArea(nullptr)
0406         , m_isPart(false)
0407         , m_loadedOnce(false)
0408     {
0409         // We want items with an icon to align with items without icon
0410         // So we use an empty QPixmap for that
0411         const int iconSize = widget->style()->pixelMetric(QStyle::PM_SmallIconSize);
0412         m_emptyIcon = QPixmap(iconSize, iconSize);
0413         m_emptyIcon.fill(Qt::transparent);
0414     }
0415     ~KEditToolBarWidgetPrivate()
0416     {
0417     }
0418 
0419     // private slots
0420     void slotToolBarSelected(int index);
0421 
0422     void slotInactiveSelectionChanged();
0423     void slotActiveSelectionChanged();
0424 
0425     void slotInsertButton();
0426     void slotRemoveButton();
0427     void slotUpButton();
0428     void slotDownButton();
0429 
0430     void selectActiveItem(const QString &);
0431 
0432     void slotChangeIcon();
0433     void slotChangeIconText();
0434 
0435     void slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList);
0436 
0437     void setupLayout();
0438 
0439     void initOldStyle(const QString &file, bool global, const QString &defaultToolbar);
0440     void initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolbar);
0441     void loadToolBarCombo(const QString &defaultToolbar);
0442     void loadActions(const QDomElement &elem);
0443 
0444     QString xmlFile(const QString &xml_file) const
0445     {
0446         return xml_file.isEmpty() ? m_componentName + QLatin1String("ui.rc") : xml_file;
0447     }
0448 
0449     /**
0450      * Load in the specified XML file and dump the raw xml
0451      */
0452     QString loadXMLFile(const QString &_xml_file)
0453     {
0454         QString raw_xml;
0455         QString xml_file = xmlFile(_xml_file);
0456         // qCDebug(DEBUG_KXMLGUI) << "loadXMLFile xml_file=" << xml_file;
0457 
0458         if (!QDir::isRelativePath(xml_file)) {
0459             raw_xml = KXMLGUIFactory::readConfigFile(xml_file);
0460         } else {
0461             raw_xml = KXMLGUIFactory::readConfigFile(xml_file, m_componentName);
0462         }
0463 
0464         return raw_xml;
0465     }
0466 
0467     /**
0468      * Look for a given item in the current toolbar
0469      */
0470     QDomElement findElementForToolBarItem(const ToolBarItem *item) const
0471     {
0472         // qDebug(240) << "looking for name=" << item->internalName() << "and tag=" << item->internalTag();
0473         for (QDomNode n = m_currentToolBarElem.firstChild(); !n.isNull(); n = n.nextSibling()) {
0474             QDomElement elem = n.toElement();
0475             if ((elem.attribute(QStringLiteral("name")) == item->internalName()) && (elem.tagName() == item->internalTag())) {
0476                 return elem;
0477             }
0478         }
0479         // qDebug(240) << "no item found in the DOM with name=" << item->internalName() << "and tag=" << item->internalTag();
0480         return QDomElement();
0481     }
0482 
0483     void insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend = false);
0484     void removeActive(ToolBarItem *item);
0485     void moveActive(ToolBarItem *item, ToolBarItem *before);
0486     void updateLocal(QDomElement &elem);
0487 
0488 #ifndef NDEBUG
0489     void dump() const
0490     {
0491         for (const auto &xmlFile : m_xmlFiles) {
0492             xmlFile.dump();
0493         }
0494     }
0495 #endif
0496 
0497     QComboBox *m_toolbarCombo;
0498 
0499     QToolButton *m_upAction;
0500     QToolButton *m_removeAction;
0501     QToolButton *m_insertAction;
0502     QToolButton *m_downAction;
0503 
0504     // QValueList<QAction*> m_actionList;
0505     KActionCollection *m_collection;
0506     KEditToolBarWidget *const m_widget;
0507     KXMLGUIFactory *m_factory;
0508     QString m_componentName;
0509 
0510     QPixmap m_emptyIcon;
0511 
0512     XmlData *m_currentXmlData;
0513     QDomElement m_currentToolBarElem;
0514 
0515     QString m_xmlFile;
0516     QString m_globalFile;
0517     QString m_rcFile;
0518     QDomDocument m_localDoc;
0519 
0520     ToolBarList m_barList;
0521     ToolBarListWidget *m_inactiveList;
0522     ToolBarListWidget *m_activeList;
0523 
0524     XmlDataList m_xmlFiles;
0525 
0526     QLabel *m_comboLabel;
0527     KSeparator *m_comboSeparator;
0528     QLabel *m_helpArea;
0529     QPushButton *m_changeIcon;
0530     QPushButton *m_changeIconText;
0531     bool m_isPart : 1;
0532     bool m_loadedOnce : 1;
0533 };
0534 
0535 }
0536 
0537 using namespace KDEPrivate;
0538 
0539 class KEditToolBarPrivate
0540 {
0541 public:
0542     KEditToolBarPrivate(KEditToolBar *qq)
0543         : q(qq)
0544     {
0545     }
0546 
0547     void init();
0548 
0549     void slotButtonClicked(QAbstractButton *button);
0550     void acceptOK(bool);
0551     void enableApply(bool);
0552     void okClicked();
0553     void applyClicked();
0554     void defaultClicked();
0555 
0556     KEditToolBar *const q;
0557     bool m_accept = false;
0558     // Save parameters for recreating widget after resetting toolbar
0559     bool m_global = false;
0560     KActionCollection *m_collection = nullptr;
0561     QString m_file;
0562     QString m_defaultToolBar;
0563     KXMLGUIFactory *m_factory = nullptr;
0564     KEditToolBarWidget *m_widget = nullptr;
0565     QVBoxLayout *m_layout = nullptr;
0566     QDialogButtonBox *m_buttonBox = nullptr;
0567 };
0568 
0569 Q_GLOBAL_STATIC(QString, s_defaultToolBarName)
0570 
0571 KEditToolBar::KEditToolBar(KActionCollection *collection, QWidget *parent)
0572     : QDialog(parent)
0573     , d(new KEditToolBarPrivate(this))
0574 {
0575     d->m_widget = new KEditToolBarWidget(collection, this);
0576     d->init();
0577     d->m_collection = collection;
0578 }
0579 
0580 KEditToolBar::KEditToolBar(KXMLGUIFactory *factory, QWidget *parent)
0581     : QDialog(parent)
0582     , d(new KEditToolBarPrivate(this))
0583 {
0584     d->m_widget = new KEditToolBarWidget(this);
0585     d->init();
0586     d->m_factory = factory;
0587 }
0588 
0589 void KEditToolBarPrivate::init()
0590 {
0591     m_accept = false;
0592     m_factory = nullptr;
0593 
0594     q->setDefaultToolBar(QString());
0595 
0596     q->setWindowTitle(i18nc("@title:window", "Configure Toolbars"));
0597     q->setModal(false);
0598 
0599     m_layout = new QVBoxLayout(q);
0600     m_layout->addWidget(m_widget);
0601 
0602     m_buttonBox = new QDialogButtonBox(q);
0603     m_buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
0604     KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
0605     KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply());
0606     KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
0607     KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
0608     q->connect(m_buttonBox, &QDialogButtonBox::clicked, q, [this](QAbstractButton *button) {
0609         slotButtonClicked(button);
0610     });
0611     QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
0612     m_layout->addWidget(m_buttonBox);
0613 
0614     q->connect(m_widget, &KEditToolBarWidget::enableOk, q, [this](bool state) {
0615         acceptOK(state);
0616         enableApply(state);
0617     });
0618     enableApply(false);
0619 
0620     q->setMinimumSize(q->sizeHint());
0621 }
0622 
0623 void KEditToolBar::setResourceFile(const QString &file, bool global)
0624 {
0625     d->m_file = file;
0626     d->m_global = global;
0627     d->m_widget->load(d->m_file, d->m_global, d->m_defaultToolBar);
0628 }
0629 
0630 KEditToolBar::~KEditToolBar()
0631 {
0632     s_defaultToolBarName()->clear();
0633 }
0634 
0635 void KEditToolBar::setDefaultToolBar(const QString &toolBarName)
0636 {
0637     if (toolBarName.isEmpty()) {
0638         d->m_defaultToolBar = *s_defaultToolBarName();
0639     } else {
0640         d->m_defaultToolBar = toolBarName;
0641     }
0642 }
0643 
0644 void KEditToolBarPrivate::acceptOK(bool b)
0645 {
0646     m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(b);
0647     m_accept = b;
0648 }
0649 
0650 void KEditToolBarPrivate::enableApply(bool b)
0651 {
0652     m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(b);
0653 }
0654 
0655 void KEditToolBarPrivate::defaultClicked()
0656 {
0657     if (KMessageBox::warningContinueCancel(
0658             q,
0659             i18n("Do you really want to reset all toolbars of this application to their default? The changes will be applied immediately."),
0660             i18n("Reset Toolbars"),
0661             KGuiItem(i18n("Reset")))
0662         != KMessageBox::Continue) {
0663         return;
0664     }
0665 
0666     KEditToolBarWidget *oldWidget = m_widget;
0667     m_widget = nullptr;
0668     m_accept = false;
0669 
0670     if (m_factory) {
0671         const auto clients = m_factory->clients();
0672         for (KXMLGUIClient *client : clients) {
0673             const QString file = client->localXMLFile();
0674             if (file.isEmpty()) {
0675                 continue;
0676             }
0677             // qDebug(240) << "Deleting local xml file" << file;
0678             // << "for client" << client << typeid(*client).name();
0679             if (QFile::exists(file)) {
0680                 if (!QFile::remove(file)) {
0681                     qCWarning(DEBUG_KXMLGUI) << "Could not delete" << file;
0682                 }
0683             }
0684         }
0685 
0686         // Reload the xml files in all clients, now that the local files are gone
0687         oldWidget->rebuildKXMLGUIClients();
0688 
0689         m_widget = new KEditToolBarWidget(q);
0690         m_widget->load(m_factory, m_defaultToolBar);
0691     } else {
0692         int slash = m_file.lastIndexOf(QLatin1Char('/')) + 1;
0693         if (slash) {
0694             m_file.remove(0, slash);
0695         }
0696         const QString xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/")
0697             + QCoreApplication::instance()->applicationName() + QLatin1Char('/') + m_file;
0698 
0699         if (QFile::exists(xml_file)) {
0700             if (!QFile::remove(xml_file)) {
0701                 qCWarning(DEBUG_KXMLGUI) << "Could not delete " << xml_file;
0702             }
0703         }
0704 
0705         m_widget = new KEditToolBarWidget(m_collection, q);
0706         q->setResourceFile(m_file, m_global);
0707     }
0708 
0709     // Copy the geometry to minimize UI flicker
0710     m_widget->setGeometry(oldWidget->geometry());
0711     delete oldWidget;
0712     m_layout->insertWidget(0, m_widget);
0713 
0714     q->connect(m_widget, &KEditToolBarWidget::enableOk, q, [this](bool state) {
0715         acceptOK(state);
0716         enableApply(state);
0717     });
0718     enableApply(false);
0719 
0720     Q_EMIT q->newToolBarConfig();
0721 #if KXMLGUI_BUILD_DEPRECATED_SINCE(4, 0)
0722     Q_EMIT q->newToolbarConfig(); // compat
0723 #endif
0724 }
0725 
0726 void KEditToolBarPrivate::slotButtonClicked(QAbstractButton *button)
0727 {
0728     QDialogButtonBox::StandardButton type = m_buttonBox->standardButton(button);
0729 
0730     switch (type) {
0731     case QDialogButtonBox::Ok:
0732         okClicked();
0733         break;
0734     case QDialogButtonBox::Apply:
0735         applyClicked();
0736         break;
0737     case QDialogButtonBox::RestoreDefaults:
0738         defaultClicked();
0739         break;
0740     default:
0741         break;
0742     }
0743 }
0744 
0745 void KEditToolBarPrivate::okClicked()
0746 {
0747     if (!m_accept) {
0748         q->reject();
0749         return;
0750     }
0751 
0752     // Do not rebuild GUI and emit the "newToolBarConfig" signal again here if the "Apply"
0753     // button was already pressed and no further changes were made.
0754     if (m_buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) {
0755         m_widget->save();
0756         Q_EMIT q->newToolBarConfig();
0757 #if KXMLGUI_BUILD_DEPRECATED_SINCE(4, 0)
0758         Q_EMIT q->newToolbarConfig(); // compat
0759 #endif
0760     }
0761     q->accept();
0762 }
0763 
0764 void KEditToolBarPrivate::applyClicked()
0765 {
0766     (void)m_widget->save();
0767     enableApply(false);
0768     Q_EMIT q->newToolBarConfig();
0769 #if KXMLGUI_BUILD_DEPRECATED_SINCE(4, 0)
0770     Q_EMIT q->newToolbarConfig(); // compat
0771 #endif
0772 }
0773 
0774 void KEditToolBar::setGlobalDefaultToolBar(const char *toolbarName)
0775 {
0776     *s_defaultToolBarName() = QString::fromLatin1(toolbarName);
0777 }
0778 
0779 KEditToolBarWidget::KEditToolBarWidget(KActionCollection *collection, QWidget *parent)
0780     : QWidget(parent)
0781     , d(new KEditToolBarWidgetPrivate(this, componentName(), collection))
0782 {
0783     d->setupLayout();
0784 }
0785 
0786 KEditToolBarWidget::KEditToolBarWidget(QWidget *parent)
0787     : QWidget(parent)
0788     , d(new KEditToolBarWidgetPrivate(this, componentName(), KXMLGUIClient::actionCollection() /*create new one*/))
0789 {
0790     d->setupLayout();
0791 }
0792 
0793 KEditToolBarWidget::~KEditToolBarWidget()
0794 {
0795     delete d;
0796 }
0797 
0798 void KEditToolBarWidget::load(const QString &file, bool global, const QString &defaultToolBar)
0799 {
0800     d->initOldStyle(file, global, defaultToolBar);
0801 }
0802 
0803 void KEditToolBarWidget::load(KXMLGUIFactory *factory, const QString &defaultToolBar)
0804 {
0805     d->initFromFactory(factory, defaultToolBar);
0806 }
0807 
0808 void KEditToolBarWidgetPrivate::initOldStyle(const QString &resourceFile, bool global, const QString &defaultToolBar)
0809 {
0810     // TODO: make sure we can call this multiple times?
0811     if (m_loadedOnce) {
0812         return;
0813     }
0814 
0815     m_loadedOnce = true;
0816     // d->m_actionList = collection->actions();
0817 
0818     // handle the merging
0819     if (global) {
0820         m_widget->loadStandardsXmlFile(); // ui_standards.rc
0821     }
0822     const QString localXML = loadXMLFile(resourceFile);
0823     m_widget->setXML(localXML, global ? true /*merge*/ : false);
0824 
0825     // first, get all of the necessary info for our local xml
0826     XmlData local(XmlData::Local, xmlFile(resourceFile), m_collection);
0827     QDomDocument domDoc;
0828     domDoc.setContent(localXML);
0829     local.setDomDocument(domDoc);
0830     m_xmlFiles.append(local);
0831 
0832     // then, the merged one (ui_standards + local xml)
0833     XmlData merge(XmlData::Merged, QString(), m_collection);
0834     merge.setDomDocument(m_widget->domDocument());
0835     m_xmlFiles.append(merge);
0836 
0837 #ifndef NDEBUG
0838     dump();
0839 #endif
0840 
0841     // now load in our toolbar combo box
0842     loadToolBarCombo(defaultToolBar);
0843     m_widget->adjustSize();
0844     m_widget->setMinimumSize(m_widget->sizeHint());
0845 }
0846 
0847 void KEditToolBarWidgetPrivate::initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolBar)
0848 {
0849     // TODO: make sure we can call this multiple times?
0850     if (m_loadedOnce) {
0851         return;
0852     }
0853 
0854     m_loadedOnce = true;
0855 
0856     m_factory = factory;
0857 
0858     // add all of the client data
0859     bool first = true;
0860     const auto clients = factory->clients();
0861     for (KXMLGUIClient *client : clients) {
0862         if (client->xmlFile().isEmpty()) {
0863             continue;
0864         }
0865 
0866         XmlData::XmlType type = XmlData::Part;
0867         if (first) {
0868             type = XmlData::Shell;
0869             first = false;
0870             Q_ASSERT(!client->localXMLFile().isEmpty()); // where would we save changes??
0871         }
0872 
0873         XmlData data(type, client->localXMLFile(), client->actionCollection());
0874         QDomDocument domDoc = client->domDocument();
0875         data.setDomDocument(domDoc);
0876         m_xmlFiles.append(data);
0877 
0878         // d->m_actionList += client->actionCollection()->actions();
0879     }
0880 
0881 #ifndef NDEBUG
0882     // d->dump();
0883 #endif
0884 
0885     // now load in our toolbar combo box
0886     loadToolBarCombo(defaultToolBar);
0887     m_widget->adjustSize();
0888     m_widget->setMinimumSize(m_widget->sizeHint());
0889 
0890     m_widget->actionCollection()->addAssociatedWidget(m_widget);
0891     const auto widgetActions = m_widget->actionCollection()->actions();
0892     for (QAction *action : widgetActions) {
0893         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0894     }
0895 }
0896 
0897 void KEditToolBarWidget::save()
0898 {
0899     // qDebug(240) << "KEditToolBarWidget::save";
0900     for (const auto &xmlFile : std::as_const(d->m_xmlFiles)) {
0901         // let's not save non-modified files
0902         if (!xmlFile.m_isModified) {
0903             continue;
0904         }
0905 
0906         // let's also skip (non-existent) merged files
0907         if (xmlFile.type() == XmlData::Merged) {
0908             continue;
0909         }
0910 
0911         // Add noMerge="1" to all the menus since we are saving the merged data
0912         QDomNodeList menuNodes = xmlFile.domDocument().elementsByTagName(QStringLiteral("Menu"));
0913         for (int i = 0; i < menuNodes.length(); ++i) {
0914             QDomNode menuNode = menuNodes.item(i);
0915             QDomElement menuElement = menuNode.toElement();
0916             if (menuElement.isNull()) {
0917                 continue;
0918             }
0919             menuElement.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
0920         }
0921 
0922         // qCDebug(DEBUG_KXMLGUI) << (*it).domDocument().toString();
0923 
0924         // qDebug(240) << "Saving " << (*it).xmlFile();
0925         // if we got this far, we might as well just save it
0926         KXMLGUIFactory::saveConfigFile(xmlFile.domDocument(), xmlFile.xmlFile());
0927     }
0928 
0929     if (!d->m_factory) {
0930         return;
0931     }
0932 
0933     rebuildKXMLGUIClients();
0934 }
0935 
0936 void KEditToolBarWidget::rebuildKXMLGUIClients()
0937 {
0938     if (!d->m_factory) {
0939         return;
0940     }
0941 
0942     const QList<KXMLGUIClient *> clients = d->m_factory->clients();
0943     // qDebug() << "factory: " << clients.count() << " clients";
0944 
0945     if (clients.isEmpty()) {
0946         return;
0947     }
0948 
0949     // remove the elements starting from the last going to the first
0950     for (auto it = clients.crbegin(); it != clients.crend(); ++it) {
0951         // qDebug() << "factory->removeClient " << client;
0952         d->m_factory->removeClient(*it);
0953     }
0954 
0955     KXMLGUIClient *firstClient = clients.first();
0956 
0957     // now, rebuild the gui from the first to the last
0958     // qDebug(240) << "rebuilding the gui";
0959     for (KXMLGUIClient *client : clients) {
0960         // qDebug(240) << "updating client " << client << " " << client->componentName() << "  xmlFile=" << client->xmlFile();
0961         QString file(client->xmlFile()); // before setting ui_standards!
0962         if (!file.isEmpty()) {
0963             // passing an empty stream forces the clients to reread the XML
0964             client->setXMLGUIBuildDocument(QDomDocument());
0965 
0966             // for the shell, merge in ui_standards.rc
0967             if (client == firstClient) { // same assumption as in the ctor: first==shell
0968                 client->loadStandardsXmlFile();
0969             }
0970 
0971             // and this forces it to use the *new* XML file
0972             client->setXMLFile(file, client == firstClient /* merge if shell */);
0973 
0974             // [we can't use reloadXML, it doesn't load ui_standards.rc]
0975         }
0976     }
0977 
0978     // Now we can add the clients to the factory
0979     // We don't do it in the loop above because adding a part automatically
0980     // adds its plugins, so we must make sure the plugins were updated first.
0981     for (KXMLGUIClient *client : clients) {
0982         d->m_factory->addClient(client);
0983     }
0984 }
0985 
0986 void KEditToolBarWidgetPrivate::setupLayout()
0987 {
0988     // the toolbar name combo
0989     m_comboLabel = new QLabel(i18n("&Toolbar:"), m_widget);
0990     m_toolbarCombo = new QComboBox(m_widget);
0991     m_comboLabel->setBuddy(m_toolbarCombo);
0992     m_comboSeparator = new KSeparator(m_widget);
0993     QObject::connect(m_toolbarCombo, qOverload<int>(&QComboBox::activated), m_widget, [this](int index) {
0994         slotToolBarSelected(index);
0995     });
0996 
0997     //  QPushButton *new_toolbar = new QPushButton(i18n("&New"), this);
0998     //  new_toolbar->setPixmap(BarIcon("document-new", KIconLoader::SizeSmall));
0999     //  new_toolbar->setEnabled(false); // disabled until implemented
1000     //  QPushButton *del_toolbar = new QPushButton(i18n("&Delete"), this);
1001     //  del_toolbar->setPixmap(BarIcon("edit-delete", KIconLoader::SizeSmall));
1002     //  del_toolbar->setEnabled(false); // disabled until implemented
1003 
1004     // our list of inactive actions
1005     QLabel *inactive_label = new QLabel(i18n("A&vailable actions:"), m_widget);
1006     m_inactiveList = new ToolBarListWidget(m_widget);
1007     m_inactiveList->setDragEnabled(true);
1008     m_inactiveList->setActiveList(false);
1009     m_inactiveList->setMinimumSize(180, 200);
1010     m_inactiveList->setDropIndicatorShown(false); // #165663
1011     inactive_label->setBuddy(m_inactiveList);
1012     QObject::connect(m_inactiveList, &QListWidget::itemSelectionChanged, m_widget, [this]() {
1013         slotInactiveSelectionChanged();
1014     });
1015     QObject::connect(m_inactiveList, &QListWidget::itemDoubleClicked, m_widget, [this]() {
1016         slotInsertButton();
1017     });
1018     QObject::connect(m_inactiveList,
1019                      &ToolBarListWidget::dropped,
1020                      m_widget,
1021                      [this](ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList) {
1022                          slotDropped(list, index, item, sourceIsActiveList);
1023                      });
1024 
1025     KListWidgetSearchLine *inactiveListSearchLine = new KListWidgetSearchLine(m_widget, m_inactiveList);
1026     inactiveListSearchLine->setPlaceholderText(i18n("Filter"));
1027 
1028     // our list of active actions
1029     QLabel *active_label = new QLabel(i18n("Curr&ent actions:"), m_widget);
1030     m_activeList = new ToolBarListWidget(m_widget);
1031     m_activeList->setDragEnabled(true);
1032     m_activeList->setActiveList(true);
1033     // With Qt-4.1 only setting MiniumWidth results in a 0-width icon column ...
1034     m_activeList->setMinimumSize(m_inactiveList->minimumWidth(), 100);
1035     active_label->setBuddy(m_activeList);
1036 
1037     QObject::connect(m_activeList, &QListWidget::itemSelectionChanged, m_widget, [this]() {
1038         slotActiveSelectionChanged();
1039     });
1040     QObject::connect(m_activeList, &QListWidget::itemDoubleClicked, m_widget, [this]() {
1041         slotRemoveButton();
1042     });
1043     QObject::connect(m_activeList,
1044                      &ToolBarListWidget::dropped,
1045                      m_widget,
1046                      [this](ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList) {
1047                          slotDropped(list, index, item, sourceIsActiveList);
1048                      });
1049 
1050     KListWidgetSearchLine *activeListSearchLine = new KListWidgetSearchLine(m_widget, m_activeList);
1051     activeListSearchLine->setPlaceholderText(i18n("Filter"));
1052 
1053     // "change icon" button
1054     m_changeIcon = new QPushButton(i18nc("@action:button", "Change &Icon..."), m_widget);
1055     m_changeIcon->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-icons")));
1056     m_changeIcon->setEnabled(m_activeList->currentItem());
1057 
1058     QObject::connect(m_changeIcon, &QPushButton::clicked, m_widget, [this]() {
1059         slotChangeIcon();
1060     });
1061 
1062     // "change icon text" button
1063     m_changeIconText = new QPushButton(i18nc("@action:button", "Change Te&xt..."), m_widget);
1064     m_changeIconText->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
1065     m_changeIconText->setEnabled(m_activeList->currentItem() != nullptr);
1066 
1067     QObject::connect(m_changeIconText, &QPushButton::clicked, m_widget, [this]() {
1068         slotChangeIconText();
1069     });
1070 
1071     // The buttons in the middle
1072 
1073     m_upAction = new QToolButton(m_widget);
1074     m_upAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1075     m_upAction->setEnabled(false);
1076     m_upAction->setAutoRepeat(true);
1077     QObject::connect(m_upAction, &QToolButton::clicked, m_widget, [this]() {
1078         slotUpButton();
1079     });
1080 
1081     m_insertAction = new QToolButton(m_widget);
1082     m_insertAction->setIcon(QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("go-previous") : QStringLiteral("go-next")));
1083     m_insertAction->setEnabled(false);
1084     QObject::connect(m_insertAction, &QToolButton::clicked, m_widget, [this]() {
1085         slotInsertButton();
1086     });
1087 
1088     m_removeAction = new QToolButton(m_widget);
1089     m_removeAction->setIcon(QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("go-next") : QStringLiteral("go-previous")));
1090     m_removeAction->setEnabled(false);
1091     QObject::connect(m_removeAction, &QToolButton::clicked, m_widget, [this]() {
1092         slotRemoveButton();
1093     });
1094 
1095     m_downAction = new QToolButton(m_widget);
1096     m_downAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1097     m_downAction->setEnabled(false);
1098     m_downAction->setAutoRepeat(true);
1099     QObject::connect(m_downAction, &QToolButton::clicked, m_widget, [this]() {
1100         slotDownButton();
1101     });
1102 
1103     m_helpArea = new QLabel(m_widget);
1104     m_helpArea->setWordWrap(true);
1105 
1106     // now start with our layouts
1107     QVBoxLayout *top_layout = new QVBoxLayout(m_widget);
1108     top_layout->setContentsMargins(0, 0, 0, 0);
1109 
1110     QVBoxLayout *name_layout = new QVBoxLayout();
1111     QHBoxLayout *list_layout = new QHBoxLayout();
1112 
1113     QVBoxLayout *inactive_layout = new QVBoxLayout();
1114     QVBoxLayout *active_layout = new QVBoxLayout();
1115     QHBoxLayout *changeIcon_layout = new QHBoxLayout();
1116 
1117     QGridLayout *button_layout = new QGridLayout();
1118 
1119     name_layout->addWidget(m_comboLabel);
1120     name_layout->addWidget(m_toolbarCombo);
1121     //  name_layout->addWidget(new_toolbar);
1122     //  name_layout->addWidget(del_toolbar);
1123 
1124     button_layout->setSpacing(0);
1125     button_layout->setRowStretch(0, 10);
1126     button_layout->addWidget(m_upAction, 1, 1);
1127     button_layout->addWidget(m_removeAction, 2, 0);
1128     button_layout->addWidget(m_insertAction, 2, 2);
1129     button_layout->addWidget(m_downAction, 3, 1);
1130     button_layout->setRowStretch(4, 10);
1131 
1132     inactive_layout->addWidget(inactive_label);
1133     inactive_layout->addWidget(inactiveListSearchLine);
1134     inactive_layout->addWidget(m_inactiveList, 1);
1135 
1136     active_layout->addWidget(active_label);
1137     active_layout->addWidget(activeListSearchLine);
1138     active_layout->addWidget(m_activeList, 1);
1139     active_layout->addLayout(changeIcon_layout);
1140 
1141     changeIcon_layout->addWidget(m_changeIcon);
1142     changeIcon_layout->addStretch(1);
1143     changeIcon_layout->addWidget(m_changeIconText);
1144 
1145     list_layout->addLayout(inactive_layout);
1146     list_layout->addLayout(button_layout);
1147     list_layout->addLayout(active_layout);
1148 
1149     top_layout->addLayout(name_layout);
1150     top_layout->addWidget(m_comboSeparator);
1151     top_layout->addLayout(list_layout, 10);
1152     top_layout->addWidget(m_helpArea);
1153     top_layout->addWidget(new KSeparator(m_widget));
1154 }
1155 
1156 void KEditToolBarWidgetPrivate::loadToolBarCombo(const QString &defaultToolBar)
1157 {
1158     const QLatin1String attrName("name");
1159     // just in case, we clear our combo
1160     m_toolbarCombo->clear();
1161 
1162     int defaultToolBarId = -1;
1163     int count = 0;
1164     // load in all of the toolbar names into this combo box
1165     for (const auto &xmlFile : std::as_const(m_xmlFiles)) {
1166         // skip the merged one in favor of the local one,
1167         // so that we can change icons
1168         // This also makes the app-defined named for "mainToolBar" appear rather than the ui_standards-defined name.
1169         if (xmlFile.type() == XmlData::Merged) {
1170             continue;
1171         }
1172 
1173         // each xml file may have any number of toolbars
1174         for (const auto &bar : std::as_const(xmlFile.barList())) {
1175             const QString text = xmlFile.toolBarText(bar);
1176             m_toolbarCombo->addItem(text);
1177             const QString name = bar.attribute(attrName);
1178             if (defaultToolBarId == -1 && name == defaultToolBar) {
1179                 defaultToolBarId = count;
1180             }
1181             count++;
1182         }
1183     }
1184     const bool showCombo = (count > 1);
1185     m_comboLabel->setVisible(showCombo);
1186     m_comboSeparator->setVisible(showCombo);
1187     m_toolbarCombo->setVisible(showCombo);
1188     if (defaultToolBarId == -1) {
1189         defaultToolBarId = 0;
1190     }
1191     // we want to the specified item selected and its actions loaded
1192     m_toolbarCombo->setCurrentIndex(defaultToolBarId);
1193     slotToolBarSelected(m_toolbarCombo->currentIndex());
1194 }
1195 
1196 void KEditToolBarWidgetPrivate::loadActions(const QDomElement &elem)
1197 {
1198     const QLatin1String tagSeparator("Separator");
1199     const QLatin1String tagSpacer("Spacer");
1200     const QLatin1String tagMerge("Merge");
1201     const QLatin1String tagActionList("ActionList");
1202     const QLatin1String tagAction("Action");
1203     const QLatin1String attrName("name");
1204 
1205     const QString separatorstring = i18n("--- separator ---");
1206     const QString spacerstring = i18n("--- expanding spacer ---");
1207 
1208     int sep_num = 0;
1209     QString sep_name(QStringLiteral("separator_%1"));
1210     int spacer_num = 0;
1211     QString spacer_name(QStringLiteral("spacer_%1"));
1212 
1213     // clear our lists
1214     m_inactiveList->clear();
1215     m_activeList->clear();
1216     m_insertAction->setEnabled(false);
1217     m_removeAction->setEnabled(false);
1218     m_upAction->setEnabled(false);
1219     m_downAction->setEnabled(false);
1220 
1221     // We'll use this action collection
1222     KActionCollection *actionCollection = m_currentXmlData->actionCollection();
1223 
1224     // store the names of our active actions
1225     QSet<QString> active_list;
1226 
1227     // Filtering message requested by translators (scripting).
1228     KLocalizedString nameFilter = ki18nc("@item:intable Action name in toolbar editor", "%1");
1229 
1230     // see if our current action is in this toolbar
1231     QDomNode n = elem.firstChild();
1232     for (; !n.isNull(); n = n.nextSibling()) {
1233         QDomElement it = n.toElement();
1234         if (it.isNull()) {
1235             continue;
1236         }
1237         if (it.tagName() == tagSeparator) {
1238             ToolBarItem *act = new ToolBarItem(m_activeList, tagSeparator, sep_name.arg(sep_num++), QString());
1239             act->setSeparator(true);
1240             act->setText(separatorstring);
1241             it.setAttribute(attrName, act->internalName());
1242             continue;
1243         }
1244         if (it.tagName() == tagSpacer) {
1245             ToolBarItem *act = new ToolBarItem(m_activeList, tagSpacer, spacer_name.arg(spacer_num++), QString());
1246             act->setSpacer(true);
1247             act->setText(spacerstring);
1248             it.setAttribute(attrName, act->internalName());
1249             continue;
1250         }
1251 
1252         if (it.tagName() == tagMerge) {
1253             // Merge can be named or not - use the name if there is one
1254             QString name = it.attribute(attrName);
1255             ToolBarItem *act =
1256                 new ToolBarItem(m_activeList, tagMerge, name, i18n("This element will be replaced with all the elements of an embedded component."));
1257             if (name.isEmpty()) {
1258                 act->setText(i18n("<Merge>"));
1259             } else {
1260                 act->setText(i18n("<Merge %1>", name));
1261             }
1262             continue;
1263         }
1264 
1265         if (it.tagName() == tagActionList) {
1266             ToolBarItem *act =
1267                 new ToolBarItem(m_activeList,
1268                                 tagActionList,
1269                                 it.attribute(attrName),
1270                                 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."));
1271             act->setText(i18n("ActionList: %1", it.attribute(attrName)));
1272             continue;
1273         }
1274 
1275         // iterate through this client's actions
1276         // This used to iterate through _all_ actions, but we don't support
1277         // putting any action into any client...
1278         const auto actions = actionCollection->actions();
1279         for (QAction *action : actions) {
1280             // do we have a match?
1281             if (it.attribute(attrName) == action->objectName()) {
1282                 // we have a match!
1283                 ToolBarItem *act = new ToolBarItem(m_activeList, it.tagName(), action->objectName(), action->toolTip());
1284                 act->setText(nameFilter.subs(KLocalizedString::removeAcceleratorMarker(action->iconText())).toString());
1285                 act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon);
1286                 act->setTextAlongsideIconHidden(action->priority() < QAction::NormalPriority);
1287 
1288                 active_list.insert(action->objectName());
1289                 break;
1290             }
1291         }
1292     }
1293 
1294     // go through the rest of the collection
1295     const auto actions = actionCollection->actions();
1296     for (QAction *action : actions) {
1297         // skip our active ones
1298         if (active_list.contains(action->objectName())) {
1299             continue;
1300         }
1301 
1302         ToolBarItem *act = new ToolBarItem(m_inactiveList, tagAction, action->objectName(), action->toolTip());
1303         act->setText(nameFilter.subs(KLocalizedString::removeAcceleratorMarker(action->text())).toString());
1304         act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon);
1305     }
1306 
1307     m_inactiveList->sortItems(Qt::AscendingOrder);
1308 
1309     // finally, add default separators and spacers to the inactive list
1310     ToolBarItem *sep = new ToolBarItem(nullptr, tagSeparator, sep_name.arg(sep_num++), QString());
1311     sep->setSeparator(true);
1312     sep->setText(separatorstring);
1313     m_inactiveList->insertItem(0, sep);
1314 
1315     ToolBarItem *spacer = new ToolBarItem(nullptr, tagSpacer, spacer_name.arg(spacer_num++), QString());
1316     spacer->setSpacer(true);
1317     spacer->setText(spacerstring);
1318     m_inactiveList->insertItem(1, spacer);
1319 }
1320 
1321 KActionCollection *KEditToolBarWidget::actionCollection() const
1322 {
1323     return d->m_collection;
1324 }
1325 
1326 void KEditToolBarWidgetPrivate::slotToolBarSelected(int index)
1327 {
1328     // We need to find the XmlData and toolbar element for this index
1329     // To do that, we do the same iteration as the one which filled in the combobox.
1330 
1331     int toolbarNumber = 0;
1332     for (auto &xmlFile : m_xmlFiles) {
1333         // skip the merged one in favor of the local one,
1334         // so that we can change icons
1335         if (xmlFile.type() == XmlData::Merged) {
1336             continue;
1337         }
1338 
1339         // each xml file may have any number of toolbars
1340         const auto &barList = xmlFile.barList();
1341         for (const auto &bar : barList) {
1342             // is this our toolbar?
1343             if (toolbarNumber == index) {
1344                 // save our current settings
1345                 m_currentXmlData = &xmlFile;
1346                 m_currentToolBarElem = bar;
1347 
1348                 // qCDebug(DEBUG_KXMLGUI) << "found toolbar" << m_currentXmlData->toolBarText(*it) << "m_currentXmlData set to";
1349                 m_currentXmlData->dump();
1350 
1351                 // If this is a Merged xmldata, clicking the "change icon" button would assert...
1352                 Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1353 
1354                 // load in our values
1355                 loadActions(m_currentToolBarElem);
1356 
1357                 if (xmlFile.type() == XmlData::Part || xmlFile.type() == XmlData::Shell) {
1358                     m_widget->setDOMDocument(xmlFile.domDocument());
1359                 }
1360                 return;
1361             }
1362             ++toolbarNumber;
1363         }
1364     }
1365 }
1366 
1367 void KEditToolBarWidgetPrivate::slotInactiveSelectionChanged()
1368 {
1369     if (!m_inactiveList->selectedItems().isEmpty()) {
1370         m_insertAction->setEnabled(true);
1371         QString statusText = static_cast<ToolBarItem *>(m_inactiveList->selectedItems().first())->statusText();
1372         m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText));
1373     } else {
1374         m_insertAction->setEnabled(false);
1375         m_helpArea->setText(QString());
1376     }
1377 }
1378 
1379 void KEditToolBarWidgetPrivate::slotActiveSelectionChanged()
1380 {
1381     ToolBarItem *toolitem = nullptr;
1382     if (!m_activeList->selectedItems().isEmpty()) {
1383         toolitem = static_cast<ToolBarItem *>(m_activeList->selectedItems().first());
1384     }
1385 
1386     m_removeAction->setEnabled(toolitem);
1387 
1388     m_changeIcon->setEnabled(toolitem && toolitem->internalTag() == QLatin1String("Action"));
1389 
1390     m_changeIconText->setEnabled(toolitem && toolitem->internalTag() == QLatin1String("Action"));
1391 
1392     if (toolitem) {
1393         m_upAction->setEnabled(toolitem->index() != 0);
1394         m_downAction->setEnabled(toolitem->index() != toolitem->listWidget()->count() - 1);
1395 
1396         QString statusText = toolitem->statusText();
1397         m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText));
1398     } else {
1399         m_upAction->setEnabled(false);
1400         m_downAction->setEnabled(false);
1401         m_helpArea->setText(QString());
1402     }
1403 }
1404 
1405 void KEditToolBarWidgetPrivate::slotInsertButton()
1406 {
1407     QString internalName = static_cast<ToolBarItem *>(m_inactiveList->currentItem())->internalName();
1408 
1409     insertActive(m_inactiveList->currentItem(), m_activeList->currentItem(), false);
1410     // we're modified, so let this change
1411     Q_EMIT m_widget->enableOk(true);
1412 
1413     slotToolBarSelected(m_toolbarCombo->currentIndex());
1414 
1415     selectActiveItem(internalName);
1416 }
1417 
1418 void KEditToolBarWidgetPrivate::selectActiveItem(const QString &internalName)
1419 {
1420     int activeItemCount = m_activeList->count();
1421     for (int i = 0; i < activeItemCount; i++) {
1422         ToolBarItem *item = static_cast<ToolBarItem *>(m_activeList->item(i));
1423         if (item->internalName() == internalName) {
1424             m_activeList->setCurrentItem(item);
1425             break;
1426         }
1427     }
1428 }
1429 
1430 void KEditToolBarWidgetPrivate::slotRemoveButton()
1431 {
1432     removeActive(m_activeList->currentItem());
1433 
1434     slotToolBarSelected(m_toolbarCombo->currentIndex());
1435 }
1436 
1437 void KEditToolBarWidgetPrivate::insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend)
1438 {
1439     if (!item) {
1440         return;
1441     }
1442 
1443     QDomElement new_item;
1444     // let's handle the separator and spacer specially
1445     if (item->isSeparator()) {
1446         new_item = m_widget->domDocument().createElement(QStringLiteral("Separator"));
1447     } else if (item->isSpacer()) {
1448         new_item = m_widget->domDocument().createElement(QStringLiteral("Spacer"));
1449     } else {
1450         new_item = m_widget->domDocument().createElement(QStringLiteral("Action"));
1451     }
1452 
1453     new_item.setAttribute(QStringLiteral("name"), item->internalName());
1454 
1455     Q_ASSERT(!m_currentToolBarElem.isNull());
1456 
1457     if (before) {
1458         // we have the item in the active list which is before the new
1459         // item.. so let's try our best to add our new item right after it
1460         QDomElement elem = findElementForToolBarItem(before);
1461         Q_ASSERT(!elem.isNull());
1462         m_currentToolBarElem.insertAfter(new_item, elem);
1463     } else {
1464         // simply put it at the beginning or the end of the list.
1465         if (prepend) {
1466             m_currentToolBarElem.insertBefore(new_item, m_currentToolBarElem.firstChild());
1467         } else {
1468             m_currentToolBarElem.appendChild(new_item);
1469         }
1470     }
1471 
1472     // and set this container as a noMerge
1473     m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1474 
1475     // update the local doc
1476     updateLocal(m_currentToolBarElem);
1477 }
1478 
1479 void KEditToolBarWidgetPrivate::removeActive(ToolBarItem *item)
1480 {
1481     if (!item) {
1482         return;
1483     }
1484 
1485     // we're modified, so let this change
1486     Q_EMIT m_widget->enableOk(true);
1487 
1488     // now iterate through to find the child to nuke
1489     QDomElement elem = findElementForToolBarItem(item);
1490     if (!elem.isNull()) {
1491         // nuke myself!
1492         m_currentToolBarElem.removeChild(elem);
1493 
1494         // and set this container as a noMerge
1495         m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1496 
1497         // update the local doc
1498         updateLocal(m_currentToolBarElem);
1499     }
1500 }
1501 
1502 void KEditToolBarWidgetPrivate::slotUpButton()
1503 {
1504     ToolBarItem *item = m_activeList->currentItem();
1505 
1506     if (!item) {
1507         Q_ASSERT(false);
1508         return;
1509     }
1510 
1511     int row = item->listWidget()->row(item) - 1;
1512     // make sure we're not the top item already
1513     if (row < 0) {
1514         Q_ASSERT(false);
1515         return;
1516     }
1517 
1518     // we're modified, so let this change
1519     Q_EMIT m_widget->enableOk(true);
1520 
1521     moveActive(item, static_cast<ToolBarItem *>(item->listWidget()->item(row - 1)));
1522 }
1523 
1524 void KEditToolBarWidgetPrivate::moveActive(ToolBarItem *item, ToolBarItem *before)
1525 {
1526     QDomElement e = findElementForToolBarItem(item);
1527 
1528     if (e.isNull()) {
1529         return;
1530     }
1531 
1532     // remove item
1533     m_activeList->takeItem(m_activeList->row(item));
1534 
1535     // put it where it's supposed to go
1536     m_activeList->insertItem(m_activeList->row(before) + 1, item);
1537 
1538     // make it selected again
1539     m_activeList->setCurrentItem(item);
1540 
1541     // and do the real move in the DOM
1542     if (!before) {
1543         m_currentToolBarElem.insertBefore(e, m_currentToolBarElem.firstChild());
1544     } else {
1545         m_currentToolBarElem.insertAfter(e, findElementForToolBarItem(before));
1546     }
1547 
1548     // and set this container as a noMerge
1549     m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1"));
1550 
1551     // update the local doc
1552     updateLocal(m_currentToolBarElem);
1553 }
1554 
1555 void KEditToolBarWidgetPrivate::slotDownButton()
1556 {
1557     ToolBarItem *item = m_activeList->currentItem();
1558 
1559     if (!item) {
1560         Q_ASSERT(false);
1561         return;
1562     }
1563 
1564     // make sure we're not the bottom item already
1565     int newRow = item->listWidget()->row(item) + 1;
1566     if (newRow >= item->listWidget()->count()) {
1567         Q_ASSERT(false);
1568         return;
1569     }
1570 
1571     // we're modified, so let this change
1572     Q_EMIT m_widget->enableOk(true);
1573 
1574     moveActive(item, static_cast<ToolBarItem *>(item->listWidget()->item(newRow)));
1575 }
1576 
1577 void KEditToolBarWidgetPrivate::updateLocal(QDomElement &elem)
1578 {
1579     for (auto &xmlFile : m_xmlFiles) {
1580         if (xmlFile.type() == XmlData::Merged) {
1581             continue;
1582         }
1583 
1584         if (xmlFile.type() == XmlData::Shell || xmlFile.type() == XmlData::Part) {
1585             if (m_currentXmlData->xmlFile() == xmlFile.xmlFile()) {
1586                 xmlFile.m_isModified = true;
1587                 return;
1588             }
1589 
1590             continue;
1591         }
1592 
1593         xmlFile.m_isModified = true;
1594         const QLatin1String attrName("name");
1595         for (const auto &bar : std::as_const(xmlFile.barList())) {
1596             const QString name(bar.attribute(attrName));
1597             const QString tag(bar.tagName());
1598             if ((tag != elem.tagName()) || (name != elem.attribute(attrName))) {
1599                 continue;
1600             }
1601 
1602             QDomElement toolbar = xmlFile.domDocument().documentElement().toElement();
1603             toolbar.replaceChild(elem, bar);
1604             return;
1605         }
1606 
1607         // just append it
1608         QDomElement toolbar = xmlFile.domDocument().documentElement().toElement();
1609         Q_ASSERT(!toolbar.isNull());
1610         toolbar.appendChild(elem);
1611     }
1612 }
1613 
1614 void KEditToolBarWidgetPrivate::slotChangeIcon()
1615 {
1616     m_currentXmlData->dump();
1617     Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1618 
1619     QString icon = KIconDialog::getIcon(KIconLoader::Toolbar,
1620                                         KIconLoader::Action,
1621                                         false,
1622                                         0,
1623                                         false, // all defaults
1624                                         m_widget,
1625                                         i18n("Change Icon"));
1626 
1627     if (icon.isEmpty()) {
1628         return;
1629     }
1630 
1631     ToolBarItem *item = m_activeList->currentItem();
1632     // qCDebug(DEBUG_KXMLGUI) << item;
1633     if (item) {
1634         item->setIcon(QIcon::fromTheme(icon));
1635 
1636         m_currentXmlData->m_isModified = true;
1637 
1638         // Get hold of ActionProperties tag
1639         QDomElement elem = KXMLGUIFactory::actionPropertiesElement(m_currentXmlData->domDocument());
1640         // Find or create an element for this action
1641         QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, item->internalName(), true /*create*/);
1642         Q_ASSERT(!act_elem.isNull());
1643         act_elem.setAttribute(QStringLiteral("icon"), icon);
1644 
1645         // we're modified, so let this change
1646         Q_EMIT m_widget->enableOk(true);
1647     }
1648 }
1649 
1650 void KEditToolBarWidgetPrivate::slotChangeIconText()
1651 {
1652     m_currentXmlData->dump();
1653     ToolBarItem *item = m_activeList->currentItem();
1654 
1655     if (item) {
1656         QString iconText = item->text();
1657         bool hidden = item->isTextAlongsideIconHidden();
1658 
1659         IconTextEditDialog dialog(m_widget);
1660         dialog.setIconText(iconText);
1661         dialog.setTextAlongsideIconHidden(hidden);
1662 
1663         bool ok = dialog.exec() == QDialog::Accepted;
1664         iconText = dialog.iconText();
1665         hidden = dialog.textAlongsideIconHidden();
1666 
1667         bool hiddenChanged = hidden != item->isTextAlongsideIconHidden();
1668         bool iconTextChanged = iconText != item->text();
1669 
1670         if (!ok || (!hiddenChanged && !iconTextChanged)) {
1671             return;
1672         }
1673 
1674         item->setText(iconText);
1675         item->setTextAlongsideIconHidden(hidden);
1676 
1677         Q_ASSERT(m_currentXmlData->type() != XmlData::Merged);
1678 
1679         m_currentXmlData->m_isModified = true;
1680 
1681         // Get hold of ActionProperties tag
1682         QDomElement elem = KXMLGUIFactory::actionPropertiesElement(m_currentXmlData->domDocument());
1683         // Find or create an element for this action
1684         QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, item->internalName(), true /*create*/);
1685         Q_ASSERT(!act_elem.isNull());
1686         if (iconTextChanged) {
1687             act_elem.setAttribute(QStringLiteral("iconText"), iconText);
1688         }
1689         if (hiddenChanged) {
1690             act_elem.setAttribute(QStringLiteral("priority"), hidden ? QAction::LowPriority : QAction::NormalPriority);
1691         }
1692 
1693         // we're modified, so let this change
1694         Q_EMIT m_widget->enableOk(true);
1695     }
1696 }
1697 
1698 void KEditToolBarWidgetPrivate::slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList)
1699 {
1700     // qCDebug(DEBUG_KXMLGUI) << "slotDropped list=" << (list==m_activeList?"activeList":"inactiveList")
1701     //         << "index=" << index << "sourceIsActiveList=" << sourceIsActiveList;
1702     if (list == m_activeList) {
1703         ToolBarItem *after = index > 0 ? static_cast<ToolBarItem *>(list->item(index - 1)) : nullptr;
1704         // qCDebug(DEBUG_KXMLGUI) << "after" << after->text() << after->internalTag();
1705         if (sourceIsActiveList) {
1706             // has been dragged within the active list (moved).
1707             moveActive(item, after);
1708         } else {
1709             // dragged from the inactive list to the active list
1710             insertActive(item, after, true);
1711         }
1712     } else if (list == m_inactiveList) {
1713         // has been dragged to the inactive list -> remove from the active list.
1714         removeActive(item);
1715     }
1716 
1717     delete item; // not needed anymore. must be deleted before slotToolBarSelected clears the lists
1718 
1719     // we're modified, so let this change
1720     Q_EMIT m_widget->enableOk(true);
1721 
1722     slotToolBarSelected(m_toolbarCombo->currentIndex());
1723 }
1724 
1725 void KEditToolBar::showEvent(QShowEvent *event)
1726 {
1727     if (!event->spontaneous()) {
1728         // The dialog has been shown, enable toolbar editing
1729         if (d->m_factory) {
1730             // call the xmlgui-factory version
1731             d->m_widget->load(d->m_factory, d->m_defaultToolBar);
1732         } else {
1733             // call the action collection version
1734             d->m_widget->load(d->m_file, d->m_global, d->m_defaultToolBar);
1735         }
1736 
1737         KToolBar::setToolBarsEditable(true);
1738     }
1739     QDialog::showEvent(event);
1740 }
1741 
1742 void KEditToolBar::hideEvent(QHideEvent *event)
1743 {
1744     // The dialog has been hidden, disable toolbar editing
1745     KToolBar::setToolBarsEditable(false);
1746 
1747     QDialog::hideEvent(event);
1748 }
1749 
1750 #include "moc_kedittoolbar.cpp"
1751 #include "moc_kedittoolbar_p.cpp"