[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)
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.
@ -881,6 +881,9 @@ class Graph(BaseObject):
startNode (Node): the node to start the visit from.
filterTypes (str list): (optional) only return the nodes of the given types
(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:
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.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
@Slot(Node, result="QVariantList")
def onlyNodesFromNode(self, startNode, filterType=None):
nodes = []
edges = []
visitor = Visitor()
def nodesDependingOnNode(self, startNode, filterTypes=None):
nodes, edges = self.nodesFromNode(startNode, filterTypes=filterTypes, reverse=True)
return nodes
def discoverVertex(vertex, graph):
if not filterType or vertex.nodeType == filterType:
nodes.append(vertex)
visitor.discoverVertex = discoverVertex
visitor.examineEdge = lambda edge, graph: edges.append(edge)
self.dfs(visitor=visitor, startNodes=[startNode], reverse=True)
def nodesRequiredForNode(self, startNode, filterTypes=None):
nodes, edges = self.nodesFromNode(startNode, filterTypes=filterTypes, reverse=False)
return nodes
@Slot(Node, result=int)

View file

@ -462,6 +462,9 @@ class BaseNode(BaseObject):
self._position = position or Position()
self._attributes = DictModel(keyAttrName='name', parent=self)
self.attributesPerUid = defaultdict(set)
self._locked = False
self.globalStatusChanged.connect(self.updateLocked)
def __getattr__(self, k):
try:
@ -813,6 +816,55 @@ class BaseNode(BaseObject):
def __repr__(self):
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)
label = Property(str, getLabel, constant=True)
nodeType = Property(str, nodeType.fget, constant=True)
@ -834,6 +886,8 @@ class BaseNode(BaseObject):
globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
lockedChanged = Signal()
locked = Property(bool, getLocked, setLocked, notify=lockedChanged)
class Node(BaseNode):

View file

@ -251,21 +251,7 @@ Item {
point2x: dst.nodeItem.x + dstAnchor.x
point2y: dst.nodeItem.y + dstAnchor.y
onPressed: {
var canEdit = true
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;
}
}
}
}
}
const canEdit = !edge.src.node.locked
if(canEdit && event.button == Qt.RightButton)
{
@ -325,27 +311,7 @@ Item {
}
MenuItem {
text: "Remove Node" + (removeFollowingButton.hovered ? "s From Here" : "")
enabled: {
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;
}
enabled: nodeMenu.currentNode ? !nodeMenu.currentNode.locked : false
onTriggered: uigraph.removeNode(nodeMenu.currentNode)
MaterialToolButton {
id: removeFollowingButton
@ -361,26 +327,7 @@ Item {
MenuSeparator {}
MenuItem {
text: "Delete Data" + (deleteFollowingButton.hovered ? " From Here" : "" ) + "..."
enabled: {
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;
}
enabled: nodeMenu.currentNode ? !nodeMenu.currentNode.locked : false
function showConfirmationDialog(deleteFollowing) {
var obj = deleteDataDialog.createObject(root,
@ -443,26 +390,7 @@ Item {
node: object
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
hovered: uigraph.hoveredNode === node
onSelectedChanged: if(selected) forceActiveFocus()

View file

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

View file

@ -167,13 +167,10 @@ ApplicationWindow {
}
else
_reconstruction.execute(node);
nodeEditor.updateNodeStatus()
}
function submit(node) {
_reconstruction.submit(node);
nodeEditor.updateNodeStatus()
}
@ -729,32 +726,8 @@ ApplicationWindow {
node: _reconstruction.selectedNode
property bool computing: _reconstruction.computing
// Make NodeEditor readOnly when computing
readOnly: false
onNodeChanged: { updateNodeStatus() }
onComputingChanged: { updateNodeStatus() }
readOnly: node.locked
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)
onUpgradeRequest: {
var n = _reconstruction.upgradeNode(node);