Пример #1
0
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()))
Пример #2
0
class INPropertyBox(QDialog, Ui_INPropertyBox):
    """
    Provide a modal dialog that allows the user to edit an instance node on an instance diagram.
    """
    treeViewUpdate = pyqtSignal()

    def __init__(self, parent=None, diagramInstance=None, model=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(INPropertyBox, self).__init__(parent)
        self.startUp = True
        self.parent = parent
        self.schemaModel = self.parent.schemaObject
        self.helper = Helper()
        self.diagramInstance = diagramInstance
        # reload the NodeInstance dictionary values in case they've been changed on another diagram
        self.diagramInstance.reloadDictValues()

        self.model = model
        self.modelData = self.model.modelData
        self.node = None
        #        self.rc = None
        self.msg = None
        self.formatChanged = False
        self.setupUi(self)
        self.initUI()

        self.populateUIfromObject()
        self.loadTemplateDropdown()

        self.startUp = False

    def initUI(self, ):
        # label grid
        self.gridLabels.setModel(self.createLabelModel())
        comboLblList = [""]
        comboLblList.extend(
            sorted(
                set(
                    self.model.instanceList("Label") +
                    self.schemaModel.instanceList("Label"))))
        #        comboLblList = self.model.loadComboBox(topLevel='Label', objectName=None, selectMsg="" )
        self.gridLabels.setItemDelegateForColumn(
            LABEL, CBDelegate(self, comboLblList, setEditable=True))
        self.gridLabels.setColumnWidth(LABEL, 200)
        self.gridLabels.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.gridLabels.setSelectionMode(QAbstractItemView.SingleSelection)

        header = self.gridLabels.horizontalHeader()
        header.setSectionResizeMode(LABEL, QHeaderView.Interactive)

        # property grid
        self.gridProps.setSortingEnabled(False)
        self.gridProps.setModel(self.createPropModel())
        self.gridProps.setSortingEnabled(False)
        comboPropList = [""]
        comboPropList.extend(
            sorted(
                set(
                    self.model.instanceList("Property") +
                    self.schemaModel.instanceList("Property"))))
        dataTypeList = [dataType.value for dataType in DataType]
        self.gridProps.setItemDelegate(NeoEditDelegate(self))
        self.gridProps.setItemDelegateForColumn(
            DATATYPE, CBDelegate(self, dataTypeList, setEditable=False))
        self.gridProps.setItemDelegateForColumn(
            PROPERTY, CBDelegate(self, comboPropList, setEditable=True))
        self.gridProps.setColumnWidth(PROPERTY, 200)
        self.gridProps.setColumnWidth(DATATYPE, 125)
        self.gridProps.setColumnWidth(VALUE, 300)
        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(VALUE, QHeaderView.Stretch)

    def populateUIfromObject(self, ):
        try:
            #            print("NZID: {}".format(self.diagramInstance.NZID))
            self.editNZID.insert(str(self.diagramInstance.NZID))
        except:
            self.editNZID.insert("No NZID")
#            print("no NZID")
        try:
            #            print("NeoID: {}".format(self.diagramInstance.neoID))
            self.editNeoID.insert(str(self.diagramInstance.neoID))
        except:
            self.editNeoID.insert("No NeoID")
#            print("no neoID")

#load Label grid
        for nodeLbl in self.diagramInstance.labelList:
            self.addLabel(self.gridLabels.model(), nodeLbl[LABEL])
        #load Property grid
        for nodeProp in self.diagramInstance.propList:
            self.addProp(self.gridProps.model(), nodeProp[PROPERTY],
                         nodeProp[DATATYPE], nodeProp[VALUE])

    def loadTemplateDropdown(self, ):
        # load node template dropdown
        dropdownList = ["No Template Selected"]
        dropdownList.extend(sorted(self.model.instanceList("Node Template")))
        self.cboTemplate.addItems(dropdownList)
        if not self.diagramInstance.nodeTemplate is None:
            index = self.cboTemplate.findText(
                self.diagramInstance.nodeTemplate)
            if index >= 0:
                self.cboTemplate.setCurrentIndex(index)

    def logMsg(self, msg):
        # add message to the log
        if logging:
            logging.info(msg)

    def createLabelModel(self):
        model = QStandardItemModel(0, 1)
        model.setHeaderData(LABEL, Qt.Horizontal, "Label")
        return model

    def createPropModel(self):

        model = QStandardItemModel(0, 3)
        model.setHeaderData(PROPERTY, Qt.Horizontal, "Property")
        model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type")
        model.setHeaderData(VALUE, Qt.Horizontal, "Value")
        # connect model slots
        model.itemChanged.connect(self.propModelItemChanged)

        return model

    def mergeTemplateWithInstanceNode(self, templateName):
        # don't merge template if we're just starting up the UI
        if self.startUp:
            return

        saveIndex, nodeTemplateDict = self.model.getDictByName(
            topLevel="Node Template", objectName=templateName)
        if nodeTemplateDict is None:
            self.logMsg(
                "Merge Template Error - no node template dictionary for {}".
                format(templateName))
            return

        # append any labels from the template not already on the instance node to the end of the label grid
        existingLabelList = [
            self.gridLabels.model().item(row, LABEL).data(Qt.EditRole)
            for row in range(0,
                             self.gridLabels.model().rowCount())
        ]
        templateLabelList = [
            nodeLbl[LABEL] for nodeLbl in nodeTemplateDict["labels"]
        ]
        newLabelList = list(set(templateLabelList) - set(existingLabelList))
        for lbl in newLabelList:
            self.addLabel(self.gridLabels.model(), lbl)

        # properties
        # what's on the form now
        existingPropList = [
            self.gridProps.model().item(row, PROPERTY).data(Qt.EditRole)
            for row in range(0,
                             self.gridProps.model().rowCount())
        ]
        existingValueList = [
            self.gridProps.model().item(row, VALUE).data(Qt.EditRole)
            for row in range(0,
                             self.gridProps.model().rowCount())
        ]
        # property list from the template
        newPropList = [
            nodeProp[PROPERTY] for nodeProp in nodeTemplateDict["properties"]
        ]
        newValueList = [
            nodeProp[PROPDEF] for nodeProp in nodeTemplateDict["properties"]
        ]  # this should get default values some day
        # add new properties to the end of the list
        mergePropList = (list(set(newPropList) - set(existingPropList)))
        for prop in mergePropList:
            val = ""
            # get default value from template first
            if prop in newPropList:
                val = newValueList[newPropList.index(prop)]
            # override with existing value for same property if it exists
            if prop in existingPropList:
                val = existingValueList[existingPropList.index(prop)]
            # set Null so editor delegates will work
            if val == "" or val is None:
                val = "Null"
            dataType = self.model.getPropertyDataType(prop)
            self.addProp(self.gridProps.model(), prop, dataType, val)

    def validate(self, ):
        # label grid checks
        if self.helper.gridNoNameError(
                grid=self.gridLabels,
                col=LABEL,
                txtMsg="You must supply a name for each Label"):
            self.gridLabels.setFocus()
            return False
        if self.helper.gridDupEntryError(
                self.gridLabels,
                col=LABEL,
                txtMsg=
                "has been entered more than once. You can only use a Label once"
        ):
            self.gridLabels.setFocus()
            return False
        # property grid checks
        if self.helper.gridNoNameError(
                grid=self.gridProps,
                col=PROPERTY,
                txtMsg="You must supply a name for each Property"):
            self.gridProps.setFocus()
            return False
        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

        model = self.gridProps.model()
        for row in range(0, model.rowCount()):
            nodeProp = [
                model.item(row, PROPERTY).data(Qt.EditRole),
                model.item(row, DATATYPE).data(Qt.EditRole),
                model.item(row, VALUE).data(Qt.EditRole)
            ]
            # property datatype matches property definition
            if self.model.propertyDataTypeValid(
                    name=nodeProp[PROPERTY],
                    dataType=nodeProp[DATATYPE]) == False:
                self.helper.displayErrMsg(
                    "Validate",
                    "You entered datatype {} for  property {} which does not match the property definition. Please enter the correct datatype."
                    .format(nodeProp[DATATYPE], nodeProp[PROPERTY]))
                self.gridProps.setFocus()
                return False
            # can't add/update a value for an Unknown datatype property
            if nodeProp[DATATYPE] == "Unknown" and nodeProp[VALUE] != "Null":
                self.helper.displayErrMsg(
                    "Validate",
                    "Property {} has Unknown datatype.  You can't add/update a value for this property.  Set the value to Null"
                    .format(nodeProp[PROPERTY]))
                self.gridProps.setFocus()
                return False

        # template defined required property has a value
        templateName = self.cboTemplate.currentText()
        saveIndex, nodeTemplateDict = self.model.getDictByName(
            topLevel="Node Template", objectName=templateName)
        if not nodeTemplateDict is None:
            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, VALUE).data(Qt.EditRole)
                ]
                if nodeProp[VALUE] == "Null":
                    #  the value is null so see if it is required
                    for templateProp in nodeTemplateDict["properties"]:
                        if templateProp[PROPERTY] == nodeProp[PROPERTY]:
                            if templateProp[PROPREQ] == Qt.Checked:
                                # this property must have a value
                                self.helper.displayErrMsg(
                                    "Validate",
                                    "The property {} is required. Please enter a value."
                                    .format(nodeProp[PROPERTY]))
                                return False

        return True

    def apply(self, ):
        # update the diagramInstance object with values from the UI.
        # save the labels
        self.diagramInstance.labelList = []
        model = self.gridLabels.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            nodeLbl = [model.item(row, LABEL).data(Qt.EditRole)]
            self.model.newLabel(
                nodeLbl[LABEL]
            )  # check to see if this is a new Label and create a Label object in the dictionary
            self.diagramInstance.labelList.append(nodeLbl)
