Exemplo n.º 1
0
def my_excepthook(type, value, tback):
    '''
    catch any exception not otherwise handled by the application and show it to the user without crashing the program.
    '''
    msg = "### {}--{}--{} ###".format(type, value, tback.tb_frame)
    clipboard = QApplication.clipboard()
    clipboard.setText(msg)
    # display error message to the user
    helper = Helper()
    helper.displayErrMsg(
        "Unexpected Error", """
An unexpected error has occured.

Please paste this error message into an email to [email protected] (it's already in the clipboard)

{}
    """.format(msg))
    # log the error
    if logging:
        logging.info("An unexpected error has occured. \n {}".format(msg))
Exemplo n.º 2
0
class PageItem():
    def __init__(self,
                 neoConName=None,
                 actionButton=None,
                 pageWidget=None,
                 pageWidgetIndex=None):
        self.settings = QSettings()
        self.helper = Helper()
        # the name of the neocon for this page
        self.neoConName = neoConName
        # see if we need to prompt for the password
        self.promptPW = None
        self.checkPW()
        # the qaction on the menubar
        self.actionButton = actionButton
        self.pageWidget = pageWidget
        self.pageWidgetIndex = pageWidgetIndex
        return

    def checkPW(self, ):
        '''If the connection is prompt for password, then prompt until the user enters something.
        '''
        # get the neocon dictionary
        neoDict = self.settings.value("NeoCon/connection/{}".format(
            self.neoConName))
        if not neoDict is None:
            if neoDict["prompt"] == "True":
                # prompt for a password if needed and save what the user enters
                pw = ''
                while len(pw) < 1:
                    pw = self.helper.passwordPrompt(conName=self.neoConName,
                                                    conURL=neoDict["URL"])
                    if not pw is None:
                        if len(pw) > 0:
                            # save the encrypted password in the page item so it's ready to be used by any function
                            self.promptPW = self.helper.putText(pw)
                        else:
                            self.helper.displayErrMsg(
                                "Prompt Password",
                                "You must enter a password.")
class ConstraintRelPropExistsDlg(QDialog, Ui_ConstraintRelPropExistsDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(ConstraintRelPropExistsDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaModel
        self.helper = Helper()
        self.initUi()

    def initUi(self, ):
        aList = sorted(self.schemaModel.instanceList("Property"))
        self.cbProperty.addItem("")
        for indexName in aList:
            self.cbProperty.addItem(indexName)

        aList = sorted(self.schemaModel.instanceList("Relationship"))
        self.cbRelationships.addItem("")
        for indexName in aList:
            self.cbRelationships.addItem(indexName)
        return

    def validate(self, ):

        # must enter or select a relationship
        if self.helper.NoTextValueError(
                self.cbRelationships.currentText(),
                "You must enter or select a Relationship"):
            return False
        # must add at least one property to the list
        if self.helper.NoTextValueError(self.cbProperty.currentText(),
                                        "You must enter or select a Property"):
            return False

        return True

    def apply(self, ):
        """
        Generate and run the create constraint statement
        """
        prop = self.cbProperty.currentText()
        relationship = self.cbRelationships.currentText()
        self.cypherCommand = None
        '''
        CREATE CONSTRAINT ON ()-[r:RelID]-() ASSERT exists(r.propname)       
        '''
        self.cypherCommand = "CREATE CONSTRAINT ON ()-[p:{}]-() ASSERT exists(p.{})".format(
            relationship, prop)

        if self.cypherCommand:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.schemaModel.createConstraint(self.cypherCommand)
            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg("Create Constraint", msg)
        else:
            self.helper.displayErrMsg(
                "Create Constraint", "Error Generating Constraint Statement.")

    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)

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

    @pyqtSlot()
    def on_pbAddToList_clicked(self):
        """
        Get the property name in the combo box and add it to the list
        """
        self.lstProperty.addItem(self.cbProperty.currentText())

    @pyqtSlot()
    def on_pbRemoveList_clicked(self):
        """
        Remove the selected property from the list
        """
        self.lstProperty.takeItem(self.lstProperty.currentRow())

    @pyqtSlot()
    def on_rbUnique_clicked(self):
        """
        Slot documentation goes here.
        """
        self.cbLabel.setEnabled(True)
        self.cbProperty.setEnabled(True)
        self.cbRelationships.setEnabled(False)
        self.pbAddToList.setEnabled(True)
        self.pbRemoveList.setEnabled(True)

    @pyqtSlot()
    def on_rbNodePropExists_clicked(self):
        """
        Slot documentation goes here.
        """
        self.cbLabel.setEnabled(True)
        self.cbProperty.setEnabled(True)
        self.cbRelationships.setEnabled(False)
        self.pbAddToList.setEnabled(True)
        self.pbRemoveList.setEnabled(True)

    @pyqtSlot()
    def on_rbNodeKey_clicked(self):
        """
        Slot documentation goes here.
        """
        self.cbLabel.setEnabled(True)
        self.cbProperty.setEnabled(True)
        self.cbRelationships.setEnabled(False)
        self.pbAddToList.setEnabled(True)
        self.pbRemoveList.setEnabled(True)

    @pyqtSlot()
    def on_rbRelPropExists_clicked(self):
        """
        Slot documentation goes here.
        """
        self.cbLabel.setEnabled(False)
        self.cbProperty.setEnabled(True)
        self.cbRelationships.setEnabled(True)
        self.pbAddToList.setEnabled(True)
        self.pbRemoveList.setEnabled(True)
Exemplo n.º 4
0
class ConstraintNodePropUniqueDlg(QDialog, Ui_ConstraintNodePropUniqueDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(ConstraintNodePropUniqueDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaModel
        self.helper = Helper()
        self.initUi()

    def initUi(self, ):
        aList = sorted(self.schemaModel.instanceList("Label"))
        self.cbLabel.addItem("")
        for indexName in aList:
            self.cbLabel.addItem(indexName)

        aList = sorted(self.schemaModel.instanceList("Property"))
        self.cbProperty.addItem("")
        for indexName in aList:
            self.cbProperty.addItem(indexName)

    def validate(self, ):

        # must enter or select a label
        if self.helper.NoTextValueError(self.cbLabel.currentText(),
                                        "You must enter or select a Label"):
            return False
        # must add at least one property to the list
        if self.helper.NoTextValueError(self.cbProperty.currentText(),
                                        "You must enter or select a Property"):
            return False

        return True

    def apply(self, ):
        """
        Generate and run the create constraint statement
        """
        prop = self.cbProperty.currentText()
        label = self.cbLabel.currentText()
        self.cypherCommand = None
        '''
        CREATE CONSTRAINT ON (p:Person) ASSERT p.name IS UNIQUE
        '''
        self.cypherCommand = "CREATE CONSTRAINT ON (p:{}) ASSERT p.{} IS UNIQUE".format(
            label, prop)

        if self.cypherCommand:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.schemaModel.createConstraint(self.cypherCommand)
            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg("Create Constraint", msg)
        else:
            self.helper.displayErrMsg(
                "Create Constraint", "Error Generating Constraint Statement.")

    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)

    @pyqtSlot()
    def on_buttonBox_rejected(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)
Exemplo n.º 5
0
class DropObjectDlg(QDialog, Ui_DropObjectDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, objectType=None, objectName=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(DropObjectDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.objectType = objectType
        self.objectName = objectName
        self.schemaModel = self.parent.schemaModel
        self.helper = Helper()
        self.initUi()

    def initUi(self, ):
        self.lblTitle.setText("Drop {}".format(self.objectType))
        self.editObject.setText(self.objectName)
        return

    def logMsg(self, msg):
        '''
        If logging is active, then log the message
        '''
        if logging:
            logging.info(msg)

    def validate(self, ):

        return True

    def apply(self, ):
        """
        Generate the drop statement
        """
        if self.objectType == "User":
            self.cypherCommand = "CALL dbms.security.deleteUser('{}')".format(
                self.objectName)
        elif self.objectType == "Role":
            self.cypherCommand = "CALL dbms.security.deleteRole('{}')".format(
                self.objectName)
        else:
            if (not "ASSERT (" in self.objectName
                    and self.objectType == "Node Key"):
                self.objectName = self.objectName.replace(
                    "ASSERT ", "ASSERT (")
                self.objectName = self.objectName.replace(" IS", ") IS")
                self.cypherCommand = "Drop {}".format(self.objectName)
            else:
                self.cypherCommand = "Drop {}".format(self.objectName)

        if self.cypherCommand:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.schemaModel.dropObject(self.cypherCommand)
            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg("Drop Schema Object", msg)
            self.logMsg("Drop Schema Object - {}".format(msg))
        else:
            self.helper.displayErrMsg(
                "Drop Schema Object",
                "Error Generating Drop Schema Object Statement.")
            self.logMsg("Error Generating Drop Schema Object Statement.")

    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)

    @pyqtSlot()
    def on_buttonBox_rejected(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)
Exemplo n.º 6
0
class DiagramScene(QGraphicsScene):
    def __init__(self, parent=None):
        super(DiagramScene, self).__init__(parent)
        self.parent = parent
        self.model = self.parent.model
        self.helper = Helper()

    def addIRelToScene(self,
                       NZID=None,
                       startNode=None,
                       endNode=None,
                       mode=None):
        '''
        called by drawDiagram to initially draw an existing diagram
        '''
        try:
            # get required data, if any of this errors out then skip adding this to the diagram
            #            startNZID = startNode.data(NODEID)
            #            endNZID = endNode.data(NODEID)
            relationInstanceDict = None
            index, relationInstanceDict = self.model.getDictByName(
                topLevel="Instance Relationship", objectName=NZID)
            startNZID = relationInstanceDict["startNZID"]
            endNZID = relationInstanceDict["endNZID"]
            # create a new relation instance object
            relInstance = RelationInstance(
                parent=self,
                model=self.model,
                relationInstanceDict=relationInstanceDict,
                startNode=self.parent.itemDict[startNZID].itemInstance,
                endNode=self.parent.itemDict[endNZID].itemInstance)
            # create a new relation item object
            relItem = RelationItem(self, relationInstance=relInstance)
            # add the relation item object to the diagram item dictionary
            self.parent.itemDict[relInstance.NZID] = relItem
            # this counts how many relationships exist between two nodes
            self.parent.addRelationship(relInstance)
        except:
            print("error adding instance rel")

    def addInode(self, point, nodeInstance=None):
        '''
        Add a Node Instance to the diagram
        '''
        try:
            nodeItem = NodeItem(self,
                                point.x(),
                                point.y(),
                                nodeInstance=nodeInstance)
            # add it to the diagramEditor's list of node/rels
            self.parent.itemDict[nodeInstance.NZID] = nodeItem
        except:
            print("error adding Instance Node")

        return nodeInstance.NZID

    def addTNode(self, point, nodeTemplateDict=None, NZID=None):
        '''
        Add a Node template to the diagram at the given point.
        Used by the drawdiagram function
        '''
        try:
            # create a NodeTemplateItem
            nodeTemplateItem = NodeTemplateItem(
                self,
                point.x(),
                point.y(),
                nodeTemplateDict=nodeTemplateDict,
                NZID=NZID)
            # add it to the diagramEditor's list of node/rels
            self.parent.itemDict[nodeTemplateItem.NZID] = nodeTemplateItem
        except:
            print("error adding template Node")

    def addTRelToScene(self,
                       relTemplateName=None,
                       NZID=None,
                       startNZID=None,
                       endNZID=None,
                       mode=None):
        # if adding a relationship template while initially loading the diagram or while dropping arelationship template, you will get the name of the relationship template

        try:

            index, relationTemplateDict = self.model.getDictByName(
                topLevel="Relationship Template", objectName=relTemplateName)
            # create a new RelationTemplateItem object
            relationTemplateItem = RelationTemplateItem(
                scene=self,
                NZID=NZID,
                relationTemplateDict=relationTemplateDict,
                startNodeItem=self.parent.itemDict[startNZID],
                endNodeItem=self.parent.itemDict[endNZID])
            # add the relation item object to the diagram item dictionary
            self.parent.itemDict[
                relationTemplateItem.NZID] = relationTemplateItem
            # tell the relationship to set it's loation and draw itself
            # for some reason, rel template doesn't draw itself when the object is created.  You have to tell it to draw
            relationTemplateItem.drawIt2()
        except:
            print("error adding template Relationship")

###########################################################
# diagram render methods
###########################################################

    def renderDiagram(self, diagramType=None, diagramName=None):
        try:
            self.diagramName = diagramName
            saveIndex, diagramDict = self.model.getDictByName(
                topLevel=diagramType, objectName=diagramName)
            self.diagramDict = diagramDict  # this is all the diagram data and contains the saved diagram items
            self.itemDict = {
            }  # dictionary of graphic items rendered on the scene
            self.drawDiagram()
            self.setSceneRect(self.itemsBoundingRect())
            self.clearSelection()
        except Exception as e:
            self.helper.displayErrMsg(
                "Render Diagram",
                "Error rendering diagram {}. Error: {}".format(
                    self.diagramName, str(e)))

    def saveImage(self, fileName=None):
        try:
            self.image = QImage(self.sceneRect().size().toSize(),
                                QImage.Format_ARGB32_Premultiplied)
            self.image.fill(Qt.white)
            self.painter = QPainter(self.image)
            self.render(self.painter)
            self.painter.end()
            self.image.save(fileName)
        except Exception as e:
            self.helper.displayErrMsg(
                "Save Image",
                "Error saving diagram image file: {}. Error: {}".format(
                    fileName, str(e)))

    def drawDiagram(self, ):
        '''get saved diagram object data and create diagram objects on the scene'''
        #        print("loaded diagramDict {}".format(self.diagramDict))
        #generate all the node instances
        for diagramItem in self.diagramDict["items"]:
            #            print(str(diagramItem))
            if diagramItem["diagramType"] == "Instance Node":
                # get the node instance object
                saveIndex, nodeDict = self.model.getDictByName(
                    topLevel="Instance Node", objectName=diagramItem["NZID"])
                nodeInstance = NodeInstance(model=self.model,
                                            nodeInstanceDict=nodeDict)
                self.addInode(QPointF(diagramItem["x"], diagramItem["y"]),
                              nodeInstance=nodeInstance)
            elif diagramItem["diagramType"] == "Instance Relationship":
                self.addIRelToScene(NZID=diagramItem["NZID"])
            elif diagramItem["diagramType"] == "Node Template":
                # get the node template
                saveIndex, nodeTemplateDict = self.model.getDictByName(
                    topLevel="Node Template", objectName=diagramItem["name"])
                self.addTNode(QPointF(diagramItem["x"], diagramItem["y"]),
                              nodeTemplateDict=nodeTemplateDict,
                              NZID=diagramItem["NZID"])
            elif diagramItem["diagramType"] == "Relationship Template":
                self.addTRelToScene(relTemplateName=diagramItem["name"],
                                    NZID=diagramItem["NZID"],
                                    startNZID=diagramItem["startNZID"],
                                    endNZID=diagramItem["endNZID"])
Exemplo n.º 7
0
class FrmPropList(QFrame, Ui_Frame):
    """
    This frame provides a UI for selecting a set of properties from all properties and setting their order
    """
    def __init__(self, parent=None, curVal=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(FrmPropList, self).__init__(parent)
        self.setupUi(self)
        self.helper = Helper()
        self.curVal = curVal

    def addProps(self, propList):
        # load the list of all properties in the template
        for prop in propList:
            if len(prop) > 0:
                self.gridAllProps.addItem(prop)
        # load the list of selected properties
        if not self.curVal is None:
            selectedPropList = self.curVal.split(", ")
            for selectedProp in selectedPropList:
                if selectedProp in [
                        str(self.gridAllProps.item(i).text())
                        for i in range(self.gridAllProps.count())
                ]:
                    self.gridSelectedProps.addItem(selectedProp)

        self.setPropListDisplay()

    def genPropList(self, ):
        '''
        generate a comma seperated list of properties from the selected properties list view
        '''
        propComma = ""
        propList = [
            str(self.gridSelectedProps.item(i).text())
            for i in range(self.gridSelectedProps.count())
        ]
        propComma = ", ".join(x for x in propList)
        return propComma

    def setPropListDisplay(self, ):
        '''
        set the label with the generated property list
        '''
        self.lblPropList.setText(self.genPropList())

    @pyqtSlot()
    def on_btnAdd_clicked(self):
        """
        User clicks >> button to add a prop to the prop list
        """
        index = self.gridAllProps.currentRow()
        if (index is not None and index > -1):
            selProperty = self.gridAllProps.item(index).text()
            if selProperty in [
                    str(self.gridSelectedProps.item(i).text())
                    for i in range(self.gridSelectedProps.count())
            ]:
                self.helper.displayErrMsg(
                    "Add Property",
                    "Property {} can only be selected once".format(
                        selProperty))
            else:
                self.gridSelectedProps.addItem(
                    self.gridAllProps.item(index).text())
            self.setPropListDisplay()
        else:
            self.helper.displayErrMsg("Add Property",
                                      "You must select a property to add.")

    @pyqtSlot()
    def on_btnRemove_clicked(self):
        """
        user clicks the << button to remove a prop from the proplist
        """
        self.gridSelectedProps.takeItem(self.gridSelectedProps.currentRow())
        self.setPropListDisplay()

    @pyqtSlot()
    def on_btnMoveUp_clicked(self):
        """
        User clicks the Move Up button, move the selected property up on in the list
        """
        self.helper.moveListItemUp(self.gridSelectedProps)
        self.setPropListDisplay()

    @pyqtSlot()
    def on_btnMoveDown_clicked(self):
        """
        User clicks the Move Down button, move the selected property up on in the list
        """
        self.helper.moveListItemDown(self.gridSelectedProps)
        self.setPropListDisplay()
Exemplo n.º 8
0
class NodeTemplateCypher():
    def __init__(self, parent=None, templateDict=None):
        self.parent = parent
        self.templateDict = templateDict
        self.type = "Node"
        self.helper = Helper()

    # this determines if the DataGridWidget should call genMatch on a grid refresh or should it suppy it's own cypher
    def isGeneric(self, ):
        return False

    # set which button driven functionality will be enabled
    def refreshOK(self, ):
        return True

    def exportOK(self, ):
        return True

    def newOK(self, ):
        return True

    def deleteOK(self, ):
        return True

    def rowSelect(self, ):
        return False

    def setNullOK(self, ):
        return True

    def genDeleteDetach(self, row=None, dataGrid=None):
        model = dataGrid.model()
        # get the nodeID
        self.nodeID = "no node"
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "nodeID":
                self.nodeID = model.item(row, header).data(Qt.EditRole)

        cypher = 'match (n)  \n where id(n) = {}  \n detach delete n'.format(
            self.nodeID)

        return cypher

    def genUpdateLabel(self, updateIndex=None, dataGrid=None):
        model = dataGrid.model()
        # get the nodeID
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "nodeID":
                self.nodeID = model.item(updateIndex.row(),
                                         header).data(Qt.EditRole)

        # get if the checkbox is checked or not
        if model.item(updateIndex.row(),
                      updateIndex.column()).checkState() == Qt.Checked:
            self.operation = "Set"
        else:
            self.operation = "Remove"

        # get the label name
        self.updateLabel = model.headerData(updateIndex.column(),
                                            Qt.Horizontal, Qt.DisplayRole)

        p1 = self.nodeID
        p2 = self.operation
        p3 = self.updateLabel
        cypher = 'match (n) \n where id(n) = {}  \n {} n:{} '.format(
            p1, p2, p3)

        return cypher

    def genUpdateProp(self, updateIndex=None, dataGrid=None):
        cypher = ""
        rc = True
        model = dataGrid.model()
        self.nodeID = None
        # get the nodeID
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "nodeID":
                self.nodeID = model.item(updateIndex.row(),
                                         header).data(Qt.EditRole)
        if self.nodeID is None:
            return False, "Node ID not found."

        try:
            # get the new data value which is a string
            self.updateData = model.item(
                updateIndex.row(), updateIndex.column()).data(Qt.EditRole)
            # get the property name
            self.updateProp = model.headerData(updateIndex.column(),
                                               Qt.Horizontal, Qt.DisplayRole)
            # get the correct neo4j type
            neoType = model.item(updateIndex.row(),
                                 updateIndex.column()).data(Qt.UserRole + 1)
            # if the datatype comes back unknown then generate cypher comment
            if neoType == 'Unknown':
                cypher = "Can't update unknown datatype, correct datatype in node template."
                rc = False
            else:
                # generate the correct syntax that you set the property equal to
                self.setEqualTo = self.helper.genPropEqualTo(
                    dataValue=self.updateData, neoType=neoType)
                p1 = self.nodeID
                p2 = self.updateProp
                p3 = self.setEqualTo
                cypher = "match (n) \n where id(n) = {}  \n set n.{} = {} ".format(
                    p1, p2, p3)
                rc = True
        except BaseException as e:
            # something went wrong
            cypher = "Error generating cypher: {}".format(repr(e))
            rc = False
        finally:
            return rc, cypher

    def genRemoveProp(self, updateIndex=None, dataGrid=None):
        model = dataGrid.model()
        cypher = None
        self.nodeID = None
        # get the nodeID
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "nodeID":
                self.nodeID = model.item(updateIndex.row(),
                                         header).data(Qt.EditRole)
        if self.nodeID is None:
            return cypher

        # get the property name
        self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal,
                                           Qt.DisplayRole)
        # MAKE SURE IT ISN'T A REQUIRED PROPERTY
        for prop in self.templateDict["properties"]:
            if prop[PROPERTY] == self.updateProp:
                if prop[PROPREQ] != Qt.Checked:
                    p1 = self.nodeID
                    p2 = self.updateProp
                    cypher = "match (n) \n where id(n) = {}  \n remove n.{} ".format(
                        p1, p2)
                else:
                    self.helper.displayErrMsg(
                        "Set Null",
                        "Property {} is required by the Node Template. Cannot remove this property."
                        .format(self.updateProp))
        return cypher

    def genNewNode(self):
        nodeName = "n"
        p1 = self.genWhereLabelList(nodeName)
        p11 = self.genSetPropList(nodeName)
        p2 = " id(" + nodeName + ") as nodeID "
        p3 = self.genReturnLblList(nodeName)
        p4 = self.genReturnPropList(nodeName)
        if p3 != "":
            p2 = p2 + " , "
        if p4 != "" and p3 != "":
            p3 = p3 + " , "
        if p4 != "" and p3 == "":
            p2 = p2 + " , "

        cypher = 'create ({}) \n {} \n return  {} \n {} \n {} '.format(
            p1, p11, p2, p3, p4)
        return cypher

    def genMatchReturnNodeOnly(self):
        nodeName = "n"
        p1 = self.genWhereLabelList(nodeName)
        if len(p1) > 0:
            p1 = " where " + p1
        p2 = " id(" + nodeName + ") as nodeID "
        p3 = " n as Node"

        cypher = 'match (n) \n {} \n return  {}, \n {} '.format(p1, p2, p3)

        #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP
        editParmDict = []
        editParmDict.append([NODEID, False])
        #        editParmDict.append( [PROP, False] )
        #        editParmDict.append( [NODE, False] )
        for label in self.templateDict["labels"]:
            if label[REQUIRED] == 2:
                editParmDict.append([REQLBL, False])
            else:
                editParmDict.append([OPTLBL, True])
        for prop in self.templateDict["properties"]:
            editParmDict.append([PROP, True])

        return cypher, editParmDict

    def genMatch(self):
        nodeName = "n"
        p1 = self.genWhereLabelList(nodeName)
        if len(p1) > 0:
            p1 = " where " + p1
#        p2 = " id(" + nodeName +  ") as nodeID, \n n.NZID, \n n as Node "
        p2 = " id(" + nodeName + ") as nodeID "
        p3 = self.genReturnLblList(nodeName)
        p4 = self.genReturnPropList(nodeName)
        if p3 != "":
            p2 = p2 + " , "
        if p4 != "" and p3 != "":
            p3 = p3 + " , "
        if p4 != "" and p3 == "":
            p2 = p2 + " , "

        cypher = 'match (n) \n {} \n return  {} \n {} \n {}  '.format(
            p1, p2, p3, p4)

        #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP
        editParmDict = []
        editParmDict.append([NODEID, False])
        #        editParmDict.append( [PROP, False] )
        #        editParmDict.append( [NODE, False] )
        for label in self.templateDict["labels"]:
            if label[REQUIRED] == 2:
                editParmDict.append([REQLBL, False])
            else:
                editParmDict.append([OPTLBL, True])
        for prop in self.templateDict["properties"]:
            editParmDict.append([PROP, True])

        return cypher, editParmDict

    def genWhereLabelList(self, nodeName):
        'only check for labels that are marked required'
        lblList = ""
        if len(self.templateDict["labels"]) > 0:
            lblList = nodeName + ":" + ":".join(
                x[LABEL]
                for x in self.templateDict["labels"] if x[REQUIRED] == 2)
        return lblList

    def genReturnPropList(self, nodeName):
        'return all properties in the template'
        propList = ""
        propList = ",".join(nodeName + "." + x[PROPERTY] + " as " + x[PROPERTY]
                            for x in self.templateDict["properties"])
        return propList

    def genReturnLblList(self, nodeName):
        'return all labels in the template'
        lblList = ""
        lblList = ",".join(nodeName + ":" + x[LABEL] + " as " + x[LABEL]
                           for x in self.templateDict["labels"])
        return lblList

    def genSetPropList(self, nodeName):
        'return a set statement for each property that has a default value (i.e. required)'
        setPropList = []
        if not self.templateDict is None:
            for prop in self.templateDict["properties"]:
                # if the property has a default value then generate the set statement.
                if prop[PROPDEF] != "":
                    # generate the correct syntax that you set the property equal to
                    setEqualTo = self.helper.genPropEqualTo(
                        dataValue=prop[PROPDEF], neoType=prop[DATATYPE])
                    setPropList.append("set {}.{} = {}".format(
                        nodeName, prop[PROPERTY], setEqualTo))
        setProps = " \n ".join(setProp for setProp in setPropList)
        return setProps
Exemplo n.º 9
0
class EditRoleDlg(QDialog, Ui_EditRoleDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, mode=None, roleName=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(EditRoleDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.roleName = roleName
        self.mode = mode
        self.helper = Helper()
        self.initUI()

    def initUI(self, ):
        self.txtRoleName.setText(self.roleName)
        self.txtRoleName.setReadOnly(True)

        # populate the user combo box
        self.cbUserName.addItems(self.parent.schemaModel.instanceList("User"))
        # populate role users in the list box
        roleUsers = self.parent.schemaModel.getRoleUsers(
            roleName=self.roleName)
        for roleUser in roleUsers:
            self.lstRoleUsers.addItem(roleUser)

    @pyqtSlot()
    def on_dlgBtnBox_accepted(self):
        """
        User clicks Close
        """
        QDialog.accept(self)

    @pyqtSlot()
    def on_dlgBtnBox_rejected(self):
        """
        User clicks something that generates the reject
        """
        QDialog.reject(self)

    @pyqtSlot()
    def on_btnAddUser_clicked(self):
        """
        Give the user selected from the combobox the role
        """
        addUser = self.cbUserName.currentText()
        currentUsers = [
            str(self.lstRoleUsers.item(i).text())
            for i in range(self.lstRoleUsers.count())
        ]
        if addUser in currentUsers:
            # error, the user already has the selected role
            self.helper.displayErrMsg("Edit Role",
                                      "The role already has this user.")
        else:
            # give the role the user
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.parent.schemaModel.addUserRole(userName=addUser,
                                                          role=self.roleName)
            if rc:
                self.lstRoleUsers.addItem(addUser)
            else:
                self.helper.displayErrMsg("Add User to Role Error", msg)
            QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnRemoveUsers_clicked(self):
        """
        Remove the selected user from the list of users in the role
        """
        if self.lstRoleUsers.currentItem() == None:
            self.helper.displayErrMsg("Remove User From Role",
                                      "You must select a user to remove.")
            return

        removeUser = self.lstRoleUsers.currentItem().text()
        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.parent.schemaModel.removeUserRole(userName=removeUser,
                                                         role=self.roleName)
        if rc:
            self.lstRoleUsers.takeItem(self.lstRoleUsers.currentRow())
        else:
            self.helper.displayErrMsg("Remove User from Role Error", msg)
        QApplication.restoreOverrideCursor()
Exemplo n.º 10
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))
Exemplo n.º 11
0
class SyncToDBDlg(QDialog, Ui_Dialog):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(SyncToDBDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.objectName = self.parent.diagramName
        self.designModel = self.parent.model
        self.syncNeoCon = self.designModel.modelNeoCon
        self.itemDict = self.parent.itemDict
        self.helper = Helper()
        self.initPage()
        #populate ui data from object
        self.populateUIfromObject()           
        
    def initPage(self, ):

        # header area
        self.txtDiagramName.setText(self.objectName)  
        self.txtNeoCon.setText("{} - {}".format( self.syncNeoCon.name, self.syncNeoCon.neoDict["URL"]))

        # action grid
#        ITEMTYPE, DIAGRAMITEM, GRAPHDBITEM, MATCH, ACTION
        self.gridSyncActions.setModel(self.createSyncActionModel())
        self.gridSyncActions.setColumnWidth(ITEMKEY, 50)
        self.gridSyncActions.setColumnWidth(ITEMTYPE, 125)
        self.gridSyncActions.setColumnWidth(DIAGRAMITEM, 400)
        self.gridSyncActions.setColumnWidth(ACTION, 120)
        self.gridSyncActions.setColumnWidth(GRAPHDBITEM, 400)
        self.gridSyncActions.setColumnWidth(MATCH, 80)
        
        self.gridSyncActions.setItemDelegateForColumn(ACTION, CBDelegate(self, [SYNCTO,SYNCNONE,SYNCFROM] ))
        header = self.gridSyncActions.horizontalHeader()
        header.setSectionResizeMode(ITEMKEY, QHeaderView.Fixed)  
        header.setSectionResizeMode(ITEMTYPE, QHeaderView.Fixed)  
        header.setSectionResizeMode(DIAGRAMITEM, QHeaderView.Interactive)
        header.setSectionResizeMode(GRAPHDBITEM, QHeaderView.Interactive)  
        header.setSectionResizeMode(MATCH, QHeaderView.Fixed)  
        header.setSectionResizeMode(ACTION, QHeaderView.Fixed)  
        header.setHighlightSections(False)

    
    def createSyncActionModel(self):
#        ITEMTYPE, DIAGRAMITEM, ACTION, GRAPHDBITEM, MATCH
        model = QStandardItemModel(0, 6)
        model.setHeaderData(ITEMKEY, Qt.Horizontal, "Key")
        model.setHeaderData(ITEMTYPE, Qt.Horizontal, "Type")
        model.setHeaderData(DIAGRAMITEM, Qt.Horizontal, "Diagram Item")
        model.setHeaderData(ACTION, Qt.Horizontal, "Action")
        model.setHeaderData(GRAPHDBITEM, Qt.Horizontal, "Neo4j Item")
        model.setHeaderData(MATCH, Qt.Horizontal, "Match")
        
        return model  
    
    def populateUIfromObject(self, ):
        if self.itemDict is not None:
            # scan the nodes        
            for key, value in self.itemDict.items():
                objectItemDict = value.getObjectDict()
                if objectItemDict["diagramType"] == "Instance Node":
                    rc, msg = value.itemInstance.checkNode()
                    self.addActionRow(rc, msg, value)
                
            # scan the relationships
            for key, value in self.itemDict.items():
                objectItemDict = value.getObjectDict()
                if objectItemDict["diagramType"] == "Instance Relationship":
                    rc, msg = value.relationInstance.checkRelationship()
                    self.addActionRow(rc, msg, value)

                        
    def addActionRow(self, rc, msg, diagramItem):
#        ITEMTYPE, DIAGRAMITEM, ACTION, GRAPHDBITEM, MATCH
        objectItemDict = diagramItem.getObjectDict()
        
        item0 = QStandardItem(diagramItem.NZID())
        item0.setEditable(False)
        
        item1 = QStandardItem(objectItemDict["diagramType"])
        item1.setEditable(False)
        
        if objectItemDict["diagramType"] == "Instance Node":
            objectDict = diagramItem.itemInstance.getObjectDict()
#            item2 = QStandardItem("({} {})".format(diagramItem.itemInstance.labelList, diagramItem.itemInstance.propList))
            item2 = QStandardItem(objectDict["displayName"])
            item2.setEditable(False)
            item3 = QStandardItem(self.syncNeoCon.displayNode(diagramItem.itemInstance.node))
            item3.setEditable(False)
        elif objectItemDict["diagramType"] == "Instance Relationship":
            objectDict = diagramItem.relationInstance.getObjectDict()
#            item2 = QStandardItem("({})".format(diagramItem.relationInstance.propList))
            item2 = QStandardItem(objectDict["displayName"])
            item2.setEditable(False)
            item3 = QStandardItem(self.syncNeoCon.displayRelationship(diagramItem.relationInstance.relationship))
            item3.setEditable(False)
        else:
            item2 = QStandardItem("({})".format("No Object"))
            item2.setEditable(False)
            item3 = QStandardItem("No Object")
            item3.setEditable(False)
            
            
        if rc == FOUNDMATCH:
            item4 = QStandardItem("Match")
        if rc == FOUNDNOMATCH:
            item4 = QStandardItem("Different")
        if rc == NOTFOUND:
            item4 = QStandardItem("Not in Neo4j")
        item4.setEditable(False)      
        
        if rc == FOUNDMATCH:
            item5 = QStandardItem(SYNCNONE)
        if rc == FOUNDNOMATCH:
            item5 = QStandardItem(SYNCTO)
        if rc == NOTFOUND:
            item5 = QStandardItem(SYNCTO)
        item5.setEditable(True)        

        self.gridSyncActions.model().appendRow([item0, item1,item2, item5, item3, item4])  

        
    def validate(self, ):
        
        return True
        
    def apply(self, ):
        '''
        The dialog passes all edits so process all the sync actions.
        '''
        model = self.gridSyncActions.model()
        numrows = model.rowCount()
        for row in range(0,numrows):
            syncAction = model.item(row,ACTION).data(Qt.EditRole)
            rc = None
            msg = ""
            if syncAction == SYNCTO:
                key = model.item(row,ITEMKEY ).data(Qt.EditRole)
                item = self.itemDict[key]
                if item.diagramType == "Instance Relationship":
                    rc, msg = item.relationInstance.syncToDB()
                if item.diagramType == "Instance Node":    
                    rc, msg = item.itemInstance.syncToDB()
            if syncAction == SYNCFROM:
                key = model.item(row,ITEMKEY ).data(Qt.EditRole)
                item = self.itemDict[key]
                if item.diagramType == "Instance Relationship":
                    rc, msg = item.relationInstance.syncFromDB()
                if item.diagramType == "Instance Node":    
                    rc, msg = item.itemInstance.syncFromDB()     
            # display error message if any
            if not rc is None:
                if rc == False:
                     self.helper.displayErrMsg("Sync Diagram Error", msg)
                     
    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)
    
    @pyqtSlot()
    def on_buttonBox_rejected(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)
Exemplo n.º 12
0
class dlgReverseEngineer(QDialog, Ui_dlgReverseEngineer):
    """
    This dialog box provides a facility to scan a neo4j database and generate node and relationship templates
    """
    def __init__(self,
                 parent=None,
                 schemaModel=None,
                 model=None,
                 settings=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(dlgReverseEngineer, self).__init__(parent)
        self.parent = parent
        self.schemaModel = schemaModel
        self.echo = True
        self.settings = settings
        self.model = model
        self.helper = Helper()
        self.neoTypeFunc = NeoTypeFunc()
        self.myNeoCon = self.model.modelNeoCon
        self.setupUi(self)
        self.initPage()

    def initPage(self, ):
        # header area
        self.editNeoURL.setText("{} - {}".format(self.myNeoCon.name,
                                                 self.myNeoCon.neoDict["URL"]))
        self.cbScanNodes.setCheckState(Qt.Checked)
        self.cbScanRels.setCheckState(Qt.Checked)
        # count of nodes and rels
        self.countStuff()
        # update percents
        self.testScan()
        # results area
        self.clearResults()
        # buttons
        self.stopScan = False
        self.btnStop.setEnabled(False)
        self.btnStart.setEnabled(True)

    def countStuff(self, ):
        # get max ID's in use
        try:
            cypher = '''call dbms.queryJmx("org.neo4j:instance=kernel#0,name=Primitive count") yield attributes
                        with  keys(attributes) as k , attributes
                        unwind k as row
                        return "ID Allocations" as type,row,attributes[row]["value"]
                     '''
            #run the query
            rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
            if rc1:
                self.maxRelId = 0
                self.maxNodeId = 0
                for row in self.myNeoCon.resultSet:
                    if row["row"] == 'NumberOfNodeIdsInUse':
                        self.maxNodeId = row['attributes[row]["value"]']
                    if row["row"] == 'NumberOfRelationshipIdsInUse':
                        self.maxRelId = row['attributes[row]["value"]']
                msg = "Max Node ID: {} Max Relationship ID: {}".format(
                    self.maxNodeId, self.maxRelId)
#                    self.editNumNodes.setText(str(self.myNeoCon.resultSet[0]["count(*)"]))
            else:
                msg = "Get Max ID Error {}".format(msg1)
        except BaseException as e:
            msg = "{} - Get Max ID failed.".format(repr(e))
        finally:
            QApplication.restoreOverrideCursor()
            self.displayScanMsg(msg)

        # get number of nodes
        try:
            cypher = "MATCH (n) RETURN count(*)"
            #run the query
            rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
            if rc1:
                msg = "Counted {} Nodes.".format(str(self.myNeoCon.resultSet))
                self.editNumNodes.setText(
                    str(self.myNeoCon.resultSet[0]["count(*)"]))
            else:
                msg = "Count Nodes Error {}".format(msg1)
        except BaseException as e:
            msg = "{} - Node Count failed.".format(repr(e))
        finally:
            QApplication.restoreOverrideCursor()
            self.displayScanMsg(msg)

        # get number of rels
        try:
            cypher = "MATCH ()-[r]->() RETURN count(*)"
            #run the query
            rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
            if rc1:
                msg = "Counted {} Relationships.".format(
                    str(self.myNeoCon.resultSet))
                self.editNumRels.setText(
                    str(self.myNeoCon.resultSet[0]["count(*)"]))
            else:
                msg = "Count Relationships Error {}".format(msg1)
        except BaseException as e:
            msg = "{} - Relationships Count failed.".format(repr(e))
        finally:
            QApplication.restoreOverrideCursor()
            self.displayScanMsg(msg)

    def clearResults(self, ):
        # message text area
        self.editProgress.clear()
        # NODE results grid
        #        GENERATE,, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN
        self.gridTemplates.setModel(self.createResultsModel())
        self.gridTemplates.setColumnWidth(GENERATE, 50)
        self.gridTemplates.setColumnWidth(TEMPLATENAME, 150)
        self.gridTemplates.setColumnWidth(LABELPATTERN, 300)
        self.gridTemplates.setColumnWidth(PROPERTYPATTERN, 300)
        self.gridTemplates.setColumnWidth(NODECOUNT, 100)

        header = self.gridTemplates.horizontalHeader()
        header.setSectionResizeMode(GENERATE, QHeaderView.Fixed)
        header.setSectionResizeMode(TEMPLATENAME, QHeaderView.Interactive)
        header.setSectionResizeMode(LABELPATTERN, QHeaderView.Interactive)
        header.setSectionResizeMode(PROPERTYPATTERN, QHeaderView.Interactive)
        header.setSectionResizeMode(NODECOUNT, QHeaderView.Fixed)

        # RELATIONSHIP results grid
        #       GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, RELPROPERTYPATTERN = range(6)
        self.gridTemplates_Rel.setModel(self.createRelResultsModel())
        self.gridTemplates_Rel.setColumnWidth(GENERATE, 50)
        self.gridTemplates_Rel.setColumnWidth(RELTEMPLATENAME, 150)
        self.gridTemplates_Rel.setColumnWidth(RELATIONSHIPNAME, 125)
        self.gridTemplates_Rel.setColumnWidth(FROMTEMPLATE, 150)
        self.gridTemplates_Rel.setColumnWidth(TOTEMPLATE, 150)
        self.gridTemplates_Rel.setColumnWidth(RELPROPERTYPATTERN, 300)
        self.gridTemplates_Rel.setColumnWidth(RELCOUNT, 100)
        self.gridTemplates_Rel.setColumnWidth(RELKEY, 100)

        header = self.gridTemplates_Rel.horizontalHeader()
        header.setSectionResizeMode(GENERATE, QHeaderView.Fixed)
        header.setSectionResizeMode(RELTEMPLATENAME, QHeaderView.Interactive)
        header.setSectionResizeMode(RELATIONSHIPNAME, QHeaderView.Interactive)
        header.setSectionResizeMode(FROMTEMPLATE, QHeaderView.Interactive)
        header.setSectionResizeMode(TOTEMPLATE, QHeaderView.Interactive)
        header.setSectionResizeMode(RELPROPERTYPATTERN,
                                    QHeaderView.Interactive)
        header.setSectionResizeMode(RELCOUNT, QHeaderView.Fixed)
        header.setSectionResizeMode(RELKEY, QHeaderView.Fixed)

    def createRelResultsModel(self):
        #       GENERATE, TEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, PROPERTYPATTERN = range(6)
        model = QStandardItemModel(0, 8)
        model.setHeaderData(GENERATE, Qt.Horizontal, "")
        model.setHeaderData(RELTEMPLATENAME, Qt.Horizontal, "Template Name")
        model.setHeaderData(RELATIONSHIPNAME, Qt.Horizontal,
                            "Relationship Name")
        model.setHeaderData(FROMTEMPLATE, Qt.Horizontal, "From Node Template")
        model.setHeaderData(TOTEMPLATE, Qt.Horizontal, "To Node Template")
        model.setHeaderData(RELPROPERTYPATTERN, Qt.Horizontal,
                            "Property Pattern")
        model.setHeaderData(RELCOUNT, Qt.Horizontal, "# Scanned")
        model.setHeaderData(RELKEY, Qt.Horizontal, "Unique Key")
        model.dataChanged.connect(self.templateRelGridChanged)
        return model

    def createResultsModel(self):
        #        GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN
        model = QStandardItemModel(0, 5)
        model.setHeaderData(GENERATE, Qt.Horizontal, "")
        model.setHeaderData(TEMPLATENAME, Qt.Horizontal, "Template Name")
        model.setHeaderData(LABELPATTERN, Qt.Horizontal, "Label Pattern")
        model.setHeaderData(PROPERTYPATTERN, Qt.Horizontal, "Property Pattern")
        model.setHeaderData(NODECOUNT, Qt.Horizontal, "# Scanned")
        model.dataChanged.connect(self.templateGridChanged)
        return model

    def addResultRow(self, model, c1, c2, c3, c4, c5):
        #        GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN, COUNT
        item1 = QStandardItem(c1)
        item1.setEditable(True)
        item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        item2 = QStandardItem(c2)
        item3 = QStandardItem(c3)
        item3.setEditable(False)
        item4 = QStandardItem(c4)
        item4.setEditable(False)
        item5 = QStandardItem(c5)
        item5.setEditable(False)
        if c1 in [0, 1, 2]:
            item1.setCheckState(c1)
        else:
            item1.setCheckState(Qt.Unchecked)

        model.appendRow([item1, item2, item3, item4, item5])

    def addRelResultRow(self, model, c1, c2, c3, c4, c5, c6, c7, c8):
        #       GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, PROPERTYPATTERN, COUNT
        item1 = QStandardItem(c1)
        item1.setEditable(True)
        item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        item2 = QStandardItem(c2)
        # save the original rel templatename in case the user renames it on the grid.
        item2.setData(c2, Qt.UserRole)
        item3 = QStandardItem(c3)
        item3.setEditable(False)
        item4 = QStandardItem(c4)
        item4.setEditable(False)
        item5 = QStandardItem(c5)
        item5.setEditable(False)
        item6 = QStandardItem(c6)
        item6.setEditable(False)
        item7 = QStandardItem(c7)
        item7.setEditable(False)
        item8 = QStandardItem(c8)
        item8.setEditable(False)
        if c1 in [0, 1, 2]:
            item1.setCheckState(c1)
        else:
            item1.setCheckState(Qt.Unchecked)

#        print(c1, c2, c3, c4, c5, c6, c7, c8)
        model.appendRow(
            [item1, item2, item3, item4, item5, item6, item7, item8])

    def newScan(self, ):
        self.clearResults()
        self.patternList = []  # keeps track of the unique label combinations
        #        self.patternListCount = []   # keeps track of the number of nodes matching the unique label combinations in patternList
        self.relPatternListCount = []
        self.nodeDict = {}  # dictionary to hold discovered node patterns
        self.relDict = {
        }  # dictionary to hold discovered relationship patterns

    def displayScanMsg(self, text):
        if self.echo:
            # display on UI
            self.editProgress.appendPlainText("{}: {}".format(
                str(datetime.now()), text))
            QApplication.processEvents()
        # add real logging here
#        logging.info(text)

    def logMessage(self, msg):
        if self.echo:
            # display on UI
            self.browserOutput.append(msg)
            QApplication.processEvents()
#        logging.info(msg)

    def templateGridChanged(self, index1, index2):
        '''
        The user can over write the automatically generted Node template name.
        This needs to be recorded in the nodeDict template dictionary so it
        can be properly generated.
        '''
        if index1.column() == TEMPLATENAME:
            #            print("row - {}, col - {}, data - {}".format(index1.row(), index1.column(), index1.data(role = Qt.DisplayRole)))
            #            print("before:{}".format(self.nodeDict["{0:0>5}".format(index1.row())]))
            beforeTemplateName = self.nodeDict["{0:0>5}".format(
                index1.row())]["templateLblName"]
            afterTemplateName = index1.data(role=Qt.DisplayRole)
            #update the node pattern dictionary with the new name
            try:
                self.nodeDict["{0:0>5}".format(
                    index1.row())]["templateLblName"] = afterTemplateName
                # update the rel dict
                for key, value in self.relDict.items():
                    if value["toTemplate"] == beforeTemplateName:
                        value["toTemplate"] = afterTemplateName
                    if value["fromTemplate"] == beforeTemplateName:
                        value["fromTemplate"] = afterTemplateName
                # update the rel template grid on the UI
                model = self.gridTemplates_Rel.model()
                numrows = model.rowCount()
                for row in range(0, numrows):
                    curRelTemplateName = model.item(row, RELTEMPLATENAME).data(
                        Qt.EditRole)
                    if model.item(row, FROMTEMPLATE).data(
                            Qt.EditRole) == beforeTemplateName:
                        model.setData(model.index(row, FROMTEMPLATE),
                                      afterTemplateName)
                        model.setData(
                            model.index(row, RELTEMPLATENAME),
                            curRelTemplateName.replace(beforeTemplateName,
                                                       afterTemplateName))
                    if model.item(row, TOTEMPLATE).data(
                            Qt.EditRole) == beforeTemplateName:
                        model.setData(model.index(row, TOTEMPLATE),
                                      afterTemplateName)
                        model.setData(
                            model.index(row, RELTEMPLATENAME),
                            curRelTemplateName.replace(beforeTemplateName,
                                                       afterTemplateName))

            except:
                pass
#                print("error")

            finally:
                pass
#                print("AFTER:{}".format(self.nodeDict["{0:0>5}".format(index1.row())]))

    def templateRelGridChanged(self, index1, index2):
        '''
        The user can over write the automatically generated Relationship template name.
        '''
        return

    @pyqtSlot()
    def on_btnStart_clicked(self):
        """
        Reverse Engineer The current Graph.
        Create Node and Relationship templates
        """
        # switch to the progress tab
        self.tabWidget.setCurrentIndex(0)

        # for now, must scan nodes
        if self.cbScanNodes.isChecked() == False:
            self.cbScanNodes.setCheckState(Qt.Checked)

        #setup for a new scan
        self.newScan()
        self.stopScan = False
        self.btnStop.setEnabled(True)
        self.btnStart.setEnabled(False)

        # do the scan
        self.scanAll()

        # reset the buttons
        self.btnStop.setEnabled(False)
        self.btnStart.setEnabled(True)

        # switch to the results tab
        self.tabWidget.setCurrentIndex(1)

    def getDataTypes(self,
                     nodeID=None,
                     relID=None,
                     propDataTypeDict=None,
                     propList=None):
        if not nodeID is None:
            returnPropList = self.genReturnPropList("n", propList)
            if len(returnPropList) > 0:
                cypher = "match (n) where id(n) = {} return {}".format(
                    str(nodeID), returnPropList)
            else:
                return
        if not relID is None:
            returnPropList = self.genReturnPropList("r", propList)
            if len(returnPropList) > 0:
                cypher = "match ()-[r]->() where id(r) = {} return {}".format(
                    str(relID), returnPropList)
            else:
                return

        #run the query
        rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
        if rc1:
            record = self.myNeoCon.resultSet[0]
            # get the datatype for each property
            # value has the correct python object for each cell in the result set
            for index, value in record.items():
                if not value is None:
                    dataType = self.neoTypeFunc.getNeo4jDataType(value)
                    propDataTypeDict[index] = dataType

    def genReturnPropList(self, nodeName, propList):
        'return all properties in the template'
        genPropList = ""
        genPropList = ",".join(nodeName + "." + x + " as " + x
                               for x in propList)
        return genPropList

    def scanAll(self, ):
        msg = "Scan finished"
        if self.cbScanNodes.isChecked() == True:
            # scan the nodes
            self.displayScanMsg("Start Scanning Nodes.")
            limitAmt = self.spinProcessSize.value()
            skipIncrement = self.spinSkipAmt.value()
            skipAmt = 0
            totAmt = 0
            self.moreData = True
            try:
                while (self.moreData and self.stopScan == False):
                    rc = False
                    cypher = "match (n) return id(n) as nodeID, labels(n), keys(n) skip {} limit {}".format(
                        str(skipAmt), str(limitAmt))
                    #run the query
                    rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
                    if rc1 == True:
                        x = self.processModelChunk()
                        if x > 0:
                            totAmt = totAmt + x
                            self.displayScanMsg(
                                "Skip to: {} Processed: {} Total Processed {} Nodes."
                                .format(str(skipAmt), str(limitAmt), totAmt))
                            skipAmt = skipAmt + limitAmt + skipIncrement
                        else:
                            self.moreData = False
                            rc = True
                            msg = "Scan Nodes complete"
                    else:
                        msg = "Scan Nodes Error {}".format(msg1)
                        self.moreData = False

            except BaseException as e:
                msg = "{} - Node Scan failed.".format(repr(e))
            finally:
                self.displayScanMsg(msg)
            # add scanned node templates to the grid
            if self.stopScan == False:
                self.genNodeResult()

        if self.cbScanRels.isChecked() == True:
            # scan the relationships
            self.displayScanMsg("Start Scanning Relationships.")
            limitAmt = self.spinProcessSize.value()
            skipIncrement = self.spinSkipAmt.value()
            skipAmt = 0
            totAmt = 0
            self.moreData = True
            try:
                while (self.moreData and self.stopScan == False):
                    rc = False
                    cypher = "match (f)-[r]->(t) return id(r), keys(r), type(r), labels(f), labels(t) skip {} limit {}".format(
                        str(skipAmt), str(limitAmt))
                    #run the query
                    rc1, msg1 = self.myNeoCon.runCypherAuto(cypher)
                    if rc1:
                        x = self.processRelModelChunk()
                        if x > 0:
                            totAmt = totAmt + x
                            self.displayScanMsg(
                                "Skip to: {} Processed: {} Total Processed {} Relationships."
                                .format(str(skipAmt), str(x), totAmt))
                            skipAmt = skipAmt + limitAmt + skipIncrement
                        else:
                            self.moreData = False
                            rc = True
                            msg = "Scan Relationships complete"
                    else:
                        msg = "Scan Relationships Error {}".format(msg1)
                        self.moreData = False

            except BaseException as e:
                msg = "{} - Relationships Scan failed.".format(repr(e))
            finally:
                self.displayScanMsg(msg)

            if self.stopScan == False:
                self.genRelResult()

            return rc, msg

    def processModelChunk(self, ):
        ctr = 0
        for record in self.myNeoCon.resultSet:
            ctr = ctr + 1
            labels = record["labels(n)"]
            props = record["keys(n)"]
            nodeID = record["nodeID"]
            try:
                x = self.patternList.index(labels)
                patternName = "{0:0>5}".format(x)
            except ValueError:
                nextx = len(self.patternList)
                self.patternList.insert(nextx, labels)
                patternName = "{0:0>5}".format(nextx)
                self.nodeDict[patternName] = {}
                self.nodeDict[patternName]["propList"] = []
                self.nodeDict[patternName]["labelList"] = labels
                self.nodeDict[patternName]["templateName"] = "Node{}".format(
                    patternName)
                self.nodeDict[patternName]["templateLblName"] = "{}".format(
                    "_".join(labels))
                self.nodeDict[patternName]["propDataType"] = {}
                self.nodeDict[patternName]["count"] = 0
            finally:
                count = self.nodeDict[patternName]["count"] + 1
                self.nodeDict[patternName]["count"] = count
                # get datatypes for newly discovered properties
                newProps = list(
                    set(props) -
                    set(self.nodeDict[patternName].get("propList", [])))
                if len(newProps) > 0:
                    self.getDataTypes(
                        nodeID=nodeID,
                        propDataTypeDict=self.nodeDict[patternName]
                        ["propDataType"],
                        propList=newProps)
                # merge in any newly discovered properties
                self.nodeDict[patternName]["propList"] = list(
                    set(self.nodeDict[patternName].get("propList", []) +
                        props))
        return ctr

    def processRelModelChunk(self, ):
        ctr = 0
        for record in self.myNeoCon.resultSet:
            ctr = ctr + 1
            relProps = record["keys(r)"]
            relType = record["type(r)"]
            fromLbls = record["labels(f)"]
            toLbls = record["labels(t)"]
            relID = record["id(r)"]
            # get from and to node templates
            fromTemplate = "Unknown"
            toTemplate = "Unknown"
            for nodepattern, nodedata in self.nodeDict.items():
                if nodedata["labelList"] == fromLbls:
                    fromTemplate = nodedata["templateLblName"]
                if nodedata["labelList"] == toLbls:
                    toTemplate = nodedata["templateLblName"]
            try:
                relKey = "{}:{}:{}".format(relType, fromTemplate, toTemplate)
                # check to see if the relKey entry in the dictionary already exists, if it doesn't you get a KeyError
                check = self.relDict[relKey]
            except KeyError:
                # count how many times the relType has been used in a rel template
                countReltypeUsed = self.countRelType(relType)
                # the relkey doesn't exist so add it to the dictionary
                self.relDict[relKey] = {}
                self.relDict[relKey]["propList"] = []
                self.relDict[relKey]["propDataType"] = {}
                if countReltypeUsed > 0:
                    self.relDict[relKey]["templateName"] = "{0}{1:0>3}".format(
                        relType, countReltypeUsed)
                else:
                    self.relDict[relKey]["templateName"] = relType
                self.relDict[relKey]["relName"] = relType
                self.relDict[relKey]["fromTemplate"] = fromTemplate
                self.relDict[relKey]["toTemplate"] = toTemplate
                self.relDict[relKey]["count"] = 0
            finally:
                count = self.relDict[relKey]["count"] + 1
                self.relDict[relKey]["count"] = count
                # get datatypes for newly discovered properties
                newProps = list(
                    set(relProps) -
                    set(self.relDict[relKey].get("propList", [])))
                if len(newProps) > 0:
                    self.getDataTypes(
                        relID=relID,
                        propDataTypeDict=self.relDict[relKey]["propDataType"],
                        propList=newProps)
                # merge in the properties
                self.relDict[relKey]["propList"] = list(
                    set(self.relDict[relKey].get("propList", []) + relProps))

        return ctr

    def countRelType(self, relType):
        cnt = 0
        for key in self.relDict.keys():
            if self.relDict[key]["relName"] == relType:
                cnt = cnt + 1
        return cnt

    def testScan(self):
        '''simulate the scan to get total nodes and percent nodes
        '''
        # simulate Node Count
        self.stopScan = False
        totNodes = int(self.editNumNodes.text())
        nodesRemaining = totNodes
        totRels = int(self.editNumRels.text())
        relsRemaining = totRels
        limitAmt = self.spinProcessSize.value()
        skipIncrement = self.spinSkipAmt.value()
        skipAmt = 0
        totAmt = 0
        self.moreData = True
        try:
            while (self.moreData and self.stopScan == False):
                if nodesRemaining > limitAmt:
                    x = limitAmt
                    nodesRemaining = nodesRemaining - limitAmt - skipIncrement
                else:
                    x = nodesRemaining
                    nodesRemaining = 0
                if x > 0:
                    totAmt = totAmt + x
                    #                    print("Skip to: {} Processed: {} Total Processed {} Nodes.".format(str(skipAmt), str(limitAmt),totAmt))
                    skipAmt = skipAmt + limitAmt + skipIncrement
                else:
                    self.moreData = False
            else:
                self.moreData = False
        except BaseException as e:
            self.helper.displayErrMsg(
                "Calcuate Percents",
                "{} - Error Calculating Percents.".format(repr(e)))
        finally:
            # update UI
            self.txtNodeScanAmt.setText(str(totAmt))
            displayPercent = "{0:.0%}".format(totAmt / totNodes)
            self.txtNodePercent.setText(displayPercent)

        # simulate Rel Count
        self.stopScan = False
        totRels = int(self.editNumRels.text())
        relsRemaining = totRels
        limitAmt = self.spinProcessSize.value()
        skipIncrement = self.spinSkipAmt.value()
        skipAmt = 0
        totAmt = 0
        self.moreData = True
        try:
            while (self.moreData and self.stopScan == False):
                if relsRemaining > limitAmt:
                    x = limitAmt
                    relsRemaining = relsRemaining - limitAmt - skipIncrement
                else:
                    x = relsRemaining
                    relsRemaining = 0
                if x > 0:
                    totAmt = totAmt + x
                    #                    print("Skip to: {} Processed: {} Total Processed {} Nodes.".format(str(skipAmt), str(limitAmt),totAmt))
                    skipAmt = skipAmt + limitAmt + skipIncrement
                else:
                    self.moreData = False
            else:
                self.moreData = False
        except BaseException as e:
            self.helper.displayErrMsg(
                "Calcuate Percents",
                "{} - Error Calculating Percents.".format(repr(e)))
        finally:
            # update UI
            self.txtRelScanAmt.setText(str(totAmt))
            if totRels == 0 or totRels is None:
                displayPercent = "{0:.0%}".format(0)
            else:
                displayPercent = "{0:.0%}".format(totAmt / totRels)
            self.txtRelPercent.setText(displayPercent)

    def genNodeResult(self, ):
        '''
        Scan the reverse engineered node dictionary and create Node Template rows in the grid
        GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN        
        '''
        for key in sorted(self.nodeDict.keys()):
            value = self.nodeDict[key]
            self.addResultRow(self.gridTemplates.model(), Qt.Checked,
                              str(value["templateLblName"]),
                              str(value["labelList"]), str(value["propList"]),
                              str(value["count"]))
#            print("{}-{} {}".format(key, value["labelList"], value["propList"]))

    def genNodeTemplates(self, ):
        '''
        Scan the reverse engineered node dictionary and create Node Templates
        GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN
        '''
        # generate node templates
        model = self.gridTemplates.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            genFlag = model.item(row, GENERATE).checkState()
            # is the pattern checked?
            if genFlag == Qt.Unchecked:
                continue
            if self.genNodeTemplate(model, row) == False:
                break
            # uncheck the template
            model.item(row, GENERATE).setCheckState(Qt.Unchecked)
            # refresh the treeview
            self.model.updateTV()

    def genRelTemplates(self, ):
        # generate rel templates
        model = self.gridTemplates_Rel.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            genFlag = model.item(row, GENERATE).checkState()
            # is the pattern checked?
            if genFlag == Qt.Unchecked:
                continue
            if self.genRelTemplate(model, row) == False:
                break
            # uncheck the template
            model.item(row, GENERATE).setCheckState(Qt.Unchecked)
            # refresh the treeview
            self.model.updateTV()

    def genNodeTemplate(self, model, row):

        templateName = model.item(row, TEMPLATENAME).data(Qt.EditRole)
        labelPattern = model.item(row, LABELPATTERN).data(Qt.EditRole)
        # find entry in nodeDict that matches the label pattern in the grid
        # you can't use the template name because the user may have overwritten it
        labelPatternList = None
        for key, value in self.nodeDict.items():
            if str(value["labelList"]) == labelPattern:
                labelPatternList = value["labelList"]
                propertyPatternList = value["propList"]
                propertyDataTypeDict = value.get("propDataType", {})
        # this should never happen
        if labelPatternList == None:
            self.helper.displayErrMsg(
                "Create New Node Template Error",
                "The label pattern {} not found".format(labelPattern))
            self.gridTemplates.setFocus()
            return False
        # make sure there is a template name
        if self.helper.NoTextValueError(
                templateName, "You must supply a name for the Node Template"):
            self.gridTemplates.setFocus()
            return False
        #make sure template name doesn't exist
        index, nodeDict = self.model.getDictByName("Node Template",
                                                   templateName)
        if not nodeDict is None:
            self.helper.displayErrMsg(
                "Create New Node Template Error",
                "The node template {} already exists".format(templateName))
            self.gridTemplates.setFocus()
            return False
#        print("{} {} {}".format(templateName, str(labelPatternList),str(propertyPatternList) ))
        labelList = []
        for label in labelPatternList:
            nodeLbl = [label, Qt.Checked, Qt.Unchecked]
            self.model.newLabel(label)
            labelList.append(nodeLbl)
        propList = []
        for property in propertyPatternList:
            dataType = propertyDataTypeDict.get(property, "Unknown")
            nodeProp = [
                property, dataType, Qt.Unchecked, "", Qt.Unchecked,
                Qt.Unchecked, Qt.Unchecked
            ]
            self.model.newProperty(property, dataType=dataType)
            propList.append(nodeProp)

        # generate the constraints and indexes
        conList = []
        # CONTYPE, CONLBL, CONPROP, CONPROPLIST
        # 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
        nodeTemplateDict = self.model.newNodeTemplate(
            name=templateName,
            labelList=labelList,
            propList=propList,
            conList=conList,
            idxList=idxList,
            desc="Template generated from Reverse Engineering.")

        self.model.modelData["Node Template"].append(nodeTemplateDict)

        return True

    def genRelTemplate(self, model, row):
        '''
        Create a relationship template based on the row in the results grid
        GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, RELPROPERTYPATTERN = range(6)
        '''
        # templateName and relationship name
        templateName = model.item(row, RELTEMPLATENAME).data(Qt.EditRole)
        relName = model.item(row, RELATIONSHIPNAME).data(Qt.EditRole)
        fromTemplate = model.item(row, FROMTEMPLATE).data(Qt.EditRole)
        toTemplate = model.item(row, TOTEMPLATE).data(Qt.EditRole)
        uniqueKey = model.item(row, RELKEY).data(Qt.EditRole)

        #        # get property pattern from relDict since it is an actual list, not a string like what is stored in the grid
        propertyPatternList = self.relDict[uniqueKey]["propList"]
        propertyDataTypeDict = self.relDict[uniqueKey].get("propDataType", {})
        # make sure there is a relationship name
        if self.helper.NoTextValueError(
                relName, "You must supply a name for the Relationship"):
            self.gridTemplates_Rel.setFocus()
            return False
        # make sure there is a template name
        if self.helper.NoTextValueError(
                templateName,
                "You must supply a name for the Relationship Template"):
            self.gridTemplates_Rel.setFocus()
            return False
        #make sure template name doesn't exist
        index, nodeDict = self.model.getDictByName("Relationship Template",
                                                   templateName)
        if not nodeDict is None:
            self.helper.displayErrMsg(
                "Create New Relationship Template Error",
                "The Relationship template {} already exists".format(
                    templateName))
            self.gridTemplates.setFocus()
            return False

#        print("{} {}".format(templateName,str(propertyPatternList) ))
# if its a new rel type then add it to the model
        self.model.newRelationship(relName)
        # properties
        propList = []
        for property in propertyPatternList:
            dataType = propertyDataTypeDict.get(property, "Unknown")
            relProp = [property, dataType, Qt.Unchecked, "", Qt.Unchecked]
            self.model.newProperty(property, dataType=dataType)
            propList.append(relProp)
        # 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=relName)

        relDict = self.model.newRelTemplateDict(
            name=templateName,
            relname=relName,
            propList=propList,
            fromTemplate=fromTemplate,
            toTemplate=toTemplate,
            conList=conList,
            desc="Template generated from Reverse Engineering.")
        self.model.modelData["Relationship Template"].append(relDict)
        return True

    def genRelResult(self, ):
        '''
        Scan the reverse engineered relationship dictionary and create Relationship Template rows in the grid
        '''
        for key in sorted(self.relDict.keys()):
            value = self.relDict[key]
            #            self.addRelResultRow(self.gridTemplates_Rel.model(), Qt.Checked, "{}".format(key), str(value["relName"]), str(value["fromTemplate"]), str(value["toTemplate"]), str(value["propList"]), str(value["count"]) )
            self.addRelResultRow(self.gridTemplates_Rel.model(), Qt.Checked,
                                 str(value["templateName"]),
                                 str(value["relName"]),
                                 str(value["fromTemplate"]),
                                 str(value["toTemplate"]),
                                 str(value["propList"]), str(value["count"]),
                                 key)

    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Slot documentation goes here.
        
        @param button DESCRIPTION
        @type QAbstractButton
        """
        QDialog.accept(self)

#    @pyqtSlot()
#    def on_rbAllNodes_clicked(self):
#        """
#        User selects to scan all nodes so set relationships to scan all
#        """
#        if self.rbAllNodes.isChecked():
#            self.rbAllRels.setChecked(True)
#        else:
#            self.rbAllRels.setChecked(False)

#    @pyqtSlot()
#    def on_rbPercentNodes_clicked(self):
#        """
#        User selects to scan a percentage of nodes so set relationships to scan percentage
#        """
#        if self.rbPercentNodes.isChecked():
#            self.rbPercentRels.setChecked(True)
#        else:
#            self.rbPercentRels.setChecked(False)
#
#    @pyqtSlot()
#    def on_rbAllRels_clicked(self):
#        """
#        User selects to scan all Rels, so set nodes to scan all
#        """
#        if self.rbAllRels.isChecked():
#            self.rbAllNodes.setChecked(True)
#        else:
#            self.rbAllNodes.setChecked(False)
#
#    @pyqtSlot()
#    def on_rbPercentRels_clicked(self):
#        """
#        User selects to scan a percentage of relationships
#        Set nodes to scan percentage
#        """
#        if self.rbPercentRels.isChecked():
#            self.rbPercentNodes.setChecked(True)
#        else:
#            self.rbPercentNodes.setChecked(False)

    @pyqtSlot()
    def on_btnSaveTemplates_clicked(self):
        """
        User requests to Generate the node templates
        """
        self.genNodeTemplates()
        return

    @pyqtSlot(bool)
    def on_cbScanRels_clicked(self, checked):
        """
        The scan relationships checkbox has been clicked
        Check to make sure the scan Nodes relationship has also been clicked.
        You can't scan relationships without having scanned node patterns.
        
        @param checked DESCRIPTION
        @type bool
        """
        if checked:
            self.cbScanNodes.setCheckState(Qt.Checked)

    @pyqtSlot()
    def on_pbUncheck_clicked(self):
        """
        Uncheck all the node templates
        """
        model = self.gridTemplates.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            model.item(row, GENERATE).setCheckState(Qt.Unchecked)

    @pyqtSlot()
    def on_pbCheck_clicked(self):
        """
        check all the node template checkboxes
        """
        model = self.gridTemplates.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            model.item(row, GENERATE).setCheckState(Qt.Checked)

    @pyqtSlot()
    def on_pbUncheck_Rel_clicked(self):
        """
        Uncheck all the relationship templates
        """
        model = self.gridTemplates_Rel.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            model.item(row, GENERATE).setCheckState(Qt.Unchecked)

    @pyqtSlot()
    def on_pbCheck_Rel_clicked(self):
        """
        check all the node template checkboxes
        """
        model = self.gridTemplates_Rel.model()
        numrows = model.rowCount()
        for row in range(0, numrows):
            model.item(row, GENERATE).setCheckState(Qt.Checked)

    @pyqtSlot()
    def on_btnSaveTemplates_Rel_clicked(self):
        """
        User requests to Generate the relationship templates
        """
        self.genRelTemplates()
        return

    @pyqtSlot(bool)
    def on_cbScanNodes_clicked(self, checked):
        """
        The scan nodes checkbox has been clicked
        If they unchecked it display a message and turn it back on.  You always have to scan Nodes
        """
        if not checked:
            self.helper.displayErrMsg("Reverse Engineer",
                                      "You always have to scan the Nodes")
            self.cbScanNodes.setCheckState(Qt.Checked)

    @pyqtSlot()
    def on_btnStop_clicked(self):
        """
        User  clicks the stop button to stop reverse engineering
        """
        self.stopScan = True
        self.displayScanMsg("User Canceled Scan")

    @pyqtSlot(int)
    def on_spinProcessSize_valueChanged(self, p0):
        """
        User changed the process size spin box
        
        @param p0 DESCRIPTION
        @type int
        """
        # update the percents
        self.testScan()

    @pyqtSlot(int)
    def on_spinSkipAmt_valueChanged(self, p0):
        """
        User changed the skip amount spin box
        
        @param p0 DESCRIPTION
        @type int
        """
        #update the percents
        self.testScan()
Exemplo n.º 13
0
class CypherPageWidget(QWidget, Ui_CypherPageWidget):
    """
    Implements a tab on the main UI that provides the schema editor, cypher editor, and file explorer.
    """
    treeViewUpdate = pyqtSignal()

    def __init__(self, parent=None, pageItem=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CypherPageWidget, self).__init__(parent)
        self.pageType = "CYPHER"
        self.settings = QSettings()
        self.parent = parent
        self.pageItem = pageItem

        self.setupUi(self)
        self.initUI()
        self.helper = Helper()
        self.treeViewUpdate.connect(self.populateTree)

        ########################################################################
        # Schema editor setup
        ########################################################################
        self.schemaNeoDriver = NeoDriver(name=self.pageItem.neoConName,
                                         promptPW=self.pageItem.promptPW)

        self.schemaModel = SchemaModel(self, neoDriver=self.schemaNeoDriver)
        self.refreshSchemaModel()
        self.schemaModel.setUpdateTreeViewMethod(
            method=self.on_btnRefresh_clicked)
        self.tvSchema.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvSchema.customContextMenuRequested.connect(self.openMenu)
        self.clearTree()
        self.populateTree()

        # display a default cypher tab
        self.on_btnNew_clicked()

        # display an error message if the schema connection doesn't work
        rc, msg = self.schemaModel.testSchemaConnection()
        if rc == False:
            self.helper.displayErrMsg("Connect Schema",
                                      "The Connection Failed: {}".format(msg))

    def logMsg(self, msg):
        '''
        If logging is active, then log the message
        '''
        if logging:
            logging.info(msg)

##############################################################################
#   UI setups not generated
##############################################################################

    def initUI(self):

        try:
            self.defaultProjPath = self.settings.value("Default/ProjPath")
            if self.defaultProjPath is None:
                self.defaultProjPath = '//'
        except:
            self.defaultProjPath = '//'

        self.currentProjPath = self.defaultProjPath
        self.setUpFileExplorer(self.defaultProjPath)

#########################################################################################
# schema tree view methods
#########################################################################################

    def clearTree(self, ):
        self.editNeo4j.clear()
        self.tvSchema.clear()
        self.tvSchema.setColumnCount(1)
        self.tvSchema.setHeaderLabels(["Schema Items"])
        self.tvSchema.setItemsExpandable(True)

    def populateTree(self, addObject=None):
        # put neocon url in text box above the tree view
        self.editNeo4j.setText("{} - {}".format(
            self.schemaNeoDriver.name, self.schemaNeoDriver.neoDict["URL"]))
        #selected = None
        self.tvSchema.clear()
        self.tvSchema.setColumnCount(1)
        self.tvSchema.setHeaderLabels(["Schema Items"])
        self.tvSchema.setItemsExpandable(True)
        nodeTypes = {}
        parent = self.tvSchema.invisibleRootItem()
        # add tree items
        for item in self.schemaModel.schemaData["TopLevel"]:
            topItem = self.addParent(parent, 0, item, "data")
            nodeTypes[item] = topItem
            for object in sorted(self.schemaModel.schemaData[item],
                                 key=itemgetter('name')):
                #                childItem = self.addChild(topItem, 0, object["name"], "data")
                self.addChild(topItem, 0, object["name"], "data")

        self.tvSchema.resizeColumnToContents(0)

    def addParent(self, parent, column, title, data):
        item = QTreeWidgetItem(parent, [title])
        item.setData(column, Qt.UserRole, data)
        item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
        item.setExpanded(True)
        return item

    def addChild(self, parent, column, title, data):
        item = QTreeWidgetItem(parent, [
            title,
        ])
        item.setData(column, Qt.UserRole, data)
        return item

#####################################################################################
# Context menus for schema tree view
#####################################################################################

    def openMenu(self, position):
        selected = self.tvSchema.currentItem()
        if not (selected is None):
            parent = self.tvSchema.currentItem().parent()
            # top level object menus
            if (parent is None):
                menu = QMenu()
                if (selected.data(0, 0) == "User"):
                    self.addMenu(menu=menu, text="New...", method=self.newUser)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Role"):
                    self.addMenu(menu=menu, text="New...", method=self.newRole)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Index"):
                    self.addMenu(menu=menu,
                                 text="New...",
                                 method=self.newIndex)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Relationship Property Exists"):
                    self.addMenu(menu=menu,
                                 text="New...",
                                 method=self.newConstraintRelPropExists)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Node Key"):
                    self.addMenu(menu=menu,
                                 text="New...",
                                 method=self.newConstraintNodeKey)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Node Property Unique"):
                    self.addMenu(menu=menu,
                                 text="New...",
                                 method=self.newConstraintNodePropUnique)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                if (selected.data(0, 0) == "Node Property Exists"):
                    self.addMenu(menu=menu,
                                 text="New...",
                                 method=self.newConstraintNodePropExists)
                    self.addMenu(menu=menu,
                                 text="Generate Create(s)...",
                                 method=self.genCreates)
                    self.addMenu(menu=menu,
                                 text="Generate Drop(s)...",
                                 method=self.genDrops)
                # pop up the menu
                menu.exec_(self.tvSchema.mapToGlobal(position))

            # object instance pop up menus
            if not (parent is None):
                menu = QMenu()
                if parent.data(0, 0) == "Label":
                    self.addMenu(menu=menu,
                                 text="Generate Match...",
                                 method=self.matchSchemaObject)
                if parent.data(0, 0) == "Property":
                    self.addMenu(menu=menu,
                                 text="Generate Match...",
                                 method=self.matchSchemaObject)
                if parent.data(0, 0) == "Relationship":
                    self.addMenu(menu=menu,
                                 text="Generate Match...",
                                 method=self.matchSchemaObject)
                if parent.data(0, 0) == "User":
                    self.addMenu(menu=menu,
                                 text="Edit...",
                                 method=self.editUser)
                    self.addMenu(menu=menu,
                                 text="Drop...",
                                 method=self.dropSchemaObject)
                if parent.data(0, 0) == "Role":
                    self.addMenu(menu=menu,
                                 text="Edit...",
                                 method=self.editRole)
                    self.addMenu(menu=menu,
                                 text="Drop...",
                                 method=self.dropSchemaObject)
                if parent.data(0, 0) == "Index":
                    self.addMenu(menu=menu,
                                 text="Drop...",
                                 method=self.dropSchemaObject)
                    self.addMenu(menu=menu,
                                 text="Generate Match...",
                                 method=self.matchSchemaObject)
                if parent.data(0, 0) in [
                        "Node Key", "Node Property Unique",
                        "Node Property Exists", "Relationship Property Exists"
                ]:
                    self.addMenu(menu=menu,
                                 text="Drop...",
                                 method=self.dropSchemaObject)
                    self.addMenu(menu=menu,
                                 text="Generate Match...",
                                 method=self.matchSchemaObject)
                # pop up the menu
                menu.exec_(self.tvSchema.mapToGlobal(position))

    def addMenu(self, menu=None, text=None, method=None):
        menuAction = menu.addAction(text)
        menuAction.triggered.connect(method)

##############################################################################
#   schema menu options
##############################################################################

    def resetPassword(self, ):
        """
        User requests the change user password from main menu
        """
        d = ChangeUserPW(parent=self)
        if d.exec_():
            pass

    def newUser(self, ):
        '''
        prompt for the new user name and bring up the user editor dialog box
        '''
        text, ok = QInputDialog.getText(self, 'Create New User',
                                        'Enter the User Name:')
        if ok:
            # make sure they entered a valid user name
            # see if the user name already exists
            if text in [
                    object["name"]
                    for object in self.schemaModel.schemaData["User"]
            ]:
                self.helper.displayErrMsg(
                    "Create New User",
                    "The user {} already exists. Cannot create new user.".
                    format(text))
            else:
                # create the user with requires password change on first login.
                QApplication.setOverrideCursor(Qt.WaitCursor)
                rc, msg = self.schemaModel.createUser(text)
                if rc:
                    self.schemaModel.updateTV()
                    QApplication.restoreOverrideCursor()
                    #show the edit user dialog box
                    d = EditUserDlg(self, userName=text, mode="NEW")
                    if d.exec_():
                        self.on_btnRefresh_clicked()

                else:
                    self.helper.displayErrMsg("Create User Error", msg)
                    QApplication.restoreOverrideCursor()

    def editUser(self, ):
        '''
        edit an existing user
        '''
        d = EditUserDlg(self,
                        userName=self.tvSchema.currentItem().data(0, 0),
                        mode="EDIT")
        if d.exec_():
            self.on_btnRefresh_clicked()

    def newRole(self, ):
        '''
        prompt for the new user name and bring up the user editor dialog box
        '''
        text, ok = QInputDialog.getText(self, 'Create New Role',
                                        'Enter the Role Name:')
        if ok:
            # make sure they entered a valid Role Name

            # see if the user name already exists
            if text in [
                    object["name"]
                    for object in self.schemaModel.schemaData["Role"]
            ]:
                self.helper.displayErrMsg(
                    "Create New Role",
                    "The role {} already exists. Cannot create new role.".
                    format(text))
            else:
                # create the role if needed
                QApplication.setOverrideCursor(Qt.WaitCursor)
                rc, msg = self.schemaModel.createRole(text)
                if rc:
                    self.schemaModel.updateTV()
                    QApplication.restoreOverrideCursor()
                    d = EditRoleDlg(self, roleName=text, mode="NEW")
                    if d.exec_():
                        self.on_btnRefresh_clicked()
                else:
                    self.helper.displayErrMsg("Create Role Error", msg)
                    QApplication.restoreOverrideCursor()

    def editRole(self, ):
        '''
        edit an existing role
        '''
        d = EditRoleDlg(self,
                        roleName=self.tvSchema.currentItem().data(0, 0),
                        mode="EDIT")
        if d.exec_():
            self.on_btnRefresh_clicked()

        return

    def newIndex(self, ):
        d = CreateIndexDlg(self)
        if d.exec_():
            self.on_btnRefresh_clicked()

    def newConstraintNodeKey(self, ):
        d = ConstraintNodeKeyDlg(self)
        if d.exec_():
            self.on_btnRefresh_clicked()

    def newConstraintNodePropExists(self, ):
        d = ConstraintNodePropExistsDlg(self)
        if d.exec_():
            self.on_btnRefresh_clicked()

    def newConstraintNodePropUnique(self, ):
        d = ConstraintNodePropUniqueDlg(self)
        if d.exec_():
            self.on_btnRefresh_clicked()

    def newConstraintRelPropExists(self, ):
        d = ConstraintRelPropExistsDlg(self)
        if d.exec_():
            self.on_btnRefresh_clicked()

    def dropSchemaObject(self, type):
        objectName = self.tvSchema.currentItem().data(0, 0)
        objectType = self.tvSchema.currentItem().parent().data(0, 0)
        d = DropObjectDlg(self, objectType=objectType, objectName=objectName)
        if d.exec_():
            self.on_btnRefresh_clicked()
        return

    def matchSchemaObject(self, ):
        objectName = self.tvSchema.currentItem().data(0, 0)
        objectType = self.tvSchema.currentItem().parent().data(0, 0)
        cypher = self.schemaModel.genMatchFromConstraint(objectName=objectName,
                                                         objectType=objectType)
        # add the comment
        editCypher = "// Generated by NodeEra from schema connection: {} \n// Match {} schema object: {} \n{}".format(
            self.pageItem.neoConName, objectType, objectName, cypher)
        self.addCypherEditorTab(fileName=None,
                                fileText=editCypher,
                                mode=MODENEW)

    def genCreates(self, ):
        '''
        display the generate schema dialog with the object selected, and the generate create only option selected
        '''
        objectName = self.tvSchema.currentItem().data(0, 0)
        d = GenerateSchemaDlg(self.parent,
                              objectType=objectName,
                              genOption="CreateOnly")
        if d.exec_():
            pass

    def genDrops(self, ):
        '''
        display the generate schema dialog with the object selected, and the generate drop only option selected
        '''
        objectName = self.tvSchema.currentItem().data(0, 0)
        d = GenerateSchemaDlg(self.parent,
                              objectType=objectName,
                              genOption="DropOnly")
        if d.exec_():
            pass

##############################################################################
#   signal slots
##############################################################################

    @pyqtSlot(int)
    def on_tabCypher_tabCloseRequested(self, index):
        """
        Process request to close a schema tab
        
        @param index DESCRIPTION
        @type int
        """
        #        print("cypher tab index {} request close".format(str(index)))
        if self.tabCypher.widget(index).tabType == "CYPHER":
            self.tabCypher.widget(index).close()
            self.tabCypher.removeTab(index)

##############################################################################
#   main buttons
##############################################################################

    @pyqtSlot()
    def on_btnUndo_clicked(self):
        """
        Slot documentation goes here.
        """
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                self.tabCypher.currentWidget().editor.SendScintilla(
                    QsciScintilla.SCI_UNDO)

    @pyqtSlot()
    def on_btnRedo_clicked(self):
        """
        Slot documentation goes here.
        """
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                self.tabCypher.currentWidget().editor.SendScintilla(
                    QsciScintilla.SCI_REDO)

    @pyqtSlot()
    def on_btnComment_clicked(self):
        """
        comment the selected lines.
        if no lines are selected, then comment the line where the cursor is.
        adds a single line comment "//" to the very beginning of each selected line.
        """
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":

                startPosition = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONSTART)
                endPosition = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONEND)
                startLine = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION,
                                       startPosition)
                endLine = self.tabCypher.currentWidget().editor.SendScintilla(
                    QsciScintilla.SCI_LINEFROMPOSITION, endPosition)
                if startPosition != endPosition:
                    # if there is a selection, then comment every line in the selection
                    for line in range(startLine, endLine + 1):
                        self.tabCypher.currentWidget().editor.SendScintilla(
                            QsciScintilla.SCI_GOTOLINE, line)
                        self.tabCypher.currentWidget().editor.insert("//")
                else:
                    # there is no selection, so comment the line where the cursor is
                    self.tabCypher.currentWidget().editor.SendScintilla(
                        QsciScintilla.SCI_GOTOLINE, startLine)
                    self.tabCypher.currentWidget().editor.insert("//")

    @pyqtSlot()
    def on_btnUnComment_clicked(self):
        """
        uncomment the selected lines, if they have a comment. 
        only supports removing a single line comment "//" at the very beginning of the line.
        """
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                startPosition = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONSTART)
                endPosition = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONEND)
                startLine = self.tabCypher.currentWidget(
                ).editor.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION,
                                       startPosition)
                endLine = self.tabCypher.currentWidget().editor.SendScintilla(
                    QsciScintilla.SCI_LINEFROMPOSITION, endPosition)
                if startPosition != endPosition:
                    # if there is a selection, then comment every line in the selection
                    for line in range(startLine, endLine + 1):
                        self.tabCypher.currentWidget().editor.SendScintilla(
                            QsciScintilla.SCI_GOTOLINE, line)
                        currentPos = self.tabCypher.currentWidget(
                        ).editor.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS)
                        self.tabCypher.currentWidget().editor.SendScintilla(
                            QsciScintilla.SCI_SETSELECTIONSTART, currentPos)
                        self.tabCypher.currentWidget().editor.SendScintilla(
                            QsciScintilla.SCI_SETSELECTIONEND, currentPos + 2)
                        # see if its a //
                        if self.tabCypher.currentWidget().editor.selectedText(
                        ) == "//":
                            self.tabCypher.currentWidget(
                            ).editor.removeSelectedText()
                        else:
                            # this will remove the selection
                            self.tabCypher.currentWidget(
                            ).editor.SendScintilla(QsciScintilla.SCI_GOTOLINE,
                                                   startLine)
                else:
                    # there is no selection, so comment the line where the cursor is
                    self.tabCypher.currentWidget().editor.SendScintilla(
                        QsciScintilla.SCI_GOTOLINE, startLine)
                    currentPos = self.tabCypher.currentWidget(
                    ).editor.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS)
                    self.tabCypher.currentWidget().editor.SendScintilla(
                        QsciScintilla.SCI_SETSELECTIONSTART, currentPos)
                    self.tabCypher.currentWidget().editor.SendScintilla(
                        QsciScintilla.SCI_SETSELECTIONEND, currentPos + 2)
                    # see if its a //
                    if self.tabCypher.currentWidget().editor.selectedText(
                    ) == "//":
                        self.tabCypher.currentWidget(
                        ).editor.removeSelectedText()
                    else:
                        # this will remove the selection
                        self.tabCypher.currentWidget().editor.SendScintilla(
                            QsciScintilla.SCI_GOTOLINE, startLine)

    @pyqtSlot()
    def on_btnRefresh_clicked(self):
        """
        This refreshes the schema treeview
        """
        self.refreshSchemaModel()

    @pyqtSlot()
    def refreshSchemaModel(self):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.schemaModel.refreshModel()
        if rc == False:
            self.helper.displayErrMsg("Refresh Schema", msg)

        self.populateTree()
        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnOpen_clicked(self):
        """
        Slot documentation goes here.
        """
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.ExistingFile)
        dlg.setAcceptMode(QFileDialog.AcceptOpen)
        dlg.setNameFilters([
            "Cypher Query (*.cyp *.cypher)", "Cypher Query (*.cyp)",
            "Cypher Query (*.cypher)", "all files (*.*)"
        ])
        dlg.setDirectory(self.settings.value("Default/ProjPath"))
        if dlg.exec_():
            fileNames = dlg.selectedFiles()
            if fileNames:
                fileName = fileNames[0]
                self.addCypherEditorTab(fileName=fileName, mode=MODEEDIT)

    @pyqtSlot()
    def on_btnNew_clicked(self):
        """
        Add a new CypherEditnGrid tab
        """
        # update unnamed file counter
        global unNamedFileCounter
        unNamedFileCounter = unNamedFileCounter + 1
        # Add a new tab with a CypherEditnGrid widget
        newCypherEditnGrid = CypherEditGridWidget(parent=self, mode=MODENEW)
        index = self.tabCypher.addTab(
            newCypherEditnGrid,
            "{}".format("Unsaved-0{}".format(unNamedFileCounter)))
        newCypherEditnGrid.tabIndex = index
        self.tabCypher.setCurrentIndex(index)

    @pyqtSlot()
    def on_btnSave_clicked(self):
        """
        Slot documentation goes here.
        bool QsciScintilla::isModified	(		)	const
        Returns true if the text has been modified.
        """
        #get current tab
        #        print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName))
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                self.tabCypher.currentWidget().save()

    @pyqtSlot()
    def on_btnSaveAs_clicked(self):
        """
        Slot documentation goes here.
        """
        #        print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName))
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                self.tabCypher.currentWidget().saveAs()

    @pyqtSlot()
    def on_btnClose_clicked(self):
        """
        Slot documentation goes here.
        """
        #        print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName))
        if self.tabCypher.count() > 0:
            if self.tabCypher.currentWidget().tabType == "CYPHER":
                self.on_tabCypher_tabCloseRequested(
                    self.tabCypher.currentIndex())

    @pyqtSlot()
    def on_btnZoomIn_clicked(self):
        """
        iterate thru all the tab widgets and tell them to zoom in
        """
        for index in range(self.tabCypher.count()):
            self.tabCypher.widget(index).zoomIn()

    @pyqtSlot()
    def on_btnZoomOut_clicked(self):
        """
        iterate thru all the tab widgets and tell them to zoom out
        """
        for index in range(self.tabCypher.count()):
            self.tabCypher.widget(index).zoomOut()

