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 <QMimeData>
|
||||
#include <QPixmap>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSplitter>
|
||||
|
|
@ -614,9 +615,19 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
&GraphEditorWidget::onRefreshTimer);
|
||||
|
||||
if (m_client) {
|
||||
m_client->SetChangeCallback([this] {
|
||||
QMetaObject::invokeMethod(m_changeTimer,
|
||||
qOverload<>(&QTimer::start),
|
||||
QPointer<QTimer> changeTimer = m_changeTimer;
|
||||
m_client->SetChangeCallback([changeTimer] {
|
||||
auto *app = QCoreApplication::instance();
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(app,
|
||||
[changeTimer]() {
|
||||
if (changeTimer) {
|
||||
changeTimer->start();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
|
@ -636,6 +647,11 @@ GraphEditorWidget::GraphEditorWidget(warppipe::Client *client,
|
|||
void GraphEditorWidget::onRefreshTimer() {
|
||||
m_model->refreshFromClient();
|
||||
|
||||
if (m_scene &&
|
||||
m_scene->itemIndexMethod() != QGraphicsScene::BspTreeIndex) {
|
||||
m_scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
|
||||
}
|
||||
|
||||
if (!m_graphReady && m_model->allNodeIds().size() > 0) {
|
||||
m_graphReady = true;
|
||||
Q_EMIT graphReady();
|
||||
|
|
@ -648,6 +664,13 @@ void GraphEditorWidget::scheduleSaveLayout() {
|
|||
}
|
||||
|
||||
GraphEditorWidget::~GraphEditorWidget() {
|
||||
if (m_scene) {
|
||||
disconnect(m_scene, nullptr, this, nullptr);
|
||||
}
|
||||
if (m_model) {
|
||||
disconnect(m_model, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
if (m_client) {
|
||||
m_client->SetChangeCallback(nullptr);
|
||||
}
|
||||
|
|
@ -670,6 +693,45 @@ int GraphEditorWidget::linkCount() const {
|
|||
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) {
|
||||
m_debugScreenshotDir = dir;
|
||||
QDir d(dir);
|
||||
|
|
@ -812,7 +874,7 @@ void GraphEditorWidget::showCanvasContextMenu(const QPoint &screenPos,
|
|||
QAction *savePresetAction = menu.addAction(QStringLiteral("Save Preset..."));
|
||||
QAction *loadPresetAction = menu.addAction(QStringLiteral("Load Preset..."));
|
||||
|
||||
QAction *chosen = menu.exec(screenPos);
|
||||
QAction *chosen = execMenuAction(menu, screenPos);
|
||||
if (chosen == createSink) {
|
||||
createVirtualNode(true, scenePos);
|
||||
} else if (chosen == createSource) {
|
||||
|
|
@ -904,7 +966,7 @@ void GraphEditorWidget::showNodeContextMenu(const QPoint &screenPos,
|
|||
QStringLiteral(
|
||||
"application/warppipe-virtual-graph"))));
|
||||
|
||||
QAction *chosen = menu.exec(screenPos);
|
||||
QAction *chosen = execMenuAction(menu, screenPos);
|
||||
if (!chosen) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -931,9 +993,9 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
|||
const QPointF &scenePos) {
|
||||
if (isSink) {
|
||||
bool ok = false;
|
||||
QString name = QInputDialog::getText(
|
||||
this, QStringLiteral("Create Virtual Sink"),
|
||||
QStringLiteral("Node name:"), QLineEdit::Normal, QString(), &ok);
|
||||
QString name = promptTextInput(QStringLiteral("Create Virtual Sink"),
|
||||
QStringLiteral("Node name:"),
|
||||
&ok);
|
||||
if (!ok || name.trimmed().isEmpty())
|
||||
return;
|
||||
|
||||
|
|
@ -941,8 +1003,8 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
|||
m_model->setPendingPosition(nodeName, scenePos);
|
||||
auto result = m_client->CreateVirtualSink(nodeName);
|
||||
if (!result.status.ok()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
||||
QString::fromStdString(result.status.message));
|
||||
showWarningDialog(QStringLiteral("Error"),
|
||||
QString::fromStdString(result.status.message));
|
||||
return;
|
||||
}
|
||||
m_model->refreshFromClient();
|
||||
|
|
@ -1016,8 +1078,8 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
|||
|
||||
QString name = nameEdit->text().trimmed();
|
||||
if (name.isEmpty()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
||||
QStringLiteral("Name cannot be empty."));
|
||||
showWarningDialog(QStringLiteral("Error"),
|
||||
QStringLiteral("Name cannot be empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1032,8 +1094,8 @@ void GraphEditorWidget::createVirtualNode(bool isSink,
|
|||
|
||||
auto result = m_client->CreateVirtualSource(nodeName, opts);
|
||||
if (!result.status.ok()) {
|
||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
||||
QString::fromStdString(result.status.message));
|
||||
showWarningDialog(QStringLiteral("Error"),
|
||||
QString::fromStdString(result.status.message));
|
||||
return;
|
||||
}
|
||||
m_model->refreshFromClient();
|
||||
|
|
@ -1397,9 +1459,9 @@ void GraphEditorWidget::savePreset() {
|
|||
if (!dir.exists())
|
||||
dir.mkpath(".");
|
||||
|
||||
QString path = QFileDialog::getSaveFileName(
|
||||
this, QStringLiteral("Save Preset"), m_presetDir,
|
||||
QStringLiteral("JSON files (*.json)"));
|
||||
QString path = chooseSaveFilePath(QStringLiteral("Save Preset"),
|
||||
m_presetDir,
|
||||
QStringLiteral("JSON files (*.json)"));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
if (!path.endsWith(QStringLiteral(".json"), Qt::CaseInsensitive))
|
||||
|
|
@ -1410,15 +1472,15 @@ void GraphEditorWidget::savePreset() {
|
|||
mw->statusBar()->showMessage(
|
||||
QStringLiteral("Preset saved: ") + QFileInfo(path).fileName(), 4000);
|
||||
} else {
|
||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
||||
QStringLiteral("Failed to save preset."));
|
||||
showWarningDialog(QStringLiteral("Error"),
|
||||
QStringLiteral("Failed to save preset."));
|
||||
}
|
||||
}
|
||||
|
||||
void GraphEditorWidget::loadPreset() {
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
this, QStringLiteral("Load Preset"), m_presetDir,
|
||||
QStringLiteral("JSON files (*.json)"));
|
||||
QString path = chooseOpenFilePath(QStringLiteral("Load Preset"),
|
||||
m_presetDir,
|
||||
QStringLiteral("JSON files (*.json)"));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
|
|
@ -1427,8 +1489,8 @@ void GraphEditorWidget::loadPreset() {
|
|||
mw->statusBar()->showMessage(
|
||||
QStringLiteral("Preset loaded: ") + QFileInfo(path).fileName(), 4000);
|
||||
} else {
|
||||
QMessageBox::warning(this, QStringLiteral("Error"),
|
||||
QStringLiteral("Failed to load preset."));
|
||||
showWarningDialog(QStringLiteral("Error"),
|
||||
QStringLiteral("Failed to load preset."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class QSplitter;
|
|||
class QTabWidget;
|
||||
class QSlider;
|
||||
class QTimer;
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class DeleteVirtualNodeCommand;
|
||||
|
||||
enum class ConnectionStyleType : uint8_t {
|
||||
|
|
@ -59,6 +61,20 @@ private slots:
|
|||
void onContextMenuRequested(const QPoint &pos);
|
||||
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:
|
||||
void showCanvasContextMenu(const QPoint &screenPos, const QPointF &scenePos);
|
||||
void showNodeContextMenu(const QPoint &screenPos, uint32_t pwNodeId,
|
||||
|
|
|
|||
|
|
@ -352,12 +352,11 @@ void WarpGraphModel::refreshFromClient() {
|
|||
return;
|
||||
}
|
||||
|
||||
Q_EMIT beginBatchUpdate();
|
||||
m_refreshing = true;
|
||||
bool sceneChanged = false;
|
||||
auto nodesResult = m_client->ListNodes();
|
||||
if (!nodesResult.ok()) {
|
||||
m_refreshing = false;
|
||||
Q_EMIT endBatchUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -512,6 +511,10 @@ void WarpGraphModel::refreshFromClient() {
|
|||
m_volumeStates[qtId] = {};
|
||||
}
|
||||
|
||||
if (!sceneChanged) {
|
||||
sceneChanged = true;
|
||||
Q_EMIT beginBatchUpdate();
|
||||
}
|
||||
Q_EMIT nodeCreated(qtId);
|
||||
}
|
||||
|
||||
|
|
@ -526,6 +529,10 @@ void WarpGraphModel::refreshFromClient() {
|
|||
if (it == m_pwToQt.end()) {
|
||||
continue;
|
||||
}
|
||||
if (!sceneChanged) {
|
||||
sceneChanged = true;
|
||||
Q_EMIT beginBatchUpdate();
|
||||
}
|
||||
QtNodes::NodeId qtId = it->second;
|
||||
auto nodeIt = m_nodes.find(qtId);
|
||||
if (nodeIt == m_nodes.end()) {
|
||||
|
|
@ -581,6 +588,10 @@ void WarpGraphModel::refreshFromClient() {
|
|||
QtNodes::ConnectionId connId{outNodeIt->second, outPortIdx,
|
||||
inNodeIt->second, inPortIdx};
|
||||
if (m_connections.find(connId) == m_connections.end()) {
|
||||
if (!sceneChanged) {
|
||||
sceneChanged = true;
|
||||
Q_EMIT beginBatchUpdate();
|
||||
}
|
||||
m_connections.insert(connId);
|
||||
m_linkIdToConn.emplace(link.id.value, connId);
|
||||
Q_EMIT connectionCreated(connId);
|
||||
|
|
@ -609,6 +620,10 @@ void WarpGraphModel::refreshFromClient() {
|
|||
{
|
||||
auto connIt = m_connections.find(connId);
|
||||
if (connIt != m_connections.end()) {
|
||||
if (!sceneChanged) {
|
||||
sceneChanged = true;
|
||||
Q_EMIT beginBatchUpdate();
|
||||
}
|
||||
m_connections.erase(connIt);
|
||||
Q_EMIT connectionDeleted(connId);
|
||||
}
|
||||
|
|
@ -696,7 +711,9 @@ void WarpGraphModel::refreshFromClient() {
|
|||
}
|
||||
|
||||
m_refreshing = false;
|
||||
Q_EMIT endBatchUpdate();
|
||||
if (sceneChanged) {
|
||||
Q_EMIT endBatchUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
const WarpNodeData *
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public:
|
|||
explicit ZoomGraphicsView(QtNodes::BasicGraphicsScene *scene,
|
||||
QWidget *parent = nullptr)
|
||||
: QtNodes::GraphicsView(scene, parent) {
|
||||
setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
|
||||
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
||||
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());
|
||||
}
|
||||
|
||||
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") {
|
||||
auto result = warppipe::Client::Create(DefaultOptions());
|
||||
if (!result.ok()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue