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