##############################################################################
#   support functions
##############################################################################

    def addCypherEditorTab(self, fileName=None, fileText=None, mode=None):

        if fileName is None:
            # update unnamed file counter
            global unNamedFileCounter
            unNamedFileCounter = unNamedFileCounter + 1
            fileName = "{}".format("Unsaved-0{}".format(unNamedFileCounter))
        tab = CypherEditGridWidget(parent=self,
                                   fileName=fileName,
                                   fileText=fileText,
                                   mode=mode)
        head, tail = ntpath.split(QFileInfo(fileName).fileName())
        self.tabCypher.addTab(tab, "{}".format(tail))
        self.tabCypher.setCurrentWidget(tab)

    def save(self, ):
        '''the parent calls this method to tell the cypherPageWidget to save all it's open cyphereditngrid tabs
        '''
        for tabIndex in range(0, self.tabCypher.count()):
            tab = self.tabCypher.widget(tabIndex)
            # switch focus to the tab
            self.tabCypher.setCurrentIndex(tabIndex)
            # tell the tab to close
            tab.close()

#####################################################################################################
## file system explorer related methods
#####################################################################################################

    def setUpFileExplorer(self, folder):
        tvFileModel = QFileSystemModel()
        tvFileModel.setRootPath(folder)
        self.tvFileSystem.setModel(tvFileModel)
        self.tvFileSystem.setRootIndex(tvFileModel.index(folder))
        self.tvFileSystem.hideColumn(1)
        self.tvFileSystem.hideColumn(2)
        self.tvFileSystem.hideColumn(3)
        self.tvFileSystem.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFileSystem.customContextMenuRequested.connect(
            self.fileSystemMenu)
        #        self.tvFileSystem.doubleClicked.connect(self.tvFileSystem_doubleClicked)

        self.setPathDisplay(folder)

    def setPathDisplay(self, pathName):
        self.editPath.clear()
        self.editPath.setText(pathName)
        self.editPath.setToolTip(pathName)

    @pyqtSlot()
    def on_btnPickPath_clicked(self):
        """
        The user selects a new folder as the top level of the file system explorer
        """
        folder = str(
            QFileDialog.getExistingDirectory(self, "Select Directory",
                                             self.currentProjPath))
        if folder:
            self.setUpFileExplorer(folder)
            self.currentProjPath = folder

##########################################################################################
## file system tree view methods
##########################################################################################

    def fileSystemMenu(self, position):
        #get a qmodelindex
        index = self.tvFileSystem.selectedIndexes()[0]
        if not (index is None):
            #            print("{}-{}".format(index.row(), index.column()))
            # get a QFileInfo
            fileInfo = index.model().fileInfo(index)
            if fileInfo.isFile():
                if fileInfo.suffix().upper() == "MDL":
                    #                    print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName()))
                    menu = QMenu()
                    editCypherAction = menu.addAction("Open Model")
                    editCypherAction.triggered.connect(
                        self.openFileSystemModel)
                    menu.exec_(self.tvFileSystem.mapToGlobal(position))
                    return
                if fileInfo.suffix().upper() == "CYP":
                    #                    print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName()))
                    menu = QMenu()
                    editCypherAction = menu.addAction("Edit Cypher")
                    editCypherAction.triggered.connect(
                        self.openFileSystemCypher)
                    menu.exec_(self.tvFileSystem.mapToGlobal(position))
                    return

    def openFileSystemCypher(self, ):
        index = self.tvFileSystem.selectedIndexes()[0]
        if not (index is None):
            # get a QFileInfo
            fileInfo = index.model().fileInfo(index)
            fileName = fileInfo.absoluteFilePath()
            self.logMsg("Open Cypher File: {}".format(fileName))
            self.addCypherEditorTab(fileName=fileName, mode=MODEEDIT)

    def openFileSystemModel(self, ):
        index = self.tvFileSystem.selectedIndexes()[0]
        if not (index is None):
            # get a QFileInfo
            fileInfo = index.model().fileInfo(index)
            fileName = fileInfo.absoluteFilePath()
            self.logMsg("Open Project File: {}".format(fileName))
            self.parent.loadProject(fileName=fileName)

    @pyqtSlot(QModelIndex)
    def on_tvFileSystem_doubleClicked(self, index):
        """
        User doubleclicks on the file system tree view
        - if it is a .cyp file then load it into a cypher tab
        - if is is a .mdl file then load it into a project tab
        
        @param index DESCRIPTION
        @type QModelIndex
        """
        index = self.tvFileSystem.selectedIndexes()[0]
        #        print("{}-{}".format(index.row(), index.column()))
        fileInfo = index.model().fileInfo(index)
        if fileInfo.isFile():
            if fileInfo.suffix().upper() == "MDL":
                #                print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName()))
                self.openFileSystemModel()
            if fileInfo.suffix().upper() == "CYP":
                self.openFileSystemCypher()

    @pyqtSlot(QModelIndex)
    def on_tvSchema_doubleClicked(self, index):
        """
        User doubleclicks on the schema tree view
        - if it is a user then show user editor
        - if is is a role then show role editor
        
        @param index DESCRIPTION
        @type QModelIndex
        """
        index = self.tvSchema.selectedIndexes()[0]
        #        print("{}-{}".format(index.row(), index.column()))

        selected = self.tvSchema.currentItem()
        if not (selected is None):
            parent = self.tvSchema.currentItem().parent()
            if not (parent is None):
                #                print(parent.data(0, 0))
                #                print(self.tvSchema.currentItem().data(0,0))
                if parent.data(0, 0) == "User":
                    self.editUser()
                if parent.data(0, 0) == "Role":
                    self.editRole()
Exemplo n.º 14
0
class CypherEditGridWidget(QWidget, Ui_cypherEditGridWidget):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, fileName=None, fileText=None, mode=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CypherEditGridWidget, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.settings = QSettings()
        self.initUI()
        self.initScintilla()
        self.helper = Helper()
        self.mode = mode
        self.tabType = "CYPHER"
        self.tabName = fileName
        self.tabIndex = None  # this is the index into the tabWidget of the tab this widget is on
        self.fileName = fileName
        self.fileText = fileText
        self.resultSet = None

        # create a neocon object for this file tab
        self.neoDriver = NeoDriver(name=self.parent.pageItem.neoConName,
                                   promptPW=self.parent.pageItem.promptPW)

        # add the data grid widget.
        self.dataGridGeneric = DataGridGeneric()
        self.dataGrid = DataGridWidget(self,
                                       neoCon=self.neoDriver,
                                       genCypher=self.dataGridGeneric)
        self.nodeGridLayout = QVBoxLayout(self.frmDataGrid)
        self.nodeGridLayout.setObjectName("nodeGridLayout")
        self.nodeGridLayout.setContentsMargins(1, 1, 1, 1)
        self.nodeGridLayout.setSpacing(1)
        self.nodeGridLayout.addWidget(self.dataGrid)

        if self.mode == MODENEW:
            if not self.fileText is None:
                self.loadText()

        if self.mode == MODEEDIT:
            self.loadFile()

        # position the splitter
        self.show(
        )  # you have to do this to force all the widgets sizes to update
        half = int((self.frmEditnGrid.height() / 2))
        self.splitter.setSizes([half, half])

    def logMsg(self, msg):

        if logging:
            logging.info(msg)

    def initUI(self, ):
        #initialize state of commit buttons and dropdown
        self.btnCommit.setEnabled(False)
        self.btnRollBack.setEnabled(False)
        self.cmbAutoCommit.setCurrentIndex(0)

    def initScintilla(self):
        # add and initialize the control to self.frmEditor
        self.editor = QsciScintilla()
        self.editor.setLexer(None)
        self.editor.setUtf8(True)  # Set encoding to UTF-8
        self.editor.setWrapMode(QsciScintilla.WrapNone)
        self.editor.setEolVisibility(False)
        self.editor.setIndentationsUseTabs(False)
        self.editor.setTabWidth(4)
        self.editor.setIndentationGuides(True)
        self.editor.setAutoIndent(True)
        self.editor.setMarginType(0, QsciScintilla.NumberMargin)
        self.editor.setMarginWidth(0, "00000")
        self.editor.setMarginsForegroundColor(QColor("#ffffffff"))
        self.editor.setMarginsBackgroundColor(QColor("#00000000"))
        self.verticalLayout_2.addWidget(self.editor)
        # setup the lexer
        self.lexer = CypherLexer(self.editor)
        self.editor.setLexer(self.lexer)
        self.setScintillaFontSize()
        self.editor.SendScintilla(self.editor.SCI_GETCURRENTPOS, 0)
        self.editor.setCaretForegroundColor(QColor("#ff0000ff"))
        self.editor.setCaretLineVisible(True)
        self.editor.setCaretLineBackgroundColor(QColor("#1f0000ff"))
        self.editor.setCaretWidth(2)
        self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

    def setScintillaFontSize(self, ):
        # set font size to value saved in settings
        try:
            fontSize = int(self.settings.value("Lexer/FontSize", "10"))
        except:
            fontSize = 10
        finally:
            for style in range(5):
                self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE,
                                          style, fontSize)
            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34,
                                      fontSize)
            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35,
                                      fontSize)

#####################################################################################
# methods related to the cypher file
#####################################################################################

    def loadText(self, ):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.editor.append(self.fileText)
        self.editor.setModified(True)
        QApplication.restoreOverrideCursor()

    def loadFile(self, ):
        file = QFile(self.fileName)
        if not file.open(QFile.ReadOnly | QFile.Text):
            QMessageBox.warning(
                self, "NodeMaker", "Cannot read file %s:\n%s." %
                (self.fileName, file.errorString()))
            return False

        instr = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.editor.append(instr.readAll())
        self.editor.setModified(False)
        QApplication.restoreOverrideCursor()

    def save(self, ):
        if self.mode == MODENEW:
            self.saveAs()
        else:
            self.saveIt()

    def saveAs(self, ):
        # first test to see if the file has changed

        # get filename to save as
        #
        dlg = QFileDialog()
        dlg.setAcceptMode(QFileDialog.AcceptSave)
        dlg.setDefaultSuffix("cyp")
        dlg.setNameFilters([
            "Cypher Query (*.cyp *.cypher)", "Cypher Query (*.cyp)",
            "Cypher Query (*.cypher)", "all files (*.*)"
        ])
        dlg.setDirectory(self.parent.settings.value("Default/ProjPath"))
        if dlg.exec_():
            fileNames = dlg.selectedFiles()
            if fileNames:
                self.fileName = fileNames[0]
                # save the file
                self.saveIt()

    def saveIt(self, ):
        file = QFile(self.fileName)
        if not file.open(QFile.WriteOnly | QFile.Text):
            QMessageBox.warning(
                self, "NodeEra", "Cannot write file %s:\n%s." %
                (self.fileName, file.errorString()))
            return

        outstr = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        outstr << self.editor.text()
        head, tail = ntpath.split(QFileInfo(self.fileName).fileName())
        self.parent.tabCypher.setTabText(self.parent.tabCypher.currentIndex(),
                                         tail)
        self.mode = MODEEDIT
        QApplication.restoreOverrideCursor()

    def close(self, ):
        #        print("editngrid close {}".format(self.fileName))
        # see if there is an open transaction and cancel the close
        self.checkOpenTxn()
        # see if text has changed and save the file if the user wants to
        if self.editor.isModified():
            # see if the user wants to save it
            if self.fileName is None:
                # these are unsaved cypher files so they have no filename yet
                displayName = self.parent.tabCypher.tabText(self.tabIndex)
            else:
                displayName = self.fileName
            if self.helper.saveChangedObject("Cypher File", displayName):
                self.save()
        return True

