Example #1
0
    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)
Example #2
0
    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 __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)
Example #4
0
    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)
Example #5
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()))
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()
Example #7
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
Example #8
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)