def setupTabs(self): """ Setup the various tabs in the AddressWidget. """ groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"] for group in groups: proxyModel = QSortFilterProxyModel(self) proxyModel.setSourceModel(self.tableModel) proxyModel.setDynamicSortFilter(True) tableView = QTableView() tableView.setModel(proxyModel) tableView.setSortingEnabled(True) tableView.setSelectionBehavior(QAbstractItemView.SelectRows) tableView.horizontalHeader().setStretchLastSection(True) tableView.verticalHeader().hide() tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) tableView.setSelectionMode(QAbstractItemView.SingleSelection) # This here be the magic: we use the group name (e.g. "ABC") to # build the regex for the QSortFilterProxyModel for the group's # tab. The regex will end up looking like "^[ABC].*", only # allowing this tab to display items where the name starts with # "A", "B", or "C". Notice that we set it to be case-insensitive. reFilter = "^[%s].*" % group proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive)) proxyModel.setFilterKeyColumn(0) # Filter on the "name" column proxyModel.sort(0, Qt.AscendingOrder) # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) viewselectionmodel = tableView.selectionModel() tableView.selectionModel().selectionChanged.connect(self.selectionChanged) self.addTab(tableView, group)
def setupTabs(self): """ Setup the various tabs in the AddressWidget. """ groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"] for group in groups: proxyModel = QSortFilterProxyModel(self) proxyModel.setSourceModel(self.tableModel) proxyModel.setDynamicSortFilter(True) tableView = QTableView() tableView.setModel(proxyModel) tableView.setSortingEnabled(True) tableView.setSelectionBehavior(QAbstractItemView.SelectRows) tableView.horizontalHeader().setStretchLastSection(True) tableView.verticalHeader().hide() tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) tableView.setSelectionMode(QAbstractItemView.SingleSelection) # This here be the magic: we use the group name (e.g. "ABC") to # build the regex for the QSortFilterProxyModel for the group's # tab. The regex will end up looking like "^[ABC].*", only # allowing this tab to display items where the name starts with # "A", "B", or "C". Notice that we set it to be case-insensitive. reFilter = "^[%s].*" % group proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive)) proxyModel.setFilterKeyColumn(0) # Filter on the "name" column proxyModel.sort(0, Qt.AscendingOrder) # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) viewselectionmodel = tableView.selectionModel() tableView.selectionModel().selectionChanged.connect( self.selectionChanged) self.addTab(tableView, group)
class ArrayDatasetTableWidget(QWidget): """ Wrapper over a QTableView with buttons to add and delete rows/columns """ def __init__(self, type: np.dtype = np.byte, parent=None): super().__init__(parent) self.model = ArrayDatasetTableModel(dtype=type, parent=self) self.view = QTableView() self.view.setModel(self.model) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setItemDelegate(ValueDelegate(type, parent)) self.setLayout(QGridLayout()) self.toolbox = QToolBar() self.add_row_button = QAction(text="➕ Add Row") self.add_row_button.triggered.connect(self.model.add_row) self.remove_row_button = QAction(text="➖ Remove Row") self.remove_row_button.triggered.connect( partial(self.model.delete_index, True)) self.add_column_button = QAction(text="➕ Add Column") self.add_column_button.triggered.connect(self.model.add_column) self.remove_column_button = QAction(text="➖ Remove Column") self.remove_column_button.triggered.connect( partial(self.model.delete_index, False)) self.toolbox.addAction(self.add_row_button) self.toolbox.addAction(self.remove_row_button) self.toolbox.addAction(self.add_column_button) self.toolbox.addAction(self.remove_column_button) self.layout().addWidget(self.toolbox) self.layout().addWidget(self.view)
def get_view_for_submodel(submodel): main_widget = QWidget() hbox_layout = QHBoxLayout(main_widget) vbox_layout = QVBoxLayout() table_view = QTableView() table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.setSelectionMode(QAbstractItemView.SingleSelection) table_view.setModel(submodel) add_button = QPushButton("+") add_button.clicked.connect(lambda: add_item_to_model(submodel)) remove_button = QPushButton("-") remove_button.clicked.connect( lambda: remove_item_from_model(submodel, table_view)) vbox_layout.addWidget(add_button) vbox_layout.addWidget(remove_button) vbox_layout.addItem( QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) hbox_layout.addWidget(table_view) hbox_layout.addLayout(vbox_layout) return main_widget
class AddressWidget(QDialog): selectionChanged = Signal(QItemSelection) def __init__(self, parent=None): super(AddressWidget, self).__init__(parent) self.tableModel = TableModel() self.tableView = QTableView() self.setupTable() statusLabel = QLabel("Tabular data view demo") layout = QVBoxLayout() layout.addWidget(self.tableView) layout.addWidget(statusLabel) self.setLayout(layout) self.setWindowTitle("Address Book") self.resize(800,500) # add test data self.populateTestData() def setupTable(self): proxyModel = QSortFilterProxyModel(self) proxyModel.setSourceModel(self.tableModel) proxyModel.setDynamicSortFilter(True) self.tableView.setModel(proxyModel) self.tableView.setSortingEnabled(True) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.verticalHeader().hide() self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) proxyModel.setFilterKeyColumn(0) # Filter on the "name" column proxyModel.sort(0, Qt.AscendingOrder) viewselectionmodel = self.tableView.selectionModel() self.tableView.selectionModel().selectionChanged.connect(self.selectionChanged) def populateTestData(self): addresses = [{"name": "John Doe", "address": "Alameda"}, {"name": "Alan Turing", "address": "San Deigo"}, {"name": "Bjarne Stroutsup", "address": "Columbia"}, {"name": "Herb Sutter", "address": "Seattle"}, {"name": "Micheal Konin", "address": "Colorado"}] for i in range(len(addresses)): self.tableModel.insertRows(0) ix = self.tableModel.index(0, 0, QModelIndex()) self.tableModel.setData(ix, addresses[i]["name"], Qt.EditRole) ix = self.tableModel.index(0, 1, QModelIndex()) self.tableModel.setData(ix, addresses[i]["address"], Qt.EditRole) self.tableView.resizeRowToContents(ix.row())
class DebugBreakpointsWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugBreakpointsListModel(self.table, data) self.table.setModel(self.model) self.item_delegate = DebugBreakpointsItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.table) self.setLayout(layout) def notifyOffsetChanged(self, offset): pass def notifyBreakpointsChanged(self, new_rows): self.model.update_rows(new_rows) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
def __init__(self, data_list, header, *args): QWidget.__init__(self, *args) # setGeometry(x_pos, y_pos, width, height) self.setGeometry(300, 200, 570, 450) self.setWindowTitle('Click on column title to sort') # Setup the model and view '''tmodel = MyTableModel(self, data_list, header) tview = QTableView() tview.setModel(tmodel) delegate = MyDelegate() tview.setItemDelegate(delegate)''' # Setup the proxy model for sorting and filtering tmodel = MyTableModel(self, data_list, header) pmodel = QSortFilterProxyModel() pmodel.setSourceModel(tmodel) tview = QTableView() tview.setModel(pmodel) delegate = MyDelegate() tview.setItemDelegate(delegate) # TableView properties tview.resizeColumnsToContents() # set column width to fit contents tview.setShowGrid(False) # hide gridlines #tview.verticalHeader().hide() # row labels #tview.horizontalHeader().hide() # column labels # Select a single row at a time tview.setSelectionBehavior(QTableView.SelectRows) tview.setSelectionMode(QTableView.SingleSelection) # Enable sorting tview.setSortingEnabled(True) # Drag and drop reordering using header labels '''tview.verticalHeader().setSectionsMovable(True) tview.verticalHeader().setDragEnabled(True) tview.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove) tview.horizontalHeader().setSectionsMovable(True) tview.horizontalHeader().setDragEnabled(True) tview.horizontalHeader().setDragDropMode(QAbstractItemView.InternalMove)''' # Drag and drop reordering using rows tview.setDragEnabled(True) tview.setAcceptDrops(True) tview.setDragDropMode(QTableView.InternalMove) tview.setDragDropOverwriteMode(False) layout = QVBoxLayout(self) layout.addWidget(tview) self.setLayout(layout)
def _create_device_table(self) -> QTableView: device_table = QTableView() device_table.setModel(self._device_model) device_table.setSelectionBehavior(QAbstractItemView.SelectRows) device_table.setSelectionMode(QAbstractItemView.SingleSelection) device_table.selectionModel().selectionChanged.connect( self._on_device_table_selection_changed) horizontal_header = device_table.horizontalHeader() vertical_header = device_table.verticalHeader() horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents) vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents) horizontal_header.setStretchLastSection(True) return device_table
class DebugModulesWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugModulesListModel(self.table, data) self.table.setModel(self.model) self.item_delegate = DebugModulesItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth( i, self.item_delegate.sizeHint( self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) update_layout = QHBoxLayout() update_layout.setContentsMargins(0, 0, 0, 0) update_label = QLabel("Data is Stale") update_button = QPushButton("Refresh") update_button.clicked.connect(lambda: self.refresh()) update_layout.addWidget(update_label) update_layout.addStretch(1) update_layout.addWidget(update_button) self.update_box = QWidget() self.update_box.setLayout(update_layout) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.layout.addWidget(self.table) self.setLayout(self.layout) def notifyOffsetChanged(self, offset): pass def refresh(self): debug_state = binjaplug.get_state(self.bv) debug_state.ui.update_modules() def notifyModulesChanged(self, new_modules): self.model.update_rows(new_modules) self.table.resizeColumnsToContents() self.layout.removeWidget(self.update_box) self.update_box.setVisible(False) def mark_dirty(self): self.layout.addWidget(self.update_box) self.update_box.setVisible(True) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class MetricsDialog(PluginDialog): ENABLE = True def __init__(self, conn=None, parent=None): super().__init__(parent) self.conn = conn self.view = QTableView() self.model = MetricModel() self.buttons = QDialogButtonBox(QDialogButtonBox.Ok) self.view.setModel(self.model) self.view.setAlternatingRowColors(True) self.view.horizontalHeader().hide() self.view.verticalHeader().hide() self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.buttons.accepted.connect(self.accept) self.setWindowTitle(self.tr("Project metrics")) v_layout = QVBoxLayout() v_layout.addWidget(self.view) v_layout.addWidget(self.buttons) self.setLayout(v_layout) # Async stuff self.metrics_runnable = None self.populate() def populate(self): """Async implementation to populate the view Notes: When closing the dialog window, the thread is not stopped. """ def compute_metrics(conn): """Async function""" return { "variants": get_variant_count(conn), "snps": get_snp_count(conn), "transitions": get_variant_transition(conn), "transversions": get_variant_transversion(conn), "samples": get_sample_count(conn), } self.model.add_metrics(self.tr("Loading..."), self.tr("data...")) self.metrics_runnable = SqlRunnable(self.conn, compute_metrics) self.metrics_runnable.finished.connect(self.loaded) QThreadPool.globalInstance().start(self.metrics_runnable) def loaded(self): """Called at the end of the thread and populate data""" results = self.metrics_runnable.results self.model.clear() ratio = results["transitions"] / results["transversions"] self.model.add_metrics("Variant count", results["variants"]) self.model.add_metrics("Snp count", results["snps"]) self.model.add_metrics("Transition count", results["transitions"]) self.model.add_metrics("Transversion count", results["transversions"]) self.model.add_metrics("Tr/tv ratio", ratio) self.model.add_metrics("Sample count", results["variants"]) self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.view.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch)
class DebugThreadsWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugThreadsListModel(self.table) self.table.setModel(self.model) self.table.clicked.connect(self.threadRowClicked) self.item_delegate = DebugThreadsItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.table) self.setLayout(layout) def notifyOffsetChanged(self, offset): pass # called from QTableView's clicked signal # index: QModelIndex def threadRowClicked(self, index): index = self.model.createIndex(index.row(), 0) tid_str = self.model.data(index, Qt.DisplayRole) #print('clicked to change to thread %s' % tid_str) stateObj = binjaplug.get_state(self.bv) if stateObj.connected and not stateObj.running: tid = int(tid_str, 16) stateObj.threads.current = tid stateObj.ui.context_display() stateObj.ui.on_step() else: print('cannot set thread in current state') # called from plugin's context_display() function def notifyThreadsChanged(self, new_threads): idx_selected = self.model.update_rows(new_threads) if idx_selected: self.table.setCurrentIndex(idx_selected) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class RemoverDialog(QDialog): def __init__(self, parent=None): super(RemoverDialog, self).__init__(parent) self.setupUi() def setupUi(self): self.menuBar = QMenuBar() self.menuBar.show() self.pathInputBox = QLineEdit(self) self.pathInputBox.setEnabled(False) self.pathInputBox.setToolTip( 'Input a path or drag a directory here...') self.openPathButton = QPushButton('Open...', self) self.openPathButton.clicked.connect(self.openPath) inputLayout = QHBoxLayout() inputLayout.addWidget(QLabel('Path:', self)) inputLayout.addWidget(self.pathInputBox) inputLayout.addWidget(self.openPathButton) self.filterFolderCheckBox = QCheckBox('Folders', self) self.filterFolderCheckBox.setChecked(True) self.filterFolderCheckBox.toggled.connect(self.filter) self.filterFileCheckBox = QCheckBox('Files', self) self.filterFileCheckBox.setChecked(True) self.filterFileCheckBox.toggled.connect(self.filter) self.filterSuffixCheckBox = QCheckBox('Suffixes', self) self.filterSuffixCheckBox.setChecked(True) self.filterSuffixCheckBox.toggled.connect(self.filter) filterLayout = QHBoxLayout() filterLayout.addWidget(self.filterFolderCheckBox) filterLayout.addWidget(self.filterFileCheckBox) filterLayout.addWidget(self.filterSuffixCheckBox) filterLayout.addStretch() self.trashButton = QPushButton('Trash', self) self.trashButton.clicked.connect(self.trash) self.deleteButton = QPushButton('Delete', self) self.deleteButton.clicked.connect(self.delete) confirmLayout = QHBoxLayout() confirmLayout.addStretch() confirmLayout.addWidget(self.trashButton) confirmLayout.addWidget(self.deleteButton) layout = QVBoxLayout() layout.addLayout(inputLayout) layout.addLayout(filterLayout) layout.addWidget(self.createResultView()) layout.addLayout(confirmLayout) self.setAcceptDrops(True) self.setLayout(layout) self.setMinimumWidth(600) self.setWindowTitle('Remover') self.setWindowIcon(QApplication.style().standardIcon( QStyle.SP_DirIcon)) def createResultView(self): self.resultModel = ResultModel(self) self.resultView = QTableView(self) self.resultView.setSortingEnabled(True) self.resultView.setShowGrid(False) self.resultView.setAlternatingRowColors(True) self.resultView.verticalHeader().hide() self.resultView.setSelectionBehavior(QAbstractItemView.SelectRows) self.resultView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.sortFilterModel = SortFilterResultModel(self.resultModel, self) self.resultView.setModel(self.sortFilterModel) return self.resultView def dragEnterEvent(self, event): if event.mimeData().hasUrls() and len(event.mimeData().urls()) == 1: event.acceptProposedAction() def dropEvent(self, event): if event.mimeData().hasUrls() and len(event.mimeData().urls()) == 1: self.pathInputBox.setText(event.mimeData().urls()[0].toLocalFile()) self.reloadPath(self.pathInputBox.text()) def openPath(self): path = QFileDialog.getExistingDirectory( parent=self, caption='Open', options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if path: self.pathInputBox.setText(path) self.reloadPath(path) def filter(self): filters = [] if self.filterFolderCheckBox.isChecked(): filters.append('folder') if self.filterFileCheckBox.isChecked(): filters.append('file') if self.filterSuffixCheckBox.isChecked(): filters.append('suffix') self.sortFilterModel.setFilterRegularExpression('|'.join(filters)) self.sortFilterModel.filterRegularExpression() self.resultView.resizeRowsToContents() def trash(self): folders, files = self.collectSelectedFolderFiles() try: for file in files: send2trash(file) for folder in folders: if QFile(folder).exists(): send2trash(folder) except: QMessageBox.warning(self, 'Failed', 'Failed to trash selected files/folders') return self.reloadPath(self.pathInputBox.text()) def delete(self): folders, files = self.collectSelectedFolderFiles() try: for file in files: os.remove(file) for folder in folders: shutil.rmtree(folder) except: QMessageBox.warning(self, 'Failed', 'Failed to delete selected files/folders') return self.reloadPath(self.pathInputBox.text()) def collectSelectedFolderFiles(self): folders, files, suffixes = [], [], [] for index in self.resultView.selectedIndexes(): if index.column() != 0: # 忽略第二列的selection continue item = self.sortFilterModel.data(index, Qt.UserRole) if 'folder' == item.type: folders.append(item.name) elif 'file' == item.type: files.append(item.name) elif 'suffix' == item.type: suffixes.append(item.name[2:]) # 将后缀符合选中条件的文件添加到files中 path = self.pathInputBox.text() iterator = QDirIterator(path, filter=QDir.Files | QDir.Dirs | QDir.Hidden | QDir.NoDotAndDotDot, flags=QDirIterator.Subdirectories) folderPaths, filePaths = set(), set() while iterator.hasNext(): file = iterator.next() if '.' == file[-1] or '..' == file[-2]: continue fileInfo = QFileInfo(file) if fileInfo.isDir(): if fileInfo.fileName() in folders: folderPaths.add(fileInfo.absoluteFilePath()) if fileInfo.isFile(): if fileInfo.fileName() in files: filePaths.add(fileInfo.absoluteFilePath()) if fileInfo.suffix() in suffixes: filePaths.add(fileInfo.absoluteFilePath()) return sorted(folderPaths), filePaths def reloadPath(self, path): self.resultModel.reload(path) self.resultView.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch) self.resultView.horizontalHeader().setSectionResizeMode( 1, QHeaderView.ResizeToContents) self.filter() self.resultView.sortByColumn(1, Qt.DescendingOrder)
class AttributesTab(QWidget): def __init__(self): """ Widget containing the table summary of all attributes """ QWidget.__init__(self) # Data self.attributes = {} # Attributes names to ids -> {attr_name: attr_id, ...} self.attr_idx = {} # Attributes ids to indexes -> {attr_id: attr_idx, ...} self.students = {} # Students names to ids -> {std_name: std_id, ...} self.std_idx = {} # Students ids to indexes -> {std_id: std_idx, ...} self.data = {} # Attributes and students ids to cell data -> {(attr_id, std_id): cell_data, ...} self.dm: AttributesTableModel = None # Widget self.table_attributes = QTableView() self.table_attributes.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_attributes.setSelectionMode(QAbstractItemView.SingleSelection) self.table_attributes.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) self.table_attributes.verticalHeader().setFixedWidth(150) # Signals self.table_attributes.clicked.connect(self.on_cell_clicked) self.sig_cell_clicked: Signal = None # Layout self.__set_layout() self.set_data([], [], {}) def __set_layout(self) -> None: layout = QHBoxLayout() layout.setSpacing(0) layout.addWidget(self.table_attributes) self.setLayout(layout) def set_data(self, attributes_data: list, students_data: list, data: dict) -> None: """ Sets the data inside the table :param attributes_data: Attributes list (needs to be ordered) = [(attr_id, attr_name), ...] :param students_data: Students list (needs to be ordered) = [(std_id, std_name), ...] :param data: Table's data {(attr_id, std_id}: cell_data, ...} """ self.attributes = {} self.attr_idx = {} i = 0 for attr_id, attr_name in attributes_data: self.attributes[attr_name] = attr_id self.attr_idx[attr_id] = i i += 1 self.students = {} self.std_idx = {} i = 0 for std_id, std_name in students_data: self.students[std_name] = std_id self.std_idx[std_id] = i i += 1 self.data = data attr_header = [a[1] for a in attributes_data] std_header = [s[1] for s in students_data] data_for_model = [] for _ in range(len(std_header)): data_for_model.append([None] * len(attr_header)) for attr_id, std_id in data: data_for_model[self.std_idx[std_id]][self.attr_idx[attr_id]] = data[(attr_id, std_id)] self.dm = AttributesTableModel(self.table_attributes, data_for_model, attr_header, std_header) def on_cell_clicked(self, item) -> None: """ Triggered when a cell is clicked. Emits a signal with the attribute and student ids """ attr_id = None for a in self.attr_idx: if self.attr_idx[a] == item.column(): attr_id = a std_id = None for s in self.std_idx: if self.std_idx[s] == item.row(): std_id = s self.sig_cell_clicked.emit(attr_id, std_id)
class WTableParameterEdit(QWidget): dataChanged = Signal() def __init__(self, obj): super(WTableParameterEdit, self).__init__() self._obj = obj self._model = WTableParameterModel(self._obj) self._view = QTableView() self._view.setModel(self._model) self.setupUi() self.setNotEditableHidden(True) self.resizeToContent() self._model.dataChanged.connect(self.dataChanged.emit) def resizeToContent(self): """ Resize all the columns (but the last) to fit the content or the minimum size. """ rootIndex = self._view.rootIndex() for colId in range(self._model.columnCount() - 1): self._view.resizeColumnToContents(colId) def setNotEditableHidden(self, hidden): """Set the hide state of all rows that are not editable.""" rootIndex = self._view.rootIndex() for rowId in range(self._model.rowCount()): isEditableRow = False for colId in range(self._model.columnCount()): flag = self._model.flags( self._model.index(rowId, colId, rootIndex)) if flag & Qt.ItemIsEditable: isEditableRow = True self._view.setRowHidden(rowId, not isEditableRow and hidden) def setupUi(self): header = QLabel(f"{type(self._obj).__name__}") font = header.font() font.setBold(True) header.setFont(font) self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(header) if isinstance(self._obj, (list, dict)): label = f"The {type(self._obj).__name__} has {len(self._obj)} element" if len(self._obj) != 1: label += "s" self.mainLayout.addWidget(QLabel(label + ".")) self.mainLayout.addStretch() elif self._obj is None: self.mainLayout.addWidget(QLabel("Not implemented yet.")) self.mainLayout.addStretch() else: self.mainLayout.addWidget(self._view) self.setLayout(self.mainLayout) # === table settings === # self._view.resizeColumnsToContents() self._view.verticalHeader().setVisible(False) self._view.setFocusPolicy(Qt.NoFocus) self._view.setSelectionMode(QAbstractItemView.SingleSelection) self._view.setEditTriggers(QAbstractItemView.AllEditTriggers) self._view.horizontalHeader().setStretchLastSection(True) self._view.horizontalHeader().setSectionsClickable(False) # self._view.setMinimumWidth(650) self.setMinimumWidth(600)
class AddressWidget(QDialog): selectionChanged = Signal(QItemSelection) def __init__(self, parent=None): super(AddressWidget, self).__init__(parent) self.tableModel = TableModel() self.tableView = QTableView() self.setupTable() statusLabel = QLabel("Tabular data view demo") layout = QVBoxLayout() layout.addWidget(self.tableView) layout.addWidget(statusLabel) self.setLayout(layout) self.setWindowTitle("Address Book") self.resize(800, 500) # add test data self.populateTestData() def setupTable(self): proxyModel = QSortFilterProxyModel(self) proxyModel.setSourceModel(self.tableModel) proxyModel.setDynamicSortFilter(True) self.tableView.setModel(proxyModel) self.tableView.setSortingEnabled(True) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.verticalHeader().hide() self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) proxyModel.setFilterKeyColumn(0) # Filter on the "name" column proxyModel.sort(0, Qt.AscendingOrder) viewselectionmodel = self.tableView.selectionModel() self.tableView.selectionModel().selectionChanged.connect( self.selectionChanged) def populateTestData(self): addresses = [{ "name": "John Doe", "address": "Alameda" }, { "name": "Alan Turing", "address": "San Deigo" }, { "name": "Bjarne Stroutsup", "address": "Columbia" }, { "name": "Herb Sutter", "address": "Seattle" }, { "name": "Micheal Konin", "address": "Colorado" }] for i in range(len(addresses)): self.tableModel.insertRows(0) ix = self.tableModel.index(0, 0, QModelIndex()) self.tableModel.setData(ix, addresses[i]["name"], Qt.EditRole) ix = self.tableModel.index(0, 1, QModelIndex()) self.tableModel.setData(ix, addresses[i]["address"], Qt.EditRole) self.tableView.resizeRowToContents(ix.row())
class WorkSpaceWidget(QWidget): def __init__(self, parent, file_clicked): super().__init__(parent) self.main_layout = QVBoxLayout() # Tool Bar self.toolbar = QToolBar(self) # delete action on toolbar self.delete_action_tb = QAction("DELETE TABLE ROW", self) self.delete_action_tb.setStatusTip("Obrisi Red U Tabeli") self.delete_action_tb.triggered.connect(self.delete_table_row_tb) self.toolbar.addAction(self.delete_action_tb) # ADD ONE TOOLBAR BUTTON self.add_one_action_tb = QAction("ADD TABLE ROW", self) self.add_one_action_tb.setStatusTip("ADD SINGLE ROW TO TABLE") self.add_one_action_tb.triggered.connect(self.add_table_row_handler) self.toolbar.addAction(self.add_one_action_tb) self.setLayout(self.main_layout) self.file_clicked = file_clicked self.abstract_table_model = AbstractTableModel(self.file_clicked) self.database_type = self.abstract_table_model.database_type self.create_tab_widget() self.check_database_type_and_run() self.tab_widget.addTab(self.main_table, QIcon("img/iconXLNK.png"), self.file_clicked) self.main_layout.addWidget(self.toolbar) self.main_layout.addWidget(self.tab_widget) # TODO Srediti da funkcija bise element iz tabele klikom na dugme delete u ToolBar-u. def delete_table_row_tb(self): print("Ugraditi funkciju za brisanje reda iz tabele.") # TODO srediti dodavnje u tabelu. def add_table_row_handler(self): self.addWindow = InsertOneForm( self.abstract_table_model.file_handler.metadata[0]["columns"], self.check_data) self.main_layout.addWidget(self.addWindow) # chekiranje validnosti podataka def check_data(self): self.return_data = self.addWindow.final_data self.return_metadata = self.addWindow.metadata_columns not_valid = False i = 0 for data in self.return_data: if data.get(self.return_metadata[i]) == "" or data.get( self.return_metadata[i]) == " " or data.get( self.return_metadata[i]) == None: print("Nije uredu") not_valid = True else: print("sve ok") i += 1 if not_valid != True: self.new_instanc = SmartPhone(self.return_data[0]["brand"], self.return_data[1]["model"], self.return_data[2]["price"], self.return_data[3]["made_in"], self.return_data[4]["dealer"], self.return_data[5]["imei_code"], self.return_data[6]["stores"]) self.abstract_table_model.file_handler.insert(self.new_instanc) def check_database_type_and_run(self): if self.database_type == "serial" or self.database_type == "sequential": self.create_table() else: # TODO Obraditi izuzetak. pass def create_tab_widget(self): self.tab_widget = QTabWidget(self) self.tab_widget.setTabsClosable(True) def create_table(self): self.main_table = QTableView(self.tab_widget) self.main_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.main_table.setSelectionMode(QAbstractItemView.SingleSelection) self.main_table.setModel(self.abstract_table_model) # makes responsive tabel sizes max_width = 1620 // self.abstract_table_model.column_number() for width in range(self.abstract_table_model.column_number()): self.main_table.setColumnWidth(width, max_width)
class DictWidget(QWidget): def __init__(self, parent=None): super().__init__() self.view = QTableView() self.model = DictModel() self.proxy_model = QSortFilterProxyModel() self.search_bar = QLineEdit() self._show_loading = False self.proxy_model.setSourceModel(self.model) self.view.setModel(self.proxy_model) self.view.setAlternatingRowColors(True) self.view.horizontalHeader().setStretchLastSection(True) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSortingEnabled(True) self.view.verticalHeader().hide() self.search_bar.textChanged.connect( self.proxy_model.setFilterRegularExpression) self.search_bar.setVisible(False) self._show_search_action = QAction("show search bar") self._show_search_action.setCheckable(True) self._show_search_action.setShortcutContext(Qt.WidgetShortcut) self._show_search_action.setShortcut(QKeySequence.Find) self._show_search_action.triggered.connect(self._on_show_search) self._close_search_action = QAction() self._close_search_action.setShortcut(QKeySequence(Qt.Key_Escape)) self._close_search_action.setShortcutContext(Qt.WidgetShortcut) self._close_search_action.triggered.connect(self._on_close_search) self.view.addAction(self._show_search_action) self.search_bar.addAction(self._close_search_action) _layout = QVBoxLayout() _layout.addWidget(self.view) _layout.addWidget(self.search_bar) _layout.setContentsMargins(0, 0, 0, 0) self.setLayout(_layout) print("init") def set_dict(self, data: dict): self.model.set_dict(data) def _on_show_search(self): self.search_bar.setVisible(True) self.search_bar.setFocus(Qt.ShortcutFocusReason) def _on_close_search(self): self.search_bar.hide() self.search_bar.clear() self.view.setFocus(Qt.ShortcutFocusReason) def set_header_visible(self, visible=True): self.view.horizontalHeader().setVisible(visible) def clear(self): self.model.clear() def paintEvent(self, event: QPaintEvent): if self._show_loading: painter = QPainter(self) painter.drawText(self.rect(), Qt.AlignCenter, self.tr("Loading ...")) else: super().paintEvent(event) def set_loading(self, show=True): self._show_loading = True self.view.setVisible(not show) self.update()
class _ExtractSeriesWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) # {series name: {frame name: [ (Attribute, QPersistentModelIndex) ] self.seriesOptions: Dict[str, Dict[str, List[Tuple[ int, QPersistentModelIndex]]]] = dict() # {frame name: attribute model} self.models: Dict[str, CustomProxyAttributeModel] = dict() self.seriesView = CustomSignalView(parent=self) self.seriesModel = CustomStringListModel(self) self.seriesModel.setHeaderLabel('Series name') self.addSeriesButton = QPushButton('Add', self) self.removeSeriesButton = QPushButton('Remove', self) self.addSeriesButton.clicked.connect(self.addSeries) self.removeSeriesButton.clicked.connect(self.removeSeries) self.seriesView.setModel(self.seriesModel) self.seriesView.setDragDropMode(QTableView.InternalMove) self.seriesView.setDragDropOverwriteMode(False) self.seriesView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.seriesView.verticalHeader().hide() # Connect selection to change self.seriesView.selectedRowChanged[str, str].connect( self.onSeriesSelectionChanged) # When a series is added it should be immediately edited self.seriesModel.rowAppended.connect(self.editSeriesName) self.seriesModel.rowsInserted.connect(self.checkNoSeries) self.seriesModel.rowsRemoved.connect(self.checkNoSeries) self.workbenchView = WorkbenchView(self, editable=False) self.workbench: WorkbenchModel = None self.workbenchView.selectedRowChanged[str, str].connect( self.onFrameSelectionChanged) self.attributesView = SearchableAttributeTableWidget( self, True, False, False, [Types.Numeric, Types.Ordinal]) firstRowLayout = QHBoxLayout() firstRowLayout.setSpacing(5) selectionGroup = QGroupBox( title= 'Select a time series. Then select the columns to add from the current ' 'datasets', parent=self) firstRowLayout.addWidget(self.seriesView) buttonLayout = QVBoxLayout() buttonLayout.addWidget(self.addSeriesButton) buttonLayout.addWidget(self.removeSeriesButton) firstRowLayout.addLayout(buttonLayout) firstRowLayout.addSpacing(30) firstRowLayout.addWidget(self.workbenchView) firstRowLayout.addSpacing(30) firstRowLayout.addWidget(self.attributesView) selectionGroup.setLayout(firstRowLayout) # Time axis labels model with add/remove buttons self.timeAxisModel = CustomStringListModel(self) self.timeAxisModel.setHeaderLabel('Time labels') self.timeAxisView = CustomSignalView(self) self.timeAxisView.setModel(self.timeAxisModel) self.addTimeButton = QPushButton('Add', self) self.removeTimeButton = QPushButton('Remove', self) self.addTimeButton.clicked.connect(self.addTimeLabel) self.removeTimeButton.clicked.connect(self.removeTimeLabel) self.timeAxisView.setDragDropMode(QTableView.InternalMove) self.timeAxisView.setDragDropOverwriteMode(False) self.timeAxisView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.timeAxisView.verticalHeader().hide() self.timeAxisModel.rowAppended.connect(self.editTimeLabelName) # Concatenation model self.timeSeriesDataModel = ConcatenatedModel(self) self.timeSeriesDataView = QTableView(self) self.timeSeriesDataView.setSelectionMode(QTableView.NoSelection) self.timeSeriesDataView.setItemDelegateForColumn( 1, ComboBoxDelegate(self.timeAxisModel, self.timeSeriesDataView)) self.timeSeriesDataView.setEditTriggers(QTableView.CurrentChanged | QTableView.DoubleClicked) self.timeSeriesDataView.verticalHeader().hide() # Update the label column when some label changes in the label table self.timeAxisModel.dataChanged.connect( self.timeSeriesDataModel.timeAxisLabelChanged) groupTime = QGroupBox( title= 'Add the time points (ordered) and set the correspondence to every selected column', parent=self) secondRowLayout = QHBoxLayout() secondRowLayout.setSpacing(5) # labelLayout = QVBoxLayout() # lab = QLabel('Here you should define every time point, in the correct order. After adding ' # 'double-click a row to edit the point name and drag rows to reorder them', self) # lab.setWordWrap(True) # labelLayout.addWidget(lab) # labelLayout.addWidget(self.timeAxisView) secondRowLayout.addWidget(self.timeAxisView) timeButtonLayout = QVBoxLayout() timeButtonLayout.addWidget(self.addTimeButton) timeButtonLayout.addWidget(self.removeTimeButton) secondRowLayout.addLayout(timeButtonLayout) secondRowLayout.addSpacing(30) # labelLayout = QVBoxLayout() # lab = QLabel('Every selected column for the current series will be listed here. Click the right ' # 'column of the table to set the time label associated with every original column', # self) # lab.setWordWrap(True) # labelLayout.addWidget(lab) secondRowLayout.addWidget(self.timeSeriesDataView) # secondRowLayout.addLayout(labelLayout) groupTime.setLayout(secondRowLayout) self.outputName = QLineEdit(self) self.warningLabel = MessageLabel(text='', color='orange', icon=QMessageBox.Warning, parent=self) lastRowLayout = QFormLayout() lastRowLayout.addRow('Output variable name:', self.outputName) self.outputName.setPlaceholderText('Output name') lastRowLayout.setVerticalSpacing(0) lastRowLayout.addRow('', self.warningLabel) lastRowLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self.warningLabel.hide() self.outputName.textChanged.connect(self.checkOutputName) layout = QVBoxLayout(self) layout.addWidget(selectionGroup) layout.addWidget(groupTime) layout.addLayout(lastRowLayout) self.checkNoSeries() def setWorkbench(self, w: WorkbenchModel) -> None: """ Sets the workbench and initialises every attribute model (one for each frame) """ self.workbench = w self.workbenchView.setModel(w) # Set a default name for output if self.workbench: name = 'time_series_{:d}' n = 1 name_n = name.format(n) while name_n in self.workbench.names: n += 1 name_n = name.format(n) self.outputName.setText(name_n) def addSourceFrameModel(self, frameName: str) -> None: if self.workbench: dfModel = self.workbench.getDataframeModelByName(frameName) # Create an attribute model with checkboxes standardModel = AttributeTableModel(self, checkable=True, editable=False, showTypes=True) standardModel.setFrameModel(dfModel) # Create a proxy to filter data in the concatenation customProxy = CustomProxyAttributeModel(self) customProxy.setSourceModel(standardModel) # Add proxy to the list of models self.models[frameName] = customProxy # Add proxy as source model self.timeSeriesDataModel.addSourceModel(customProxy) @Slot() def checkNoSeries(self) -> None: if not self.seriesModel.rowCount(): self.workbenchView.setEnabled(False) self.attributesView.setEnabled(False) self.timeAxisView.setEnabled(False) self.addTimeButton.setEnabled(False) self.removeTimeButton.setEnabled(False) self.timeSeriesDataView.setEnabled(False) else: self.workbenchView.setEnabled(True) self.attributesView.setEnabled(True) self.timeAxisView.setEnabled(True) self.addTimeButton.setEnabled(True) self.removeTimeButton.setEnabled(True) self.timeSeriesDataView.setEnabled(True) def persistOptionsSetForSeries(self, seriesName: str) -> None: if seriesName: seriesValues: Dict[str, List[Tuple[int, QPersistentModelIndex]]] = dict() for r in range(self.timeSeriesDataModel.rowCount()): column0Index: QModelIndex = self.timeSeriesDataModel.index( r, 0, QModelIndex()) column1Index: QModelIndex = self.timeSeriesDataModel.index( r, 1, QModelIndex()) sourceIndex: QModelIndex = self.timeSeriesDataModel.mapToSource( column0Index) proxy: CustomProxyAttributeModel = sourceIndex.model() frameName: str = proxy.sourceModel().frameModel().name attrIndexInFrame: int = proxy.mapToSource(sourceIndex).row() timeLabelIndex: QPersistentModelIndex = column1Index.data( Qt.DisplayRole) if seriesValues.get(frameName, None): seriesValues[frameName].append( (attrIndexInFrame, timeLabelIndex)) else: seriesValues[frameName] = [(attrIndexInFrame, timeLabelIndex)] self.seriesOptions[seriesName] = seriesValues @Slot(str, str) def onSeriesSelectionChanged(self, new: str, old: str) -> None: # Save current set options self.persistOptionsSetForSeries(old) if new: # Get options of new selection newOptions: Dict[str, List[Tuple[int, QPersistentModelIndex]]] = \ self.seriesOptions.get(new, dict()) for frameName, proxyModel in self.models.items(): frameOptions = newOptions.get(frameName, None) self.setOptionsForFrame(frameName, frameOptions) # Update proxy view on the time label columns proxyModel.dataChanged.emit( proxyModel.index(0, 1, QModelIndex()), proxyModel.index(proxyModel.rowCount() - 1, 1, QModelIndex()), [Qt.DisplayRole, Qt.EditRole]) # Every time series change clear frame selection in workbench self.workbenchView.clearSelection() @Slot(str, str) def onFrameSelectionChanged(self, newFrame: str, _: str) -> None: if not newFrame: # Nothing is selected return self.attributesView.setAttributeModel( AttributeTableModel(self)) # Check if frame is already in the source models if newFrame not in self.models.keys(): # Create a new proxy and add it to source models self.addSourceFrameModel(newFrame) if len(self.models) == 1: # If it is the first model added then set up the view self.timeSeriesDataView.setModel(self.timeSeriesDataModel) self.timeSeriesDataView.horizontalHeader( ).setSectionResizeMode(0, QHeaderView.Stretch) self.timeSeriesDataView.horizontalHeader( ).setSectionResizeMode(1, QHeaderView.Stretch) # Update the attribute table self.attributesView.setAttributeModel( self.models[newFrame].sourceModel()) def setOptionsForFrame( self, frameName: str, options: Optional[List[Tuple[int, QPersistentModelIndex]]]) -> None: customProxyModel = self.models[frameName] attributeTableModel = customProxyModel.sourceModel() attributeTableModel.setAllChecked(False) if options: proxySelection: Dict[int, QPersistentModelIndex] = { i: pmi for i, pmi in options } customProxyModel.attributes = proxySelection attributeTableModel.setChecked(list(proxySelection.keys()), value=True) else: customProxyModel.attributes = dict() @Slot() def addSeries(self) -> None: # Append new row self.seriesModel.appendEmptyRow() # In oder to avoid copying previous options for frameName, proxyModel in self.models.items(): self.setOptionsForFrame(frameName, None) @Slot() def removeSeries(self) -> None: selected: List[QModelIndex] = self.seriesView.selectedIndexes() if selected: seriesName: str = selected[0].data(Qt.DisplayRole) # Remove row self.seriesModel.removeRow(selected[0].row()) # Remove options for series if they exists self.seriesOptions.pop(seriesName, None) @Slot() def addTimeLabel(self) -> None: self.timeAxisModel.appendEmptyRow() @Slot() def removeTimeLabel(self) -> None: selected: List[QModelIndex] = self.timeAxisView.selectedIndexes() if selected: self.timeAxisModel.removeRow(selected[0].row()) # Update model self.timeSeriesDataModel.dataChanged.emit( self.timeSeriesDataModel.index(0, 1, QModelIndex()), self.timeSeriesDataModel.index( self.timeSeriesDataModel.rowCount() - 1, 1, QModelIndex()), [Qt.DisplayRole, Qt.EditRole]) @Slot() def editSeriesName(self) -> None: index = self.seriesModel.index(self.seriesModel.rowCount() - 1, 0, QModelIndex()) self.seriesView.setCurrentIndex(index) self.seriesView.edit(index) @Slot() def editTimeLabelName(self) -> None: index = self.timeAxisModel.index(self.timeAxisModel.rowCount() - 1, 0, QModelIndex()) self.timeAxisView.setCurrentIndex(index) self.timeAxisView.edit(index) @Slot(str) def checkOutputName(self, text: str) -> None: if self.workbench and text in self.workbench.names: self.warningLabel.setText( 'Variable {:s} will be overwritten'.format(text)) self.warningLabel.show() else: self.warningLabel.hide()
class VStdAttributesDialog(QDialog): def __init__(self, parent, sig_attribute_edition: Signal, student: Student, attributes: list): """ Confirm dialog for dangerous actions :param parent: gui's main window :param sig_attribute_edition: Signal to emit in order to edit the selected attribute :param student: Current student :param attributes: List of attributes """ QDialog.__init__(self, parent) self.setFixedSize(QSize(350, 350)) self.setWindowFlag(Qt.WindowStaysOnTopHint) self.student = student self.attributes = attributes # Widgets self.std_info = QLabel(f"{student.firstname} {student.lastname}") self.table_attributes = QTableView() self.table_attributes.setFixedWidth(300) self.table_attributes.horizontalHeader().setStretchLastSection(True) self.table_attributes.horizontalHeader().hide() self.table_attributes.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_attributes.setSelectionMode( QAbstractItemView.SingleSelection) self.table_attributes.verticalHeader().setSectionResizeMode( QHeaderView.Fixed) self.table_attributes.verticalHeader().setFixedWidth(150) self.dm: AttributesTableModel = None self.attr_idx = { } # Attributes ids to indexes -> {attr_id: attr_idx, ...} # Close button self.ok_btn = QPushButton("Ok") self.ok_btn.clicked.connect(self.accept) self.ok_btn.setFixedSize(QSize(60, 33)) # Signals self.sig_attribute_edition = sig_attribute_edition self.table_attributes.clicked.connect(self.on_attr_clicked) # Layout layout = QVBoxLayout() layout.addWidget(self.std_info) layout.setAlignment(self.std_info, Qt.AlignCenter) layout.addWidget(self.table_attributes) layout.addWidget(self.ok_btn) layout.setAlignment(self.ok_btn, Qt.AlignCenter) self.setLayout(layout) self.setStyleSheet(get_stylesheet("dialog")) # Initialization with the given attributes self.attributes_updated(self.attributes) def attributes_updated(self, attributes: list) -> None: """ Called when an attribute changed and needs to be updated in the tableview :param attributes: List of attributes """ self.attributes = attributes header = [a[1] for a in attributes] # Table Header data_for_model = [a[2] for a in attributes] # Table data # Save indexes self.attr_idx = {} i = 0 for attr_id, _, _ in attributes: self.attr_idx[attr_id] = i i += 1 # Create the table model self.dm = AttributesTableModel(self.table_attributes, data_for_model, header) def on_attr_clicked(self, item) -> None: """ Triggered when a cell is clicked. Emits a signal with the attribute and student ids """ attr_id = None for a in self.attr_idx: if self.attr_idx[a] == item.row(): attr_id = a self.sig_attribute_edition.emit(attr_id, self.student.id)