1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "binarytoolsconfigview.h"
6#include "binarytoolssetting.h"
7#include "environmentview.h"
8
9#include "common/widget/collapsewidget.h"
10#include "common/util/custompaths.h"
11#include "common/dialog/contextdialog.h"
12
13#include <QComboBox>
14#include <QPushButton>
15#include <QLineEdit>
16#include <QFileDialog>
17#include <QFont>
18#include <QInputDialog>
19#include <qmessagebox.h>
20#include <QGridLayout>
21#include <QLabel>
22#include <QStandardPaths>
23#include <QScrollArea>
24#include <QJsonArray>
25#include <QJsonDocument>
26#include <QJsonObject>
27
28static QString CURRENT_COMMAND = "Current command";
29static QString ALL_COMMAND = "All command";
30static QString ENVIRONMENT = "Environment";
31
32class BinaryToolsConfigViewPrivate
33{
34 friend class BinaryToolsConfigView;
35
36 QGridLayout *gridLayout = nullptr;
37 QWidget *compatConfigWidget = nullptr;
38 QComboBox *runComandCombo = nullptr;
39 QLineEdit *toolArgsEdit = nullptr;
40 QDialog *combinationDialog = nullptr;
41 QLabel *commandCombination = nullptr;
42 QLabel *nameLabel = nullptr;
43 QLabel *commandLabel = nullptr;
44 QLineEdit *executableDirEdit = nullptr;
45 QLineEdit *workingDirEdit = nullptr;
46 EnvironmentView *envView = nullptr;
47 QPushButton *addButton = nullptr;
48 QPushButton *deleteButton = nullptr;
49 QPushButton *renameButton = nullptr;
50 QPushButton *combineButton = nullptr;
51 QPushButton *useCombinationButton = nullptr;
52 BinaryToolsSetting *settings = nullptr;
53 QList<QString> programList;
54 QList<QStringList> argsList;
55 QList<QString> workingDirList;
56 QList<QMap<QString, QVariant>> envList;
57};
58
59BinaryToolsConfigView::BinaryToolsConfigView(QWidget *parent)
60 : QWidget(parent)
61 , d(new BinaryToolsConfigViewPrivate)
62{
63 d->compatConfigWidget = new QWidget(this);
64 d->nameLabel = new QLabel();
65
66 d->runComandCombo = new QComboBox(this);
67 d->runComandCombo->setMinimumContentsLength(15);
68 d->runComandCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
69
70 d->addButton = new QPushButton(tr("Add"), this);
71 d->deleteButton = new QPushButton(tr("Delete"), this);
72 d->renameButton = new QPushButton(tr("Rename"), this);
73 d->combineButton = new QPushButton(tr("Combine"), this);
74
75 d->gridLayout = new QGridLayout(this);
76 d->gridLayout->setSpacing(6);
77 d->gridLayout->setContentsMargins(10, 10, 10, 10);
78
79 auto configLabel = new QLabel(this);
80 configLabel->setText(tr("Binary configuration:"));
81
82 d->gridLayout->addWidget(configLabel, 0, 0, 1, 1);
83 d->gridLayout->addWidget(d->runComandCombo, 0, 1, 1, 1);
84 d->gridLayout->addWidget(d->addButton, 0, 2, 1, 1);
85 d->gridLayout->addWidget(d->deleteButton, 0, 3, 1, 1);
86 d->gridLayout->addWidget(d->renameButton, 0, 4, 1, 1);
87 d->gridLayout->addWidget(d->combineButton, 0, 5, 1, 1);
88 d->gridLayout->addWidget(d->compatConfigWidget, 1, 0, 1, 6);
89
90 setConfigWidget();
91
92 d->combinationDialog = new QDialog(this);
93 d->commandCombination = new QLabel(tr("command combination:"), d->combinationDialog);
94 d->useCombinationButton = new QPushButton(tr("Use Combination Command"), d->combinationDialog);
95 initializeCombinationDialog();
96
97 if (!d->settings) {
98 QString iniPath = CustomPaths::user(CustomPaths::Flags::Configures) + QDir::separator() + QString("binarytools.ini");
99 bool setDefaultVaule = false;
100 if (!QFile::exists(iniPath)) {
101 setDefaultVaule = true;
102 }
103 d->settings = new BinaryToolsSetting(iniPath, this);
104 if (setDefaultVaule) {
105 initializeCombo();
106 }
107 }
108 readConfig();
109
110 connect(d->runComandCombo, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
111 this, &BinaryToolsConfigView::currentConfigChanged);
112
113 connect(d->addButton, &QPushButton::clicked,
114 this, &BinaryToolsConfigView::addCompatConfig);
115
116 connect(d->deleteButton, &QPushButton::clicked,
117 this, &BinaryToolsConfigView::deleteCompatConfig);
118
119 connect(d->renameButton, &QPushButton::clicked,
120 this, &BinaryToolsConfigView::renameCompatConfig);
121
122 connect(d->combineButton, &QPushButton::clicked,
123 this, &BinaryToolsConfigView::combineCompatConfig);
124}
125
126BinaryToolsConfigView::~BinaryToolsConfigView()
127{
128 if (d)
129 delete d;
130}
131
132bool BinaryToolsConfigView::saveConfig()
133{
134 QString curCommand = d->runComandCombo->currentText();
135 if (curCommand.isEmpty())
136 return false;
137
138 if (d->executableDirEdit->text().isEmpty()) {
139 QMessageBox msgBox(QMessageBox::Warning, tr("Warning"),
140 tr("Please select working directory."),
141 QMessageBox::Ok, d->compatConfigWidget);
142 msgBox.exec();
143 return false;
144 }
145
146 if (d->toolArgsEdit->text().isEmpty())
147 d->toolArgsEdit->setText("");
148 QStringList commandList = QStringList() << d->executableDirEdit->text() << d->toolArgsEdit->text()
149 << d->nameLabel->text() << d->workingDirEdit->text();
150
151 d->settings->setValue(CURRENT_COMMAND, curCommand);
152 d->settings->setValue(curCommand, commandList);
153 d->settings->setValue(curCommand + ENVIRONMENT, d->envView->getEnvironment());
154 return true;
155}
156
157void BinaryToolsConfigView::readConfig()
158{
159 QStringList allCommand = qvariant_cast<QStringList>(d->settings->getValue(ALL_COMMAND));
160 for (QString command : allCommand) {
161 if (d->runComandCombo->findText(command) == -1)
162 d->runComandCombo->addItem(command);
163 }
164
165 QString curCommand = qvariant_cast<QString>(d->settings->getValue(CURRENT_COMMAND, ""));
166 if (curCommand.isEmpty())
167 return;
168 int index = d->runComandCombo->findText(curCommand);
169 d->runComandCombo->setCurrentIndex(index);
170 updateView(curCommand);
171}
172
173QList<QString> BinaryToolsConfigView::getProgramList()
174{
175 saveConfig();
176 if (d->programList.isEmpty())
177 d->programList.push_back(d->executableDirEdit->text());
178 return d->programList;
179}
180
181QList<QStringList> BinaryToolsConfigView::getArgumentsList()
182{
183 if (d->argsList.isEmpty())
184 d->argsList.push_back(d->toolArgsEdit->text().split(" "));
185 return d->argsList;
186}
187
188QList<QString> BinaryToolsConfigView::getWorkingDirList()
189{
190 if (d->workingDirList.isEmpty())
191 d->workingDirList.push_back(d->workingDirEdit->text());
192 return d->workingDirList;
193}
194
195QList<QMap<QString, QVariant>> BinaryToolsConfigView::getEnvironmentList()
196{
197 if (d->envList.isEmpty())
198 d->envList.push_back(d->envView->getEnvironment());
199 return d->envList;
200}
201
202void BinaryToolsConfigView::updateView(const QString &command)
203{
204 if (command.isEmpty()) {
205 d->nameLabel->setText("");
206 d->commandLabel->setText("");
207 d->toolArgsEdit->setText("");
208 d->executableDirEdit->setText("");
209 d->workingDirEdit->setText("");
210 d->envView->setValue({});
211 return;
212 }
213
214 BinaryToolsSetting settings;
215 QStringList argList = qvariant_cast<QStringList>(d->settings->getValue(command, "/usr/bin/" + command));
216 if (argList.isEmpty())
217 return;
218 d->executableDirEdit->setText(argList.at(0));
219 d->toolArgsEdit->setText(argList.size() > 1 ? argList.at(1) : "");
220 d->nameLabel->setText(argList.size() > 2 ? argList.at(2) : "");
221 d->workingDirEdit->setText(argList.size() > 3 ? argList.at(3) : "");
222 d->commandLabel->setText(d->executableDirEdit->text() + " " + d->toolArgsEdit->text());
223
224 QMap<QString, QVariant> map = qvariant_cast<QMap<QString, QVariant>>(d->settings->getValue(command + ENVIRONMENT));
225 d->envView->setValue(map);
226}
227
228void BinaryToolsConfigView::currentConfigChanged(const QString &text)
229{
230 if (d->settings->getValue(CURRENT_COMMAND).toString() == text)
231 return;
232
233 d->settings->setValue(CURRENT_COMMAND, text);
234 updateView(text);
235 emit comboChanged();
236}
237
238void BinaryToolsConfigView::initializeCombo()
239{
240 QString fileName = CustomPaths::global(CustomPaths::Flags::Configures)
241 + QDir::separator() + QString("binarytool.support");
242 QFile file(fileName);
243 if (file.open(QIODevice::ReadOnly)) {
244 QByteArray data = file.readAll();
245 file.close();
246
247 QJsonDocument doc = QJsonDocument::fromJson(data);
248 QJsonArray array = doc.array();
249 for (auto node : array) {
250 auto obj = node.toObject();
251 QString name = obj.value("name").toString();
252 appendCommand(name);
253 }
254 }
255}
256
257void BinaryToolsConfigView::initializeCombinationDialog()
258{
259 QRect parentRect = this->geometry();
260 int parentX = parentRect.x();
261 int parentY = parentRect.y();
262 int parentWidth = parentRect.width();
263 int parentHeight = parentRect.height();
264 QRect dialogRect = d->combinationDialog->geometry();
265 int dialogWidth = dialogRect.width();
266 int dialogHeight = dialogRect.height();
267 int x = parentX + (parentWidth - dialogWidth) / 2;
268 int y = parentY + (parentHeight - dialogHeight) / 2;
269 setGeometry(x, y, dialogWidth, dialogHeight);
270
271 auto gridLayout = new QGridLayout(d->combinationDialog);
272 d->combinationDialog->setLayout(gridLayout);
273 gridLayout->setSpacing(30);
274 gridLayout->setContentsMargins(30, 30, 30, 30);
275 gridLayout->addWidget(d->commandCombination, 0, 0, 1, 2);
276 gridLayout->addWidget(d->useCombinationButton, 1, 1, 1, 1);
277 QObject::connect(d->useCombinationButton, &QPushButton::clicked, [=](){
278 d->combinationDialog->close();
279 emit useCombinationCommand();
280 });
281}
282
283void BinaryToolsConfigView::addCompatConfig()
284{
285 if (!d->runComandCombo->currentText().isEmpty() && !saveConfig())
286 return;
287
288 bool ok;
289 QString name = QInputDialog::getText(this, tr("Add new command"),
290 tr("New command name:"),
291 QLineEdit::Normal, "", &ok);
292 if (!ok)
293 return;
294
295 appendCommand(name);
296}
297
298void BinaryToolsConfigView::deleteCompatConfig()
299{
300 bool doSave = false, doCancle = false;
301 auto ok = [&](bool checked) {
302 Q_UNUSED(checked);
303 doSave = true;
304 };
305 auto no = [&](bool checked) {
306 Q_UNUSED(checked);
307 doSave = false;
308 };
309 auto cancel = [&](bool checked) {
310 Q_UNUSED(checked);
311 };
312
313 ContextDialog::question(tr("Delete Configuration?"),
314 tr("Do you really want to delete the command <b>%1</b>?").arg(d->nameLabel->text()),
315 QMessageBox::Question,
316 ok, no, cancel);
317 if (!doSave || doCancle)
318 return;
319
320 QStringList allCommand = qvariant_cast<QStringList>(d->settings->getValue(ALL_COMMAND));
321 allCommand.removeOne(d->runComandCombo->currentText());
322 d->settings->setValue(ALL_COMMAND, allCommand);
323 d->settings->deleteKey(d->runComandCombo->currentText());
324 d->settings->deleteKey(d->runComandCombo->currentText() + ENVIRONMENT);
325
326 int curIndex = d->runComandCombo->currentIndex();
327 d->runComandCombo->removeItem(curIndex);
328 d->runComandCombo->setCurrentIndex((curIndex - 1) >= 0 ? curIndex - 1 : 0);
329 if (d->runComandCombo->currentText().isEmpty())
330 d->deleteButton->setEnabled(false);
331}
332
333void BinaryToolsConfigView::renameCompatConfig()
334{
335 bool ok;
336 QString name = QInputDialog::getText(this, tr("Rename..."),
337 tr("New name for command <b>%1</b>:").
338 arg(d->runComandCombo->currentText()),
339 QLineEdit::Normal,
340 d->runComandCombo->currentText(), &ok);
341 if (!ok)
342 return;
343
344 if (name == d->runComandCombo->currentText())
345 return;
346
347 QString uniName = uniqueName(name);
348 if (uniName.isEmpty())
349 return;
350
351 QStringList allCommand = qvariant_cast<QStringList>(d->settings->getValue(ALL_COMMAND));
352 int index = allCommand.indexOf(d->runComandCombo->currentText());
353 if (index != -1) {
354 allCommand.replace(index, uniName);
355 } else {
356 allCommand.append(uniName);
357 }
358 d->settings->setValue(CURRENT_COMMAND, uniName);
359 d->settings->setValue(ALL_COMMAND, allCommand);
360 d->nameLabel->setText(name);
361 QStringList commandList = QStringList() << d->executableDirEdit->text()<< d->toolArgsEdit->text()
362 << d->nameLabel->text() << d->workingDirEdit->text();
363 d->settings->setValue(uniName, commandList);
364 d->settings->setValue(uniName + ENVIRONMENT, d->envView->getEnvironment());
365 d->settings->deleteKey(d->runComandCombo->currentText());
366 d->settings->deleteKey(d->runComandCombo->currentText() + ENVIRONMENT);
367
368 int itemIndex = d->runComandCombo->currentIndex();
369 d->runComandCombo->insertItem(itemIndex + 1, uniName);
370 d->runComandCombo->setCurrentText(uniName);
371 d->runComandCombo->removeItem(itemIndex);
372}
373
374void BinaryToolsConfigView::combineCompatConfig()
375{
376 saveConfig();
377 d->combinationDialog->showNormal();
378 d->programList.push_back(d->executableDirEdit->text());
379 d->argsList.push_back(d->toolArgsEdit->text().split(" "));
380 d->workingDirList.push_back(d->workingDirEdit->text());
381 d->envList.push_back(d->envView->getEnvironment());
382 d->commandCombination->setText(d->commandCombination->text() + " " + d->nameLabel->text());
383}
384
385void BinaryToolsConfigView::setConfigWidget()
386{
387 d->compatConfigWidget->setContentsMargins(0, 0, 0, 0);
388 d->compatConfigWidget->setAutoFillBackground(true);
389 auto cmdLabel = new QLabel(d->compatConfigWidget);
390 QFont ft;
391 ft.setBold(true);
392 cmdLabel->setFont(ft);
393 cmdLabel->setText(tr("Command:"));
394 d->commandLabel = new QLabel(d->compatConfigWidget);
395
396 auto argsLabel = new QLabel(d->compatConfigWidget);
397 argsLabel->setText(tr("Tool arguments:"));
398 d->toolArgsEdit = new QLineEdit(d->compatConfigWidget);
399 d->toolArgsEdit->setPlaceholderText(tr("Input your arguments."));
400
401 auto exeLabel = new QLabel(d->compatConfigWidget);
402 exeLabel->setText(tr("Executable:"));
403 d->executableDirEdit = new QLineEdit(d->compatConfigWidget);
404 auto browseButton1 = new QPushButton(tr("Browse..."), d->compatConfigWidget);
405
406 auto workLabel = new QLabel(d->compatConfigWidget);
407 workLabel->setText(tr("Working directory:"));
408 d->workingDirEdit = new QLineEdit(d->compatConfigWidget);
409 auto browseButton2 = new QPushButton(tr("Browse..."), d->compatConfigWidget);
410
411 auto envLabel = new QLabel(d->compatConfigWidget);
412 envLabel->setText(tr("Envrioment: Base environment for this command configuration."));
413 auto appendButton = new QPushButton(tr("&Append"), d->compatConfigWidget);
414 auto deleteButton = new QPushButton(tr("&Delete"), d->compatConfigWidget);
415 auto resetButton = new QPushButton(tr("&Reset"), d->compatConfigWidget);
416 deleteButton->setEnabled(false);
417 d->envView = new EnvironmentView();
418 d->envView->setFixedHeight(250);
419
420 auto gridLayout = new QGridLayout(d->compatConfigWidget);
421 gridLayout->setSpacing(5);
422 gridLayout->setContentsMargins(5, 5, 5, 5);
423
424 gridLayout->addWidget(cmdLabel, 0, 0, 1, 1);
425 gridLayout->addWidget(d->commandLabel, 0, 1, 1, 2);
426 gridLayout->addWidget(argsLabel, 1, 0, 1, 1);
427 gridLayout->addWidget(d->toolArgsEdit, 1, 1, 1, 2);
428 gridLayout->addWidget(exeLabel, 2, 0, 1, 1);
429 gridLayout->addWidget(d->executableDirEdit, 2, 1, 1, 1);
430 gridLayout->addWidget(browseButton1, 2, 2, 1, 1);
431 gridLayout->addWidget(workLabel, 3, 0, 1, 1);
432 gridLayout->addWidget(d->workingDirEdit, 3, 1, 1, 1);
433 gridLayout->addWidget(browseButton2, 3, 2, 1, 1);
434 gridLayout->addWidget(envLabel, 4, 0, 1, 3);
435 gridLayout->addWidget(appendButton, 5, 0, 1, 1);
436 gridLayout->addWidget(deleteButton, 6, 0, 1, 1);
437 gridLayout->addWidget(resetButton, 7, 0, 1, 1);
438 gridLayout->addWidget(d->envView, 5, 1, 5, 2);
439 d->compatConfigWidget->setLayout(gridLayout);
440
441 connect(browseButton1, &QPushButton::clicked, [=]() {
442 QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
443 QString filePath = QFileDialog::getOpenFileName(nullptr, tr("Select Executabel Path"), dir);
444 if (filePath.isEmpty() && !QFileInfo(filePath).exists())
445 return;
446 d->executableDirEdit->setText(filePath);
447 });
448
449 connect(browseButton2, &QPushButton::clicked, [=]() {
450 QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
451 QString filePath = QFileDialog::getExistingDirectory(nullptr, tr("Select Working Directory"), dir);
452 if (filePath.isEmpty() && !QFileInfo(filePath).exists())
453 return;
454 d->workingDirEdit->setText(filePath);
455 });
456
457 connect(appendButton, &QPushButton::clicked, d->envView, &EnvironmentView::appendRow);
458 connect(deleteButton, &QPushButton::clicked, d->envView, &EnvironmentView::deleteRow);
459 connect(resetButton, &QPushButton::clicked, d->envView, &EnvironmentView::initModel);
460
461 connect(d->toolArgsEdit, &QLineEdit::textChanged, [=](){
462 d->commandLabel->setText(d->executableDirEdit->text() + " " + d->toolArgsEdit->text());
463 });
464
465 connect(d->executableDirEdit, &QLineEdit::textChanged, [=](){
466 d->commandLabel->setText(d->executableDirEdit->text() + " " + d->toolArgsEdit->text());
467 });
468
469 connect(d->envView, &EnvironmentView::deleteSignal, [=](bool enable){
470 deleteButton->setEnabled(enable);
471 });
472
473 connect(this, &BinaryToolsConfigView::comboChanged, [=]{
474 deleteButton->setEnabled(false);
475 });
476}
477
478void BinaryToolsConfigView::appendCommand(const QString &name)
479{
480 QString uniName = uniqueName(name);
481 if (uniName.isEmpty())
482 return;
483
484 QStringList allCommand = qvariant_cast<QStringList>(d->settings->getValue(ALL_COMMAND));
485 allCommand.append(uniName);
486 d->settings->setValue(CURRENT_COMMAND, uniName);
487 d->settings->setValue(ALL_COMMAND, allCommand);
488
489 d->nameLabel->setText(name);
490 d->toolArgsEdit->setText("");
491 d->executableDirEdit->setText("/usr/bin/" + name);
492 d->workingDirEdit->setText(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
493 d->envView->initModel();
494 QStringList commandList = QStringList() << d->executableDirEdit->text()<< d->toolArgsEdit->text()
495 << d->nameLabel->text() << d->workingDirEdit->text();
496 d->settings->setValue(uniName, commandList);
497 d->settings->setValue(uniName + ENVIRONMENT, d->envView->getEnvironment());
498
499 //call currentConfigChanged()
500 d->runComandCombo->addItem(uniName);
501 d->runComandCombo->setCurrentText(uniName);
502 d->deleteButton->setEnabled(true);
503}
504
505QString BinaryToolsConfigView::uniqueName(const QString &name)
506{
507 QString result = name.trimmed();
508 if (!result.isEmpty()) {
509 if (d->runComandCombo->findText(result) == -1)
510 return result;
511 int i = 2;
512
513 QString tryName = result + QString::number(i);
514 while (d->runComandCombo->findText(tryName) != -1) {
515 tryName = result + QString::number(++i);
516 }
517 return tryName;
518 }
519 return result;
520}
521