Files
evcharger-app/flotten-updater/mainwindow.cpp

322 lines
11 KiB
C++

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QInputDialog>
#include <QSortFilterProxyModel>
#include <QMenu>
#include <QInputDialog>
#include <QMessageBox>
#include <QJsonObject>
#include <QJsonArray>
#include <algorithm>
#include <utility>
#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<Ui::MainWindow>()},
m_settings{settings},
m_model{std::make_unique<DevicesModel>(settings, key, cert, this)},
m_proxyModel{std::make_unique<QSortFilterProxyModel>(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<std::shared_ptr<DevicesConnection>> 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));
}