#include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include "flottenupdatersettings.h" #include "devicesmodel.h" #include "requestdialog.h" #include "setarbitraryapikeydialog.h" #include "addserialsrangedialog.h" MainWindow::MainWindow(FlottenUpdaterSettings &settings, const QSslKey &key, const QSslCertificate &cert, QWidget *parent) : QMainWindow{parent}, m_ui{std::make_unique()}, m_settings{settings}, m_model{std::make_unique(settings, key, cert, this)}, m_proxyModel{std::make_unique(this)} { m_ui->setupUi(this); m_proxyModel->setSourceModel(m_model.get()); m_proxyModel->setSortRole(Qt::EditRole); m_ui->treeView->setModel(m_proxyModel.get()); connect(m_ui->pushButtonConnectAll, &QAbstractButton::pressed, m_model.get(), &DevicesModel::connectAll); connect(m_ui->pushButtonDisconnectAll, &QAbstractButton::pressed, m_model.get(), &DevicesModel::disconnectAll); connect(m_ui->pushButtonAdd, &QAbstractButton::pressed, this, &MainWindow::doAdd); connect(m_ui->pushButtonRemove, &QAbstractButton::pressed, this, &MainWindow::doRemove); connect(m_ui->pushButtonAddRange, &QAbstractButton::pressed, this, &MainWindow::doAddRange); connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::selectionChanged); m_ui->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui->treeView->header(), &QTreeView::customContextMenuRequested, this, &MainWindow::headerContextMenuRequested); connect(m_ui->treeView, &QTreeView::customContextMenuRequested, this, &MainWindow::contextMenuRequested); selectionChanged(); } MainWindow::~MainWindow() = default; void MainWindow::doAdd() { bool ok{}; const auto serial = QInputDialog::getText(this, tr("Serial"), tr("Serial"), QLineEdit::Normal, {}, &ok); if (!ok) return; if (m_model->addClient(serial)) m_settings.setSerials(m_model->serials()); else QMessageBox::warning(this, tr("Error while adding serial!"), tr("Error while adding serial!")); } void MainWindow::doRemove() { auto selectedRows = m_ui->treeView->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) return; // map proxied indices to those from the model std::transform(std::begin(selectedRows), std::end(selectedRows), std::begin(selectedRows), [&](const QModelIndex &index){ return m_proxyModel->mapToSource(index); }); removeRows(std::move(selectedRows)); } void MainWindow::doAddRange() { AddSerialsRangeDialog dialog{this}; if (dialog.exec() != QDialog::Accepted) return; int succeeded{}, failed{}; for (auto serial = dialog.from(); serial <= dialog.to(); serial++) { if (m_model->addClient(QString::number(serial))) succeeded++; else { qWarning() << "adding serial" << serial << "failed"; failed++; } } if (succeeded > 0) m_settings.setSerials(m_model->serials()); if (failed > 0) QMessageBox::warning(this, tr("Error while adding!"), tr("%0 rows could not be adding!").arg(failed)); } void MainWindow::selectionChanged() { auto count = m_ui->treeView->selectionModel()->selectedRows().count(); m_ui->pushButtonRemove->setEnabled(count > 0); m_ui->statusbar->showMessage(tr("%0 selected").arg(m_ui->treeView->selectionModel()->selectedRows().count())); } void MainWindow::headerContextMenuRequested(const QPoint &pos) { QMenu menu; auto addColumnAction = menu.addAction(tr("Add new column...")); QAction *removeColumnAction{}; auto header = m_ui->treeView->header(); const auto section = header->logicalIndexAt(pos); if (section >= 0) { removeColumnAction = menu.addAction(tr("Remove column %0").arg(header->model()->headerData(section, Qt::Horizontal).toString())); if (!m_model->customColumnRemovable(section)) removeColumnAction->setEnabled(false); } if (auto selectedAction = menu.exec(header->mapToGlobal(pos)); selectedAction == addColumnAction) { bool ok{}; auto apiKey = QInputDialog::getText(this, tr("Enter api key"), tr("Api key:"), QLineEdit::Normal, {}, &ok); if (!ok) return; m_model->addCustomColumn(apiKey); } else if (removeColumnAction && selectedAction == removeColumnAction) { m_model->removeCustomColumn(section); } } void MainWindow::contextMenuRequested(const QPoint &pos) { auto selectedRows = m_ui->treeView->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) return; // map proxied indices to those from the model std::transform(std::begin(selectedRows), std::end(selectedRows), std::begin(selectedRows), [&](const QModelIndex &index){ return m_proxyModel->mapToSource(index); }); // get all the devices for selected indices std::vector> devices; devices.reserve(selectedRows.size()); std::transform(std::begin(selectedRows), std::end(selectedRows), std::back_inserter(devices), [&](const QModelIndex &index){ auto device = m_model->getDevice(index); Q_ASSERT(device); return device; }); Q_ASSERT(std::all_of(std::begin(devices), std::end(devices), [](const auto &device)->bool{ return device.get(); })); QMenu menu; auto actionSetUpdateUrl = menu.addAction(tr("Set update url...")); auto actionStartUpdate = menu.addAction(tr("Start update...")); auto actionReboot = menu.addAction(tr("Reboot...")); auto actionSetChargectrlOverride = menu.addAction(tr("Set chargectrl override...")); auto actionSetAbitraryApiKey = menu.addAction(tr("Set abitrary api key...")); auto resetNvsKey = menu.addAction(tr("Reset nvs key...")); auto actionOpenApps = menu.addAction(tr("Open app(s)...")); auto actionRemove = menu.addAction(tr("Remove...")); if (const auto selected = menu.exec(m_ui->treeView->viewport()->mapToGlobal(pos)); selected == actionSetUpdateUrl) { bool ok{}; const auto url = QInputDialog::getText(this, tr("Enter update url..."), tr("Update url:"), QLineEdit::Normal, {}, &ok); if (!ok) return; QJsonObject msg { { "type", "setValue" }, { "key", "ocu" }, { "value", url }, { "sudo", true } }; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } else if (selected == actionStartUpdate) { QString branch; { QInputDialog dialog{this}; dialog.setWindowTitle(tr("Select update release...")); dialog.setInputMode(QInputDialog::TextInput); dialog.setLabelText(tr("Update release")); dialog.setComboBoxEditable(true); dialog.setComboBoxItems({QStringLiteral("__default")}); if (dialog.exec() != QDialog::Accepted) return; branch = dialog.textValue(); } QJsonObject msg { { "type", "setValue" }, { "key", "oct" }, { "value", branch }, { "sudo", true } }; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } else if (selected == actionReboot) { if (const auto result = QMessageBox::question(this, tr("Are you sure?"), tr("Do you really want to reboot selected devices?")); result == QMessageBox::Yes) { QJsonObject msg { { "type", "setValue" }, { "key", "rst" }, { "value", 1 }, { "sudo", true } }; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } } else if (selected == actionSetChargectrlOverride) { QJsonObject msg { { "type", "setValue" }, { "key", "stao" }, { "value", QJsonObject { { "car", 2 }, { "error", 0 }, { "phases", QJsonArray { true, true, true, true, true, true } }, { "temperature", 30 }, { "voltageL1", 230 }, { "voltageL2", 230 }, { "voltageL3", 230 }, { "voltageN", 0 }, { "ampereL1", 32 }, { "ampereL2", 32 }, { "ampereL3", 32 }, { "powerL1", 7360 }, { "powerL2", 7360 }, { "powerL3", 7360 }, { "powerN", 0 }, { "powerTotal", 22080 }, { "powerFactorL1", 90 }, { "powerFactorL2", 90 }, { "powerFactorL3", 90 }, { "powerFactorN", 0 } } }, { "sudo", true } }; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } else if (selected == actionSetAbitraryApiKey) { SetArbitraryApiKeyDialog dialog{this}; if (dialog.exec() == QDialog::Accepted) { QJsonObject msg { { "type", "setValue" }, { "key", dialog.apiKey() }, { "value", dialog.value() } }; if (dialog.sudo()) msg["sudo"] = true; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } } else if (selected == resetNvsKey) { bool ok{}; auto nvsKey = QInputDialog::getText(this, tr("Please input nvs key to be reset"), tr("Nvs key"), QLineEdit::Normal, {}, &ok); if (!ok) return; QJsonObject msg { { "type", "nvsReset" }, { "key", nvsKey }, { "sudo", true } }; RequestDialog{std::move(msg), std::move(devices), this}.exec(); } else if (selected == actionOpenApps) { } else if (selected == actionRemove) { removeRows(std::move(selectedRows)); } } void MainWindow::removeRows(QModelIndexList &&indexes) { if (QMessageBox::question( this, tr("Confirm deletion of devices"), tr("Do you really want to remove %0 devices?").arg(indexes.count()) ) != QMessageBox::Yes) return; int succeeded{}, failed{}; std::sort(indexes.begin(), indexes.end(), [](const QModelIndex &a, const QModelIndex &b) { return a.row() > b.row(); }); for (const QModelIndex &index : std::as_const(indexes)) if (m_model->removeRows(index.row(), 1, index.parent())) succeeded++; else { failed++; qWarning() << "removing row" << index.row() << "failed"; } if (succeeded > 0) m_settings.setSerials(m_model->serials()); if (failed > 0) QMessageBox::warning(this, tr("Error while removing!"), tr("%0 rows could not be removed!").arg(failed)); }