File indexing completed on 2024-04-21 03:59:35
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"