Milestone1
This commit is contained in:
parent
a1094ab7ea
commit
4addf989cc
17 changed files with 2876 additions and 0 deletions
652
src/gui/PipeWireGraphModel.cpp
Normal file
652
src/gui/PipeWireGraphModel.cpp
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
#include "PipeWireGraphModel.h"
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QStandardPaths>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
PipeWireGraphModel::PipeWireGraphModel(Potato::PipeWireController *controller, QObject *parent)
|
||||
: QtNodes::AbstractGraphModel()
|
||||
, m_controller(controller)
|
||||
{
|
||||
if (parent) {
|
||||
setParent(parent);
|
||||
}
|
||||
}
|
||||
|
||||
QtNodes::NodeId PipeWireGraphModel::newNodeId()
|
||||
{
|
||||
return m_nextNodeId++;
|
||||
}
|
||||
|
||||
std::unordered_set<QtNodes::NodeId> PipeWireGraphModel::allNodeIds() const
|
||||
{
|
||||
std::unordered_set<QtNodes::NodeId> ids;
|
||||
ids.reserve(m_nodes.size());
|
||||
for (const auto &entry : m_nodes) {
|
||||
ids.insert(entry.first);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::unordered_set<QtNodes::ConnectionId> PipeWireGraphModel::allConnectionIds(QtNodes::NodeId const nodeId) const
|
||||
{
|
||||
std::unordered_set<QtNodes::ConnectionId> result;
|
||||
for (const auto &conn : m_connections) {
|
||||
if (conn.outNodeId == nodeId || conn.inNodeId == nodeId) {
|
||||
result.insert(conn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unordered_set<QtNodes::ConnectionId> PipeWireGraphModel::connections(QtNodes::NodeId nodeId,
|
||||
QtNodes::PortType portType,
|
||||
QtNodes::PortIndex portIndex) const
|
||||
{
|
||||
std::unordered_set<QtNodes::ConnectionId> result;
|
||||
for (const auto &conn : m_connections) {
|
||||
if (portType == QtNodes::PortType::Out) {
|
||||
if (conn.outNodeId == nodeId && conn.outPortIndex == portIndex) {
|
||||
result.insert(conn);
|
||||
}
|
||||
} else if (portType == QtNodes::PortType::In) {
|
||||
if (conn.inNodeId == nodeId && conn.inPortIndex == portIndex) {
|
||||
result.insert(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::connectionExists(QtNodes::ConnectionId const connectionId) const
|
||||
{
|
||||
return m_connections.find(connectionId) != m_connections.end();
|
||||
}
|
||||
|
||||
QtNodes::NodeId PipeWireGraphModel::addNode(QString const nodeType)
|
||||
{
|
||||
Q_UNUSED(nodeType)
|
||||
const QtNodes::NodeId nodeId = newNodeId();
|
||||
|
||||
Potato::NodeInfo info;
|
||||
info.id = 0;
|
||||
info.name = QString("Node %1").arg(static_cast<quint32>(nodeId));
|
||||
info.description = info.name;
|
||||
info.stableId = info.name;
|
||||
|
||||
m_nodes.emplace(nodeId, info);
|
||||
QPointF position = nextPosition();
|
||||
if (!info.stableId.isEmpty() && m_layoutByStableId.contains(info.stableId)) {
|
||||
position = m_layoutByStableId.value(info.stableId);
|
||||
}
|
||||
m_positions.emplace(nodeId, position);
|
||||
updateLayoutForNode(nodeId, position);
|
||||
|
||||
Q_EMIT nodeCreated(nodeId);
|
||||
Q_EMIT nodeUpdated(nodeId);
|
||||
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::connectionPossible(QtNodes::ConnectionId const connectionId) const
|
||||
{
|
||||
if (!nodeExists(connectionId.outNodeId) || !nodeExists(connectionId.inNodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connectionExists(connectionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto outIt = m_nodes.find(connectionId.outNodeId);
|
||||
const auto inIt = m_nodes.find(connectionId.inNodeId);
|
||||
|
||||
if (outIt == m_nodes.end() || inIt == m_nodes.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &outInfo = outIt->second;
|
||||
const auto &inInfo = inIt->second;
|
||||
|
||||
if (connectionId.outPortIndex >= static_cast<QtNodes::PortIndex>(outInfo.outputPorts.size())) {
|
||||
return false;
|
||||
}
|
||||
if (connectionId.inPortIndex >= static_cast<QtNodes::PortIndex>(inInfo.inputPorts.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::addConnection(QtNodes::ConnectionId const connectionId)
|
||||
{
|
||||
if (!connectionPossible(connectionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connections.insert(connectionId);
|
||||
Q_EMIT connectionCreated(connectionId);
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::nodeExists(QtNodes::NodeId const nodeId) const
|
||||
{
|
||||
return m_nodes.find(nodeId) != m_nodes.end();
|
||||
}
|
||||
|
||||
QVariant PipeWireGraphModel::nodeData(QtNodes::NodeId nodeId, QtNodes::NodeRole role) const
|
||||
{
|
||||
auto it = m_nodes.find(nodeId);
|
||||
if (it == m_nodes.end()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto &info = it->second;
|
||||
|
||||
switch (role) {
|
||||
case QtNodes::NodeRole::Caption:
|
||||
return info.name;
|
||||
case QtNodes::NodeRole::CaptionVisible:
|
||||
return true;
|
||||
case QtNodes::NodeRole::Position: {
|
||||
auto posIt = m_positions.find(nodeId);
|
||||
if (posIt != m_positions.end()) {
|
||||
return posIt->second;
|
||||
}
|
||||
return QPointF(0, 0);
|
||||
}
|
||||
case QtNodes::NodeRole::Size:
|
||||
return QSize(180, 80);
|
||||
case QtNodes::NodeRole::InPortCount:
|
||||
return static_cast<unsigned int>(info.inputPorts.size());
|
||||
case QtNodes::NodeRole::OutPortCount:
|
||||
return static_cast<unsigned int>(info.outputPorts.size());
|
||||
case QtNodes::NodeRole::Type:
|
||||
return QString("PipeWire");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::setNodeData(QtNodes::NodeId nodeId, QtNodes::NodeRole role, QVariant value)
|
||||
{
|
||||
if (!nodeExists(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == QtNodes::NodeRole::Position) {
|
||||
const QPointF position = value.toPointF();
|
||||
m_positions[nodeId] = position;
|
||||
updateLayoutForNode(nodeId, position);
|
||||
Q_EMIT nodePositionUpdated(nodeId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant PipeWireGraphModel::portData(QtNodes::NodeId nodeId,
|
||||
QtNodes::PortType portType,
|
||||
QtNodes::PortIndex portIndex,
|
||||
QtNodes::PortRole role) const
|
||||
{
|
||||
auto it = m_nodes.find(nodeId);
|
||||
if (it == m_nodes.end()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto &info = it->second;
|
||||
|
||||
if (role == QtNodes::PortRole::DataType) {
|
||||
return QString("audio");
|
||||
}
|
||||
|
||||
if (role == QtNodes::PortRole::CaptionVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (role == QtNodes::PortRole::Caption) {
|
||||
if (portType == QtNodes::PortType::In) {
|
||||
if (portIndex < static_cast<QtNodes::PortIndex>(info.inputPorts.size())) {
|
||||
return portLabel(info.inputPorts.at(portIndex));
|
||||
}
|
||||
} else if (portType == QtNodes::PortType::Out) {
|
||||
if (portIndex < static_cast<QtNodes::PortIndex>(info.outputPorts.size())) {
|
||||
return portLabel(info.outputPorts.at(portIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (role == QtNodes::PortRole::ConnectionPolicyRole) {
|
||||
if (portType == QtNodes::PortType::In) {
|
||||
return QVariant::fromValue(QtNodes::ConnectionPolicy::One);
|
||||
}
|
||||
return QVariant::fromValue(QtNodes::ConnectionPolicy::Many);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::setPortData(QtNodes::NodeId, QtNodes::PortType, QtNodes::PortIndex,
|
||||
QVariant const &, QtNodes::PortRole)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::deleteConnection(QtNodes::ConnectionId const connectionId)
|
||||
{
|
||||
auto it = m_connections.find(connectionId);
|
||||
if (it == m_connections.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connections.erase(it);
|
||||
Q_EMIT connectionDeleted(connectionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::deleteNode(QtNodes::NodeId const nodeId)
|
||||
{
|
||||
if (!nodeExists(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<QtNodes::ConnectionId> toRemove;
|
||||
for (const auto &conn : m_connections) {
|
||||
if (conn.outNodeId == nodeId || conn.inNodeId == nodeId) {
|
||||
toRemove.push_back(conn);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &conn : toRemove) {
|
||||
deleteConnection(conn);
|
||||
}
|
||||
|
||||
m_nodes.erase(nodeId);
|
||||
m_positions.erase(nodeId);
|
||||
Q_EMIT nodeDeleted(nodeId);
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject PipeWireGraphModel::saveNode(QtNodes::NodeId const) const
|
||||
{
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::loadNode(QJsonObject const &)
|
||||
{
|
||||
}
|
||||
|
||||
QtNodes::NodeId PipeWireGraphModel::addPipeWireNode(const Potato::NodeInfo &node)
|
||||
{
|
||||
if (m_pwToNode.find(node.id) != m_pwToNode.end()) {
|
||||
return m_pwToNode.at(node.id);
|
||||
}
|
||||
|
||||
const QtNodes::NodeId nodeId = newNodeId();
|
||||
m_nodes.emplace(nodeId, node);
|
||||
m_pwToNode.emplace(node.id, nodeId);
|
||||
QPointF position = nextPosition();
|
||||
if (!node.stableId.isEmpty() && m_layoutByStableId.contains(node.stableId)) {
|
||||
position = m_layoutByStableId.value(node.stableId);
|
||||
}
|
||||
m_positions.emplace(nodeId, position);
|
||||
updateLayoutForNode(nodeId, position);
|
||||
|
||||
Q_EMIT nodeCreated(nodeId);
|
||||
Q_EMIT nodeUpdated(nodeId);
|
||||
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::removePipeWireNode(uint32_t nodeId)
|
||||
{
|
||||
auto it = m_pwToNode.find(nodeId);
|
||||
if (it == m_pwToNode.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteNode(it->second);
|
||||
m_pwToNode.erase(it);
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::addPipeWireConnection(const Potato::LinkInfo &link, QtNodes::ConnectionId *connectionId)
|
||||
{
|
||||
bool ok = false;
|
||||
QtNodes::ConnectionId localId = connectionFromPipeWire(link, &ok);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connectionExists(localId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connections.insert(localId);
|
||||
m_linkIdToConnection.emplace(link.id, localId);
|
||||
if (connectionId) {
|
||||
*connectionId = localId;
|
||||
}
|
||||
Q_EMIT connectionCreated(localId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::removePipeWireConnection(uint32_t linkId)
|
||||
{
|
||||
auto it = m_linkIdToConnection.find(linkId);
|
||||
if (it == m_linkIdToConnection.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteConnection(it->second);
|
||||
m_linkIdToConnection.erase(it);
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::findConnectionForLink(uint32_t linkId, QtNodes::ConnectionId &connectionId) const
|
||||
{
|
||||
auto it = m_linkIdToConnection.find(linkId);
|
||||
if (it == m_linkIdToConnection.end()) {
|
||||
return false;
|
||||
}
|
||||
connectionId = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Potato::NodeInfo *PipeWireGraphModel::nodeInfo(QtNodes::NodeId nodeId) const
|
||||
{
|
||||
auto it = m_nodes.find(nodeId);
|
||||
if (it == m_nodes.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::connectionIdForLink(const Potato::LinkInfo &link, QtNodes::ConnectionId &connectionId) const
|
||||
{
|
||||
bool ok = false;
|
||||
connectionId = connectionFromPipeWire(link, &ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::reset()
|
||||
{
|
||||
m_connections.clear();
|
||||
m_linkIdToConnection.clear();
|
||||
m_nodes.clear();
|
||||
m_pwToNode.clear();
|
||||
m_positions.clear();
|
||||
m_nextNodeId = 1;
|
||||
Q_EMIT modelReset();
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::autoArrange()
|
||||
{
|
||||
std::vector<QtNodes::NodeId> ids;
|
||||
ids.reserve(m_nodes.size());
|
||||
|
||||
for (const auto &entry : m_nodes) {
|
||||
ids.push_back(entry.first);
|
||||
}
|
||||
|
||||
std::sort(ids.begin(), ids.end(), [this](QtNodes::NodeId a, QtNodes::NodeId b) {
|
||||
const QString &left = m_nodes.at(a).stableId;
|
||||
const QString &right = m_nodes.at(b).stableId;
|
||||
return left < right;
|
||||
});
|
||||
|
||||
const int columns = 4;
|
||||
const qreal spacingX = 260.0;
|
||||
const qreal spacingY = 160.0;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(ids.size()); ++i) {
|
||||
const int row = i / columns;
|
||||
const int col = i % columns;
|
||||
const QPointF position(col * spacingX, row * spacingY);
|
||||
m_positions[ids[i]] = position;
|
||||
updateLayoutForNode(ids[i], position);
|
||||
Q_EMIT nodePositionUpdated(ids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::loadLayout()
|
||||
{
|
||||
m_layoutByStableId.clear();
|
||||
const QString path = layoutFilePath();
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||
if (doc.isArray()) {
|
||||
applyLayoutData(doc.array());
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.isObject()) {
|
||||
const QJsonObject root = doc.object();
|
||||
const QJsonArray nodes = root.value("nodes").toArray();
|
||||
applyLayoutData(nodes);
|
||||
|
||||
const QJsonObject view = root.value("view").toObject();
|
||||
if (!view.isEmpty()) {
|
||||
m_viewScale = view.value("scale").toDouble(1.0);
|
||||
const double x = view.value("center_x").toDouble(0.0);
|
||||
const double y = view.value("center_y").toDouble(0.0);
|
||||
m_viewCenter = QPointF(x, y);
|
||||
m_hasViewState = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::saveLayout() const
|
||||
{
|
||||
const QString path = layoutFilePath();
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeLayoutToFile(path);
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::saveLayoutAs(const QString &path) const
|
||||
{
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
writeLayoutToFile(path);
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::resetLayout()
|
||||
{
|
||||
m_layoutByStableId.clear();
|
||||
autoArrange();
|
||||
saveLayout();
|
||||
}
|
||||
|
||||
QString PipeWireGraphModel::defaultLayoutPath() const
|
||||
{
|
||||
return layoutFilePath();
|
||||
}
|
||||
|
||||
QtNodes::ConnectionId PipeWireGraphModel::connectionFromPipeWire(const Potato::LinkInfo &link, bool *ok) const
|
||||
{
|
||||
auto outIt = m_pwToNode.find(link.outputNodeId);
|
||||
auto inIt = m_pwToNode.find(link.inputNodeId);
|
||||
if (outIt == m_pwToNode.end() || inIt == m_pwToNode.end()) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return QtNodes::ConnectionId{QtNodes::InvalidNodeId, 0, QtNodes::InvalidNodeId, 0};
|
||||
}
|
||||
|
||||
const auto &outInfo = m_nodes.at(outIt->second);
|
||||
const auto &inInfo = m_nodes.at(inIt->second);
|
||||
|
||||
QtNodes::PortIndex outIndex = 0;
|
||||
QtNodes::PortIndex inIndex = 0;
|
||||
|
||||
if (!findPortIndex(outInfo, link.outputPortId, QtNodes::PortType::Out, outIndex)) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return QtNodes::ConnectionId{QtNodes::InvalidNodeId, 0, QtNodes::InvalidNodeId, 0};
|
||||
}
|
||||
|
||||
if (!findPortIndex(inInfo, link.inputPortId, QtNodes::PortType::In, inIndex)) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return QtNodes::ConnectionId{QtNodes::InvalidNodeId, 0, QtNodes::InvalidNodeId, 0};
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
|
||||
return QtNodes::ConnectionId{outIt->second, outIndex, inIt->second, inIndex};
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::findPortIndex(const Potato::NodeInfo &node, uint32_t portId,
|
||||
QtNodes::PortType type, QtNodes::PortIndex &index) const
|
||||
{
|
||||
if (type == QtNodes::PortType::In) {
|
||||
for (int i = 0; i < node.inputPorts.size(); ++i) {
|
||||
if (node.inputPorts.at(i).id == portId) {
|
||||
index = static_cast<QtNodes::PortIndex>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.outputPorts.size(); ++i) {
|
||||
if (node.outputPorts.at(i).id == portId) {
|
||||
index = static_cast<QtNodes::PortIndex>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString PipeWireGraphModel::portLabel(const Potato::PortInfo &port) const
|
||||
{
|
||||
return port.name;
|
||||
}
|
||||
|
||||
QPointF PipeWireGraphModel::nextPosition() const
|
||||
{
|
||||
const int index = static_cast<int>(m_positions.size());
|
||||
const int columns = 4;
|
||||
const qreal spacingX = 260.0;
|
||||
const qreal spacingY = 160.0;
|
||||
const int row = index / columns;
|
||||
const int col = index % columns;
|
||||
return QPointF(col * spacingX, row * spacingY);
|
||||
}
|
||||
|
||||
QString PipeWireGraphModel::layoutFilePath() const
|
||||
{
|
||||
const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
if (baseDir.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return baseDir + QString("/layout.json");
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::updateLayoutForNode(QtNodes::NodeId nodeId, QPointF position)
|
||||
{
|
||||
auto it = m_nodes.find(nodeId);
|
||||
if (it == m_nodes.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString stableId = it->second.stableId;
|
||||
if (!stableId.isEmpty()) {
|
||||
m_layoutByStableId.insert(stableId, position);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::writeLayoutToFile(const QString &path) const
|
||||
{
|
||||
QJsonArray nodes;
|
||||
for (const auto &entry : m_nodes) {
|
||||
const auto &info = entry.second;
|
||||
if (info.stableId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto posIt = m_positions.find(entry.first);
|
||||
if (posIt == m_positions.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QPointF pos = posIt->second;
|
||||
QJsonObject obj;
|
||||
obj["id"] = info.stableId;
|
||||
obj["x"] = pos.x();
|
||||
obj["y"] = pos.y();
|
||||
nodes.append(obj);
|
||||
}
|
||||
|
||||
QJsonObject root;
|
||||
root["nodes"] = nodes;
|
||||
QJsonObject view;
|
||||
view["scale"] = m_viewScale;
|
||||
view["center_x"] = m_viewCenter.x();
|
||||
view["center_y"] = m_viewCenter.y();
|
||||
root["view"] = view;
|
||||
|
||||
QFile file(path);
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(QJsonDocument(root).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::applyLayoutData(const QJsonArray &nodes)
|
||||
{
|
||||
for (const auto &entry : nodes) {
|
||||
const QJsonObject obj = entry.toObject();
|
||||
const QString id = obj.value("id").toString();
|
||||
const double x = obj.value("x").toDouble();
|
||||
const double y = obj.value("y").toDouble();
|
||||
if (!id.isEmpty()) {
|
||||
m_layoutByStableId.insert(id, QPointF(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireGraphModel::setViewState(double scale, const QPointF ¢er)
|
||||
{
|
||||
m_viewScale = scale;
|
||||
m_viewCenter = center;
|
||||
m_hasViewState = true;
|
||||
}
|
||||
|
||||
bool PipeWireGraphModel::viewState(double &scale, QPointF ¢er) const
|
||||
{
|
||||
if (!m_hasViewState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
scale = m_viewScale;
|
||||
center = m_viewCenter;
|
||||
return true;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue