SceneSwitcher/plugins/mqtt/topic-selection.cpp
2025-05-27 20:23:07 +02:00

285 lines
7.0 KiB
C++

#include "topic-selection.hpp"
#include "layout-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "ui-helpers.hpp"
#include <QHeaderView>
#include <QMessageBox>
namespace advss {
MqttTopicListWidget::MqttTopicListWidget(QWidget *parent)
: QWidget(parent),
_table(new QTableWidget(0, 2, this)),
_controls(new ListControls(this, false))
{
_table->setHorizontalHeaderLabels(
QStringList()
<< obs_module_text("AdvSceneSwitcher.mqttConnection.topic")
<< obs_module_text("AdvSceneSwitcher.mqttConnection.qos"));
_table->horizontalHeader()->setStretchLastSection(true);
_table->setSelectionBehavior(QAbstractItemView::SelectRows);
_table->setSelectionMode(QAbstractItemView::SingleSelection);
_table->setEditTriggers(QAbstractItemView::DoubleClicked |
QAbstractItemView::SelectedClicked);
_table->verticalHeader()->hide();
_table->horizontalHeader()->setSectionResizeMode(0,
QHeaderView::Stretch);
_table->horizontalHeader()->setSectionResizeMode(
1, QHeaderView::ResizeToContents);
connect(_table, &QTableWidget::itemChanged, this,
&MqttTopicListWidget::ModifyRow);
connect(_controls, &ListControls::Add, this,
&MqttTopicListWidget::AddTopic);
connect(_controls, &ListControls::Remove, this,
&MqttTopicListWidget::RemoveSelectedRow);
auto mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(_table);
mainLayout->addWidget(_controls);
setLayout(mainLayout);
}
void MqttTopicListWidget::SetValues(const std::vector<std::string> &topics,
const std::vector<int> &qos)
{
assert(topics.size() == qos.size());
if (topics.size() != qos.size()) {
blog(LOG_WARNING, "%s topics and qos size mismatch", __func__);
return;
}
_table->setRowCount(0);
_topicSet.clear();
std::vector<std::pair<QString, int>> entries;
for (size_t i = 0; i < topics.size(); ++i) {
auto topic = QString::fromStdString(topics[i]).trimmed();
int qos_ = qos[i];
if (topic.isEmpty()) {
blog(LOG_INFO, "%s ignoring empty topic entry",
__func__);
continue;
}
if (_topicSet.contains(topic)) {
blog(LOG_INFO, "%s discarding duplicate topic \"%s\"",
__func__, topics[i].c_str());
continue;
}
if (qos_ < 0 || qos_ > 2) {
blog(LOG_INFO,
"%s discard invalid QoS level \"%d\" and set to \"1\"",
__func__, qos[i]);
qos_ = 1;
}
entries.emplace_back(topic, qos_);
_topicSet.insert(topic);
}
for (const auto &entry : entries) {
int row = _table->rowCount();
_table->insertRow(row);
_table->setItem(row, 0, new QTableWidgetItem(entry.first));
_table->setItem(
row, 1,
new QTableWidgetItem(QString::number(entry.second)));
}
SortTable();
}
std::vector<std::string> MqttTopicListWidget::GetTopics()
{
std::vector<std::string> result;
for (int i = 0; i < _table->rowCount(); ++i) {
auto topic = _table->item(i, 0)->text().trimmed();
result.push_back(topic.toStdString());
}
return result;
}
std::vector<int> MqttTopicListWidget::GetQoS()
{
std::vector<int> result;
for (int i = 0; i < _table->rowCount(); ++i) {
bool isValidInt = false;
int qos = _table->item(i, 1)->text().toInt(&isValidInt);
result.push_back(isValidInt ? qos : 1);
}
return result;
}
void MqttTopicListWidget::AddTopic()
{
AddMqttTopicDialog dialog(this);
if (dialog.exec() != QDialog::Accepted) {
return;
}
auto topic = dialog.Topic();
int qos = dialog.QoS();
if (topic.isEmpty()) {
ShowTopicEmptyWarning();
return;
}
if (_topicSet.contains(topic)) {
ShowTopicDuplicateWarning();
return;
}
InsertSorted(topic, qos);
_topicSet.insert(topic);
}
void MqttTopicListWidget::InsertSorted(const QString &topic, int qos)
{
int row = 0;
while (row < _table->rowCount()) {
QString current = _table->item(row, 0)->text();
if (topic.compare(current, Qt::CaseInsensitive) < 0)
break;
++row;
}
_table->insertRow(row);
_table->setItem(row, 0, new QTableWidgetItem(topic));
_table->setItem(row, 1, new QTableWidgetItem(QString::number(qos)));
}
void MqttTopicListWidget::SortTable()
{
_table->sortItems(0, Qt::AscendingOrder);
}
void MqttTopicListWidget::ShowTopicDuplicateWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic"));
}
void MqttTopicListWidget::ShowTopicEmptyWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic"));
}
void MqttTopicListWidget::ShowQoSRangeWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.qosRange.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.qosRange"));
}
void MqttTopicListWidget::RemoveSelectedRow()
{
auto selected = _table->currentRow();
if (selected >= 0) {
QString topic = _table->item(selected, 0)->text();
_topicSet.remove(topic);
_table->removeRow(selected);
}
}
void MqttTopicListWidget::ModifyRow(QTableWidgetItem *item)
{
if (!item) {
return;
}
int row = item->row();
auto tableItemTopic = _table->item(row, 0);
auto tableItemQos = _table->item(row, 1);
if (!tableItemTopic || !tableItemQos) {
return;
}
QString topic = tableItemTopic->text().trimmed();
QString qosStr = tableItemQos->text().trimmed();
// Validate topic column
if (item->column() == 0) {
QString oldTopic = _topicSet.values().value(row);
if (topic.isEmpty()) {
ShowTopicEmptyWarning();
item->setText(oldTopic);
return;
}
if (_topicSet.contains(topic) && topic != oldTopic) {
ShowTopicDuplicateWarning();
item->setText(oldTopic);
return;
}
_topicSet.remove(oldTopic);
_topicSet.insert(topic);
SortTable();
}
// Validate QoS column
if (item->column() == 1) {
bool isValidInt;
int qos = qosStr.toInt(&isValidInt);
if (!isValidInt || qos < 0 || qos > 2) {
ShowQoSRangeWarning();
item->setText("1");
}
}
SortTable();
}
AddMqttTopicDialog::AddMqttTopicDialog(QWidget *parent)
: QDialog(parent),
_topicEdit(new QLineEdit(this)),
_qosSpin(new QSpinBox(this))
{
setWindowTitle(
obs_module_text("AdvSceneSwitcher.mqttConnection.add.title"));
_topicEdit->setText("/#");
_qosSpin->setRange(0, 2);
_qosSpin->setValue(1);
auto buttonbox = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
buttonbox->setCenterButtons(true);
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto inputLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.mqttConnection.add.layout"),
inputLayout, {{"{{topic}}", _topicEdit}, {"{{QoS}}", _qosSpin}},
false);
auto layout = new QVBoxLayout();
layout->addLayout(inputLayout);
layout->addWidget(buttonbox);
setLayout(layout);
}
QString AddMqttTopicDialog::Topic() const
{
return _topicEdit->text().trimmed();
}
int AddMqttTopicDialog::QoS() const
{
return _qosSpin->value();
}
} // namespace advss