File indexing completed on 2024-04-28 05:48:31

0001 //
0002 // configview.cpp
0003 //
0004 // Description: View for configuring the set of targets to be used with the debugger
0005 //
0006 //
0007 // SPDX-FileCopyrightText: 2010 Ian Wakeling <ian.wakeling@ntlworld.com>
0008 // SPDX-FileCopyrightText: 2012 Kåre Särs <kare.sars@iki.fi>
0009 //
0010 //  SPDX-License-Identifier: LGPL-2.0-only
0011 
0012 #include "configview.h"
0013 
0014 #include <QCheckBox>
0015 #include <QCompleter>
0016 #include <QFileDialog>
0017 #include <QFileSystemModel>
0018 #include <QJsonArray>
0019 #include <QJsonDocument>
0020 #include <QLayout>
0021 #include <QPushButton>
0022 #include <QSpinBox>
0023 #include <QStandardPaths>
0024 
0025 #include <KTextEditor/Document>
0026 #include <KTextEditor/View>
0027 
0028 #include <KActionCollection>
0029 #include <KConfigGroup>
0030 #include <KLocalizedString>
0031 #include <KSelectAction>
0032 
0033 #include "advanced_settings.h"
0034 #include "dap/settings.h"
0035 #include "json_placeholders.h"
0036 #include "plugin_kategdb.h"
0037 #include <json_utils.h>
0038 
0039 constexpr int CONFIG_VERSION = 5;
0040 const static QString F_TARGET = QStringLiteral("target");
0041 const static QString F_DEBUGGER = QStringLiteral("debuggerKey");
0042 const static QString F_PROFILE = QStringLiteral("debuggerProfile");
0043 const static QString F_FILE = QStringLiteral("file");
0044 const static QString F_WORKDIR = QStringLiteral("workdir");
0045 const static QString F_ARGS = QStringLiteral("args");
0046 const static QString F_PID = QStringLiteral("pid");
0047 
0048 void ConfigView::refreshUI()
0049 {
0050     // first false then true to make sure a layout is set
0051     m_useBottomLayout = false;
0052     resizeEvent(nullptr);
0053     m_useBottomLayout = true;
0054     resizeEvent(nullptr);
0055 }
0056 
0057 std::optional<QJsonDocument> loadJSON(const QString &path)
0058 {
0059     QFile file(path);
0060     if (!file.open(QIODevice::ReadOnly)) {
0061         return std::nullopt;
0062     }
0063     QJsonParseError error;
0064     const auto json = QJsonDocument::fromJson(file.readAll(), &error);
0065     file.close();
0066     if (error.error != QJsonParseError::NoError) {
0067         return std::nullopt;
0068     }
0069     return json;
0070 }
0071 
0072 ConfigView::ConfigView(QWidget *parent, KTextEditor::MainWindow *mainWin, KatePluginGDB *plugin)
0073     : QWidget(parent)
0074     , m_mainWindow(mainWin)
0075 {
0076     m_clientCombo = new QComboBox(this);
0077     m_clientCombo->setEditable(false);
0078     m_clientCombo->addItem(QStringLiteral("GDB"));
0079     m_clientCombo->insertSeparator(1);
0080     m_dapConfigPath = plugin->configPath();
0081     readDAPSettings();
0082 
0083     m_targetCombo = new QComboBox(this);
0084     m_targetCombo->setEditable(true);
0085     // don't let Qt insert items when the user edits; new targets are only
0086     // added when the user explicitly says so
0087     m_targetCombo->setInsertPolicy(QComboBox::NoInsert);
0088     m_targetCombo->setDuplicatesEnabled(true);
0089 
0090     m_addTarget = new QToolButton(this);
0091     m_addTarget->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0092     m_addTarget->setToolTip(i18n("Add new target"));
0093 
0094     m_copyTarget = new QToolButton(this);
0095     m_copyTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0096     m_copyTarget->setToolTip(i18n("Copy target"));
0097 
0098     m_deleteTarget = new QToolButton(this);
0099     m_deleteTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0100     m_deleteTarget->setToolTip(i18n("Delete target"));
0101 
0102     m_line = new QFrame(this);
0103     m_line->setFrameShadow(QFrame::Sunken);
0104 
0105     m_execLabel = new QLabel(i18n("Executable:"));
0106     m_execLabel->setBuddy(m_targetCombo);
0107 
0108     m_executable = new QLineEdit(this);
0109     QCompleter *completer1 = new QCompleter(this);
0110     QFileSystemModel *model = new QFileSystemModel(this);
0111     model->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
0112     completer1->setModel(model);
0113     m_executable->setCompleter(completer1);
0114     m_executable->setClearButtonEnabled(true);
0115     m_browseExe = new QToolButton(this);
0116     m_browseExe->setIcon(QIcon::fromTheme(QStringLiteral("application-x-executable")));
0117 
0118     m_workingDirectory = new QLineEdit(this);
0119     QCompleter *completer2 = new QCompleter(this);
0120     QFileSystemModel *model2 = new QFileSystemModel(completer2);
0121 
0122     completer2->setModel(model2);
0123     m_workingDirectory->setCompleter(completer2);
0124     m_workingDirectory->setClearButtonEnabled(true);
0125     m_workDirLabel = new QLabel(i18n("Working Directory:"));
0126     m_workDirLabel->setBuddy(m_workingDirectory);
0127     m_browseDir = new QToolButton(this);
0128     m_browseDir->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory")));
0129 
0130     m_processId = new QSpinBox(this);
0131     m_processId->setMinimum(1);
0132     m_processId->setMaximum(4194304);
0133     m_processIdLabel = new QLabel(i18n("Process Id:"), this);
0134     m_processIdLabel->setBuddy(m_processId);
0135 
0136     m_arguments = new QLineEdit(this);
0137     m_arguments->setClearButtonEnabled(true);
0138     m_argumentsLabel = new QLabel(i18nc("Program argument list", "Arguments:"));
0139     m_argumentsLabel->setBuddy(m_arguments);
0140 
0141     m_takeFocus = new QCheckBox(i18nc("Checkbox to for keeping focus on the command line", "Keep focus"));
0142     m_takeFocus->setToolTip(i18n("Keep the focus on the command line"));
0143 
0144     m_redirectTerminal = new QCheckBox(i18n("Redirect IO"));
0145     m_redirectTerminal->setToolTip(i18n("Redirect the debugged programs IO to a separate tab"));
0146 
0147     m_advancedSettings = new QPushButton(i18n("Advanced Settings"));
0148 
0149     m_checBoxLayout = nullptr;
0150 
0151     // ensure layout is set
0152     refreshUI();
0153 
0154     m_advanced = new AdvancedGDBSettings(this);
0155     m_advanced->hide();
0156 
0157     connect(m_targetCombo, &QComboBox::editTextChanged, this, &ConfigView::slotTargetEdited);
0158     connect(m_targetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ConfigView::slotTargetSelected);
0159     connect(m_addTarget, &QToolButton::clicked, this, &ConfigView::slotAddTarget);
0160     connect(m_copyTarget, &QToolButton::clicked, this, &ConfigView::slotCopyTarget);
0161     connect(m_deleteTarget, &QToolButton::clicked, this, &ConfigView::slotDeleteTarget);
0162     connect(m_browseExe, &QToolButton::clicked, this, &ConfigView::slotBrowseExec);
0163     connect(m_browseDir, &QToolButton::clicked, this, &ConfigView::slotBrowseDir);
0164     connect(m_redirectTerminal, &QCheckBox::toggled, this, &ConfigView::showIO);
0165     connect(m_advancedSettings, &QPushButton::clicked, this, &ConfigView::slotAdvancedClicked);
0166 
0167     connect(m_clientCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigView::refreshUI);
0168 }
0169 
0170 ConfigView::~ConfigView()
0171 {
0172 }
0173 
0174 void ConfigView::readDAPSettings()
0175 {
0176     // read servers file
0177     const auto json = loadJSON(QStringLiteral(":/debugger/dap.json"));
0178     if (!json)
0179         return;
0180 
0181     auto baseObject = json->object();
0182 
0183     {
0184         const QString settingsPath = m_dapConfigPath.toLocalFile();
0185 
0186         const auto userJson = loadJSON(settingsPath);
0187         if (userJson) {
0188             baseObject = json::merge(baseObject, userJson->object());
0189         }
0190     }
0191 
0192     const auto servers = baseObject[QStringLiteral("dap")].toObject();
0193 
0194     int index = m_clientCombo->count();
0195 
0196     for (auto itServer = servers.constBegin(); itServer != servers.constEnd(); ++itServer) {
0197         const auto server = itServer.value().toObject();
0198         const auto jsonProfiles = dap::settings::expandConfigurations(server);
0199         if (!jsonProfiles)
0200             continue;
0201 
0202         QHash<QString, DAPAdapterSettings> profiles;
0203 
0204         for (auto itProfile = jsonProfiles->constBegin(); itProfile != jsonProfiles->constEnd(); ++itProfile) {
0205             profiles[itProfile.key()] = DAPAdapterSettings();
0206             DAPAdapterSettings &conf = profiles[itProfile.key()];
0207             conf.settings = itProfile->toObject();
0208             conf.index = index++;
0209 
0210             QSet<QString> variables;
0211             json::findVariables(conf.settings, variables);
0212 
0213             for (const auto &var : variables) {
0214                 if (var.startsWith(QStringLiteral("#")))
0215                     continue;
0216                 conf.variables.append(var);
0217             }
0218 
0219             m_clientCombo->addItem(QStringLiteral("%1 | %2").arg(itServer.key()).arg(itProfile.key()), conf.variables);
0220         }
0221 
0222         m_dapAdapterSettings[itServer.key()] = profiles;
0223 
0224         m_clientCombo->insertSeparator(index++);
0225     }
0226 }
0227 
0228 void ConfigView::registerActions(KActionCollection *actionCollection)
0229 {
0230     m_targetSelectAction = actionCollection->add<KSelectAction>(QStringLiteral("targets"));
0231     m_targetSelectAction->setText(i18n("Targets"));
0232     connect(m_targetSelectAction, &KSelectAction::indexTriggered, this, &ConfigView::slotTargetSelected);
0233 }
0234 
0235 void upgradeConfigV1_3(QStringList &targetConfStrs)
0236 {
0237     if (targetConfStrs.count() == 3) {
0238         // valid old style config, translate it now; note the
0239         // reordering happening here!
0240         QStringList temp;
0241         temp << targetConfStrs[2];
0242         temp << targetConfStrs[1];
0243         targetConfStrs.swap(temp);
0244     }
0245 }
0246 
0247 void upgradeConfigV3_4(QStringList &targetConfStrs, const QStringList &args)
0248 {
0249     targetConfStrs.prepend(targetConfStrs[0].right(15));
0250 
0251     const QString targetName(QStringLiteral("%1<%2>"));
0252 
0253     for (int i = 0; i < args.size(); ++i) {
0254         const QString &argStr = args.at(i);
0255         if (i > 0) {
0256             // copy the firsts and change the arguments
0257             targetConfStrs[0] = targetName.arg(targetConfStrs[0]).arg(i + 1);
0258             if (targetConfStrs.count() > 3) {
0259                 targetConfStrs[3] = argStr;
0260             }
0261         }
0262     }
0263 }
0264 
0265 void upgradeConfigV4_5(QStringList targetConfStrs, QJsonObject &conf)
0266 {
0267     typedef ConfigView::TargetStringOrder I;
0268 
0269     while (targetConfStrs.count() < I::CustomStartIndex) {
0270         targetConfStrs << QString();
0271     }
0272 
0273     auto insertField = [&conf, targetConfStrs](const QString &field, I index) {
0274         const QString value = targetConfStrs[index].trimmed();
0275         if (!value.isEmpty()) {
0276             conf[field] = value;
0277         }
0278     };
0279 
0280     // read fields
0281     insertField(F_TARGET, I::NameIndex);
0282     insertField(F_FILE, I::ExecIndex);
0283     insertField(F_WORKDIR, I::WorkDirIndex);
0284     insertField(F_ARGS, I::ArgsIndex);
0285     // read advanced settings
0286     for (int i = 0; i < I::GDBIndex; ++i) {
0287         targetConfStrs.takeFirst();
0288     }
0289     const auto advanced = AdvancedGDBSettings::upgradeConfigV4_5(targetConfStrs);
0290     if (!advanced.isEmpty()) {
0291         conf[QStringLiteral("advanced")] = advanced;
0292     }
0293 }
0294 
0295 QByteArray serialize(const QJsonObject obj)
0296 {
0297     const QJsonDocument doc(obj);
0298     return doc.toJson(QJsonDocument::Compact);
0299 }
0300 
0301 QJsonObject unserialize(const QString map)
0302 {
0303     const auto doc = QJsonDocument::fromJson(map.toLatin1());
0304     return doc.object();
0305 }
0306 
0307 void ConfigView::readConfig(const KConfigGroup &group)
0308 {
0309     m_targetCombo->clear();
0310 
0311     const int version = group.readEntry(QStringLiteral("version"), CONFIG_VERSION);
0312     const int targetCount = group.readEntry(QStringLiteral("targetCount"), 1);
0313     int lastTarget = group.readEntry(QStringLiteral("lastTarget"), 0);
0314     const QString targetKey(QStringLiteral("target_%1"));
0315 
0316     QStringList args;
0317     if (version < 4) {
0318         const int argsListsCount = group.readEntry(QStringLiteral("argsCount"), 0);
0319         const QString argsKey(QStringLiteral("args_%1"));
0320         const QString targetName(QStringLiteral("%1<%2>"));
0321 
0322         for (int nArg = 0; nArg < argsListsCount; ++nArg) {
0323             const QString argStr = group.readEntry(argsKey.arg(nArg), QString());
0324         }
0325     }
0326 
0327     for (int i = 0; i < targetCount; i++) {
0328         QJsonObject targetConf;
0329 
0330         if (version < 5) {
0331             QStringList targetConfStrs;
0332             targetConfStrs = group.readEntry(targetKey.arg(i), QStringList());
0333             if (targetConfStrs.count() == 0) {
0334                 continue;
0335             }
0336 
0337             if (version == 1) {
0338                 upgradeConfigV1_3(targetConfStrs);
0339             }
0340 
0341             if (version < 4) {
0342                 upgradeConfigV3_4(targetConfStrs, args);
0343             }
0344             if (version < 5) {
0345                 upgradeConfigV4_5(targetConfStrs, targetConf);
0346             }
0347         } else {
0348             const QString data = group.readEntry(targetKey.arg(i), QString());
0349             targetConf = unserialize(data);
0350         }
0351 
0352         if (!targetConf.isEmpty()) {
0353             m_targetCombo->addItem(targetConf[QStringLiteral("target")].toString(), targetConf);
0354         }
0355     }
0356 
0357     // make sure there is at least one item.
0358     if (m_targetCombo->count() == 0) {
0359         slotAddTarget();
0360     }
0361 
0362     QStringList targetNames;
0363     for (int i = 0; i < m_targetCombo->count(); i++) {
0364         targetNames << m_targetCombo->itemText(i);
0365     }
0366     m_targetSelectAction->setItems(targetNames);
0367 
0368     if (lastTarget < 0 || lastTarget >= m_targetCombo->count()) {
0369         lastTarget = 0;
0370     }
0371     m_targetCombo->setCurrentIndex(lastTarget);
0372 
0373     m_takeFocus->setChecked(group.readEntry("alwaysFocusOnInput", false));
0374 
0375     m_redirectTerminal->setChecked(group.readEntry("redirectTerminal", false));
0376 }
0377 
0378 void ConfigView::writeConfig(KConfigGroup &group)
0379 {
0380     // make sure the data is up to date before writing
0381     saveCurrentToIndex(m_currentTarget);
0382 
0383     group.writeEntry("version", CONFIG_VERSION);
0384 
0385     QString targetKey(QStringLiteral("target_%1"));
0386 
0387     group.writeEntry("targetCount", m_targetCombo->count());
0388     group.writeEntry("lastTarget", m_targetCombo->currentIndex());
0389     for (int i = 0; i < m_targetCombo->count(); i++) {
0390         QJsonObject targetConf = m_targetCombo->itemData(i).toJsonObject();
0391         group.writeEntry(targetKey.arg(i), serialize(targetConf));
0392     }
0393 
0394     group.writeEntry("alwaysFocusOnInput", m_takeFocus->isChecked());
0395     group.writeEntry("redirectTerminal", m_redirectTerminal->isChecked());
0396 }
0397 
0398 const GDBTargetConf ConfigView::currentGDBTarget() const
0399 {
0400     GDBTargetConf cfg;
0401     cfg.targetName = m_targetCombo->currentText();
0402     cfg.executable = m_executable->text();
0403     cfg.workDir = m_workingDirectory->text();
0404     cfg.arguments = m_arguments->text();
0405 
0406     const auto advancedConfig = m_advanced->configs();
0407     {
0408         cfg.gdbCmd = advancedConfig[AdvancedGDBSettings::F_GDB].toString(QStringLiteral("gdb"));
0409         cfg.srcPaths.clear();
0410         for (const auto &value : advancedConfig[AdvancedGDBSettings::F_SRC_PATHS].toArray()) {
0411             cfg.srcPaths << value.toString();
0412         }
0413         cfg.customInit = AdvancedGDBSettings::commandList(advancedConfig);
0414     }
0415 
0416     return cfg;
0417 }
0418 
0419 const DAPTargetConf ConfigView::currentDAPTarget(bool full) const
0420 {
0421     DAPTargetConf cfg;
0422     cfg.targetName = m_targetCombo->currentText();
0423 
0424     const int comboIndex = m_clientCombo->currentIndex();
0425     bool found = false;
0426     // find config
0427     for (auto itS = m_dapAdapterSettings.constBegin(); !found && itS != m_dapAdapterSettings.constEnd(); ++itS) {
0428         for (auto itP = itS->constBegin(); itP != itS->constEnd(); ++itP) {
0429             if (itP->index == comboIndex) {
0430                 cfg.debugger = itS.key();
0431                 cfg.debuggerProfile = itP.key();
0432                 if (full) {
0433                     cfg.dapSettings = itP.value();
0434                 }
0435                 found = true;
0436                 break;
0437             }
0438         }
0439     }
0440     const QStringList &variables = m_clientCombo->currentData().toStringList();
0441     for (const auto &field : variables) {
0442         // file
0443         if (field == F_FILE) {
0444             cfg.variables[F_FILE] = m_executable->text();
0445             // working dir
0446         } else if (field == F_WORKDIR) {
0447             cfg.variables[F_WORKDIR] = m_workingDirectory->text();
0448             // pid
0449         } else if (field == F_PID) {
0450             cfg.variables[F_PID] = m_processId->value();
0451             // arguments
0452         } else if (field == F_ARGS) {
0453             cfg.variables[F_ARGS] = m_arguments->text();
0454             // other
0455         } else if (m_dapFields.contains(field)) {
0456             cfg.variables[field] = m_dapFields[field].input->text();
0457         }
0458     }
0459     return cfg;
0460 }
0461 
0462 bool ConfigView::takeFocusAlways() const
0463 {
0464     return m_takeFocus->isChecked();
0465 }
0466 
0467 bool ConfigView::showIOTab() const
0468 {
0469     return m_redirectTerminal->isChecked();
0470 }
0471 
0472 void ConfigView::slotTargetEdited(const QString &newText)
0473 {
0474     QString newComboText(newText);
0475     for (int i = 0; i < m_targetCombo->count(); ++i) {
0476         if (i != m_targetCombo->currentIndex() && m_targetCombo->itemText(i) == newComboText) {
0477             newComboText = newComboText + QStringLiteral(" 2");
0478         }
0479     }
0480     int cursorPosition = m_targetCombo->lineEdit()->cursorPosition();
0481     m_targetCombo->setItemText(m_targetCombo->currentIndex(), newComboText);
0482     m_targetCombo->lineEdit()->setCursorPosition(cursorPosition);
0483 
0484     // rebuild the target menu
0485     QStringList targets;
0486     for (int i = 0; i < m_targetCombo->count(); ++i) {
0487         targets.append(m_targetCombo->itemText(i));
0488     }
0489     m_targetSelectAction->setItems(targets);
0490     m_targetSelectAction->setCurrentItem(m_targetCombo->currentIndex());
0491 }
0492 
0493 void ConfigView::slotTargetSelected(int index)
0494 {
0495     if ((index < 0) || (index >= m_targetCombo->count())) {
0496         return;
0497     }
0498 
0499     if ((m_currentTarget > 0) && (m_currentTarget < m_targetCombo->count())) {
0500         saveCurrentToIndex(m_currentTarget);
0501     }
0502 
0503     const int clientIndex = loadFromIndex(index);
0504     if (clientIndex < 0)
0505         return;
0506 
0507     m_currentTarget = index;
0508 
0509     if (clientIndex == 0) {
0510         setAdvancedOptions();
0511     }
0512 
0513     // Keep combo box and menu in sync
0514     m_targetCombo->setCurrentIndex(index);
0515     m_targetSelectAction->setCurrentItem(index);
0516 
0517     m_clientCombo->setCurrentIndex(clientIndex);
0518 }
0519 
0520 void ConfigView::slotAddTarget()
0521 {
0522     QJsonObject targetConf;
0523 
0524     targetConf[F_TARGET] = i18n("Target %1", m_targetCombo->count() + 1);
0525 
0526     m_targetCombo->addItem(targetConf[F_TARGET].toString(), targetConf);
0527     m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1);
0528 }
0529 
0530 void ConfigView::slotCopyTarget()
0531 {
0532     QJsonObject tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toJsonObject();
0533     if (tmp.isEmpty()) {
0534         slotAddTarget();
0535         return;
0536     }
0537     tmp[F_TARGET] = i18n("Target %1", m_targetCombo->count() + 1);
0538     m_targetCombo->addItem(tmp[F_TARGET].toString(), tmp);
0539     m_targetCombo->setCurrentIndex(m_targetCombo->count() - 1);
0540 }
0541 
0542 void ConfigView::slotDeleteTarget()
0543 {
0544     m_targetCombo->blockSignals(true);
0545     int currentIndex = m_targetCombo->currentIndex();
0546     m_targetCombo->removeItem(currentIndex);
0547     if (m_targetCombo->count() == 0) {
0548         slotAddTarget();
0549     }
0550 
0551     const int clientIndex = loadFromIndex(m_targetCombo->currentIndex());
0552     m_targetCombo->blockSignals(false);
0553 
0554     if (clientIndex >= 0) {
0555         m_clientCombo->setCurrentIndex(clientIndex);
0556     }
0557 }
0558 
0559 bool ConfigView::debuggerIsGDB() const
0560 {
0561     return m_clientCombo->currentIndex() == 0;
0562 }
0563 
0564 void ConfigView::resizeEvent(QResizeEvent *)
0565 {
0566     const bool toVertical = m_useBottomLayout && size().height() > size().width();
0567     const bool toHorizontal = !m_useBottomLayout && (size().height() < size().width());
0568 
0569     if (!toVertical && !toHorizontal)
0570         return;
0571 
0572     const bool is_dbg = debuggerIsGDB();
0573     const QStringList debuggerVariables = m_clientCombo->currentData().toStringList();
0574 
0575     // check if preformatted inputs are required
0576     m_advancedSettings->setVisible(is_dbg);
0577     const bool needsExe = is_dbg || debuggerVariables.contains(F_FILE);
0578     const bool needsWdir = is_dbg || debuggerVariables.contains(F_WORKDIR);
0579     const bool needsArgs = is_dbg || debuggerVariables.contains(F_ARGS);
0580     const bool needsPid = !is_dbg && debuggerVariables.contains(F_PID);
0581 
0582     if (toVertical) {
0583         // Set layout for the side
0584         delete m_checBoxLayout;
0585         m_checBoxLayout = nullptr;
0586         delete layout();
0587         QGridLayout *layout = new QGridLayout(this);
0588         layout->setContentsMargins(0, 0, 0, 0);
0589 
0590         layout->addWidget(m_clientCombo, 0, 0);
0591         layout->addWidget(m_targetCombo, 1, 0);
0592         layout->addWidget(m_addTarget, 1, 1);
0593         layout->addWidget(m_copyTarget, 1, 2);
0594         layout->addWidget(m_deleteTarget, 1, 3);
0595         m_line->setFrameShape(QFrame::HLine);
0596         layout->addWidget(m_line, 2, 0, 1, 4);
0597 
0598         int row = 3;
0599 
0600         if (needsExe) {
0601             layout->addWidget(m_execLabel, ++row, 0, Qt::AlignLeft);
0602             layout->addWidget(m_executable, ++row, 0, 1, 3);
0603             layout->addWidget(m_browseExe, row, 3);
0604         }
0605 
0606         if (needsWdir) {
0607             layout->addWidget(m_workDirLabel, ++row, 0, Qt::AlignLeft);
0608             layout->addWidget(m_workingDirectory, ++row, 0, 1, 3);
0609             layout->addWidget(m_browseDir, row, 3);
0610         }
0611 
0612         if (needsArgs) {
0613             layout->addWidget(m_argumentsLabel, ++row, 0, Qt::AlignLeft);
0614             layout->addWidget(m_arguments, ++row, 0, 1, 4);
0615         }
0616 
0617         if (needsPid) {
0618             layout->addWidget(m_processIdLabel, ++row, 0, Qt::AlignLeft);
0619             layout->addWidget(m_processId, ++row, 0, 1, 4);
0620         }
0621 
0622         for (const auto &fieldName : debuggerVariables) {
0623             if (fieldName == F_FILE)
0624                 continue;
0625             if (fieldName == F_ARGS)
0626                 continue;
0627             if (fieldName == F_PID)
0628                 continue;
0629             if (fieldName == F_WORKDIR)
0630                 continue;
0631 
0632             const auto &field = getDapField(fieldName);
0633 
0634             layout->addWidget(field.label, ++row, 0, Qt::AlignLeft);
0635             layout->addWidget(field.input, ++row, 0, 1, 4);
0636         }
0637 
0638         layout->addWidget(m_takeFocus, ++row, 0, 1, 4);
0639         layout->addWidget(m_redirectTerminal, ++row, 0, 1, 4);
0640         if (is_dbg) {
0641             layout->addWidget(m_advancedSettings, ++row, 0, 1, 4);
0642         }
0643 
0644         layout->addItem(new QSpacerItem(1, 1), ++row, 0);
0645         layout->setColumnStretch(0, 1);
0646         layout->setRowStretch(row, 1);
0647 
0648         m_useBottomLayout = false;
0649     } else if (toHorizontal) {
0650         // Set layout for the bottom
0651         delete m_checBoxLayout;
0652         delete layout();
0653         m_checBoxLayout = new QHBoxLayout();
0654         m_checBoxLayout->addWidget(m_takeFocus, 10);
0655         m_checBoxLayout->addWidget(m_redirectTerminal, 10);
0656         if (is_dbg) {
0657             m_checBoxLayout->addWidget(m_advancedSettings, 0);
0658         }
0659 
0660         QGridLayout *layout = new QGridLayout(this);
0661         layout->setContentsMargins(0, 0, 0, 0);
0662         layout->addWidget(m_clientCombo, 0, 0, 1, 6);
0663 
0664         layout->addWidget(m_targetCombo, 1, 0, 1, 3);
0665 
0666         layout->addWidget(m_addTarget, 2, 0);
0667         layout->addWidget(m_copyTarget, 2, 1);
0668         layout->addWidget(m_deleteTarget, 2, 2);
0669 
0670         int row = 0;
0671 
0672         if (needsExe) {
0673             layout->addWidget(m_execLabel, ++row, 5, Qt::AlignRight);
0674             layout->addWidget(m_executable, row, 6);
0675             layout->addWidget(m_browseExe, row, 7);
0676         }
0677 
0678         if (needsWdir) {
0679             layout->addWidget(m_workDirLabel, ++row, 5, Qt::AlignRight);
0680             layout->addWidget(m_workingDirectory, row, 6);
0681             layout->addWidget(m_browseDir, row, 7);
0682         }
0683 
0684         if (needsArgs) {
0685             layout->addWidget(m_argumentsLabel, ++row, 5, Qt::AlignRight);
0686             layout->addWidget(m_arguments, row, 6, 1, 2);
0687         }
0688 
0689         if (needsPid) {
0690             layout->addWidget(m_processIdLabel, ++row, 5, Qt::AlignRight);
0691             layout->addWidget(m_processId, row, 6);
0692         }
0693 
0694         for (const auto &fieldName : debuggerVariables) {
0695             if (fieldName == F_FILE)
0696                 continue;
0697             if (fieldName == F_ARGS)
0698                 continue;
0699             if (fieldName == F_PID)
0700                 continue;
0701             if (fieldName == F_WORKDIR)
0702                 continue;
0703 
0704             const auto &field = getDapField(fieldName);
0705 
0706             layout->addWidget(field.label, ++row, 5, Qt::AlignRight);
0707             layout->addWidget(field.input, row, 6);
0708         }
0709 
0710         layout->addLayout(m_checBoxLayout, ++row, 5, 1, 3);
0711 
0712         layout->addItem(new QSpacerItem(1, 1), ++row, 0);
0713         layout->setColumnStretch(6, 100);
0714         layout->setRowStretch(row, 100);
0715 
0716         m_line->setFrameShape(QFrame::VLine);
0717         layout->addWidget(m_line, 1, 3, row - 1, 1);
0718 
0719         m_useBottomLayout = true;
0720     }
0721 
0722     if (toVertical || toHorizontal) {
0723         m_advancedSettings->setVisible(is_dbg);
0724 
0725         // exe
0726         m_execLabel->setVisible(needsExe);
0727         m_executable->setVisible(needsExe);
0728         m_browseExe->setVisible(needsExe);
0729 
0730         // working dir
0731         m_workDirLabel->setVisible(needsWdir);
0732         m_workingDirectory->setVisible(needsWdir);
0733         m_browseDir->setVisible(needsWdir);
0734 
0735         // arguments
0736         m_argumentsLabel->setVisible(needsArgs);
0737         m_arguments->setVisible(needsArgs);
0738 
0739         // pid
0740         m_processIdLabel->setVisible(needsPid);
0741         m_processId->setVisible(needsPid);
0742 
0743         // additional dap fields
0744         for (auto it = m_dapFields.cbegin(); it != m_dapFields.cend(); ++it) {
0745             const bool visible = debuggerVariables.contains(it.key());
0746             it->label->setVisible(visible);
0747             it->input->setVisible(visible);
0748         }
0749     }
0750 }
0751 
0752 ConfigView::Field &ConfigView::getDapField(const QString &fieldName)
0753 {
0754     if (!m_dapFields.contains(fieldName)) {
0755         m_dapFields[fieldName] = Field{new QLabel(fieldName, this), new QLineEdit(this)};
0756     }
0757     return m_dapFields[fieldName];
0758 }
0759 
0760 void ConfigView::setAdvancedOptions()
0761 {
0762     const QJsonObject tmp = m_targetCombo->itemData(m_targetCombo->currentIndex()).toJsonObject();
0763 
0764     QJsonObject advanced = tmp[QStringLiteral("advanced")].toObject();
0765     const auto strGdb = advanced[QStringLiteral("gdb")].toString();
0766     if (strGdb.isEmpty()) {
0767         advanced[QStringLiteral("gdb")] = QStringLiteral("gdb");
0768     }
0769 
0770     m_advanced->setConfigs(advanced);
0771 }
0772 
0773 void ConfigView::slotAdvancedClicked()
0774 {
0775     setAdvancedOptions();
0776 
0777     QJsonObject conf = m_targetCombo->itemData(m_targetCombo->currentIndex()).toJsonObject();
0778 
0779     // Remove old advanced settings
0780     if (m_advanced->exec() == QDialog::Accepted) {
0781         // save the new values
0782         conf[QStringLiteral("advanced")] = m_advanced->configs();
0783         m_targetCombo->setItemData(m_targetCombo->currentIndex(), conf);
0784         Q_EMIT configChanged();
0785     }
0786 }
0787 
0788 void ConfigView::slotBrowseExec()
0789 {
0790     QString exe = m_executable->text();
0791 
0792     if (m_executable->text().isEmpty()) {
0793         // try current document dir
0794         KTextEditor::View *view = m_mainWindow->activeView();
0795 
0796         if (view != nullptr) {
0797             exe = view->document()->url().toLocalFile();
0798         }
0799     }
0800     m_executable->setText(QFileDialog::getOpenFileName(nullptr, QString(), exe, QStringLiteral("application/x-executable")));
0801 }
0802 
0803 void ConfigView::slotBrowseDir()
0804 {
0805     QString dir = m_workingDirectory->text();
0806 
0807     if (m_workingDirectory->text().isEmpty()) {
0808         // try current document dir
0809         KTextEditor::View *view = m_mainWindow->activeView();
0810 
0811         if (view != nullptr) {
0812             dir = view->document()->url().toLocalFile();
0813         }
0814     }
0815     m_workingDirectory->setText(QFileDialog::getExistingDirectory(this, QString(), dir));
0816 }
0817 
0818 void ConfigView::saveCurrentToIndex(int index)
0819 {
0820     if ((index < 0) || (index >= m_targetCombo->count())) {
0821         return;
0822     }
0823 
0824     QJsonObject tmp = m_targetCombo->itemData(index).toJsonObject();
0825 
0826     tmp[F_TARGET] = m_targetCombo->itemText(index);
0827     if (debuggerIsGDB()) {
0828         if (tmp.contains(F_DEBUGGER))
0829             tmp.remove(F_DEBUGGER);
0830         if (tmp.contains(F_PROFILE))
0831             tmp.remove(F_PROFILE);
0832         tmp[F_FILE] = m_executable->text();
0833         tmp[F_WORKDIR] = m_workingDirectory->text();
0834         tmp[F_ARGS] = m_arguments->text();
0835     } else {
0836         const auto cfg = currentDAPTarget();
0837         tmp[F_DEBUGGER] = cfg.debugger;
0838         tmp[F_PROFILE] = cfg.debuggerProfile;
0839         tmp[QStringLiteral("variables")] = QJsonObject::fromVariantHash(cfg.variables);
0840     }
0841 
0842     m_targetCombo->setItemData(index, tmp);
0843 }
0844 
0845 int ConfigView::loadFromIndex(int index)
0846 {
0847     if ((index < 0) || (index >= m_targetCombo->count())) {
0848         return -1;
0849     }
0850 
0851     QJsonObject tmp = m_targetCombo->itemData(index).toJsonObject();
0852     // The custom init strings are set in slotAdvancedClicked().
0853 
0854     const QString &debuggerKey = tmp[F_DEBUGGER].toString();
0855     if (debuggerKey.isNull() || debuggerKey.isEmpty()) {
0856         // GDB
0857         m_executable->setText(tmp[F_FILE].toString());
0858         m_workingDirectory->setText(tmp[F_WORKDIR].toString());
0859         m_arguments->setText(tmp[F_ARGS].toString());
0860 
0861         return 0;
0862     } else {
0863         // DAP
0864         if (!m_dapAdapterSettings.contains(debuggerKey))
0865             return -1;
0866         const QString &debuggerProfile = tmp[F_PROFILE].toString();
0867         if (!m_dapAdapterSettings[debuggerKey].contains(debuggerProfile))
0868             return -1;
0869         auto map = tmp[QStringLiteral("variables")].toObject();
0870 
0871         m_executable->setText(map[F_FILE].toString());
0872         map.remove(F_FILE);
0873         m_workingDirectory->setText(map[F_WORKDIR].toString());
0874         map.remove(F_WORKDIR);
0875         m_arguments->setText(map[F_ARGS].toString());
0876         map.remove(F_ARGS);
0877         m_processId->setValue(map[F_PID].toInt());
0878         map.remove(F_PID);
0879 
0880         for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
0881             const auto &field = getDapField(it.key());
0882             field.input->setText(it.value().toString());
0883         }
0884 
0885         return m_dapAdapterSettings[debuggerKey][debuggerProfile].index;
0886     }
0887 }
0888 
0889 #include "moc_configview.cpp"