##############################################################
# Button methods
##############################################################

    @pyqtSlot()
    def on_btnRun_clicked(self):
        """
        Run the query at the cursor.
        """
        self.runFileCursor()

    def runFileCursor(self):

        self.logMsg("User requests run Cypher in Cursor")
        # parse the text editor and get the index to the cypher command the cursor is pointing at
        currentCypherIndex = self.getSelectedCypher()
        # check if cursor in a query
        if currentCypherIndex is None:
            self.helper.displayErrMsg(
                "Run Query",
                "You must position cursor within the Cypher query.")
            QApplication.restoreOverrideCursor()
            return

        # get the cypher statement to be executed
        startOffset = self.cypherList[currentCypherIndex][0]
        self.dataGrid.cypher = self.cypherList[currentCypherIndex][1]

        # prompt the user for parameter values if any
        userCanceled = False
        if len(self.cypherParms[currentCypherIndex][1]) > 0:
            #            print("Parms:{}".format(self.cypherParms[currentCypherIndex][1]))
            # display dialog to gather parms
            d = CypherParmEntryDlg(
                parent=self, parms=self.cypherParms[currentCypherIndex][1])
            if d.exec_():
                self.dataGrid.parmData = d.parmDict
            else:
                userCanceled = True
                self.dataGrid.parmData = None
#            print("Parm Dictionary:{}".format(self.dataGrid.parmData))
        else:
            self.dataGrid.parmData = None

        # see if the user canceled the query rather than enter parameters
        if userCanceled:
            self.helper.displayErrMsg("Run Query", "User Canceled Query.")
            QApplication.restoreOverrideCursor()
            return

        # make sure the cypher is not just spaces, or nothing but a semicolon
        if (self.dataGrid.cypher.isspace() or len(self.dataGrid.cypher) == 0
                or self.dataGrid.cypher.strip() == ";"):
            self.helper.displayErrMsg(
                "Run Query",
                "You must position cursor within the Cypher query.")
        else:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            self.dataGrid.refreshGrid()
            QApplication.restoreOverrideCursor()
        # see if there was a syntax error and position cursor
        try:
            offset = self.dataGrid.neoCon.cypherLogDict["offset"]
            if offset > -1:
                self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS,
                                          offset + startOffset)
        except:
            pass
        finally:
            ###  hack needed on mac os to force scintilla to show cursor and highlighted line
            self.helper.displayErrMsg("Run Query With Cursor",
                                      "Query Complete")

    def getSelectedCypher(self):
        '''
        Build a list of cypher commands from the text in the editor
        '''
        # get position of cursor which is a zero based offset, it seems to return zero if editor hasn't been clicked on yet
        try:
            position = self.editor.SendScintilla(
                QsciScintilla.SCI_GETCURRENTPOS, 0)
        except:
            position = 0
        # initialize cypherList
        self.cypherList = []
        self.cypherParms = []
        parmList = []
        currentCypherIndex = None
        # get the full text from the editor
        text = self.editor.text()
        # make sure there is something in the text
        if len(text) < 1:
            return currentCypherIndex

        #	Walk through all the characters in text, and store start offset and end offset of each command
        startOffset = 0
        endOffset = 0
        foundCypher = False  # tracks if we have found at least one character that is potentially a non-comment cypher command
        inComment = False  # tracks if we're looking at comment characters
        inParm = False  # tracks if we're looking at parameter characters
        inCypher = False  # tracks if we've found a non comment character while scanning
        newParm = ""
        for chrCtr in range(0, len(text)):
            #            print("before: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher))

            # see if we're in a comment ( this doesn't work for multiline comments)
            if chrCtr + 1 < len(text) and text[chrCtr] == '/':
                inParm = False
                if text[chrCtr + 1] == "/":
                    inComment = True
                    inCypher = False
            # see if end of line
            elif ord(text[chrCtr]) in [13, 10]:
                inParm = False
                if chrCtr + 1 < len(text):
                    if not text[chrCtr + 1] in [13, 10]:
                        # end of line ends the comment
                        inComment = False

            elif text[chrCtr] == "$":
                if not inComment:
                    foundCypher = True
                    inParm = True
            elif text[chrCtr] == " ":
                if not inComment:
                    foundCypher = True
                inParm = False
            elif (text[chrCtr] == ";" and inComment == False):
                foundCypher = True
                inParm = False
                endOffset = chrCtr
                # save each command in the list
                self.cypherList.append(
                    [startOffset, text[startOffset:endOffset + 1]])
                self.cypherParms.append([startOffset, parmList])
                parmList = []
                # HAPPY PATH - see if this is the command where the cursor is located
                if (position >= startOffset and position <= endOffset):
                    currentCypherIndex = len(self.cypherList) - 1
                # set the next starting offset
                startOffset = chrCtr + 1
                endOffset = startOffset
            elif inComment == False:
                foundCypher = True
                inCypher = True

            if inParm:
                newParm = newParm + text[chrCtr]
            else:
                if len(newParm) > 0:
                    #                    print("Parameter: {}".format(newParm))
                    parmList.append(newParm)
                    newParm = ""

#            print("after: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher))

# at this point all characters have been processed, must deal with edge cases, no final semicolon etc
        if len(
                self.cypherList
        ) == 0:  # we never found a semi colon so the entire file is one cypher statement
            # return the entire text file
            self.cypherList.append([0, text])
            self.cypherParms.append([0, parmList])
            parmList = []

            currentCypherIndex = 0
        else:  # we found some characters after the last semi colon.
            lastCypher = ""
            try:
                lastCypher = text[startOffset:len(text)]
                if lastCypher.isspace(
                ) == True:  # if there is only whitespace after the last semicolon then return the last found cypher
                    if currentCypherIndex is None:
                        currentCypherIndex = len(self.cypherList) - 1
                elif len(
                        lastCypher
                ) < 1:  # there are no characters after the last semicolon, but cursor is positioned past it then return the last found cypher
                    if currentCypherIndex is None:
                        currentCypherIndex = len(self.cypherList) - 1
                elif inCypher == False and foundCypher == False:  # none of the characters are outside a comment so return last found cypher
                    if currentCypherIndex is None:
                        currentCypherIndex = len(self.cypherList) - 1
                else:
                    self.cypherList.append(
                        [startOffset, lastCypher]
                    )  # since some characters are present, add them as the last cypher command
                    self.cypherParms.append([startOffset, parmList])
                    parmList = []

                    if currentCypherIndex is None:
                        currentCypherIndex = len(self.cypherList) - 1
            except:
                if currentCypherIndex is None:
                    currentCypherIndex = len(
                        self.cypherList
                    ) - 1  # return the last cypher command found if any error

#        print("cypher list: {}".format(self.cypherList))
        return currentCypherIndex

    @pyqtSlot()
    def on_btnRunScript_clicked(self):
        """
        this button will run all the cypher commands in the file one at a time.
        """

        self.runFileAsScript()

    def runFileAsScript(self, ):
        '''
        run each cypher command in the file one at a time.
        '''
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests run Cypher file as a script")
        # parse the text editor into cypher commands, we don't care which one the cursor is in
        self.getSelectedCypher()
        if len(self.cypherList) < 1:
            self.helper.displayErrMsg(
                "Run File As Script", "The file has no cypher commands in it.")
        for cypherCmd in self.cypherList:
            # this is the starting offset of the cypher command in the entire file
            cypherOffset = cypherCmd[0]
            self.dataGrid.cypher = cypherCmd[1]
            # set editor selection to the cypher command
            self.editor.SendScintilla(QsciScintilla.SCI_SETSEL, cypherOffset,
                                      cypherOffset + len(self.dataGrid.cypher))
            # skip any cypher is not just spaces, or nothing but a semicolon
            if (self.dataGrid.cypher.isspace()
                    or len(self.dataGrid.cypher) == 0
                    or self.dataGrid.cypher.strip() == ";"):
                #self.helper.displayErrMsg("Run Query",  "You must position cursor within the Cypher query.")
                pass
            else:
                self.dataGrid.refreshGrid()
                QApplication.processEvents()
            # see if there was a syntax error and position cursor
            try:
                offset = self.dataGrid.neoCon.cypherLogDict["offset"]
                if offset > -1:
                    self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS,
                                              offset + cypherOffset)
            except:
                pass
            # set editor selection to the end of the file
            self.editor.SendScintilla(QsciScintilla.SCI_SETSEL,
                                      len(self.editor.text()),
                                      len(self.editor.text()))

        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnCommit_clicked(self):
        """
        User clicks on the Commit button.  Commit the TXN.
        """
        self.doCommit()

    def doCommit(self):
        self.logMsg("User request Commit the transaction")
        if not self.neoDriver is None:
            rc, msg = self.neoDriver.commitTxn()
            self.logMsg("Commit Complete - {}".format(msg))

    @pyqtSlot()
    def on_btnRollBack_clicked(self):
        """
        User clicks on the Rollback button.  Rollback the TXN.
        """
        self.doRollBack()

    def doRollBack(self):
        self.logMsg("User request Rollback the transaction")
        if not self.neoDriver is None:
            rc, msg = self.neoDriver.rollbackTxn()
            self.logMsg("Rollback Complete - {}".format(msg))

    def zoomIn(self, ):
        """
        increase Font Size
        """
        #        self.editor.SendScintilla(QsciScintilla.SCI_ZOOMIN)
        #        currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0)
        #        self.settings.setValue("Lexer/FontSize", currentFontSize)
        # get style 0 font size - all styles use same size
        currentFontSize = self.editor.SendScintilla(
            QsciScintilla.SCI_STYLEGETSIZE, 0)
        currentFontSize = currentFontSize + 2
        if currentFontSize < 24:
            self.settings.setValue("Lexer/FontSize", currentFontSize)
            for style in range(255):
                self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE,
                                          style, currentFontSize)
#            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize)
#            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize)

    def zoomOut(self, ):
        """
        decrease font size
        """
        #        self.editor.SendScintilla(QsciScintilla.SCI_ZOOMOUT)
        #        currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0)
        #        self.settings.setValue("Lexer/FontSize", currentFontSize)

        # get style 0 font size - all styles use same size
        currentFontSize = self.editor.SendScintilla(
            QsciScintilla.SCI_STYLEGETSIZE, 0)
        currentFontSize = currentFontSize - 2
        if currentFontSize > 4:
            self.settings.setValue("Lexer/FontSize", currentFontSize)
            for style in range(255):  # was 5
                self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE,
                                          style, currentFontSize)
#            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize)
#            self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize)

    def checkOpenTxn(self):
        '''
        check if current txn is still open.  
        if autocommit is false then ask the user to commit or rollback
        if autocommit is true then do the commit
        '''
        if not (self.neoDriver is None):
            if not (self.neoDriver.tx is None):
                if self.neoDriver.tx.closed() is False:
                    if self.neoDriver.autoCommit is True:
                        self.doCommit()
                    else:
                        # prompt user to commit or rollback the current transaction
                        msgBox = QMessageBox()
                        msgBox.setIcon(QMessageBox.Warning)
                        msgBox.setText(
                            "You have an uncommitted transaction. Do you want to commit? (click Yes to commit, No to rollback"
                        )
                        msgBox.setWindowTitle("Commit or Rollback")
                        msgBox.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)
                        result = msgBox.exec_()
                        if result == QMessageBox.Yes:
                            self.doCommit()
                        else:
                            self.doRollBack()

    @pyqtSlot(int)
    def on_cmbAutoCommit_currentIndexChanged(self, index):
        """
        User has changed the auto commit dropdown.
        
        @param index DESCRIPTION
        @type int
        """
        self.logMsg("User request transaction mode changed to {}".format(
            self.cmbAutoCommit.currentText()))
        if self.cmbAutoCommit.currentText() == "Auto Commit On":
            self.checkOpenTxn()
            if not (self.neoDriver is None):
                self.neoDriver.setAutoCommit(True)
            self.btnCommit.setEnabled(False)
            self.btnRollBack.setEnabled(False)
        if self.cmbAutoCommit.currentText() == "Auto Commit Off":
            self.checkOpenTxn()
            if not (self.neoDriver is None):
                self.neoDriver.setAutoCommit(False)
            self.btnCommit.setEnabled(True)
            self.btnRollBack.setEnabled(True)
Exemplo n.º 15
0
class DataGridWidget(QWidget, Ui_DataGridWidget):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, neoCon=None, genCypher=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(DataGridWidget, self).__init__(parent)
        self.setupUi(self)
        self.helper = Helper()
        self.neoTypeFunc = NeoTypeFunc()
        self.resultSet = None
        self.cypher = None
        self.parmData = None
        self.editParmDict = None
        self.genCypher = genCypher
        self.templateDict = self.genCypher.templateDict
        # the parent widget must supply a neoCon
        # this is the NeoDriver instance, but we still use the older neocon variable at this level in the code
        self.neoCon = neoCon
        self.parent = parent
        try:
            self.designModel = parent.designModel
        except:
            self.designModel = None

        self.neoCon.setAutoCommit(True)

        self.initUI()

        # data grid scrolling
        self.topRow = 1

        # data grid cell selection variables
        self.saveIndex = None
        self.prevIndex = None
        self.saveData = None

    def initUI(self, ):

        # get list of allowed functions on the data grid widget
        self.btnRefresh.setEnabled(self.genCypher.refreshOK())
        self.btnExport.setEnabled(self.genCypher.exportOK())
        self.btnNew.setEnabled(self.genCypher.newOK())
        self.btnDelete.setEnabled(self.genCypher.deleteOK())
        self.btnSetNull.setEnabled(self.genCypher.setNullOK())
        self.btnRefresh.setVisible(self.genCypher.refreshOK())
        self.btnExport.setVisible(self.genCypher.exportOK())
        self.btnNew.setVisible(self.genCypher.newOK())
        self.btnDelete.setVisible(self.genCypher.deleteOK())
        self.btnSetNull.setVisible(self.genCypher.setNullOK())

        # log grid
        self.gridLog.setModel(self.createLogModel())
        self.gridLog.setSortingEnabled(False)
        self.gridLog.setWordWrap(True)
        self.gridLog.setColumnWidth(0, 150)
        self.gridLog.setColumnWidth(PLAN, 100)
        self.gridLog.setColumnWidth(ERROR, 100)
        self.gridLog.setColumnWidth(CYPHER, 500)
        for x in range(UPDATES, IDXDEL + 1):
            self.gridLog.setColumnWidth(x, 150)

        self.gridLog.verticalHeader().setDefaultAlignment(Qt.AlignTop)

        # data grid
        self.gridCypherData.setAlternatingRowColors(True)
        # turn on row selection mode
        if self.genCypher.rowSelect():
            self.gridCypherData.setSelectionBehavior(
                QAbstractItemView.SelectRows)

    def logMsg(self, msg):
        '''
        This method writes the message to the trace tab and the application log
        '''
        #        print("datagridwidget log method: {}".format(msg))
        # add message to the log
        if logging:
            logging.info(msg)

        # add message to the trace tab
        ts = ('%s' % datetime.datetime.now())
        outmsg = "{} - {}".format(ts, msg)
        self.textTrace.append(outmsg)

    def logWatch(self, ):
        return

#        httpWatch = self.httpCapturer.getvalue()
#        boltWatch = self.boltCapturer.getvalue()
#
#        # update trace tab
#        if len(httpWatch) > 0:
#            self.logMsg("HTTP Watch: {}".format(httpWatch))
#        if len(boltWatch) > 0:
#            self.logMsg("BOLT Watch: {}".format(boltWatch))
#       # close and reopen the stream to clear it out
#        self.closeWatch()
#        self.initWatch()

#####################################################################################
# methods related to the log tab
#####################################################################################

    def createLogModel(self, ):
        #        TS, DURATION, CYPHER, ERROR, PLAN, UPDATES, LBLADD, LBLREMOVE, PROPSET, NODEADD,  NODEDEL, RELADD,  RELDEL, CONADD, CONDEL, IDXADD, IDXDEL = range(15)

        model = QStandardItemModel(0, 17)
        model.setHeaderData(TS, Qt.Horizontal, "Start Time")
        model.setHeaderData(DURATION, Qt.Horizontal, "Duration")
        model.setHeaderData(CYPHER, Qt.Horizontal, "Cypher")
        model.setHeaderData(ERROR, Qt.Horizontal, "Error")
        model.setHeaderData(PLAN, Qt.Horizontal, "Plan")
        model.setHeaderData(UPDATES, Qt.Horizontal, "Updates")
        model.setHeaderData(LBLADD, Qt.Horizontal, "Labels Added")
        model.setHeaderData(LBLDEL, Qt.Horizontal, "Labels Removed")
        model.setHeaderData(PROPSET, Qt.Horizontal, "Properties Updated")
        model.setHeaderData(NODEADD, Qt.Horizontal, "Nodes Created")
        model.setHeaderData(NODEDEL, Qt.Horizontal, "Nodes Deleted")
        model.setHeaderData(RELADD, Qt.Horizontal, "Relationships Created")
        model.setHeaderData(RELDEL, Qt.Horizontal, "Relationships Deleted")
        model.setHeaderData(CONADD, Qt.Horizontal, "Constraints Added")
        model.setHeaderData(CONDEL, Qt.Horizontal, "Constraints Deleted")
        model.setHeaderData(IDXADD, Qt.Horizontal, "Indexes Added")
        model.setHeaderData(IDXDEL, Qt.Horizontal, "Indexes Deleted")

        model.rowsInserted.connect(self.autoScroll)

        return model

    def refreshSchemaTreeView(self):
        '''
        tell the schema editor to refresh its tree view if any schema objects changed
        '''
        if not self.neoCon.stats is None:
            try:
                # see if any schema objects changed
                if (self.neoCon.stats.constraints_added > 0
                        or self.neoCon.stats.constraints_removed > 0
                        or self.neoCon.stats.indexes_added > 0
                        or self.neoCon.stats.indexes_removed > 0
                        or self.neoCon.stats.labels_added > 0
                        or self.neoCon.stats.labels_removed > 0):
                    #                    print("refresh schema model")
                    self.parent.parent.refreshSchemaModel()
            except:
                pass

        return

    def addGridRow(self, ):
        #        TS, DURATION, CYPHER, ERROR, PLAN, UPDATES, LBLADD, LBLREMOVE, PROPSET, NODEADD,  NODEDEL, RELADD,  RELDEL, CONADD, CONDEL, IDXADD, IDXDEL = range(15)
        '''
        add a row to the log grid
        '''
        cypherLogDict = self.neoCon.cypherLogDict
        model = self.gridLog.model()
        stats = self.neoCon.stats
        self.refreshSchemaTreeView()

        try:
            deltaTime = (
                '%s' % (cypherLogDict['endTime'] - cypherLogDict['startTime']))
        except:
            deltaTime = "no delta time"

        item1 = QStandardItem('%s' %
                              cypherLogDict.get('startTime', 'no start time'))
        item1.setEditable(False)
        item1.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        item2 = QStandardItem(deltaTime)
        item2.setEditable(False)
        item2.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        item3 = QStandardItem(cypherLogDict.get('cypher', 'no cypher'))
        item3.setEditable(False)
        item3.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        # make the plan column wider if there is any output
        if (str(cypherLogDict.get('plan', "")) == "{}"
                or str(cypherLogDict.get('plan', ""))
                == "") or cypherLogDict.get('plan', None) is None:
            planOutput = ""
        else:
            planOutput = str(cypherLogDict.get('plan', "No Plan Output"))
            # increase the width of the column.
            self.gridLog.setColumnWidth(PLAN, 300)

        itemX = QStandardItem(planOutput)
        itemX.setEditable(False)
        itemX.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        # make the error message column wider if there is any output
        if (str(cypherLogDict.get('error', "")) == "{}"
                or str(cypherLogDict.get('error', "")) == ""):
            errorOutput = ""
        else:
            errorOutput = cypherLogDict.get('error', "")
            self.gridLog.setColumnWidth(ERROR, 300)

        itemE = QStandardItem(errorOutput)
        itemE.setEditable(False)
        itemE.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        if not stats is None:
            try:
                updates = stats.contains_updates
            except:
                updates = 'unknown'

            item4 = QStandardItem(str(updates))
            item4.setEditable(False)
            item4.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item5 = QStandardItem(str(stats.labels_added))
            item5.setEditable(False)
            item5.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item6 = QStandardItem(str(stats.labels_removed))
            item6.setEditable(False)
            item6.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item7 = QStandardItem(str(stats.properties_set))
            item7.setEditable(False)
            item7.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item8 = QStandardItem(str(stats.nodes_created))
            item8.setEditable(False)
            item8.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item9 = QStandardItem(str(stats.nodes_deleted))
            item9.setEditable(False)
            item9.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item10 = QStandardItem(str(stats.relationships_created))
            item10.setEditable(False)
            item10.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item11 = QStandardItem(str(stats.relationships_deleted))
            item11.setEditable(False)
            item11.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item12 = QStandardItem(str(stats.constraints_added))
            item12.setEditable(False)
            item12.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item13 = QStandardItem(str(stats.constraints_removed))
            item13.setEditable(False)
            item13.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item14 = QStandardItem(str(stats.indexes_added))
            item14.setEditable(False)
            item14.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
            item15 = QStandardItem(str(stats.indexes_removed))
            item15.setEditable(False)
            item15.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole)
        else:
            item4 = QStandardItem('')
            item4.setEditable(False)
            item4.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item5 = QStandardItem('')
            item5.setEditable(False)
            item5.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item6 = QStandardItem('')
            item6.setEditable(False)
            item6.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item7 = QStandardItem('')
            item7.setEditable(False)
            item7.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item8 = QStandardItem('')
            item8.setEditable(False)
            item8.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item9 = QStandardItem('')
            item9.setEditable(False)
            item9.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item10 = QStandardItem('')
            item10.setEditable(False)
            item10.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item11 = QStandardItem('')
            item11.setEditable(False)
            item11.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item12 = QStandardItem('')
            item12.setEditable(False)
            item12.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item13 = QStandardItem('')
            item13.setEditable(False)
            item13.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item14 = QStandardItem('')
            item14.setEditable(False)
            item14.setData(Qt.AlignTop, Qt.TextAlignmentRole)
            item15 = QStandardItem('')
            item15.setEditable(False)
            item15.setData(Qt.AlignTop, Qt.TextAlignmentRole)

        model.appendRow([
            item1,
            item2,
            item3,
            itemX,
            itemE,
            item4,
            item5,
            item6,
            item7,
            item8,
            item9,
            item10,
            item11,
            item12,
            item13,
            item14,
            item15,
        ])

        self.gridLog.resizeRowsToContents()

        QTimer.singleShot(0, self.gridLog.scrollToBottom)

    def autoScroll(self):
        # position on last row in gird
        lastRow = self.gridLog.model().rowCount()
        index = self.gridLog.model().index(lastRow, 0, parent=QModelIndex())
        self.gridLog.scrollTo(index, hint=QAbstractItemView.PositionAtTop)
        self.gridLog.selectRow(index.row())

    def clearGridLog(self, ):
        self.gridLog.model().removeRows(0, self.gridLog.model().rowCount())

    @pyqtSlot()
    def on_btnExportLog_clicked(self):
        """
        Export the contents of the log tab to .csv file
        """
        # get filename to save as
        dlg = QFileDialog()
        dlg.setAcceptMode(QFileDialog.AcceptSave)
        dlg.setDefaultSuffix("csv")
        dlg.setNameFilters(["Log File (*.csv)", "all files (*.*)"])
        dlg.setDirectory(self.parent.settings.value("Default/ProjPath"))
        if dlg.exec_():
            fileNames = dlg.selectedFiles()
            try:
                if fileNames:
                    self.fileName = fileNames[0]
                    # save the file
                    with open(self.fileName, 'w', newline='') as csvfile:
                        csvWriter = csv.writer(csvfile,
                                               delimiter=',',
                                               quotechar='"',
                                               quoting=csv.QUOTE_MINIMAL)
                        for row in range(self.gridLog.model().rowCount()):
                            rowItems = []
                            for col in range(
                                    self.gridLog.model().columnCount()):
                                value = self.gridLog.model().index(
                                    row, col,
                                    QModelIndex()).data(Qt.DisplayRole)
                                rowItems.append(value)
                            csvWriter.writerow(c for c in rowItems)
            except BaseException as e:
                msg = "{} - {} failed.".format("Write CSV", repr(e))
                self.helper.displayErrMsg("Export CSV Error", msg)

    @pyqtSlot()
    def on_btnClearLog_clicked(self):
        """
        Clear the log grid
        """
        self.clearGridLog()

#####################################################################################
# methods related to the data tab
#####################################################################################

    def forceSelectionChange(self, ):
        '''
        this method changes the current selection to force any unprocessed updates to take place.
        '''
        self.gridCypherData.clearSelection()

    def displayDataRetrievedMsg(self, ):
        if self.neoCon.endReached == True:
            msg = "All Data Retrieved."
        else:
            msg = "More Data Exists."
        self.displayGridMessage("Retrieved {} records. {}".format(
            self.neoCon.chunkEnd, msg))
#        self.txtPosition.setText("Retrieved {} records. {}".format(self.neoCon.chunkEnd, msg ))

    def displayGridMessage(self, message):
        if not message is None:
            self.txtPosition.setText(message)
        else:
            self.txtPosition.setText("")

    def clearModel(self):
        # if we already have a model get rid of it
        if not (self.resultSet is None):
            del self.resultSet
        self.gridCypherData.setModel(None)

    def newResultModel(self):

        #        headers = self.neoCon.cursor.keys()
        headers = self.neoCon.result.keys()
        if not (headers is None):
            self.resultModel = QStandardItemModel(0, len(headers))
            for index, header in enumerate(headers):
                self.resultModel.setHeaderData(index, Qt.Horizontal, header)

        # assign the model to the grid
        self.gridCypherData.setModel(self.resultModel)
        # set the editor delegate
        self.gridCypherData.setItemDelegate(NeoEditDelegate(self))
        # connect model slots
        self.resultModel.itemChanged.connect(self.resultModelItemChanged)
        # connect grid slots
        self.gridCypherData.selectionModel().selectionChanged.connect(
            self.dataGridSelectionChanged)

        self.gridCypherData.resizeColumnsToContents()

    def runFileCursor(self, ):
        '''
        1. start dialog box to Run a cypher query and return a cursor to the result set
        2. Retrieve the first chunk of data
        '''
        #        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests retrieve {}".format(self.genCypher.type))
        # clear the grid
        self.clearModel()

        try:
            rc = False
            self.logMsg(self.cypher)
            # the dialog box actually spins up a thread that runs the query
            d = GetCursorDlg(neoCon=self.neoCon,
                             cypher=self.cypher,
                             mode="cursor",
                             parmData=self.parmData)
            if d.exec_():
                rc1 = d.rc
                msg1 = d.msg

            if rc1:
                self.logMsg("run cypher {}".format(msg1))
                if not self.neoCon.stats is None:
                    self.logMsg("stats {}".format(self.neoCon.stats))
                self.newResultModel()
                #get the first chunk
                x, msg2 = self.neoCon.forwardCursor()
                self.logMsg("rows fetched: {} - {}".format(str(x), msg2))
                self.loadModelChunk()
                self.tabWidget.setCurrentIndex(DATA)
                self.displayDataRetrievedMsg()
                # force a repaint of the grid.  this is needed as a workaround for MAC OS
                self.gridCypherData.repaint()
                # end workaround
                rc = True
                msg = "Run cypher complete"
            else:
                msg = "run cypher Error {}".format(msg1)
        except BaseException as e:
            msg = "{} - Query failed.".format(repr(e))
        finally:
            # update trace tab
            self.logWatch()
            # add the row to the log grid
            self.addGridRow()
            # set tab focus depending on results
            QApplication.restoreOverrideCursor()
            if rc == False:
                # display error message then switch to trace tab
                self.helper.displayErrMsg("Run Query With Cursor", msg)
                self.tabWidget.setCurrentIndex(TRACE)
            else:
                # switch to data tab
                self.tabWidget.setCurrentIndex(DATA)

            self.logMsg(msg)

    # - append the table model with the current cursor chunk
    def loadModelChunk(self, ):
        self.gridCypherData.setSortingEnabled(False)

        for record in self.neoCon.cursorChunk:
            itemList = []
            # generate the qstandarditem for each column in the result set based on the editParmDict dictionary
            # value has the correct python object for each cell in the result set
            # need to store both the original value and the string representation
            for index, value in enumerate(record.values()):
                item = QStandardItem()
                #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP, RELNAME
                if not self.editParmDict is None:
                    columnType = self.editParmDict[index][0]
                    editable = self.editParmDict[index][1]
                else:
                    columnType = UNKNOWN
                    editable = False

                # get the data type of the retrieved data if not null
                if not value is None:
                    dataType = self.neoTypeFunc.getNeo4jDataType(value)
                else:
                    # get the property name from the header
                    propName = self.gridCypherData.model().headerData(
                        index, Qt.Horizontal, Qt.DisplayRole)
                    if not self.editParmDict is None:
                        dataTypeLookup = ""
                        dataType = None
                        # get the datatype defined for the property name in the node or rel template
                        if "properties" in self.templateDict:
                            dataTypeLookup = [
                                prop[1]
                                for prop in self.templateDict["properties"]
                                if prop[0] == propName
                            ]
                        if len(dataTypeLookup) > 0:
                            dataType = dataTypeLookup[0]
                        else:
                            # see if you can infer the datatype from the column type
                            if columnType == NODE:
                                dataType = "Node"
                            if columnType == RELATIONSHIP:
                                dataType = "Relationship"

                        if dataType is None:
                            dataType = DataType.UNKNOWN.value
                    else:
                        dataType = DataType.UNKNOWN.value

                # UNKNOWN DATA TYPES FROM DYNAMIC CYPHER
                if columnType == UNKNOWN:
                    if dataType == "Node":
                        nodeText = ("({} {})".format(str(value.labels),
                                                     str(dict(value))))
                        item.setText(nodeText)
                    else:
                        if value is None:
                            item.setText("Null")
                        else:
                            displayText = self.neoTypeFunc.convertTypeToString(
                                value)
                            item.setText(displayText)
                    item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                # these are never editable so just display them
                elif columnType in (NODEID, RELID, RELNAME):
                    item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                    item.setText(str(value))

                elif columnType == NODE:
                    #                    if str(editable) != "True":
                    # if not editable then set the flags, otherwise the default flags are ok
                    if not editable:
                        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                    if value is None:
                        item.setText("Null")
                    else:
                        nodeText = ("({} {})".format(str(value.labels),
                                                     str(dict(value))))
                        item.setText(nodeText)

                elif columnType == RELATIONSHIP:
                    if str(editable) == "True":
                        item.setText(str(value))
                    else:
                        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

                    if value is None:
                        item.setText("Null")
                    else:
                        item.setText(str(value))

                elif columnType == PROP:
                    if not editable:
                        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                    if value is None:
                        item.setText("Null")
                    else:
                        displayText = self.neoTypeFunc.convertTypeToString(
                            value)
                        item.setText(displayText)

                elif columnType in (OPTLBL, REQLBL):
                    item.setFlags(Qt.NoItemFlags)
                    self.gridCypherData.setColumnWidth(index, 100)
                    self.gridCypherData.horizontalHeader(
                    ).setSectionResizeMode(index, QHeaderView.Fixed)
                    # set the state of the checkbox based on the data returned by the query.
                    if value is None:
                        item.setCheckState(Qt.Unchecked)
                    elif str(value) == 'True':
                        item.setCheckState(Qt.Checked)
                    else:
                        item.setCheckState(Qt.Unchecked)
                    if str(editable) == "True":
                        item.setFlags(Qt.ItemIsUserCheckable
                                      | Qt.ItemIsEnabled)
                    else:
                        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

                # store the actual data in the user defined role
                item.setData(value, Qt.UserRole)
                # store the datatype in role + 1
                item.setData(dataType, Qt.UserRole + 1)

                #                print("Column:  Value retrieved: {} python type: {}".format(str(value), type(value)))
                # add the item just created to the item list
                itemList.append(item)

            # add the entire item list to the model.  this adds a row of data to the grid
            self.gridCypherData.model().appendRow(itemList)
        # set
        self.gridCypherData.resizeColumnsToContents()

    def runCypher(self, requestType, cypher):
        '''
        Run a Cypher query and return the entire result set
        '''
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests {}".format(requestType))
        try:
            rc = False
            self.logMsg(cypher)
            #run the query
            rc1, msg1 = self.neoCon.runCypherAuto(cypher)
            if rc1:
                self.logMsg("{} Node {}".format(requestType, msg1))
                self.logMsg("stats {}".format(self.neoCon.stats))
                rc = True
                msg = "{} Node complete".format(requestType)
            else:
                msg = "{} Node Error {}".format(requestType, msg1)
        except BaseException as e:
            msg = "{} - {} failed.".format(requestType, repr(e))
        finally:
            # update trace tab
            self.logWatch()
            # add the row to the log grid
            self.addGridRow()
            # set tab focus depending on results
            QApplication.restoreOverrideCursor()
            if rc == False:
                self.helper.displayErrMsg("Process Node", msg)
                self.tabWidget.setCurrentIndex(TRACE)
            else:
                # show positive message at bottom of grid
                self.displayGridMessage(msg)
                self.tabWidget.setCurrentIndex(DATA)
            self.logMsg(msg)

    @pyqtSlot()
    def on_btnExport_clicked(self):
        """
        Export the query result set to a csv file
        """
        self.logMsg("User requests export current query")
        # get filename to save as
        dlg = DlgExportCSV(parent=self)
        if dlg.exec_():
            try:
                self.logMsg("export {}".format(dlg.fileName))
                #            # run the query, fetch all rows into the resultset
                if (not self.cypher is None and len(self.cypher) > 0):
                    self.runCypher("Export Current Query", self.cypher)
                    # write the csv file
                    with open(dlg.fileName, 'w', newline='') as csvfile:
                        csvwriter = csv.writer(
                            csvfile,
                            delimiter=dlg.delimiterChar,
                            quotechar=dlg.quoteChar,
                            quoting=dlg.useQuote,
                            doublequote=dlg.doubleQuote,
                            escapechar=dlg.escapeChar,
                            lineterminator=dlg.lineTerminator)
                        if dlg.writeHeader:
                            #                            csvwriter.writerow(self.neoCon.cursor.keys())
                            csvwriter.writerow(self.neoCon.result.keys())
                        for record in self.neoCon.resultSet:
                            csvwriter.writerow(record.values())
                        self.helper.displayErrMsg("Export Query",
                                                  "Data Export Complete!")

                else:
                    self.logMsg("No current query to export")
                    self.helper.displayErrMsg("Export Query",
                                              "No current query to export")

            except BaseException as e:
                self.logMsg("Error Exporting Data - {}".format(repr(e)))
                self.helper.displayErrMsg(
                    "Export Query",
                    "Error Exporting Data - {}".format(repr(e)))

    @pyqtSlot()
    def on_btnBegin_clicked(self):
        """
        position back to the first row in the result set
        """
        self.logMsg("User requests scroll to top")
        index = self.gridCypherData.model().index(0, 0, parent=QModelIndex())
        self.gridCypherData.scrollTo(index,
                                     hint=QAbstractItemView.PositionAtTop)
        self.gridCypherData.selectRow(0)
        self.displayDataRetrievedMsg()
        self.gridCypherData.repaint()

    @pyqtSlot()
    def on_btnBack_clicked(self):
        """
        scroll the table back one "chunk"
        """
        # set top row to the row of the currently selected item
        if self.gridCypherData.currentIndex().row() < 0:
            self.topRow = 0
        else:
            self.topRow = self.gridCypherData.currentIndex().row()
        # backup by chunksize
        self.topRow = self.topRow - self.neoCon.chunkSize
        if self.topRow < 0:
            self.topRow = 0
        index = self.gridCypherData.model().index(self.topRow,
                                                  0,
                                                  parent=QModelIndex())
        self.gridCypherData.scrollTo(index,
                                     hint=QAbstractItemView.PositionAtTop)
        self.gridCypherData.selectRow(self.topRow)
        self.displayDataRetrievedMsg()
        self.gridCypherData.repaint()

    @pyqtSlot()
    def on_btnForward_clicked(self):
        """
        if positioned in the last chunk:
            move the cursor through the result set one "chunk". 
            scroll the table to the first row of the most recently retrieved chunk.
        if positioned prior to the last chunk:
            scroll the table forward to the beginning of the next chunk. no data is retrieved.
        """
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests scroll forward")
        # set top row to the currently selected row
        if self.gridCypherData.currentIndex().row() < 0:
            self.topRow = 0
        else:
            self.topRow = self.gridCypherData.currentIndex().row()
#        print("starting top row:{}".format(self.topRow))
# if we are positioned inside the most recently retrieved chunk then get another chunk
        if self.topRow + 1 >= self.neoCon.chunkStart:
            #            print("begin chunkstart:{} chunkend:{} topRow:{}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow))
            ctr, msg = self.neoCon.forwardCursor()
            # update trace tab
            self.logWatch()
            self.logMsg("Retrieved {} records. Message:{}".format(ctr, msg))
            self.loadModelChunk()
            self.topRow = self.topRow + self.neoCon.chunkSize + 1
            # see if past end of tableview
            if self.topRow > self.gridCypherData.model().rowCount():
                self.topRow = self.gridCypherData.model().rowCount()
            index = self.gridCypherData.model().index(self.topRow - 1,
                                                      0,
                                                      parent=QModelIndex())
            self.gridCypherData.scrollTo(index,
                                         hint=QAbstractItemView.PositionAtTop)
            self.gridCypherData.selectRow(index.row())
#            print("read data. chunkstart:{} chunkend:{} topRow:{}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow))

        else:
            # we are positioned in a previously retrieved chunk not the current chunk so just scroll forward chunkSize
            self.topRow = self.topRow + self.neoCon.chunkSize
            #            print("chunkstart:{} chunkend:{} newtop: {}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow))
            if self.topRow > self.neoCon.chunkEnd - 1:
                self.topRow = self.neoCon.chunkEnd
            index = self.gridCypherData.model().index(self.topRow - 1,
                                                      0,
                                                      parent=QModelIndex())
            self.gridCypherData.scrollTo(index,
                                         hint=QAbstractItemView.PositionAtTop)
            self.gridCypherData.selectRow(index.row())

        QApplication.restoreOverrideCursor()
        self.displayDataRetrievedMsg()
        self.gridCypherData.repaint()

    @pyqtSlot()
    def on_btnEnd_clicked(self):
        """
        User clicks the move to end button.  Do Fetch cursor until all rows retrieved and loaded to the grid
        """
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests scroll to end")
        while self.neoCon.forwardCursor()[0] > 0:
            self.logMsg("Advanced to record {}".format(self.neoCon.chunkEnd))
            self.loadModelChunk()
        index = self.gridCypherData.model().index(self.neoCon.chunkEnd - 1,
                                                  0,
                                                  parent=QModelIndex())
        self.gridCypherData.scrollTo(index,
                                     hint=QAbstractItemView.PositionAtTop)
        self.gridCypherData.selectRow(index.row())
        QApplication.restoreOverrideCursor()
        self.displayDataRetrievedMsg()
        self.gridCypherData.repaint()

    def createBlankNode(self, ):
        self.neoID = None
        self.cypher = self.genCypher.genNewNode()
        self.runCypher("New Node", self.cypher)
        if self.neoCon.resultSet:
            self.neoID = self.neoCon.resultSet[0]["nodeID"]
        return self.neoID

    @pyqtSlot()
    def on_btnRefresh_clicked(self):
        """
        Generate the match query and run it.
        """
        self.refreshGrid()

    def refreshGrid(self, **kwargs):
        '''
        1. if special use data grid then generate the cypher match statement
        2. run the cypher statement and populate the grid
        the calling application is responsible for locking the cursor
        '''
        if not self.genCypher.isGeneric():

            # see if a nodetemplatedict is being passed in as a kwarg
            aTemplateDict = kwargs.get("nodeTemplateDict", None)
            if not aTemplateDict is None:
                self.templateDict = aTemplateDict
            self.cypher, self.editParmDict = self.genCypher.genMatch(**kwargs)
        else:
            self.editParmDict = None

        if not self.cypher is None:
            self.runFileCursor()
        else:
            self.clearModel()

    @pyqtSlot()
    def on_btnNew_clicked(self):
        """
        Create a new Node/Rel based on the template
        """
        if self.genCypher.type == "Node":
            # display the new node dialog box
            dlg = EditNodeDlg(parent=self)
            dlg.exec()
            # refresh the grid
            self.refreshGrid()
            # position to the new node
            if not dlg.neoID is None:
                findIt = self.gridCypherData.model().findItems(
                    str(dlg.neoID), flags=Qt.MatchExactly, column=0)
                if len(findIt) > 0:
                    findItem = findIt[0]
                    self.gridCypherData.scrollTo(findItem.index())
                    self.gridCypherData.selectRow(findItem.row())

        if self.genCypher.type == "Relationship":
            # display the new rel dialog box
            dlg = EditRelDlg(parent=self)
            dlg.exec()
            # refresh the grid
            self.refreshGrid()
            # position to the new rel
            if not dlg.neoID is None:
                findIt = self.gridCypherData.model().findItems(
                    str(dlg.neoID), flags=Qt.MatchExactly, column=2)
                if len(findIt) > 0:
                    findItem = findIt[0]
                    self.gridCypherData.scrollTo(findItem.index())
                    self.gridCypherData.selectRow(findItem.row())

    @pyqtSlot()
    def on_btnDelete_clicked(self):
        """
        Delete the node or relationship identified by the rows selected in the grid.
        """
        indexes = self.gridCypherData.selectionModel().selectedIndexes()
        if len(indexes) > 0:
            startIndex = indexes[0]
            endIndex = indexes[len(indexes) - 1]
            if self.helper.delRowsPrompt(startIndex.row() + 1,
                                         endIndex.row() + 1):
                #                print("delete rows {}-{}".format(startIndex.row()+1, endIndex.row()+1))
                for row in range(startIndex.row(), endIndex.row() + 1):
                    # delete the node in neo4j
                    self.deleteDetachCypher = self.genCypher.genDeleteDetach(
                        row=row, dataGrid=self.gridCypherData)
                    # do the update
                    self.runCypher("Delete/Detach Node",
                                   self.deleteDetachCypher)
                # refresh the grid to make deleted rows go away
                self.on_btnRefresh_clicked()
            else:
                pass
#                print("delete canceled")
        else:
            self.helper.displayErrMsg(
                "Delete Row", "You must select a row or rows to delete.")

#####################################################################################
# methods related to the trace tab
#####################################################################################

    @pyqtSlot()
    def on_btnExportTrace_clicked(self):
        """
        Export the contents of the trace tab to .txt file
        """
        # get filename to save as
        dlg = QFileDialog()
        dlg.setAcceptMode(QFileDialog.AcceptSave)
        dlg.setDefaultSuffix("txt")
        dlg.setNameFilters(["Trace File (*.txt)", "all files (*.*)"])
        dlg.setDirectory(self.parent.settings.value("Default/ProjPath"))
        if dlg.exec_():
            fileNames = dlg.selectedFiles()
            if fileNames:
                self.fileName = fileNames[0]
                # save the file
                file = QFile(self.fileName)
                if not file.open(QFile.WriteOnly | QFile.Text):
                    self.helper.displayErrMsg(
                        "Export Trace Error", "Cannot write file {} {}".format(
                            self.fileName, file.errorString()))
                    return
                outstr = QTextStream(file)
                QApplication.setOverrideCursor(Qt.WaitCursor)
                outstr << self.textTrace.toPlainText()
                QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnClearTrace_clicked(self):
        """
        clear the contents of the trace tab
        """
        self.textTrace.clear()

###########################################################################
# KEYBOARD AND MOUSE EVENTS - keep these functions as examples and for debugging when needed
###########################################################################

#    @pyqtSlot(QModelIndex)
#    def on_gridCypherData_pressed(self, index):
#        """
#        Slot documentation goes here.
#
#        @param index DESCRIPTION
#        @type QModelIndex
#        """
#        print("Cell pressed {} {}".format(index.row(), index.column()))

#    @pyqtSlot(QModelIndex)
#    def on_gridCypherData_clicked(self, index):
#        """
#        Slot documentation goes here.
#
#        @param index DESCRIPTION
#        @type QModelIndex
#        """
#        print("Cell clicked {} {} - data {}".format(index.row(), index.column(), index.data(role=Qt.DisplayRole)))

#    @pyqtSlot(QModelIndex)
#    def on_gridCypherData_entered(self, index):
#        """
#        Slot documentation goes here.
#
#        @param index DESCRIPTION
#        @type QModelIndex
#        """
#        print("Cell entered {} {}".format(index.row(), index.column()))

#    @pyqtSlot(QModelIndex)
#    def on_gridCypherData_activated(self, index):
#        """
#        Slot documentation goes here.
#
#        @param index DESCRIPTION
#        @type QModelIndex
#        """
#        print("Cell activated {} {} - data {}".format(index.row(), index.column(), index.data(role=Qt.DisplayRole)))

    def dataGridSelectionChanged(self, selected, deselected):
        # no longer used.
        #        print("dataGridSelectionChanged")
        return

    def resultModelItemChanged(self, item):

        #        print("item data changed {} at row:{} col:{}".format(str(item.checkState()), item.index().row(), item.index().column()))
        # a cell changed so update the node label or property
        columnIndex = item.index().column()
        if not self.editParmDict is None:
            columnType = self.editParmDict[columnIndex][0]
        else:
            columnType = UNKNOWN
        # is this a label column?
        if columnType in (OPTLBL, REQLBL):
            # force selection of this cell
            self.gridCypherData.setCurrentIndex(item.index())
            # update the node in neo4j
            self.updateCypher = self.genCypher.genUpdateLabel(
                updateIndex=item.index(), dataGrid=self.gridCypherData)
            # do the update
            self.runCypher("Update Label", self.updateCypher)
        if columnType == PROP:
            # generate a cypher match/set statement aka update
            rc, self.updateCypher = self.genCypher.genUpdateProp(
                updateIndex=item.index(), dataGrid=self.gridCypherData)
            #            print("data changed from {} to {} cypher {}".format(self.saveData, item.index().data(role = Qt.DisplayRole), self.updateCypher))
            # do the update
            if rc == True:
                self.runCypher("Update Property", self.updateCypher)
            else:
                self.helper.displayErrMsg("Update Property", self.updateCypher)

    @pyqtSlot()
    def on_btnSetNull_clicked(self):
        """
        User clicks the set property value null button
        """
        indexes = self.gridCypherData.selectionModel().selectedIndexes()
        if len(indexes) > 0:
            selectedIndex = indexes[0]
            columnNum = indexes[0].column()
            if not self.editParmDict is None:
                columnType = self.editParmDict[columnNum][0]
            else:
                columnType = UNKNOWN
            if columnType == PROP:
                #                print("set item null {} at row:{} col:{}".format(selectedIndex.data(role = Qt.DisplayRole), selectedIndex.row(), selectedIndex.column()))
                # generate a cypher match/set statement aka update
                self.removeCypher = self.genCypher.genRemoveProp(
                    updateIndex=selectedIndex, dataGrid=self.gridCypherData)
                # do the update
                if self.removeCypher != None:
                    self.runCypher("Remove Property", self.removeCypher)
                    self.refreshGrid()
            else:
                self.helper.displayErrMsg("Set Value Null",
                                          "You must select a property value.")
        else:
            self.helper.displayErrMsg("Set Value Null",
                                      "You must select a property value.")
Exemplo n.º 16
0
class CreateIndexDlg(QDialog, Ui_CreateIndexDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CreateIndexDlg, self).__init__(parent)
        self.helper = Helper()
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaModel
        self.initUi()
        
    def initUi(self, ):
        aList = self.schemaModel.instanceList("Label")
        self.cbLabel.addItem("")
        aSortedList = sorted(aList)
        for indexName in aSortedList:
            self.cbLabel.addItem(indexName)
        
        aList = sorted(self.schemaModel.instanceList("Property"))
        self.cbProperty.addItem("")
        for indexName in aList:
            self.cbProperty.addItem(indexName)
        return

    def validate(self, ):

        # must enter or select a label
        if self.helper.NoTextValueError(self.cbLabel.currentText(), "You must enter or select a Label"):
            return False
        # must add at least one property to the list
        if self.helper.NoTextValueError(self.lstProperty, "You must have at least one property in the list."):
            return False
        
        return True
        
    def apply(self, ):
        """
        Generate the create index statement
        """
        propList = [str(self.lstProperty.item(i).text()) for i in range(self.lstProperty.count())]
        propComma = ",".join(x for x in propList) 
        self.cypherCommand = "CREATE INDEX ON :{}({})".format(self.cbLabel.currentText(), str(propComma))
        if self.cypherCommand:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.schemaModel.createIndex(self.cypherCommand)
            QApplication.restoreOverrideCursor()            
            self.helper.displayErrMsg("Create Index", msg)
        else:
            self.helper.displayErrMsg("Create Index", "Error Generating Index Statement.")

    
    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)
    
    @pyqtSlot()
    def on_buttonBox_rejected(self):
        """
        Slot documentation goes here.
        """
        QDialog.reject(self)
    
    @pyqtSlot()
    def on_pbAddToList_clicked(self):
        """
        Get the property name in the combo box and add it to the list
        """
        self.lstProperty.addItem(self.cbProperty.currentText())
    
    @pyqtSlot()
    def on_pbRemoveList_clicked(self):
        """
        Remove the selected property from the list
        """
        self.lstProperty.takeItem(self.lstProperty.currentRow())
    
    @pyqtSlot()
    def on_btnMoveUp_clicked(self):
        """
        User clicks the Move Up button, move the selected property up on in the list
        """
        self.helper.moveListItemUp(self.lstProperty)
    
    @pyqtSlot()
    def on_btnMoveDown_clicked(self):
        """
        User clicks the Move Down button, move the selected property up on in the list
        """
        self.helper.moveListItemDown(self.lstProperty)        
Exemplo n.º 17
0
class ConstraintNodeKeyDlg(QDialog, Ui_ConstraintNodeKeyDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(ConstraintNodeKeyDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaModel
        self.helper = Helper()
        self.initUi()

    def initUi(self, ):
        aList = sorted(self.schemaModel.instanceList("Label"))
        self.cbLabel.addItem("")
        for indexName in aList:
            self.cbLabel.addItem(indexName)

        aList = sorted(self.schemaModel.instanceList("Property"))
        self.cbProperty.addItem("")
        for indexName in aList:
            self.cbProperty.addItem(indexName)

    def validate(self, ):
        # must enter or select a label
        if self.helper.NoTextValueError(self.cbLabel.currentText(),
                                        "You must enter or select a Label"):
            return False
        # must add at least one property to the list
        if self.helper.NoTextValueError(
                self.lstProperty,
                "You must have at least one property in the list."):
            return False

        return True

    def apply(self, ):
        """
        Generate and run the create constraint statement
        """
        propList = [
            "p." + str(self.lstProperty.item(i).text())
            for i in range(self.lstProperty.count())
        ]
        propComma = ",".join(x for x in propList)
        label = self.cbLabel.currentText()
        self.cypherCommand = None
        '''
        CREATE CONSTRAINT ON (p:Person) ASSERT (p.firstname, p.surname) IS NODE KEY            
        '''
        self.cypherCommand = "CREATE CONSTRAINT ON (p:{}) ASSERT ({}) IS NODE KEY".format(
            label, str(propComma))

        if self.cypherCommand:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.schemaModel.createConstraint(self.cypherCommand)
            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg("Create Node KeyConstraint", msg)
        else:
            self.helper.displayErrMsg(
                "Create Node Key Constraint",
                "Error Generating Node Key Constraint Statement.")

    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)

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

    @pyqtSlot()
    def on_pbAddToList_clicked(self):
        """
        Get the property name in the combo box and add it to the list
        """
        if len(
                self.lstProperty.findItems(self.cbProperty.currentText(),
                                           Qt.MatchFixedString)) == 0:
            self.lstProperty.addItem(self.cbProperty.currentText())
        else:
            self.helper.displayErrMsg(
                "Create Node Key Constraint",
                "You can't use the same property twice.")

    @pyqtSlot()
    def on_pbRemoveList_clicked(self):
        """
        Remove the selected property from the list
        """
        self.lstProperty.takeItem(self.lstProperty.currentRow())

    @pyqtSlot()
    def on_btnMoveUp_clicked(self):
        """
        User clicks the Move Up button, move the selected property up on in the list
        """
        self.helper.moveListItemUp(self.lstProperty)

    @pyqtSlot()
    def on_btnMoveDown_clicked(self):
        """
        User clicks the Move Down button, move the selected property down on in the list
        """
        self.helper.moveListItemDown(self.lstProperty)
Exemplo n.º 18
0
class EditRelDlg(QDialog, Ui_EditRelDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(EditRelDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.designModel = self.parent.designModel
        self.templateDict = self.parent.templateDict
        self.neoCon = self.parent.neoCon
        self.helper = Helper()
        self.rel = None
        self.neoID = None
        # add edit relationship widet
        self.editRel = EditRelWidget(self, templateDict=self.templateDict)
        self.editRelLayout = QVBoxLayout(self.frameBody)
        self.editRelLayout.setObjectName("editRelLayout")
        self.editRelLayout.addWidget(self.editRel)
        # finish UI setup
        self.txtTitle.setText("Add New Relationship")

        self.lastAddNeoID = None

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

    @pyqtSlot()
    def on_btnAddNew_clicked(self):
        """
        User clicks the add new button so add a new relationship
        """
        # validate the data entry
        if self.validate():
            QApplication.setOverrideCursor(Qt.WaitCursor)
            try:
                #  create a new relationship
                selectedFromNode = self.editRel.cmbFromNode.currentText()
                endIndex = selectedFromNode.find("]")
                fromNeoID = selectedFromNode[1:endIndex]
                selectedToNode = self.editRel.cmbToNode.currentText()
                endIndex = selectedToNode.find("]")
                toNeoID = selectedToNode[1:endIndex]
                createCypher = self.helper.genCreateRelCypher(
                    relInstanceDict=self.getObjectDict(),
                    fromNeoID=fromNeoID,
                    toNeoID=toNeoID)
                rc, msg = self.neoCon.runCypherExplicit(createCypher)
                QApplication.restoreOverrideCursor()
                if not rc == True:
                    self.helper.displayErrMsg("Create New Relationship", msg)
                else:
                    firstRec = self.neoCon.resultSet[0]
                    if not firstRec is None:
                        self.neoID = firstRec["id(r)"]
                        self.rel = firstRec["r"]
                    self.helper.displayErrMsg("Create New Relationship",
                                              "New Relationship Created.")
            except BaseException as e:
                self.helper.displayErrMsg(
                    "Create New Relationship",
                    "Error creating Relationship - {}".format(repr(e)))
            finally:
                QApplication.restoreOverrideCursor()

    def getObjectDict(self, ):
        '''
        This function returns a dictionary with all the data entered on the UI
        '''
        objectDict = {}
        objectDict["neoID"] = self.neoID
        objectDict["relName"] = self.templateDict["relname"]
        #save the attributes
        propList = []
        model = self.editRel.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, VALUE).data(Qt.EditRole)
            ]
            # only include properties that aren't null
            propList.append(relProp)
        objectDict["properties"] = propList

        return objectDict

    def validate(self, ):
        # make sure all properties have valid datatypes
        #        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
        # make sure all required properties have a value
        if not self.templateDict is None:
            model = self.editRel.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, VALUE).data(Qt.EditRole)
                ]
                if relProp[VALUE] == "Null":
                    #  the value is null so see if it is required
                    for templateProp in self.templateDict["properties"]:
                        if templateProp[PROPERTY] == relProp[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(relProp[PROPERTY]))
                                return False
        return True

    @pyqtSlot()
    def on_btnClose_clicked(self):
        """
        User Clicks the close button so close the dialog
        """
        QDialog.accept(self)
