Exemplo n.º 1
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()))
Exemplo n.º 2
0
class PathTemplateTab(QWidget, Ui_PathTemplateTab):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, model=None, name=None, tabType=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(PathTemplateTab, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.model = model
        self.tabType = tabType
        self.tabName = name
        saveIndex, pathTemplateDict = self.model.getDictByName(
            topLevel=self.tabType, objectName=name)
        self.pathTemplateDict = pathTemplateDict
        self.helper = Helper()

        self.lastTreeViewWidgetItem = None

        # add cypher CypherEditGridWidget
        global unNamedFileCounter
        unNamedFileCounter = unNamedFileCounter + 1
        fileName = "{}".format("Unsaved-0{}".format(unNamedFileCounter))
        self.cypherEditGrid = CypherEditGridWidget(parent=parent,
                                                   fileName=fileName,
                                                   fileText="",
                                                   mode=MODENEW)
        self.frmDataGridLayout = QVBoxLayout(self.frmDataGrid)
        self.frmDataGridLayout.setObjectName("frmDataGridLayout")
        self.frmDataGridLayout.setContentsMargins(1, 1, 1, 1)
        self.frmDataGridLayout.setSpacing(1)
        self.frmDataGridLayout.addWidget(self.cypherEditGrid)

        # setup output grid model
        self.gridOutput.setModel(self.createOutputModel())
        self.gridOutput.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.gridOutput.setSelectionMode(QAbstractItemView.SingleSelection)
        #        SOURCE, SOURCENAME, COLTYPE, COLVAL, COLNAME, DISPLAYIT, SORT, GROUP
        self.gridOutput.setColumnWidth(SOURCE, 200)
        self.gridOutput.setColumnWidth(SOURCENAME, 200)
        self.gridOutput.setColumnWidth(COLTYPE, 100)
        self.gridOutput.setColumnWidth(COLVAL, 200)
        self.gridOutput.setColumnWidth(COLNAME, 200)
        self.gridOutput.setColumnWidth(DISPLAYIT, 75)
        self.gridOutput.setColumnWidth(SORT, 75)
        self.gridOutput.setColumnWidth(GROUP, 75)
        # header
        header = self.gridOutput.horizontalHeader()
        header.setSectionResizeMode(SOURCE, QHeaderView.Interactive)
        header.setSectionResizeMode(SOURCENAME, QHeaderView.Interactive)
        header.setSectionResizeMode(COLTYPE, QHeaderView.Fixed)
        header.setSectionResizeMode(COLVAL, QHeaderView.Interactive)
        header.setSectionResizeMode(COLNAME, QHeaderView.Interactive)
        header.setSectionResizeMode(DISPLAYIT, QHeaderView.Fixed)
        header.setSectionResizeMode(SORT, QHeaderView.Fixed)
        header.setSectionResizeMode(GROUP, QHeaderView.Fixed)

        # column type
        colTypeList = ["", "Property", "Label", "Function"]
        self.gridOutput.setItemDelegateForColumn(
            COLTYPE, CBDelegate(self, colTypeList, setEditable=True))
        # sort
        sortList = ["No", "Ascending", "Descending"]
        self.gridOutput.setItemDelegateForColumn(
            SORT, CBDelegate(self, sortList, setEditable=True))
        # group
        groupList = ["No", "Yes"]
        self.gridOutput.setItemDelegateForColumn(
            GROUP, CBDelegate(self, groupList, setEditable=True))

        # path treeview setup
        self.tvPath.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvPath.customContextMenuRequested.connect(self.openMenu)
        self.tvPath.setDragDropMode(QAbstractItemView.DragOnly)

        # initiliaze UI from pathTemplateDict
        self.initUI()

    def save(self, ):
        #        print("save path {}".format(self.tabName))
        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)
                # if its the root node then save the description.
                if queryPathNodeDict["type"] == "Path Template":
                    self.pathTemplateDict["description"] = queryPathNodeDict[
                        "description"]
                tvPathIterator.__iadd__(1)
            else:
                break

        self.pathTemplateDict["queryPath"] = nodeList
        self.model.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, 200)
        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):
        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])

    def rePopulateMetaBox(self, queryPathNode=None):
        '''update the displayname'''
        model = self.gridMetaBox.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            name = model.item(row, ATTRNAME).data(Qt.EditRole)
            if name == "Display Name":
                # update displayName in the grid
                model.setData(model.item(row, ATTRVALUE).index(),
                              queryPathNode.displayName,
                              role=Qt.EditRole)
                # update displayName on the tree view
                queryPathNode.treeItem.setData(0, Qt.EditRole,
                                               queryPathNode.displayName)

#    def metaBoxEditorClosed(self, ):
#        print("editor closed")

    def metaBoxModelItemChanged(self, item):
        #        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)

        # 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 initUI(self):
        self.populateTree()

