def __init__(self): super().__init__() self.setWindowTitle('World Country Diagram') self.resize(500, 700) treeView = QTreeView() treeView.setHeaderHidden(True) treeModel = QStandardItemModel() rootNode = treeModel.invisibleRootItem() # America america = StandardItem('America', 16, set_bold=True) california = StandardItem('California', 14) america.appendRow(california) oakland = StandardItem('Oakland', 12, color=QColor(155, 0, 0)) sanfrancisco = StandardItem('San Francisco', 12, color=QColor(155, 0, 0)) sanjose = StandardItem('San Jose', 12, color=QColor(155, 0, 0)) california.appendRow(oakland) california.appendRow(sanfrancisco) california.appendRow(sanjose) texas = StandardItem('Texas', 14) america.appendRow(texas) austin = StandardItem('Austin', 12, color=QColor(155, 0, 0)) houston = StandardItem('Houston', 12, color=QColor(155, 0, 0)) dallas = StandardItem('dallas', 12, color=QColor(155, 0, 0)) texas.appendRow(austin) texas.appendRow(houston) texas.appendRow(dallas) # Canada canada = StandardItem('Canada', 16, set_bold=True) alberta = StandardItem('Alberta', 14) bc = StandardItem('British Columbia', 14) ontario = StandardItem('Ontario', 14) canada.appendRows([alberta, bc, ontario]) rootNode.appendRow(america) rootNode.appendRow(canada) treeView.setModel(treeModel) treeView.expandAll() treeView.doubleClicked.connect(self.getValue) self.setCentralWidget(treeView)
class CatsTreeWindow(PBDialog): """Extension of `PBDialog` that shows the categories tree""" def __init__( self, parent=None, askCats=False, askForBib=None, askForExp=None, expButton=True, previous=[], single=False, multipleRecords=False, ): """Initialize instance parameters and call the function that creates the layout. Parameters: parent (default None): the parent widget askCats (default False): if True, enable checkboxes for selection of categories askForBib (default None): the optional key which identifies in the database the bibtex entry for which categories are being selected askForExp (default None): the optional ID which identifies in the database the experiment for which categories are being selected expButton (default True): if True, add a button to accept the widget content and later ask for experiments previous (default []): the list of categories that must be selected at the beginning single (default False): if True, only allow the selection of a single category (the parent category, typically). Multiple checkboxes can be selected, but only the first one will be considered multipleRecords: used when dealing with categories corresponding to multiple records. Activate a tristate checkbox for the initial list of categories, which are typically not the same for all the elements in the list """ PBDialog.__init__(self, parent) self.setWindowTitle(cwstr.cats) self.currLayout = QVBoxLayout(self) self.setLayout(self.currLayout) self.askCats = askCats self.askForBib = askForBib self.askForExp = askForExp self.expButton = expButton self.previous = previous self.single = single self.multipleRecords = multipleRecords self.result = False self.marked = [] self.root_model = None self.proxyModel = None self.tree = None self.menu = None self.timer = None self.expsButton = None self.filterInput = None self.newCatButton = None self.acceptButton = None self.cancelButton = None self.setMinimumWidth(400) self.setMinimumHeight(600) self.createForm() def populateAskCat(self): """If selection of categories is allowed, add some information on the bibtex/experiment for which the categories are requested and a simple message, then create few required empty lists """ if self.askCats: if self.askForBib is not None: try: bibitem = pBDB.bibs.getByBibkey(self.askForBib, saveQuery=False)[0] except IndexError: pBGUILogger.warning( cwstr.entryNotInDb % self.askForBib, exc_info=True, ) return try: if bibitem["inspire"] != "" and bibitem[ "inspire"] is not None: link = "<a href='%s'>%s</a>" % ( pBView.getLink(self.askForBib, "inspire"), self.askForBib, ) elif bibitem["arxiv"] != "" and bibitem[ "arxiv"] is not None: link = "<a href='%s'>%s</a>" % ( pBView.getLink(self.askForBib, "arxiv"), self.askForBib, ) elif bibitem["doi"] != "" and bibitem["doi"] is not None: link = "<a href='%s'>%s</a>" % ( pBView.getLink(self.askForBib, "doi"), self.askForBib, ) else: link = self.askForBib bibtext = PBLabel( cwstr.markCatBibKAT % (link, bibitem["author"], bibitem["title"])) except KeyError: bibtext = PBLabel(cwstr.markCatBibK % (self.askForBib)) self.currLayout.addWidget(bibtext) elif self.askForExp is not None: try: expitem = pBDB.exps.getByID(self.askForExp)[0] except IndexError: pBGUILogger.warning( cwstr.expNotInDb % self.askForExp, exc_info=True, ) return try: exptext = PBLabel( cwstr.markCatExpINC % (self.askForExp, expitem["name"], expitem["comments"])) except KeyError: exptext = PBLabel(cwstr.markCatExpI % (self.askForExp)) self.currLayout.addWidget(exptext) else: if self.single: comment = PBLabel(cwstr.selectCat) else: comment = PBLabel(cwstr.selectCats) self.currLayout.addWidget(comment) self.marked = [] self.parent().selectedCats = [] return True def onCancel(self): """Reject the dialog content and close the window""" self.result = False self.close() def onOk(self, exps=False): """Accept the dialog content (update the list of selected categories) and close the window. May set `self.result` to "Exps" for later opening of a new dialog to ask for experiments. Parameter: exps (default False): if True, set the result to "Exps", otherwise to "Ok" """ self.parent().selectedCats = [ idC for idC in self.root_model.selectedCats.keys() if self.root_model.selectedCats[idC] == True ] self.parent().previousUnchanged = [ idC for idC in self.root_model.previousSaved.keys() if self.root_model.previousSaved[idC] == True ] if (self.single and len(self.parent().selectedCats) > 1 and self.parent().selectedCats[0] == 0): self.parent().selectedCats.pop(0) self.parent().selectedCats = [self.parent().selectedCats[0]] self.result = "Exps" if exps else "Ok" self.close() def changeFilter(self, string): """When the filter `QLineEdit` is changed, update the `LeafFilterProxyModel` regexp filter Parameter: string: the new filter string """ self.proxyModel.setFilterRegExp(str(string)) self.tree.expandAll() def onAskExps(self): """Action to perform when the selection of categories will be folloed by the selection of experiments. Call `self.onOk` with `exps = True`. """ self.onOk(exps=True) def onNewCat(self): """Action to perform when the creation of a new category is requested """ editCategory(self, self.parent()) def keyPressEvent(self, e): """Manage the key press events. Do nothing unless `Esc` is pressed: in this case close the dialog """ if e.key() == Qt.Key_Escape: self.close() def createForm(self): """Create the dialog content, connect the model to the view and eventually add the buttons at the end """ self.populateAskCat() catsTree = pBDB.cats.getHier() self.filterInput = QLineEdit("", self) self.filterInput.setPlaceholderText(cwstr.filterCat) self.filterInput.textChanged.connect(self.changeFilter) self.currLayout.addWidget(self.filterInput) self.filterInput.setFocus() self.tree = QTreeView(self) self.currLayout.addWidget(self.tree) self.tree.setMouseTracking(True) self.tree.entered.connect(self.handleItemEntered) self.tree.doubleClicked.connect(self.cellDoubleClick) self.tree.setExpandsOnDoubleClick(False) catsNamedTree = self._populateTree(catsTree[0], 0) self.root_model = CatsModel( pBDB.cats.getAll(), [catsNamedTree], self, self.previous, multipleRecords=self.multipleRecords, ) self.proxyModel = LeafFilterProxyModel(self) self.proxyModel.setSourceModel(self.root_model) self.proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive) self.proxyModel.setFilterKeyColumn(-1) self.tree.setModel(self.proxyModel) self.tree.expandAll() self.tree.setHeaderHidden(True) # self.tree.doubleClicked.connect(self.askAndPerformAction) self.newCatButton = QPushButton(cwstr.addNew, self) self.newCatButton.clicked.connect(self.onNewCat) self.currLayout.addWidget(self.newCatButton) if self.askCats: self.acceptButton = QPushButton(cwstr.ok, self) self.acceptButton.clicked.connect(self.onOk) self.currLayout.addWidget(self.acceptButton) if self.expButton: self.expsButton = QPushButton(cwstr.askExp, self) self.expsButton.clicked.connect(self.onAskExps) self.currLayout.addWidget(self.expsButton) # cancel button self.cancelButton = QPushButton(cwstr.cancel, self) self.cancelButton.clicked.connect(self.onCancel) self.cancelButton.setAutoDefault(True) self.currLayout.addWidget(self.cancelButton) def _populateTree(self, children, idCat): """Read the list of categories recursively and populate the categories tree Parameters: children: the list of children categories of the currently considered one idCat: the id of the current category """ name = pBDB.cats.getByID(idCat)[0]["name"] children_list = [] for child in cats_alphabetical(children, pBDB): child_item = self._populateTree(children[child], child) children_list.append(child_item) return NamedElement(idCat, name, children_list) def handleItemEntered(self, index): """Process event when mouse enters an item and create a `QTooltip` which describes the category, with a timer Parameter: index: a `QModelIndex` instance """ if index.isValid(): row = index.row() else: return try: idString = self.proxyModel.sibling(row, 0, index).data() except AttributeError: pBLogger.debug("", exc_info=True) return try: idCat, catName = idString.split(": ") except AttributeError: pBLogger.debug("", exc_info=True) return idCat = idCat.strip() try: self.timer.stop() QToolTip.showText(QCursor.pos(), "", self.tree.viewport()) except AttributeError: pass try: catData = pBDB.cats.getByID(idCat)[0] except IndexError: pBGUILogger.exception(cwstr.failedFind) return self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(lambda: QToolTip.showText( QCursor.pos(), cwstr.catId.format(idC=idCat, cat=catData["name"]) + cwstr. entriesCorrespondent.format(en=pBDB.catBib.countByCat(idCat)) + cwstr.expsAssociated.format(ex=pBDB.catExp.countByCat(idCat)), self.tree.viewport(), self.tree.visualRect(index), 3000, )) self.timer.start(500) def contextMenuEvent(self, event): """Create a right click menu with few actions on the selected category Parameter: event: a `QEvent` """ indexes = self.tree.selectedIndexes() try: index = indexes[0] except IndexError: pBLogger.debug(cwstr.clickMissingIndex) return if index.isValid(): row = index.row() else: return try: idString = self.proxyModel.sibling(row, 0, index).data() except AttributeError: pBLogger.debug("", exc_info=True) return try: idCat, catName = idString.split(": ") except AttributeError: pBLogger.debug("", exc_info=True) return idCat = idCat.strip() menu = PBMenu() self.menu = menu titAction = QAction(cwstr.catDescr % catName) titAction.setDisabled(True) bibAction = QAction(cwstr.openEntryList) modAction = QAction(cwstr.modify) delAction = QAction(cwstr.delete) subAction = QAction(cwstr.addSub) menu.possibleActions = [ titAction, None, bibAction, None, modAction, delAction, None, subAction, ] menu.fillMenu() action = menu.exec_(event.globalPos()) if action == bibAction: self.parent().reloadMainContent(pBDB.bibs.getByCat(idCat)) elif action == modAction: editCategory(self, self.parent(), idCat) elif action == delAction: deleteCategory(self, self.parent(), idCat, catName) elif action == subAction: editCategory(self, self.parent(), useParentCat=idCat) return def cellDoubleClick(self, index): """Process event when mouse double clicks an item. Opens a link if some columns Parameter: index: a `QModelIndex` instance """ if index.isValid(): row = index.row() col = index.column() else: return try: idString = self.proxyModel.sibling(row, 0, index).data() except AttributeError: pBLogger.debug("", exc_info=True) return try: idCat, catName = idString.split(": ") except AttributeError: pBLogger.debug("", exc_info=True) return idCat = idCat.strip() self.parent().reloadMainContent(pBDB.bibs.getByCat(idCat)) return def recreateTable(self): """Delete the previous widgets and recreate them with new data""" self.cleanLayout() self.createForm()
class ClassTreeWidget(QDialog): """ TreeView widget to show pyleecan classes structured by their inheritance together with the selected class description. """ def __init__(self, keys=None, expand=True): super(ClassTreeWidget, self).__init__() self.setupUi() self.expandAll = expand self.setMinimumHeight(600) # === default variables === self.classDict = ClassInfo().get_dict() self.keys = keys or ClassInfo().get_base_classes() # TODO all classes self.selectionModel = self.treeView.selectionModel() # === Signals === self.selectionModel.selectionChanged.connect(self.onSelectionChanged) self.treeView.doubleClicked.connect(self.onClassSelected) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) # === Generate content === self.generate() def onClassSelected(self, index): """Method to accept the selection if a class was double clicked.""" if index.isValid(): self.accept() def onSelectionChanged(self, itSelection): """ """ index = itSelection.indexes()[0] desc = index.model().itemFromIndex(index).data() self.text.setText(desc) def getSelectedClass(self): """Get the currently selected class by its name.""" index = self.selectionModel.selectedIndexes()[0] return index.model().itemFromIndex(index).text() def setupUi(self): """Init. the UI.""" self.setWindowIcon(QIcon(ICON)) # === Widgets === # TreeView model = QtGui.QStandardItemModel(0, 1) model.setHorizontalHeaderLabels(["Class"]) self.treeView = QTreeView() self.treeView.rootNode = model.invisibleRootItem() self.treeView.setModel(model) self.treeView.setAlternatingRowColors(False) # size options # setting min. width in self.generate to fit content self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) # Hide Debug Columns # self.treeView.hideColumn(1) # text output self.text = QLabel() self.text.setAlignment(Qt.AlignTop) self.text.setWordWrap(True) self.text.setMinimumWidth(300) # Splitters self.splitter = QSplitter(self) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter.addWidget(self.treeView) self.splitter.addWidget(self.text) # dialog buttons self.buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) # window to create confirmation and cancellation buttons # === Layout === # Horizontal Div. layout = QVBoxLayout() # layout.setContentsMargins(0, 0, 0, 0) # layout.setSpacing(0) layout.addWidget(self.splitter) layout.addWidget(self.buttons) self.setLayout(layout) def generate(self): """Method to (recursively) build the tree (view) of the data object.""" self.treeDict = dict() for key in self.keys: self.genTreeDict(key, self.treeDict) self.genTreeView(self.treeDict) # set first row active & expand all branches index = self.treeView.model().index(0, 0) self.treeView.setCurrentIndex(index) self.treeView.expandAll() wHint = self.treeView.sizeHintForColumn(0) self.treeView.setMinimumWidth(wHint) self.treeView.setColumnWidth(0, wHint) if not self.expandAll: self.treeView.collapseAll() def genTreeDict(self, key, parent): """Generate a dict structure of the classes recursively on the parent dict.""" parent[key] = dict() for typ in self.classDict[key]["daughters"]: if key == self.classDict[typ]["mother"]: self.genTreeDict(typ, parent[key]) def genTreeView(self, tree, parent=None): """Generate the view item structure on the parent item.""" # init if root if parent is None: parent = self.treeView.rootNode self.treeView.rootNode.removeRows( 0, self.treeView.rootNode.rowCount()) for key, item in tree.items(): desc = ( f"Class: {key} \nPackage: {self.classDict[key]['package']}" + f"\nDescription: {self.classDict[key]['desc']}") row = self.addRow(parent, key, desc) if item: self.genTreeView(item, parent=row) def addRow(self, parent, name="", desc=""): """Add a new row to the parent item.""" item = QtGui.QStandardItem(name) item.setEditable(False) item.setData(desc) parent.appendRow([item]) return item