Exemplo n.º 19
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.º 20
0
class TPPropertyBox(QDialog, Ui_TPPropertyBox):
    """
    Class documentation goes here.
    """
    def __init__(self,
                 parent=None,
                 mode=None,
                 objectDict=None,
                 designModel=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(TPPropertyBox, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaObject
        self.settings = QSettings()
        self.helper = Helper()
        self.designModel = designModel
        self.modelData = self.designModel.modelData
        if objectDict is None:
            self.objectDict = self.designModel.newPathTemplateDict()
        else:
            self.objectDict = objectDict
        self.mode = mode

        # get the class that controls the data grid for relationship templates
        self.CypherGenPath = CypherGenPath(parent=self,
                                           templateDict=self.objectDict)

        # get neocon object for this project page
        self.neoCon = NeoDriver(name=self.parent.pageItem.neoConName,
                                promptPW=self.parent.pageItem.promptPW)

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

        # add the data grid widget.
        self.nodeGrid = DataGridWidget(self,
                                       neoCon=self.neoCon,
                                       genCypher=self.CypherGenPath)
        self.nodeGridLayout = QVBoxLayout(self.dataTabFrame)
        self.nodeGridLayout.setObjectName("nodeGridLayout")
        self.nodeGridLayout.addWidget(self.nodeGrid)

        self.populatingMetaBox = False

        #populate ui data from object
        self.populateUIfromObject()

        if self.mode == "NEW":
            self.txtPathTemplateName.setFocus()
        else:
            # disable definition fields
            self.txtPathTemplateName.setEnabled(False)

    def populateUIfromObject(self):
        self.txtPathTemplateName.setText(self.objectDict["name"])
        self.editDescription.appendPlainText(self.objectDict["description"])
        self.populateTree()

    def validate(self):
        if self.objectDict is None:
            self.objectDict = {}
        templateName = self.txtPathTemplateName.text()
        # template name
        if self.helper.NoTextValueError(templateName,
                                        "Must enter a Path Template Name"):
            self.txtPathTemplateName.setFocus()
            return False
        # find duplicate cypher variables
        varList = []
        # iterate through the treeview
        tvPathIterator = QTreeWidgetItemIterator(
            self.tvPath, flags=QTreeWidgetItemIterator.All)
        returnCount = 0
        while tvPathIterator:
            if not tvPathIterator.value() is None:
                queryPathNodeItem = tvPathIterator.value()
                thisQueryPathNode = queryPathNodeItem.data(0, Qt.UserRole)
                if thisQueryPathNode.type in ["Node", "Relationship"]:
                    returnCount = returnCount + thisQueryPathNode.numReturnProps(
                    )
                    if returnCount < 1:
                        self.helper.displayErrMsg(
                            thisQueryPathNode.templateName,
                            "Must return at least one property.")
                        return False
                    if self.helper.NoTextValueError(
                            thisQueryPathNode.templateName,
                            "Must supply a template name."):
                        return False
                    if self.helper.NoTextValueError(
                            thisQueryPathNode.cypherVar,
                            "Must supply a cypher variable for {}.".format(
                                thisQueryPathNode.templateName)):
                        return False
                    if thisQueryPathNode.cypherVar in varList:
                        self.helper.displayErrMsg(
                            "Validate Path",
                            "Error - duplicate cypher variable {} defined in template: {} "
                            .format(thisQueryPathNode.cypherVar,
                                    thisQueryPathNode.templateName))
                        return False
                    if thisQueryPathNode.templateName == "No Template Selected" and thisQueryPathNode.blankTemplate == False:
                        self.helper.displayErrMsg(
                            "Validate Path",
                            "Error - must select a template or set blank template to True "
                        )
                        return False

                    varList.append(thisQueryPathNode.cypherVar)
                tvPathIterator.__iadd__(1)
            else:
                break
        # passed all edits so return True
        return True

    def apply(self, ):
        '''save the object dictionary'''
        self.objectDict["name"] = self.txtPathTemplateName.text()
        self.objectDict["description"] = self.editDescription.toPlainText()
        nodeList = []
        # iterate through the treeview
        tvPathIterator = QTreeWidgetItemIterator(
            self.tvPath, flags=QTreeWidgetItemIterator.All)
        while tvPathIterator:
            if not tvPathIterator.value() is None:
                #                print("save node {}".format(tvPathIterator.value().text(0)))
                queryPathNodeItem = tvPathIterator.value()
                queryPathNodeDict = queryPathNodeItem.data(0,
                                                           Qt.UserRole).dict()
                nodeList.append(queryPathNodeDict)
                tvPathIterator.__iadd__(1)
            else:
                break

        self.objectDict["queryPath"] = nodeList
        self.designModel.setModelDirty()

#-----------------------------------------------------------------------------------------------------------------------
#  metabox grid methods
#-----------------------------------------------------------------------------------------------------------------------

    def createMetaBoxModel(self):
        #        ATTRNAME, ATTRVALUE  gridNodeMeta
        model = QStandardItemModel(0, 2)
        model.setHeaderData(ATTRNAME, Qt.Horizontal, "Attribute")
        model.setHeaderData(ATTRVALUE, Qt.Horizontal, "Value")
        return model

    def clearMetaBox(self):
        self.lblMetaHeader.setText("Definition")
        # metabox grid setup
        #        ATTRNAME, ATTRVALUE  gridMetaBox
        self.gridMetaBox.setModel(None)
        self.metaBoxModel = self.createMetaBoxModel()
        self.gridMetaBox.setModel(self.metaBoxModel)
        self.gridMetaBox.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.gridMetaBox.setSelectionMode(QAbstractItemView.SingleSelection)
        self.gridMetaBox.setColumnWidth(ATTRNAME, 150)
        self.gridMetaBox.setColumnWidth(ATTRVALUE, 300)
        # header
        header = self.gridMetaBox.horizontalHeader()
        header.setSectionResizeMode(ATTRNAME, QHeaderView.Fixed)
        header.setSectionResizeMode(ATTRVALUE, QHeaderView.Stretch)
        # set editor delegate
        self.gridMetaBox.setItemDelegateForColumn(ATTRVALUE,
                                                  MetaBoxDelegate(self))
        #        self.gridMetaBox.itemDelegateForColumn(ATTRVALUE).closeEditor.connect(self.metaBoxEditorClosed)
        # connect model slots
        self.metaBoxModel.itemChanged.connect(self.metaBoxModelItemChanged)
        # connect grid slots
        self.gridMetaBox.selectionModel().selectionChanged.connect(
            self.metaBoxGridSelectionChanged)

    def addMetaRow(self, attribute=None, value=None, editable=None):
        '''
        add a row to the  metabox grid
        '''
        #        print("add metarow {}-{}-{}".format(attribute, value, editable))
        self.gridMetaBox.setSortingEnabled(False)
        # attribute
        if attribute is None:
            attribute = ""
        item1 = QStandardItem(attribute)
        item1.setEditable(False)
        # value
        if value is None:
            value = ""
        item2 = QStandardItem(str(value))
        # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it
        item2.setData(attribute, Qt.UserRole)
        item2.setEditable(editable)

        self.gridMetaBox.model().appendRow([item1, item2])

    def populateMetaBox(self, queryPathNode=None):
        self.populatingMetaBox = True
        if not queryPathNode is None:
            self.clearMetaBox()
            self.lblMetaHeader.setText("{} Definition".format(
                queryPathNode.type))
            for attr in queryPathNode.attributes():
                self.addMetaRow(attribute=attr[0],
                                value=attr[1],
                                editable=attr[2])
        self.populatingMetaBox = False

    def metaBoxModelItemChanged(self, item):
        if self.populatingMetaBox == False:
            print("item data changed {} at row:{} col:{}".format(
                str(item.checkState()),
                item.index().row(),
                item.index().column()))
            # a cell changed so see if other changes are needed
            # update the queryPathNode object
            selected = self.tvPath.currentItem()
            if not (selected is None):
                queryPathNode = selected.data(0, Qt.UserRole)
                name = self.gridMetaBox.model().item(
                    item.index().row(), ATTRNAME).data(Qt.EditRole)
                value = self.gridMetaBox.model().item(
                    item.index().row(), ATTRVALUE).data(Qt.EditRole)
                queryPathNode.updateAttr(name=name, value=value)
                #            if name == "Blank Template" and value == "True":
                #                # set template name to first entry in list
                #                queryPathNode.updateAttr(name="Node Template", value="No Template Selected")
                # reload the metabox since changing one property can update others
                self.populateMetaBox(queryPathNode=queryPathNode)
                # repopulate return property grid as template may have
                self.populatePropBox(queryPathNode=queryPathNode)
                # update the tree view description
                queryPathNode.updateTreeView()
                # refresh cypher view
                self.refreshCypher()

    #        # update displayName
    #        self.rePopulateMetaBox(queryPathNode=queryPathNode)
    # force selection of this cell
            self.gridMetaBox.setCurrentIndex(item.index())

    def metaBoxGridSelectionChanged(self):
        # not used
        #        print("metabox grid selection changed")
        return

    def getCurrentCypherVar(self, ):
        # get the queryPathNode object
        selected = self.tvPath.currentItem()
        if not (selected is None):
            queryPathNode = selected.data(0, Qt.UserRole)
            return queryPathNode.cypherVar
        return None

#-----------------------------------------------------------------------------------------------------------------------
#  property box grid methods
#-----------------------------------------------------------------------------------------------------------------------

    def createPropBoxModel(self):
        #       PROPRETURN, PROPPARM, PROPNAME, COLNAME
        model = QStandardItemModel(0, 4)
        model.setHeaderData(PROPRETURN, Qt.Horizontal, "Return")
        model.setHeaderData(PROPPARM, Qt.Horizontal, "Parameter")
        model.setHeaderData(PROPNAME, Qt.Horizontal, "Property/Function")
        model.setHeaderData(COLNAME, Qt.Horizontal, "Column Name")
        return model

    def clearPropBox(self):
        # property box grid setup
        #       PROPRETURN, PROPPARM, PROPNAME, COLNAME
        self.gridPropBox.setModel(None)
        self.propBoxModel = self.createPropBoxModel()
        self.gridPropBox.setModel(self.propBoxModel)
        self.gridPropBox.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.gridPropBox.setSelectionMode(QAbstractItemView.SingleSelection)
        self.gridPropBox.setColumnWidth(PROPRETURN, 100)
        self.gridPropBox.setColumnWidth(PROPPARM, 100)
        self.gridPropBox.setColumnWidth(PROPNAME, 400)
        self.gridPropBox.setColumnWidth(COLNAME, 400)
        # header
        header = self.gridPropBox.horizontalHeader()
        header.setSectionResizeMode(PROPRETURN, QHeaderView.Fixed)
        header.setSectionResizeMode(PROPPARM, QHeaderView.Fixed)
        header.setSectionResizeMode(PROPNAME, QHeaderView.Stretch)
        header.setSectionResizeMode(COLNAME, QHeaderView.Stretch)
        # set editor delegate
        #        self.gridPropBox.setItemDelegateForColumn(PROPNAME, MetaBoxDelegate(self))
        # connect model slots
        self.propBoxModel.itemChanged.connect(self.propBoxModelItemChanged)
        # connect grid slots
        self.gridPropBox.selectionModel().selectionChanged.connect(
            self.propBoxGridSelectionChanged)

#    def addPropRow(self, propName=None, colName=None, propParm=None, propReturn=None ):

    def addPropRow(self, returnProp=None):
        '''
        add a row to the property box grid
        '''
        #  PROPRETURN, PROPPARM, PROPNAME, COLNAME
        self.gridPropBox.setSortingEnabled(False)
        # checkbox to add property to the return clause
        propReturn = returnProp[PROPRETURN]
        if propReturn is None or propReturn == "":
            propReturn = Qt.Unchecked
        item1 = QStandardItem()
        item1.setEditable(True)
        item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        #        # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it
        #        item1.setData(propReturn, Qt.UserRole)
        if propReturn in [0, 1, 2]:
            item1.setCheckState(propReturn)
        else:
            item1.setCheckState(Qt.Unchecked)
        item1.setText("")

        # checkbox to indicate property is a parameter
        propParm = returnProp[PROPPARM]
        if propParm is None or propParm == "":
            propParm = Qt.Unchecked
        item2 = QStandardItem()
        item2.setEditable(True)
        item2.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        #        # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it
        #        item2.setData(propParm, Qt.UserRole)
        if propParm in [0, 1, 2]:
            item2.setCheckState(propParm)
        else:
            item2.setCheckState(Qt.Unchecked)
        item2.setText("")

        # property name
        if returnProp[PROPNAME] is None:
            returnProp[PROPNAME] = ""
        item3 = QStandardItem(returnProp[PROPNAME])
        item3.setEditable(False)

        # column name
        if returnProp[COLNAME] is None or returnProp[COLNAME] == "":
            colName = "{}_{}".format(str(self.getCurrentCypherVar()),
                                     returnProp[PROPNAME])
        else:
            colName = returnProp[COLNAME]
        item4 = QStandardItem(colName)
        item4.setEditable(True)

        # add it to the grid
        self.gridPropBox.model().appendRow([item1, item2, item3, item4])

    def populatePropBox(self, queryPathNode=None):
        #        PROPRETURN, PARAMETER, PROPNAME, COLNAME
        if not queryPathNode is None:
            self.clearPropBox()
            for returnProp in queryPathNode.returnProps:
                self.addPropRow(returnProp)

    def propBoxModelItemChanged(self, item):
        # a cell changed so see if other changes are needed
        # update the queryPathNode object
        # print("item data changed {} {} at row:{} col:{}".format(str(item.checkState()), propName, item.index().row(), item.index().column()))
        selected = self.tvPath.currentItem()
        if not (selected is None):
            queryPathNode = selected.data(0, Qt.UserRole)
            propName = self.gridPropBox.model().item(
                item.index().row(), PROPNAME).data(Qt.EditRole)
            colName = self.gridPropBox.model().item(item.index().row(),
                                                    COLNAME).data(Qt.EditRole)
            propReturn = self.gridPropBox.model().item(
                item.index().row(), PROPRETURN).checkState()
            propParm = self.gridPropBox.model().item(item.index().row(),
                                                     PROPPARM).checkState()
            queryPathNode.updateProp([propReturn, propParm, propName, colName])
            self.refreshCypher()

        # force selection of this cell
        self.gridPropBox.setCurrentIndex(item.index())

    def propBoxGridSelectionChanged(self):
        # not used
        #        print("propbox grid selection changed")
        return

#-----------------------------------------------------------------------------------------------------------------------
#  tree view methods
#-----------------------------------------------------------------------------------------------------------------------

    def clearTree(self):
        self.tvPath.clear()
        self.tvPath.setColumnCount(1)
        self.tvPath.setHeaderLabels(["Query Path"])
        self.tvPath.setItemsExpandable(True)

    def getMaxTreeOrder(self, ):
        '''scan the tree and find the max order attribute.  This is used to increment and create the next highest order number'''
        max = 0
        # iterate through the treeview
        tvPathIterator = QTreeWidgetItemIterator(
            self.tvPath, flags=QTreeWidgetItemIterator.All)
        while tvPathIterator:
            if not tvPathIterator.value() is None:
                queryPathNodeItem = tvPathIterator.value()
                queryPathNodeDict = queryPathNodeItem.data(0,
                                                           Qt.UserRole).dict()
                order = queryPathNodeDict.get("order", 0)
                # save the value for order if it's greater
                if order > max:
                    max = order
                tvPathIterator.__iadd__(1)
            else:
                break
        return max

    def findParentWidget(self, findOrder=None):
        '''scan the tree and find the treeviewwidget with the matching parentOrder'''
        # if the find id is None then this is the root so return the tree view itself
        if findOrder is None:
            return self.tvPath
        # find the parent tree view widget
        parentWidget = None
        # iterate through the treeview
        tvPathIterator = QTreeWidgetItemIterator(
            self.tvPath, flags=QTreeWidgetItemIterator.All)
        while tvPathIterator:
            if not tvPathIterator.value() is None:
                queryPathNodeItem = tvPathIterator.value()
                queryPathNodeDict = queryPathNodeItem.data(0,
                                                           Qt.UserRole).dict()
                order = queryPathNodeDict.get("order", 0)
                # save the value for order if it's greater
                if order == findOrder:
                    parentWidget = queryPathNodeItem
                    break
                tvPathIterator.__iadd__(1)
            else:
                break

        return parentWidget

    def populateTree(self, ):
        self.clearTree()
        #        print("path dict {}".format(self.objectDict))
        # add tree items
        if len(self.objectDict["queryPath"]) > 0:
            for tvPathDict in self.objectDict["queryPath"]:
                # create the tree view path item object
                pathItem = QueryPathNode(designModel=self.designModel,
                                         nodeDict=tvPathDict)
                #                print("add path item {}".format(pathItem.displayName))
                # figure out who the parent is
                parent = self.findParentWidget(findOrder=pathItem.parentOrder)
                # add the treewidgetitem to the tree
                pathItem.treeItem = self.addTreeNode(parent=parent,
                                                     pathItem=pathItem)
        else:
            # add a new root node
            pathItem = QueryPathNode(designModel=self.designModel,
                                     root=True,
                                     parentOrder=None,
                                     order=0,
                                     type="Path Template")
            pathItem.treeItem = self.addTreeNode(parent=self.tvPath,
                                                 pathItem=pathItem)

        self.tvPath.resizeColumnToContents(0)
        self.tvPath.setCurrentItem(self.tvPath.topLevelItem(0))
        # update cypher
        self.refreshCypher()

    def addTreeNode(self, parent=None, pathItem=None):
        #        print("add tree node {}".format(pathItem.displayName))
        item = QTreeWidgetItem(parent, [pathItem.displayName])
        item.setData(0, Qt.UserRole, pathItem)
        item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
        item.setExpanded(True)
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                      | Qt.ItemIsEnabled)
        return item

###########################################################################################
# query path tree view methods
###########################################################################################

    def openMenu(self, position):
        selected = self.tvPath.currentItem()
        if not (selected is None):
            tvPathNode = selected.data(0, Qt.UserRole)
            if (tvPathNode.type == "Path Template"):
                menu = QMenu()
                addNodeAction = menu.addAction("Add Node")
                addNodeAction.triggered.connect(self.addNodeTreeNode)
                addNodeAction = menu.addAction("Add Relationship")
                addNodeAction.triggered.connect(self.addRelTreeNode)
                menu.exec_(self.tvPath.mapToGlobal(position))
                return
            if (tvPathNode.type == "Node"):
                menu = QMenu()
                #                addNodeAction = menu.addAction("Add Relationship")
                #                addNodeAction.triggered.connect(self.addRelTreeNode)
                subOutMenu = QMenu("Out Bound Relationships", parent=menu)
                # generate outboundrel menu items
                outBoundList = self.parent.model.getOutboundRelTemplates(
                    nodeTemplateName=tvPathNode.templateName)
                for relTemplate in outBoundList:
                    aSubAction = subOutMenu.addAction(relTemplate["name"])
                    aSubAction.setData(relTemplate)
                    aSubAction.triggered.connect(self.addOutboundRel)
#                    print("outboundlist {}".format(relTemplate))
                if len(outBoundList) == 0:
                    aSubAction = subOutMenu.addAction(
                        "No Outbound Relationship Templates")
                menu.addMenu(subOutMenu)

                subInMenu = QMenu("In Bound Relationships", parent=menu)
                # generate outboundrel menu items
                inBoundList = self.parent.model.getInboundRelTemplates(
                    nodeTemplateName=tvPathNode.templateName)
                for relTemplate in inBoundList:
                    aSubAction = subInMenu.addAction(relTemplate["name"])
                    aSubAction.setData(relTemplate)
                    aSubAction.triggered.connect(self.addInboundRel)
#                    print("inboundlist {}".format(relTemplate))
                if len(inBoundList) == 0:
                    aSubAction = subInMenu.addAction(
                        "No Inbound Relationship Templates")
                menu.addMenu(subInMenu)

                removeNodeAction = menu.addAction("Remove this Node")
                removeNodeAction.triggered.connect(self.removeTreeNode)
                menu.exec_(self.tvPath.mapToGlobal(position))
                return
            if (tvPathNode.type == "Relationship"):
                menu = QMenu()
                addNodeAction = menu.addAction("Add Node")
                addNodeAction.triggered.connect(self.addNodeTreeNode)
                addNodeAction = menu.addAction("Remove This Relationship")
                addNodeAction.triggered.connect(self.removeTreeNode)
                menu.exec_(self.tvPath.mapToGlobal(position))
                return

    def addOutboundRel(self):
        # get the action that called this method
        aSubAction = QObject.sender(self)
        relTemplateDict = aSubAction.data()
        if not relTemplateDict is None:
            # add the rel template to the path
            self.addRelTreeNode(templateName=relTemplateDict["name"])
            # add the node template to the path
            self.addNodeTreeNode(templateName=relTemplateDict["toTemplate"])

    def addInboundRel(self):
        # get the action that called this method
        aSubAction = QObject.sender(self)
        relTemplateDict = aSubAction.data()
        if not relTemplateDict is None:
            # add the rel template to the path
            self.addRelTreeNode(templateName=relTemplateDict["name"])
            # add the node template to the path
            self.addNodeTreeNode(templateName=relTemplateDict["fromTemplate"])

    def addRelTreeNode(self, templateName=None):
        '''add a relationship tree-node into the query path tree'''
        parentItem = self.tvPath.currentItem()
        parentDict = parentItem.data(0, Qt.UserRole).dict()
        parentOrder = parentDict.get("order", 0)
        order = self.getMaxTreeOrder() + 1
        #        print("add rel node {}-{}".format(order, parentOrder))
        queryPathNode = QueryPathNode(designModel=self.designModel,
                                      parentOrder=parentOrder,
                                      order=order,
                                      type="Relationship",
                                      templateName=templateName)
        # set a ypher variable name for the relationship
        queryPathNode.cypherVar = "r{}".format(
            str(queryPathNode.order).lower())
        #        queryPathNode.reltype = self.designModel.getRelType(templateName)
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)
        # update cypher
        self.refreshCypher()

    def addNodeTreeNode(self, templateName=None):
        '''add a node tree-node into the query path tree'''
        # don't know why this happens...
        if type(templateName) is bool:
            templateName = None
        parentItem = self.tvPath.currentItem()
        parentDict = parentItem.data(0, Qt.UserRole).dict()
        parentOrder = parentDict.get("order", 0)
        order = self.getMaxTreeOrder() + 1
        #        print("add node node {}-{}".format(order, parentOrder))
        queryPathNode = QueryPathNode(designModel=self.designModel,
                                      parentOrder=parentOrder,
                                      order=order,
                                      type="Node",
                                      templateName=templateName)
        # set a cypher variable name for the Node
        queryPathNode.cypherVar = "{}".format(
            queryPathNode.templateName[0].lower())
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)
        # update cypher
        self.refreshCypher()

    def removeTreeNode(self):
        '''remove a node from the tree and all descedants'''
        #        print("remove item")
        currentItem = self.tvPath.currentItem()
        parentItem = currentItem.parent()
        parentItem.removeChild(currentItem)
        self.tvPath.takeTopLevelItem(
            self.tvPath.indexOfTopLevelItem(currentItem))
        # update cypher
        self.refreshCypher()

    @pyqtSlot()
    def on_okButton_clicked(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)

    @pyqtSlot()
    def on_cancelButton_clicked(self):
        """
        User Selects the Cancel button
        """
        QDialog.reject(self)

    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
    def on_tvPath_currentItemChanged(self, current, previous):
        """
        Not Used
        
        @param current DESCRIPTION
        @type QTreeWidgetItem
        @param previous DESCRIPTION
        @type QTreeWidgetItem
        """
        print("on tvpath current item changed")
        return

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

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

    @pyqtSlot(QTreeWidgetItem, int)
    def on_tvPath_itemClicked(self, item, column):
        """
        Slot documentation goes here.
        
        @param item DESCRIPTION
        @type QTreeWidgetItem
        @param column DESCRIPTION
        @type int
        """
        print("on_tvPath_itemClicked")

    @pyqtSlot(QTreeWidgetItem, int)
    def on_tvPath_itemActivated(self, item, column):
        """
        Slot documentation goes here.
        
        @param item DESCRIPTION
        @type QTreeWidgetItem
        @param column DESCRIPTION
        @type int
        """
        print("on_tvPath_itemActivated")

    @pyqtSlot(QTreeWidgetItem, int)
    def on_tvPath_itemEntered(self, item, column):
        """
        Slot documentation goes here.
        
        @param item DESCRIPTION
        @type QTreeWidgetItem
        @param column DESCRIPTION
        @type int
        """
        print("on_tvPath_itemEntered")

    @pyqtSlot()
    def on_tvPath_itemSelectionChanged(self):
        """
        User clicks on an item in the tree view
        """
        print("on_tvPath_itemSelectionChanged")
        selected = self.tvPath.currentItem()
        if not (selected is None):
            #            parent = self.tvPath.currentItem().parent()
            queryPathNode = selected.data(0, Qt.UserRole)
            self.populateMetaBox(queryPathNode=queryPathNode)
            self.populatePropBox(queryPathNode=queryPathNode)

    @pyqtSlot(int)
    def on_tabPathTemplate_currentChanged(self, index):
        """
        The user has switched to a different tab
        DEFINITION, DESCRIPTION, DATAGRID
        @param index DESCRIPTION
        @type int
        """
        # user switched to the description tab.  regenerate the description
        #        if index == DESCRIPTION:
        #            if self.validate():
        #                self.apply()
        #                # get generated description
        #                self.brwsrGenDescription.setText(self.designModel.getNodeDescription(self.objectDict["name"]))

        # user switched to the definition tab.
        #        if index == DEFINITION:
        #            self.syncDefCheckBoxes()
        # user switched to the data grid then update object dictionary so query will generate with latest values
        if index == DATAGRID:
            if self.validate():
                self.apply()
                self.nodeGrid.refreshGrid()
            else:
                self.tabPathTemplate.setCurrentIndex(0)

    def refreshCypher(self):
        if self.validate():
            self.apply()
            self.cypher, self.editParmDict = self.nodeGrid.genCypher.genMatch()
            #                print("cypher:{}".format(self.cypher))
            self.txtCypher.clear()
            self.txtCypher.appendPlainText(self.cypher)

    @pyqtSlot()
    def on_btnAddNode_clicked(self):
        """
        user clicks on add node button
        """
        '''add a node tree-node into the query path tree'''
        parentItem = self.tvPath.currentItem()
        parentDict = parentItem.data(0, Qt.UserRole).dict()
        parentOrder = parentDict.get("order", 0)
        order = self.getMaxTreeOrder() + 1
        queryPathNode = QueryPathNode(designModel=self.designModel,
                                      parentOrder=parentOrder,
                                      order=order,
                                      type="Node",
                                      templateName="Anonymous Node")
        # set a cypher variable name for the Node
        queryPathNode.cypherVar = "n{}".format(
            str(queryPathNode.order).lower())
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)
        # update cypher
        self.refreshCypher()

    @pyqtSlot()
    def on_btnAddRel_clicked(self):
        """
        user clicks on add relationship button
        """
        parentItem = self.tvPath.currentItem()
        parentDict = parentItem.data(0, Qt.UserRole).dict()
        parentOrder = parentDict.get("order", 0)
        order = self.getMaxTreeOrder() + 1
        queryPathNode = QueryPathNode(designModel=self.designModel,
                                      parentOrder=parentOrder,
                                      order=order,
                                      type="Relationship",
                                      templateName="Anonymous Relationship")
        # set a ypher variable name for the relationship
        queryPathNode.cypherVar = "r{}".format(
            str(queryPathNode.order).lower())
        queryPathNode.treeItem = self.addTreeNode(parent=parentItem,
                                                  pathItem=queryPathNode)
        self.tvPath.setCurrentItem(queryPathNode.treeItem)
        # update cypher
        self.refreshCypher()

    @pyqtSlot()
    def on_btnRemove_clicked(self):
        """
        User clicks on remove button
        """
        return

    @pyqtSlot()
    def on_btnAdd_clicked(self):
        """
        Slot documentation goes here.
        """
        # TODO: not implemented yet
        raise NotImplementedError

    @pyqtSlot()
    def on_btnRemove_2_clicked(self):
        """
        Slot documentation goes here.
        """
        # TODO: not implemented yet
        raise NotImplementedError
Exemplo n.º 21
0
class NodeeraMain(QMainWindow, Ui_NodeeraMain):

    displayWelcomeMsg = pyqtSignal(str)
    closeWelcomeMsg = pyqtSignal()
    """
    Create Ui_NodeeraMain and display it.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(NodeeraMain, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent

        # object data
        self.pageDict = {}
        self.curPage = None
        self.helper = Helper()

        # get startup settings
        self.initSettings()

        # launch the welcome wagon
        self.welcomeDlg = HelloUserDlg(self)
        self.welcomeDlg.show()
        QApplication.processEvents()
        self.displayWelcomeMsg.emit("Starting NodeEra...")

        # setup stuff not covered by generated code
        icon = QIcon()
        icon.addPixmap(QPixmap("icons/RUN and DATA TAB/Disconnected.png"),
                       QIcon.Normal, QIcon.Off)
        self.actionOpen_Connection.setIcon(icon)

        # main window
        self.setTitle("No Connection")
        self.resize(self.winSize)
        self.move(self.position)

        # display welcome message
        self.displayWelcomeMsg.emit("Welcome To NodeEra")

        # display announcement message from website
        try:
            url = 'https://{}'.format(appPage)
            response = requests.get(url)
            if response.status_code == 200:
                start = response.text.find('class="display"') + 16
                end = response.text.find('</p>', start)
                self.displayWelcomeMsg.emit(response.text[start:end])
                self.displayWelcomeMsg.emit(
                    "For more information see www.singerlinks.com/nodeera")
            else:
                self.logMsg(
                    "Unable to access announcement page:{} http response:{}".
                    format(appPage, response.status_code))
                self.displayWelcomeMsg.emit(
                    "Unable to access announcement page:{} http response:{}".
                    format(appPage, response.status_code))

        except Exception as e:
            self.logMsg("Error accessing announcement page - {}".format(
                repr(e)))
            self.displayWelcomeMsg.emit(
                "Error accessing announcement page - {}".format(repr(e)))

        # auto open last used connection
        try:
            lastNeoConName = self.settings.value("NeoCon/LastUsed")
            if not lastNeoConName is None:
                neoDict = self.settings.value(
                    "NeoCon/connection/{}".format(lastNeoConName))
                self.openConnection(neoConName=lastNeoConName,
                                    neoConDict=neoDict)
                self.displayWelcomeMsg.emit(" ")
                self.displayWelcomeMsg.emit(
                    "Opened most recent connection: {}".format(lastNeoConName))

            self.setTitle(lastNeoConName)

        except:
            self.logMsg(
                "Unable to open last connection: {}".format(lastNeoConName))
            self.displayWelcomeMsg.emit(
                "Unable to open last connection: {}".format(lastNeoConName))

        # auto close the welcome dialog box
#        time.sleep(5)
#        self.closeWelcomeMsg.emit()

    def logMsg(self, msg):
        if logging:
            logging.info(msg)

    def initSettings(self, ):
        '''
        get the system settings needed to start NodeEra.
        If a system setting doesn't exist then create it with default value - this happens on initial startup
        '''
        self.settings = QSettings()
        try:
            self.expirationDate = self.helper.getText(
                self.settings.value("License/expirationDate"))
            if self.expirationDate is None:
                self.expirationDate = 'No expiration date set'
                self.settings.setValue(
                    "License/expirationDate",
                    self.helper.putText(self.expirationDate))
        except:
            self.expirationDate = 'No expiration date set'
            self.settings.setValue("License/expirationDate",
                                   self.helper.putText(self.expirationDate))

        try:
            self.currentVersion = self.settings.value("License/currentVersion")
            if self.currentVersion is None:
                self.currentVersion = currentVersion
                self.settings.setValue("License/currentVersion",
                                       self.currentVersion)
            elif self.currentVersion != currentVersion:
                self.currentVersion = currentVersion
                self.settings.setValue("License/currentVersion",
                                       self.currentVersion)
        except:
            self.currentVersion = currentVersion
            self.settings.setValue("License/currentVersion",
                                   self.currentVersion)

        try:
            self.winSize = self.settings.value("MainWindow/Size")
            if self.winSize is None:
                self.winSize = QSize(800, 500)
        except:
            self.winSize = QSize(800, 500)

        try:
            self.position = self.settings.value("MainWindow/Position")
            if self.position is None:
                self.position = QPoint(0, 0)
        except:
            self.position = QPoint(0, 0)

        try:
            self.recentList = self.settings.value("Default/RecentList")
            if self.recentList is None:
                self.recentList = []
                self.settings.setValue("Default/RecentList", self.recentList)
        except:
            self.recentList = []
            self.settings.setValue("Default/RecentList", self.recentList)

        try:
            self.defaultLoggingPath = self.settings.value(
                "Default/LoggingPath")
            if self.defaultLoggingPath is None:
                self.logDir = os.getcwd()
                self.logDir = os.path.realpath(os.path.abspath(self.logDir))
                self.settings.setValue("Default/LoggingPath", self.logDir)
        except:
            self.logDir = os.getcwd()
            self.logDir = os.path.realpath(os.path.abspath(self.logDir))
            self.settings.setValue("Default/LoggingPath", self.logDir)

        try:
            self.defaultProjPath = self.settings.value("Default/ProjPath")
            if self.defaultProjPath is None:
                self.defaultProjPath = os.getcwd()
                self.defaultProjPath = os.path.realpath(
                    os.path.abspath(self.defaultProjPath))
                self.settings.setValue("Default/ProjectPath",
                                       self.defaultProjPath)
        except:
            self.defaultProjPath = os.getcwd()
            self.defaultProjPath = os.path.realpath(
                os.path.abspath(self.defaultProjPath))
            self.settings.setValue("Default/ProjectPath", self.defaultProjPath)

        # default custom formats for diagram objects
        # Display Format - Instance Node
        try:
            test = self.settings.value("Default/Format/InstanceNode")
            if test is None:
                self.settings.setValue("Default/Format/InstanceNode",
                                       INodeFormat().formatDict)
        except:
            self.settings.setValue("Default/Format/InstanceNode",
                                   INodeFormat().formatDict)

        # Display Format - Instance Relationship
        try:
            test = self.settings.value("Default/Format/InstanceRelation")
            if test is None:
                self.settings.setValue("Default/Format/InstanceRelation",
                                       IRelFormat().formatDict)
        except:
            self.settings.setValue("Default/Format/InstanceRelation",
                                   IRelFormat().formatDict)

        # Display Format - Template Node
        try:
            test = self.settings.value("Default/Format/TemplateNode")
            if test is None:
                self.settings.setValue("Default/Format/TemplateNode",
                                       TNodeFormat().formatDict)
        except:
            self.settings.setValue("Default/Format/TemplateNode",
                                   TNodeFormat().formatDict)

        # Display Format - Template Relationship
        try:
            test = self.settings.value("Default/Format/TemplateRelation")
            if test is None:
                self.settings.setValue("Default/Format/TemplateRelation",
                                       TRelFormat().formatDict)
        except:
            self.settings.setValue("Default/Format/TemplateRelation",
                                   TRelFormat().formatDict)

        # page setup
        try:
            test = self.settings.value("Default/PageSetup")
            if test is None:
                self.settings.setValue("Default/PageSetup",
                                       PageSetup().objectDict)
        except:
            self.settings.setValue("Default/PageSetup", PageSetup().objectDict)

        # default project neocon
        try:
            defaultNeoConName = self.settings.value("NeoCon/Default")
            if defaultNeoConName is None:
                self.settings.setValue("NeoCon/Default", "LOCAL")
        except:
            self.settings.setValue("NeoCon/Default", "LOCAL")

        # LOCAL neocon definition
        try:
            self.localNeoCon = self.settings.value("NeoCon/connection/LOCAL")
            if self.localNeoCon is None:
                self.settings.setValue("NeoCon/connection/LOCAL",
                                       NeoDriver().localNeoConDict())
        except:
            self.settings.setValue("NeoCon/connection/LOCAL",
                                   NeoDriver().localNeoConDict())

        # default lexer font size
        try:
            defaultLexerFontSize = self.settings.value("Lexer/FontSize")
            if defaultLexerFontSize is None:
                self.settings.setValue("Lexer/FontSize", "10")
        except:
            self.settings.setValue("Lexer/FontSize", "10")

        # validate all neocons have the prompt dictionary key which was added in 1.04
        self.settings.beginGroup("NeoCon/connection")
        neoKeys = self.settings.childKeys()
        for key in neoKeys:
            neoDict = self.settings.value(key)
            promptVal = neoDict.get("prompt", None)
            if promptVal is None:
                neoDict["prompt"] = "False"
                self.settings.setValue(key, neoDict)

        self.settings.endGroup()

    def setTitle(self, filename):
        self.setWindowTitle("{} - {}".format(productName, filename))

    def setMenuAccess(self, ):
        '''
        determine what connection tab is currently selected and enable/disable menu items as needed
        '''
        # turn on all Settings menu items
        for action in self.menuSettings.actions():
            if type(action) == QAction and not action.isSeparator():
                action.setEnabled(True)

        try:
            # get tab widget (schema or project) that is currently active - if there isn't one this will cause an exception
            currentTab = self.stackedPageItems.currentWidget(
            ).tabPage.currentWidget()
            # enable all the schema actions
            for action in self.menuNeo.actions():
                if type(action) == QAction and not action.isSeparator():
                    action.setEnabled(True)
            for action in self.menuProject.actions():
                # enable project actions
                if type(action) == QAction and not action.isSeparator():
                    if currentTab.pageType == "PROJECT":
                        action.setEnabled(True)
                    else:
                        if action.text() in [
                                "New", "Open...", "Recent Projects"
                        ]:
                            action.setEnabled(True)
                        else:
                            action.setEnabled(False)

        except:
            # no connection tabs are open
            # disable all project menu actions
            for action in self.menuProject.actions():
                if type(action) == QAction and not action.isSeparator():
                    action.setEnabled(False)
            for action in self.menuNeo.actions():
                if type(action) == QAction and not action.isSeparator():
                    if action.text() in [
                            "Close Connection", "Generate Schema..."
                    ]:
                        action.setEnabled(False)
                    else:
                        action.setEnabled(True)

    ########################################################################
    #     NEO CONNECTION Dropdown Menu Actions
    ########################################################################
    @pyqtSlot()
    def on_actionOpen_Connection_triggered(self):
        """
        This slot provides functionality for the open connection button
        """
        d = dlgNeoCons(parent=self)
        if d.exec_():
            if d.selectedNeoConName:
                # make sure it isn't already opened
                if d.selectedNeoConName not in self.pageDict:
                    self.openConnection(neoConName=d.selectedNeoConName,
                                        neoConDict=d.selectedNeoConDict)
                else:
                    self.helper.displayErrMsg(
                        "NodeEra - Open Connection",
                        "Connection {} is already open.".format(
                            d.selectedNeoConName))
                    # switch to the page they tried to open
                    self.pageDict[d.selectedNeoConName].actionButton.trigger()

    def openConnection(self, neoConName=None, neoConDict=None):
        '''
        User selects a connection to open so create the schema page and display it
        '''
        # set the last used neocon in system settings
        self.settings.setValue("NeoCon/LastUsed", neoConName)
        # create a new toolbar button and add it to the toolbar
        newConAction = QAction(self)
        newConAction.setObjectName("newConnection")
        newConAction.setText("Neo4j - {}".format(neoConName))
        newConAction.setData(neoConName)

        newConAction.setToolTip(neoConDict["URL"])
        newConAction.setCheckable(True)
        newConAction.setChecked(True)
        newConAction.triggered.connect(self.connectionClicked)
        self.tbConnection.addAction(newConAction)

        # add a tabPage widget to the stacked widget
        newPageWidget = PageWidget(parent=self)
        newPageWidget.pageType = "Schema"
        widgetIndex = self.stackedPageItems.addWidget(newPageWidget)
        # save new pageItem
        newPageItem = PageItem(neoConName=neoConName,
                               actionButton=newConAction,
                               pageWidget=newPageWidget,
                               pageWidgetIndex=widgetIndex)
        self.pageDict[neoConName] = newPageItem
        # add the schema tab
        cypherTab = CypherPageWidget(parent=self, pageItem=newPageItem)
        newPageWidget.tabPage.addTab(cypherTab,
                                     "Schema - {}".format(neoConName))

        # click the new action to force selection logic
        newConAction.trigger()
        self.logMsg("Open Connection: {}".format(neoConName))

    @pyqtSlot()
    def connectionClicked(self):
        '''
        User clicks on a connection in the menu bar so switch to that stacked widget
        '''
        self.logMsg("connection clicked {}".format(self.sender().text()))
        # uncheck all the page action buttons
        for pageName in self.pageDict:
            self.pageDict[pageName].actionButton.setChecked(False)
        # check the one just clicked
        self.sender().setChecked(True)
        # save the current page name
        self.curPage = self.sender().data()
        # switch the stacked page widget to the one just clicked
        self.stackedPageItems.setCurrentIndex(
            self.pageDict[self.curPage].pageWidgetIndex)
        # update the main window title
        self.setTitle(self.curPage)
        # adjust the menu items
        self.setMenuAccess()

    @pyqtSlot()
    def on_actionClose_Connection_triggered(self):
        """
        Close the active connection and remove the page from the UI
        """
        if self.curPage:
            if self.curPage in self.pageDict:
                # must find the schema tab and tell it to close, it will tell all other tabs to close.  schema tab is always the first one (index=0)
                self.pageDict[self.curPage].pageWidget.closeSchemaTab()
                self.removeConnection()

    def removeConnection(self, ):
        '''if the tab page widget is responding to the close request it only needs this logic to remove the connection
        '''
        curPage = self.pageDict.get(self.curPage, None)
        if not curPage is None:
            # remove the pageWidget from the stacked widget
            self.stackedPageItems.removeWidget(
                self.pageDict[self.curPage].pageWidget)
            del self.pageDict[self.curPage].pageWidget
            # remove the action from menu's and toolbars
            self.tbConnection.removeAction(
                self.pageDict[self.curPage].actionButton)
            # take the page out of the dictionary
            del self.pageDict[self.curPage]
            # if any pages left select the first one
            if len(self.pageDict) > 0:
                for pageName in self.pageDict:
                    self.pageDict[pageName].actionButton.trigger()
                    break
            else:
                # there are no open connections and the home page is closed
                self.setTitle("No Connection")

    @pyqtSlot()
    def on_actionNeo4j_Connection_Manager_triggered(self):
        """
        Display the Connection Manager
        """
        d = dlgNeoCons(parent=self)
        if d.exec_():
            pass

    @pyqtSlot()
    def on_actionExit_triggered(self):
        """
        User selected the File / Exit menu item.  Tell all the open connections to close
        """
        self.closeOpenStuff()
        # close the app
        self.close()

    def closeEvent(self, event):
        # close open connections
        self.closeOpenStuff()
        #save the window state
        self.settings.setValue("MainWindow/Size", self.size())
        self.settings.setValue("MainWindow/Position", self.pos())

        event.accept()

    def closeOpenStuff(self):
        # get a list of all the keys in the pageDict dictionary
        keys = list(self.pageDict.keys())
        # iterate thru the list of dictionary keys.  this is required as the dictionary will be changing size, i.e. you can't simply iterate thru the dictionary
        for key in keys:
            pageItem = self.pageDict[key]
            actionButton = pageItem.actionButton
            actionButton.trigger()
            # must find the schema tab and tell it to close, it will tell all other tabs to close.  schema tab is always the first one (index=0)
            pageItem.pageWidget.closeSchemaTab()
            self.removeConnection()

#####################################################################
# SETTINGS DROPDOWNS
#####################################################################

    @pyqtSlot()
    def on_actionSystem_Preferences_triggered(self):
        """
        User selects the System Preferences menu item.  Display the system preferences dialog box.
        """
        self.editSystemPreferences()

    def editSystemPreferences(self, ):
        """
        User selects the System Preferences menu item.  Display the system preferences dialog box.
        """
        if not (self.settings is None):
            d = SystemPreferenceBox(self, settings=self.settings)
            if d.exec_():
                self.settings.sync()

#####################################################################
# PROJECT METHODS
#####################################################################

    @pyqtSlot()
    def on_actionNewProject_triggered(self):
        """
        Open new project
        """
        self.loadProject(fileName=None)

    @pyqtSlot()
    def on_actionOpenProject_triggered(self):
        """
        Open an existing project file
        """
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.ExistingFile)
        dlg.setAcceptMode(QFileDialog.AcceptOpen)
        dlg.setNameFilters(["NodeEra Project (*.mdl)", "all files (*.*)"])
        dlg.setDirectory(self.settings.value("Default/ProjPath"))
        if dlg.exec_():
            fileNames = dlg.selectedFiles()
            if fileNames:
                fileName = fileNames[0]
                self.loadProject(fileName=fileName)

    @pyqtSlot()
    def on_actionSaveProject_triggered(self):
        """
        Save the open project
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                newName = curPageWidget.tabPage.currentWidget().saveProject()
                # if this was an unsaved project then it was really a save as so the tab name has to be updated
                if newName is not None:
                    curPageWidget.tabPage.setTabText(
                        curPageWidget.tabPage.currentIndex(),
                        "Project: {} - {}".format(
                            newName, self.pageDict[self.curPage].neoConName))

    @pyqtSlot()
    def on_actionSaveProjectAs_triggered(self):
        """
        Save Project As
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                newName = curPageWidget.tabPage.currentWidget().saveProjectAs()
                if newName is not None:
                    curPageWidget.tabPage.setTabText(
                        curPageWidget.tabPage.currentIndex(),
                        "Project: {} - {}".format(
                            newName, self.pageDict[self.curPage].neoConName))

    @pyqtSlot()
    def on_actionReverse_Engineer_triggered(self):
        """
        User selects the Reverse Engineer menu item.  Display the Reverse Engineer dialog box.
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                curPageWidget.tabPage.currentWidget().reverseEngineerGraph()
            else:
                self.helper.displayErrMsg(
                    "Reverse Engineer", "{} not a project".format(
                        curPageWidget.tabPage.currentWidget().pageType))

    @pyqtSlot()
    def on_actionProjectProperties_triggered(self):
        """
        User selects the project properties menu item. Display the project properties dialog box.
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                curPageWidget.tabPage.currentWidget().editProjectProperties()
            else:
                self.helper.displayErrMsg(
                    "Project Properties", "{} not a project".format(
                        curPageWidget.tabPage.currentWidget().pageType))

    def getSchemaObject(self, ):
        curPageWidget = self.stackedPageItems.currentWidget()
        for ctr in range(0, curPageWidget.tabPage.count()):
            tab = curPageWidget.tabPage.widget(ctr)
            if tab.pageType == "CYPHER":
                return tab.schemaModel
        return None

    def getSchemaTab(self, ):
        curPageWidget = self.stackedPageItems.currentWidget()
        for ctr in range(0, curPageWidget.tabPage.count()):
            tab = curPageWidget.tabPage.widget(ctr)
            if tab.pageType == "CYPHER":
                return tab
        return None

    def loadProject(self, fileName=None):
        '''
        Load the project file with name fileName.  If fileName is None, create a new empty project.
        '''

        # create a temporary file name if its a new project
        if fileName is None:
            # update unnamed file counter
            global unNamedFileCounter
            unNamedFileCounter = unNamedFileCounter + 1
            shortName = "{}".format(
                "New Project-0{}".format(unNamedFileCounter))
        else:
            head, shortName = ntpath.split(QFileInfo(fileName).fileName())

        # make sure the project isn't already loaded
        curPageWidget = self.stackedPageItems.currentWidget()
        for ctr in range(0, curPageWidget.tabPage.count()):
            tab = curPageWidget.tabPage.widget(ctr)
            if tab.pageType == "PROJECT":
                if (not fileName is None) and (tab.fileName == fileName):
                    self.helper.displayErrMsg(
                        "Open Project",
                        "The project file: {} is already open.  It can only be open once."
                        .format(fileName))
                    return

        # create the project widget
        projectTab = ProjectPageWidget(parent=self,
                                       settings=self.settings,
                                       pageItem=self.pageDict[self.curPage],
                                       fileName=fileName)

        curPageWidget = self.stackedPageItems.currentWidget()

        # add the project widget as a tab on the current page widget
        x = curPageWidget.tabPage.addTab(
            projectTab,
            "Project: {} - {}".format(shortName,
                                      self.pageDict[self.curPage].neoConName))
        curPageWidget.tabPage.setCurrentIndex(x)

    @pyqtSlot()
    def on_actionOnline_Help_triggered(self):
        """
        User selects the Online Help menu item.  Dislay Online Help menu.
        """
        d = OnlineHelpDLG(self)
        if d.exec_():
            pass

    @pyqtSlot()
    def on_actionAbout_triggered(self):
        """
        User selects Help / About menu item.  Display the about dialog box
        """
        d = HelpAboutDLG(self)
        if d.exec_():
            pass

    @pyqtSlot()
    def on_actionGenerate_Schema_triggered(self):
        """
        User requests the generate schema dialog box from main menu
        """
        d = GenerateSchemaDlg(self)
        if d.exec_():
            pass

    @pyqtSlot()
    def on_actionForward_Engineer_triggered(self):
        """
        User requests to perform forward engineering from the open project from main menu
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                curPageWidget.tabPage.currentWidget().forwardEngineerGraph()
            else:
                self.logMsg(
                    "User requested to forward engineer but current tab is not a Project"
                )

    @pyqtSlot()
    def on_actionGenerate_Reports_triggered(self):
        """
        User selects the Generate Project Reports menu item.  Display the Generate Reports dialog box.
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "PROJECT":
                curPageWidget.tabPage.currentWidget().generateProjectReports()

    @pyqtSlot()
    def on_actionReset_User_Password_triggered(self):
        """
        User requests the change user password from main menu
        """
        curPageWidget = self.stackedPageItems.currentWidget()
        if not curPageWidget is None:
            if curPageWidget.tabPage.currentWidget().pageType == "CYPHER":
                curPageWidget.tabPage.currentWidget().resetPassword()

    @pyqtSlot(QAction)
    def on_menuRecent_Projects_triggered(self, action):
        """
        User clicked on a recent file menu item
        
        @param action DESCRIPTION
        @type QAction
        """
        self.loadProject(fileName=action.data())

    @pyqtSlot()
    def on_menuRecent_Projects_aboutToShow(self):
        """
        user hovering on recent projects menu item
        """
        recentList = self.settings.value("Default/RecentList")
        if len(recentList) == 0:
            return
        else:
            # remove any existing actions
            self.menuRecent_Projects.clear()
            for projectFile in recentList:
                # create actions for the recent files
                aSubAction = self.menuRecent_Projects.addAction(projectFile)
                aSubAction.setData(projectFile)
Exemplo n.º 22
0
class CopyNodeToDiagramDlg(QDialog, Ui_CopyNodeToDiagramDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, rightClickPos=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CopyNodeToDiagramDlg, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.settings = QSettings()
        self.rightClickPos = rightClickPos
        self.designModel = self.parent.model
        self.syncNeoCon = self.designModel.modelNeoCon
        self.itemDict = self.parent.itemDict
        self.helper = Helper()
        self.neoTypeFunc = NeoTypeFunc()
        self.nodeGrid = None

        # header area
        self.txtDiagramName.setText(self.parent.diagramName)
        self.txtNeoCon.setText("{} - {}".format(
            self.syncNeoCon.name, self.syncNeoCon.neoDict["URL"]))

        # load  node template dropdown and disable it
        dropdownList = []
        dropdownList.append("No Template Selected")
        dropdownList.extend(
            sorted(self.designModel.instanceList("Node Template")))
        self.cboNodeTemplates.addItems(dropdownList)
        self.rbFilterTemplate.setChecked(True)

        # get neocon object for this project page
        self.neoCon = NeoDriver(name=self.parent.parent.pageItem.neoConName,
                                promptPW=self.parent.parent.pageItem.promptPW)

        # add the data grid widget.
        self.addNodeCypher = AddNodeCypher()

        self.nodeGrid = DataGridWidget(self,
                                       neoCon=self.neoCon,
                                       genCypher=self.addNodeCypher)
        self.nodeGridLayout = QVBoxLayout(self.frmDataGrid)
        self.nodeGridLayout.setObjectName("nodeGridLayout")
        self.nodeGridLayout.addWidget(self.nodeGrid)

    def refreshDatabaseNodeGrid(self, ):
        # first make sure the node grid has been created.
        if self.nodeGrid is None:
            return

        nodeTemplate = self.cboNodeTemplates.currentText()
        index, nodeTemplateDict = self.designModel.getDictByName(
            topLevel="Node Template", objectName=nodeTemplate)
        if self.rbFilterTemplate.isChecked() == True:
            useTemplate = True
        else:
            useTemplate = False

        self.nodeGrid.refreshGrid(useTemplate=useTemplate,
                                  nodeTemplate=nodeTemplate,
                                  nodeTemplateDict=nodeTemplateDict)

    def getNodeRels(self, nodeID, nodeIDList):
        '''
        Run a query that retrieves all relationships between one node and all other nodes on a diagram
        '''
        try:
            msg = None
            p1 = str(nodeID)
            p2 = str(nodeIDList)
            cypher = '''match (f)-[r]->(t)
                            where ((id(f) = {} and id(t) in {}) or (id(t) = {} and id(f) in {}))
                            return id(f),
                                    f.NZID,
                                    f, 
                                    id(r), 
                                    type(r),
                                    r.NZID,
                                    r,
                                    id(t),
                                    t.NZID,
                                    t
                            '''.format(p1, p2, p1, p2)
            #            print(cypher)
            #run the query
            rc1, msg1 = self.syncNeoCon.runCypherAuto(cypher)

        except BaseException as e:
            msg = "{} - Get Relationships failed.".format(repr(e))
        finally:
            QApplication.restoreOverrideCursor()
            if not msg is None:
                self.helper.displayErrMsg("Error Retrieving Relationships.",
                                          msg)

#####################################################################################
# dialog buttons
#####################################################################################

    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Slot documentation goes here.
        
        @param button DESCRIPTION
        @type QAbstractButton
        """

    @pyqtSlot()
    def on_buttonBox_accepted(self):
        """
        
        """
        QDialog.accept(self)

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

    @pyqtSlot()
    def on_rbAllNodes_clicked(self):
        """
        User selects all Nodes option, so configure UI and refresh the grid
        """
        self.cboNodeTemplates.setEnabled(False)
        self.refreshDatabaseNodeGrid()

    @pyqtSlot()
    def on_rbFilterTemplate_clicked(self):
        """
        User selects filter on template option
        """
        self.cboNodeTemplates.setEnabled(True)
        self.refreshDatabaseNodeGrid()

    @pyqtSlot(int)
    def on_cboNodeTemplates_currentIndexChanged(self, index):
        """
        User changes the node template selection so refresh the grid
        
        @param index DESCRIPTION
        @type int
        """
        self.refreshDatabaseNodeGrid()

    @pyqtSlot()
    def on_btnAdd_clicked(self):
        """
        User clicks the add button so add the selected nodes to the diagram
        """

        applyX = self.rightClickPos.x()
        applyY = self.rightClickPos.y()
        QApplication.setOverrideCursor(Qt.WaitCursor)
        if self.rbAllNodes.isChecked() == True:
            ID, LBLS, PROPS, NODE = range(4)
        else:
            ID, NODE = range(2)
        # get the selected node template
        if self.rbFilterTemplate.isChecked() == True:
            nodeTemplate = self.cboNodeTemplates.currentText()
        else:
            nodeTemplate = None

        # make sure there is data in the grid
        if self.nodeGrid.gridCypherData.selectionModel() is None:
            self.helper.displayErrMsg("Copy Node",
                                      "Select a Node Template or All Nodes.")
            QApplication.restoreOverrideCursor()
            return

        # check if any rows selected
        if len(self.nodeGrid.gridCypherData.selectionModel().selectedRows(
                column=0)) > 0:
            # looking at nodes in the database
            for itemIndex in self.nodeGrid.gridCypherData.selectionModel(
            ).selectedRows(column=0):
                # get the Neo4j node id from the result set
                instanceNodeID = self.nodeGrid.gridCypherData.model().item(
                    itemIndex.row(), ID).data(Qt.EditRole)
                # see if it's already an instance node in the model
                instanceNZID = self.designModel.lookupNZID(
                    neoID=instanceNodeID, topLevel="Instance Node")
                # get the node object
                n = self.nodeGrid.gridCypherData.model().item(
                    itemIndex.row(), NODE).data(Qt.UserRole)
                # get the list of labels from the node object
                lbls = [[lbl] for lbl in n.labels]
                # get the dictionary of properties from the node object  and convert it to a list
                props = []
                for key, val in dict(n).items():
                    props.append([
                        key,
                        self.neoTypeFunc.getNeo4jDataType(value=val),
                        self.neoTypeFunc.convertTypeToString(dataValue=val)
                    ])
                # create a new node instance dictionary
                newNodeInstanceDict = self.designModel.newNodeInstance(
                    nodeID=instanceNodeID,
                    templateName=nodeTemplate,
                    labelList=lbls,
                    propList=props,
                    NZID=instanceNZID)
                NZID = self.parent.scene.dropINode(
                    point=QPointF(applyX, applyY),
                    nodeInstanceDict=newNodeInstanceDict,
                    NZID=instanceNZID)

                if not NZID is None:
                    applyX = applyX + 200

            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg(
                "Copy Node", "Selected Node(s) were added to diagram.")
            # update project model
            self.parent.model.setModelDirty()
            self.parent.model.updateTV()
        else:
            QApplication.restoreOverrideCursor()
            self.helper.displayErrMsg(
                "Copy Node", "You must select a node to add to the diagram.")

        QApplication.restoreOverrideCursor()
