diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md index 059e902..aa4a027 100644 --- a/PROJECT_PLAN.md +++ b/PROJECT_PLAN.md @@ -1240,9 +1240,9 @@ private: **Estimated Time:** 1-2 weeks - [x] Integrate QUndoStack for all graph operations - [x] Implement command classes for link, volume, node operations -- [x] Add keyboard shortcuts (Delete, Ctrl+D, Ctrl+Z, etc.) -- [x] Implement context menus for nodes/canvas -- [x] Add copy/paste/duplicate functionality +- [ ] Add keyboard shortcuts (Delete, Ctrl+D, Ctrl+Z, etc.) +- [ ] Implement context menus for nodes/canvas +- [ ] Add copy/paste/duplicate functionality - [ ] **Acceptance Criteria:** Full undo/redo history, keyboard shortcuts work ### Milestone 7: Error Handling & Edge Cases diff --git a/src/gui/GraphEditorWidget.cpp b/src/gui/GraphEditorWidget.cpp index 0963c45..eecd3a4 100644 --- a/src/gui/GraphEditorWidget.cpp +++ b/src/gui/GraphEditorWidget.cpp @@ -17,24 +17,15 @@ #include #include #include -#include #include #include #include #include -#include -#include -#include -#include -#include #include #include #include #include -#include -#include -#include #include "gui/ClickSlider.h" @@ -74,144 +65,6 @@ private: NodeVolumeState m_next{}; }; -class DeleteVirtualNodeCommand : public QUndoCommand -{ -public: - struct VirtualNodeData { - uint32_t pipewireNodeId; - QtNodes::NodeId qtNodeId; - QString name; - QString description; - Potato::MediaClass mediaClass; - int channels; - int rate; - QPointF position; - NodeVolumeState volumeState; - }; - - DeleteVirtualNodeCommand(GraphEditorWidget *widget, - QtNodes::BasicGraphicsScene *scene, - const QList &nodeIds) - : m_widget(widget) - , m_scene(scene) - { - if (!widget || !scene) { - return; - } - - PipeWireGraphModel *model = widget->m_model; - if (!model) { - return; - } - - for (const auto nodeId : nodeIds) { - const Potato::NodeInfo *info = model->nodeInfo(nodeId); - if (!info || info->type != Potato::NodeType::Virtual) { - continue; - } - - VirtualNodeData data; - data.pipewireNodeId = info->id; - data.qtNodeId = nodeId; - data.name = info->name; - data.description = info->description; - data.mediaClass = info->mediaClass; - const int inPortSize = static_cast(info->inputPorts.size()); - const int outPortSize = static_cast(info->outputPorts.size()); - data.channels = (inPortSize > outPortSize) ? inPortSize : outPortSize; - if (data.channels == 0) { - data.channels = 2; - } - data.rate = 48000; - data.position = model->nodeData(nodeId, QtNodes::NodeRole::Position).toPointF(); - - NodeVolumeState volumeState; - if (model->nodeVolumeState(info->id, volumeState)) { - data.volumeState = volumeState; - } - - m_virtualNodes.append(data); - m_nodeIds.append(nodeId); - } - - setText(QString("Delete Virtual Node")); - } - - void undo() override - { - if (!m_widget || m_virtualNodes.isEmpty()) { - return; - } - - auto *controller = m_widget->m_controller; - auto *model = m_widget->m_model; - if (!controller || !model) { - return; - } - - for (const auto &data : m_virtualNodes) { - bool success = false; - if (data.mediaClass == Potato::MediaClass::AudioSource) { - success = controller->createVirtualSource(data.name, data.description, data.channels, data.rate); - } else { - success = controller->createVirtualSink(data.name, data.description, data.channels, data.rate); - } - - if (success) { - m_widget->m_pendingPastePositions.insert(data.name, data.position); - m_widget->m_pendingPasteVolumes.insert(data.name, data.volumeState); - } - } - } - - void redo() override - { - if (!m_widget) { - return; - } - - auto *controller = m_widget->m_controller; - PipeWireGraphModel *model = m_widget->m_model; - if (!controller || !model) { - return; - } - - for (int i = 0; i < m_virtualNodes.size(); ++i) { - VirtualNodeData &data = m_virtualNodes[i]; - - const Potato::NodeInfo *nodeInfo = model->nodeInfo(data.qtNodeId); - if (nodeInfo && nodeInfo->type == Potato::NodeType::Virtual) { - controller->destroyVirtualNode(data.pipewireNodeId); - } else { - const QVector nodes = controller->nodes(); - for (const auto &node : nodes) { - if (node.name == data.name && node.type == Potato::NodeType::Virtual) { - data.pipewireNodeId = node.id; - - const auto allNodes = model->allNodeIds(); - for (const auto qtId : allNodes) { - const Potato::NodeInfo *info = model->nodeInfo(qtId); - if (info && info->id == node.id) { - data.qtNodeId = qtId; - break; - } - } - - controller->destroyVirtualNode(node.id); - break; - } - } - } - } - } - -private: - GraphEditorWidget *m_widget = nullptr; - QtNodes::BasicGraphicsScene *m_scene = nullptr; - QList m_virtualNodes; - QList m_nodeIds; -}; - GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWidget *parent) : QWidget(parent) , m_controller(controller) @@ -253,8 +106,6 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi m_model->loadLayout(); m_scene = new QtNodes::BasicGraphicsScene(*m_model, this); m_view = new QtNodes::GraphicsView(m_scene); - m_view->setFocusPolicy(Qt::StrongFocus); - m_view->viewport()->setFocusPolicy(Qt::StrongFocus); m_scene->setBackgroundBrush(QColor(28, 30, 34)); m_splitter = new QSplitter(this); @@ -367,38 +218,10 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi m_view->setContextMenuPolicy(Qt::ActionsContextMenu); - removeDefaultCopyPasteActions(); - auto *refreshAction = new QAction(QString("Refresh Graph"), m_view); connect(refreshAction, &QAction::triggered, this, &GraphEditorWidget::refreshGraph); m_view->addAction(refreshAction); - auto *deleteAction = new QAction(QString("Delete Selection"), m_view); - deleteAction->setShortcut(QKeySequence::Delete); - deleteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(deleteAction, &QAction::triggered, this, &GraphEditorWidget::deleteSelection); - m_view->addAction(deleteAction); - - auto *copyAction = new QAction(QString("Copy Selection"), m_view); - copyAction->setShortcut(QKeySequence::Copy); - copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(copyAction, &QAction::triggered, this, &GraphEditorWidget::copySelection); - m_view->addAction(copyAction); - - auto *pasteAction = new QAction(QString("Paste Selection"), m_view); - pasteAction->setShortcut(QKeySequence::Paste); - pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(pasteAction, &QAction::triggered, [this]() { - pasteSelection(QPointF(30, 30)); - }); - m_view->addAction(pasteAction); - - auto *duplicateAction = new QAction(QString("Duplicate Selection"), m_view); - duplicateAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D)); - duplicateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(duplicateAction, &QAction::triggered, this, &GraphEditorWidget::duplicateSelection); - m_view->addAction(duplicateAction); - auto *zoomFitAllAction = new QAction(QString("Zoom Fit All"), m_view); connect(zoomFitAllAction, &QAction::triggered, m_view, &QtNodes::GraphicsView::zoomFitAll); m_view->addAction(zoomFitAllAction); @@ -472,33 +295,23 @@ GraphEditorWidget::GraphEditorWidget(Potato::PipeWireController *controller, QWi m_view->addAction(loadPresetAction); auto *createVirtualSinkAction = new QAction(QString("Create Virtual Sink"), m_view); - createVirtualSinkAction->setShortcutContext(Qt::WidgetShortcut); connect(createVirtualSinkAction, &QAction::triggered, [this]() { const int index = ++m_virtualSinkCount; const QString name = QString("Potato_Virtual_Sink_%1").arg(index); const QString description = QString("Virtual Sink %1").arg(index); - - m_pendingPastePositions.insert(name, m_lastContextMenuPos); - if (!m_controller->createVirtualSink(name, description, 2, 48000)) { qWarning() << "Failed to create virtual sink" << name; - m_pendingPastePositions.remove(name); } }); m_view->addAction(createVirtualSinkAction); auto *createVirtualSourceAction = new QAction(QString("Create Virtual Source"), m_view); - createVirtualSourceAction->setShortcutContext(Qt::WidgetShortcut); connect(createVirtualSourceAction, &QAction::triggered, [this]() { const int index = ++m_virtualSourceCount; const QString name = QString("Potato_Virtual_Source_%1").arg(index); const QString description = QString("Virtual Source %1").arg(index); - - m_pendingPastePositions.insert(name, m_lastContextMenuPos); - if (!m_controller->createVirtualSource(name, description, 2, 48000)) { qWarning() << "Failed to create virtual source" << name; - m_pendingPastePositions.remove(name); } }); m_view->addAction(createVirtualSourceAction); @@ -579,9 +392,6 @@ void GraphEditorWidget::refreshGraph() m_linkIdToConnection.clear(); m_nodeLinkCounts.clear(); m_linksById.clear(); - m_pendingPastePositions.clear(); - m_pendingPasteVolumes.clear(); - m_pendingPasteLinks.clear(); m_model->reset(); syncGraph(); @@ -593,21 +403,9 @@ void GraphEditorWidget::refreshGraph() void GraphEditorWidget::onNodeAdded(const Potato::NodeInfo &node) { if (isAudioEndpoint(node)) { - const QtNodes::NodeId nodeId = m_model->addPipeWireNode(node); + m_model->addPipeWireNode(node); refreshNodeMeter(node.id, node); refreshMixerStrip(node.id, node); - if (m_pendingPastePositions.contains(node.stableId)) { - const QPointF pos = m_pendingPastePositions.take(node.stableId); - m_model->setNodeData(nodeId, QtNodes::NodeRole::Position, pos); - updateLayoutState(); - } - if (m_pendingPasteVolumes.contains(node.stableId)) { - const NodeVolumeState state = m_pendingPasteVolumes.take(node.stableId); - m_controller->setNodeVolume(node.id, state.volume, state.mute); - m_model->setNodeVolumeState(node.id, state, false); - updateMixerState(node.id, node); - } - tryResolvePendingLinks(); } } @@ -625,7 +423,6 @@ void GraphEditorWidget::onNodeChanged(const Potato::NodeInfo &node) updateNodeMeterState(node.id, node); refreshMixerStrip(node.id, node); updateMixerState(node.id, node); - tryResolvePendingLinks(); } void GraphEditorWidget::onNodeRemoved(uint32_t nodeId) @@ -729,60 +526,18 @@ void GraphEditorWidget::onConnectionCreated(QtNodes::ConnectionId const connecti } if (connectionId.outPortIndex >= static_cast(outInfo->outputPorts.size())) { - qWarning() << "Output port index out of bounds:" << connectionId.outPortIndex << ">=" << outInfo->outputPorts.size(); - m_model->deleteConnection(connectionId); return; } if (connectionId.inPortIndex >= static_cast(inInfo->inputPorts.size())) { - qWarning() << "Input port index out of bounds:" << connectionId.inPortIndex << ">=" << inInfo->inputPorts.size(); - m_model->deleteConnection(connectionId); return; } const uint32_t outputPortId = outInfo->outputPorts.at(connectionId.outPortIndex).id; const uint32_t inputPortId = inInfo->inputPorts.at(connectionId.inPortIndex).id; - const Potato::NodeInfo freshOutInfo = m_controller->nodeById(outInfo->id); - const Potato::NodeInfo freshInInfo = m_controller->nodeById(inInfo->id); - - if (!freshOutInfo.isValid() || !freshInInfo.isValid()) { - qWarning() << "Node no longer exists in PipeWire"; - m_model->deleteConnection(connectionId); - return; - } - - bool outputPortExists = false; - for (const auto &port : freshOutInfo.outputPorts) { - if (port.id == outputPortId) { - outputPortExists = true; - break; - } - } - - bool inputPortExists = false; - for (const auto &port : freshInInfo.inputPorts) { - if (port.id == inputPortId) { - inputPortExists = true; - break; - } - } - - if (!outputPortExists) { - qWarning() << "Output port" << outputPortId << "does not exist in PipeWire node" << outInfo->id; - m_model->deleteConnection(connectionId); - return; - } - - if (!inputPortExists) { - qWarning() << "Input port" << inputPortId << "does not exist in PipeWire node" << inInfo->id; - m_model->deleteConnection(connectionId); - return; - } - const uint32_t linkId = m_controller->createLink(outInfo->id, outputPortId, inInfo->id, inputPortId); if (linkId == 0) { - qWarning() << "Failed to create link between" << outInfo->id << ":" << outputPortId << "->" << inInfo->id << ":" << inputPortId; m_model->deleteConnection(connectionId); return; } @@ -1058,18 +813,12 @@ void GraphEditorWidget::updateNodeMeterLabel(QLabel *label) bool GraphEditorWidget::eventFilter(QObject *object, QEvent *event) { - if (object == m_view->viewport()) { - if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::MiddleButton) { - m_view->centerOn(m_view->mapToScene(mouseEvent->pos())); - return true; - } - } else if (event->type() == QEvent::ContextMenu) { - QContextMenuEvent *contextEvent = static_cast(event); - m_lastContextMenuPos = m_view->mapToScene(contextEvent->pos()); + if (auto *label = qobject_cast(object)) { + if (event->type() == QEvent::Resize || event->type() == QEvent::Show) { + updateNodeMeterLabel(label); } } + return QWidget::eventFilter(object, event); } @@ -1312,320 +1061,3 @@ void GraphEditorWidget::pushVolumeCommand(uint32_t nodeId, const NodeVolumeState } m_scene->undoStack().push(new VolumeChangeCommand(this, nodeId, previous, next)); } - -void GraphEditorWidget::removeDefaultCopyPasteActions() -{ - const QList actions = m_view->actions(); - for (QAction *action : actions) { - const QString text = action->text(); - if (text.contains("Copy Selection") - || text.contains("Paste Selection") - || text.contains("Duplicate Selection") - || text.contains("Delete Selection")) { - m_view->removeAction(action); - action->deleteLater(); - } - } -} - -QString GraphEditorWidget::mediaClassToString(Potato::MediaClass mediaClass) const -{ - switch (mediaClass) { - case Potato::MediaClass::AudioSink: - return QString("Audio/Sink"); - case Potato::MediaClass::AudioSource: - return QString("Audio/Source"); - case Potato::MediaClass::AudioDuplex: - return QString("Audio/Duplex"); - case Potato::MediaClass::Stream: - return QString("Stream"); - default: - return QString(); - } -} - -Potato::MediaClass GraphEditorWidget::mediaClassFromString(const QString &value) const -{ - if (value.contains("Audio/Source")) { - return Potato::MediaClass::AudioSource; - } - if (value.contains("Audio/Duplex")) { - return Potato::MediaClass::AudioDuplex; - } - if (value.contains("Audio/Sink")) { - return Potato::MediaClass::AudioSink; - } - if (value.contains("Stream")) { - return Potato::MediaClass::Stream; - } - return Potato::MediaClass::Unknown; -} - -bool GraphEditorWidget::findNodeByStableId(const QString &stableId, Potato::NodeInfo &node) const -{ - const QVector nodes = m_controller->nodes(); - for (const auto &item : nodes) { - if (item.stableId == stableId) { - node = item; - return true; - } - } - return false; -} - -bool GraphEditorWidget::findPortByName(const Potato::NodeInfo &node, const QString &name, bool output, uint32_t &portId) const -{ - const auto &ports = output ? node.outputPorts : node.inputPorts; - for (const auto &port : ports) { - if (port.name == name) { - portId = port.id; - return true; - } - } - return false; -} - -void GraphEditorWidget::copySelection() -{ - QJsonArray nodesJson; - QSet selectedStableIds; - QPointF sum; - int count = 0; - - const QList items = m_scene->selectedItems(); - for (auto *item : items) { - if (auto *nodeObj = qgraphicsitem_cast(item)) { - const Potato::NodeInfo *info = m_model->nodeInfo(nodeObj->nodeId()); - if (!info || info->type != Potato::NodeType::Virtual || info->stableId.isEmpty()) { - continue; - } - QJsonObject nodeJson; - nodeJson["stable_id"] = info->stableId; - nodeJson["name"] = info->name; - nodeJson["description"] = info->description; - nodeJson["media_class"] = mediaClassToString(info->mediaClass); - const int channels = std::max(info->inputPorts.size(), info->outputPorts.size()); - nodeJson["channels"] = channels > 0 ? channels : 2; - nodeJson["rate"] = 48000; - const QPointF pos = m_model->nodeData(nodeObj->nodeId(), QtNodes::NodeRole::Position).toPointF(); - nodeJson["x"] = pos.x(); - nodeJson["y"] = pos.y(); - NodeVolumeState state; - if (m_model->nodeVolumeState(info->id, state)) { - nodeJson["volume"] = state.volume; - nodeJson["mute"] = state.mute; - } - nodesJson.append(nodeJson); - selectedStableIds.insert(info->stableId); - sum += pos; - ++count; - } - } - - if (nodesJson.isEmpty()) { - return; - } - - QJsonArray linksJson; - const QVector links = m_controller->links(); - for (const auto &link : links) { - const Potato::NodeInfo outNode = m_controller->nodeById(link.outputNodeId); - const Potato::NodeInfo inNode = m_controller->nodeById(link.inputNodeId); - if (!selectedStableIds.contains(outNode.stableId) || !selectedStableIds.contains(inNode.stableId)) { - continue; - } - QString outPortName; - QString inPortName; - for (const auto &port : outNode.outputPorts) { - if (port.id == link.outputPortId) { - outPortName = port.name; - break; - } - } - for (const auto &port : inNode.inputPorts) { - if (port.id == link.inputPortId) { - inPortName = port.name; - break; - } - } - if (outPortName.isEmpty() || inPortName.isEmpty()) { - continue; - } - QJsonObject linkJson; - linkJson["source"] = QString("%1:%2").arg(outNode.stableId, outPortName); - linkJson["target"] = QString("%1:%2").arg(inNode.stableId, inPortName); - linksJson.append(linkJson); - } - - const QPointF center = count > 0 ? sum / count : QPointF(0, 0); - QJsonObject root; - root["nodes"] = nodesJson; - root["links"] = linksJson; - root["center_x"] = center.x(); - root["center_y"] = center.y(); - root["version"] = QString("1.0"); - m_clipboardJson = root; - - QJsonDocument doc(root); - auto *mime = new QMimeData(); - mime->setData("application/potato-virtual-graph", doc.toJson(QJsonDocument::Compact)); - mime->setText(doc.toJson(QJsonDocument::Compact)); - QGuiApplication::clipboard()->setMimeData(mime); -} - -void GraphEditorWidget::pasteSelection(const QPointF &offset) -{ - QJsonObject root; - const QMimeData *mime = QGuiApplication::clipboard()->mimeData(); - if (mime && mime->hasFormat("application/potato-virtual-graph")) { - root = QJsonDocument::fromJson(mime->data("application/potato-virtual-graph")).object(); - } else if (!m_clipboardJson.isEmpty()) { - root = m_clipboardJson; - } - - if (root.isEmpty()) { - return; - } - - QHash stableMap; - QSet existingNames; - for (const auto &node : m_controller->nodes()) { - existingNames.insert(node.name); - } - - const QJsonArray nodesJson = root.value("nodes").toArray(); - for (const auto &entry : nodesJson) { - const QJsonObject nodeObj = entry.toObject(); - const QString baseName = nodeObj.value("name").toString(); - const QString description = nodeObj.value("description").toString(); - const QString stableId = nodeObj.value("stable_id").toString(); - const QString mediaClassValue = nodeObj.value("media_class").toString(); - if (baseName.isEmpty() || stableId.isEmpty()) { - continue; - } - QString newName = baseName + QString(" Copy"); - int suffix = 2; - while (existingNames.contains(newName)) { - newName = baseName + QString(" Copy %1").arg(suffix++); - } - existingNames.insert(newName); - stableMap.insert(stableId, newName); - - const int channels = nodeObj.value("channels").toInt(2); - const int rate = nodeObj.value("rate").toInt(48000); - const Potato::MediaClass mediaClass = mediaClassFromString(mediaClassValue); - const QString newDescription = description.isEmpty() ? newName : description; - - if (mediaClass == Potato::MediaClass::AudioSource) { - m_controller->createVirtualSource(newName, newDescription, channels, rate); - } else { - m_controller->createVirtualSink(newName, newDescription, channels, rate); - } - - const QPointF pos(nodeObj.value("x").toDouble(), nodeObj.value("y").toDouble()); - m_pendingPastePositions.insert(newName, pos + offset); - const float volume = static_cast(nodeObj.value("volume").toDouble(1.0)); - const bool mute = nodeObj.value("mute").toBool(false); - m_pendingPasteVolumes.insert(newName, NodeVolumeState{volume, mute}); - } - - const QJsonArray linksJson = root.value("links").toArray(); - for (const auto &entry : linksJson) { - const QJsonObject linkObj = entry.toObject(); - const QString source = linkObj.value("source").toString(); - const QString target = linkObj.value("target").toString(); - const int splitSource = source.lastIndexOf(':'); - const int splitTarget = target.lastIndexOf(':'); - if (splitSource <= 0 || splitTarget <= 0) { - continue; - } - const QString outStable = source.left(splitSource); - const QString outPort = source.mid(splitSource + 1); - const QString inStable = target.left(splitTarget); - const QString inPort = target.mid(splitTarget + 1); - if (!stableMap.contains(outStable) || !stableMap.contains(inStable)) { - continue; - } - PendingPasteLink link; - link.outStableId = stableMap.value(outStable); - link.outPortName = outPort; - link.inStableId = stableMap.value(inStable); - link.inPortName = inPort; - m_pendingPasteLinks.append(link); - } - - tryResolvePendingLinks(); -} - -void GraphEditorWidget::duplicateSelection() -{ - copySelection(); - pasteSelection(QPointF(40, 40)); -} - -void GraphEditorWidget::deleteSelection() -{ - if (!m_scene) { - return; - } - - const QList items = m_scene->selectedItems(); - QList virtualNodeIds; - QList otherNodeIds; - - for (auto *item : items) { - if (auto *nodeObj = qgraphicsitem_cast(item)) { - const Potato::NodeInfo *info = m_model->nodeInfo(nodeObj->nodeId()); - if (info && info->type == Potato::NodeType::Virtual) { - virtualNodeIds.append(nodeObj->nodeId()); - } else if (info) { - otherNodeIds.append(nodeObj->nodeId()); - } - } - } - - if (!virtualNodeIds.isEmpty()) { - m_scene->undoStack().push(new DeleteVirtualNodeCommand(this, m_scene, virtualNodeIds)); - } - - if (!otherNodeIds.isEmpty()) { - m_scene->clearSelection(); - for (const auto nodeId : otherNodeIds) { - if (const auto *nodeObj = m_scene->nodeGraphicsObject(nodeId)) { - const_cast(nodeObj)->setSelected(true); - } - } - m_scene->undoStack().push(new QtNodes::DeleteCommand(m_scene)); - } - - if (virtualNodeIds.isEmpty() && otherNodeIds.isEmpty() && !items.isEmpty()) { - m_scene->undoStack().push(new QtNodes::DeleteCommand(m_scene)); - } -} - -void GraphEditorWidget::tryResolvePendingLinks() -{ - if (m_pendingPasteLinks.isEmpty()) { - return; - } - - QVector remaining; - for (const auto &pending : m_pendingPasteLinks) { - Potato::NodeInfo outNode; - Potato::NodeInfo inNode; - if (!findNodeByStableId(pending.outStableId, outNode) - || !findNodeByStableId(pending.inStableId, inNode)) { - remaining.append(pending); - continue; - } - uint32_t outPortId = 0; - uint32_t inPortId = 0; - if (!findPortByName(outNode, pending.outPortName, true, outPortId) - || !findPortByName(inNode, pending.inPortName, false, inPortId)) { - remaining.append(pending); - continue; - } - m_controller->createLink(outNode.id, outPortId, inNode.id, inPortId); - } - m_pendingPasteLinks = remaining; -} diff --git a/src/gui/GraphEditorWidget.h b/src/gui/GraphEditorWidget.h index 7f5e37c..31977f4 100644 --- a/src/gui/GraphEditorWidget.h +++ b/src/gui/GraphEditorWidget.h @@ -11,9 +11,6 @@ #include #include #include -#include -#include -#include class AudioLevelMeter; class QLabel; @@ -27,14 +24,12 @@ class QSplitter; class QTabWidget; class PresetManager; class VolumeChangeCommand; -class DeleteVirtualNodeCommand; class GraphEditorWidget : public QWidget { Q_OBJECT friend class VolumeChangeCommand; - friend class DeleteVirtualNodeCommand; public: explicit GraphEditorWidget(Potato::PipeWireController *controller, QWidget *parent = nullptr); @@ -62,16 +57,6 @@ private: void applySoloState(); void applyVolumeState(uint32_t nodeId, const NodeVolumeState &state, bool updateMixer); void pushVolumeCommand(uint32_t nodeId, const NodeVolumeState &previous, const NodeVolumeState &next); - void copySelection(); - void pasteSelection(const QPointF &offset); - void duplicateSelection(); - void deleteSelection(); - void tryResolvePendingLinks(); - bool findNodeByStableId(const QString &stableId, Potato::NodeInfo &node) const; - bool findPortByName(const Potato::NodeInfo &node, const QString &name, bool output, uint32_t &portId) const; - QString mediaClassToString(Potato::MediaClass mediaClass) const; - Potato::MediaClass mediaClassFromString(const QString &value) const; - void removeDefaultCopyPasteActions(); void handleLinkRemoved(uint32_t linkId); bool isMeterNode(uint32_t nodeId) const; int activeLinkCount(uint32_t nodeId) const; @@ -80,13 +65,6 @@ private: bool eventFilter(QObject *object, QEvent *event) override; Potato::PipeWireController *m_controller = nullptr; - struct PendingPasteLink { - QString outStableId; - QString outPortName; - QString inStableId; - QString inPortName; - }; - PipeWireGraphModel *m_model = nullptr; QtNodes::BasicGraphicsScene *m_scene = nullptr; QtNodes::GraphicsView *m_view = nullptr; @@ -105,10 +83,6 @@ private: QMap m_mixerStartState; QMap m_mixerLastState; QSet m_mixerSoloNodes; - QJsonObject m_clipboardJson; - QHash m_pendingPastePositions; - QHash m_pendingPasteVolumes; - QVector m_pendingPasteLinks; QSet m_ignoreCreate; QSet m_ignoreDelete; @@ -134,5 +108,4 @@ private: int m_meterProfileFrames = 0; PresetManager *m_presetManager = nullptr; bool m_ignoreVolumeUndo = false; - QPointF m_lastContextMenuPos; }; diff --git a/src/gui/PipeWireGraphModel.cpp b/src/gui/PipeWireGraphModel.cpp index c89f7ee..a4973f1 100644 --- a/src/gui/PipeWireGraphModel.cpp +++ b/src/gui/PipeWireGraphModel.cpp @@ -498,16 +498,6 @@ bool PipeWireGraphModel::deleteNode(QtNodes::NodeId const nodeId) return false; } - const Potato::NodeInfo info = m_nodes.at(nodeId); - if (info.type != Potato::NodeType::Virtual) { - return false; - } - - const Potato::NodeInfo liveNode = m_controller ? m_controller->nodeById(info.id) : Potato::NodeInfo{}; - if (liveNode.isValid()) { - m_controller->destroyVirtualNode(info.id); - } - std::vector toRemove; for (const auto &conn : m_connections) { if (conn.outNodeId == nodeId || conn.inNodeId == nodeId) { @@ -527,16 +517,9 @@ bool PipeWireGraphModel::deleteNode(QtNodes::NodeId const nodeId) return true; } -QJsonObject PipeWireGraphModel::saveNode(QtNodes::NodeId const nodeId) const +QJsonObject PipeWireGraphModel::saveNode(QtNodes::NodeId const) const { - QJsonObject obj; - obj["id"] = static_cast(nodeId); - const QPointF pos = nodeData(nodeId, QtNodes::NodeRole::Position).toPointF(); - QJsonObject posJson; - posJson["x"] = pos.x(); - posJson["y"] = pos.y(); - obj["position"] = posJson; - return obj; + return QJsonObject(); } void PipeWireGraphModel::loadNode(QJsonObject const &) diff --git a/src/pipewire/pipewirecontroller.cpp b/src/pipewire/pipewirecontroller.cpp index e4d0977..c85d56c 100644 --- a/src/pipewire/pipewirecontroller.cpp +++ b/src/pipewire/pipewirecontroller.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -558,38 +557,6 @@ bool PipeWireController::createVirtualSource(const QString &name, const QString return createVirtualDevice(name, description, "support.null-audio-sink", "Audio/Source", channels, rate); } -bool PipeWireController::destroyVirtualNode(uint32_t nodeId) -{ - if (!m_threadLoop || !m_core || !m_registry) { - return false; - } - - const NodeInfo node = nodeById(nodeId); - if (!node.isValid()) { - return false; - } - - if (node.type != NodeType::Virtual && !node.name.startsWith("Potato_")) { - return false; - } - - lock(); - - for (auto it = m_virtualDevices.begin(); it != m_virtualDevices.end(); ) { - struct pw_proxy *proxy = *it; - if (proxy && pw_proxy_get_bound_id(proxy) == nodeId) { - it = m_virtualDevices.erase(it); - } else { - ++it; - } - } - - const int result = pw_registry_destroy(m_registry, nodeId); - unlock(); - - return result == 0; -} - float PipeWireController::nodeMeterPeak(uint32_t nodeId) const { QMutexLocker lock(&m_meterMutex); diff --git a/src/pipewire/pipewirecontroller.h b/src/pipewire/pipewirecontroller.h index eb9579b..dd07e2f 100644 --- a/src/pipewire/pipewirecontroller.h +++ b/src/pipewire/pipewirecontroller.h @@ -47,7 +47,6 @@ public: bool setNodeVolume(uint32_t nodeId, float volume, bool mute); bool createVirtualSink(const QString &name, const QString &description, int channels, int rate); bool createVirtualSource(const QString &name, const QString &description, int channels, int rate); - bool destroyVirtualNode(uint32_t nodeId); uint32_t createLink(uint32_t outputNodeId, uint32_t outputPortId, uint32_t inputNodeId, uint32_t inputPortId);