[core/ui] add a locked property on nodes

Add locked property on core nodes and update UI in the same commit because both parts are very dependent for this change.
This commit is contained in:
Julien-Haudegond 2020-08-11 12:56:34 +02:00
parent 8a9499a711
commit ff7f8b3e36
5 changed files with 69 additions and 118 deletions

View file

@ -873,7 +873,7 @@ class Graph(BaseObject):
flowEdges.append(link) flowEdges.append(link)
return flowEdges return flowEdges
def nodesFromNode(self, startNode, filterTypes=None): def nodesFromNode(self, startNode, filterTypes=None, reverse=True):
""" """
Return the node chain from startNode to the graph leaves. Return the node chain from startNode to the graph leaves.
@ -881,6 +881,9 @@ class Graph(BaseObject):
startNode (Node): the node to start the visit from. startNode (Node): the node to start the visit from.
filterTypes (str list): (optional) only return the nodes of the given types filterTypes (str list): (optional) only return the nodes of the given types
(does not stop the visit, this is a post-process only) (does not stop the visit, this is a post-process only)
reverse (bool): (optional) direction of visit.
True is for getting nodes depending on the startNode.
False is for getting nodes required for the startNode.
Returns: Returns:
The list of nodes and edges, from startNode to the graph leaves following edges. The list of nodes and edges, from startNode to the graph leaves following edges.
""" """
@ -894,22 +897,15 @@ class Graph(BaseObject):
visitor.discoverVertex = discoverVertex visitor.discoverVertex = discoverVertex
visitor.examineEdge = lambda edge, graph: edges.append(edge) visitor.examineEdge = lambda edge, graph: edges.append(edge)
self.dfs(visitor=visitor, startNodes=[startNode], reverse=True) self.dfs(visitor=visitor, startNodes=[startNode], reverse=reverse)
return nodes, edges return nodes, edges
@Slot(Node, result="QVariantList") def nodesDependingOnNode(self, startNode, filterTypes=None):
def onlyNodesFromNode(self, startNode, filterType=None): nodes, edges = self.nodesFromNode(startNode, filterTypes=filterTypes, reverse=True)
nodes = [] return nodes
edges = []
visitor = Visitor()
def discoverVertex(vertex, graph): def nodesRequiredForNode(self, startNode, filterTypes=None):
if not filterType or vertex.nodeType == filterType: nodes, edges = self.nodesFromNode(startNode, filterTypes=filterTypes, reverse=False)
nodes.append(vertex)
visitor.discoverVertex = discoverVertex
visitor.examineEdge = lambda edge, graph: edges.append(edge)
self.dfs(visitor=visitor, startNodes=[startNode], reverse=True)
return nodes return nodes
@Slot(Node, result=int) @Slot(Node, result=int)

View file

@ -462,6 +462,9 @@ class BaseNode(BaseObject):
self._position = position or Position() self._position = position or Position()
self._attributes = DictModel(keyAttrName='name', parent=self) self._attributes = DictModel(keyAttrName='name', parent=self)
self.attributesPerUid = defaultdict(set) self.attributesPerUid = defaultdict(set)
self._locked = False
self.globalStatusChanged.connect(self.updateLocked)
def __getattr__(self, k): def __getattr__(self, k):
try: try:
@ -813,6 +816,55 @@ class BaseNode(BaseObject):
def __repr__(self): def __repr__(self):
return self.name return self.name
def getLocked(self):
return self._locked
def setLocked(self, lock):
if self._locked == lock:
return
self._locked = lock
self.lockedChanged.emit()
def updateLocked(self):
currentStatus = self.getGlobalStatus()
lockedStatus = (Status.RUNNING, Status.SUBMITTED)
# Unlock required nodes if the current node changes to Error
if currentStatus == Status.ERROR:
requiredNodes = self.graph.nodesRequiredForNode(self)
for node in requiredNodes:
node.setLocked(False)
# Avoid useless travel through nodes
# For instance: when loading a scene with successful nodes
if not self._locked and currentStatus == Status.SUCCESS:
return
if currentStatus == Status.SUCCESS:
# At this moment, the node is necessarily locked because of previous if statement
requiredNodes = self.graph.nodesRequiredForNode(self)
dependentNodes = self.graph.nodesDependingOnNode(self)
stayLocked = None
# Check if at least one dependentNode is submitted or currently running
for node in dependentNodes:
if node.getGlobalStatus() in lockedStatus and node._chunks.at(0).statusNodeName == node.name:
stayLocked = True
break
if not stayLocked:
# Unlock every required node
for node in requiredNodes:
node.setLocked(False)
return
elif currentStatus in lockedStatus:
requiredNodes = self.graph.nodesRequiredForNode(self)
for node in requiredNodes:
node.setLocked(True)
return
self.setLocked(False)
name = Property(str, getName, constant=True) name = Property(str, getName, constant=True)
label = Property(str, getLabel, constant=True) label = Property(str, getLabel, constant=True)
nodeType = Property(str, nodeType.fget, constant=True) nodeType = Property(str, nodeType.fget, constant=True)
@ -834,6 +886,8 @@ class BaseNode(BaseObject):
globalStatusChanged = Signal() globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged) globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged) isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
lockedChanged = Signal()
locked = Property(bool, getLocked, setLocked, notify=lockedChanged)
class Node(BaseNode): class Node(BaseNode):

