diff --git a/app/node/graph.cpp b/app/node/graph.cpp index f652340fb..bdae43570 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -44,6 +44,19 @@ void NodeGraph::Clear() } } +int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const +{ + int count = 0; + + foreach (Node *ctx, node_children_) { + if (ctx->ContextContainsNode(node) && (!except_itself || ctx != node)) { + count++; + } + } + + return count; +} + void NodeGraph::childEvent(QChildEvent *event) { super::childEvent(event); diff --git a/app/node/graph.h b/app/node/graph.h index 21c8f023e..fdf923b56 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -63,6 +63,8 @@ public: return default_nodes_; } + int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const; + signals: /** * @brief Signal emitted when a Node is added to the graph diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index bc1983c41..66d2fddf9 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -210,7 +210,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } if (otio_block->schema_name() == "Gap") { @@ -218,7 +218,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } // Update this after it's used but before any continue statements @@ -246,7 +246,7 @@ bool LoadOTIOTask::Run() QFileInfo info(probed_item->filename()); probed_item->SetLabel(info.fileName()); - FolderAddChild add(sequence_footage, probed_item, true); + FolderAddChild add(sequence_footage, probed_item); add.redo_now(); } @@ -254,10 +254,10 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position clip in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); // Position footage in its context - sequence->parent()->SetNodePosition(probed_item, block, QPointF(-2, 0)); + block->SetNodePositionInContext(probed_item, QPointF(-2, 0)); if (track->type() == Track::kVideo) { @@ -266,14 +266,14 @@ bool LoadOTIOTask::Run() Node::ConnectEdge(probed_item, NodeInput(transform, TransformDistortNode::kTextureInput)); Node::ConnectEdge(transform, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(transform, block, QPointF(-1, 0)); + block->SetNodePositionInContext(transform, QPointF(-1, 0)); } else { VolumeNode* volume_node = new VolumeNode(); volume_node->setParent(sequence->parent()); Node::ConnectEdge(probed_item, NodeInput(volume_node, VolumeNode::kSamplesInput)); Node::ConnectEdge(volume_node, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(volume_node, block, QPointF(-1, 0)); + block->SetNodePositionInContext(volume_node, QPointF(-1, 0)); } } } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 4c8385d9d..3da2c70f1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -125,43 +125,7 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - MultiUndoCommand* command = new MultiUndoCommand(); - - { - // First remove any selected edges - QVector selected_edges = scene_.GetSelectedEdges(); - - if (!selected_edges.isEmpty()) { - Node::OutputConnections removed_connections(selected_edges.size()); - - for (int i=0; iadd_child(new NodeEdgeRemoveCommand(edge->output(), edge->input())); - removed_connections[i] = {edge->output(), edge->input()}; - } - } - } - - { - // Secondly remove any nodes - QVector selected_nodes = scene_.GetSelectedNodes(); - - // Ensure no nodes are "undeletable" - for (int i=0;iCanBeDeleted()) { - selected_nodes.removeAt(i); - i--; - } - } - - if (!selected_nodes.isEmpty()) { - for (Node* node : qAsConst(selected_nodes)) { - command->add_child(new NodeRemoveAndDisconnectCommand(node)); - } - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); + scene_.DeleteSelected(); } void NodeView::SelectAll() @@ -428,6 +392,11 @@ void NodeView::mousePressEvent(QMouseEvent *event) } super::mousePressEvent(event); + + auto selected_items = scene_.GetSelectedItems(); + foreach (NodeViewItem *i, selected_items) { + dragging_nodes_.insert(i, i->GetNodePosition()); + } } void NodeView::mouseMoveEvent(QMouseEvent *event) @@ -658,6 +627,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) DetachItemsFromCursor(); } + for (auto it=dragging_nodes_.cbegin(); it!=dragging_nodes_.cend(); it++) { + NodeViewItem *i = it.key(); + QPointF current_pos = i->GetNodePosition(); + if (it.value() != current_pos) { + command->add_child(new NodeSetPositionCommand(i->GetNode(), i->GetContext(), current_pos)); + } + } + dragging_nodes_.clear(); + Core::instance()->undo_stack()->pushIfHasChildren(command); super::mouseReleaseEvent(event); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8616974f5..c9d7e0e43 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -219,6 +219,8 @@ private: QVector last_set_filter_nodes_; QMap context_offsets_; + QMap dragging_nodes_; + double scale_; bool create_edge_already_exists_; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 1b52624a6..564e6552a 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -50,9 +50,7 @@ void NodeViewContext::AddChild(Node *node) return; } - NodeViewItem *item = new NodeViewItem(this); - item->SetNode(node, context_); - item->SetNodePosition(context_->GetNodePositionInContext(node)); + NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); @@ -162,6 +160,36 @@ void NodeViewContext::SetCurvedEdges(bool e) } } +void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) +{ + // Delete any selected edges + foreach (NodeViewEdge *edge, edges_) { + if (edge->isSelected()) { + command->AddEdge(edge->output(), edge->input()); + } + } + + // Delete any selected nodes + foreach (NodeViewItem *node, item_map_) { + if (node->isSelected()) { + command->AddNode(node->GetNode(), context_); + } + } +} + +QVector NodeViewContext::GetSelectedItems() const +{ + QVector items; + + for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { + if (it.value()->isSelected()) { + items.append(it.value()); + } + } + + return items; +} + void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Set pen and brush diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 4ef21f68f..8a84751de 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -7,6 +7,7 @@ #include "node/node.h" #include "nodeviewcommon.h" #include "nodeviewedge.h" +#include "nodeviewundo.h" namespace olive { @@ -22,6 +23,10 @@ public: void SetCurvedEdges(bool e); + void DeleteSelected(NodeViewDeleteCommand *command); + + QVector GetSelectedItems() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1c73f8ddd..009ea45ac 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,10 +39,10 @@ namespace olive { -NodeViewItem::NodeViewItem(QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(nullptr), - context_(nullptr), + node_(n), + context_(context), expanded_(false), hide_titlebar_(false), highlighted_index_(-1), @@ -69,6 +69,24 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : setRect(title_bar_rect_); output_connector_ = new NodeViewItemConnector(true, this); + + // Set up node + node_->Retranslate(); + + foreach (const QString& input, node_->inputs()) { + if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { + node_inputs_.append(input); + } + } + + UpdateInputConnectors(); + + connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + + SetNodePosition(context_->GetNodePositionInContext(node_)); + + SetExpanded(node_->property("expanded").toBool()); } QPointF NodeViewItem::GetNodePosition() const @@ -208,37 +226,6 @@ int NodeViewItem::GetIndexAt(QPointF pt) const return -1; } -void NodeViewItem::SetNode(Node *n, Node *context) -{ - if (node_) { - disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - disconnect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - node_ = n; - context_ = context; - - node_inputs_.clear(); - input_connectors_.clear(); - - if (node_) { - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - - connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - update(); -} - void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { if (node_inputs_.isEmpty() @@ -248,6 +235,7 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; + node_->setProperty("expanded", e); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -507,8 +495,8 @@ void NodeViewItem::SetLabelAsOutput(bool e) NodeViewEdge *NodeViewItem::GetEdgeFromInputConnector(NodeViewItemConnector *connector) { - ssize_t index = -1; - for (ssize_t i=0; iSetFlowDirection(direction_); } - - // Iterate over edge items setting direction - foreach (NodeViewEdge* edge, edges_) { - edge->SetFlowDirection(direction_); - } } void NodeViewScene::clear() @@ -63,9 +59,6 @@ void NodeViewScene::clear() delete it.value(); } item_map_.clear(); - - qDeleteAll(edges_); - edges_.clear(); } void NodeViewScene::SelectAll() @@ -86,22 +79,22 @@ void NodeViewScene::DeselectAll() } } +void NodeViewScene::DeleteSelected() +{ + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, context_map_) { + ctx->DeleteSelected(command); + } + + Core::instance()->undo_stack()->push(command); +} + NodeViewItem *NodeViewScene::NodeToUIObject(Node *n) { return item_map_.value(n); } -NodeViewEdge *NodeViewScene::EdgeToUIObject(Node *output, const NodeInput& input) -{ - foreach (NodeViewEdge* edge, edges_) { - if (edge->output() == output && edge->input() == input) { - return edge; - } - } - - return nullptr; -} - QVector NodeViewScene::GetSelectedNodes() const { QHash::const_iterator iterator; @@ -118,29 +111,13 @@ QVector NodeViewScene::GetSelectedNodes() const QVector NodeViewScene::GetSelectedItems() const { - QHash::const_iterator iterator; - QVector selected; + QVector items; - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - if (iterator.value()->isSelected()) { - selected.append(iterator.value()); - } + foreach (NodeViewContext *ctx, context_map_) { + items.append(ctx->GetSelectedItems()); } - return selected; -} - -QVector NodeViewScene::GetSelectedEdges() const -{ - QVector edges; - - foreach (NodeViewEdge* e, edges_) { - if (e->isSelected()) { - edges.append(e); - } - } - - return edges; + return items; } NodeViewContext *NodeViewScene::AddContext(Node *node) diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index db49aca89..0ef1adfab 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -42,6 +42,8 @@ public: void SelectAll(); void DeselectAll(); + void DeleteSelected(); + /** * @brief Retrieve the graphical widget corresponding to a specific Node * @@ -54,22 +56,15 @@ public: * in this view/scene), this function returns nullptr. */ NodeViewItem* NodeToUIObject(Node* n); - NodeViewEdge *EdgeToUIObject(Node *output, const NodeInput &input); QVector GetSelectedNodes() const; QVector GetSelectedItems() const; - QVector GetSelectedEdges() const; const QHash& item_map() const { return item_map_; } - const QVector& edges() const - { - return edges_; - } - Qt::Orientation GetFlowOrientation() const; NodeViewCommon::FlowDirection GetFlowDirection() const; @@ -96,8 +91,6 @@ private: QHash item_map_; - QVector edges_; - NodeGraph* graph_; NodeViewCommon::FlowDirection direction_; diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 5d7af0263..54106070f 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -193,4 +193,100 @@ void NodeOverrideColorCommand::undo() node_->SetOverrideColor(old_index_); } +NodeViewDeleteCommand::NodeViewDeleteCommand() +{ +} + +void NodeViewDeleteCommand::AddNode(Node *node, Node *context) +{ + foreach (const NodePair &pair, nodes_) { + if (pair.first == node && pair.second == context) { + return; + } + } + + nodes_.append(NodePair({node, context})); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (context->ContextContainsNode(it->second)) { + AddEdge(it->second, it->first); + } + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (context->ContextContainsNode(it->second.node())) { + AddEdge(it->first, it->second); + } + } +} + +void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input) +{ + foreach (const Node::OutputConnection &edge, edges_) { + if (edge.first == output && edge.second == input) { + return; + } + } + + edges_.append({output, input}); +} + +Project *NodeViewDeleteCommand::GetRelevantProject() const +{ + if (!nodes_.isEmpty()) { + return nodes_.first().first->project(); + } + + if (!edges_.isEmpty()) { + return edges_.first().first->project(); + } + + return nullptr; +} + +void NodeViewDeleteCommand::redo() +{ + foreach (const Node::OutputConnection &edge, edges_) { + Node::DisconnectEdge(edge.first, edge.second); + } + + foreach (const NodePair &pair, nodes_) { + RemovedNode rn; + + rn.node = pair.first; + rn.context = pair.second; + rn.pos = rn.context->GetNodePositionInContext(rn.node); + + rn.context->RemoveNodeFromContext(rn.node); + + // If node is no longer in any contexts and is not connected to anything, remove it + if (rn.node->parent()->GetNumberOfContextsNodeIsIn(rn.node, true) == 0 + && rn.node->input_connections().empty() + && rn.node->output_connections().empty()) { + rn.removed_from_graph = rn.node->parent(); + rn.node->setParent(&memory_manager_); + } else { + rn.removed_from_graph = nullptr; + } + + removed_nodes_.append(rn); + } +} + +void NodeViewDeleteCommand::undo() +{ + for (auto rn=removed_nodes_.crbegin(); rn!=removed_nodes_.crend(); rn++) { + if (rn->removed_from_graph) { + rn->node->setParent(rn->removed_from_graph); + } + + rn->context->SetNodePositionInContext(rn->node, rn->pos); + } + removed_nodes_.clear(); + + for (auto edge=edges_.crbegin(); edge!=edges_.crend(); edge++) { + Node::ConnectEdge(edge->first, edge->second); + } +} + } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 7ed3e434e..567198b5b 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -363,6 +363,42 @@ private: }; +class NodeViewDeleteCommand : public UndoCommand +{ +public: + NodeViewDeleteCommand(); + + void AddNode(Node *node, Node *context); + + void AddEdge(Node *output, const NodeInput &input); + + virtual Project * GetRelevantProject() const override; + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + using NodePair = QPair; + + QVector nodes_; + + QVector edges_; + + struct RemovedNode { + Node *node; + Node *context; + QPointF pos; + NodeGraph *removed_from_graph; + }; + + QVector removed_nodes_; + + QObject memory_manager_; + +}; + } #endif // NODEVIEWUNDO_H