File indexing completed on 2024-05-05 05:51:23
0001 /* This file is part of the KDE project 0002 * 0003 * SPDX-FileCopyrightText: 2019 Dominik Haumann <dhaumann@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 #include "kateexternaltoolsconfigwidget.h" 0008 #include "externaltoolsplugin.h" 0009 #include "kateexternaltool.h" 0010 0011 #include <KLineEdit> 0012 #include <KTextEditor/Editor> 0013 #include <KTextEditor/View> 0014 #include <ktexteditor_version.h> 0015 0016 #include <KConfig> 0017 #include <KConfigGroup> 0018 #include <KIconLoader> 0019 #include <KMimeTypeChooser> 0020 #include <KSharedConfig> 0021 #include <QBitmap> 0022 #include <QMenu> 0023 #include <QMessageBox> 0024 #include <QRegularExpression> 0025 #include <QRegularExpressionValidator> 0026 #include <QStandardItem> 0027 #include <QStandardPaths> 0028 #include <QTreeView> 0029 0030 namespace 0031 { 0032 constexpr int ToolRole = Qt::UserRole + 1; 0033 0034 /** 0035 * Helper function to create a QStandardItem that internally stores a pointer to a KateExternalTool. 0036 */ 0037 QStandardItem *newToolItem(const QIcon &icon, KateExternalTool *tool) 0038 { 0039 auto item = new QStandardItem(icon, tool->translatedName()); 0040 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); 0041 item->setData(QVariant::fromValue(reinterpret_cast<quintptr>(tool)), ToolRole); 0042 return item; 0043 } 0044 0045 /** 0046 * Helper function to return an internally stored KateExternalTool for a QStandardItem. 0047 * If a nullptr is returned, it means the QStandardItem is a category. 0048 */ 0049 KateExternalTool *toolForItem(QStandardItem *item) 0050 { 0051 return item ? reinterpret_cast<KateExternalTool *>(item->data(ToolRole).value<quintptr>()) : nullptr; 0052 } 0053 0054 QIcon blankIcon() 0055 { 0056 QPixmap pm(KIconLoader::SizeSmall, KIconLoader::SizeSmall); 0057 pm.fill(); 0058 pm.setMask(pm.createHeuristicMask()); 0059 return QIcon(pm); 0060 } 0061 0062 static void makeToolUnique(KateExternalTool *tool, const QList<KateExternalTool *> &tools) 0063 { 0064 // Ensure that tool->name is unique 0065 int i = 1; 0066 QString name = tool->name; 0067 while (true) { 0068 const bool isUnique = std::none_of(tools.cbegin(), tools.cend(), [tool, &name](const KateExternalTool *t) { 0069 return (t != tool) && (t->name == name); 0070 }); 0071 if (isUnique) { 0072 break; 0073 } 0074 name = tool->name + QString::number(i++); 0075 } 0076 tool->name = name; 0077 0078 // Ensure tool->actionName is unique 0079 i = 1; 0080 QString actName = tool->actionName; 0081 while (true) { 0082 const bool isUnique = std::none_of(tools.cbegin(), tools.cend(), [tool, &actName](const KateExternalTool *t) { 0083 return (t != tool) && (t->actionName == actName); 0084 }); 0085 if (isUnique) { 0086 break; 0087 } 0088 actName = tool->actionName + QString::number(i++); 0089 } 0090 tool->actionName = actName; 0091 0092 // Ensure the tool->cmdname is unique 0093 // empty command line name is OK 0094 if (tool->cmdname.isEmpty()) { 0095 return; 0096 } 0097 0098 i = 1; 0099 QString cmdname = tool->cmdname; 0100 while (true) { 0101 const bool isUnique = std::none_of(tools.cbegin(), tools.cend(), [tool, &cmdname](const KateExternalTool *t) { 0102 return (t != tool) && (t->cmdname == cmdname); 0103 }); 0104 if (isUnique) { 0105 break; 0106 } 0107 cmdname = tool->cmdname + QString::number(i++); 0108 } 0109 tool->cmdname = cmdname; 0110 } 0111 0112 static KateExternalTool defaultTool(const QString &actionName, const QList<KateExternalTool> &defaultTools) 0113 { 0114 auto it = std::find_if(defaultTools.cbegin(), defaultTools.cend(), [actionName](const KateExternalTool &defaultTool) { 0115 return actionName == defaultTool.actionName; 0116 }); 0117 return (it != defaultTools.cend()) ? *it : KateExternalTool(); 0118 } 0119 0120 static bool isDefaultTool(KateExternalTool *tool, const QList<KateExternalTool> &defaultTools) 0121 { 0122 return tool && !defaultTool(tool->actionName, defaultTools).actionName.isEmpty(); 0123 } 0124 } 0125 0126 // BEGIN KateExternalToolServiceEditor 0127 KateExternalToolServiceEditor::KateExternalToolServiceEditor(KateExternalTool *tool, KateExternalToolsPlugin *plugin, QWidget *parent) 0128 : QDialog(parent) 0129 , m_plugin(plugin) 0130 , m_tool(tool) 0131 { 0132 setWindowTitle(i18n("Edit External Tool")); 0133 setWindowIcon(QIcon::fromTheme(QStringLiteral("system-run"))); 0134 0135 ui.setupUi(this); 0136 ui.btnIcon->setIconSize(KIconLoader::SizeSmall); 0137 0138 connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &KateExternalToolServiceEditor::slotOKClicked); 0139 connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0140 connect(ui.btnMimeType, &QToolButton::clicked, this, &KateExternalToolServiceEditor::showMTDlg); 0141 0142 Q_ASSERT(m_tool != nullptr); 0143 ui.edtName->setText(m_tool->translatedName()); 0144 if (!m_tool->icon.isEmpty()) { 0145 ui.btnIcon->setIcon(m_tool->icon); 0146 } 0147 0148 ui.edtExecutable->setText(m_tool->executable); 0149 ui.edtArgs->setText(m_tool->arguments); 0150 ui.edtInput->setText(m_tool->input); 0151 ui.edtWorkingDir->setText(m_tool->workingDir); 0152 ui.edtWorkingDir->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); 0153 ui.edtMimeType->setText(m_tool->mimetypes.join(QStringLiteral("; "))); 0154 ui.cmbSave->setCurrentIndex(static_cast<int>(m_tool->saveMode)); 0155 ui.chkReload->setChecked(m_tool->reload); 0156 ui.cmbOutput->setCurrentIndex(static_cast<int>(m_tool->outputMode)); 0157 ui.edtCommand->setText(m_tool->cmdname); 0158 ui.cmbTrigger->setCurrentIndex((int)m_tool->trigger); 0159 0160 static const QRegularExpressionValidator cmdLineValidator(QRegularExpression(QStringLiteral("[\\w-]*"))); 0161 ui.edtCommand->setValidator(&cmdLineValidator); 0162 0163 if (isDefaultTool(tool, m_plugin->defaultTools())) { 0164 ui.buttonBox->setStandardButtons(ui.buttonBox->standardButtons() | QDialogButtonBox::RestoreDefaults); 0165 ui.buttonBox->setToolTip(i18n("Revert tool to default settings")); 0166 connect(ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [this, tool]() { 0167 const auto t = defaultTool(tool->actionName, m_plugin->defaultTools()); 0168 ui.edtName->setText(t.translatedName()); 0169 ui.btnIcon->setIcon(t.icon); 0170 ui.edtExecutable->setText(t.executable); 0171 ui.edtArgs->setText(t.arguments); 0172 ui.edtInput->setText(t.input); 0173 ui.edtWorkingDir->setText(t.workingDir); 0174 ui.edtMimeType->setText(t.mimetypes.join(QStringLiteral("; "))); 0175 ui.cmbSave->setCurrentIndex(static_cast<int>(t.saveMode)); 0176 ui.chkReload->setChecked(t.reload); 0177 ui.cmbOutput->setCurrentIndex(static_cast<int>(t.outputMode)); 0178 ui.edtCommand->setText(t.cmdname); 0179 ui.cmbTrigger->setCurrentIndex(static_cast<int>(t.trigger)); 0180 }); 0181 } 0182 0183 // add support for variable expansion 0184 KTextEditor::Editor::instance()->addVariableExpansion({ui.edtExecutable->lineEdit(), ui.edtArgs, ui.edtInput, ui.edtWorkingDir->lineEdit()}); 0185 } 0186 0187 void KateExternalToolServiceEditor::slotOKClicked() 0188 { 0189 if (ui.edtName->text().isEmpty() || ui.edtExecutable->text().isEmpty()) { 0190 QMessageBox::information(this, i18n("External Tool"), i18n("You must specify at least a name and an executable")); 0191 return; 0192 } 0193 0194 const bool hasTrigger = ui.cmbTrigger->currentIndex() != (int)KateExternalTool::Trigger::None; 0195 if (hasTrigger && ui.edtMimeType->text().isEmpty()) { 0196 QMessageBox::information(this, i18n("External Tool"), i18n("With 'Trigger' enabled, at least one mimetype needs to be specified.")); 0197 return; 0198 } 0199 0200 accept(); 0201 } 0202 0203 void KateExternalToolServiceEditor::showMTDlg() 0204 { 0205 QString text = i18n("Select the MimeTypes for which to enable this tool."); 0206 QStringList list = ui.edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), Qt::SkipEmptyParts); 0207 KMimeTypeChooserDialog d(i18n("Select Mime Types"), text, list, QStringLiteral("text"), this); 0208 if (d.exec() == QDialog::Accepted) { 0209 ui.edtMimeType->setText(d.chooser()->mimeTypes().join(QStringLiteral(";"))); 0210 } 0211 } 0212 0213 // END KateExternalToolServiceEditor 0214 0215 // BEGIN KateExternalToolsConfigWidget 0216 KateExternalToolsConfigWidget::KateExternalToolsConfigWidget(QWidget *parent, KateExternalToolsPlugin *plugin) 0217 : KTextEditor::ConfigPage(parent) 0218 , m_plugin(plugin) 0219 { 0220 setupUi(this); 0221 layout()->setContentsMargins(0, 0, 0, 0); 0222 layout()->setSpacing(0); 0223 lbTools->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::BottomEdge})); 0224 lbTools->setModel(&m_toolsModel); 0225 lbTools->setSelectionMode(QAbstractItemView::SingleSelection); 0226 lbTools->setDragEnabled(true); 0227 lbTools->setAcceptDrops(true); 0228 lbTools->setDefaultDropAction(Qt::MoveAction); 0229 lbTools->setDropIndicatorShown(true); 0230 lbTools->setDragDropOverwriteMode(false); 0231 lbTools->setDragDropMode(QAbstractItemView::InternalMove); 0232 0233 horizontalLayout->setSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); 0234 horizontalLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin), 0235 style()->pixelMetric(QStyle::PM_LayoutTopMargin), 0236 style()->pixelMetric(QStyle::PM_LayoutRightMargin), 0237 style()->pixelMetric(QStyle::PM_LayoutBottomMargin)); 0238 0239 // Add... button popup menu 0240 auto addMenu = new QMenu(btnAdd); 0241 auto addToolAction = addMenu->addAction(i18n("Add Tool...")); 0242 auto addDefaultsMenu = addMenu->addMenu(i18n("Add Tool from Defaults")); 0243 addMenu->addSeparator(); 0244 auto addCategoryAction = addMenu->addAction(i18n("Add Category")); 0245 btnAdd->setMenu(addMenu); 0246 connect(addDefaultsMenu, &QMenu::aboutToShow, [this, addDefaultsMenu]() { 0247 lazyInitDefaultsMenu(addDefaultsMenu); 0248 }); 0249 0250 connect(addCategoryAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddCategory); 0251 connect(addToolAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddTool); 0252 connect(btnRemove, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotRemove); 0253 connect(btnEdit, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotEdit); 0254 connect(lbTools->selectionModel(), &QItemSelectionModel::currentChanged, [this]() { 0255 slotSelectionChanged(); 0256 }); 0257 connect(lbTools, &QTreeView::doubleClicked, this, &KateExternalToolsConfigWidget::slotEdit); 0258 0259 // reset triggers a reload of the existing tools 0260 reset(); 0261 slotSelectionChanged(); 0262 0263 connect(&m_toolsModel, &QStandardItemModel::itemChanged, this, &KateExternalToolsConfigWidget::slotItemChanged); 0264 } 0265 0266 KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() 0267 { 0268 } 0269 0270 QString KateExternalToolsConfigWidget::name() const 0271 { 0272 return i18n("External Tools"); 0273 } 0274 0275 QString KateExternalToolsConfigWidget::fullName() const 0276 { 0277 return i18n("External Tools"); 0278 } 0279 0280 QIcon KateExternalToolsConfigWidget::icon() const 0281 { 0282 return QIcon::fromTheme(QStringLiteral("system-run")); 0283 } 0284 0285 void KateExternalToolsConfigWidget::reset() 0286 { 0287 m_toolsModel.clear(); 0288 m_toolsModel.invisibleRootItem()->setFlags(Qt::NoItemFlags); 0289 0290 // the "Uncategorized" category always exists 0291 m_noCategory = addCategory(i18n("Uncategorized")); 0292 m_noCategory->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); 0293 0294 // create other tools and categories 0295 const auto tools = m_plugin->tools(); 0296 for (KateExternalTool *tool : tools) { 0297 auto item = newToolItem(tool->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(tool->icon), tool); 0298 auto category = tool->category.isEmpty() ? m_noCategory : addCategory(tool->category); 0299 category->appendRow(item); 0300 } 0301 lbTools->expandAll(); 0302 m_changed = false; 0303 } 0304 0305 void KateExternalToolsConfigWidget::apply() 0306 { 0307 if (!m_changed) { 0308 return; 0309 } 0310 m_changed = false; 0311 0312 KSharedConfigPtr config = m_plugin->config(); 0313 config->group(QStringLiteral("Global")).writeEntry("firststart", false); 0314 config->sync(); 0315 0316 m_plugin->removeTools(m_toolsToRemove); 0317 m_changedTools.erase(std::remove_if(m_changedTools.begin(), 0318 m_changedTools.end(), 0319 [this](const ChangedToolInfo &cti) { 0320 return std::find(m_toolsToRemove.begin(), m_toolsToRemove.end(), cti.tool) != m_toolsToRemove.end(); 0321 }), 0322 m_changedTools.end()); 0323 m_toolsToRemove.clear(); 0324 0325 for (auto &[tool, oldName] : m_changedTools) { 0326 m_plugin->save(tool, oldName); 0327 } 0328 m_changedTools.clear(); 0329 0330 // So that KateExternalToolsPluginView::rebuildMenu() is called, 0331 // needed to update the menu actions 0332 Q_EMIT m_plugin->externalToolsChanged(); 0333 } 0334 0335 void KateExternalToolsConfigWidget::slotSelectionChanged() 0336 { 0337 // update button state 0338 auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); 0339 const bool isToolItem = toolForItem(item) != nullptr; 0340 const bool isCategory = item && !isToolItem; 0341 0342 btnEdit->setEnabled(isToolItem || isCategory); 0343 btnRemove->setEnabled(isToolItem); 0344 } 0345 0346 bool KateExternalToolsConfigWidget::editTool(KateExternalTool *tool) 0347 { 0348 bool changed = false; 0349 KSharedConfigPtr config = m_plugin->config(); 0350 0351 KateExternalToolServiceEditor editor(tool, m_plugin, this); 0352 KConfigGroup editorGroup = config->group(QStringLiteral("Editor")); 0353 editor.resize(editorGroup.readEntry("Size", QSize())); 0354 0355 if (editor.exec() == QDialog::Accepted) { 0356 const QString oldName = tool->name; 0357 tool->name = editor.ui.edtName->text().trimmed(); 0358 tool->icon = editor.ui.btnIcon->icon(); 0359 tool->arguments = editor.ui.edtArgs->text(); 0360 tool->input = editor.ui.edtInput->toPlainText(); 0361 tool->workingDir = editor.ui.edtWorkingDir->text(); 0362 tool->mimetypes = editor.ui.edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), Qt::SkipEmptyParts); 0363 tool->saveMode = static_cast<KateExternalTool::SaveMode>(editor.ui.cmbSave->currentIndex()); 0364 tool->reload = editor.ui.chkReload->isChecked(); 0365 tool->outputMode = static_cast<KateExternalTool::OutputMode>(editor.ui.cmbOutput->currentIndex()); 0366 tool->cmdname = editor.ui.edtCommand->text().trimmed(); 0367 tool->trigger = static_cast<KateExternalTool::Trigger>(editor.ui.cmbTrigger->currentIndex()); 0368 0369 tool->executable = editor.ui.edtExecutable->text().trimmed(); 0370 tool->hasexec = tool->executable.contains(QLatin1Char('$')) ? std::nullopt : std::optional<bool>(tool->checkExec()); 0371 0372 // sticky action collection name, never changes again, so that shortcuts stay 0373 if (tool->actionName.isEmpty()) { 0374 tool->actionName = QStringLiteral("externaltool_") + QString(tool->name).remove(QRegularExpression(QStringLiteral("\\W+"))); 0375 } 0376 0377 makeToolUnique(tool, m_plugin->tools()); 0378 0379 const bool renamed = !oldName.isEmpty() && oldName != tool->name; 0380 // Delay saving to apply() 0381 m_changedTools.push_back({tool, renamed ? oldName : QString{}}); 0382 0383 changed = true; 0384 } 0385 0386 editorGroup.writeEntry("Size", editor.size()); 0387 config->sync(); 0388 0389 return changed; 0390 } 0391 0392 void KateExternalToolsConfigWidget::lazyInitDefaultsMenu(QMenu *defaultsMenu) 0393 { 0394 if (!defaultsMenu->isEmpty()) { 0395 return; 0396 } 0397 0398 // create tool actions 0399 std::map<QString, QMenu *> categories; 0400 0401 // first add categorized actions, such that the submenus appear at the top 0402 int defaultToolsIndex = 0; 0403 for (const auto &tool : m_plugin->defaultTools()) { 0404 const QString category = tool.category.isEmpty() ? i18n("Uncategorized") : tool.translatedCategory(); 0405 auto categoryMenu = categories[category]; 0406 if (!categoryMenu) { 0407 categoryMenu = new QMenu(category, this); 0408 categories[category] = categoryMenu; 0409 defaultsMenu->addMenu(categoryMenu); 0410 } 0411 0412 auto a = categoryMenu->addAction(QIcon::fromTheme(tool.icon), tool.translatedName()); 0413 a->setData(defaultToolsIndex); 0414 0415 connect(a, &QAction::triggered, [this, a]() { 0416 slotAddDefaultTool(a->data().toInt()); 0417 }); 0418 ++defaultToolsIndex; 0419 } 0420 } 0421 0422 void KateExternalToolsConfigWidget::slotAddDefaultTool(int defaultToolsIndex) 0423 { 0424 const auto &defaultTools = m_plugin->defaultTools(); 0425 if (defaultToolsIndex < 0 || defaultToolsIndex > defaultTools.size()) { 0426 return; 0427 } 0428 0429 addNewTool(new KateExternalTool(defaultTools[defaultToolsIndex])); 0430 } 0431 0432 void KateExternalToolsConfigWidget::addNewTool(KateExternalTool *tool) 0433 { 0434 makeToolUnique(tool, m_plugin->tools()); 0435 0436 auto item = newToolItem(tool->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(tool->icon), tool); 0437 auto category = addCategory(tool->translatedCategory()); 0438 category->appendRow(item); 0439 tool->category = category->text(); 0440 lbTools->setCurrentIndex(item->index()); 0441 0442 m_plugin->addNewTool(tool); 0443 m_changedTools.push_back({tool, {}}); 0444 0445 Q_EMIT changed(); 0446 m_changed = true; 0447 } 0448 0449 QStandardItem *KateExternalToolsConfigWidget::addCategory(const QString &translatedCategory) 0450 { 0451 if (translatedCategory.isEmpty() || (m_noCategory && translatedCategory == i18n("Uncategorized"))) { 0452 return currentCategory(); 0453 } 0454 0455 // search for existing category 0456 auto items = m_toolsModel.findItems(translatedCategory); 0457 if (!items.empty()) { 0458 return items.front(); 0459 } 0460 0461 // ...otherwise, create it 0462 auto item = new QStandardItem(translatedCategory); 0463 0464 // for now, categories are not movable, otherwise, the use can move a 0465 // category into another category, which is not supported right now 0466 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable); 0467 0468 m_toolsModel.appendRow(item); 0469 return item; 0470 } 0471 0472 QStandardItem *KateExternalToolsConfigWidget::currentCategory() const 0473 { 0474 auto index = lbTools->currentIndex(); 0475 if (!index.isValid()) { 0476 return m_noCategory; 0477 } 0478 0479 auto item = m_toolsModel.itemFromIndex(index); 0480 auto tool = toolForItem(item); 0481 if (tool) { 0482 // the parent of a ToolItem is always a category 0483 return item->parent(); 0484 } 0485 0486 // item is no ToolItem, so we must have a category at hand 0487 return item; 0488 } 0489 0490 void KateExternalToolsConfigWidget::slotAddCategory() 0491 { 0492 // find unique name 0493 QString name = i18n("New Category"); 0494 int i = 1; 0495 while (!m_toolsModel.findItems(name, Qt::MatchFixedString).isEmpty()) { 0496 name = (i18n("New Category %1", i++)); 0497 } 0498 0499 // add category and switch to edit mode 0500 auto item = addCategory(name); 0501 lbTools->edit(item->index()); 0502 } 0503 0504 void KateExternalToolsConfigWidget::slotAddTool() 0505 { 0506 std::unique_ptr tool = std::make_unique<KateExternalTool>(); 0507 if (editTool(tool.get())) { 0508 addNewTool(tool.release()); 0509 } 0510 } 0511 0512 void KateExternalToolsConfigWidget::slotRemove() 0513 { 0514 auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); 0515 auto tool = toolForItem(item); 0516 0517 if (tool) { 0518 item->parent()->removeRow(item->index().row()); 0519 // Delay calling m_plugin->removeTools() to apply() 0520 m_toolsToRemove.push_back(tool); 0521 Q_EMIT changed(); 0522 m_changed = true; 0523 } 0524 } 0525 0526 void KateExternalToolsConfigWidget::slotEdit() 0527 { 0528 auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); 0529 auto tool = toolForItem(item); 0530 if (!tool) { 0531 if (item) { 0532 lbTools->edit(item->index()); 0533 } 0534 return; 0535 } 0536 // show the item in an editor 0537 if (editTool(tool)) { 0538 // renew the icon and name 0539 item->setText(tool->name); 0540 item->setIcon(tool->icon.isEmpty() ? blankIcon() : QIcon::fromTheme(tool->icon)); 0541 0542 Q_EMIT changed(); 0543 m_changed = true; 0544 } 0545 } 0546 0547 void KateExternalToolsConfigWidget::slotItemChanged(QStandardItem *item) 0548 { 0549 // If a tool was drag and dropped to some other category, we need 0550 // to update the tool's category 0551 if (KateExternalTool *tool = toolForItem(item)) { 0552 if (QStandardItem *parentCategory = item->parent()) { 0553 tool->category = parentCategory != m_noCategory ? parentCategory->text() : QString{}; 0554 // Changes will be saved in apply() 0555 m_changedTools.push_back({tool, {}}); 0556 } 0557 } 0558 0559 m_changed = true; 0560 Q_EMIT changed(); 0561 } 0562 0563 // END KateExternalToolsConfigWidget 0564 0565 #include "moc_kateexternaltoolsconfigwidget.cpp" 0566 0567 // kate: space-indent on; indent-width 4; replace-tabs on;