Exemplo n.º 23
0
class NeoEditDelegate(QStyledItemDelegate):
    def __init__(self, owner):
        super(NeoEditDelegate, self).__init__(owner)
        self.booleanItems = ["True", "False"]
        self.hintSize = None
        self.helper = Helper()

    def createEditor(self, parent, option, index):
        # create the appropriate widget based on the datatype
        dataType = index.data(Qt.UserRole + 1)
        self.hintSize = QSize(option.rect.width(), option.rect.height())
        if dataType == DataType.INT.value:
            self.editor = QLineEdit(parent)
            self.editor.setValidator(QIntValidator())
        elif dataType == DataType.FLOAT.value:
            self.editor = QLineEdit(parent)
            self.editor.setValidator(QDoubleValidator())
        elif dataType == DataType.STRING.value:
            self.editor = QLineEdit(parent)
        elif dataType == DataType.BOOLEAN.value:
            self.editor = QComboBox(parent)
            self.editor.addItems(self.booleanItems)
        elif dataType == DataType.POINTWGS84.value:
            self.editor = FrmGPoint(parent)
            self.editor.setAutoFillBackground(True)
            self.hintSize = QSize(300, 40)
        elif dataType == DataType.POINTCARTESIAN.value:
            self.editor = FrmPoint(parent)
            self.editor.setAutoFillBackground(True)
            self.hintSize = QSize(300, 40)
        elif dataType == DataType.TIME.value:
            #            self.editor = FrmTime(parent=parent, tz=True)
            self.editor = QLineEdit(parent)
        elif dataType == DataType.LOCALTIME.value:
            #            self.editor = FrmTime(parent=parent, tz=False)
            self.editor = QLineEdit(parent)
        elif dataType == DataType.DATE.value:
            self.editor = QDateTimeEdit(parent)
            self.editor.setCalendarPopup(True)
            self.editor.setDisplayFormat("yyyy/MM/dd")
        elif dataType == DataType.DATETIME.value:
            #            self.editor = QDateTimeEdit(parent)
            #            self.editor.setCalendarPopup(False)
            #            self.editor.setDisplayFormat("yyyy-MM-dd hh:mm:ss:zzz")
            self.editor = QLineEdit(parent)
        elif dataType == DataType.LOCALDATETIME.value:
            self.editor = QLineEdit(parent)
        elif dataType == DataType.DURATION.value:
            self.editor = QLineEdit(parent)
        else:
            self.editor = QLineEdit(parent)

        return self.editor

    def setEditorData(self, editor, index):
        '''
        this takes the string stored in the grid cell and populates the editor widget with that value.
        '''
        value = index.data(Qt.DisplayRole)
        dataType = index.data(Qt.UserRole + 1)
        if isinstance(editor, QLineEdit):
            if ((dataType == DataType.INT.value
                 or dataType == DataType.FLOAT.value) and value == "Null"):
                editor.setText("")
            else:
                editor.setText(value)
        # boolean datatype uses a listbox
        elif isinstance(editor, QComboBox):
            try:
                num = self.booleanItems.index(value)
                editor.setCurrentIndex(num)
            except:
                editor.setCurrentIndex(0)
        elif isinstance(editor, FrmPoint):
            self.editor.setText(value)
        elif isinstance(editor, FrmGPoint):
            self.editor.setText(value)
        elif isinstance(editor, QDateTimeEdit):
            try:
                aDate = None
                if dataType == DataType.DATE.value:
                    if value == "Null":
                        aDate = QDate.currentDate()
                        editor.setDate(aDate)
                    else:
                        aDate = QDate.fromString(value, format=Qt.ISODate)
                        editor.setDate(aDate)

            except:
                pass

    def setModelData(self, editor, model, index):
        '''
        this takes the current value of the editor widget, converts it back to a string and stores it back in the item model
        '''
        dataType = index.data(Qt.UserRole + 1)
        # string, integer, and float use a qlineedit
        if isinstance(editor, QLineEdit):
            if dataType == DataType.DATETIME.value:
                value = editor.text()
                try:
                    aNeoDateTime = DateTime.from_iso_format(value)
                    saveStr = aNeoDateTime.iso_format()
                    model.setData(index, saveStr, Qt.DisplayRole)
                except:
                    self.helper.displayErrMsg(
                        "Date Time",
                        "Entered text [{}] is not a valid ISO date time.".
                        format(value))
                    model.setData(index, value, Qt.DisplayRole)
            elif dataType == DataType.LOCALDATETIME.value:
                value = editor.text()
                try:
                    aNeoDateTime = DateTime.from_iso_format(value)
                    saveStr = aNeoDateTime.iso_format()
                    model.setData(index, saveStr, Qt.DisplayRole)
                except:
                    self.helper.displayErrMsg(
                        "Date Time",
                        "Entered text [{}] is not a valid ISO date time.".
                        format(value))
                    model.setData(index, value, Qt.DisplayRole)
            elif dataType in [DataType.LOCALTIME.value, DataType.TIME.value]:
                value = editor.text()
                try:
                    aNeoTime = Time.from_iso_format(value)
                    saveStr = aNeoTime.iso_format()
                    tz = ""
                    if aNeoTime.tzinfo is not None:
                        offset = aNeoTime.tzinfo.utcoffset(self)
                        tz = "%+03d:%02d" % divmod(
                            offset.total_seconds() // 60, 60)
                    returnStr = "{}{}".format(saveStr, tz)
                    model.setData(index, returnStr, Qt.DisplayRole)
                except:
                    self.helper.displayErrMsg(
                        "Time",
                        "Entered text [{}] is not a valid ISO time.".format(
                            value))
                    model.setData(index, value, Qt.DisplayRole)
            else:
                value = editor.text()
                model.setData(index, value, Qt.DisplayRole)
        # boolean datatype uses a listbox
        elif isinstance(editor, QComboBox):
            value = editor.currentText()
            model.setData(index, value, Qt.DisplayRole)
        elif isinstance(editor, FrmPoint):
            value = editor.getText()
            model.setData(index, value, Qt.DisplayRole)
        elif isinstance(editor, FrmGPoint):
            value = editor.getText()
            model.setData(index, value, Qt.DisplayRole)
#        elif isinstance(editor, FrmTime ):
#            value = editor.getText()
#            model.setData(index, value, Qt.DisplayRole)
#        elif isinstance(editor, FrmDateTime ):
#            value = editor.getText()
#            model.setData(index, value, Qt.DisplayRole)
        elif isinstance(editor, QDateTimeEdit):
            if dataType == DataType.DATE.value:
                value = editor.date().toString(Qt.ISODate)
                model.setData(index, value, Qt.DisplayRole)
#            if dataType == DataType.DATETIME.value:
#                value = editor.dateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")
##                print("editor datetime: {} editor string: {}".format(editor.dateTime(), value))
#                model.setData(index, value, Qt.DisplayRole)

    def updateEditorGeometry(self, editor, option, index):
        dataType = index.data(Qt.UserRole + 1)
        if isinstance(editor, FrmPoint):
            editor.setGeometry(
                QRect(option.rect.left(), option.rect.top(),
                      editor.frameGeometry().width(),
                      editor.frameGeometry().height()))
            self.editor.parent().parent().setRowHeight(
                index.row(),
                editor.frameGeometry().height())
            self.editor.parent().parent().setColumnWidth(
                index.column(),
                editor.frameGeometry().width())
        elif isinstance(editor, FrmGPoint):
            editor.setGeometry(
                QRect(option.rect.left(), option.rect.top(),
                      editor.frameGeometry().width(),
                      editor.frameGeometry().height()))
            self.editor.parent().parent().setRowHeight(
                index.row(),
                editor.frameGeometry().height())
            self.editor.parent().parent().setColumnWidth(
                index.column(),
                editor.frameGeometry().width())
        elif isinstance(editor, QDateTimeEdit):
            if dataType == DataType.DATETIME.value:
                # for some reason, the qdatetimeedit doesn't know it's width so we hard code it
                editor.setGeometry(
                    QRect(option.rect.left(), option.rect.top(), 180,
                          editor.frameGeometry().height()))
                self.editor.parent().parent().setRowHeight(
                    index.row(),
                    editor.frameGeometry().height())
                self.editor.parent().parent().setColumnWidth(
                    index.column(),
                    editor.frameGeometry().width())
            else:
                editor.setGeometry(
                    QRect(option.rect.left(), option.rect.top(), 100,
                          editor.frameGeometry().height()))
                self.editor.parent().parent().setRowHeight(
                    index.row(),
                    editor.frameGeometry().height())
                self.editor.parent().parent().setColumnWidth(
                    index.column(),
                    editor.frameGeometry().width())

        else:
            editor.setGeometry(option.rect)
Exemplo n.º 24
0
class EditRelWidget(QWidget, Ui_EditRelWidget):
    """
    This widget provides a generic UI to enter a relationship
    """
    def __init__(self, parent=None, templateDict=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(EditRelWidget, self).__init__(parent)
        self.setupUi(self)
        self.helper = Helper()
        self.parent = parent
        self.neoCon = self.parent.neoCon
        self.designModel = self.parent.designModel
        self.templateDict = templateDict
        # rel type
        self.lblRelationship.setText("Relationship Type: {}".format(
            self.templateDict["relname"]))
        # from Node
        self.lblFromNode.setText("From Node - Node Template: {}".format(
            self.templateDict["fromTemplate"]))
        index, nodeTemplateDict = self.designModel.getDictByName(
            topLevel="Node Template",
            objectName=self.templateDict["fromTemplate"])
        fromNodeTemplateCypher = NodeTemplateCypher(
            templateDict=nodeTemplateDict)
        getNodesCypher, x = fromNodeTemplateCypher.genMatchReturnNodeOnly()
        #        print(getNodesCypher)
        rc, msg = self.runCypher(
            requestType="Get From Nodes based on Node Template: {}".format(
                self.templateDict["fromTemplate"]),
            cypher=getNodesCypher)
        if rc == True:
            self.loadNodeDropDown(dropDown=self.cmbFromNode)
        else:
            self.helper.displayErrMsg(
                "Load From Nodes", "Error loading From Nodes - {}".format(msg))

        # to Node
        self.lblToNode.setText("To Node - Node Template: {}".format(
            self.templateDict["toTemplate"]))
        index, nodeTemplateDict = self.designModel.getDictByName(
            topLevel="Node Template",
            objectName=self.templateDict["toTemplate"])
        fromNodeTemplateCypher = NodeTemplateCypher(
            templateDict=nodeTemplateDict)
        getNodesCypher, x = fromNodeTemplateCypher.genMatchReturnNodeOnly()
        #        print(getNodesCypher)
        rc, msg = self.runCypher(
            requestType="Get From Nodes based on Node Template: {}".format(
                self.templateDict["toTemplate"]),
            cypher=getNodesCypher)
        if rc == True:
            self.loadNodeDropDown(dropDown=self.cmbToNode)
        else:
            self.helper.displayErrMsg(
                "Load To Nodes", "Error loading From Nodes - {}".format(msg))

        # property grid
        self.gridProps.setSortingEnabled(False)
        self.gridProps.setModel(self.createPropModel())
        self.gridProps.setItemDelegate(NeoEditDelegate(self))
        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)

        self.populateUIfromObject()

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

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

        return model

    def populateUIfromObject(self, ):
        if self.templateDict is not None:
            #load props  PROPERTY, EXISTS, PROPREQ,PROPDEF,UNIQUE, PROPNODEKEY
            for nodeProp in self.templateDict["properties"]:
                self.addProp(self.gridProps.model(), nodeProp[PROPERTY],
                             nodeProp[DATATYPE], nodeProp[PROPREQ],
                             nodeProp[PROPDEF])

    def loadNodeDropDown(self, dropDown=None):
        #        print(self.neoCon.resultSet)
        dropdownList = []
        nodeList = [
            "[{}]-".format(str(result["nodeID"])) + str(result["Node"])
            for result in self.neoCon.resultSet
        ]
        dropdownList.extend(nodeList)
        dropDown.addItems(dropdownList)

    def addProp(self, model, prop, dataType, required, default):
        '''
        add a row to the property grid
        '''
        self.gridProps.setSortingEnabled(False)
        if required == Qt.Checked:
            item1 = QStandardItem("{}".format(prop))
        else:
            item1 = QStandardItem("{}".format(prop))
        item1.setEditable(False)
        item2 = QStandardItem(dataType)
        item2.setEditable(False)

        if default is None or default == "":
            item3 = QStandardItem("Null")
        else:
            item3 = QStandardItem(default)

        item3.setData(dataType, Qt.UserRole + 1)
        item3.setEditable(True)
        model.appendRow([item1, item2, item3])

    def runCypher(self, requestType, cypher):
        '''
        Run a Cypher query and return the entire result set
        '''
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.logMsg("User requests {}".format(requestType))
        try:
            rc = False
            self.logMsg(cypher)
            #run the query
            rc1, msg1 = self.neoCon.runCypherAuto(cypher)
            if rc1:
                self.logMsg("{} Relationship {}".format(requestType, msg1))
                rc = True
                msg = "{} Relationship complete".format(requestType)
            else:
                rc = False
                msg = "{} Relationship Error {}".format(requestType, msg1)
        except BaseException as e:
            msg = "{} - {} failed.".format(requestType, repr(e))
        finally:
            QApplication.restoreOverrideCursor()
            if rc == False:
                self.helper.displayErrMsg("Process Query", msg)
            self.logMsg(msg)

        return rc, msg

    @pyqtSlot()
    def on_btnSetNull_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_btnAddNew_clicked(self):
        """
        User clicks button to add new relationship
        """
        # the add logic is in the parent dialog
        self.parent.on_btnAddNew_clicked()
Exemplo n.º 25
0
class FormPropertyBox(QDialog, Ui_FormPropertyBox):
    """
    Form Definition Editor
    """
    def __init__(self, parent=None, mode=None, objectDict = None, designModel = None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(FormPropertyBox, self).__init__(parent)
        self.setupUi(self)
        self.parent = parent
        self.schemaModel = self.parent.schemaObject 
        self.settings = QSettings()
        self.helper = Helper()
        self.designModel = designModel
        self.modelData = self.designModel.modelData
        if objectDict is None:
            self.objectDict = self.designModel.newFormDict()
        else:
            self.objectDict = objectDict
        self.mode = mode

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

        # add preview widget
        self.formPreview = FormMain(self.frmPreview )
        self.formPreviewLayout = QVBoxLayout(self.frmPreview)
        self.formPreviewLayout.setObjectName("MainFormLayout")
        self.formPreviewLayout.addWidget(self.formPreview)    


        self.populatingMetaBox = False
                       

        
        # position the splitter
        self.show()  # you have to do this to force all the widgets sizes to update 
        third = int((self.width()  / 3)) 
        self.vSplitter.setSizes([third*2, third])   
        half = int((self.height()  / 2)) 
        self.hSplitter.setSizes([half, half])   
        
        #populate ui data from object
        self.populateUIfromObject()

    def populateUIfromObject(self):
        self.populateTree()
        self.genForm()

    def genForm(self):
        '''generate the user defined form
        '''
        if self.validate():
            self.apply()
            self.formPreview.generateForm(formDict=self.objectDict)
        
#-----------------------------------------------------------------------------------------------------------------------
#  tree view methods
#-----------------------------------------------------------------------------------------------------------------------        
    def clearTree(self):
        self.tvOutline.clear()
        self.tvOutline.setColumnCount(1)
        self.tvOutline.setHeaderLabels(["Form Outline"])
        self.tvOutline.setItemsExpandable(True)        

    def getMaxTreeOrder(self, ):
        '''scan the tree and find the max itemNum attribute.  This is used to increment and create the next highest item number'''
        max = 0
        # iterate through the treeview
        tvPathIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All)
        while tvPathIterator:
            if not tvPathIterator.value() is None:
                formTVItem = tvPathIterator.value()
                formItem = formTVItem.data(0, Qt.UserRole)
                # save the value for order if it's greater
                if formItem.itemDict["idNum"] > max:
                    max = formItem.itemDict["idNum"]
                tvPathIterator.__iadd__(1)
            else:
                break       
        return max
    
        
    def populateTree(self, ):   
        self.clearTree()
#        print("path dict {}".format(self.objectDict))
        # add tree items
        if len(self.objectDict["formOutline"]) > 0:
            for formItemDict in self.objectDict["formOutline"]:
                # create the tree view form item object
                if formItemDict["type"] == "Form":
                    formItem = FormDef(itemDict=formItemDict)
                if formItemDict["type"] == "Row":
                    formItem = FormRowDef(itemDict=formItemDict)
                if formItemDict["type"] == "Widget":
                    if formItemDict["widgetType"] == "Label":
                        formItem = LabelWidgetDef(itemDict=formItemDict)   
                    if formItemDict["widgetType"] == "Button":
                        formItem = ButtonWidgetDef(itemDict=formItemDict)                       
                # get the parent tree view widget
                parent = self.findParentWidget(findID=formItemDict["parentID"])
                # add the treewidgetitem to the tree
                formItem.treeItem = self.addTreeNode(parent=parent, formItem=formItem)
        else:
            # add a new root node
            formItem = FormDef(root=True,  idNum=0, parentID=None, type="Form", itemName=self.objectDict["name"])
            formItem.treeItem = self.addTreeNode(parent=self.tvOutline, formItem=formItem)
            
        self.tvOutline.resizeColumnToContents(0)  
        self.tvOutline.setCurrentItem(self.tvOutline.topLevelItem(0))
     
        
    def addTreeNode(self, parent=None, formItem=None):
#        print("add tree node {}".format(pathItem.displayName))
        item = QTreeWidgetItem(parent, [formItem.displayName])
        item.setData(0, Qt.UserRole, formItem)
        item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
        item.setExpanded (True)
        item.setFlags(  Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled )
        return item

    def findParentWidget(self, findID=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 findID is None:
            return self.tvOutline
        # find the parent tree view widget
        parentWidget = None
        # iterate through the treeview
        tvOutlineIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All)
        while tvOutlineIterator:
            if not tvOutlineIterator.value() is None:
                # get the treeview widget
                tvWidget = tvOutlineIterator.value()
                # get the form item object from the widget
                formItem = tvWidget.data(0, Qt.UserRole)
                # get the idNum of the form item object
                idNum = formItem.itemDict["idNum"]
                # check to see if this is the idNum we're looking for
                if idNum == findID:
                    parentWidget = tvWidget
                    break
                tvOutlineIterator.__iadd__(1)
            else:
                break     
                
        return parentWidget        
        
    def openMenu(self,position):
        selected = self.tvOutline.currentItem()
        if not (selected is None):
            tvFormItem = selected.data(0, Qt.UserRole)
            if (tvFormItem.itemDict["type"] == "Form"):
                menu = QMenu()
                addItemAction = menu.addAction("Add Row")
                addItemAction.triggered.connect(self.addRow)                
                menu.exec_(self.tvOutline.mapToGlobal(position))  
                return
            if (tvFormItem.itemDict["type"] == "Row"):
                menu = QMenu()
                addItemAction = menu.addAction("Add Button Widget")
                addItemAction.triggered.connect(self.addButtonWidget)      
                addItemAction = menu.addAction("Add Label Widget")
                addItemAction.triggered.connect(self.addLabelWidget)      
                addItemAction = menu.addAction("Remove This Row")
                addItemAction.triggered.connect(self.removeRow)
                menu.exec_(self.tvOutline.mapToGlobal(position))  
                return
            if (tvFormItem.itemDict["type"] == "Widget"):
                menu = QMenu()
                addItemAction = menu.addAction("Remove This Widget")
                addItemAction.triggered.connect(self.removeWidget)
                menu.exec_(self.tvOutline.mapToGlobal(position))  
                return


        
    def addRow(self):
        parentTVItem = self.tvOutline.currentItem()
        parentFormItem = parentTVItem.data(0, Qt.UserRole)
        parentID = parentFormItem.itemDict["idNum"]
        nextID = self.getMaxTreeOrder() + 1
        newFormItem = FormRowDef(parentID=parentID, idNum=nextID, type="Row")
        newFormItem.treeItem = self.addTreeNode(parent=parentTVItem, formItem=newFormItem)
        self.tvOutline.setCurrentItem(newFormItem.treeItem)
        
        # update form view
        self.genForm()
        
    def removeRow(self):
        self.removeTVItem()

    def addLabelWidget(self):
        self.addWidget(type="Label")

    def addButtonWidget(self):
        self.addWidget(type="Button")
        
    def addWidget(self, type=None):
        
        parentTVItem = self.tvOutline.currentItem()
        parentFormItem = parentTVItem.data(0, Qt.UserRole)
        parentID = parentFormItem.itemDict["idNum"]
        nextID = self.getMaxTreeOrder() + 1
        if type == "Label":
            newFormItem = LabelWidgetDef(parentID=parentID, idNum=nextID, type="Widget")
        if type == "Button":
            newFormItem = ButtonWidgetDef(parentID=parentID, idNum=nextID, type="Widget")
        if not newFormItem is None:
            newFormItem.treeItem = self.addTreeNode(parent=parentTVItem, formItem=newFormItem)
            self.tvOutline.setCurrentItem(newFormItem.treeItem)
        # update form view
        self.genForm()        
        
    def removeWidget(self):
        self.removeTVItem()
        
    def removeTVItem(self):
        '''remove a form item from the tree and all descedants'''
        currentItem = self.tvOutline.currentItem()
        formDefItem = currentItem.data(0, Qt.UserRole)
        if formDefItem.itemDict["type"] == "Form":
            self.helper.displayErrMsg("Form Editor", "Cannot remove the form definition")
            return
        parentItem = currentItem.parent()
        parentItem.removeChild(currentItem)
        self.tvOutline.takeTopLevelItem(self.tvOutline.indexOfTopLevelItem(currentItem))
        
        # update form view
        self.genForm()        
        
    def validate(self):
        
        return True
        
    def apply(self):
        '''save the object dictionary'''
        formItemList = []
        # iterate through the treeview
        tvOutlineIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All)
        while tvOutlineIterator:
            if not tvOutlineIterator.value() is None:
                 # get the treeview widget
                tvWidget = tvOutlineIterator.value()
                # get the form item object from the widget
                formDefItem = tvWidget.data(0, Qt.UserRole)
                if formDefItem.itemDict["type"] == "Form":
                    self.objectDict["name"] = formDefItem.itemDict["itemName"]
                    self.objectDict["description"] = formDefItem.itemDict["description"]
                formItemList.append(formDefItem.dict())
                tvOutlineIterator.__iadd__(1)
            else:
                break
            
        self.objectDict["formOutline"] = formItemList
        self.designModel.setModelDirty()     
        
    @pyqtSlot()
    def on_btnAdd_clicked(self):
        """
        User clicks the add button
        """
        selected = self.tvOutline.currentItem()
        if not (selected is None):
            tvFormItem = selected.data(0, Qt.UserRole)
            if (tvFormItem.type == "Form"):
                self.addRow()
                return
            if (tvFormItem.type == "Row"):
                self.addWidget()
                return
            if (tvFormItem.type == "Widget"):
                self.helper.displayErrMsg("Form Design", "Cannot add anything below a widget")
                return

    
    @pyqtSlot()
    def on_btnRemove_clicked(self):
        """
        User clicks remove button
        """
        self.removeTVItem()
    
    @pyqtSlot()
    def on_btnUp_clicked(self):
        """
        User clicks the up button
        """
        print("move tree item up")
        
    @pyqtSlot()
    def on_btnDown_clicked(self):
        """
        User clicks the down button
        """
        print("move tree item down")
    
    @pyqtSlot()
    def on_btnClose_clicked(self):
        """
        Slot documentation goes here.
        """
        if self.validate():
            self.apply()
            QDialog.accept(self)
            

    
    @pyqtSlot()
    def on_tvOutline_itemSelectionChanged(self):
        """
        User clicks on an item in the tree view
        """
        print("on_tvOutline_itemSelectionChanged")
        selected = self.tvOutline.currentItem()
        if not (selected is None):
