# Created By: Virgil Dupras # Created On: 2009-11-01 # Copyright 2015 Hardcoded Software (http://www.hardcoded.net) # # This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html import typing from PyQt5.QtCore import ( Qt, QAbstractTableModel, QModelIndex, QItemSelectionModel, QItemSelection, ) from qt.column import Columns, Column class Table(QAbstractTableModel): # Flags you want when index.isValid() is False. In those cases, _getFlags() is never called. INVALID_INDEX_FLAGS = Qt.ItemFlag.ItemIsEnabled COLUMNS: typing.List[Column] = [] def __init__(self, model, view, **kwargs): super().__init__(**kwargs) self.model = model self.view = view self.view.setModel(self) self.model.view = self if hasattr(self.model, "_columns"): self._columns = Columns(self.model._columns, self.COLUMNS, view.horizontalHeader()) self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged) def _updateModelSelection(self): # Takes the selection on the view's side and update the model with it. # an _updateViewSelection() call will normally result in an _updateModelSelection() call. # to avoid infinite loops, we check that the selection will actually change before calling # model.select() new_indexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()] if new_indexes != self.model.selected_indexes: self.model.select(new_indexes) def _updateViewSelection(self): # Takes the selection on the model's side and update the view with it. new_selection = QItemSelection() column_count = self.columnCount(QModelIndex()) for index in self.model.selected_indexes: new_selection.select(self.createIndex(index, 0), self.createIndex(index, column_count - 1)) self.view.selectionModel().select(new_selection, QItemSelectionModel.ClearAndSelect) if len(new_selection.indexes()): current_index = new_selection.indexes()[0] self.view.selectionModel().setCurrentIndex(current_index, QItemSelectionModel.Current) self.view.scrollTo(current_index) # --- Data Model methods # Virtual def _getData(self, row, column, role): if role in (Qt.DisplayRole, Qt.EditRole): attrname = column.name return row.get_cell_value(attrname) elif role == Qt.TextAlignmentRole: return column.alignment return None # Virtual def _getFlags(self, row, column): flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if row.can_edit_cell(column.name): flags |= Qt.ItemIsEditable return flags # Virtual def _setData(self, row, column, value, role): if role == Qt.EditRole: attrname = column.name if attrname == "from": attrname = "from_" setattr(row, attrname, value) return True return False def columnCount(self, index): return self.model._columns.columns_count() def data(self, index, role): if not index.isValid(): return None row = self.model[index.row()] column = self.model._columns.column_by_index(index.column()) return self._getData(row, column, role) def flags(self, index): if not index.isValid(): return self.INVALID_INDEX_FLAGS row = self.model[index.row()] column = self.model._columns.column_by_index(index.column()) return self._getFlags(row, column) def headerData(self, section, orientation, role): if orientation != Qt.Horizontal: return None if section >= self.model._columns.columns_count(): return None column = self.model._columns.column_by_index(section) if role == Qt.DisplayRole: return column.display elif role == Qt.TextAlignmentRole: return column.alignment else: return None def revert(self): self.model.cancel_edits() def rowCount(self, index): if index.isValid(): return 0 return len(self.model) def setData(self, index, value, role): if not index.isValid(): return False row = self.model[index.row()] column = self.model._columns.column_by_index(index.column()) return self._setData(row, column, value, role) def sort(self, section, order): column = self.model._columns.column_by_index(section) attrname = column.name self.model.sort_by(attrname, desc=order == Qt.DescendingOrder) def submit(self): self.model.save_edits() return True # --- Events def selectionChanged(self, selected, deselected): self._updateModelSelection() # --- model --> view def refresh(self): self.beginResetModel() self.endResetModel() self._updateViewSelection() def show_selected_row(self): if self.model.selected_index is not None: self.view.showRow(self.model.selected_index) def start_editing(self): self.view.editSelected() def stop_editing(self): self.view.setFocus() # enough to stop editing def update_selection(self): self._updateViewSelection()