def __initFileTreeView__(self, model): treeView = QTreeView() treeView.setIndentation(20) treeView.setSortingEnabled(True) treeView.setMinimumWidth(100) treeView.setModel(model) treeView.setRootIndex(model.index(r'C:')) for i in range(1, 5): treeView.hideColumn(i) return treeView
class WTreeEdit(QWidget): """TreeEdit widget is to show and edit all of the pyleecan objects data.""" # Signals dataChanged = Signal() def __init__(self, obj, *args, **kwargs): QWidget.__init__(self, *args, **kwargs) self.class_dict = ClassInfo().get_dict() self.treeDict = None # helper to track changes self.obj = obj # the object self.is_save_needed = False self.model = TreeEditModel(obj) self.setupUi() # === Signals === self.selectionModel.selectionChanged.connect(self.onSelectionChanged) self.treeView.collapsed.connect(self.onItemCollapse) self.treeView.expanded.connect(self.onItemExpand) self.treeView.customContextMenuRequested.connect(self.openContextMenu) self.model.dataChanged.connect(self.onDataChanged) self.dataChanged.connect(self.setSaveNeeded) # === Finalize === # set 'root' the selected item and resize columns self.treeView.setCurrentIndex(self.treeView.model().index(0, 0)) self.treeView.resizeColumnToContents(0) def setupUi(self): """Setup the UI""" # === Widgets === # TreeView self.treeView = QTreeView() # self.treeView.rootNode = model.invisibleRootItem() self.treeView.setModel(self.model) self.treeView.setAlternatingRowColors(False) # self.treeView.setColumnWidth(0, 150) self.treeView.setMinimumWidth(100) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.selectionModel = self.treeView.selectionModel() self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.statusBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.statusBar.setStyleSheet( "QStatusBar {border: 1px solid rgb(200, 200, 200)}") self.saveLabel = QLabel("unsaved") self.saveLabel.setVisible(False) self.statusBar.addPermanentWidget(self.saveLabel) # Splitters self.leftSplitter = QSplitter() self.leftSplitter.setStretchFactor(0, 0) self.leftSplitter.setStretchFactor(1, 1) # === Layout === # Horizontal Div. self.hLayout = QVBoxLayout() self.hLayout.setContentsMargins(0, 0, 0, 0) self.hLayout.setSpacing(0) # add widgets to layout self.hLayout.addWidget(self.leftSplitter) self.hLayout.addWidget(self.statusBar) # add widgets self.leftSplitter.addWidget(self.treeView) self.setLayout(self.hLayout) def update(self, obj): """Check if object has changed and update tree in case.""" if not obj is self.obj: self.obj = obj self.model = TreeEditModel(obj) self.treeView.setModel(self.model) self.model.dataChanged.connect(self.onDataChanged) self.selectionModel = self.treeView.selectionModel() self.selectionModel.selectionChanged.connect( self.onSelectionChanged) self.treeView.setCurrentIndex(self.treeView.model().index(0, 0)) self.setSaveNeeded(True) def setSaveNeeded(self, state=True): self.is_save_needed = state self.saveLabel.setVisible(state) def openContextMenu(self, point): """Generate and open context the menu at the given point position.""" index = self.treeView.indexAt(point) pos = QtGui.QCursor.pos() if not index.isValid(): return # get the data item = self.model.item(index) obj_info = self.model.get_obj_info(item) # init the menu menu = TreeEditContextMenu(obj_dict=obj_info, parent=self) menu.exec_(pos) self.onSelectionChanged(self.selectionModel.selection()) def onItemCollapse(self, index): """Slot for item collapsed""" # dynamic resize for ii in range(3): self.treeView.resizeColumnToContents(ii) def onItemExpand(self, index): """Slot for item expand""" # dynamic resize for ii in range(3): self.treeView.resizeColumnToContents(ii) def onDataChanged(self, first=None, last=None): """Slot for changed data""" self.dataChanged.emit() self.onSelectionChanged(self.selectionModel.selection()) def onSelectionChanged(self, itemSelection): """Slot for changed item selection""" # get the index if itemSelection.indexes(): index = itemSelection.indexes()[0] else: index = self.treeView.model().index(0, 0) self.treeView.setCurrentIndex(index) return # get the data item = self.model.item(index) obj = item.object() typ = type(obj).__name__ obj_info = self.model.get_obj_info(item) ref_typ = obj_info["ref_typ"] if obj_info else None # set statusbar information on class typ msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}" self.statusBar.showMessage(msg) # --- choose the respective widget by class type --- # numpy array -> table editor if typ == "ndarray": widget = WTableData(obj, editable=True) widget.dataChanged.connect(self.dataChanged.emit) elif typ == "MeshSolution": widget = WMeshSolution(obj) # only a view (not editable) # list (no pyleecan type, non empty) -> table editor # TODO add another widget for lists of non 'primitive' types (e.g. DataND) elif isinstance(obj, list) and not self.isListType(ref_typ) and obj: widget = WTableData(obj, editable=True) widget.dataChanged.connect(self.dataChanged.emit) # generic editor else: # widget = SimpleInputWidget().generate(obj) widget = WTableParameterEdit(obj) widget.dataChanged.connect(self.dataChanged.emit) # show the widget if self.leftSplitter.widget(1) is None: self.leftSplitter.addWidget(widget) else: self.leftSplitter.replaceWidget(1, widget) widget.setParent( self.leftSplitter) # workaround for PySide2 replace bug widget.show() pass def isListType(self, typ): if not typ: return False return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict def isDictType(self, typ): if not typ: return False return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
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