# PamhyrTable.py -- Pamhyr abstract table model # Copyright (C) 2023-2026 INRAE # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -*- coding: utf-8 -*- import logging import traceback from datetime import timezone from tools import trace, timer, parse_datetime from Model.Except import NotImplementedMethodeError from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, QModelIndex, pyqtSlot, QRect, ) from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QStyledItemDelegate, QHeaderView, QMessageBox, ) _translate = QCoreApplication.translate logger = logging.getLogger() class PamhyrTextDelegate(QStyledItemDelegate): def __init__(self, parent=None): super(PamhyrTextDelegate, self).__init__(parent) def createEditor(self, parent, option, index): index.model().data(index, Qt.DisplayRole) return QLineEdit(parent) def setEditorData(self, editor, index): value = index.model().data(index, Qt.DisplayRole) editor.setText(str(value)) def setModelData(self, editor, model, index): model.setData(index, editor.text()) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) class PamhyrTableModel(QAbstractTableModel): def date_to_timestamp(self, value): """Convert an accepted calendar date to its raw UTC timestamp.""" try: entered_date = parse_datetime(value) except ValueError as error: QMessageBox.warning( self._table_view, _translate("PamhyrTableModel", "Invalid date/time"), _translate( "PamhyrTableModel", "The entered date/time is invalid.\n" "Expected formats:\n" "YYYY-MM-DD [HH:MM[:SS]],\n" "DD-MM-YYYY [HH:MM[:SS]] or\n" "DD/MM/YYYY [HH:MM[:SS]]." ) ) raise error return entered_date.replace(tzinfo=timezone.utc).timestamp() def _setup_delegates(self): if self._table_view is None: return for h in self._headers: if h in self._delegates: self._table_view.setItemDelegateForColumn( self._headers.index(h), self._delegates[h] ) else: self._table_view.setItemDelegateForColumn( self._headers.index(h), PamhyrTextDelegate( parent=self ) ) def __init__(self, table_view=None, table_headers={}, editable_headers=[], delegates={}, trad=None, data=None, undo=None, opt_data=None, options=["rows_selection"], parent=None, start_date=None): super(PamhyrTableModel, self).__init__() self._table_view = table_view self._table_headers = table_headers self._headers = list(table_headers.keys()) self._editable_headers = editable_headers self._delegates = delegates self._trad = trad self._parent = parent self._data = data self._opt_data = opt_data self._options = options self._undo = undo self._lst = [] self._start_date = start_date self._setup_delegates() self._setup_lst() self._table_view_configure() def _setup_lst(self): self._lst = self._data def _table_view_configure(self): self._table_view.setModel(self) if "rows_selection" in self._options: self._table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self._table_view.horizontalHeader()\ .setSectionResizeMode(QHeaderView.Stretch) self._table_view.setAlternatingRowColors(True) self._table_view.resizeColumnsToContents() def flags(self, index): column = index.column() options = Qt.ItemIsEnabled | Qt.ItemIsSelectable if self._headers[column] in self._editable_headers: options |= Qt.ItemIsEditable return options def rowCount(self, parent=QModelIndex()): return len(self._lst) def columnCount(self, parent=QModelIndex()): return len(self._headers) def headerData(self, section, orientation, role): if role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: return self._table_headers[self._headers[section]] return QVariant() def is_same_data(self, index, value): current = self.data(index, Qt.ItemDataRole.DisplayRole) if isinstance(current, QVariant): current = current.value() if str(current) == str(value): return True if hasattr(value, "display_name"): if str(current) == str(value.display_name()): return True if hasattr(value, "name"): if str(current) == str(value.name): return True try: return ( float(str(current).replace(",", ".")) == float(str(value).replace(",", ".")) ) except (TypeError, ValueError): return False def data(self, index, role): raise NotImplementedMethodeError(self, self.data) def setData(self, index, value, role=Qt.EditRole): raise NotImplementedMethodeError(self, self.setData) def undo(self): self._undo.undo() self.layoutChanged.emit() def redo(self): self._undo.redo() self.layoutChanged.emit() def update(self): self.layoutChanged.emit()