Compare commits
4 commits
0dbd10b5e3
...
5bb41373b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bb41373b2 | |||
| 5fa5a63d1a | |||
| 69d9a9e3f1 | |||
| 16fc02837a |
6 changed files with 1602 additions and 28 deletions
|
|
@ -39,6 +39,7 @@
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QPointer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
|
|
@ -614,9 +615,19 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
||||||
&GraphEditorWidget::onRefreshTimer);
|
&GraphEditorWidget::onRefreshTimer);
|
||||||
|
|
||||||
if (m_client) {
|
if (m_client) {
|
||||||
m_client->SetChangeCallback([this] {
|
QPointer<QTimer> changeTimer = m_changeTimer;
|
||||||
QMetaObject::invokeMethod(m_changeTimer,
|
m_client->SetChangeCallback([changeTimer] {
|
||||||
qOverload<>(&QTimer::start),
|
auto *app = QCoreApplication::instance();
|
||||||
|
if (!app) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(app,
|
||||||
|
[changeTimer]() {
|
||||||
|
if (changeTimer) {
|
||||||
|
changeTimer->start();
|
||||||
|
}
|
||||||
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -636,6 +647,11 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
||||||
void GraphEditorWidget::onRefreshTimer() {
|
void GraphEditorWidget::onRefreshTimer() {
|
||||||
m_model->refreshFromClient();
|
m_model->refreshFromClient();
|
||||||
|
|
||||||
|
if (m_scene &&
|
||||||
|
m_scene->itemIndexMethod() != QGraphicsScene::BspTreeIndex) {
|
||||||
|
m_scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_graphReady && m_model->allNodeIds().size() > 0) {
|
if (!m_graphReady && m_model->allNodeIds().size() > 0) {
|
||||||
m_graphReady = true;
|
m_graphReady = true;
|
||||||
Q_EMIT graphReady();
|
Q_EMIT graphReady();
|
||||||
|
|
@ -648,6 +664,13 @@ void GraphEditorWidget::scheduleSaveLayout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphEditorWidget::~GraphEditorWidget() {
|
GraphEditorWidget::~GraphEditorWidget() {
|
||||||
|
if (m_scene) {
|
||||||
|
disconnect(m_scene, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
if (m_model) {
|
||||||
|
disconnect(m_model, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_client) {
|
if (m_client) {
|
||||||
m_client->SetChangeCallback(nullptr);
|
m_client->SetChangeCallback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -670,6 +693,45 @@ int GraphEditorWidget::linkCount() const {
|
||||||
return count / 2;
|
return count / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAction *GraphEditorWidget::execMenuAction(QMenu &menu,
|
||||||
|
const QPoint &screenPos) {
|
||||||
|
return menu.exec(screenPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GraphEditorWidget::promptTextInput(const QString &title,
|
||||||
|
const QString &label,
|
||||||
|
bool *ok) {
|
||||||
|
return QInputDialog::getText(this,
|
||||||
|
title,
|
||||||
|
label,
|
||||||
|
QLineEdit::Normal,
|
||||||
|
QString(),
|
||||||
|
ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GraphEditorWidget::chooseSaveFilePath(const QString &title,
|
||||||
|
const QString &initialDir,
|
||||||
|
const QString &filter) {
|
||||||
|
return QFileDialog::getSaveFileName(this,
|
||||||
|
title,
|
||||||
|
initialDir,
|
||||||
|
filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GraphEditorWidget::chooseOpenFilePath(const QString &title,
|
||||||
|
const QString &initialDir,
|
||||||
|
const QString &filter) {
|
||||||
|
return QFileDialog::getOpenFileName(this,
|
||||||
|
title,
|
||||||
|
initialDir,
|
||||||
|
filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphEditorWidget::showWarningDialog(const QString &title,
|
||||||
|
const QString &message) {
|
||||||
|
QMessageBox::warning(this, title, message);
|
||||||
|
}
|
||||||
|
|
||||||
void GraphEditorWidget::setDebugScreenshotDir(const QString &dir) {
|
void GraphEditorWidget::setDebugScreenshotDir(const QString &dir) {
|
||||||
m_debugScreenshotDir = dir;
|
m_debugScreenshotDir = dir;
|
||||||
QDir d(dir);
|
QDir d(dir);
|
||||||
|
|
@ -812,7 +874,7 @@ void GraphEditorWidget::showCanvasContextMenu(const QPoint &screenPos,
|
||||||
QAction *savePresetAction = menu.addAction(QStringLiteral("Save Preset..."));
|
QAction *savePresetAction = menu.addAction(QStringLiteral("Save Preset..."));
|
||||||
QAction *loadPresetAction = menu.addAction(QStringLiteral("Load Preset..."));
|
QAction *loadPresetAction = menu.addAction(QStringLiteral("Load Preset..."));
|
||||||
|
|
||||||
QAction *chosen = menu.exec(screenPos);
|
QAction *chosen = execMenuAction(menu, screenPos);
|
||||||
if (chosen == createSink) {
|
if (chosen == createSink) {
|
||||||
createVirtualNode(true, scenePos);
|
createVirtualNode(true, scenePos);
|
||||||
} else if (chosen == createSource) {
|
} else if (chosen == createSource) {
|
||||||
|
|
@ -904,7 +966,7 @@ void GraphEditorWidget::showNodeContextMenu(const QPoint &screenPos,
|
||||||
QStringLiteral(
|
QStringLiteral(
|
||||||
"application/warppipe-virtual-graph"))));
|
"application/warppipe-virtual-graph"))));
|
||||||
|
|
||||||
QAction *chosen = menu.exec(screenPos);
|
QAction *chosen = execMenuAction(menu, screenPos);
|
||||||
if (!chosen) {
|
if (!chosen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -931,9 +993,9 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
||||||
const QPointF &scenePos) {
|
const QPointF &scenePos) {
|
||||||
if (isSink) {
|
if (isSink) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
QString name = QInputDialog::getText(
|
QString name = promptTextInput(QStringLiteral("Create Virtual Sink"),
|
||||||
this, QStringLiteral("Create Virtual Sink"),
|
QStringLiteral("Node name:"),
|
||||||
QStringLiteral("Node name:"), QLineEdit::Normal, QString(), &ok);
|
&ok);
|
||||||
if (!ok || name.trimmed().isEmpty())
|
if (!ok || name.trimmed().isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -941,7 +1003,7 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
||||||
m_model->setPendingPosition(nodeName, scenePos);
|
m_model->setPendingPosition(nodeName, scenePos);
|
||||||
auto result = m_client->CreateVirtualSink(nodeName);
|
auto result = m_client->CreateVirtualSink(nodeName);
|
||||||
if (!result.status.ok()) {
|
if (!result.status.ok()) {
|
||||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
showWarningDialog(QStringLiteral("Error"),
|
||||||
QString::fromStdString(result.status.message));
|
QString::fromStdString(result.status.message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1016,7 +1078,7 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
||||||
|
|
||||||
QString name = nameEdit->text().trimmed();
|
QString name = nameEdit->text().trimmed();
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
showWarningDialog(QStringLiteral("Error"),
|
||||||
QStringLiteral("Name cannot be empty."));
|
QStringLiteral("Name cannot be empty."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1032,7 +1094,7 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
||||||
|
|
||||||
auto result = m_client->CreateVirtualSource(nodeName, opts);
|
auto result = m_client->CreateVirtualSource(nodeName, opts);
|
||||||
if (!result.status.ok()) {
|
if (!result.status.ok()) {
|
||||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
showWarningDialog(QStringLiteral("Error"),
|
||||||
QString::fromStdString(result.status.message));
|
QString::fromStdString(result.status.message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1397,8 +1459,8 @@ void GraphEditorWidget::savePreset() {
|
||||||
if (!dir.exists())
|
if (!dir.exists())
|
||||||
dir.mkpath(".");
|
dir.mkpath(".");
|
||||||
|
|
||||||
QString path = QFileDialog::getSaveFileName(
|
QString path = chooseSaveFilePath(QStringLiteral("Save Preset"),
|
||||||
this, QStringLiteral("Save Preset"), m_presetDir,
|
m_presetDir,
|
||||||
QStringLiteral("JSON files (*.json)"));
|
QStringLiteral("JSON files (*.json)"));
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
@ -1410,14 +1472,14 @@ void GraphEditorWidget::savePreset() {
|
||||||
mw->statusBar()->showMessage(
|
mw->statusBar()->showMessage(
|
||||||
QStringLiteral("Preset saved: ") + QFileInfo(path).fileName(), 4000);
|
QStringLiteral("Preset saved: ") + QFileInfo(path).fileName(), 4000);
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
showWarningDialog(QStringLiteral("Error"),
|
||||||
QStringLiteral("Failed to save preset."));
|
QStringLiteral("Failed to save preset."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphEditorWidget::loadPreset() {
|
void GraphEditorWidget::loadPreset() {
|
||||||
QString path = QFileDialog::getOpenFileName(
|
QString path = chooseOpenFilePath(QStringLiteral("Load Preset"),
|
||||||
this, QStringLiteral("Load Preset"), m_presetDir,
|
m_presetDir,
|
||||||
QStringLiteral("JSON files (*.json)"));
|
QStringLiteral("JSON files (*.json)"));
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
@ -1427,7 +1489,7 @@ void GraphEditorWidget::loadPreset() {
|
||||||
mw->statusBar()->showMessage(
|
mw->statusBar()->showMessage(
|
||||||
QStringLiteral("Preset loaded: ") + QFileInfo(path).fileName(), 4000);
|
QStringLiteral("Preset loaded: ") + QFileInfo(path).fileName(), 4000);
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
showWarningDialog(QStringLiteral("Error"),
|
||||||
QStringLiteral("Failed to load preset."));
|
QStringLiteral("Failed to load preset."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ class QSplitter;
|
||||||
class QTabWidget;
|
class QTabWidget;
|
||||||
class QSlider;
|
class QSlider;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
class QAction;
|
||||||
|
class QMenu;
|
||||||
class DeleteVirtualNodeCommand;
|
class DeleteVirtualNodeCommand;
|
||||||
|
|
||||||
enum class ConnectionStyleType : uint8_t {
|
enum class ConnectionStyleType : uint8_t {
|
||||||
|
|
@ -59,6 +61,20 @@ private slots:
|
||||||
void onContextMenuRequested(const QPoint &pos);
|
void onContextMenuRequested(const QPoint &pos);
|
||||||
void scheduleSaveLayout();
|
void scheduleSaveLayout();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual QAction *execMenuAction(QMenu &menu, const QPoint &screenPos);
|
||||||
|
virtual QString promptTextInput(const QString &title,
|
||||||
|
const QString &label,
|
||||||
|
bool *ok);
|
||||||
|
virtual QString chooseSaveFilePath(const QString &title,
|
||||||
|
const QString &initialDir,
|
||||||
|
const QString &filter);
|
||||||
|
virtual QString chooseOpenFilePath(const QString &title,
|
||||||
|
const QString &initialDir,
|
||||||
|
const QString &filter);
|
||||||
|
virtual void showWarningDialog(const QString &title,
|
||||||
|
const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void showCanvasContextMenu(const QPoint &screenPos, const QPointF &scenePos);
|
void showCanvasContextMenu(const QPoint &screenPos, const QPointF &scenePos);
|
||||||
void showNodeContextMenu(const QPoint &screenPos, uint32_t pwNodeId,
|
void showNodeContextMenu(const QPoint &screenPos, uint32_t pwNodeId,
|
||||||
|
|
|
||||||
|
|
@ -352,12 +352,11 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT beginBatchUpdate();
|
|
||||||
m_refreshing = true;
|
m_refreshing = true;
|
||||||
|
bool sceneChanged = false;
|
||||||
auto nodesResult = m_client->ListNodes();
|
auto nodesResult = m_client->ListNodes();
|
||||||
if (!nodesResult.ok()) {
|
if (!nodesResult.ok()) {
|
||||||
m_refreshing = false;
|
m_refreshing = false;
|
||||||
Q_EMIT endBatchUpdate();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,6 +511,10 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
m_volumeStates[qtId] = {};
|
m_volumeStates[qtId] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sceneChanged) {
|
||||||
|
sceneChanged = true;
|
||||||
|
Q_EMIT beginBatchUpdate();
|
||||||
|
}
|
||||||
Q_EMIT nodeCreated(qtId);
|
Q_EMIT nodeCreated(qtId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,6 +529,10 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
if (it == m_pwToQt.end()) {
|
if (it == m_pwToQt.end()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!sceneChanged) {
|
||||||
|
sceneChanged = true;
|
||||||
|
Q_EMIT beginBatchUpdate();
|
||||||
|
}
|
||||||
QtNodes::NodeId qtId = it->second;
|
QtNodes::NodeId qtId = it->second;
|
||||||
auto nodeIt = m_nodes.find(qtId);
|
auto nodeIt = m_nodes.find(qtId);
|
||||||
if (nodeIt == m_nodes.end()) {
|
if (nodeIt == m_nodes.end()) {
|
||||||
|
|
@ -581,6 +588,10 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
QtNodes::ConnectionId connId{outNodeIt->second, outPortIdx,
|
QtNodes::ConnectionId connId{outNodeIt->second, outPortIdx,
|
||||||
inNodeIt->second, inPortIdx};
|
inNodeIt->second, inPortIdx};
|
||||||
if (m_connections.find(connId) == m_connections.end()) {
|
if (m_connections.find(connId) == m_connections.end()) {
|
||||||
|
if (!sceneChanged) {
|
||||||
|
sceneChanged = true;
|
||||||
|
Q_EMIT beginBatchUpdate();
|
||||||
|
}
|
||||||
m_connections.insert(connId);
|
m_connections.insert(connId);
|
||||||
m_linkIdToConn.emplace(link.id.value, connId);
|
m_linkIdToConn.emplace(link.id.value, connId);
|
||||||
Q_EMIT connectionCreated(connId);
|
Q_EMIT connectionCreated(connId);
|
||||||
|
|
@ -609,6 +620,10 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
{
|
{
|
||||||
auto connIt = m_connections.find(connId);
|
auto connIt = m_connections.find(connId);
|
||||||
if (connIt != m_connections.end()) {
|
if (connIt != m_connections.end()) {
|
||||||
|
if (!sceneChanged) {
|
||||||
|
sceneChanged = true;
|
||||||
|
Q_EMIT beginBatchUpdate();
|
||||||
|
}
|
||||||
m_connections.erase(connIt);
|
m_connections.erase(connIt);
|
||||||
Q_EMIT connectionDeleted(connId);
|
Q_EMIT connectionDeleted(connId);
|
||||||
}
|
}
|
||||||
|
|
@ -696,8 +711,10 @@ void WarpGraphModel::refreshFromClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
m_refreshing = false;
|
m_refreshing = false;
|
||||||
|
if (sceneChanged) {
|
||||||
Q_EMIT endBatchUpdate();
|
Q_EMIT endBatchUpdate();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const WarpNodeData *
|
const WarpNodeData *
|
||||||
WarpGraphModel::warpNodeData(QtNodes::NodeId nodeId) const {
|
WarpGraphModel::warpNodeData(QtNodes::NodeId nodeId) const {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public:
|
||||||
explicit ZoomGraphicsView(QtNodes::BasicGraphicsScene *scene,
|
explicit ZoomGraphicsView(QtNodes::BasicGraphicsScene *scene,
|
||||||
QWidget *parent = nullptr)
|
QWidget *parent = nullptr)
|
||||||
: QtNodes::GraphicsView(scene, parent) {
|
: QtNodes::GraphicsView(scene, parent) {
|
||||||
setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
|
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
||||||
setCacheMode(QGraphicsView::CacheNone);
|
setCacheMode(QGraphicsView::CacheNone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -626,6 +626,60 @@ TEST_CASE("set default sink without metadata returns unavailable") {
|
||||||
REQUIRE_FALSE(status.ok());
|
REQUIRE_FALSE(status.ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set default source without metadata returns unavailable") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
if (!result.ok()) {
|
||||||
|
SUCCEED("PipeWire unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status = result.value->SetDefaultSource("");
|
||||||
|
REQUIRE_FALSE(status.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("GetVirtualNodeInfo returns details for created virtual sink") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
if (!result.ok()) {
|
||||||
|
SUCCEED("PipeWire unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
warppipe::VirtualNodeOptions options;
|
||||||
|
options.display_name = "warppipe-test-info";
|
||||||
|
options.group = "warppipe-test";
|
||||||
|
options.format.rate = 48000;
|
||||||
|
options.format.channels = 2;
|
||||||
|
|
||||||
|
auto sink = result.value->CreateVirtualSink("warppipe-info-sink", options);
|
||||||
|
if (!sink.ok()) {
|
||||||
|
if (sink.status.code == warppipe::StatusCode::kUnavailable) {
|
||||||
|
SUCCEED("PipeWire unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
REQUIRE(sink.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = result.value->GetVirtualNodeInfo(sink.value.node);
|
||||||
|
REQUIRE(info.ok());
|
||||||
|
REQUIRE(info.value.node.value == sink.value.node.value);
|
||||||
|
REQUIRE(info.value.name == sink.value.name);
|
||||||
|
REQUIRE_FALSE(info.value.is_source);
|
||||||
|
|
||||||
|
REQUIRE(result.value->RemoveNode(sink.value.node).ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("GetVirtualNodeInfo missing node returns not found") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
if (!result.ok()) {
|
||||||
|
SUCCEED("PipeWire unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = result.value->GetVirtualNodeInfo(warppipe::NodeId{999999});
|
||||||
|
REQUIRE_FALSE(info.ok());
|
||||||
|
REQUIRE(info.status.code == warppipe::StatusCode::kNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("NodeInfo captures application properties") {
|
TEST_CASE("NodeInfo captures application properties") {
|
||||||
auto result = warppipe::Client::Create(DefaultOptions());
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
if (!result.ok()) {
|
if (!result.ok()) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue