2019-09-10 00:54:28 +00:00
|
|
|
# Created By: Virgil Dupras
|
|
|
|
# Created On: 2009-11-01
|
|
|
|
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
2020-01-01 02:16:27 +00:00
|
|
|
#
|
|
|
|
# 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
|
2019-09-10 00:54:28 +00:00
|
|
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
from PyQt5.QtCore import (
|
|
|
|
Qt,
|
|
|
|
QAbstractTableModel,
|
|
|
|
QModelIndex,
|
|
|
|
QItemSelectionModel,
|
|
|
|
QItemSelection,
|
|
|
|
)
|
2019-09-10 00:54:28 +00:00
|
|
|
|
|
|
|
from .column import Columns
|
|
|
|
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
class Table(QAbstractTableModel):
|
|
|
|
# Flags you want when index.isValid() is False. In those cases, _getFlags() is never called.
|
|
|
|
INVALID_INDEX_FLAGS = Qt.ItemIsEnabled
|
|
|
|
COLUMNS = []
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def __init__(self, model, view, **kwargs):
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
self.model = model
|
|
|
|
self.view = view
|
|
|
|
self.view.setModel(self)
|
|
|
|
self.model.view = self
|
2020-01-01 02:16:27 +00:00
|
|
|
if hasattr(self.model, "columns"):
|
2021-08-15 09:10:18 +00:00
|
|
|
self.columns = Columns(self.model.columns, self.COLUMNS, view.horizontalHeader())
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2021-08-15 09:10:18 +00:00
|
|
|
self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
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()
|
2021-08-15 09:10:18 +00:00
|
|
|
newIndexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()]
|
2019-09-10 00:54:28 +00:00
|
|
|
if newIndexes != self.model.selected_indexes:
|
|
|
|
self.model.select(newIndexes)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def _updateViewSelection(self):
|
|
|
|
# Takes the selection on the model's side and update the view with it.
|
|
|
|
newSelection = QItemSelection()
|
|
|
|
columnCount = self.columnCount(QModelIndex())
|
|
|
|
for index in self.model.selected_indexes:
|
2021-08-15 09:10:18 +00:00
|
|
|
newSelection.select(self.createIndex(index, 0), self.createIndex(index, columnCount - 1))
|
|
|
|
self.view.selectionModel().select(newSelection, QItemSelectionModel.ClearAndSelect)
|
2019-09-10 00:54:28 +00:00
|
|
|
if len(newSelection.indexes()):
|
|
|
|
currentIndex = newSelection.indexes()[0]
|
2021-08-15 09:10:18 +00:00
|
|
|
self.view.selectionModel().setCurrentIndex(currentIndex, QItemSelectionModel.Current)
|
2019-09-10 00:54:28 +00:00
|
|
|
self.view.scrollTo(currentIndex)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Data Model methods
|
2019-09-10 00:54:28 +00:00
|
|
|
# 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
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
# Virtual
|
|
|
|
def _getFlags(self, row, column):
|
|
|
|
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
|
|
|
if row.can_edit_cell(column.name):
|
|
|
|
flags |= Qt.ItemIsEditable
|
|
|
|
return flags
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
# Virtual
|
|
|
|
def _setData(self, row, column, value, role):
|
|
|
|
if role == Qt.EditRole:
|
|
|
|
attrname = column.name
|
2020-01-01 02:16:27 +00:00
|
|
|
if attrname == "from":
|
|
|
|
attrname = "from_"
|
2019-09-10 00:54:28 +00:00
|
|
|
setattr(row, attrname, value)
|
|
|
|
return True
|
|
|
|
return False
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def columnCount(self, index):
|
|
|
|
return self.model.columns.columns_count()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
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)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
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)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
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
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def revert(self):
|
|
|
|
self.model.cancel_edits()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def rowCount(self, index):
|
|
|
|
if index.isValid():
|
|
|
|
return 0
|
|
|
|
return len(self.model)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
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)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def sort(self, section, order):
|
|
|
|
column = self.model.columns.column_by_index(section)
|
|
|
|
attrname = column.name
|
2020-01-01 02:16:27 +00:00
|
|
|
self.model.sort_by(attrname, desc=order == Qt.DescendingOrder)
|
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def submit(self):
|
|
|
|
self.model.save_edits()
|
|
|
|
return True
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- Events
|
2019-09-10 00:54:28 +00:00
|
|
|
def selectionChanged(self, selected, deselected):
|
|
|
|
self._updateModelSelection()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
|
|
|
# --- model --> view
|
2019-09-10 00:54:28 +00:00
|
|
|
def refresh(self):
|
|
|
|
self.beginResetModel()
|
|
|
|
self.endResetModel()
|
|
|
|
self._updateViewSelection()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def show_selected_row(self):
|
|
|
|
if self.model.selected_index is not None:
|
|
|
|
self.view.showRow(self.model.selected_index)
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def start_editing(self):
|
|
|
|
self.view.editSelected()
|
2020-01-01 02:16:27 +00:00
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def stop_editing(self):
|
2020-01-01 02:16:27 +00:00
|
|
|
self.view.setFocus() # enough to stop editing
|
|
|
|
|
2019-09-10 00:54:28 +00:00
|
|
|
def update_selection(self):
|
|
|
|
self._updateViewSelection()
|