Undo/Redo
QtNodes provides built-in undo/redo support using Qt’s undo framework.
How It Works
The BasicGraphicsScene maintains a QUndoStack. User actions
automatically push commands to this stack:
Creating nodes
Deleting nodes
Creating connections
Deleting connections
Moving nodes
Duplicating nodes
Accessing the Undo Stack
BasicGraphicsScene scene(model);
// Get the undo stack
QUndoStack& undoStack = scene.undoStack();
// Wire up to UI
QAction* undoAction = undoStack.createUndoAction(this, "Undo");
undoAction->setShortcut(QKeySequence::Undo);
QAction* redoAction = undoStack.createRedoAction(this, "Redo");
redoAction->setShortcut(QKeySequence::Redo);
editMenu->addAction(undoAction);
editMenu->addAction(redoAction);
Built-in Commands
QtNodes provides these QUndoCommand implementations in UndoCommands.cpp:
Command |
Description |
|---|---|
|
Removes nodes and their connections |
|
Duplicates selected nodes |
|
Removes a connection |
|
Creates a connection |
|
Moves a node to a new position |
Serialization Requirement
Undo/redo for node deletion requires serialization support. Make sure your
model implements saveNode() and loadNode():
QJsonObject MyModel::saveNode(NodeId nodeId) const override
{
// Save all data needed to recreate this node
QJsonObject json;
json["id"] = static_cast<qint64>(nodeId);
QPointF pos = nodeData(nodeId, NodeRole::Position).toPointF();
json["position"] = QJsonObject{{"x", pos.x()}, {"y", pos.y()}};
// Save your custom data too
json["internal-data"] = getNodeInternalData(nodeId);
return json;
}
void MyModel::loadNode(QJsonObject const& json) override
{
// Recreate node from saved data
NodeId nodeId = static_cast<NodeId>(json["id"].toInt());
_nextId = std::max(_nextId, nodeId + 1);
_nodes.insert(nodeId);
emit nodeCreated(nodeId);
// Restore position
auto pos = json["position"].toObject();
setNodeData(nodeId, NodeRole::Position,
QPointF(pos["x"].toDouble(), pos["y"].toDouble()));
// Restore custom data
restoreNodeInternalData(nodeId, json["internal-data"].toObject());
}
Warning
Without proper saveNode()/loadNode(), deleted nodes cannot be
restored by undo.
Custom Undo Commands
Create custom commands for your own operations:
class ChangeNodeColorCommand : public QUndoCommand
{
public:
ChangeNodeColorCommand(MyModel* model, NodeId nodeId, QColor newColor)
: _model(model)
, _nodeId(nodeId)
, _newColor(newColor)
, _oldColor(model->getNodeColor(nodeId))
{
setText(QString("Change color of node %1").arg(nodeId));
}
void undo() override {
_model->setNodeColor(_nodeId, _oldColor);
}
void redo() override {
_model->setNodeColor(_nodeId, _newColor);
}
private:
MyModel* _model;
NodeId _nodeId;
QColor _newColor;
QColor _oldColor;
};
// Push to stack
scene.undoStack().push(
new ChangeNodeColorCommand(&model, nodeId, Qt::red)
);
Grouping Commands
Use macros to group multiple commands:
undoStack.beginMacro("Batch Operation");
// These will undo/redo together
undoStack.push(new Command1(...));
undoStack.push(new Command2(...));
undoStack.push(new Command3(...));
undoStack.endMacro();
Clearing History
// Clear all undo history
undoStack.clear();
// Mark current state as "clean" (for save indicators)
undoStack.setClean();
// Check if modified since last save
if (!undoStack.isClean()) {
// Show "unsaved changes" warning
}
Keyboard Shortcuts
Default shortcuts (handled by GraphicsView):
Ctrl+Z– UndoCtrl+Shift+ZorCtrl+Y– RedoDelete– Delete selection (createsDeleteCommand)Ctrl+D– Duplicate (createsDuplicateCommand)
See also
Serialization – Required for undo/redo