View file

@ -251,21 +251,7 @@ Item {
point2x: dst.nodeItem.x + dstAnchor.x point2x: dst.nodeItem.x + dstAnchor.x
point2y: dst.nodeItem.y + dstAnchor.y point2y: dst.nodeItem.y + dstAnchor.y
onPressed: { onPressed: {
var canEdit = true const canEdit = !edge.src.node.locked
if(_reconstruction.computing) {
if(uigraph.taskManager.nodes.contains(edge.src.node)) {
canEdit = false;
} else {
if(object.globalStatus == "SUCCESS") {
var nodes = uigraph.graph.onlyNodesFromNode(edge.src.node);
for(var i = 0; i < nodes.length; i++) {
if(["SUBMITTED", "RUNNING"].includes(nodes[i].globalStatus) && nodes[i].chunks.at(0).statusNodeName == nodes[i].name) {
canEdit = false;
}
}
}
}
}
if(canEdit && event.button == Qt.RightButton) if(canEdit && event.button == Qt.RightButton)
{ {
@ -325,27 +311,7 @@ Item {
} }
MenuItem { MenuItem {
text: "Remove Node" + (removeFollowingButton.hovered ? "s From Here" : "") text: "Remove Node" + (removeFollowingButton.hovered ? "s From Here" : "")
enabled: { enabled: nodeMenu.currentNode ? !nodeMenu.currentNode.locked : false
if(! _reconstruction.computing) {
return true;
}
if(uigraph.taskManager.nodes.contains(uigraph.selectedNode)) {
return false;
} else {
if(uigraph.selectedNode.globalStatus == "SUCCESS") {
var nodes = uigraph.graph.onlyNodesFromNode(uigraph.selectedNode);
for(var i = 0; i < nodes.length; i++) {
if(["SUBMITTED", "RUNNING"].includes(nodes[i].globalStatus) && nodes[i].chunks.at(0).statusNodeName == nodes[i].name) {
return false;
}
}
}
}
return true;
}
onTriggered: uigraph.removeNode(nodeMenu.currentNode) onTriggered: uigraph.removeNode(nodeMenu.currentNode)
MaterialToolButton { MaterialToolButton {
id: removeFollowingButton id: removeFollowingButton
@ -361,26 +327,7 @@ Item {
MenuSeparator {} MenuSeparator {}
MenuItem { MenuItem {
text: "Delete Data" + (deleteFollowingButton.hovered ? " From Here" : "" ) + "..." text: "Delete Data" + (deleteFollowingButton.hovered ? " From Here" : "" ) + "..."
enabled: { enabled: nodeMenu.currentNode ? !nodeMenu.currentNode.locked : false
if(! _reconstruction.computing) {
return true;
}
if(uigraph.taskManager.nodes.contains(uigraph.selectedNode) || ["SUBMITTED", "RUNNING"].includes(_reconstruction.selectedNode.globalStatus)) {
return false;
} else {
if(uigraph.selectedNode.globalStatus == "SUCCESS") {
var nodes = uigraph.graph.onlyNodesFromNode(uigraph.selectedNode);
for(var i = 0; i < nodes.length; i++) {
if(["SUBMITTED", "RUNNING"].includes(nodes[i].globalStatus) && nodes[i].chunks.at(0).statusNodeName == nodes[i].name) {
return false;
}
}
}
}
return true;
}
function showConfirmationDialog(deleteFollowing) { function showConfirmationDialog(deleteFollowing) {
var obj = deleteDataDialog.createObject(root, var obj = deleteDataDialog.createObject(root,
@ -443,26 +390,7 @@ Item {
node: object node: object
width: uigraph.layout.nodeWidth width: uigraph.layout.nodeWidth
readOnly: {
if(! uigraph.computing) {
return false;
}
if(object.globalStatus == "SUCCESS") {
var nodes = uigraph.graph.onlyNodesFromNode(object);
for(var i = 0; i < nodes.length; i++) {
if(["SUBMITTED", "RUNNING"].includes(nodes[i].globalStatus) && nodes[i].chunks.at(0).statusNodeName == nodes[i].name) {
return true;
}
}
} else if(["SUBMITTED", "RUNNING"].includes(object.globalStatus)) {
return true;
} else {
return false;
}
return false;
}
selected: uigraph.selectedNode === node selected: uigraph.selectedNode === node
hovered: uigraph.hoveredNode === node hovered: uigraph.hoveredNode === node
onSelectedChanged: if(selected) forceActiveFocus() onSelectedChanged: if(selected) forceActiveFocus()

View file

@ -16,7 +16,7 @@ Item {
/// The underlying Node object /// The underlying Node object
property variant node property variant node
/// Whether the node can be modified /// Whether the node can be modified
property bool readOnly: false property bool readOnly: node.locked
/// Whether the node is in compatibility mode /// Whether the node is in compatibility mode
readonly property bool isCompatibilityNode: node.hasOwnProperty("compatibilityIssue") readonly property bool isCompatibilityNode: node.hasOwnProperty("compatibilityIssue")
/// Mouse related states /// Mouse related states

View file

@ -167,13 +167,10 @@ ApplicationWindow {
} }
else else
_reconstruction.execute(node); _reconstruction.execute(node);
nodeEditor.updateNodeStatus()
} }
function submit(node) { function submit(node) {
_reconstruction.submit(node); _reconstruction.submit(node);
nodeEditor.updateNodeStatus()
} }
@ -729,32 +726,8 @@ ApplicationWindow {
node: _reconstruction.selectedNode node: _reconstruction.selectedNode
property bool computing: _reconstruction.computing property bool computing: _reconstruction.computing
// Make NodeEditor readOnly when computing // Make NodeEditor readOnly when computing
readOnly: false readOnly: node.locked
onNodeChanged: { updateNodeStatus() }
onComputingChanged: { updateNodeStatus() }
function updateNodeStatus() {
if(! _reconstruction.computing) {
readOnly = false;
return;
}
if(node.globalStatus === "SUCCESS") {
var nodes = _reconstruction.graph.onlyNodesFromNode(node);
for(var i = 0; i < nodes.length; i++) {
if(["SUBMITTED", "RUNNING"].includes(nodes[i].globalStatus) && nodes[i].chunks.at(0).statusNodeName == nodes[i].name) {
readOnly = true;
return;
}
}
readOnly = false;
} else if(["SUBMITTED", "RUNNING"].includes(node.globalStatus)) {
readOnly = true;
} else {
readOnly = false;
}
}
onAttributeDoubleClicked: workspaceView.viewAttribute(attribute, mouse) onAttributeDoubleClicked: workspaceView.viewAttribute(attribute, mouse)
onUpgradeRequest: { onUpgradeRequest: {
var n = _reconstruction.upgradeNode(node); var n = _reconstruction.upgradeNode(node);