nodeview: fix issues when deleting nodes while dragging

This commit is contained in:
itsmattkc
2022-01-04 11:16:56 -08:00
parent 352a41a59e
commit d4fa9cb906
4 changed files with 121 additions and 73 deletions
+108 -73
View File
@@ -477,77 +477,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
if (HandRelease(event)) return;
if (create_edge_) {
// Check if the edge was reconnected to the same place as before
MultiUndoCommand* command = new MultiUndoCommand();
bool reconnected_to_itself = false;
if (create_edge_already_exists_) {
if (create_edge_output_item_ == create_edge_->from_item() && create_edge_->input() == create_edge_input_) {
reconnected_to_itself = true;
} else {
// We are moving (or removing) an existing edge
command->add_child(new NodeEdgeRemoveCommand(create_edge_->output(), create_edge_->input()));
}
} else {
// We're creating a new edge, which means this UI object is only temporary
delete create_edge_;
}
create_edge_ = nullptr;
// Clear highlight if we set one
if (create_edge_output_item_) {
create_edge_output_item_->SetHighlighted(false);
}
if (create_edge_input_item_) {
create_edge_input_item_->SetHighlighted(false);
}
if (create_edge_output_item_ && create_edge_input_item_) {
NodeInput &creating_input = create_edge_input_;
if (creating_input.IsValid()) {
// Make connection
if (!reconnected_to_itself) {
Node *creating_output = create_edge_output_item_->GetNode();
while (NodeGroup *output_group = dynamic_cast<NodeGroup*>(creating_output)) {
creating_output = output_group->GetOutputPassthrough();
}
while (NodeGroup *input_group = dynamic_cast<NodeGroup*>(creating_input.node())) {
creating_input = input_group->GetInputPassthroughs().value(creating_input.input());
}
if (creating_input.IsConnected()) {
Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input};
command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second));
}
command->add_child(new NodeEdgeAddCommand(creating_output, creating_input));
// If the output is not in the input's context, add it now. We check the item rather than
// the node itself, because sometimes a node may not be in the context but another node
// representing it will be (e.g. groups)
if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) {
command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos())));
}
}
creating_input.Reset();
}
}
create_edge_output_item_ = nullptr;
create_edge_input_item_ = nullptr;
// Collapse any items we expanded
for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) {
CollapseItem(*it);
}
create_edge_expanded_items_.clear();
Core::instance()->undo_stack()->pushIfHasChildren(command);
EndEdgeDrag();
}
MultiUndoCommand* command = new MultiUndoCommand();
@@ -1122,7 +1052,7 @@ void NodeView::PositionNewEdge(const QPoint &pos)
NodeViewItem* nvi = create_edge_expanded_items_.at(i);
QPointF local_pt = nvi->mapFromScene(scene_pt);
if (nvi->contains(local_pt) || (!nvi->IsOutputItem() && nvi->parentItem()->contains(nvi->parentItem()->mapFromScene(scene_pt)) && local_pt.y() > nvi->rect().bottom())) {
if (nvi->scene() == &scene_ && (nvi->contains(local_pt) || (!nvi->IsOutputItem() && nvi->parentItem()->contains(nvi->parentItem()->mapFromScene(scene_pt)) && local_pt.y() > nvi->rect().bottom()))) {
break;
} else {
// Collapsing an item will destroy its children, so if the cursor item happens to be a child
@@ -1310,6 +1240,31 @@ void NodeView::LabelSelectedNodes()
Core::instance()->LabelNodes(selected_nodes_);
}
void NodeView::ItemAboutToBeDeleted(NodeViewItem *item)
{
dragging_items_.remove(item);
if (create_edge_) {
// Item should be removed from scene, but not yet deleted, allowing a safe PositionNewEdge call
// to disconnect
PositionNewEdge(mapFromGlobal(QCursor::pos()));
QGraphicsItem *test = item;
do {
if (test == item) {
break;
}
test = test->parentItem();
} while (test);
if (test == item) {
// Cancel edge function
EndEdgeDrag(true);
}
}
}
void NodeView::PasteNodesInternal(const QVector<Node *> &duplicate_nodes)
{
// If no graph, do nothing
@@ -1381,7 +1336,10 @@ void NodeView::PasteNodesInternal(const QVector<Node *> &duplicate_nodes)
void NodeView::AddContext(Node *n)
{
scene_.AddContext(n);
NodeViewContext *ctx = scene_.AddContext(n);
connect(ctx, &NodeViewContext::ItemAboutToBeDeleted, this, &NodeView::ItemAboutToBeDeleted);
connect(n, &Node::RemovedFromGraph, this, &NodeView::NodeRemovedFromGraph);
}
@@ -1414,6 +1372,83 @@ void NodeView::CollapseItem(NodeViewItem *item)
item->setZValue(0);
}
void NodeView::EndEdgeDrag(bool cancel)
{
// Check if the edge was reconnected to the same place as before
MultiUndoCommand* command = new MultiUndoCommand();
bool reconnected_to_itself = false;
if (create_edge_already_exists_) {
if (!cancel) {
if (create_edge_output_item_ == create_edge_->from_item() && create_edge_->input() == create_edge_input_) {
reconnected_to_itself = true;
} else {
// We are moving (or removing) an existing edge
command->add_child(new NodeEdgeRemoveCommand(create_edge_->output(), create_edge_->input()));
}
}
} else {
// We're creating a new edge, which means this UI object is only temporary
delete create_edge_;
}
create_edge_ = nullptr;
// Clear highlight if we set one
if (create_edge_output_item_) {
create_edge_output_item_->SetHighlighted(false);
}
if (create_edge_input_item_) {
create_edge_input_item_->SetHighlighted(false);
}
if (create_edge_output_item_ && create_edge_input_item_ && !cancel) {
NodeInput &creating_input = create_edge_input_;
if (creating_input.IsValid()) {
// Make connection
if (!reconnected_to_itself) {
Node *creating_output = create_edge_output_item_->GetNode();
while (NodeGroup *output_group = dynamic_cast<NodeGroup*>(creating_output)) {
creating_output = output_group->GetOutputPassthrough();
}
while (NodeGroup *input_group = dynamic_cast<NodeGroup*>(creating_input.node())) {
creating_input = input_group->GetInputPassthroughs().value(creating_input.input());
}
if (creating_input.IsConnected()) {
Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input};
command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second));
}
command->add_child(new NodeEdgeAddCommand(creating_output, creating_input));
// If the output is not in the input's context, add it now. We check the item rather than
// the node itself, because sometimes a node may not be in the context but another node
// representing it will be (e.g. groups)
if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) {
command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos())));
}
}
creating_input.Reset();
}
}
create_edge_output_item_ = nullptr;
create_edge_input_item_ = nullptr;
// Collapse any items we expanded
for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) {
CollapseItem(*it);
}
create_edge_expanded_items_.clear();
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void NodeView::SetAttachedItems(const QVector<AttachedItem> &items)
{
// Detach anything currently attached
+4
View File
@@ -167,6 +167,8 @@ private:
void CollapseItem(NodeViewItem *item);
void EndEdgeDrag(bool cancel = false);
NodeViewMiniMap *minimap_;
struct AttachedItem {
@@ -248,6 +250,8 @@ private slots:
void LabelSelectedNodes();
void ItemAboutToBeDeleted(NodeViewItem *item);
};
}
+6
View File
@@ -85,6 +85,12 @@ void NodeViewContext::RemoveChild(Node *node)
NodeViewItem *item = item_map_.take(node);
// Remove from scene before emitting signal so that any drag functions that might be happening
// now can be handled before the item is destroyed
scene()->removeItem(item);
emit ItemAboutToBeDeleted(item);
// Delete edges first because the edge destructor will try to reference item (maybe that should
// be changed...)
QVector<NodeViewEdge*> edges_to_remove = item->GetAllEdgesRecursively();
+3
View File
@@ -54,6 +54,9 @@ public slots:
bool ChildInputDisconnected(Node *output, const NodeInput& input);
signals:
void ItemAboutToBeDeleted(NodeViewItem *item);
protected:
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;