class GenericManagerWidget(QtGui.QWidget, FORM_CLASS):
    Install, Delete, Uninstall, Update, Create = range(5)
    def __init__(self, genericDbManager = None, parent = None):
        """
        Constructor
        """
        super(GenericManagerWidget, self).__init__(parent)
        self.setupUi(self)
        self.genericDbManager = genericDbManager
        self.versionDict = {'2.1.3':1, 'FTer_2a_Ed':2, 'Non_Edgv':3}
        self.textDict = {'EarthCoverage':self.tr('Earth Coverage'), 
                            'Customization':self.tr('Customization'), 
                            'Style':self.tr('Style'), 
                            'ValidationConfig':self.tr('Validation'), 
                            'FieldToolBoxConfig':self.tr('Field Toolbox Configuration'),
                            'Permission':self.tr('Permissions')}
        self.captionDict = {'EarthCoverage':self.tr('Earth Coverage'), 
                            'Customization':self.tr('Customization'), 
                            'Style':self.tr('Style'), 
                            'ValidationConfig':self.tr('Validation'), 
                            'FieldToolBoxConfig':self.tr('Reclassification Setup Files'),
                            'Permission':self.tr('Select a dsgtools permission profile')}
        self.filterDict = {'EarthCoverage':self.tr('Earth Coverage Setup File (*.dsgearthcov)'), 
                            'Customization':self.tr('DsgTools Customization File (*.dsgcustom)'), 
                            'Style':self.tr('DsgTools Styles File (*.dsgstyle)'), 
                            'ValidationConfig':self.tr('DsgTools Validation Configuration File (*.dsgvalidcfg)'), 
                            'FieldToolBoxConfig':self.tr('Reclassification Setup Files (*.reclas)'),
                            'Permission':self.tr('DsgTools Permission Profile File (*.dsgperm)')}
        self.widgetName = self.textDict[self.getWhoAmI()]
        self.genericDict = None
        self.setComponentsEnabled(False)
        self.utils = Utils()
        self.setHeaders()
        self.setButtons()
        self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeWidget.customContextMenuRequested.connect(self.createMenuAssigned)
       
    def setButtons(self):
        createText = self.createPushButton.text()
        self.createPushButton.setText(createText.replace(self.tr('Setting'),self.widgetName))
        deleteText = self.deletePushButton.text()
        self.deletePushButton.setText(deleteText.replace(self.tr('Setting'),self.widgetName))
    
    def setHeaders(self):
        viewType = self.getViewType()
        if viewType == DsgEnums.Database:
            self.treeWidget.setHeaderLabels([self.tr('Database'), self.widgetName])
        else:
            self.treeWidget.setHeaderLabels([self.widgetName, self.tr('Database')])
        return viewType
    
    def getWhoAmI(self):
        return str(self.__class__).split('.')[-1].replace('\'>', '').replace('ManagerWidget','')
    
    def setChildParameter(self):
        """
        Reimplement in each child
        """
        pass
    
    def setComponentsEnabled(self, enabled):
        """
        Changes states of all components of the widget, according to the boolean parameter enabled.
        """
        self.treeWidget.setEnabled(enabled)
        self.importPushButton.setEnabled(enabled)
        self.batchImportPushButton.setEnabled(enabled)
        self.exportPushButton.setEnabled(enabled)
        self.batchExportPushButton.setEnabled(enabled)
        self.databasePerspectivePushButton.setEnabled(enabled)
        self.propertyPerspectivePushButton.setEnabled(enabled)

    def populateConfigInterface(self, templateDb, jsonDict = None):
        """
        Must be reimplemented in each child
        """
        pass    

    def readJsonFromDatabase(self, propertyName, edgvVersion):
        """
        Reads the profile file, gets a dictionary of it and builds the tree widget
        """
        self.genericDict = self.genericDbManager.getCustomization(propertyName, edgvVersion)

    @pyqtSlot(bool)
    def on_importPushButton_clicked(self):
        """
        Imports a property file into dsgtools_admindb
        """
        fd = QFileDialog()
        widgetType = self.getWhoAmI()
        filename = fd.getOpenFileName(caption=self.captionDict[widgetType],filter=self.filterDict[widgetType])
        if filename == '':
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Warning! Select a file to import!'))
            return
        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.genericDbManager.importSetting(filename)
            QApplication.restoreOverrideCursor()
            QMessageBox.information(self, self.tr('Success!'), self.widgetName + self.tr(' successfully imported.'))
        except Exception as e:
            QApplication.restoreOverrideCursor()
            QMessageBox.critical(self, self.tr('Error!'), self.tr('Error! Problem importing ') +self.widgetName + ': '  + ':'.join(e.args))
        self.refresh()
    
    @pyqtSlot(bool)
    def on_exportPushButton_clicked(self):
        """
        Export selected properties.
        """
        exportPropertyList = self.selectConfig()
        if exportPropertyList == []:
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Warning! Select a profile to export!'))
            return
        fd = QFileDialog()
        folder = fd.getExistingDirectory(caption = self.tr('Select a folder to output'))
        if folder == '':
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Warning! Select a output!'))
            return
        edgvVersion = self.genericDbManager.edgvVersion
        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            for exportProperty in exportPropertyList:
                self.genericDbManager.exportSetting(exportProperty, edgvVersion, folder)
            QApplication.restoreOverrideCursor()
            QMessageBox.information(self, self.tr('Success!'), self.widgetName + self.tr(' successfully exported.'))
        except Exception as e:
            QApplication.restoreOverrideCursor()
            QMessageBox.critical(self, self.tr('Error!'), self.tr('Error! Problem exporting ') + self.widgetName + ': ' + ':'.join(e.args))
        
    @pyqtSlot(bool)
    def on_batchExportPushButton_clicked(self):
        """
        Exports all configs from dsgtools_admindb.
        """
        fd = QFileDialog()
        folder = fd.getExistingDirectory(caption = self.tr('Select a folder to output'))
        if folder == '':
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Warning! Select a output!'))
            return
        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.genericDbManager.batchExportSettings(folder)
            QApplication.restoreOverrideCursor()
            QMessageBox.information(self, self.tr('Success!'), self.widgetName + self.tr(' successfully exported.'))
        except Exception as e:
            QApplication.restoreOverrideCursor()
            QMessageBox.critical(self, self.tr('Error!'), self.tr('Error! Problem exporting ') + self.widgetName + ': ' + ':'.join(e.args))
    
    @pyqtSlot(bool)
    def on_batchImportPushButton_clicked(self):
        """
        Imports all config files from a folder into dsgtools_admindb. It only works for a single type of config per time.
        """
        fd = QFileDialog()
        folder = fd.getExistingDirectory(caption = self.tr('Select a folder with json files: '))
        if folder == '':
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Warning! Select a input folder!'))
            return
        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.genericDbManager.batchImportSettings(folder)
            QApplication.restoreOverrideCursor()
            QMessageBox.information(self, self.tr('Success!'), self.widgetName + self.tr(' successfully imported.'))
        except Exception as e:
            QApplication.restoreOverrideCursor()
            QMessageBox.critical(self, self.tr('Error!'), self.tr('Error! Problem importing ') + self.widgetName + ': ' + ':'.join(e.args))

    @pyqtSlot(bool)
    def on_applyPushButton_clicked(self):
        dbList = self.genericDbManager.dbDict.keys()
        successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Install, dbList = dbList)
        header, operation = self.getApplyHeader()
        self.outputMessage(operation, header, successDict, exceptionDict)

    @pyqtSlot(bool)
    def on_deletePushButton_clicked(self):
        successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Delete)
        header, operation = self.getDeleteHeader()
        self.outputMessage(operation, header, successDict, exceptionDict)

    @pyqtSlot(bool)
    def on_uninstallFromSelectedPushButton_clicked(self):
        dbList = []
        successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Uninstall, dbList)
        header, operation = self.getUninstallFromSelected()
        self.outputMessage(operation, header, successDict, exceptionDict)

    def getViewType(self):
        if self.databasePerspectivePushButton.isChecked():
            return DsgEnums.Database
        else:
            return DsgEnums.Property

    @pyqtSlot(bool, name='on_databasePerspectivePushButton_clicked')
    @pyqtSlot(bool, name='on_propertyPerspectivePushButton_clicked')
    def refresh(self):
        viewType = self.setHeaders()
        propertyPerspectiveDict = self.genericDbManager.getPropertyPerspectiveDict(viewType)
        self.treeWidget.clear()
        rootNode = self.treeWidget.invisibleRootItem()
        if viewType == DsgEnums.Database:
            propertyList = self.genericDbManager.dbDict.keys()
        else:
            propertyList = propertyPerspectiveDict.keys()
        for key in propertyList:
            parentCustomItem = self.utils.createWidgetItem(rootNode, key, 0)
            if key in propertyPerspectiveDict.keys():
                for item in propertyPerspectiveDict[key]:
                    if item and item <> '':
                        dbItem = self.utils.createWidgetItem(parentCustomItem, item, 1)
        self.treeWidget.sortItems(0, Qt.AscendingOrder)
        self.treeWidget.expandAll()
        self.treeWidget.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.treeWidget.header().setStretchLastSection(False)
    
    def outputMessage(self, operation, header, successDict, exceptionDict):
        """
        successDict = {configName: [--list of successful databases--]}
        exceptionDict = {configName: {dbName: errorText}}
        """
        viewType = self.getViewType()
        msg = header
        for setting in successDict.keys():
            successList = successDict[setting]
            if len(successDict[setting]) > 0:
                msg += self.tr('\nSuccessful ')
                msg += operation + ' : '
                msg += setting
                if successList:
                    if len(successList) > 0:
                        try:
                            msg += self.tr(' on databases ') + ', '.join(successList)
                        except: #none type case, just add .
                            msg += '.'
        msg += self.logInternalError(exceptionDict)
        QMessageBox.warning(self, self.tr('Operation Complete!'), msg)
    
    def logInternalError(self, exceptionDict):
        """
        exceptionDict = {configName: {dbName: errorText}}
        """
        msg = ''
        configList = exceptionDict.keys()
        if len(configList) > 0:
            msg += self.tr('\nConfig with error:') + ','.join(configList)
            msg+= self.tr('\nError messages for each config and database were output in qgis log.')
            for config in configList:
                for dbName in exceptionDict[config].keys():
                    if exceptionDict[config][dbName] != dict():
                        QgsMessageLog.logMessage(self.tr('Error for config ')+ config + ' in database ' +dbName+' : '+exceptionDict[config][dbName], "DSG Tools Plugin", QgsMessageLog.CRITICAL)
        return msg 

    def manageSetting(self, config, manageType, dbList = [], parameterDict = dict()):
        if manageType == GenericManagerWidget.Install:
            return self.genericDbManager.installSetting(config, dbNameList = dbList)
        elif manageType == GenericManagerWidget.Delete:
            return self.genericDbManager.deleteSetting(config)
        elif manageType == GenericManagerWidget.Uninstall:
            return self.genericDbManager.uninstallSetting(config, dbNameList = dbList)
        elif manageType == GenericManagerWidget.Update:
            return self.genericDbManager.updateSetting(config, parameterDict['newJsonDict'])
        elif manageType == GenericManagerWidget.Create:
            return self.genericDbManager.createSetting(config, parameterDict['newJsonDict'])
    
    def selectConfig(self):
        availableConfig = self.genericDbManager.getPropertyPerspectiveDict().keys()
        dlg = ListSelector(availableConfig,[])
        dlg.exec_()
        selectedConfig = dlg.getSelected()
        return selectedConfig

    def manageSettings(self, manageType, dbList = [], selectedConfig = [], parameterDict = dict()):
        """
        Executes the setting work according to manageType
        successDict = {configName: [--list of successful databases--]}
        exceptionDict = {configName: {dbName: errorText}}
        """
        if selectedConfig == []:
            selectedConfig = self.selectConfig()
            if selectedConfig == []:
                QMessageBox.warning(self, self.tr('Warning!'), self.tr('Select at least one configuration!'))
                return (dict(),dict())
        successDict = dict()
        exceptionDict = dict()
        if self.lookAndPromptForStructuralChanges(dbList = dbList):
            for config in selectedConfig:
                QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
                sucessList, errorDict = self.manageSetting(config, manageType, dbList = dbList, parameterDict = parameterDict)
                QApplication.restoreOverrideCursor()
                successDict[config] = sucessList
                if errorDict != dict():
                    exceptionDict[config] = errorDict
            self.refresh()
            return successDict, exceptionDict
        else:
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Operation canceled by user!'))
            return (dict(),dict())
    
    def createMenuAssigned(self, position):
        """
        Creates a pop up menu
        """
        viewType = self.getViewType()
        if viewType == DsgEnums.Database:
            self.createDbPerspectiveContextMenu(position)
        if viewType == DsgEnums.Property:
            self.createPropertyPerspectiveContextMenu(position)

    def createDbPerspectiveContextMenu(self, position):
        menu = QMenu()
        item = self.treeWidget.itemAt(position)
        if item:
            if item.text(0) != '':
                menu.addAction(self.tr('Uninstall all settings from selected database'), self.uninstallSettings)
                menu.addAction(self.tr('Manage settings from selected database'), self.manageDbSettings)
            elif item.text(1) != '':
                menu.addAction(self.tr('Update selected setting'), self.updateSelectedSetting)
                menu.addAction(self.tr('Clone selected setting'), self.cloneSelectedSetting)
                menu.addAction(self.tr('Uninstall selected setting'), self.uninstallSettings)
                menu.addAction(self.tr('Delete selected setting'), self.deleteSelectedSetting)
        menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
    
    def createPropertyPerspectiveContextMenu(self, position):
        menu = QMenu()
        item = self.treeWidget.itemAt(position)
        if item:
            if item.text(0) != '':
                menu.addAction(self.tr('Update selected setting'), self.updateSelectedSetting)
                menu.addAction(self.tr('Clone selected setting'), self.cloneSelectedSetting)
                menu.addAction(self.tr('Manage selected setting'), self.manageSelectedSetting)
                menu.addAction(self.tr('Uninstall selected setting on all databases'), self.uninstallSettings)
                menu.addAction(self.tr('Delete selected setting'), self.deleteSelectedSetting)                
            elif item.text(1) != '':
                menu.addAction(self.tr('Manage Settings on database'), self.manageDbSettings)
                menu.addAction(self.tr('Uninstall selected setting on selected database'), self.uninstallSettings)
        menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
    
    def manageDbSettings(self):
        """
        1. get installed profiles and available profiles
        2. populate selection with items from #1
        3. get final lists and uninstall items and them install items
        """
        uiParameterDict = self.getParametersFromInterface()
        propertyPerspectiveDict = self.genericDbManager.getPropertyPerspectiveDict()
        availableConfig = [i for i in propertyPerspectiveDict.keys() if i not in uiParameterDict['parameterList']]
        dlg = ListSelector(availableConfig,uiParameterDict['parameterList'])
        dlg.exec_()
        fromLs, toLs = dlg.getInputAndOutputLists()
        #build install list: elements from toLs that were not in uiParameterDict['parameterList']
        installList = [i for i in toLs if i not in uiParameterDict['parameterList']]
        #build uninstall list: : elements from fromLs that were not in availableConfig
        uninstallList = [i for i in fromLs if i in uiParameterDict['parameterList']]
        if (installList == [] and uninstallList == []):
            QMessageBox.warning(self, self.tr('Error!'), self.tr('Select at least one configuration to manage!'))
            return
        if installList <> []:
            #install:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Install, selectedConfig = installList, dbList = uiParameterDict['databaseList'])
            header, operation = self.getApplyHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)
        if uninstallList <> []:
            #uninstall:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Uninstall, selectedConfig = uninstallList, dbList = uiParameterDict['databaseList'])
            header, operation = self.getUninstallSelectedSettingHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)

    def manageSelectedSetting(self):
        """
        1. get installed profiles and available profiles
        2. populate selection with items from #1
        3. get final lists and uninstall items and them install items
        """
        uiParameterDict = self.getParametersFromInterface()
        propertyPerspectiveDict = self.genericDbManager.getPropertyPerspectiveDict(viewType = DsgEnums.Database)
        availableDb = [i for i in propertyPerspectiveDict.keys() if i not in uiParameterDict['databaseList']]
        dlg = ListSelector(availableDb,uiParameterDict['databaseList'])
        dlg.exec_()
        fromLs, toLs = dlg.getInputAndOutputLists()
        #build install list: elements from toLs that were not in uiParameterDict['parameterList']
        installList = [i for i in toLs if i not in uiParameterDict['databaseList']]
        #build uninstall list: : elements from fromLs that were not in availableConfig
        uninstallList = [i for i in fromLs if i in uiParameterDict['databaseList']]
        if (installList == [] and uninstallList == []):
            QMessageBox.warning(self, self.tr('Error!'), self.tr('Select at least one configuration database to manage!'))
            return
        if installList <> []:
            #install:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Install, selectedConfig = uiParameterDict['parameterList'], dbList = installList)
            header, operation = self.getApplyHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)
        if uninstallList <> []:
            #uninstall:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Uninstall, selectedConfig = uiParameterDict['parameterList'], dbList = uninstallList)
            header, operation = self.getUninstallSelectedSettingHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)

    def updateSelectedSetting(self):
        """
        1. get setting dict
        2. populate setting interface
        3. from new dict, update setting
        """
        currItem = self.treeWidget.currentItem()
        if self.getViewType() == DsgEnums.Database:
            settingName = currItem.text(1)
        else:
            settingName = currItem.text(0)
        edgvVersion = self.genericDbManager.edgvVersion
        templateDb = self.genericDbManager.instantiateTemplateDb(edgvVersion)
        originalDict = self.genericDbManager.getSetting(settingName, edgvVersion)
        newDict = self.populateConfigInterface(templateDb, jsonDict = originalDict)
        if newDict:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Update, selectedConfig = [settingName], parameterDict = {'newJsonDict':newDict})
            header, operation = self.getUpdateSelectedSettingHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)
    
    def cloneSelectedSetting(self):
        currItem = self.treeWidget.currentItem()
        if self.getViewType() == DsgEnums.Database:
            settingName = currItem.text(1)
        else:
            settingName = currItem.text(0)
        edgvVersion = self.genericDbManager.edgvVersion
        templateDb = self.genericDbManager.instantiateTemplateDb(edgvVersion)
        originalDict = self.genericDbManager.getSetting(settingName, edgvVersion)
        newDict = self.populateConfigInterface(templateDb, jsonDict = originalDict)
        if newDict:
            successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Create, selectedConfig = [settingName], parameterDict = {'newJsonDict':newDict})
            header, operation = self.getUpdateSelectedSettingHeader()
            self.outputMessage(operation, header, successDict, exceptionDict)

    def getParametersFromInterface(self):
        """
        Gets selected database and selected property. 
        Returns {'databaseList':dbList, 'parameterList':parameterList}
        """
        currItem = self.treeWidget.currentItem()
        if self.getViewType() == DsgEnums.Database:
            #2 possibilities: leaf (if first column is '') or parent (if first column != '')
            if currItem.text(0) == '':
                #leaf -> must get 
                parentNode = currItem.parent()
                dbName = parentNode.text(0)
                parameter = currItem.text(1)
                return {'databaseList':[dbName], 'parameterList':[parameter]}
            else:
                #parent
                dbName = currItem.text(0)
                childCount = currItem.childCount()
                parameterList = []
                for i in range(childCount):
                    childNode = currItem.child(i)
                    parameterName = childNode.text(1)
                    if parameterName not in parameterList:
                        parameterList.append(parameterName)
                return {'databaseList':[dbName], 'parameterList':parameterList}
        else:
            if currItem.text(0) == '':
                #leaf
                parentNode = currItem.parent()
                parameter = parentNode.text(0)
                dbName = currItem.text(1)
                return {'databaseList':[dbName], 'parameterList':[parameter]}
            else:
                #parent
                parameter = currItem.text(0)
                childCount = currItem.childCount()
                dbList = []
                for i in range(childCount):
                    childNode = currItem.child(i)
                    dbName = childNode.text(1)
                    if dbName not in dbList:
                        dbList.append(dbName)
                return {'databaseList':dbList, 'parameterList':[parameter]}

    def uninstallSettings(self):
        edgvVersion = self.genericDbManager.edgvVersion
        uiParameterDict = self.getParametersFromInterface()
        successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Uninstall, dbList = uiParameterDict['databaseList'], selectedConfig = uiParameterDict['parameterList'])
        header, operation = self.getUninstallSelectedSettingHeader()
        self.outputMessage(operation, header, successDict, exceptionDict)
    
    def deleteSelectedSetting(self):
        edgvVersion = self.genericDbManager.edgvVersion
        uiParameterDict = self.getParametersFromInterface()
        settingTextList = ', '.join(uiParameterDict['parameterList'])
        if QtGui.QMessageBox.question(self, self.tr('Question'), self.tr('Do you really want to delete ')+settingTextList+'?', QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel:
            return
        successDict, exceptionDict = self.manageSettings(GenericManagerWidget.Delete, selectedConfig = uiParameterDict['parameterList'])
        header, operation = self.getDeleteHeader()
        self.outputMessage(operation, header, successDict, exceptionDict)
    
    def lookAndPromptForStructuralChanges(self, dbList = []):
        '''
        Returns True if user accepts the process
        '''
        structuralChanges = self.genericDbManager.hasStructuralChanges(dbList)
        if structuralChanges != []:
            dbChangeList = ', '.join(structuralChanges)
            if QtGui.QMessageBox.question(self, self.tr('Question'), self.tr('Do you really want to apply selected operation on ')+dbChangeList+'?'+self.tr(' (Data may be lost in the process)'), QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel) == QtGui.QMessageBox.Cancel:
                return False
            else:
                return True
        else:
            return True
Example #2
0
class CustomTableSelector(QtGui.QWidget, FORM_CLASS):
    selectionChanged = pyqtSignal(list, str)

    def __init__(self, customNumber=None, parent=None):
        """Constructor."""
        super(self.__class__, self).__init__(parent)
        self.fromLs = []
        self.toLs = []
        self.utils = Utils()
        self.setupUi(self)

    def resizeTrees(self):
        """
        Expands headers
        """
        self.fromTreeWidget.expandAll()
        self.fromTreeWidget.header().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        self.fromTreeWidget.header().setStretchLastSection(False)
        self.toTreeWidget.expandAll()
        self.toTreeWidget.header().setResizeMode(
            QtGui.QHeaderView.ResizeToContents)
        self.toTreeWidget.header().setStretchLastSection(False)

    def sortItems(self, treeWidget):
        """
        Sorts items from input treeWidget
        """
        rootNode = treeWidget.invisibleRootItem()
        rootNode.sortChildren(0, Qt.AscendingOrder)
        for i in range(rootNode.childCount()):
            rootNode.child(i).sortChildren(1, Qt.AscendingOrder)

    def setTitle(self, title):
        """
        Setting the title
        """
        self.groupBox.setTitle(title)

    def setFilterColumn(self, customNumber=None):
        """
        Chooses which column is going to be used in the filter
        """
        if isinstance(customNumber, int):
            self.filterColumnKey = self.headerList[customNumber]
        elif self.headerList:
            self.filterColumnKey = self.headerList[1]
        else:
            self.filterColumnKey = self.headerList[0]

    def clearAll(self):
        """
        Clears everything to return to the initial state
        """
        self.filterLineEdit.clear()

    def setHeaders(self, headerList, customNumber=None):
        """
        Sets fromTreeWidget and toTreeWidget headers
        """
        self.headerList = headerList
        self.fromTreeWidget.setHeaderLabels(headerList)
        self.toTreeWidget.setHeaderLabels(headerList)
        self.setFilterColumn(customNumber=customNumber)

    def setInitialState(self, fromDictList, unique=False):
        """
        Sets the initial state
        """
        self.fromLs = []
        self.toLs = []
        self.fromTreeWidget.clear()
        self.fromTreeWidget.clear()
        if not isinstance(fromDictList, int):
            self.addItemsToTree(self.fromTreeWidget,
                                fromDictList,
                                self.fromLs,
                                unique=unique)

    def getChildNode(self, parentNode, textList):
        """
        Returns child node with columns equals to textList items. If no node is found, return None
        """
        for i in range(parentNode.childCount()):
            nodeFound = True
            childNode = parentNode.child(i)
            for j in range(len(textList)):
                if childNode.text(j) != textList[j]:
                    nodeFound = False
                    break
            if nodeFound:
                return childNode
        return None

    def addItemsToTree(self,
                       treeWidget,
                       addItemDictList,
                       controlList,
                       unique=False):
        """
        Adds items from addItemDictList in treeWidget.
        addItemDictList = [-list of dicts with keys corresponding to header list texts-]
        unique: only adds item if it is not in already in tree
        """
        rootNode = treeWidget.invisibleRootItem()  #invisible root item
        for dictItem in addItemDictList:
            firstColumnChild = self.getChildNode(
                rootNode, [dictItem[self.headerList[0]]] + [''] *
                (len(self.headerList) - 1)
            )  #looks for a item in the format ['first column text', '','',...,'']
            if not firstColumnChild:
                firstColumnChild = self.utils.createWidgetItem(
                    rootNode, dictItem[self.headerList[0]], 0)
            textList = [
                dictItem[self.headerList[i]]
                for i in range(len(self.headerList))
            ]
            if unique:
                childNode = self.getChildNode(firstColumnChild, textList)
                if not childNode:
                    item = self.utils.createWidgetItem(firstColumnChild,
                                                       textList)
                    itemList = self.getItemList(item)
                    if itemList not in controlList:
                        controlList.append(itemList)
            else:
                item = self.utils.createWidgetItem(firstColumnChild, textList)
                itemList = self.getItemList(item)
                controlList.append(itemList)
        self.resizeTrees()
        self.sortItems(treeWidget)

    def getItemList(self, item, returnAsDict=False):
        """
        Gets item as a list
        """
        if returnAsDict:
            returnItem = dict()
        else:
            returnItem = []
        for i in range(item.columnCount()):
            if returnAsDict:
                returnItem[self.headerList[i]] = item.text(i)
            else:
                returnItem.append(item.text(i))
        return returnItem

    def getLists(self, sender):
        """
        Returns a list composed by (originTreeWidget, --list that controls previous originTreeWidget--, destinationTreeWidget, --list that controls previous destinationTreeWidget--)
        """
        text = sender.text()
        if text == '>':
            return self.fromTreeWidget, self.fromLs, self.toTreeWidget, self.toLs, False
        if text == '>>':
            return self.fromTreeWidget, self.fromLs, self.toTreeWidget, self.toLs, True
        if text == '<':
            return self.toTreeWidget, self.toLs, self.fromTreeWidget, self.fromLs, False
        if text == '<<':
            return self.toTreeWidget, self.toLs, self.fromTreeWidget, self.fromLs, True

    @pyqtSlot(bool, name='on_pushButtonSelectOne_clicked')
    @pyqtSlot(bool, name='on_pushButtonDeselectOne_clicked')
    @pyqtSlot(bool, name='on_pushButtonSelectAll_clicked')
    @pyqtSlot(bool, name='on_pushButtonDeselectAll_clicked')
    def selectItems(self, isSelected, selectedItems=[]):
        """
        Adds the selected items to the "to" list
        """
        #gets lists
        originTreeWidget, originControlLs, destinationTreeWidget, destinationControlLs, allItems = self.getLists(
            self.sender())
        #root nodes
        originRoot = originTreeWidget.invisibleRootItem()
        destinationRoot = destinationTreeWidget.invisibleRootItem()
        selectedItemList = []
        self.getSelectedItems(originRoot, selectedItemList)
        for i in range(originRoot.childCount())[::-1]:
            catChild = originRoot.child(i)
            #if son of originRootNode is selected, adds it to destinationRootNode
            moveNode = allItems or (catChild in selectedItemList)
            #get destination parent, creates one in destination if not exists
            destinationCatChild = self.getDestinationNode(
                destinationRoot, catChild)
            for j in range(catChild.childCount())[::-1]:
                nodeChild = catChild.child(j)
                moveChild = (nodeChild in selectedItemList) or moveNode
                if self.moveChild(catChild, j, destinationCatChild, moveChild):
                    itemList = self.getItemList(nodeChild)
                    destinationControlLs.append(itemList)
                    originControlLs.pop(originControlLs.index(itemList))
            destinationCatChild.sortChildren(1, Qt.AscendingOrder)
            if catChild.childCount() == 0:
                originRoot.takeChild(i)
            destinationRoot.sortChildren(0, Qt.AscendingOrder)
        for i in range(destinationRoot.childCount())[::-1]:
            if destinationRoot.child(i).childCount() == 0:
                destinationRoot.takeChild(i)
        destinationRoot.sortChildren(0, Qt.AscendingOrder)
        self.resizeTrees()

    def getSelectedItems(self, treeWidgetNode, itemList):
        """
        Recursive method to get all selected nodes of treeWidget
        """
        for i in range(treeWidgetNode.childCount()):
            childItem = treeWidgetNode.child(i)
            if childItem.isSelected() and (childItem not in itemList):
                itemList.append(childItem)
            for j in range(childItem.childCount()):
                self.getSelectedItems(childItem, itemList)

    def moveChild(self, parentNode, idx, destinationNode, isSelected):
        """
        If node is selected, removes node from parentNode and adds it to destinationNode
        """
        if isSelected:
            child = parentNode.takeChild(idx)
            destinationNode.addChild(child)
            return True
        else:
            return False

    def getDestinationNode(self, destinationRoot, catChild, returnNew=True):
        """
        Looks for node in destination and returns it. If none is found, creates one and returns it
        """
        #get destination parent, creates one in destination if not exists
        destinationCatChild = None
        if isinstance(catChild, list):
            comparisonText = catChild[0]
            if returnNew:
                itemTextList = [catChild[i] for i in range(len(catChild))]
        else:
            comparisonText = catChild.text(0)
            if returnNew:
                itemTextList = [
                    catChild.text(i) for i in range(catChild.columnCount())
                ]

        for i in range(destinationRoot.childCount()):
            candidate = destinationRoot.child(i)
            if candidate.text(0) == comparisonText:
                #if candidate is found, returns candidate
                return candidate
        #if candidate is not found, creates one and returns it
        if returnNew:
            if not destinationCatChild:
                return QTreeWidgetItem(destinationRoot, itemTextList)
        else:
            return None

    def on_filterLineEdit_textChanged(self, text):
        """
        Filters the items to make it easier to spot and select them
        """
        classes = [
            node[1].lower() for node in self.fromLs
            if text.lower() in node[1].lower()
        ]  #text list
        filteredClasses = [
            i for i in classes
            if i.lower() not in [j[1].lower() for j in self.toLs]
        ]  #text list
        self.filterTree(self.fromTreeWidget, self.fromLs, filteredClasses, 1)
        self.resizeTrees()

    def filterTree(self, treeWidget, controlList, filterList, columnIdx):
        '''
        Actual filter
        '''
        treeWidget.clear()
        rootNode = treeWidget.invisibleRootItem()
        #remove items that are not in filterList
        for item in controlList:
            if item[columnIdx].lower() in filterList:
                firstColumnChild = self.getChildNode(
                    rootNode, [item[0]] + [''] * (len(item) - 1)
                )  #looks for a item in the format ['first column text', '','',...,'']
                if not firstColumnChild:
                    firstColumnChild = self.utils.createWidgetItem(
                        rootNode, item[0], 0)
                QTreeWidgetItem(firstColumnChild, item)
        rootNode.sortChildren(0, Qt.AscendingOrder)
        for i in range(rootNode.childCount()):
            rootNode.child(i).sortChildren(1, Qt.AscendingOrder)

    def getSelectedNodes(self, concatenated=True):
        """
        Returns a list of selected nodes converted into a string separated by ','
        """
        selected = []
        rootNode = self.toTreeWidget.invisibleRootItem()
        for i in range(rootNode.childCount()):
            catNode = rootNode.child(i)
            for j in range(catNode.childCount()):
                item = catNode.child(j)
                if concatenated:
                    catList = [item.text(i) for i in range(item.columnCount())]
                    selected.append(','.join(catList))
                else:
                    selected.append(item)
        return selected

    def addItemsToWidget(self, itemList, unique=False):
        """
        Adds items to tree that is already built.
        """
        self.addItemsToTree(self.fromTreeWidget,
                            itemList,
                            self.fromLs,
                            unique=unique)

    def removeItemsFromWidget(self, removeList):
        """
        Searches both lists and removes items that are in removeList
        """
        self.removeItemsFromTree(removeList, self.fromTreeWidget, self.fromLs)
        self.removeItemsFromTree(removeList, self.toTreeWidget, self.toLs)

    def removeItemsFromTree(self, dictItemList, treeWidget, controlList):
        """
        Searches treeWidget and removes items that are in removeList and updates controlList
        """
        treeRoot = treeWidget.invisibleRootItem()
        catList = [i[self.headerList[0]] for i in dictItemList]
        returnList = []
        for i in range(treeRoot.childCount())[::-1]:
            catChild = treeRoot.child(i)
            if catChild.text(0) in catList:
                for j in range(catChild.childCount())[::-1]:
                    nodeChild = catChild.child(j)
                    nodeChildDict = self.getItemList(nodeChild,
                                                     returnAsDict=True)
                    nodeChildDict[self.headerList[0]] = catChild.text(0)
                    if nodeChildDict in dictItemList:
                        catChild.takeChild(j)
                        itemList = self.getItemList(nodeChild)
                        controlList.pop(controlList.index(itemList))
        for i in range(treeRoot.childCount())[::-1]:
            if treeRoot.child(i).childCount() == 0:
                treeRoot.takeChild(i)
        treeRoot.sortChildren(0, Qt.AscendingOrder)
        for i in range(treeRoot.childCount()):
            treeRoot.child(i).sortChildren(1, Qt.AscendingOrder)
class CustomTableSelector(QtGui.QWidget, FORM_CLASS):
    selectionChanged = pyqtSignal(list,str)

    def __init__(self, customNumber = None, parent = None):
        """Constructor."""
        super(self.__class__, self).__init__(parent)
        self.fromLs = []
        self.toLs = []
        self.utils = Utils()
        self.setupUi(self)
    
    def resizeTrees(self):
        """
        Expands headers
        """
        self.fromTreeWidget.expandAll()
        self.fromTreeWidget.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.fromTreeWidget.header().setStretchLastSection(False)
        self.toTreeWidget.expandAll()
        self.toTreeWidget.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.toTreeWidget.header().setStretchLastSection(False)
    
    def sortItems(self, treeWidget):
        """
        Sorts items from input treeWidget
        """
        rootNode = treeWidget.invisibleRootItem()
        rootNode.sortChildren(0, Qt.AscendingOrder)
        for i in range(rootNode.childCount()):
            rootNode.child(i).sortChildren(1, Qt.AscendingOrder)
    
    def setTitle(self,title):
        """
        Setting the title
        """
        self.groupBox.setTitle(title)
    
    def setFilterColumn(self, customNumber = None):
        """
        Chooses which column is going to be used in the filter
        """
        if isinstance(customNumber, int):
            self.filterColumnKey = self.headerList[customNumber]
        elif self.headerList:
            self.filterColumnKey = self.headerList[1]
        else:
            self.filterColumnKey = self.headerList[0]
    
    def clearAll(self):
        """
        Clears everything to return to the initial state
        """
        self.filterLineEdit.clear()
    
    def setHeaders(self, headerList, customNumber = None):
        """
        Sets fromTreeWidget and toTreeWidget headers
        """
        self.headerList = headerList
        self.fromTreeWidget.setHeaderLabels(headerList)
        self.toTreeWidget.setHeaderLabels(headerList)
        self.setFilterColumn(customNumber = customNumber)
    
    def setInitialState(self, fromDictList, unique=False):
        """
        Sets the initial state
        """
        self.fromLs = []
        self.toLs = []
        self.fromTreeWidget.clear()
        self.fromTreeWidget.clear()
        if not isinstance(fromDictList, int):
            self.addItemsToTree(self.fromTreeWidget, fromDictList, self.fromLs, unique = unique)
    
    def getChildNode(self, parentNode, textList):
        """
        Returns child node with columns equals to textList items. If no node is found, return None
        """
        for i in range(parentNode.childCount()):
            nodeFound = True
            childNode = parentNode.child(i)
            for j in range(len(textList)):
                if childNode.text(j) != textList[j]:
                    nodeFound = False
                    break
            if nodeFound:
                return childNode
        return None

    def addItemsToTree(self, treeWidget, addItemDictList, controlList, unique = False):
        """
        Adds items from addItemDictList in treeWidget.
        addItemDictList = [-list of dicts with keys corresponding to header list texts-]
        unique: only adds item if it is not in already in tree
        """
        rootNode = treeWidget.invisibleRootItem() #invisible root item
        for dictItem in addItemDictList:
            firstColumnChild = self.getChildNode(rootNode, [dictItem[self.headerList[0]]]+['']*(len(self.headerList)-1)) #looks for a item in the format ['first column text', '','',...,'']
            if not firstColumnChild:
                firstColumnChild = self.utils.createWidgetItem(rootNode,dictItem[self.headerList[0]],0)
            textList = [dictItem[self.headerList[i]] for i in range(len(self.headerList))]
            if unique:
                childNode = self.getChildNode(firstColumnChild, textList)
                if not childNode:
                    item = self.utils.createWidgetItem(firstColumnChild,textList)
                    itemList = self.getItemList(item)
                    if itemList not in controlList:
                        controlList.append(itemList)
            else:
                item = self.utils.createWidgetItem(firstColumnChild,textList)
                itemList = self.getItemList(item)
                controlList.append(itemList)
        self.resizeTrees()
        self.sortItems(treeWidget)
    
    def getItemList(self, item, returnAsDict = False):
        """
        Gets item as a list
        """
        if returnAsDict:
            returnItem = dict()
        else:
            returnItem = []
        for i in range(item.columnCount()):
            if returnAsDict:
                returnItem[self.headerList[i]] = item.text(i)
            else:
                returnItem.append(item.text(i))
        return returnItem

    def getLists(self, sender):
        """
        Returns a list composed by (originTreeWidget, --list that controls previous originTreeWidget--, destinationTreeWidget, --list that controls previous destinationTreeWidget--)
        """
        text = sender.text()
        if text == '>':
            return self.fromTreeWidget, self.fromLs, self.toTreeWidget, self.toLs, False
        if text == '>>':
            return self.fromTreeWidget, self.fromLs, self.toTreeWidget, self.toLs, True
        if text == '<':
            return self.toTreeWidget, self.toLs, self.fromTreeWidget, self.fromLs, False
        if text == '<<':
            return self.toTreeWidget, self.toLs, self.fromTreeWidget, self.fromLs, True

    @pyqtSlot(bool, name='on_pushButtonSelectOne_clicked')
    @pyqtSlot(bool, name='on_pushButtonDeselectOne_clicked')
    @pyqtSlot(bool, name='on_pushButtonSelectAll_clicked')
    @pyqtSlot(bool, name='on_pushButtonDeselectAll_clicked')
    def selectItems(self, isSelected, selectedItems=[]):
        """
        Adds the selected items to the "to" list
        """
        #gets lists
        originTreeWidget, originControlLs, destinationTreeWidget, destinationControlLs, allItems = self.getLists(self.sender())
        #root nodes
        originRoot = originTreeWidget.invisibleRootItem()
        destinationRoot = destinationTreeWidget.invisibleRootItem()
        selectedItemList = []
        self.getSelectedItems(originRoot, selectedItemList)
        for i in range(originRoot.childCount())[::-1]:
            catChild = originRoot.child(i)
            #if son of originRootNode is selected, adds it to destinationRootNode
            moveNode = allItems or (catChild in selectedItemList)
            #get destination parent, creates one in destination if not exists
            destinationCatChild = self.getDestinationNode(destinationRoot, catChild)
            for j in range(catChild.childCount())[::-1]:
                nodeChild = catChild.child(j)
                moveChild = (nodeChild in selectedItemList) or moveNode
                if self.moveChild(catChild, j, destinationCatChild, moveChild):
                    itemList = self.getItemList(nodeChild)
                    destinationControlLs.append(itemList)
                    originControlLs.pop(originControlLs.index(itemList))
            destinationCatChild.sortChildren(1, Qt.AscendingOrder)
            if catChild.childCount() == 0:
                originRoot.takeChild(i)
            destinationRoot.sortChildren(0, Qt.AscendingOrder)
        for i in range(destinationRoot.childCount())[::-1]:
            if destinationRoot.child(i).childCount() == 0:
                destinationRoot.takeChild(i)
        destinationRoot.sortChildren(0, Qt.AscendingOrder)
        self.resizeTrees()

    def getSelectedItems(self, treeWidgetNode, itemList):
        """
        Recursive method to get all selected nodes of treeWidget
        """
        for i in range(treeWidgetNode.childCount()):
            childItem = treeWidgetNode.child(i)
            if childItem.isSelected() and (childItem not in itemList):
                itemList.append(childItem)
            for j in range(childItem.childCount()):
                self.getSelectedItems(childItem, itemList)
    
    def moveChild(self, parentNode, idx, destinationNode, isSelected):
        """
        If node is selected, removes node from parentNode and adds it to destinationNode
        """
        if isSelected:
            child = parentNode.takeChild(idx)
            destinationNode.addChild(child)
            return True
        else:
            return False

    def getDestinationNode(self, destinationRoot, catChild, returnNew = True):
        """
        Looks for node in destination and returns it. If none is found, creates one and returns it
        """
        #get destination parent, creates one in destination if not exists
        destinationCatChild = None
        if isinstance(catChild,list):
            comparisonText = catChild[0]
            if returnNew:
                itemTextList = [catChild[i] for i in range(len(catChild))]
        else:
            comparisonText = catChild.text(0)
            if returnNew:
                itemTextList = [catChild.text(i) for i in range(catChild.columnCount())]

        for i in range(destinationRoot.childCount()):
            candidate = destinationRoot.child(i)
            if candidate.text(0) == comparisonText:
                #if candidate is found, returns candidate
                return candidate
        #if candidate is not found, creates one and returns it
        if returnNew:
            if not destinationCatChild:
                return QTreeWidgetItem(destinationRoot,itemTextList)
        else:
            return None
    
    def on_filterLineEdit_textChanged(self, text):
        """
        Filters the items to make it easier to spot and select them
        """
        classes = [node[1].lower() for node in self.fromLs if text.lower() in node[1].lower()] #text list
        filteredClasses = [i for i in classes if i.lower() not in [j[1].lower() for j in self.toLs]] #text list
        self.filterTree(self.fromTreeWidget, self.fromLs, filteredClasses, 1)
        self.resizeTrees()
    
    def filterTree(self, treeWidget, controlList, filterList, columnIdx):
        '''
        Actual filter
        '''
        treeWidget.clear()
        rootNode = treeWidget.invisibleRootItem()
        #remove items that are not in filterList
        for item in controlList:
            if item[columnIdx].lower() in filterList:
                firstColumnChild = self.getChildNode(rootNode, [item[0]]+['']*(len(item)-1)) #looks for a item in the format ['first column text', '','',...,'']
                if not firstColumnChild:
                    firstColumnChild = self.utils.createWidgetItem(rootNode, item[0], 0)
                QTreeWidgetItem(firstColumnChild, item)
        rootNode.sortChildren(0, Qt.AscendingOrder)
        for i in range(rootNode.childCount()):
            rootNode.child(i).sortChildren(1, Qt.AscendingOrder)
    
    def getSelectedNodes(self, concatenated = True):
        """
        Returns a list of selected nodes converted into a string separated by ','
        """
        selected = []
        rootNode = self.toTreeWidget.invisibleRootItem()
        for i in range(rootNode.childCount()):
            catNode = rootNode.child(i)
            for j in range(catNode.childCount()):
                item = catNode.child(j)
                if concatenated:
                    catList = [item.text(i) for i in range(item.columnCount())]
                    selected.append(','.join(catList))
                else:
                    selected.append(item)
        return selected

    def addItemsToWidget(self, itemList, unique = False):
        """
        Adds items to tree that is already built.
        """
        self.addItemsToTree(self.fromTreeWidget, itemList, self.fromLs, unique = unique)
    
    def removeItemsFromWidget(self, removeList):
        """
        Searches both lists and removes items that are in removeList
        """
        self.removeItemsFromTree(removeList, self.fromTreeWidget, self.fromLs)
        self.removeItemsFromTree(removeList, self.toTreeWidget, self.toLs)
    
    def removeItemsFromTree(self, dictItemList, treeWidget, controlList):
        """
        Searches treeWidget and removes items that are in removeList and updates controlList
        """        
        treeRoot = treeWidget.invisibleRootItem()
        catList = [i[self.headerList[0]] for i in dictItemList]
        returnList = []
        for i in range(treeRoot.childCount())[::-1]:
            catChild = treeRoot.child(i)
            if catChild.text(0) in catList:
                for j in range(catChild.childCount())[::-1]:
                    nodeChild = catChild.child(j)
                    nodeChildDict = self.getItemList(nodeChild, returnAsDict = True)
                    nodeChildDict[self.headerList[0]] = catChild.text(0)
                    if nodeChildDict in dictItemList:
                        catChild.takeChild(j)
                        itemList = self.getItemList(nodeChild)
                        controlList.pop(controlList.index(itemList))
        for i in range(treeRoot.childCount())[::-1]:
            if treeRoot.child(i).childCount() == 0:
                treeRoot.takeChild(i)
        treeRoot.sortChildren(0, Qt.AscendingOrder)
        for i in range(treeRoot.childCount()):
            treeRoot.child(i).sortChildren(1, Qt.AscendingOrder)