#            parent = self.tvPath.currentItem().parent()
            formDefItem = selected.data(0, Qt.UserRole)
            self.populateMetaBox(formDefItem=formDefItem)

 
#-----------------------------------------------------------------------------------------------------------------------
#  metabox grid methods
#-----------------------------------------------------------------------------------------------------------------------        
    def createMetaBoxModel(self):
#        ATTRNAME, ATTRVALUE  gridNodeMeta
        model = QStandardItemModel(0, 2)
        model.setHeaderData(ATTRNAME, Qt.Horizontal, "Attribute")
        model.setHeaderData(ATTRVALUE, Qt.Horizontal, "Value")
        return model    
        
    def clearMetaBox(self):
#        self.lblMetaHeader.setText("Definition")
        # metabox grid setup
#        ATTRNAME, ATTRVALUE  gridMetaBox
        self.gridMetaBox.setModel(None)
        self.metaBoxModel = self.createMetaBoxModel()
        self.gridMetaBox.setModel(self.metaBoxModel)
        self.gridMetaBox.setSelectionBehavior(QAbstractItemView.SelectItems) 
        self.gridMetaBox.setSelectionMode(QAbstractItemView.SingleSelection)
        self.gridMetaBox.setColumnWidth(ATTRNAME, 150)   
        self.gridMetaBox.setColumnWidth(ATTRVALUE, 300)   
        # hide vertical header
        vheader = self.gridMetaBox.verticalHeader()
        vheader.hide()
        # 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, dictKey=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)
        # save the dictKey in the attribute name so we can update the itemDict later
        item1.setData(dictKey, Qt.UserRole)
        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, formDefItem=None):
        self.populatingMetaBox = True
        if not formDefItem is None:
            self.clearMetaBox()
            for attr in formDefItem.attributes():
                self.addMetaRow(attribute=attr[0], dictKey = attr[1], value=formDefItem.itemDict[attr[1]], editable=attr[2] )       
                
        self.populatingMetaBox = False
        
        
    def metaBoxModelItemChanged(self, item):
        if self.populatingMetaBox == False:
            print("item data changed {} at row:{} col:{}".format(str(item.checkState()), item.index().row(), item.index().column()))
            # a cell changed so see if other changes are needed
            selected = self.tvOutline.currentItem()
            if not (selected is None):
                formDefItem = selected.data(0, Qt.UserRole)  
#                name= self.gridMetaBox.model().item(item.index().row(),ATTRNAME).data(Qt.EditRole)
                # get the dictionary key for this attribute
                name= self.gridMetaBox.model().item(item.index().row(),ATTRNAME).data(Qt.UserRole)
                # get the value entered by the user
                value= self.gridMetaBox.model().item(item.index().row(),ATTRVALUE).data(Qt.EditRole)
                formDefItem.updateAttr(name=name, value=value)
                # update the tree view description
                formDefItem.updateTreeView()

            # force selection of this cell
            self.gridMetaBox.setCurrentIndex(item.index())
        
            # update form view
            self.genForm()

    def metaBoxGridSelectionChanged(self):
        # not used
        print("metabox grid selection changed")
        return
Exemplo n.º 26
0
class ChangeUserPW(QDialog, Ui_ChangeUserPW):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(ChangeUserPW, self).__init__(parent)
        self.setupUi(self)
        self.helper = Helper()
        self.settings = QSettings()
        self.parent = parent
        self.editPW.setEchoMode(QLineEdit.Password)
        self.editNewPW.setEchoMode(QLineEdit.Password)
        self.editRepeatPW.setEchoMode(QLineEdit.Password)
        
    def logMessage(self, msg):
        if logging:
            logging.info(msg)                    
            
    @pyqtSlot()
    def on_btnCurrentShow_clicked(self):
        """
        User clicks the show button for the current password
        """
        if self.btnCurrentShow.text() == "Show":
            self.btnCurrentShow.setText("Hide")
            self.editPW.setEchoMode(QLineEdit.Normal)
        else:
            self.btnCurrentShow.setText("Show")
            self.editPW.setEchoMode(QLineEdit.Password)            
    
    @pyqtSlot()
    def on_btnNewShow_clicked(self):
        """
        User clicks the show button for the new passwords
        """
        if self.btnNewShow.text() == "Show":
            self.btnNewShow.setText("Hide")
            self.editNewPW.setEchoMode(QLineEdit.Normal)
            self.editRepeatPW.setEchoMode(QLineEdit.Normal)
        else:
            self.btnNewShow.setText("Show")
            self.editNewPW.setEchoMode(QLineEdit.Password)      
            self.editRepeatPW.setEchoMode(QLineEdit.Password)      
    
    @pyqtSlot()
    def on_btnReset_clicked(self):
        """
        User clicks the reset password button so do it
        """
        if self.validate():
            self.resetPassword()

    def validate(self, ):
        if self.helper.NoTextValueError(self.editUserID.text(), "You must enter a user ID to login with."):
            self.editUserID.setFocus()
            return False        
        if self.helper.NoTextValueError(self.editPW.text(), "You must enter a password to login with."):
            self.editPW.setFocus()
            return False        
        if self.helper.NoTextValueError(self.editNewPW.text(), "You must enter a new password."):
            self.editNewPW.setFocus()
            return False        
        if self.helper.NoTextValueError(self.editRepeatPW.text(), "You must repeat the new password."):
            self.editRepeatPW.setFocus()
            return False      
        if self.editNewPW.text() != self.editRepeatPW.text():
            self.helper.displayErrMsg("Reset Password", "The new password does not match the repeat password.")
            self.editNewPW.setFocus()
            return False    
        
        return True

    def resetPassword(self):
        '''login to Neo4j and reset the password
        '''
        # get the currently selected schema tab's neocon name
        newConName = self.parent.pageItem.neoConName
        # get the neocon dictionary from settings
        newConDict = self.settings.value("NeoCon/connection/{}".format(newConName))
        newConDict["userid"]=self.editUserID.text()
        savePW = self.helper.putText(self.editPW.text())
        newConDict["password"]=savePW
        # create a new neoCon using the userid/password the person entered on this form
        self.newNeoCon = NeoDriver(name=newConName,  neoDict = newConDict)
        
        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.changePassword(userName=self.editUserID.text(), pw=self.editNewPW.text(), forceChange=False)
        if rc:
            self.helper.displayErrMsg("Change Password", msg)
        else:
            self.helper.displayErrMsg("Change Password Error", msg)
        QApplication.restoreOverrideCursor()         

    def changePassword(self, userName=None, pw=None, forceChange=None):
        try:
#            cypherCmd = "CALL dbms.security.changeUserPassword('{}','{}',{})".format(userName, pw, str(forceChange))
            cypherCmd = "CALL dbms.changePassword('{}')".format(pw)
            self.logMessage("Attempting: {}".format(cypherCmd))
            #run the query
            rc1, msg1 = self.newNeoCon.runCypherAuto(cypherCmd)
            if rc1:
                msg = "Password Changed."       
            else:
                msg = "Change Password Error {}".format(msg1)
        except BaseException as e:
            msg = "{} - Change Password Error.".format(repr(e))
        finally: 
            self.logMessage(msg) 
            return rc1, msg                          
        
    @pyqtSlot()
    def on_btnClose_clicked(self):
        """
        User clicks the Close button so exit the dialog
        """
        QDialog.accept(self)
Exemplo n.º 27
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.º 28
0
class RelationInstance():
    ''' This represents one NodeEra managed relationship that can be on one or more diagrams.
        This class manages reading and writing the relationship to Neo4j
        The RelationItem class creates the arc graphics item and the text graphics item and draws them on the scene.
        startNode and endNode must be a node instance
    '''    
    def __init__(self, parent, relationInstanceDict=None, startNode=None, endNode=None, model=None):
        self.helper = Helper()
        self.neoTypeFunc = NeoTypeFunc()
        self.diagramType = "Instance Relationship"
        self.logMsg = None
        self.relationInstanceDict = relationInstanceDict
        
        self.parent = parent
        self.model = model
        self.neoCon = self.model.modelNeoCon
        
        self.relationship = None 

        # set local variables to what is in the dictionary
        if self.relationInstanceDict:
            self.relTemplate = self.relationInstanceDict.get("relTemplate", None)
            self.relName = self.relationInstanceDict.get("relName", "NoRelationshipName")
            self.propList = self.relationInstanceDict.get("properties", [])
            self.startNZID = self.relationInstanceDict.get("startNZID", None)
            self.endNZID = self.relationInstanceDict.get("endNZID", None)
            
            self.startNode = None
            self.endNode = None

            if self.relationInstanceDict.get("NZID", None) == None:
                self.NZID = str(uuid.uuid4())
            else:
                self.NZID = self.relationInstanceDict["NZID"]        
            self.neoID=self.relationInstanceDict.get("neoID", None)
        # if  no dictionary passed then set defaults
        else:
            self.relName = "NoRelationshipName"
            self.relTemplate = None
            self.propList = []
            self.NZID = str(uuid.uuid4())
            self.neoID=None
            self.startNZID = None
            self.endNZID = None
            self.startNode = None
            self.endNode = None
        
        # get start and end node item objects
        if not startNode is None:
            self.startNode = startNode
            self.startNZID = self.startNode.NZID
        if not endNode is None:
            self.endNode = endNode
            self.endNZID = self.endNode.NZID
       
        self.getFormat()

        if not self.relationInstanceDict:
            self.relationInstanceDict = self.getObjectDict()
            
    def reloadDictValues(self, ):
        '''
        This function will reset the local variables to the instance rel dictionary.
        This is needed when open diagram relationship instances need to be updated.
        '''
        index, relDict = self.model.getDictByName(topLevel="Instance Relationship",objectName=self.NZID)
        if relDict:
            # set local variables to what is in the dictionary
            self.relInstanceDict = relDict
            self.nodeTemplate = self.relInstanceDict.get("relTemplate", None)
            self.propList = self.relInstanceDict.get("properties", [])
            self.NZID = self.relInstanceDict["NZID"]        
            self.neoID=self.relInstanceDict.get("neoID", None)        
    
    def getFormat(self, ):
        '''
        determine if the rel instance has a template format or should use the project default format
        '''
        # get the default
        self.relFormat = IRelFormat(formatDict=self.model.modelData["IRformat"])
        # get a custom template format if there is one
        if not self.relTemplate is None:
            index, relTemplateDict = self.model.getDictByName(topLevel="Relationship Template",objectName=self.relTemplate)
            if not relTemplateDict is None:
                self.instanceRelFormatDict = relTemplateDict.get("IRformat", None)
                if not self.instanceRelFormatDict is None:
                    self.relFormat = IRelFormat(formatDict=self.instanceRelFormatDict) 
                    

    def getObjectDict(self, ):
        objectDict = {}
        objectDict["NZID"] = self.NZID
        objectDict["name"] = self.NZID
        objectDict["neoID"] = self.neoID
        objectDict["diagramType"] = self.diagramType
        objectDict["relName"] = self.relName        
        objectDict["properties"] = self.propList
        objectDict["startNZID"] = self.startNZID
        objectDict["endNZID"] = self.endNZID
        objectDict["relTemplate"] = self.relTemplate
        self.genRelSignature(objectDict)
        return objectDict
        
    def genRelSignature(self, relDict):
        '''generate the display name for a relationship instance
        '''
        if self.neoID is None:
            relID = self.NZID        
        else:
            relID = self.neoID
            
        if (self.startNode is None or self.startNode.neoID is None):
            fromID = "0"
        else:
            fromID = self.startNode.neoID
            
        if (self.endNode is None or self.startNode.neoID is None):
            toID = "0"
        else:
            toID = self.endNode.neoID            

        propList = self.helper.genPropValueList("n", relDict)
        signature = "({})-[{}:{} {}{}{}]->({})".format(str(fromID), str(relID), self.relName, "{", propList, "}", str(toID))
        relDict["displayName"] =  signature   
        
    def setLogMethod(self, logMethod=None):
        if logMethod is None:
            if self.logMsg is None:
                self.logMsg = self.noLog
        else:
            self.logMsg = logMethod
            
    def noLog(self, msg ):
        return
        
    def deleteRel(self, logMethod=None):
        '''This method deletes the relationship from the database.'''
        # set the logging method
        self.setLogMethod(logMethod)
        # if it doesn't have a neoID it's never been created in the db so nothing to delete
        if self.neoID is None:
            self.logMsg("The Relationship does not exist in the database.  Cannot delete.")
            return True, "No Relationship to delete."
        # delete the Relationship based on the neoID    
        cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + "  delete r"
        rc, msg = self.neoCon.runCypherAuto(cypher)
        self.logMsg(msg)
        return rc, msg


    def checkRelationship(self, logMethod=None):
        '''This method checks to see if a matching relationship exists in the database.
        '''
        # set the logging method
        self.setLogMethod(logMethod)
        # if it doesn't have a neoID it's never been created in the db so create a blank one
        if self.neoID is None:
            rc = NOTFOUND
            msg = "Diagram Relationship Doesn't Exist in the Graph"
            return rc, msg
        
        # see if the node still exists in the db based on the node id
        cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + " return r"
        rc, msg = self.neoCon.runCypherExplicit(cypher)
        if rc is False:
            self.logMsg(msg)
            msg = "Error Querying Graph."     
            return NOTFOUND,  msg
        else:
            if len(self.neoCon.resultSet) > 0:
                firstRec = self.neoCon.resultSet[0]
                if not firstRec is None:
                    self.relationship = firstRec["r"]  # the rel was found so set it
                    if self.compareRel():
                        return FOUNDMATCH, "Diagram Relationship Matches Graph"
                    else:
                        return FOUNDNOMATCH, "Diagram Relationship Doesn't Match Graph"
                else:
                    return NOTFOUND, "Diagram Relationship Doesn't Exist in Graph"
            else:
                return NOTFOUND,  "Diagram Relationship Doesn't Exist in Graph"
        return

    def compareRel(self):
        '''Compare the Relationship object retrieved from the database with the current state of the relationship item'''
        rc = True
        # need to check if the start and end nodes are the same
        pass
        # compare graph properties to diagram properties
        for propName, propVal in dict(self.relationship).items():
            try:
                dataType = self.neoTypeFunc.getNeo4jDataType(value=propVal)
                dataVal = self.neoTypeFunc.convertTypeToString(propVal)
                # special case as an integer is also a valid float
                if dataType == "Integer":
                    if ([propName, "Integer", dataVal] in self.propList or [propName, "Float", dataVal] in self.propList):
                        continue
                    else:
                        rc = False
                        break
                elif [propName, dataType, dataVal] in self.propList:
                    continue
                else:
                    rc = False
                    break
            except:
                rc = False
                break
                
        # compare diagram properties to graph properties
        for prop in self.propList:
            try:
                # is the property name one of the keys in the node
                if prop[0] in [propName for propName, propVal in dict(self.relationship).items() ]:
                    pVal = [propVal for propName, propVal in dict(self.relationship).items() if propName == prop[0]][0]
                    # does the datatype and data value match - special case for Float because the value could be an int or a float 
                    if (prop[1] == "Float" and self.neoTypeFunc.getNeo4jDataType(value=pVal) in ["Float", "Integer"] and prop[2] == self.neoTypeFunc.convertTypeToString(pVal) ):
                        continue
                    elif (prop[1] == self.neoTypeFunc.getNeo4jDataType(value=pVal) and prop[2] == self.neoTypeFunc.convertTypeToString(pVal)):
                        continue
                    else:
                        rc = False
                        break
                else:
                    # is the prop missing in the neo4j relationship because it's null on the diagram?
                    if prop[2] == "Null":
                        continue
                    else:
                        rc = False
                        break
            except:
                    rc = False
                    break
        
        return rc
        
    def getRelationship(self, logMethod=None):
        '''
        This method retrieves the relationship from Neo4j and sets the relationship object.  At this point the relationship object looks like it is in Neo4j
        '''
        # set the logging method
        self.setLogMethod(logMethod)
        # if there's no neoID then there isn't a relationship in neo4j
        if self.neoID is None:
            return None, "Relationship [{}] doesn't exist in Neo4j".format(self.neoID)
        # try to retrieve the relationship based on the neoid    
        cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + " return r"
        rc, msg = self.neoCon.runCypherAuto(cypher)
        if rc is False:
            return  rc, "Retrieve Relationship [{}] error - {}".format(self.neoID, msg) 
        else:
            # relationship found, save it
            firstRec = None
            if len(self.neoCon.resultSet) > 0:
                firstRec = self.neoCon.resultSet[0]
            if not firstRec is None:
                self.relationship = firstRec["r"]  # the rel was found so set it
                return True, "Relationship [{}] Found".format(self.neoID)            
            # relationship not found, it might have been deleted thru the backend
            else:
                return None, "Relationship [{}] Not Found".format(self.neoID)


    def createNewRelationship(self, logMethod=None):
        '''
        create a new relationship in neo4j based on the current state of the relation instance object
        '''
        # set the logging method
        self.setLogMethod(logMethod)
        # force creation of the start and end nodes in Neo4j if they don't exist
        rc, msg = self.startNode.getNode(logMethod)
        if rc == False:
            return rc, "Create Relationship error on from node - {}".format(msg)
        rc, msg = self.endNode.getNode(logMethod)
        if rc == False:
            return rc, "Create Relationship error on to node - {}".format(msg)
        
        # generate cypher stmt to create the relationship
        cypher = self.helper.genCreateRelCypher(relInstanceDict = self.getObjectDict(), rel = self.relationship, fromNeoID = self.startNode.neoID, toNeoID=self.endNode.neoID)
        # run the create 
        rc, msg = self.neoCon.runCypherAuto(cypher)      

        if rc is True:            
            firstRec = None
            if len(self.neoCon.resultSet) > 0:
                firstRec = self.neoCon.resultSet[0]
            if not firstRec is None:
                self.relationship = firstRec["r"]  # the rel was found so set it
                self.neoID = firstRec["id(r)"]
                self.relationInstanceDict["neoID"] = self.neoID
                return rc, "New Relationship Created - {}".format(msg)
        else:
            self.logMsg(msg)                
            return rc, "Create New Relationship [{}] error - {}".format(self.NZID, msg)
        
        return False, "Unexpected Error"
        
    def updateRelProps(self, ):
        '''
        set the properties of the relationship  object to what has been entered on the dialog
        '''
        # properties
        relProps = dict(self.relationship)
        #set all existing property values in the relationship object to None
        for key in relProps:
            self.relationship[key]=None
        # set all property values from the grid
        for property in self.propList:
            self.relationship[property[0]]=property[1]
            
    def updateRelationship(self, logMethod=None):
        '''
        relationship already exists, so all you can do is change the properties if any
        '''
        # set the logging method
        self.setLogMethod(logMethod)
        # generate cypher stmt to create the relationship 
        cypher = self.helper.genUpdateRelCypher(relInstanceDict = self.getObjectDict(), rel = self.relationship, relID = self.neoID)
        # run the create
        rc, msg = self.neoCon.runCypherAuto(cypher)    
        if rc is True:
            return rc, "Existing Relationship Updated - {}".format(msg)
        else:
            self.logMsg(msg)                
            return rc, "Update Existing Relationship [{}] error - {}".format(self.NZID, msg)        

        
    def syncToDB(self, logMethod=None):

        # get the relationship from the db, 
        rc, msg = self.getRelationship(logMethod)
        if rc is True:
            # relationship found in neo4j and relationship object set.  generate cypher to update relationship to look like the ui
            rc, msg = self.updateRelationship(logMethod)
            if rc is True:
                return rc, "Sync Relationship OK - {}".format(msg)
            else:
                return rc, "Sync Relationship Error - {}".format(msg)                
        elif rc is False:
            # error processing the match statement - big fail
            return rc, "Sync Relationship Error - {}".format(msg)   
            
        elif rc is None:
            # relationship object was not found in neo4j so create it
            rc, msg = self.createNewRelationship(logMethod)
            if rc is True:
                return rc, "Sync Relationship OK - {}".format(msg)
            else:
                return rc, "Sync Relationship Error - {}".format(msg)                

        else:
            return rc, "Unknown Sync Relationship Error - {}".format(msg)

    def syncFromDB(self, logMethod=None):
        # set the logging method
        self.setLogMethod(logMethod)     
        # should never happen, but if this instance rel has no neoID it can't be matched to a neo4j node so no way to sync it from neo4j
        if self.neoID is None:
            msg = "Error - this instance relationship has never been saved to Neo4j"
            self.helper.displayErrMsg("Sync From DB", msg)
            return False, msg
        rc, msg = self.getRelationship()
        if rc is True:
            # get the dictionary of properties from the relationship object  and convert it to a list
            props = []
            for key, val in dict(self.relationship).items():
                props.append( [key, self.neoTypeFunc.getNeo4jDataType(value=val),self.neoTypeFunc.convertTypeToString(dataValue=val) ])
            # set instance props to look like what's in the neo4j relationship object just retrieved    
            self.propList = props
            # update the object dictionary
            self.relationInstanceDict = self.getObjectDict()
            # save it to the design model
            self.saveToModel()
            return True, "Instance Relationship Synced"
        else:
            return False, "Error syncing Instance Relationship"        

    def saveToModel(self, ):
        # save the current state of the relation instance to the project model
        saveIndex = self.model.instanceTopLevelIndex("Instance Relationship", self.NZID)
        if not saveIndex is None:
            self.model.modelData["Instance Relationship"][saveIndex]=self.relationInstanceDict
Exemplo n.º 29
0
class RelTemplateCypher():
    def __init__(self, parent=None, templateDict=None):
        self.parent = parent
        self.templateDict = templateDict
        self.type = "Relationship"
        self.helper = Helper()

    # this determines if the DataGridWidget should call genMatch on a grid refresh or should it suppy it's own cypher
    def isGeneric(self, ):
        return False

    # set which button driven functionality will be enabled

    def refreshOK(self, ):
        return True

    def exportOK(self, ):
        return True

    def newOK(self, ):
        return True

    def deleteOK(self, ):
        return True

    def rowSelect(self, ):
        return False

    def setNullOK(self, ):
        return True

    def genDeleteDetach(self, row=None, dataGrid=None):
        model = dataGrid.model()
        # get the relID
        self.relID = None
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "rel_id":
                self.relID = model.item(row, header).data(Qt.EditRole)

        if not self.relID is None:
            p1 = self.relID
            cypher = " ".join(
                ["match (f)-[r]->(t) \n", "where id(r) = {} \n",
                 "delete r"]).format(p1)

        return cypher

    def genUpdateProp(self, updateIndex=None, dataGrid=None):
        cypher = ""
        rc = True
        model = dataGrid.model()
        # get the RELATIONSHIP ID
        self.relID = None
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "rel_id":
                self.relID = model.item(updateIndex.row(),
                                        header).data(Qt.EditRole)

        if self.relID is None:
            return False, "Relationship ID not found."

        try:
            self.updateData = model.item(
                updateIndex.row(), updateIndex.column()).data(Qt.EditRole)
            self.updateProp = model.headerData(updateIndex.column(),
                                               Qt.Horizontal, Qt.DisplayRole)
            # get the correct neo4j type
            neoType = model.item(updateIndex.row(),
                                 updateIndex.column()).data(Qt.UserRole + 1)
            # if the datatype comes back unknown then generate cypher comment
            if neoType == 'Unknown':
                cypher = "Can't update unknown datatype, correct datatype in relationship template."
                rc = False
            else:
                # generate the correct syntax that you set the property equal to
                self.setEqualTo = self.helper.genPropEqualTo(
                    dataValue=self.updateData, neoType=neoType)
                p1 = self.relID
                p2 = self.updateProp
                p3 = self.setEqualTo

                cypher = " ".join([
                    "match (f)-[r]->(t) \n", "where id(r) = {} \n",
                    "set r.{} = {}"
                ]).format(p1, p2, p3)
                rc = True

        except BaseException as e:
            # something went wrong
            cypher = "Error generating cypher: {}".format(repr(e))
            rc = False
        finally:
            return rc, cypher

    def genRemoveProp(self, updateIndex=None, dataGrid=None):
        model = dataGrid.model()
        cypher = None
        self.relID = None
        # get the relID
        for header in range(model.columnCount()):
            if model.headerData(header, Qt.Horizontal,
                                Qt.DisplayRole) == "rel_id":
                self.relID = model.item(updateIndex.row(),
                                        header).data(Qt.EditRole)
        if self.relID is None:
            return cypher
        # get the property name
        self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal,
                                           Qt.DisplayRole)
        # MAKE SURE IT ISN'T A REQUIRED PROPERTY
        for prop in self.templateDict["properties"]:
            if prop[PROPERTY] == self.updateProp:
                if prop[PROPREQ] != Qt.Checked:
                    p1 = self.relID
                    p2 = self.updateProp
                    cypher = "match (f)-[r]->(t) \n where id(r) = {}  \n remove r.{} ".format(
                        p1, p2)
                else:
                    self.helper.displayErrMsg(
                        "Set Null",
                        "Property {} is required by the Relationship Template. Cannot remove this property."
                        .format(self.updateProp))
        return cypher

    def genMatch(self):
        '''
        Generate the match cypher statement that the data grid will use to refresh the grid.
        Generate the editParmDict dictionary that tells the data grid how to handle each column in the result set
        '''
        #        fromNodeVar = "f"
        #        toNodeVar = "t"
        relVar = "r"
        p1 = self.templateDict["relname"]
        p3 = self.genReturnPropList(relVar)
        if len(p3) > 1:
            p2 = ","
        else:
            p2 = ""

        cypher = " ".join([
            "match (f)-[r]->(t) \n",
            "where type(r) = '{}' \n",
            "return  id(f) as From_NodeID, \n",
            "id(t) as To_NodeID, \n",
            "id(r) as rel_id, \n",
            "type(r) as rel_name {} \n",
            "{} \n",
        ]).format(p1, p2, p3)

        #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP, RELNAME
        editParmDict = []
        editParmDict.append([NODEID, False])
        editParmDict.append([NODEID, False])
        editParmDict.append([RELID, False])
        editParmDict.append([RELNAME, False])
        for prop in self.templateDict["properties"]:
            editParmDict.append([PROP, True])

        return cypher, editParmDict

    def genReturnPropList(self, nodeName):
        'return all properties in the template'
        propList = ""
        propList = ",".join(nodeName + "." + x[PROPERTY] + " as " +
                            x[PROPERTY] + " \n"
                            for x in self.templateDict["properties"])
        return propList

    def genSetPropList(self, nodeName):
        'return a set statement for each property that has a default value (i.e. required)'
        setPropList = []
        if not self.templateDict is None:
            for prop in self.templateDict["properties"]:
                # if the property has a default value then generate the set statement.
                if prop[PROPDEF] != "":
                    # generate the correct syntax that you set the property equal to
                    setEqualTo = self.helper.genPropEqualTo(
                        dataValue=prop[PROPDEF], neoType=prop[DATATYPE])
                    setPropList.append("set {}.{} = {}".format(
                        nodeName, prop[PROPERTY], setEqualTo))
        setProps = " \n ".join(setProp for setProp in setPropList)
        return setProps
Exemplo n.º 30
0
class EditUserDlg(QDialog, Ui_EditUserDlg):
    """
    Class documentation goes here.
    """
    def __init__(self, parent=None, mode=None, userName=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(EditUserDlg, self).__init__(parent)
        self.parent = parent
        self.setupUi(self)
        self.userName = userName
        self.mode = mode
        self.helper = Helper()

        self.initUI()

    def initUI(self, ):
        self.txtUserName.setText(self.userName)
        self.txtUserName.setReadOnly(True)

        # get the user status and change password flag
        self.resetFlags()

        # populate the role combo box
        self.cbRoleName.addItems(self.parent.schemaModel.instanceList("Role"))
        # populate users roles in the list box
        userRoles = self.parent.schemaModel.getUserRoles(
            userName=self.userName)
        for userRole in userRoles:
            self.lstUserRoles.addItem(userRole)

    def resetFlags(self, ):
        # get the user status and change password flag
        self.userFlags = self.parent.schemaModel.getUserFlags(
            userName=self.userName)
        if "is_suspended" in self.userFlags:
            self.pbActivate.setChecked(False)
            self.pbSuspend.setChecked(True)
        else:
            self.pbSuspend.setChecked(False)
            self.pbActivate.setChecked(True)
        if "password_change_required" in self.userFlags:
            self.pbChangeOnLogin.setChecked(True)
        else:
            self.pbChangeOnLogin.setChecked(False)
        # the password change required flag can't be changed, the radio button just indicates how it is currently set
        self.pbChangeOnLogin.setEnabled(False)

    @pyqtSlot()
    def on_btnAddRole_clicked(self):
        """
        Give the user a role from the combobox of roles
        """
        addRole = self.cbRoleName.currentText()
        currentRoles = [
            str(self.lstUserRoles.item(i).text())
            for i in range(self.lstUserRoles.count())
        ]
        if addRole in currentRoles:
            # error, the user already has the selected role
            self.helper.displayErrMsg("Edit User",
                                      "The user already has this role.")
        else:
            # give the user the role
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.parent.schemaModel.addUserRole(
                userName=self.userName, role=addRole)
            if rc:
                self.lstUserRoles.addItem(addRole)
            else:
                self.helper.displayErrMsg("Add Role to User Error", msg)
            QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnRemoveRoles_clicked(self):
        """
        Remove the selected role from the user
        """
        removeRole = self.lstUserRoles.currentItem().text()
        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.parent.schemaModel.removeUserRole(
            userName=self.userName, role=removeRole)
        if rc:
            self.lstUserRoles.takeItem(self.lstUserRoles.currentRow())
        else:
            self.helper.displayErrMsg("Remove Role from User Error", msg)
        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_dlgBtnBox_accepted(self):
        """
        User clicks Close
        """
        QDialog.accept(self)

    @pyqtSlot()
    def on_dlgBtnBox_rejected(self):
        """
        User clicks something that generates the reject
        """
        QDialog.reject(self)

    @pyqtSlot()
    def on_pbActivate_clicked(self):
        """
        Activate a suspended user
        """
        if not "is_suspended" in self.userFlags:
            # nothing to do
            return

        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.parent.schemaModel.activateUser(userName=self.userName)
        if rc:
            self.resetFlags()
        else:
            self.helper.displayErrMsg("Activate User Error", msg)
            self.resetFlags()

        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_pbSuspend_clicked(self):
        """
        Suspend an active user
        """
        if "is_suspended" in self.userFlags:
            # nothing to do
            return

        QApplication.setOverrideCursor(Qt.WaitCursor)
        rc, msg = self.parent.schemaModel.suspendUser(userName=self.userName)
        if rc:
            self.resetFlags()
        else:
            self.helper.displayErrMsg("Suspend User Error", msg)
            self.resetFlags()

        QApplication.restoreOverrideCursor()

    @pyqtSlot()
    def on_btnChangePassword_clicked(self):
        """
        User requests to change the password.
        """
        if not self.helper.NoTextValueError(self.txtNewPassword.text(),
                                            "You must supply a new password"):
            QApplication.setOverrideCursor(Qt.WaitCursor)
            rc, msg = self.parent.schemaModel.changePassword(
                userName=self.userName,
                pw=self.txtNewPassword.text(),
                forceChange=self.pbChangeOnLogin.isChecked())
            if rc:
                self.helper.displayErrMsg("Change Password",
                                          "The password has been changed")
            else:
                self.helper.displayErrMsg("Change Password Error", msg)
            self.resetFlags()
            QApplication.restoreOverrideCursor()