#        #save the attributes
        self.diagramInstance.propList = []
        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, VALUE).data(Qt.EditRole)
            ]
            #            print("save prop {}".format(nodeProp))
            self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE])
            self.diagramInstance.propList.append(nodeProp)
        #save the template
        selectedTemplate = self.cboTemplate.currentText()
        self.diagramInstance.nodeTemplate = selectedTemplate
        # save the node itself in Neo4j
        rc, msg = self.updateNeo()

        return rc, msg

    def updateNeo(self, ):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        if self.modelData["SyncMode"] == "On":
            rc, msg = self.diagramInstance.syncToDB(self.logMsg)
            if rc is True:
                returnmsg = "Update Node Succesful"
            else:
                returnmsg = "Update Node Failed: {}".format(msg)
                self.helper.displayErrMsg("Update Node", returnmsg)
        else:
            rc = True
            returnmsg = "Database Sync is off - Node not saved to graph"

        QApplication.restoreOverrideCursor()

        return rc, returnmsg

    @pyqtSlot()
    def on_btnCreateTemplate_clicked(self):
        """
        Create a new Node Template based on the labels and properties entered into this Instance Node.
        This is the same logic as in the reverse engineering dialog
        """
        text, ok = QInputDialog.getText(self, 'New Node Template',
                                        'Enter the Node Template Name:')
        if ok:
            #  make sure they entered something
            if len(text) < 1:
                self.helper.displayErrMsg(
                    "Create New Node Template Error",
                    "You must enter a name.".format(text))
            #make sure entered name doesn't existing
            index, nodeDict = self.model.getDictByName("Node Template", text)
            #create a node template dictionary
            if not nodeDict is None:
                self.helper.displayErrMsg(
                    "Create New Node Template Error",
                    "The node template {} already exists".format(text))
                return

            labelList = []
            model = self.gridLabels.model()
            numrows = model.rowCount()
            for row in range(0, numrows):
                nodeLbl = [
                    model.item(row, LABEL).data(Qt.EditRole), Qt.Checked,
                    Qt.Unchecked
                ]
                self.model.newLabel(
                    nodeLbl[LABEL]
                )  # check to see if this is a new Label and create a Label object in the dictionary
                labelList.append(nodeLbl)
            propList = []
            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), Qt.Unchecked,
                    "", Qt.Unchecked, Qt.Unchecked, Qt.Unchecked
                ]
                self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE])
                propList.append(nodeProp)
            # check for constraints that match label/prop
            # generate the constraints and indexes
            conList = []
            # look at each constraint in the schemaModel and see if it belongs to the node template
            self.schemaModel.matchConstraintNodeTemplate(
                conList, [lbl[0] for lbl in labelList],
                [prop[0] for prop in propList])
            # look at each index in the schemaModel and see if it belongs to the node template
            idxList = []
            self.schemaModel.matchIndexNodeTemplate(
                idxList, [lbl[0] for lbl in labelList],
                [prop[0] for prop in propList])
            #save it to the model
            nodeDict = self.model.newNodeTemplate(
                name=text,
                labelList=labelList,
                propList=propList,
                conList=conList,
                idxList=idxList,
                desc="Template generated from Instance Node.")
            self.model.modelData["Node Template"].append(nodeDict)
            # refresh the treeview
            self.model.updateTV()
            # set combo box
            self.loadTemplateDropdown()
            if not (self.cboTemplate.findText(text) is None):
                self.cboTemplate.setCurrentIndex(
                    self.cboTemplate.findText(text))

    @pyqtSlot()
    def on_btnLabelUp_clicked(self):
        """
        User clicks on label up button
        """
        self.helper.moveTableViewRowUp(self.gridLabels)

    @pyqtSlot()
    def on_btnLabelDown_clicked(self):
        """
        User clicks on label down button
        """
        self.helper.moveTableViewRowDown(self.gridLabels)

    @pyqtSlot()
    def on_btnLabelAdd_clicked(self):
        """
        Slot documentation goes here.
        """
        self.addLabel(self.gridLabels.model(), "")
        # common function to adjust grid after appending a row
        self.helper.adjustGrid(grid=self.gridLabels)
