# Table.py -- Pamhyr # Copyright (C) 2023-2025 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 numpy as np import logging import traceback from tools import timer, trace from PyQt5.QtGui import ( QFont, QColor ) from PyQt5.QtWidgets import ( QMessageBox, QStyledItemDelegate, QLineEdit ) from PyQt5.QtCore import ( QModelIndex, Qt, QVariant, QCoreApplication ) from View.Tools.PamhyrTable import PamhyrTableModel from Model.Geometry.PointXYZ import PointXYZ from Model.Geometry.ProfileXYZ import ProfileXYZ from View.Geometry.Profile.UndoCommand import * logger = logging.getLogger() _translate = QCoreApplication.translate class GeometryProfileTableModel(PamhyrTableModel): def get_true_data_row(self, row): profile = self._data.point(row) return next( map( lambda e: e[0], filter( lambda e: e[1] == profile, enumerate(self._data._points) ) ), 0 ) def data(self, index, role=Qt.DisplayRole): if index.isValid(): if role == Qt.DisplayRole: value = "" if index.column() == 0: value = self._data.point(index.row()).x elif index.column() == 1: value = self._data.point(index.row()).y elif index.column() == 2: value = self._data.point(index.row()).z elif index.column() == 3: value = self._data.point(index.row()).name elif index.column() == 4: station = self._data.get_station() if station is None: return "-" else: value = station[index.row()] return f"{value:.3f}" if 0 <= index.column() < 3: return f"{value:.4f}" return f"{value}" if role == Qt.TextAlignmentRole: return Qt.AlignHCenter | Qt.AlignVCenter if index.column() == 2: value = self._data.point(index.row()).z if role == Qt.ForegroundRole: if value == self._data.z_min(): return QColor("red") elif value == self._data.z_max(): return QColor("blue") if index.column() == 3: value = self._data.point(index.row()).name if value.strip().upper() in ["RG", "RD"]: if role == Qt.FontRole: font = QFont() font.setBold(True) return font if role == Qt.ForegroundRole: return QColor("darkRed") return QVariant() def setData(self, index, value, role=Qt.EditRole): row = index.row() column = index.column() if role == Qt.EditRole: try: if column == 0: self._undo.push( SetXCommand( self._data, row, self._data.point(row).x, value.replace(",", ".") ) ) elif column == 1: self._undo.push( SetYCommand( self._data, row, self._data.point(row).y, value.replace(",", ".") ) ) elif column == 2: self._undo.push( SetZCommand( self._data, row, self._data.point(row).z, value.replace(",", ".") ) ) elif column == 3: self._undo.push( SetNameCommand( self._data, row, self._data.point(row).name, value ) ) except Exception as e: logger.info(e) logger.debug(traceback.format_exc()) self.dataChanged.emit(index, index) return True self.dataChanged.emit(index, index) self.layoutChanged.emit() return False def index(self, row, column, parent=QModelIndex()): if not self.hasIndex(row, column, parent): return QModelIndex() return self.createIndex(row, column, QModelIndex()) def flags(self, index): flg = Qt.ItemIsSelectable if index.column() == 4: return flg return Qt.ItemIsEditable | Qt.ItemIsEnabled | flg def insert_row(self, row, parent=QModelIndex()): self.beginInsertRows(parent, row, row - 1) row = self.get_true_data_row(row) self._undo.push( AddCommand( self._data, row ) ) self.endInsertRows() self.layoutChanged.emit() def remove_rows(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) self._undo.push( DelCommand( self._data, list( map( lambda r: self._data.point(r), rows ) ) ) ) self.endRemoveRows() self.layoutChanged.emit() def sort(self, column='x', order=Qt.AscendingOrder): self.layoutAboutToBeChanged.emit() reverse = (order != Qt.AscendingOrder) self._undo.push( SortCommand( self._data, column, reverse ) ) self.layoutChanged.emit() def move_up(self, row, parent=QModelIndex()): if row <= 0: return target = row + 1 self.beginMoveRows(parent, row - 1, row - 1, parent, target) row = self.get_true_data_row(row) self._undo.push( MoveCommand( self._data, "up", row ) ) self.endMoveRows() self.layoutChanged.emit() def move_down(self, row, parent=QModelIndex()): if row > self._data.number_points: return target = row self.beginMoveRows(parent, row + 1, row + 1, parent, target) row = self.get_true_data_row(row) self._undo.push( MoveCommand( self._data, "down", row ) ) self.endMoveRows() self.layoutChanged.emit() def purge(self, np_purge): self._undo.push( PurgeCommand( self._data, np_purge ) ) self.layoutChanged.emit() def reverse(self): self._undo.push( ReverseCommand( self._data ) ) self.layoutChanged.emit() def paste(self, row, header, data): if row > self._data.number_points: return if len(data) == 0: return self.layoutAboutToBeChanged.emit() points = list( map( lambda d: self._data.point_from_data(header, d), data ) ) row = self.get_true_data_row(row) self._undo.push( PasteCommand( self._data, row, points ) ) self.layoutAboutToBeChanged.emit() self.layoutChanged.emit() def undo(self): self._undo.undo() self.layoutChanged.emit() def redo(self): self._undo.redo() self.layoutChanged.emit()