#-----------------------------------------------------------------------------------------------------------------------
#  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.pathTemplateDict))
        # add tree items
        if len(self.pathTemplateDict["queryPath"]) > 0:
            for tvPathDict in self.pathTemplateDict["queryPath"]:
                # create the tree view path item object
                pathItem = QueryPathNode(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(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))

    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)
                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)
                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(parentOrder=parentOrder,
                                      order=order,
                                      type="Relationship",
                                      templateName=templateName)
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)

    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(parentOrder=parentOrder,
                                      order=order,
                                      type="Node",
                                      templateName=templateName)
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)

    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))

#-----------------------------------------------------------------------------------------------------------------------
#  return grid methods
#-----------------------------------------------------------------------------------------------------------------------

    def createOutputModel(self):
        #        SOURCE, SOURCENAME, COLTYPE, COLVAL, COLNAME, DISPLAYIT, SORT, GROUP
        model = QStandardItemModel(0, 8)
        model.setHeaderData(SOURCE, Qt.Horizontal, "Source")
        model.setHeaderData(SOURCENAME, Qt.Horizontal, "Source Name")
        model.setHeaderData(COLTYPE, Qt.Horizontal, "Column Type")
        model.setHeaderData(COLVAL, Qt.Horizontal, "Column Value")
        model.setHeaderData(COLNAME, Qt.Horizontal, "Column Name")
        model.setHeaderData(DISPLAYIT, Qt.Horizontal, "Display ?")
        model.setHeaderData(SORT, Qt.Horizontal, "Sort")
        model.setHeaderData(GROUP, Qt.Horizontal, "Group")

        return model

    def addOutputRow(self,
                     source=None,
                     sourceName=None,
                     colType=None,
                     colVal=None,
                     colName=None,
                     displayIt=None,
                     sort=None,
                     group=None):
        '''
        add a row to the output grid
        SOURCE, SOURCENAME, COLTYPE, COLVAL, COLNAME, DISPLAYIT, SORT, GROUP
        '''
        self.gridOutput.setSortingEnabled(False)
        # SOURCE
        if source is None:
            source = ""
        item1 = QStandardItem(source)
        item1.setEditable(True)
        # SOURCENAME
        if sourceName is None:
            sourceName = ""
        item2 = QStandardItem(sourceName)
        item2.setEditable(True)
        # COLTYPE
        if colType is None:
            colType = ""
        item3 = QStandardItem(colType)
        item3.setEditable(True)
        # COLVAL
        if colVal is None:
            colVal = ""
        item4 = QStandardItem(colVal)
        item4.setEditable(True)
        # COLNAME
        if colName is None:
            colName = ""
        item5 = QStandardItem(colName)
        item5.setEditable(True)
        # DISPLAYIT
        if displayIt is None:
            displayIt = Qt.Checked
        item6 = QStandardItem()
        item6.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        item6.setText("Display")
        if displayIt in [0, 1, 2]:
            item6.setCheckState(displayIt)
        else:
            item2.setCheckState(Qt.Unchecked)

        item6.setEditable(True)
        # SORT
        if sort is None:
            sort = "No"
        item7 = QStandardItem(sort)
        item7.setEditable(True)
        # GROUP
        if group is None:
            group = "No"
        item8 = QStandardItem(group)
        item8.setEditable(True)

        self.gridOutput.model().appendRow(
            [item1, item2, item3, item4, item5, item6, item7, item8])

    @pyqtSlot()
    def on_btnSaveAs_clicked(self):
        """
        User clicks button to save the generated query
        """
        return

    @pyqtSlot()
    def on_btnAdd_clicked(self):
        """
        User clicks Add Property
        """
        self.addOutputRow()

    @pyqtSlot()
    def on_btnRemove_clicked(self):
        """
        User clicks remove row button
        """
        indexes = self.gridOutput.selectionModel().selectedIndexes()
        for index in indexes:
            self.gridOutput.model().removeRows(index.row(), 1)

    @pyqtSlot()
    def on_btnUp_clicked(self):
        """
        User clicks Up button
        """
        self.helper.moveTableViewRowUp(self.gridOutput)

    @pyqtSlot()
    def on_btnDown_clicked(self):
        """
        User clicks Down button
        """
        self.helper.moveTableViewRowDown(self.gridOutput)

    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
    def on_tvPath_currentItemChanged(self, current, previous):
        """
        Slot documentation goes here.
        
        @param current DESCRIPTION
        @type QTreeWidgetItem
        @param previous DESCRIPTION
        @type QTreeWidgetItem
        """
        return

    @pyqtSlot(QModelIndex)
    def on_tvPath_clicked(self, index):
        """
        Slot documentation goes here.
        
        @param index DESCRIPTION
        @type QModelIndex
        """
        #        print("on_tvPath_clicked")
        return

    @pyqtSlot(QModelIndex)
    def on_tvPath_activated(self, index):
        """
        Slot documentation goes here.
        
        @param index DESCRIPTION
        @type QModelIndex
        """
        #        print("on_tvPath_activated")
        return

    @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")
        return

    @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")
        return

    @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")
        return

    @pyqtSlot()
    def on_tvPath_itemSelectionChanged(self):
        """
        Slot documentation goes here.
        """
        #        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)
Exemplo n.º 3
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()))
Exemplo n.º 4
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))