#        # scroll to bottom to insure the new row is visible
#        self.gridLabels.scrollToBottom()

    @pyqtSlot()
    def on_btnLabelRemove_clicked(self):
        """
        Slot documentation goes here.
        """
        indexes = self.gridLabels.selectionModel().selectedIndexes()
        for index in sorted(indexes):
            #            print('Row %d is selected' % index.row())
            self.gridLabels.model().removeRows(index.row(), 1)

    @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):
        """
        User clicks Add Property
        """
        self.addProp(self.gridProps.model(), "", "String", "Null")
        # common function to adjust grid after appending a row
        self.helper.adjustGrid(grid=self.gridProps)
#        # scroll to bottom to insure the new row is visible
#        self.gridProps.scrollToBottom()

    @pyqtSlot()
    def on_btnMakeNull_clicked(self):
        """
        User requests to set a property value to Null
        """
        # grid only allows single selection
        indexes = self.gridProps.selectionModel().selectedIndexes()
        for index in indexes:
            valueIndex = self.gridProps.model().index(index.row(), VALUE)
            self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole)

    @pyqtSlot()
    def on_btnPropRemove_clicked(self):
        """
        Slot documentation goes here.
        """
        # grid only allows single selection
        indexes = self.gridProps.selectionModel().selectedIndexes()
        for index in indexes:
            self.gridProps.model().removeRows(index.row(), 1)

    @pyqtSlot()
    def on_okButton_clicked(self):
        """
        User clicked OK button, validate the data then sync to graph
        """
        if self.validate():
            rc, msg = self.apply()
            if rc == True:
                QDialog.accept(self)

    @pyqtSlot()
    def on_cancelButton_clicked(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)

    def addLabel(self, model, label):
        self.gridLabels.setSortingEnabled(False)
        item1 = QStandardItem(label)
        item1.setEditable(True)
        model.appendRow([
            item1,
        ])
#        self.gridLabels.resizeColumnsToContents()
#        self.gridLabels.setSortingEnabled(True)

    def addProp(self, model, prop, dataType, value):
        '''
        add a row to the property grid
        '''
        self.gridProps.setSortingEnabled(False)
        item1 = QStandardItem(prop)
        item1.setEditable(True)
        item11 = QStandardItem(dataType)
        item11.setEditable(True)
        item2 = QStandardItem(value)
        item2.setData(dataType, Qt.UserRole + 1)
        item2.setEditable(True)
        model.appendRow([item1, item11, item2])

    @pyqtSlot(int)
    def on_cboTemplate_currentIndexChanged(self, index):
        """
        Slot documentation goes here.
        
        @param index DESCRIPTION
        @type int
        """
        #        print("template changed {}".format(str(index)))
        # the new node template might have a custom format so set this to true which will force
        # a diagram redraw when the dialog closes.
        self.formatChanged = True
        if index > 0:
            self.mergeTemplateWithInstanceNode(self.cboTemplate.currentText())

    def propModelItemChanged(self, item):

        #        print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column()))
        templateName = self.cboTemplate.currentText()
        columnNum = item.index().column()
        if columnNum == PROPERTY:
            # if property has changed then change the datatype
            propName = self.gridProps.model().item(item.index().row(),
                                                   PROPERTY).data(Qt.EditRole)
            dataType = self.model.getPropertyDataType(propName)
            self.gridProps.model().item(item.index().row(),
                                        DATATYPE).setText(dataType)
            # see if this property has a default value defined in the template
            defaultVal = self.model.getTemplatePropDefVal(
                templateName=templateName, propName=propName)
            if not defaultVal is None:
                self.gridProps.model().item(item.index().row(),
                                            VALUE).setText(defaultVal)

        if columnNum == DATATYPE:
            # if datatype has changed then change value to "Null"
            self.gridProps.model().item(item.index().row(),
                                        VALUE).setText("Null")
            dataType = self.gridProps.model().item(item.index().row(),
                                                   DATATYPE).data(Qt.EditRole)
            self.gridProps.model().item(item.index().row(),
                                        VALUE).setData(dataType,
                                                       Qt.UserRole + 1)

    @pyqtSlot(int)
    def on_tabNodeInspector_currentChanged(self, index):
        """
        User has switched to another tab
        
        @param index DESCRIPTION
        @type int
        """
        # user switched to the description tab.  must regenerate description if there is a node template selected
        if index == DESCRIPTION:
            if self.cboTemplate.currentIndex() > 0:
                saveIndex, objectDict = self.model.getDictByName(
                    topLevel="Node Template",
                    objectName=self.cboTemplate.currentText())
                if not objectDict is None:
                    self.brwsrGeneratedDesc.setText(
                        self.model.getNodeDescription(
                            self.cboTemplate.currentText()))
                else:
                    self.helper.displayErrMsg(
                        "Get Description",
                        "Error - could not find node template: {}".format(
                            self.cboTemplate.currentText()))
Пример #3
0
class IRPropertyBox(QDialog, Ui_IRPropertyBox):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, diagramInstance=None, model=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(IRPropertyBox, self).__init__(parent)
        self.appStartup = True
        self.mergeTemplate = False
        self.parent = parent
        self.schemaModel = self.parent.schemaObject
        self.helper = Helper()
        # this is the RelationInstance object - called generically diagramInstance.
        self.diagramInstance = diagramInstance
        self.diagramInstance.reloadDictValues()
        self.model = model
        self.modelData = self.model.modelData
        self.rel = None
        self.setupUi(self)
        #        self.appSetToTemplate = False
        #        self.appSetFromTemplate = False

        self.initUI()

        self.populateUIfromObject()

        self.appStartup = False
        self.msg = ""

    def initUI(self, ):

        # property grid
        self.gridProps.setSortingEnabled(False)
        self.gridProps.setModel(self.createPropModel())
        self.gridProps.setSortingEnabled(False)
        comboPropList = [""]
        comboPropList.extend(
            sorted(
                set(
                    self.model.instanceList("Property") +
                    self.schemaModel.instanceList("Property"))))
        dataTypeList = [dataType.value for dataType in DataType]
        self.gridProps.setItemDelegate(NeoEditDelegate(self))
        self.gridProps.setItemDelegateForColumn(
            DATATYPE, CBDelegate(self, dataTypeList, setEditable=False))
        self.gridProps.setItemDelegateForColumn(
            PROPERTY, CBDelegate(self, comboPropList, setEditable=True))
        self.gridProps.setColumnWidth(PROPERTY, 200)
        self.gridProps.setColumnWidth(DATATYPE, 125)
        self.gridProps.setColumnWidth(VALUE, 300)
        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(VALUE, QHeaderView.Stretch)

    def populateUIfromObject(self, ):

        # load rel name list, select rel name
        self.loadRelationshipNameDropDown()
        # load from nodes, select the from node and load the from node template if any
        self.loadFromNodeDropdown()
        # load to nodes, select the to node and load the to node template if any
        self.loadToNodeDropdown()
        # load rel template names, select rel template name if any
        self.loadRelTemplateDropDown()

        #NZID
        try:
            self.EditNZID.insert(str(self.diagramInstance.NZID))
        except:
            print("no NZID")  # this shouldn't happen
        # neoID
        try:
            self.editNeoID.insert(str(self.diagramInstance.neoID))
        except:
            self.editNeoID.insert("None")

        # disable rel name if already defined
        if self.editNeoID.text() == "None":
            self.cboRelName.setEnabled(True)
        else:
            self.cboRelName.setEnabled(False)

        # disable dropdowns if start and end nodes defined
        if self.cboFromNode.currentIndex() > 0:
            self.cboFromNode.setEnabled(False)
        if self.cboToNode.currentIndex() > 0:
            self.cboToNode.setEnabled(False)

        # load the properties
        for nodeProp in self.diagramInstance.propList:
            self.addProp(self.gridProps.model(), nodeProp[PROPERTY],
                         nodeProp[DATATYPE], nodeProp[VALUE])

    def loadRelationshipNameDropDown(self, ):
        # load relationship name (type)  dropdown
        dropdownList = []
        dropdownList.append("Enter or Select Relationship Type")
        # get relationship types from the project model
        templateRelList = [
            relDict["name"] for relDict in self.modelData["Relationship"]
        ]
        #        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 (self.diagramInstance.relName is None
                or self.diagramInstance.relName == "NoRelationshipName"):
            self.cboRelName.setCurrentIndex(0)
        else:
            # if the relationship name has been set then display the rel name
            index = self.cboRelName.findText(self.diagramInstance.relName)
            if index >= 0:
                self.cboRelName.setCurrentIndex(index)
            else:
                # its not a template rel name so just set the text value of the combo box
                self.cboRelName.setEditText(self.diagramInstance.relName)

    def loadFromNodeDropdown(self, ):
        # load from node dropdown
        dropdownList = []
        dropdownList.append("No From Node Selected")
        nodeList = self.model.instanceDisplayNameList("Instance Node")
        for node in nodeList:
            dropdownList.append(node)
        self.cboFromNode.addItems(dropdownList)
        if not self.diagramInstance.startNZID is None:
            item, objectDict = self.model.getDictByName(
                "Instance Node", self.diagramInstance.startNZID)
            if not objectDict is None:
                index = self.cboFromNode.findText(
                    objectDict.get("displayName", ""))
                if index >= 0:
                    self.cboFromNode.setCurrentIndex(index)
                    self.cboFromNode.setEnabled(False)

    def loadToNodeDropdown(self, ):

        # load from node dropdown
        dropdownList = []
        dropdownList.append("No To Node Selected")
        nodeList = self.model.instanceDisplayNameList("Instance Node")
        for node in nodeList:
            dropdownList.append(node)
        self.cboToNode.addItems(dropdownList)
        if not self.diagramInstance.endNZID is None:
            item, objectDict = self.model.getDictByName(
                "Instance Node", self.diagramInstance.endNZID)
            if not objectDict is None:
                index = self.cboToNode.findText(
                    objectDict.get("displayName", ""))
                if index >= 0:
                    self.cboToNode.setCurrentIndex(index)
                    self.cboToNode.setEnabled(False)

    def loadRelTemplateDropDown(self, ):
        # load rel template dropdown
        self.cboTemplate.clear()
        startNodeTemplate = self.txtFromTemplate.text()
        endNodeTemplate = self.txtToTemplate.text()
        #        relName = self.diagramInstance.relName
        relName = self.cboRelName.currentText()
        dropdownList = ["No Template Selected"]
        dropdownList.extend(
            sorted(
                self.model.matchAllRelTemplates(
                    startNodeTemplate=startNodeTemplate,
                    endNodeTemplate=endNodeTemplate,
                    relName=relName)))
        self.cboTemplate.addItems(dropdownList)
        if not self.diagramInstance.relTemplate is None:
            index = self.cboTemplate.findText(self.diagramInstance.relTemplate)
            if index >= 0:
                self.cboTemplate.setCurrentIndex(index)

    def createPropModel(self):

        model = QStandardItemModel(0, 3)
        model.setHeaderData(PROPERTY, Qt.Horizontal, "Property")
        model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type")
        model.setHeaderData(VALUE, Qt.Horizontal, "Value")
        # connect model slots
        model.itemChanged.connect(self.propModelItemChanged)

        return model

    def getNodeInstance(self, NZID):
        '''
        Using an NZID, create a NodeInstanceObject and return it
        if it doesn't exist, return None
        '''
        index, nodeDict = self.model.getDictByName(topLevel="Instance Node",
                                                   objectName=NZID)
        nodeInstance = NodeInstance(model=self.model,
                                    nodeInstanceDict=nodeDict)
        return nodeInstance

    def logMsg(self, msg):
        # add message to the log
        if logging:
            logging.info(msg)

    def validate(self, ):
        if self.helper.gridNoNameError(
                grid=self.gridProps,
                col=PROPERTY,
                txtMsg="You must supply a name for each Property"):
            self.gridProps.setFocus()
            return False
        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
        if self.helper.NoTextValueError(self.cboRelName.currentText(),
                                        "You must enter a relationship name."):
            self.cboRelName.setFocus()
            return False
        if self.cboRelName.currentText(
        ) == "Enter or Select Relationship Type":
            self.helper.displayErrMsg("Instance Relationship",
                                      "You must enter a relationship name.")
            self.cboRelName.setFocus()
            return False
        if self.cboFromNode.currentIndex() == 0:
            self.helper.displayErrMsg("Instance Relationship",
                                      "You must select a from node.")
            self.cboFromNode.setFocus()
            return False
        if self.cboToNode.currentIndex() == 0:
            self.helper.displayErrMsg("Instance Relationship",
                                      "You must select a to node.")
            self.cboToNode.setFocus()
            return False
        # property datatype matches property definition
        model = self.gridProps.model()
        for row in range(0, model.rowCount()):
            nodeProp = [
                model.item(row, PROPERTY).data(Qt.EditRole),
                model.item(row, DATATYPE).data(Qt.EditRole),
                model.item(row, VALUE).data(Qt.EditRole)
            ]
            if self.model.propertyDataTypeValid(
                    name=nodeProp[PROPERTY],
                    dataType=nodeProp[DATATYPE]) == False:
                self.helper.displayErrMsg(
                    "Validate",
                    "You entered datatype {} for  property {} which does not match the property definition. Please enter the correct datatype."
                    .format(nodeProp[DATATYPE], nodeProp[PROPERTY]))
                self.gridProps.setFocus()
                return False

        # template defined required property has a value
        templateName = self.cboTemplate.currentText()
        saveIndex, relTemplateDict = self.model.getDictByName(
            topLevel="Relationship Template", objectName=templateName)
        if not relTemplateDict is None:
            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, VALUE).data(Qt.EditRole)
                ]
                if nodeProp[VALUE] == "Null":
                    #  the value is null so see if it is required
                    for templateProp in relTemplateDict["properties"]:
                        if templateProp[PROPERTY] == nodeProp[PROPERTY]:
                            if templateProp[PROPREQ] == Qt.Checked:
                                # this property must have a value
                                self.helper.displayErrMsg(
                                    "Validate",
                                    "The property {} is required. Please enter a value."
                                    .format(nodeProp[PROPERTY]))
                                return False
        return True

    def apply(self, ):
        #update the diagramInstance object with values from the UI.
        self.diagramInstance.relName = self.cboRelName.currentText()
        # if its a new rel type then add it to the model
        self.model.newRelationship(self.diagramInstance.relName)
        # update property list
        self.diagramInstance.propList = []
        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, VALUE).data(Qt.EditRole)
            ]
            #            print(nodeProp)
            # if its a new property add it to the model
            self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE])
            self.diagramInstance.propList.append(nodeProp)
        #save the template
        selectedTemplate = self.cboTemplate.currentText()
        self.diagramInstance.relTemplate = selectedTemplate
        # save the relationship itself in Neo4j
        rc, msg = self.updateNeo()
        self.msg = msg
        return rc, msg

    def updateNeo(self, ):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        if self.modelData["SyncMode"] == "On":
            rc, msg = self.diagramInstance.syncToDB(self.logMsg)
            if rc is True:
                returnmsg = "Update Relationship Succesful"
            else:
                returnmsg = "Update Relationship Failed: {}".format(msg)
                self.helper.displayErrMsg("Update Relationship", returnmsg)
        else:
            rc = True
            returnmsg = "Database Sync is off - Relationship not saved to graph"

        QApplication.restoreOverrideCursor()
        return rc, returnmsg

    @pyqtSlot()
    def on_btnPropAdd_clicked(self):
        """
        Slot documentation goes here.
        """
        self.addProp(self.gridProps.model(), "", "String", "Null")
        # common function to adjust grid after appending 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()
        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")

    @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_okButton_clicked(self):
        """
        User clicked on OK button, validate data and sync to graph
        """
        if self.validate():
            rc, msg = self.apply()
            if rc == True:
                QDialog.accept(self)

    @pyqtSlot()
    def on_cancelButton_clicked(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)

    def addProp(self, model, prop, dataType, value):

        self.gridProps.setSortingEnabled(False)
        item1 = QStandardItem(prop)
        item1.setEditable(True)
        item11 = QStandardItem(dataType)
        item11.setEditable(True)
        item2 = QStandardItem(value)
        item2.setData(dataType, Qt.UserRole + 1)
        item2.setEditable(True)
        model.appendRow([item1, item11, item2])

    @pyqtSlot()
    def on_btnCreateTemplate_clicked(self):
        """
        Create a new Relationship Template based on this instance relationship.
        """
        text, ok = QInputDialog.getText(
            self, 'New Relationship Template',
            'Enter the Relationship Template Name:')
        if ok:
            #  make sure they entered something
            if len(text) < 1:
                self.helper.displayErrMsg(
                    "Create New Relationship Template Error",
                    "You must enter a name.".format(text))
            #make sure entered name doesn't exist
            index, relDict = self.model.getDictByName("Relationship Template",
                                                      text)
            if not relDict is None:
                self.helper.displayErrMsg(
                    "Create New Relationship Template Error",
                    "The Relationship template {} already exists".format(text))
                return
            # validate the data first, then add the new relationship template
            if self.validate():
                # if its a new rel type then add it to the model
                self.model.newRelationship(self.cboRelName.currentText())
                # save the properties
                propList = []
                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),
                        Qt.Unchecked, "", Qt.Unchecked
                    ]
                    self.model.newProperty(nodeProp[PROPERTY],
                                           nodeProp[DATATYPE])
                    propList.append(nodeProp)
                # generate the constraints
                conList = []
                # look at each constraint in the schemaModel and see if it belongs to the rel template
                self.schemaModel.matchConstraintRelTemplate(
                    conList, [prop[0] for prop in propList],
                    relName=self.cboRelName.currentText())
                # get the source/target node template if any
                # can only save a source target if both instance nodes are based on a node template
                try:
                    startNodeInstance = self.getNodeInstance(
                        self.diagramInstance.startNZID)
                    fromTemplate = startNodeInstance.nodeTemplate
                    endNodeInstance = self.getNodeInstance(
                        self.diagramInstance.endNZID)
                    toTemplate = endNodeInstance.nodeTemplate
                    if (not fromTemplate == "No Template Selected"
                            and not toTemplate == "No Template Selected"):
                        #save it to the model
                        relDict = self.model.newRelTemplateDict(
                            name=text,
                            relname=self.cboRelName.currentText(),
                            propList=propList,
                            desc=
                            "Template generated from Instance Relationship.",
                            conList=conList,
                            fromTemplate=fromTemplate,
                            toTemplate=toTemplate)
                        self.model.modelData["Relationship Template"].append(
                            relDict)
                        # refresh the treeview
                        self.model.updateTV()
                        # update dropdown  and select the newly created template
                        self.loadRelTemplateDropDown()
                        index = self.cboTemplate.findText(text)
                        if index >= 0:
                            self.cboTemplate.setCurrentIndex(index)
                    else:
                        self.helper.displayErrMsg(
                            "Create Template",
                            "Cannot create a relationship template without a from node template and a to node template."
                        )
                except Exception as e:
                    self.helper.displayErrMsg(
                        "Create Template",
                        "Error creating relationship template: {}".format(
                            str(e)))

    def templateChange(self, index):
        if index > 0:
            self.mergeTemplateWithInstanceRel(self.cboTemplate.currentText())

    def mergeTemplateWithInstanceRel(self, templateName):
        '''this merges the properties from the relationship template with the  properties that already exist on the instance relationship
        '''
        # if app is just starting up don't do this
        if self.appStartup:
            return
        # tell the other functions we're merging the template
        self.mergeTemplate = True

        saveIndex, relDict = self.model.getDictByName(
            topLevel="Relationship Template", objectName=templateName)
        if relDict is None:
            self.helper.displayErrMsg(
                "Merge Relationship Template",
                "Error - Relationship Template {} doesn't exist".format(
                    templateName))
            return

        # properties
        # what's on the form now
        existingPropList = [
            self.gridProps.model().item(row, PROPERTY).data(Qt.EditRole)
            for row in range(0,
                             self.gridProps.model().rowCount())
        ]
        existingValueList = [
            self.gridProps.model().item(row, VALUE).data(Qt.EditRole)
            for row in range(0,
                             self.gridProps.model().rowCount())
        ]
        # property list from the template
        newPropList = [
            nodeProp[PROPERTY] for nodeProp in relDict["properties"]
        ]
        newValueList = [
            nodeProp[PROPDEF] for nodeProp in relDict["properties"]
        ]  # this should get default values some day
        # merge them together
        mergePropList = (list(set(existingPropList + newPropList)))
        mergePropList.sort()
        self.gridProps.model().removeRows(0, self.gridProps.model().rowCount())
        for prop in mergePropList:
            val = ""
            # get default value from template first
            if prop in newPropList:
                val = newValueList[newPropList.index(prop)]
            # override with existing value for same property if it exists
            if prop in existingPropList:
                val = existingValueList[existingPropList.index(prop)]
            # set Null so editor delegates will work
            if val == "" or val is None:
                val = "Null"
            dataType = self.model.getPropertyDataType(prop)
            self.addProp(self.gridProps.model(), prop, dataType, val)

        self.diagramInstance.relName = relDict["relname"]
        index = self.cboRelName.findText(self.diagramInstance.relName)
        if index >= 0:
            self.cboRelName.setCurrentIndex(index)

        # we're done merging
        self.mergeTemplate = False

    @pyqtSlot(int)
    def on_cboTemplate_currentIndexChanged(self, index):
        """
        The User has selected a relationship template to use.
        
        @param index DESCRIPTION
        @type int
        """
        # if they selected a rel template then merge it with whatever is on the gui
        if index > 0:
            self.mergeTemplateWithInstanceRel(self.cboTemplate.currentText())

    @pyqtSlot(int)
    def on_cboFromNode_currentIndexChanged(self, index):
        """
        The user has selected a new from instance node for the relationship
        
        @param index DESCRIPTION
        @type int
        """
        if index > 0:
            # create NodeInstance object
            displayName = self.cboFromNode.currentText()
            NZID = self.model.lookupNZIDfromDisplayName(
                displayName=displayName, topLevel="Instance Node")
            if not NZID is None:
                self.diagramInstance.startNode = self.getNodeInstance(NZID)
                self.diagramInstance.startNZID = NZID
                # if the instance node is based on a node template then set that
                fromTemplate = self.diagramInstance.startNode.nodeTemplate
                self.txtFromTemplate.setText(fromTemplate)

        else:
            self.txtFromTemplate.setText("")

        # force rel template dropdown to refill valid choices
        if self.mergeTemplate == False:
            self.loadRelTemplateDropDown()

    @pyqtSlot(int)
    def on_cboToNode_currentIndexChanged(self, index):
        """
        The user has selected a new to instance node for the relationship
        
        @param index DESCRIPTION
        @type int
        """
        if index > 0:
            # create NodeInstance object
            displayName = self.cboToNode.currentText()
            NZID = self.model.lookupNZIDfromDisplayName(
                displayName=displayName, topLevel="Instance Node")
            if not NZID is None:
                self.diagramInstance.endNode = self.getNodeInstance(NZID)
                self.diagramInstance.endNZID = NZID
                # if the instance node is based on a node template then set that
                toTemplate = self.diagramInstance.endNode.nodeTemplate
                self.txtToTemplate.setText(toTemplate)
        else:
            self.txtToTemplate.setText("")

        # force node template dropdown to refill valid choices
        if self.mergeTemplate == False:
            self.loadRelTemplateDropDown()

    @pyqtSlot(int)
    def on_cboRelName_currentIndexChanged(self, index):
        """
        The relationship type was changed
        
        @param index DESCRIPTION
        @type int
        """
        # user changed the relationship type name so reset the template dropdown to new list of valid templates and pick the "none selected" option which forces the user to reselect a template
        if self.mergeTemplate == False:
            self.loadRelTemplateDropDown()
            self.cboTemplate.setCurrentIndex(0)

