File indexing completed on 2024-04-14 03:57:09

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