class TRPropertyBox(QDialog, Ui_TRPropertyBox): """ This displays and manages the Relationship Template Dialog box """ def __init__(self, parent=None, mode=None, objectDict=None, designModel = None): """ objectDict - Relationship Template object dictionary. For creating a new Relationship Template this will be None @param parent reference to the parent widget @type QWidget """ super(TRPropertyBox, self).__init__(parent) self.parent = parent self.schemaModel = self.parent.schemaObject self.settings = QSettings() self.formatChanged = False self.helper = Helper() self.setupUi(self) self.designModel = designModel self.modelData = self.designModel.modelData if objectDict is None: self.objectDict = self.designModel.newRelTemplateDict() else: self.objectDict = objectDict self.mode = mode # get the class that controls the data grid for relationship templates self.RelTemplateCypher = RelTemplateCypher(templateDict=self.objectDict) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # Properties Grid self.gridProps.setModel(self.createPropModel()) comboPropList = [""] comboPropList.extend(sorted(set(self.designModel.instanceList("Property") + self.schemaModel.instanceList("Property")))) dataTypeList = [dataType.value for dataType in DataType] self.gridProps.setItemDelegateForColumn(DATATYPE, CBDelegate(self, dataTypeList, setEditable=False )) self.gridProps.setItemDelegateForColumn(PROPERTY, CBDelegate(self, comboPropList, setEditable=True )) self.gridProps.setItemDelegateForColumn(PROPDEF, NeoEditDelegate(self)) self.gridProps.setColumnWidth(PROPERTY, 350) self.gridProps.setColumnWidth(DATATYPE, 125) self.gridProps.setColumnWidth(PROPREQ, 100) self.gridProps.setColumnWidth(PROPDEF, 150) self.gridProps.setColumnWidth(EXISTS, 100) self.gridProps.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridProps.setSelectionMode(QAbstractItemView.SingleSelection) header = self.gridProps.horizontalHeader() header.setSectionResizeMode(PROPERTY, QHeaderView.Interactive) header.setSectionResizeMode(DATATYPE, QHeaderView.Fixed) header.setSectionResizeMode(PROPREQ, QHeaderView.Fixed) header.setSectionResizeMode(PROPDEF, QHeaderView.Interactive) header.setSectionResizeMode(EXISTS, QHeaderView.Fixed) # constraints grid self.gridConstraints.setModel(self.createConstraintsModel()) conTypeList = ["Property Exists"] self.gridConstraints.setItemDelegateForColumn(CONTYPE, CBDelegate(self, conTypeList, setEditable=False )) self.gridConstraints.setColumnWidth(CONTYPE, 120) self.gridConstraints.setColumnWidth(CONPROP, 350) header = self.gridConstraints.horizontalHeader() header.setSectionResizeMode(CONTYPE, QHeaderView.Fixed) header.setSectionResizeMode(CONPROP, QHeaderView.Interactive) # populate the rel template dropdowns self.loadTemplateDropdowns() # add the data grid widget. self.relGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.RelTemplateCypher) self.relGridLayout = QVBoxLayout(self.dataTabFrame) self.relGridLayout.setObjectName("relGridLayout") self.relGridLayout.addWidget(self.relGrid) #populate ui data from object self.populateUIfromObject() # populate combo boxes used on constraint and index grids self.updateComboBoxes() # sync definition checkboxes with constraints self.syncDefCheckBoxes() if self.mode == "NEW": self.txtRelTemplateName.setFocus() else: # disable definition fields self.txtRelTemplateName.setEnabled(False) self.cboRelName.setEnabled(False) # disable from and to nodes if they have been defined if self.cmbFromTemplate.currentIndex() > 0: self.cmbFromTemplate.setEnabled(False) if self.cmbToTemplate.currentIndex() > 0: self.cmbToTemplate.setEnabled(False) def updateComboBoxes(self): propList = [""] + [ self.gridProps.model().item(row,PROPERTY).data(Qt.EditRole) for row in range(0,self.gridProps.model().rowCount())] self.gridConstraints.setItemDelegateForColumn(CONPROP, CBDelegate(self, propList, setEditable=True )) def loadTemplateDropdowns(self, ): # load source node template dropdown dropdownList = [] dropdownList.append("No Template Selected") nodeList = dropdownList + self.designModel.instanceList("Node Template") self.cmbFromTemplate.addItems(nodeList) self.cmbToTemplate.addItems(nodeList) def createPropModel(self): model = QStandardItemModel(0, 5) model.setHeaderData(PROPERTY, Qt.Horizontal, "Property") model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type") model.setHeaderData(PROPREQ, Qt.Horizontal, "Required") model.setHeaderData(PROPDEF, Qt.Horizontal, "Default Value") model.setHeaderData(EXISTS, Qt.Horizontal, "Exists") # connect model slots model.itemChanged.connect(self.propModelItemChanged) return model def createConstraintsModel(self): # CONSTRAINT GRID - CONTYPE, CONPROP, model = QStandardItemModel(0,2) model.setHeaderData(CONTYPE, Qt.Horizontal, "Constraint Type") model.setHeaderData(CONPROP, Qt.Horizontal, "Property") # connect model slots model.itemChanged.connect(self.constraintModelItemChanged) return model def populateUIfromObject(self, ): # default the combo boxes to nothing selected self.cmbFromTemplate.setCurrentIndex(0) self.cmbToTemplate.setCurrentIndex(0) # get the custom template format if any self.TRFormatDict = self.objectDict.get("TRformat", None) if self.TRFormatDict == None: self.rbTemplateDefaultFormat.setChecked(True) self.btnDefineTemplateFormat.setEnabled(False) else: self.rbTemplateCustomFormat.setChecked(True) self.btnDefineTemplateFormat.setEnabled(True) # get the custom instance format if any self.IRFormatDict = self.objectDict.get("IRformat", None) if self.IRFormatDict == None: self.rbInstanceDefaultFormat.setChecked(True) self.btnDefineInstanceFormat.setEnabled(False) else: self.rbInstanceCustomFormat.setChecked(True) self.btnDefineInstanceFormat.setEnabled(True) # name, relname, desc self.txtRelTemplateName.insert(str(self.objectDict["name"])) self.loadRelationshipNameDropDown() self.editDescription.appendPlainText(self.objectDict["desc"]) # from and to node templates index = self.cmbFromTemplate.findText(self.objectDict["fromTemplate"]) if index >= 0: self.cmbFromTemplate.setCurrentIndex(index) index = self.cmbToTemplate.findText(self.objectDict["toTemplate"]) if index >= 0: self.cmbToTemplate.setCurrentIndex(index) # from and to cardinalities index = self.cmbFromCardinality.findText(self.objectDict["fromCardinality"]) if index >= 0: self.cmbFromCardinality.setCurrentIndex(index) index = self.cmbToCardinality.findText(self.objectDict["toCardinality"]) if index >= 0: self.cmbToCardinality.setCurrentIndex(index) # cardinality description self.editToFromType.setText("Node Type {}".format(self.cmbFromTemplate.currentText())) self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText())) #properties for relProp in self.objectDict["properties"]: dataType = self.designModel.getPropertyDataType(relProp[PROPERTY]) self.addProp(self.gridProps.model(), relProp[PROPERTY], dataType, relProp[PROPREQ], relProp[PROPDEF], relProp[EXISTS]) # load constraints try: #CONTYPE, CONLBL, CONPROP, CONPROPLIST for nodeCon in self.objectDict["constraints"]: self.addConstraint(self.gridConstraints.model(), nodeCon[CONTYPE], nodeCon[CONPROP]) except: pass def loadRelationshipNameDropDown(self, ): # load relationship name dropdown dropdownList = [] dropdownList.append("") # blank option for user to enter a new rel type # get relationship types from the project model templateRelList = [relDict["relname"] for relDict in self.modelData["Relationship Template"] ] # get relationship types from the schemamodel - this needs to be fixed, need common access to schemamodel from project page and instance diagram schemaRelList = [rel["name"] for rel in self.schemaModel.schemaData["Relationship"] ] # schemaRelList = [] # merge, dedup, and sort the two lists and put them in the dropdown dropdownList.extend(sorted(set(templateRelList + schemaRelList))) self.cboRelName.addItems(dropdownList) # if no relationship name has been set then display enabled combo box if str(self.objectDict["relname"]) is None: self.cboRelName.setCurrentIndex(0) elif (str(self.objectDict["relname"]) == "NoRelationshipName" or len(self.objectDict["relname"]) < 1): self.cboRelName.setCurrentIndex(0) else: # if the relationship name has been set then display the rel name index = self.cboRelName.findText(str(self.objectDict["relname"])) if index >= 0: self.cboRelName.setCurrentIndex(index) else: # its not an existing name so just set the text in the combo box self.cboRelName.setEditText(str(self.objectDict["relname"])) # # PROPERTY GRID BUTTONS # @pyqtSlot() def on_btnPropUp_clicked(self): """ User clicks on property up button """ self.helper.moveTableViewRowUp(self.gridProps) @pyqtSlot() def on_btnPropDown_clicked(self): """ User clicks on property down button """ self.helper.moveTableViewRowDown(self.gridProps) @pyqtSlot() def on_btnPropAdd_clicked(self): """ Slot documentation goes here. """ self.gridProps.setSortingEnabled(False) self.addProp(self.gridProps.model(), "","Unknown","N","Null","N") # generic function to adjust grid after adding a row self.helper.adjustGrid(grid=self.gridProps) # # scroll to bottom to insure the new row is visible # self.gridProps.scrollToBottom() @pyqtSlot() def on_btnPropRemove_clicked(self): """ Slot documentation goes here. """ # indexes = self.gridProps.selectionModel().selectedIndexes() # for index in sorted(indexes): # print('Row %d is selected' % index.row()) # self.gridProps.model().removeRows(index.row(),1) indexes = self.gridProps.selectionModel().selectedIndexes() if len(indexes) > 0: for index in sorted(indexes): self.gridProps.model().removeRows(index.row(),1) else: self.helper.displayErrMsg("Remove Property", "You must select a row to remove") def addProp(self,model,prop,dataType, propreq, propdef, exists): item1 = QStandardItem(prop) item1.setEditable(True) item11 = QStandardItem(dataType) item11.setEditable(False) item12 = QStandardItem(propreq) item12.setEditable(True) item12.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item12.setText("Required") item13 = QStandardItem(propdef) # store the datatype in role + 1 item13.setData(dataType, Qt.UserRole+1) item13.setEditable(True) item2 = QStandardItem() item2.setFlags(Qt.ItemIsEnabled) item2.setText("Exists") if exists in [0, 1, 2]: item2.setCheckState(exists) else: item2.setCheckState(Qt.Unchecked) if propreq in [0, 1, 2]: item12.setCheckState(propreq) else: item12.setCheckState(Qt.Unchecked) model.appendRow([item1, item11, item12,item13, item2]) def handleItemClicked(self, item): return def addConstraint(self,model, conType, prop): # CONTYPE, CONPROP item1 = QStandardItem(conType) item1.setEditable(True) item3 = QStandardItem(prop) item3.setEditable(True) model.appendRow([item1, item3]) def syncDefCheckBoxes(self): # clear all checkboxes on the definition tab self.clearCheckBoxes() # loop through the constraints grid model=self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): relCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] # process Property Exists if relCon[CONTYPE] == "Property Exists": self.setPropCheckBox(prop=relCon[CONPROP], chkBox=EXISTS ) self.setPropCheckBox(prop=relCon[CONPROP], chkBox=PROPREQ ) def setPropCheckBox(self, prop=None, chkBox=None): try: model = self.gridProps.model() numrows = model.rowCount() if not prop is None: for row in range(0,numrows): if prop == model.item(row,PROPERTY).data(Qt.EditRole): model.item(row,chkBox).setCheckState(Qt.Checked) except: pass def clearCheckBoxes(self): model = self.gridProps.model() numrows = model.rowCount() for row in range(0,numrows): model.item(row,EXISTS).setCheckState(0) ################################################################################# # DIALOG BUTTONS ################################################################################# @pyqtSlot() def on_okButton_clicked(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_cancelButton_clicked(self): """ User Selects the Cancel button """ QDialog.reject(self) def validate(self): if self.objectDict is None: self.objectDict = {} templateName = self.txtRelTemplateName.text() # template name if self.helper.NoTextValueError(templateName, "Must enter a Relationship Template Name"): self.txtRelTemplateName.setFocus() return False # relationship name # name = self.txtRelName.text() name = self.cboRelName.currentText() if self.helper.NoTextValueError(name, "Must enter a Relationship Name"): # self.txtRelName.setFocus() self.cboRelName.setFocus() return False # dup check if self.mode == 'NEW': if self.helper.DupObjectError(designModel = self.designModel, objName=templateName, topLevel = "Relationship Template", txtMsg = "A Relationship Template named {} already exists".format(name)): self.txtRelTemplateName.setFocus() return False # must have a From Node Template if self.helper.NoComboBoxSelectionError(self.cmbFromTemplate, "Must enter a From Node Template"): self.cmbFromTemplate.setFocus() return False # must have a To Node Template if self.helper.NoComboBoxSelectionError(self.cmbToTemplate, "Must enter a To Node Template"): self.cmbToTemplate.setFocus() return False # property name must exist if self.helper.gridNoNameError(grid=self.gridProps, col=PROPERTY, txtMsg="You must supply a name for each Property"): self.gridProps.setFocus() return False # dup property if self.helper.gridDupEntryError(self.gridProps, col=PROPERTY, txtMsg="has been entered more than once. You can only use a Property once"): self.gridProps.setFocus() return False # # required property must have a default value # model = self.gridProps.model() # numrows = model.rowCount() # for row in range(0,numrows): # nodeProp = [model.item(row,PROPERTY).data(Qt.EditRole), # model.item(row,DATATYPE).data(Qt.EditRole), # model.item(row,PROPREQ).checkState(), # model.item(row,PROPDEF).data(Qt.EditRole), # model.item(row,EXISTS).checkState()] # if nodeProp[PROPREQ] == Qt.Checked and nodeProp[PROPDEF] == "": # self.helper.displayErrMsg("Validate", "Required property {} must have a default value.".format(nodeProp[PROPERTY])) # self.gridProps.setFocus() # return False # constraint type exists if self.helper.gridNoNameError(grid=self.gridConstraints, col=CONTYPE, txtMsg="You must select a constraint type"): self.tabRelTemplate.setCurrentIndex(CONSTRAINT) self.gridConstraints.setFocus() return False model =self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): nodeCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] if nodeCon[CONTYPE] in ["Property Exists", "Property Unique"]: if self.helper.NoTextValueError(nodeCon[CONPROP], "Row {} - You must select a property".format(row+1)): self.tabRelTemplate.setCurrentIndex(CONSTRAINT) self.gridConstraints.setFocus() return False # passed all edits so return True return True def apply(self, ): self.objectDict["TRformat"] = self.TRFormatDict self.objectDict["IRformat"] = self.IRFormatDict self.objectDict["name"] = self.txtRelTemplateName.text() # self.objectDict["relname"] = self.txtRelName.text() self.objectDict["relname"] = self.cboRelName.currentText() # if its a new rel type then add it to the model self.designModel.newRelationship(self.objectDict["relname"]) if self.cmbFromTemplate.currentIndex() > 0: self.objectDict["fromTemplate"] = self.cmbFromTemplate.currentText() else: self.objectDict["fromTemplate"] = '' if self.cmbToTemplate.currentIndex() > 0: self.objectDict["toTemplate"] = self.cmbToTemplate.currentText() else: self.objectDict["toTemplate"] = '' self.objectDict["fromCardinality"] = self.cmbFromCardinality.currentText() self.objectDict["toCardinality"] = self.cmbToCardinality.currentText() desc = self.editDescription.toPlainText() if desc is not None: self.objectDict["desc"] = desc #save the properties self.objectDict["properties"] = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0,numrows): relProp = [model.item(row,PROPERTY).data(Qt.EditRole), model.item(row,DATATYPE).data(Qt.EditRole), model.item(row,PROPREQ).checkState(), model.item(row,PROPDEF).data(Qt.EditRole), model.item(row,EXISTS).checkState()] self.designModel.newProperty(relProp[PROPERTY], relProp[DATATYPE]) # check to see if this is a new Property and create a Property object in the dictionary self.objectDict["properties"].append(relProp) # save the constraints # CONTYPE, CONPROP self.objectDict["constraints"] = [] model =self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): relCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] self.objectDict["constraints"].append(relCon) @pyqtSlot() def on_rbDefaultFormat_clicked(self): """ Slot documentation goes here. """ self.btnDefineFormat.setEnabled(False) @pyqtSlot() def on_rbCustomFormat_clicked(self): """ Slot documentation goes here. """ self.btnDefineFormat.setEnabled(True) @pyqtSlot() def on_btnDefineFormat_clicked(self): """ Slot documentation goes here. """ d = IRelFormatDlg(self, modelData = None, relFormat = IRelFormat(formatDict=self.TRFormatDict)) if d.exec_(): self.TRFormatDict = IRelFormat(formatDict=d.relFormat.formatDict).formatDict # tell diagrams to redraw self.formatChanged = True def constraintModelItemChanged(self, item): self.syncDefCheckBoxes() @pyqtSlot() def on_btnAddConstraint_clicked(self): """ Slot documentation goes here. """ self.gridConstraints.setSortingEnabled(False) self.addConstraint(self.gridConstraints.model(), "", "") # generic function to adjust grid after adding a row self.helper.adjustGrid(grid=self.gridConstraints) # # scroll to bottom to insure the new row is visible # self.gridConstraints.scrollToBottom() @pyqtSlot() def on_btnRemoveConstraint_clicked(self): """ User clicked the remove row button """ indexes = self.gridConstraints.selectionModel().selectedIndexes() if len(indexes) > 0: for index in sorted(indexes): self.gridConstraints.model().removeRows(index.row(),1) self.syncDefCheckBoxes() else: self.helper.displayErrMsg("Remove Constraint", "You must select a row to remove") # indexes = self.gridConstraints.selectionModel().selectedRows() # for index in sorted(indexes): # self.gridConstraints.model().removeRows(index.row(),1) def propModelItemChanged(self, item): # print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column())) # this fires when checkbox is selected or deselected. columnIndex = item.index().column() propName = self.gridProps.model().item(item.index().row(),PROPERTY).data(Qt.EditRole) dataType = self.designModel.getPropertyDataType(propName) if columnIndex == PROPERTY: # if property has changed then change the datatype propName = self.gridProps.model().item(item.index().row(),PROPERTY).data(Qt.EditRole) # get the defined datatype for the property dataType = self.designModel.getPropertyDataType(propName) self.gridProps.model().item(item.index().row(), DATATYPE).setText(dataType) # store the datatype in role + 1 for the default value self.gridProps.model().item(item.index().row(), PROPDEF).setData(dataType, Qt.UserRole+1) # set default value to a null string self.gridProps.model().item(item.index().row(), PROPDEF).setText("") # if the property doesn't exist yet then allow the datatype to be changed if self.designModel.objectExists(topLevel="Property",objectName=propName ) == False: self.gridProps.model().item(item.index().row(), DATATYPE).setEditable(True) else: self.gridProps.model().item(item.index().row(), DATATYPE).setEditable(False) if columnIndex == DATATYPE: # datatype changed so reset default value and store new datatype dataType = self.gridProps.model().item(item.index().row(),DATATYPE).data(Qt.EditRole) # store the datatype in role + 1 self.gridProps.model().item(item.index().row(), PROPDEF).setData(dataType, Qt.UserRole+1) self.gridProps.model().item(item.index().row(), PROPDEF).setText("") @pyqtSlot(int) def on_tabRelTemplate_currentChanged(self, index): """ If the user has switched to the data tab then update the object dictionary @param index DESCRIPTION @type int """ # user switched to the description tab. must regenerate description if index == DESCRIPTION: self.apply() self.brwsrGeneratedDesc.setText(self.designModel.getRelationshipDescription(self.objectDict)) # user switched to the definition tab. must resync checkboxes with the current values on the constraints tab if index == DEFINITION: self.syncDefCheckBoxes() if index == DATAGRID: if self.validate(): self.apply() self.relGrid.refreshGrid() else: self.tabRelTemplate.setCurrentIndex(0) # user switched to the constraint tab so update the combo boxes to get latest values from def if index == CONSTRAINT: self.updateComboBoxes() @pyqtSlot() def on_btnDefineTemplateFormat_clicked(self): """ Display the Node Template Format Editor """ myTemplateRelFormatDict = self.TRFormatDict # if the template doesn't have a specific instance node format then get the project default if myTemplateRelFormatDict is None: myTemplateRelFormatDict = deepcopy(self.modelData["TRformat"]) d = TRelFormatDlg(self, modelData = None, relFormat = TRelFormat(formatDict=myTemplateRelFormatDict)) if d.exec_(): # self.TRFormatDict = TRelFormat(formatDict=d.relFormat.formatDict).formatDict self.TRFormatDict = d.relFormat.formatDict self.formatChanged = True @pyqtSlot() def on_btnDefineInstanceFormat_clicked(self): """ Display the Instance Node Format Editor """ myInstanceRelFormatDict = self.IRFormatDict # if the template doesn't have a specific instance rel format then get the project default if myInstanceRelFormatDict is None: myInstanceRelFormatDict = deepcopy(self.modelData["IRformat"]) d = IRelFormatDlg(self, modelData = None, relFormat = IRelFormat(formatDict=myInstanceRelFormatDict)) if d.exec_(): # self.IRFormatDict = IRelFormat(formatDict=d.relFormat.formatDict).formatDict self.IRFormatDict = d.relFormat.formatDict self.formatChanged = True @pyqtSlot() def on_rbTemplateDefaultFormat_clicked(self): """ If default radio button selected, then disable the define format button """ self.btnDefineTemplateFormat.setEnabled(False) @pyqtSlot() def on_rbTemplateCustomFormat_clicked(self): """ If custom radio button selected, then enable the define format button """ self.btnDefineTemplateFormat.setEnabled(True) @pyqtSlot() def on_rbInstanceDefaultFormat_clicked(self): """ If default radio button selected, then disable the define format button """ self.btnDefineInstanceFormat.setEnabled(False) @pyqtSlot() def on_rbInstanceCustomFormat_clicked(self): """ If custom radio button selected, then enable the define format button """ self.btnDefineInstanceFormat.setEnabled(True) @pyqtSlot() def on_btnSetDefaultNull_clicked(self): """ User requests to set a property default to Null """ # grid only allows single selection indexes = self.gridProps.selectionModel().selectedIndexes() for index in indexes: valueIndex = self.gridProps.model().index(index.row(), PROPDEF) self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole) @pyqtSlot(int) def on_cmbToTemplate_currentIndexChanged(self, index): """ The to template changed @param index DESCRIPTION @type int """ self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText())) # print(self.editFromToType.text()) @pyqtSlot(int) def on_cmbFromTemplate_currentIndexChanged(self, index): """ The from template changed @param index DESCRIPTION @type int """ self.editToFromType.setText("Node Type {}".format(self.cmbFromTemplate.currentText())) # print(self.editToFromType.text()) @pyqtSlot(int) def on_cmbFromCardinality_currentIndexChanged(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type int """ self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText()))
class TPPropertyBox(QDialog, Ui_TPPropertyBox): """ Class documentation goes here. """ def __init__(self, parent=None, mode=None, objectDict=None, designModel=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(TPPropertyBox, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaObject self.settings = QSettings() self.helper = Helper() self.designModel = designModel self.modelData = self.designModel.modelData if objectDict is None: self.objectDict = self.designModel.newPathTemplateDict() else: self.objectDict = objectDict self.mode = mode # get the class that controls the data grid for relationship templates self.CypherGenPath = CypherGenPath(parent=self, templateDict=self.objectDict) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # path treeview setup self.tvPath.setContextMenuPolicy(Qt.CustomContextMenu) self.tvPath.customContextMenuRequested.connect(self.openMenu) self.tvPath.setDragDropMode(QAbstractItemView.DragOnly) # add the data grid widget. self.nodeGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.CypherGenPath) self.nodeGridLayout = QVBoxLayout(self.dataTabFrame) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.addWidget(self.nodeGrid) self.populatingMetaBox = False #populate ui data from object self.populateUIfromObject() if self.mode == "NEW": self.txtPathTemplateName.setFocus() else: # disable definition fields self.txtPathTemplateName.setEnabled(False) def populateUIfromObject(self): self.txtPathTemplateName.setText(self.objectDict["name"]) self.editDescription.appendPlainText(self.objectDict["description"]) self.populateTree() def validate(self): if self.objectDict is None: self.objectDict = {} templateName = self.txtPathTemplateName.text() # template name if self.helper.NoTextValueError(templateName, "Must enter a Path Template Name"): self.txtPathTemplateName.setFocus() return False # find duplicate cypher variables varList = [] # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) returnCount = 0 while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() thisQueryPathNode = queryPathNodeItem.data(0, Qt.UserRole) if thisQueryPathNode.type in ["Node", "Relationship"]: returnCount = returnCount + thisQueryPathNode.numReturnProps( ) if returnCount < 1: self.helper.displayErrMsg( thisQueryPathNode.templateName, "Must return at least one property.") return False if self.helper.NoTextValueError( thisQueryPathNode.templateName, "Must supply a template name."): return False if self.helper.NoTextValueError( thisQueryPathNode.cypherVar, "Must supply a cypher variable for {}.".format( thisQueryPathNode.templateName)): return False if thisQueryPathNode.cypherVar in varList: self.helper.displayErrMsg( "Validate Path", "Error - duplicate cypher variable {} defined in template: {} " .format(thisQueryPathNode.cypherVar, thisQueryPathNode.templateName)) return False if thisQueryPathNode.templateName == "No Template Selected" and thisQueryPathNode.blankTemplate == False: self.helper.displayErrMsg( "Validate Path", "Error - must select a template or set blank template to True " ) return False varList.append(thisQueryPathNode.cypherVar) tvPathIterator.__iadd__(1) else: break # passed all edits so return True return True def apply(self, ): '''save the object dictionary''' self.objectDict["name"] = self.txtPathTemplateName.text() self.objectDict["description"] = self.editDescription.toPlainText() nodeList = [] # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: # print("save node {}".format(tvPathIterator.value().text(0))) queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() nodeList.append(queryPathNodeDict) tvPathIterator.__iadd__(1) else: break self.objectDict["queryPath"] = nodeList self.designModel.setModelDirty() #----------------------------------------------------------------------------------------------------------------------- # metabox grid methods #----------------------------------------------------------------------------------------------------------------------- def createMetaBoxModel(self): # ATTRNAME, ATTRVALUE gridNodeMeta model = QStandardItemModel(0, 2) model.setHeaderData(ATTRNAME, Qt.Horizontal, "Attribute") model.setHeaderData(ATTRVALUE, Qt.Horizontal, "Value") return model def clearMetaBox(self): self.lblMetaHeader.setText("Definition") # metabox grid setup # ATTRNAME, ATTRVALUE gridMetaBox self.gridMetaBox.setModel(None) self.metaBoxModel = self.createMetaBoxModel() self.gridMetaBox.setModel(self.metaBoxModel) self.gridMetaBox.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridMetaBox.setSelectionMode(QAbstractItemView.SingleSelection) self.gridMetaBox.setColumnWidth(ATTRNAME, 150) self.gridMetaBox.setColumnWidth(ATTRVALUE, 300) # header header = self.gridMetaBox.horizontalHeader() header.setSectionResizeMode(ATTRNAME, QHeaderView.Fixed) header.setSectionResizeMode(ATTRVALUE, QHeaderView.Stretch) # set editor delegate self.gridMetaBox.setItemDelegateForColumn(ATTRVALUE, MetaBoxDelegate(self)) # self.gridMetaBox.itemDelegateForColumn(ATTRVALUE).closeEditor.connect(self.metaBoxEditorClosed) # connect model slots self.metaBoxModel.itemChanged.connect(self.metaBoxModelItemChanged) # connect grid slots self.gridMetaBox.selectionModel().selectionChanged.connect( self.metaBoxGridSelectionChanged) def addMetaRow(self, attribute=None, value=None, editable=None): ''' add a row to the metabox grid ''' # print("add metarow {}-{}-{}".format(attribute, value, editable)) self.gridMetaBox.setSortingEnabled(False) # attribute if attribute is None: attribute = "" item1 = QStandardItem(attribute) item1.setEditable(False) # value if value is None: value = "" item2 = QStandardItem(str(value)) # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it item2.setData(attribute, Qt.UserRole) item2.setEditable(editable) self.gridMetaBox.model().appendRow([item1, item2]) def populateMetaBox(self, queryPathNode=None): self.populatingMetaBox = True if not queryPathNode is None: self.clearMetaBox() self.lblMetaHeader.setText("{} Definition".format( queryPathNode.type)) for attr in queryPathNode.attributes(): self.addMetaRow(attribute=attr[0], value=attr[1], editable=attr[2]) self.populatingMetaBox = False def metaBoxModelItemChanged(self, item): if self.populatingMetaBox == False: print("item data changed {} at row:{} col:{}".format( str(item.checkState()), item.index().row(), item.index().column())) # a cell changed so see if other changes are needed # update the queryPathNode object selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) name = self.gridMetaBox.model().item( item.index().row(), ATTRNAME).data(Qt.EditRole) value = self.gridMetaBox.model().item( item.index().row(), ATTRVALUE).data(Qt.EditRole) queryPathNode.updateAttr(name=name, value=value) # if name == "Blank Template" and value == "True": # # set template name to first entry in list # queryPathNode.updateAttr(name="Node Template", value="No Template Selected") # reload the metabox since changing one property can update others self.populateMetaBox(queryPathNode=queryPathNode) # repopulate return property grid as template may have self.populatePropBox(queryPathNode=queryPathNode) # update the tree view description queryPathNode.updateTreeView() # refresh cypher view self.refreshCypher() # # update displayName # self.rePopulateMetaBox(queryPathNode=queryPathNode) # force selection of this cell self.gridMetaBox.setCurrentIndex(item.index()) def metaBoxGridSelectionChanged(self): # not used # print("metabox grid selection changed") return def getCurrentCypherVar(self, ): # get the queryPathNode object selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) return queryPathNode.cypherVar return None #----------------------------------------------------------------------------------------------------------------------- # property box grid methods #----------------------------------------------------------------------------------------------------------------------- def createPropBoxModel(self): # PROPRETURN, PROPPARM, PROPNAME, COLNAME model = QStandardItemModel(0, 4) model.setHeaderData(PROPRETURN, Qt.Horizontal, "Return") model.setHeaderData(PROPPARM, Qt.Horizontal, "Parameter") model.setHeaderData(PROPNAME, Qt.Horizontal, "Property/Function") model.setHeaderData(COLNAME, Qt.Horizontal, "Column Name") return model def clearPropBox(self): # property box grid setup # PROPRETURN, PROPPARM, PROPNAME, COLNAME self.gridPropBox.setModel(None) self.propBoxModel = self.createPropBoxModel() self.gridPropBox.setModel(self.propBoxModel) self.gridPropBox.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridPropBox.setSelectionMode(QAbstractItemView.SingleSelection) self.gridPropBox.setColumnWidth(PROPRETURN, 100) self.gridPropBox.setColumnWidth(PROPPARM, 100) self.gridPropBox.setColumnWidth(PROPNAME, 400) self.gridPropBox.setColumnWidth(COLNAME, 400) # header header = self.gridPropBox.horizontalHeader() header.setSectionResizeMode(PROPRETURN, QHeaderView.Fixed) header.setSectionResizeMode(PROPPARM, QHeaderView.Fixed) header.setSectionResizeMode(PROPNAME, QHeaderView.Stretch) header.setSectionResizeMode(COLNAME, QHeaderView.Stretch) # set editor delegate # self.gridPropBox.setItemDelegateForColumn(PROPNAME, MetaBoxDelegate(self)) # connect model slots self.propBoxModel.itemChanged.connect(self.propBoxModelItemChanged) # connect grid slots self.gridPropBox.selectionModel().selectionChanged.connect( self.propBoxGridSelectionChanged) # def addPropRow(self, propName=None, colName=None, propParm=None, propReturn=None ): def addPropRow(self, returnProp=None): ''' add a row to the property box grid ''' # PROPRETURN, PROPPARM, PROPNAME, COLNAME self.gridPropBox.setSortingEnabled(False) # checkbox to add property to the return clause propReturn = returnProp[PROPRETURN] if propReturn is None or propReturn == "": propReturn = Qt.Unchecked item1 = QStandardItem() item1.setEditable(True) item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) # # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it # item1.setData(propReturn, Qt.UserRole) if propReturn in [0, 1, 2]: item1.setCheckState(propReturn) else: item1.setCheckState(Qt.Unchecked) item1.setText("") # checkbox to indicate property is a parameter propParm = returnProp[PROPPARM] if propParm is None or propParm == "": propParm = Qt.Unchecked item2 = QStandardItem() item2.setEditable(True) item2.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) # # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it # item2.setData(propParm, Qt.UserRole) if propParm in [0, 1, 2]: item2.setCheckState(propParm) else: item2.setCheckState(Qt.Unchecked) item2.setText("") # property name if returnProp[PROPNAME] is None: returnProp[PROPNAME] = "" item3 = QStandardItem(returnProp[PROPNAME]) item3.setEditable(False) # column name if returnProp[COLNAME] is None or returnProp[COLNAME] == "": colName = "{}_{}".format(str(self.getCurrentCypherVar()), returnProp[PROPNAME]) else: colName = returnProp[COLNAME] item4 = QStandardItem(colName) item4.setEditable(True) # add it to the grid self.gridPropBox.model().appendRow([item1, item2, item3, item4]) def populatePropBox(self, queryPathNode=None): # PROPRETURN, PARAMETER, PROPNAME, COLNAME if not queryPathNode is None: self.clearPropBox() for returnProp in queryPathNode.returnProps: self.addPropRow(returnProp) def propBoxModelItemChanged(self, item): # a cell changed so see if other changes are needed # update the queryPathNode object # print("item data changed {} {} at row:{} col:{}".format(str(item.checkState()), propName, item.index().row(), item.index().column())) selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) propName = self.gridPropBox.model().item( item.index().row(), PROPNAME).data(Qt.EditRole) colName = self.gridPropBox.model().item(item.index().row(), COLNAME).data(Qt.EditRole) propReturn = self.gridPropBox.model().item( item.index().row(), PROPRETURN).checkState() propParm = self.gridPropBox.model().item(item.index().row(), PROPPARM).checkState() queryPathNode.updateProp([propReturn, propParm, propName, colName]) self.refreshCypher() # force selection of this cell self.gridPropBox.setCurrentIndex(item.index()) def propBoxGridSelectionChanged(self): # not used # print("propbox grid selection changed") return #----------------------------------------------------------------------------------------------------------------------- # tree view methods #----------------------------------------------------------------------------------------------------------------------- def clearTree(self): self.tvPath.clear() self.tvPath.setColumnCount(1) self.tvPath.setHeaderLabels(["Query Path"]) self.tvPath.setItemsExpandable(True) def getMaxTreeOrder(self, ): '''scan the tree and find the max order attribute. This is used to increment and create the next highest order number''' max = 0 # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() order = queryPathNodeDict.get("order", 0) # save the value for order if it's greater if order > max: max = order tvPathIterator.__iadd__(1) else: break return max def findParentWidget(self, findOrder=None): '''scan the tree and find the treeviewwidget with the matching parentOrder''' # if the find id is None then this is the root so return the tree view itself if findOrder is None: return self.tvPath # find the parent tree view widget parentWidget = None # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() order = queryPathNodeDict.get("order", 0) # save the value for order if it's greater if order == findOrder: parentWidget = queryPathNodeItem break tvPathIterator.__iadd__(1) else: break return parentWidget def populateTree(self, ): self.clearTree() # print("path dict {}".format(self.objectDict)) # add tree items if len(self.objectDict["queryPath"]) > 0: for tvPathDict in self.objectDict["queryPath"]: # create the tree view path item object pathItem = QueryPathNode(designModel=self.designModel, nodeDict=tvPathDict) # print("add path item {}".format(pathItem.displayName)) # figure out who the parent is parent = self.findParentWidget(findOrder=pathItem.parentOrder) # add the treewidgetitem to the tree pathItem.treeItem = self.addTreeNode(parent=parent, pathItem=pathItem) else: # add a new root node pathItem = QueryPathNode(designModel=self.designModel, root=True, parentOrder=None, order=0, type="Path Template") pathItem.treeItem = self.addTreeNode(parent=self.tvPath, pathItem=pathItem) self.tvPath.resizeColumnToContents(0) self.tvPath.setCurrentItem(self.tvPath.topLevelItem(0)) # update cypher self.refreshCypher() def addTreeNode(self, parent=None, pathItem=None): # print("add tree node {}".format(pathItem.displayName)) item = QTreeWidgetItem(parent, [pathItem.displayName]) item.setData(0, Qt.UserRole, pathItem) item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) item.setExpanded(True) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) return item ########################################################################################### # query path tree view methods ########################################################################################### def openMenu(self, position): selected = self.tvPath.currentItem() if not (selected is None): tvPathNode = selected.data(0, Qt.UserRole) if (tvPathNode.type == "Path Template"): menu = QMenu() addNodeAction = menu.addAction("Add Node") addNodeAction.triggered.connect(self.addNodeTreeNode) addNodeAction = menu.addAction("Add Relationship") addNodeAction.triggered.connect(self.addRelTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return if (tvPathNode.type == "Node"): menu = QMenu() # addNodeAction = menu.addAction("Add Relationship") # addNodeAction.triggered.connect(self.addRelTreeNode) subOutMenu = QMenu("Out Bound Relationships", parent=menu) # generate outboundrel menu items outBoundList = self.parent.model.getOutboundRelTemplates( nodeTemplateName=tvPathNode.templateName) for relTemplate in outBoundList: aSubAction = subOutMenu.addAction(relTemplate["name"]) aSubAction.setData(relTemplate) aSubAction.triggered.connect(self.addOutboundRel) # print("outboundlist {}".format(relTemplate)) if len(outBoundList) == 0: aSubAction = subOutMenu.addAction( "No Outbound Relationship Templates") menu.addMenu(subOutMenu) subInMenu = QMenu("In Bound Relationships", parent=menu) # generate outboundrel menu items inBoundList = self.parent.model.getInboundRelTemplates( nodeTemplateName=tvPathNode.templateName) for relTemplate in inBoundList: aSubAction = subInMenu.addAction(relTemplate["name"]) aSubAction.setData(relTemplate) aSubAction.triggered.connect(self.addInboundRel) # print("inboundlist {}".format(relTemplate)) if len(inBoundList) == 0: aSubAction = subInMenu.addAction( "No Inbound Relationship Templates") menu.addMenu(subInMenu) removeNodeAction = menu.addAction("Remove this Node") removeNodeAction.triggered.connect(self.removeTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return if (tvPathNode.type == "Relationship"): menu = QMenu() addNodeAction = menu.addAction("Add Node") addNodeAction.triggered.connect(self.addNodeTreeNode) addNodeAction = menu.addAction("Remove This Relationship") addNodeAction.triggered.connect(self.removeTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return def addOutboundRel(self): # get the action that called this method aSubAction = QObject.sender(self) relTemplateDict = aSubAction.data() if not relTemplateDict is None: # add the rel template to the path self.addRelTreeNode(templateName=relTemplateDict["name"]) # add the node template to the path self.addNodeTreeNode(templateName=relTemplateDict["toTemplate"]) def addInboundRel(self): # get the action that called this method aSubAction = QObject.sender(self) relTemplateDict = aSubAction.data() if not relTemplateDict is None: # add the rel template to the path self.addRelTreeNode(templateName=relTemplateDict["name"]) # add the node template to the path self.addNodeTreeNode(templateName=relTemplateDict["fromTemplate"]) def addRelTreeNode(self, templateName=None): '''add a relationship tree-node into the query path tree''' parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 # print("add rel node {}-{}".format(order, parentOrder)) queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Relationship", templateName=templateName) # set a ypher variable name for the relationship queryPathNode.cypherVar = "r{}".format( str(queryPathNode.order).lower()) # queryPathNode.reltype = self.designModel.getRelType(templateName) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() def addNodeTreeNode(self, templateName=None): '''add a node tree-node into the query path tree''' # don't know why this happens... if type(templateName) is bool: templateName = None parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 # print("add node node {}-{}".format(order, parentOrder)) queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Node", templateName=templateName) # set a cypher variable name for the Node queryPathNode.cypherVar = "{}".format( queryPathNode.templateName[0].lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() def removeTreeNode(self): '''remove a node from the tree and all descedants''' # print("remove item") currentItem = self.tvPath.currentItem() parentItem = currentItem.parent() parentItem.removeChild(currentItem) self.tvPath.takeTopLevelItem( self.tvPath.indexOfTopLevelItem(currentItem)) # update cypher self.refreshCypher() @pyqtSlot() def on_okButton_clicked(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_cancelButton_clicked(self): """ User Selects the Cancel button """ QDialog.reject(self) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_tvPath_currentItemChanged(self, current, previous): """ Not Used @param current DESCRIPTION @type QTreeWidgetItem @param previous DESCRIPTION @type QTreeWidgetItem """ print("on tvpath current item changed") return @pyqtSlot(QModelIndex) def on_tvPath_clicked(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type QModelIndex """ print("on_tvPath_clicked") @pyqtSlot(QModelIndex) def on_tvPath_activated(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type QModelIndex """ print("on_tvPath_activated") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemClicked(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemClicked") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemActivated(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemActivated") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemEntered(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemEntered") @pyqtSlot() def on_tvPath_itemSelectionChanged(self): """ User clicks on an item in the tree view """ print("on_tvPath_itemSelectionChanged") selected = self.tvPath.currentItem() if not (selected is None): # parent = self.tvPath.currentItem().parent() queryPathNode = selected.data(0, Qt.UserRole) self.populateMetaBox(queryPathNode=queryPathNode) self.populatePropBox(queryPathNode=queryPathNode) @pyqtSlot(int) def on_tabPathTemplate_currentChanged(self, index): """ The user has switched to a different tab DEFINITION, DESCRIPTION, DATAGRID @param index DESCRIPTION @type int """ # user switched to the description tab. regenerate the description # if index == DESCRIPTION: # if self.validate(): # self.apply() # # get generated description # self.brwsrGenDescription.setText(self.designModel.getNodeDescription(self.objectDict["name"])) # user switched to the definition tab. # if index == DEFINITION: # self.syncDefCheckBoxes() # user switched to the data grid then update object dictionary so query will generate with latest values if index == DATAGRID: if self.validate(): self.apply() self.nodeGrid.refreshGrid() else: self.tabPathTemplate.setCurrentIndex(0) def refreshCypher(self): if self.validate(): self.apply() self.cypher, self.editParmDict = self.nodeGrid.genCypher.genMatch() # print("cypher:{}".format(self.cypher)) self.txtCypher.clear() self.txtCypher.appendPlainText(self.cypher) @pyqtSlot() def on_btnAddNode_clicked(self): """ user clicks on add node button """ '''add a node tree-node into the query path tree''' parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Node", templateName="Anonymous Node") # set a cypher variable name for the Node queryPathNode.cypherVar = "n{}".format( str(queryPathNode.order).lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() @pyqtSlot() def on_btnAddRel_clicked(self): """ user clicks on add relationship button """ parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Relationship", templateName="Anonymous Relationship") # set a ypher variable name for the relationship queryPathNode.cypherVar = "r{}".format( str(queryPathNode.order).lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() @pyqtSlot() def on_btnRemove_clicked(self): """ User clicks on remove button """ return @pyqtSlot() def on_btnAdd_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet raise NotImplementedError @pyqtSlot() def on_btnRemove_2_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet raise NotImplementedError
class CopyNodeToDiagramDlg(QDialog, Ui_CopyNodeToDiagramDlg): """ Class documentation goes here. """ def __init__(self, parent=None, rightClickPos=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CopyNodeToDiagramDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.settings = QSettings() self.rightClickPos = rightClickPos self.designModel = self.parent.model self.syncNeoCon = self.designModel.modelNeoCon self.itemDict = self.parent.itemDict self.helper = Helper() self.neoTypeFunc = NeoTypeFunc() self.nodeGrid = None # header area self.txtDiagramName.setText(self.parent.diagramName) self.txtNeoCon.setText("{} - {}".format( self.syncNeoCon.name, self.syncNeoCon.neoDict["URL"])) # load node template dropdown and disable it dropdownList = [] dropdownList.append("No Template Selected") dropdownList.extend( sorted(self.designModel.instanceList("Node Template"))) self.cboNodeTemplates.addItems(dropdownList) self.rbFilterTemplate.setChecked(True) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.parent.pageItem.neoConName, promptPW=self.parent.parent.pageItem.promptPW) # add the data grid widget. self.addNodeCypher = AddNodeCypher() self.nodeGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.addNodeCypher) self.nodeGridLayout = QVBoxLayout(self.frmDataGrid) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.addWidget(self.nodeGrid) def refreshDatabaseNodeGrid(self, ): # first make sure the node grid has been created. if self.nodeGrid is None: return nodeTemplate = self.cboNodeTemplates.currentText() index, nodeTemplateDict = self.designModel.getDictByName( topLevel="Node Template", objectName=nodeTemplate) if self.rbFilterTemplate.isChecked() == True: useTemplate = True else: useTemplate = False self.nodeGrid.refreshGrid(useTemplate=useTemplate, nodeTemplate=nodeTemplate, nodeTemplateDict=nodeTemplateDict) def getNodeRels(self, nodeID, nodeIDList): ''' Run a query that retrieves all relationships between one node and all other nodes on a diagram ''' try: msg = None p1 = str(nodeID) p2 = str(nodeIDList) cypher = '''match (f)-[r]->(t) where ((id(f) = {} and id(t) in {}) or (id(t) = {} and id(f) in {})) return id(f), f.NZID, f, id(r), type(r), r.NZID, r, id(t), t.NZID, t '''.format(p1, p2, p1, p2) # print(cypher) #run the query rc1, msg1 = self.syncNeoCon.runCypherAuto(cypher) except BaseException as e: msg = "{} - Get Relationships failed.".format(repr(e)) finally: QApplication.restoreOverrideCursor() if not msg is None: self.helper.displayErrMsg("Error Retrieving Relationships.", msg) ##################################################################################### # dialog buttons ##################################################################################### @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Slot documentation goes here. @param button DESCRIPTION @type QAbstractButton """ @pyqtSlot() def on_buttonBox_accepted(self): """ """ QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self) @pyqtSlot() def on_rbAllNodes_clicked(self): """ User selects all Nodes option, so configure UI and refresh the grid """ self.cboNodeTemplates.setEnabled(False) self.refreshDatabaseNodeGrid() @pyqtSlot() def on_rbFilterTemplate_clicked(self): """ User selects filter on template option """ self.cboNodeTemplates.setEnabled(True) self.refreshDatabaseNodeGrid() @pyqtSlot(int) def on_cboNodeTemplates_currentIndexChanged(self, index): """ User changes the node template selection so refresh the grid @param index DESCRIPTION @type int """ self.refreshDatabaseNodeGrid() @pyqtSlot() def on_btnAdd_clicked(self): """ User clicks the add button so add the selected nodes to the diagram """ applyX = self.rightClickPos.x() applyY = self.rightClickPos.y() QApplication.setOverrideCursor(Qt.WaitCursor) if self.rbAllNodes.isChecked() == True: ID, LBLS, PROPS, NODE = range(4) else: ID, NODE = range(2) # get the selected node template if self.rbFilterTemplate.isChecked() == True: nodeTemplate = self.cboNodeTemplates.currentText() else: nodeTemplate = None # make sure there is data in the grid if self.nodeGrid.gridCypherData.selectionModel() is None: self.helper.displayErrMsg("Copy Node", "Select a Node Template or All Nodes.") QApplication.restoreOverrideCursor() return # check if any rows selected if len(self.nodeGrid.gridCypherData.selectionModel().selectedRows( column=0)) > 0: # looking at nodes in the database for itemIndex in self.nodeGrid.gridCypherData.selectionModel( ).selectedRows(column=0): # get the Neo4j node id from the result set instanceNodeID = self.nodeGrid.gridCypherData.model().item( itemIndex.row(), ID).data(Qt.EditRole) # see if it's already an instance node in the model instanceNZID = self.designModel.lookupNZID( neoID=instanceNodeID, topLevel="Instance Node") # get the node object n = self.nodeGrid.gridCypherData.model().item( itemIndex.row(), NODE).data(Qt.UserRole) # get the list of labels from the node object lbls = [[lbl] for lbl in n.labels] # get the dictionary of properties from the node object and convert it to a list props = [] for key, val in dict(n).items(): props.append([ key, self.neoTypeFunc.getNeo4jDataType(value=val), self.neoTypeFunc.convertTypeToString(dataValue=val) ]) # create a new node instance dictionary newNodeInstanceDict = self.designModel.newNodeInstance( nodeID=instanceNodeID, templateName=nodeTemplate, labelList=lbls, propList=props, NZID=instanceNZID) NZID = self.parent.scene.dropINode( point=QPointF(applyX, applyY), nodeInstanceDict=newNodeInstanceDict, NZID=instanceNZID) if not NZID is None: applyX = applyX + 200 QApplication.restoreOverrideCursor() self.helper.displayErrMsg( "Copy Node", "Selected Node(s) were added to diagram.") # update project model self.parent.model.setModelDirty() self.parent.model.updateTV() else: QApplication.restoreOverrideCursor() self.helper.displayErrMsg( "Copy Node", "You must select a node to add to the diagram.") QApplication.restoreOverrideCursor()
class CypherEditGridWidget(QWidget, Ui_cypherEditGridWidget): """ Class documentation goes here. """ def __init__(self, parent=None, fileName=None, fileText=None, mode=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CypherEditGridWidget, self).__init__(parent) self.setupUi(self) self.parent = parent self.settings = QSettings() self.initUI() self.initScintilla() self.helper = Helper() self.mode = mode self.tabType = "CYPHER" self.tabName = fileName self.tabIndex = None # this is the index into the tabWidget of the tab this widget is on self.fileName = fileName self.fileText = fileText self.resultSet = None # create a neocon object for this file tab self.neoDriver = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # add the data grid widget. self.dataGridGeneric = DataGridGeneric() self.dataGrid = DataGridWidget(self, neoCon=self.neoDriver, genCypher=self.dataGridGeneric) self.nodeGridLayout = QVBoxLayout(self.frmDataGrid) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.setContentsMargins(1, 1, 1, 1) self.nodeGridLayout.setSpacing(1) self.nodeGridLayout.addWidget(self.dataGrid) if self.mode == MODENEW: if not self.fileText is None: self.loadText() if self.mode == MODEEDIT: self.loadFile() # position the splitter self.show( ) # you have to do this to force all the widgets sizes to update half = int((self.frmEditnGrid.height() / 2)) self.splitter.setSizes([half, half]) def logMsg(self, msg): if logging: logging.info(msg) def initUI(self, ): #initialize state of commit buttons and dropdown self.btnCommit.setEnabled(False) self.btnRollBack.setEnabled(False) self.cmbAutoCommit.setCurrentIndex(0) def initScintilla(self): # add and initialize the control to self.frmEditor self.editor = QsciScintilla() self.editor.setLexer(None) self.editor.setUtf8(True) # Set encoding to UTF-8 self.editor.setWrapMode(QsciScintilla.WrapNone) self.editor.setEolVisibility(False) self.editor.setIndentationsUseTabs(False) self.editor.setTabWidth(4) self.editor.setIndentationGuides(True) self.editor.setAutoIndent(True) self.editor.setMarginType(0, QsciScintilla.NumberMargin) self.editor.setMarginWidth(0, "00000") self.editor.setMarginsForegroundColor(QColor("#ffffffff")) self.editor.setMarginsBackgroundColor(QColor("#00000000")) self.verticalLayout_2.addWidget(self.editor) # setup the lexer self.lexer = CypherLexer(self.editor) self.editor.setLexer(self.lexer) self.setScintillaFontSize() self.editor.SendScintilla(self.editor.SCI_GETCURRENTPOS, 0) self.editor.setCaretForegroundColor(QColor("#ff0000ff")) self.editor.setCaretLineVisible(True) self.editor.setCaretLineBackgroundColor(QColor("#1f0000ff")) self.editor.setCaretWidth(2) self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch) def setScintillaFontSize(self, ): # set font size to value saved in settings try: fontSize = int(self.settings.value("Lexer/FontSize", "10")) except: fontSize = 10 finally: for style in range(5): self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, fontSize) self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, fontSize) self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, fontSize) ##################################################################################### # methods related to the cypher file ##################################################################################### def loadText(self, ): QApplication.setOverrideCursor(Qt.WaitCursor) self.editor.append(self.fileText) self.editor.setModified(True) QApplication.restoreOverrideCursor() def loadFile(self, ): file = QFile(self.fileName) if not file.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning( self, "NodeMaker", "Cannot read file %s:\n%s." % (self.fileName, file.errorString())) return False instr = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) self.editor.append(instr.readAll()) self.editor.setModified(False) QApplication.restoreOverrideCursor() def save(self, ): if self.mode == MODENEW: self.saveAs() else: self.saveIt() def saveAs(self, ): # first test to see if the file has changed # get filename to save as # dlg = QFileDialog() dlg.setAcceptMode(QFileDialog.AcceptSave) dlg.setDefaultSuffix("cyp") dlg.setNameFilters([ "Cypher Query (*.cyp *.cypher)", "Cypher Query (*.cyp)", "Cypher Query (*.cypher)", "all files (*.*)" ]) dlg.setDirectory(self.parent.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() if fileNames: self.fileName = fileNames[0] # save the file self.saveIt() def saveIt(self, ): file = QFile(self.fileName) if not file.open(QFile.WriteOnly | QFile.Text): QMessageBox.warning( self, "NodeEra", "Cannot write file %s:\n%s." % (self.fileName, file.errorString())) return outstr = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) outstr << self.editor.text() head, tail = ntpath.split(QFileInfo(self.fileName).fileName()) self.parent.tabCypher.setTabText(self.parent.tabCypher.currentIndex(), tail) self.mode = MODEEDIT QApplication.restoreOverrideCursor() def close(self, ): # print("editngrid close {}".format(self.fileName)) # see if there is an open transaction and cancel the close self.checkOpenTxn() # see if text has changed and save the file if the user wants to if self.editor.isModified(): # see if the user wants to save it if self.fileName is None: # these are unsaved cypher files so they have no filename yet displayName = self.parent.tabCypher.tabText(self.tabIndex) else: displayName = self.fileName if self.helper.saveChangedObject("Cypher File", displayName): self.save() return True ############################################################## # Button methods ############################################################## @pyqtSlot() def on_btnRun_clicked(self): """ Run the query at the cursor. """ self.runFileCursor() def runFileCursor(self): self.logMsg("User requests run Cypher in Cursor") # parse the text editor and get the index to the cypher command the cursor is pointing at currentCypherIndex = self.getSelectedCypher() # check if cursor in a query if currentCypherIndex is None: self.helper.displayErrMsg( "Run Query", "You must position cursor within the Cypher query.") QApplication.restoreOverrideCursor() return # get the cypher statement to be executed startOffset = self.cypherList[currentCypherIndex][0] self.dataGrid.cypher = self.cypherList[currentCypherIndex][1] # prompt the user for parameter values if any userCanceled = False if len(self.cypherParms[currentCypherIndex][1]) > 0: # print("Parms:{}".format(self.cypherParms[currentCypherIndex][1])) # display dialog to gather parms d = CypherParmEntryDlg( parent=self, parms=self.cypherParms[currentCypherIndex][1]) if d.exec_(): self.dataGrid.parmData = d.parmDict else: userCanceled = True self.dataGrid.parmData = None # print("Parm Dictionary:{}".format(self.dataGrid.parmData)) else: self.dataGrid.parmData = None # see if the user canceled the query rather than enter parameters if userCanceled: self.helper.displayErrMsg("Run Query", "User Canceled Query.") QApplication.restoreOverrideCursor() return # make sure the cypher is not just spaces, or nothing but a semicolon if (self.dataGrid.cypher.isspace() or len(self.dataGrid.cypher) == 0 or self.dataGrid.cypher.strip() == ";"): self.helper.displayErrMsg( "Run Query", "You must position cursor within the Cypher query.") else: QApplication.setOverrideCursor(Qt.WaitCursor) self.dataGrid.refreshGrid() QApplication.restoreOverrideCursor() # see if there was a syntax error and position cursor try: offset = self.dataGrid.neoCon.cypherLogDict["offset"] if offset > -1: self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS, offset + startOffset) except: pass finally: ### hack needed on mac os to force scintilla to show cursor and highlighted line self.helper.displayErrMsg("Run Query With Cursor", "Query Complete") def getSelectedCypher(self): ''' Build a list of cypher commands from the text in the editor ''' # get position of cursor which is a zero based offset, it seems to return zero if editor hasn't been clicked on yet try: position = self.editor.SendScintilla( QsciScintilla.SCI_GETCURRENTPOS, 0) except: position = 0 # initialize cypherList self.cypherList = [] self.cypherParms = [] parmList = [] currentCypherIndex = None # get the full text from the editor text = self.editor.text() # make sure there is something in the text if len(text) < 1: return currentCypherIndex # Walk through all the characters in text, and store start offset and end offset of each command startOffset = 0 endOffset = 0 foundCypher = False # tracks if we have found at least one character that is potentially a non-comment cypher command inComment = False # tracks if we're looking at comment characters inParm = False # tracks if we're looking at parameter characters inCypher = False # tracks if we've found a non comment character while scanning newParm = "" for chrCtr in range(0, len(text)): # print("before: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher)) # see if we're in a comment ( this doesn't work for multiline comments) if chrCtr + 1 < len(text) and text[chrCtr] == '/': inParm = False if text[chrCtr + 1] == "/": inComment = True inCypher = False # see if end of line elif ord(text[chrCtr]) in [13, 10]: inParm = False if chrCtr + 1 < len(text): if not text[chrCtr + 1] in [13, 10]: # end of line ends the comment inComment = False elif text[chrCtr] == "$": if not inComment: foundCypher = True inParm = True elif text[chrCtr] == " ": if not inComment: foundCypher = True inParm = False elif (text[chrCtr] == ";" and inComment == False): foundCypher = True inParm = False endOffset = chrCtr # save each command in the list self.cypherList.append( [startOffset, text[startOffset:endOffset + 1]]) self.cypherParms.append([startOffset, parmList]) parmList = [] # HAPPY PATH - see if this is the command where the cursor is located if (position >= startOffset and position <= endOffset): currentCypherIndex = len(self.cypherList) - 1 # set the next starting offset startOffset = chrCtr + 1 endOffset = startOffset elif inComment == False: foundCypher = True inCypher = True if inParm: newParm = newParm + text[chrCtr] else: if len(newParm) > 0: # print("Parameter: {}".format(newParm)) parmList.append(newParm) newParm = "" # print("after: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher)) # at this point all characters have been processed, must deal with edge cases, no final semicolon etc if len( self.cypherList ) == 0: # we never found a semi colon so the entire file is one cypher statement # return the entire text file self.cypherList.append([0, text]) self.cypherParms.append([0, parmList]) parmList = [] currentCypherIndex = 0 else: # we found some characters after the last semi colon. lastCypher = "" try: lastCypher = text[startOffset:len(text)] if lastCypher.isspace( ) == True: # if there is only whitespace after the last semicolon then return the last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 elif len( lastCypher ) < 1: # there are no characters after the last semicolon, but cursor is positioned past it then return the last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 elif inCypher == False and foundCypher == False: # none of the characters are outside a comment so return last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 else: self.cypherList.append( [startOffset, lastCypher] ) # since some characters are present, add them as the last cypher command self.cypherParms.append([startOffset, parmList]) parmList = [] if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 except: if currentCypherIndex is None: currentCypherIndex = len( self.cypherList ) - 1 # return the last cypher command found if any error # print("cypher list: {}".format(self.cypherList)) return currentCypherIndex @pyqtSlot() def on_btnRunScript_clicked(self): """ this button will run all the cypher commands in the file one at a time. """ self.runFileAsScript() def runFileAsScript(self, ): ''' run each cypher command in the file one at a time. ''' QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests run Cypher file as a script") # parse the text editor into cypher commands, we don't care which one the cursor is in self.getSelectedCypher() if len(self.cypherList) < 1: self.helper.displayErrMsg( "Run File As Script", "The file has no cypher commands in it.") for cypherCmd in self.cypherList: # this is the starting offset of the cypher command in the entire file cypherOffset = cypherCmd[0] self.dataGrid.cypher = cypherCmd[1] # set editor selection to the cypher command self.editor.SendScintilla(QsciScintilla.SCI_SETSEL, cypherOffset, cypherOffset + len(self.dataGrid.cypher)) # skip any cypher is not just spaces, or nothing but a semicolon if (self.dataGrid.cypher.isspace() or len(self.dataGrid.cypher) == 0 or self.dataGrid.cypher.strip() == ";"): #self.helper.displayErrMsg("Run Query", "You must position cursor within the Cypher query.") pass else: self.dataGrid.refreshGrid() QApplication.processEvents() # see if there was a syntax error and position cursor try: offset = self.dataGrid.neoCon.cypherLogDict["offset"] if offset > -1: self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS, offset + cypherOffset) except: pass # set editor selection to the end of the file self.editor.SendScintilla(QsciScintilla.SCI_SETSEL, len(self.editor.text()), len(self.editor.text())) QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnCommit_clicked(self): """ User clicks on the Commit button. Commit the TXN. """ self.doCommit() def doCommit(self): self.logMsg("User request Commit the transaction") if not self.neoDriver is None: rc, msg = self.neoDriver.commitTxn() self.logMsg("Commit Complete - {}".format(msg)) @pyqtSlot() def on_btnRollBack_clicked(self): """ User clicks on the Rollback button. Rollback the TXN. """ self.doRollBack() def doRollBack(self): self.logMsg("User request Rollback the transaction") if not self.neoDriver is None: rc, msg = self.neoDriver.rollbackTxn() self.logMsg("Rollback Complete - {}".format(msg)) def zoomIn(self, ): """ increase Font Size """ # self.editor.SendScintilla(QsciScintilla.SCI_ZOOMIN) # currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0) # self.settings.setValue("Lexer/FontSize", currentFontSize) # get style 0 font size - all styles use same size currentFontSize = self.editor.SendScintilla( QsciScintilla.SCI_STYLEGETSIZE, 0) currentFontSize = currentFontSize + 2 if currentFontSize < 24: self.settings.setValue("Lexer/FontSize", currentFontSize) for style in range(255): self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize) def zoomOut(self, ): """ decrease font size """ # self.editor.SendScintilla(QsciScintilla.SCI_ZOOMOUT) # currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0) # self.settings.setValue("Lexer/FontSize", currentFontSize) # get style 0 font size - all styles use same size currentFontSize = self.editor.SendScintilla( QsciScintilla.SCI_STYLEGETSIZE, 0) currentFontSize = currentFontSize - 2 if currentFontSize > 4: self.settings.setValue("Lexer/FontSize", currentFontSize) for style in range(255): # was 5 self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize) def checkOpenTxn(self): ''' check if current txn is still open. if autocommit is false then ask the user to commit or rollback if autocommit is true then do the commit ''' if not (self.neoDriver is None): if not (self.neoDriver.tx is None): if self.neoDriver.tx.closed() is False: if self.neoDriver.autoCommit is True: self.doCommit() else: # prompt user to commit or rollback the current transaction msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setText( "You have an uncommitted transaction. Do you want to commit? (click Yes to commit, No to rollback" ) msgBox.setWindowTitle("Commit or Rollback") msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) result = msgBox.exec_() if result == QMessageBox.Yes: self.doCommit() else: self.doRollBack() @pyqtSlot(int) def on_cmbAutoCommit_currentIndexChanged(self, index): """ User has changed the auto commit dropdown. @param index DESCRIPTION @type int """ self.logMsg("User request transaction mode changed to {}".format( self.cmbAutoCommit.currentText())) if self.cmbAutoCommit.currentText() == "Auto Commit On": self.checkOpenTxn() if not (self.neoDriver is None): self.neoDriver.setAutoCommit(True) self.btnCommit.setEnabled(False) self.btnRollBack.setEnabled(False) if self.cmbAutoCommit.currentText() == "Auto Commit Off": self.checkOpenTxn() if not (self.neoDriver is None): self.neoDriver.setAutoCommit(False) self.btnCommit.setEnabled(True) self.btnRollBack.setEnabled(True)