#        if index > 0:
##            relName = self.cboRelName.currentText()
#            # reload rel template dropdown to reflect newly selected rel type
##            relTemplate = self.cboTemplate.currentText()
#            self.loadRelTemplateDropDown()
#            self.cboTemplate.setCurrentIndex(0)
##            relList = ["select a relationship template"] + [relDict["name"] for relDict in self.modelData["Relationship Template"] if relDict["relname"] == relName]
##            self.cboTemplate.clear()
##            self.cboTemplate.addItems(relList)
#            # set to no rel template selected.
##            self.cboTemplate.setCurrentIndex(0)
#        else:
#            # user switched back to no rel name selected so relaod all rel templates
#            self.loadRelTemplateDropDown()
#            self.cboTemplate.setCurrentIndex(0)

    def propModelItemChanged(self, item):

        #        print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column()))

        # this checks to see if property name changed and updates the data type accordingly
        columnIndex = item.index().column()
        if columnIndex == PROPERTY:
            # if property has changed then change the datatype
            propName = self.gridProps.model().item(item.index().row(),
                                                   PROPERTY).data(Qt.EditRole)
            dataType = self.model.getPropertyDataType(propName)
            self.gridProps.model().item(item.index().row(),
                                        DATATYPE).setText(dataType)
        if columnIndex == DATATYPE:
            # if datatype has changed then change value to "Null"
            self.gridProps.model().item(item.index().row(),
                                        VALUE).setText("Null")
            dataType = self.gridProps.model().item(item.index().row(),
                                                   DATATYPE).data(Qt.EditRole)
            self.gridProps.model().item(item.index().row(),
                                        VALUE).setData(dataType,
                                                       Qt.UserRole + 1)

    @pyqtSlot()
    def on_btnSetNull_clicked(self):
        """
        User clicks button to set a property value to Null
        """
        indexes = self.gridProps.selectionModel().selectedIndexes()
        for index in indexes:
            valueIndex = self.gridProps.model().index(index.row(), VALUE)
            self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole)

    @pyqtSlot(int)
    def on_tabRelInspector_currentChanged(self, index):
        """
        User has switched to another tab
        
        @param index DESCRIPTION
        @type int
        """
        # user switched to the description tab.  must regenerate description if there is a node template selected
        if index == DESCRIPTION:
            if self.cboTemplate.currentIndex() > 0:
                saveIndex, objectDict = self.model.getDictByName(
                    topLevel="Relationship Template",
                    objectName=self.cboTemplate.currentText())
                if not objectDict is None:
                    self.brwsrGeneratedDesc.setText(
                        self.model.getRelationshipDescription(objectDict))
                else:
                    self.helper.displayErrMsg(
                        "Get Description",
                        "Error - could not find node template: {}".format(
                            self.cboTemplate.currentText))