GUI M8f: Event-driven updates, deferred link restoration, routing rules UI
This commit is contained in:
parent
e8d3f63f4d
commit
b819d6fd65
6 changed files with 440 additions and 121 deletions
|
|
@ -14,10 +14,14 @@
|
|||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGraphicsItem>
|
||||
#include <QGuiApplication>
|
||||
#include <QInputDialog>
|
||||
|
|
@ -285,6 +289,18 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
m_sidebar->addTab(m_mixerScroll, QStringLiteral("MIXER"));
|
||||
m_sidebar->addTab(presetsTab, QStringLiteral("PRESETS"));
|
||||
|
||||
m_rulesScroll = new QScrollArea();
|
||||
m_rulesScroll->setWidgetResizable(true);
|
||||
m_rulesScroll->setStyleSheet(m_mixerScroll->styleSheet());
|
||||
m_rulesContainer = new QWidget();
|
||||
m_rulesContainer->setStyleSheet(QStringLiteral("background: #1a1a1e;"));
|
||||
auto *rulesLayout = new QVBoxLayout(m_rulesContainer);
|
||||
rulesLayout->setContentsMargins(8, 8, 8, 8);
|
||||
rulesLayout->setSpacing(6);
|
||||
rulesLayout->addStretch();
|
||||
m_rulesScroll->setWidget(m_rulesContainer);
|
||||
m_sidebar->addTab(m_rulesScroll, QStringLiteral("RULES"));
|
||||
|
||||
m_splitter = new QSplitter(Qt::Horizontal);
|
||||
m_splitter->addWidget(m_view);
|
||||
m_splitter->addWidget(m_sidebar);
|
||||
|
|
@ -405,6 +421,7 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
wireVolumeWidget(nodeId);
|
||||
rebuildMixerStrips();
|
||||
rebuildNodeMeters();
|
||||
rebuildRulesList();
|
||||
});
|
||||
connect(m_model, &QtNodes::AbstractGraphModel::nodeDeleted, this,
|
||||
[this](QtNodes::NodeId nodeId) {
|
||||
|
|
@ -412,6 +429,7 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
m_nodeMeters.erase(nodeId);
|
||||
rebuildMixerStrips();
|
||||
rebuildNodeMeters();
|
||||
rebuildRulesList();
|
||||
});
|
||||
|
||||
m_saveTimer = new QTimer(this);
|
||||
|
|
@ -421,6 +439,7 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
&GraphEditorWidget::saveLayoutWithViewState);
|
||||
|
||||
m_model->refreshFromClient();
|
||||
rebuildRulesList();
|
||||
if (!hasLayout) {
|
||||
m_model->autoArrange();
|
||||
}
|
||||
|
|
@ -435,10 +454,24 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this,
|
||||
&GraphEditorWidget::saveLayoutWithViewState);
|
||||
|
||||
m_changeTimer = new QTimer(this);
|
||||
m_changeTimer->setSingleShot(true);
|
||||
m_changeTimer->setInterval(50);
|
||||
connect(m_changeTimer, &QTimer::timeout, this,
|
||||
&GraphEditorWidget::onRefreshTimer);
|
||||
|
||||
if (m_client) {
|
||||
m_client->SetChangeCallback([this] {
|
||||
QMetaObject::invokeMethod(m_changeTimer,
|
||||
qOverload<>(&QTimer::start),
|
||||
Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
m_refreshTimer = new QTimer(this);
|
||||
connect(m_refreshTimer, &QTimer::timeout, this,
|
||||
&GraphEditorWidget::onRefreshTimer);
|
||||
m_refreshTimer->start(500);
|
||||
m_refreshTimer->start(2000);
|
||||
|
||||
m_meterTimer = new QTimer(this);
|
||||
m_meterTimer->setTimerType(Qt::PreciseTimer);
|
||||
|
|
@ -1373,3 +1406,186 @@ void GraphEditorWidget::rebuildNodeMeters() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEditorWidget::rebuildRulesList() {
|
||||
if (!m_rulesContainer || !m_client)
|
||||
return;
|
||||
|
||||
auto *layout = m_rulesContainer->layout();
|
||||
if (!layout)
|
||||
return;
|
||||
|
||||
while (layout->count() > 0) {
|
||||
auto *item = layout->takeAt(0);
|
||||
if (item->widget())
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
|
||||
const QString labelStyle = QStringLiteral(
|
||||
"QLabel { color: #a0a8b6; font-size: 11px; background: transparent; }");
|
||||
const QString valueStyle = QStringLiteral(
|
||||
"QLabel { color: #ecf0f6; font-size: 12px; background: transparent; }");
|
||||
const QString btnStyle = QStringLiteral(
|
||||
"QPushButton { background: #2e2e36; color: #ecf0f6; border: 1px solid #3a3a44;"
|
||||
" border-radius: 4px; padding: 6px 12px; }"
|
||||
"QPushButton:hover { background: #3a3a44; }"
|
||||
"QPushButton:pressed { background: #44444e; }");
|
||||
const QString delBtnStyle = QStringLiteral(
|
||||
"QPushButton { background: transparent; color: #a05050; border: none;"
|
||||
" font-size: 14px; font-weight: bold; padding: 2px 6px; }"
|
||||
"QPushButton:hover { color: #e05050; }");
|
||||
|
||||
auto *header = new QLabel(QStringLiteral("ROUTING RULES"));
|
||||
header->setStyleSheet(QStringLiteral(
|
||||
"QLabel { color: #a0a8b6; font-size: 11px; font-weight: bold;"
|
||||
" background: transparent; }"));
|
||||
layout->addWidget(header);
|
||||
|
||||
auto rulesResult = m_client->ListRouteRules();
|
||||
if (rulesResult.ok()) {
|
||||
for (const auto &rule : rulesResult.value) {
|
||||
auto *card = new QWidget();
|
||||
card->setStyleSheet(QStringLiteral(
|
||||
"QWidget { background: #24242a; border-radius: 4px; }"));
|
||||
auto *cardLayout = new QHBoxLayout(card);
|
||||
cardLayout->setContentsMargins(8, 6, 4, 6);
|
||||
cardLayout->setSpacing(8);
|
||||
|
||||
QString matchText;
|
||||
if (!rule.match.application_name.empty())
|
||||
matchText += QStringLiteral("app: ") +
|
||||
QString::fromStdString(rule.match.application_name);
|
||||
if (!rule.match.process_binary.empty()) {
|
||||
if (!matchText.isEmpty()) matchText += QStringLiteral(", ");
|
||||
matchText += QStringLiteral("bin: ") +
|
||||
QString::fromStdString(rule.match.process_binary);
|
||||
}
|
||||
if (!rule.match.media_role.empty()) {
|
||||
if (!matchText.isEmpty()) matchText += QStringLiteral(", ");
|
||||
matchText += QStringLiteral("role: ") +
|
||||
QString::fromStdString(rule.match.media_role);
|
||||
}
|
||||
|
||||
auto *infoLayout = new QVBoxLayout();
|
||||
infoLayout->setContentsMargins(0, 0, 0, 0);
|
||||
infoLayout->setSpacing(2);
|
||||
|
||||
auto *matchLabel = new QLabel(matchText);
|
||||
matchLabel->setStyleSheet(valueStyle);
|
||||
infoLayout->addWidget(matchLabel);
|
||||
|
||||
auto *targetLabel = new QLabel(
|
||||
QStringLiteral("\xe2\x86\x92 ") +
|
||||
QString::fromStdString(rule.target_node));
|
||||
targetLabel->setStyleSheet(labelStyle);
|
||||
infoLayout->addWidget(targetLabel);
|
||||
|
||||
cardLayout->addLayout(infoLayout, 1);
|
||||
|
||||
auto *delBtn = new QPushButton(QStringLiteral("\xe2\x9c\x95"));
|
||||
delBtn->setFixedSize(24, 24);
|
||||
delBtn->setStyleSheet(delBtnStyle);
|
||||
warppipe::RuleId ruleId = rule.id;
|
||||
connect(delBtn, &QPushButton::clicked, this, [this, ruleId]() {
|
||||
m_client->RemoveRouteRule(ruleId);
|
||||
rebuildRulesList();
|
||||
});
|
||||
cardLayout->addWidget(delBtn);
|
||||
|
||||
layout->addWidget(card);
|
||||
}
|
||||
}
|
||||
|
||||
auto *addBtn = new QPushButton(QStringLiteral("Add Rule..."));
|
||||
addBtn->setStyleSheet(btnStyle);
|
||||
connect(addBtn, &QPushButton::clicked, this,
|
||||
&GraphEditorWidget::showAddRuleDialog);
|
||||
layout->addWidget(addBtn);
|
||||
|
||||
static_cast<QVBoxLayout *>(layout)->addStretch();
|
||||
}
|
||||
|
||||
void GraphEditorWidget::showAddRuleDialog() {
|
||||
if (!m_client)
|
||||
return;
|
||||
|
||||
QDialog dlg(this);
|
||||
dlg.setWindowTitle(QStringLiteral("Add Routing Rule"));
|
||||
dlg.setStyleSheet(QStringLiteral(
|
||||
"QDialog { background: #1e1e22; }"
|
||||
"QLabel { color: #ecf0f6; }"
|
||||
"QLineEdit { background: #2a2a32; color: #ecf0f6; border: 1px solid #3a3a44;"
|
||||
" border-radius: 4px; padding: 4px 8px; }"
|
||||
"QComboBox { background: #2a2a32; color: #ecf0f6; border: 1px solid #3a3a44;"
|
||||
" border-radius: 4px; padding: 4px 8px; }"
|
||||
"QComboBox::drop-down { border: none; }"
|
||||
"QComboBox QAbstractItemView { background: #2a2a32; color: #ecf0f6;"
|
||||
" selection-background-color: #3a3a44; }"));
|
||||
|
||||
auto *form = new QFormLayout(&dlg);
|
||||
form->setContentsMargins(16, 16, 16, 16);
|
||||
form->setSpacing(8);
|
||||
|
||||
auto *appNameEdit = new QLineEdit();
|
||||
appNameEdit->setPlaceholderText(QStringLiteral("e.g. Firefox"));
|
||||
form->addRow(QStringLiteral("Application Name:"), appNameEdit);
|
||||
|
||||
auto *processBinEdit = new QLineEdit();
|
||||
processBinEdit->setPlaceholderText(QStringLiteral("e.g. firefox"));
|
||||
form->addRow(QStringLiteral("Process Binary:"), processBinEdit);
|
||||
|
||||
auto *mediaRoleEdit = new QLineEdit();
|
||||
mediaRoleEdit->setPlaceholderText(QStringLiteral("e.g. Music"));
|
||||
form->addRow(QStringLiteral("Media Role:"), mediaRoleEdit);
|
||||
|
||||
auto *targetCombo = new QComboBox();
|
||||
auto nodesResult = m_client->ListNodes();
|
||||
if (nodesResult.ok()) {
|
||||
for (const auto &node : nodesResult.value) {
|
||||
if (node.media_class.find("Sink") != std::string::npos) {
|
||||
QString label = QString::fromStdString(
|
||||
node.description.empty() ? node.name : node.description);
|
||||
targetCombo->addItem(label, QString::fromStdString(node.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
form->addRow(QStringLiteral("Target Node:"), targetCombo);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background: #2e2e36; color: #ecf0f6; border: 1px solid #3a3a44;"
|
||||
" border-radius: 4px; padding: 6px 16px; }"
|
||||
"QPushButton:hover { background: #3a3a44; }"));
|
||||
connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
|
||||
form->addRow(buttons);
|
||||
|
||||
if (dlg.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
std::string appName = appNameEdit->text().trimmed().toStdString();
|
||||
std::string procBin = processBinEdit->text().trimmed().toStdString();
|
||||
std::string role = mediaRoleEdit->text().trimmed().toStdString();
|
||||
std::string target = targetCombo->currentData().toString().toStdString();
|
||||
|
||||
if (appName.empty() && procBin.empty() && role.empty()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Invalid Rule"),
|
||||
QStringLiteral("At least one match field must be filled."));
|
||||
return;
|
||||
}
|
||||
if (target.empty()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Invalid Rule"),
|
||||
QStringLiteral("A target node must be selected."));
|
||||
return;
|
||||
}
|
||||
|
||||
warppipe::RouteRule rule;
|
||||
rule.match.application_name = appName;
|
||||
rule.match.process_binary = procBin;
|
||||
rule.match.media_role = role;
|
||||
rule.target_node = target;
|
||||
m_client->AddRouteRule(rule);
|
||||
rebuildRulesList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ private:
|
|||
void rebuildMixerStrips();
|
||||
void updateMeters();
|
||||
void rebuildNodeMeters();
|
||||
void rebuildRulesList();
|
||||
void showAddRuleDialog();
|
||||
|
||||
struct PendingPasteLink {
|
||||
std::string outNodeName;
|
||||
|
|
@ -87,6 +89,7 @@ private:
|
|||
QSplitter *m_splitter = nullptr;
|
||||
QTabWidget *m_sidebar = nullptr;
|
||||
QTimer *m_refreshTimer = nullptr;
|
||||
QTimer *m_changeTimer = nullptr;
|
||||
QTimer *m_saveTimer = nullptr;
|
||||
QString m_layoutPath;
|
||||
QString m_presetDir;
|
||||
|
|
@ -110,4 +113,7 @@ private:
|
|||
QLabel *label = nullptr;
|
||||
};
|
||||
std::unordered_map<QtNodes::NodeId, NodeMeterRow> m_nodeMeters;
|
||||
|
||||
QWidget *m_rulesContainer = nullptr;
|
||||
QScrollArea *m_rulesScroll = nullptr;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue