Pamhyr2/src/View/Geometry/Profile/Window.py

331 lines
9.2 KiB
Python

# Window.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 <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import copy
import sys
import csv
from time import time
import logging
from tools import trace, timer, logger_exception
from Modules import Modules
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5.QtCore import (
QModelIndex, Qt, QEvent, QCoreApplication
)
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QFileDialog, QCheckBox,
QUndoStack, QShortcut, QTableView, QAbstractItemView,
QHeaderView, QVBoxLayout, QAction,
)
from Model.Geometry.Reach import Reach
from Model.Geometry.ProfileXYZ import ProfileXYZ
from View.Tools.PamhyrWindow import PamhyrWindow
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Geometry.Profile.Plot import Plot
from View.Geometry.Profile.Table import GeometryProfileTableModel
from View.Geometry.Profile.Translate import GeometryProfileTranslate
from View.Geometry.PurgeDialog import PurgeDialog
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ProfileWindow(PamhyrWindow):
_pamhyr_ui = "GeometryCrossSection"
_pamhyr_name = "Geometry cross-section"
def __init__(self, profile=None, study=None, config=None, parent=None):
self._profile = profile
trad = GeometryProfileTranslate()
name = (
trad[self._pamhyr_name] +
f" - {self._profile.name} {self._profile.rk}"
)
super(ProfileWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(profile)
self.setup_table()
self.setup_plot()
self.setup_connections()
def setup_table(self):
if self._study.is_read_only():
editable_headers = []
else:
editable_headers = ["name", "x", "y", "z"]
table_headers = self._trad.get_dict("table_headers")
table = self.find(QTableView, "tableView")
self._tablemodel = GeometryProfileTableModel(
table_view=table,
table_headers=table_headers,
editable_headers=editable_headers,
data=self._profile,
undo=self._undo_stack
)
table.setModel(self._tablemodel)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_plot(self):
self._tablemodel.blockSignals(True)
self._canvas = MplCanvas(width=3, height=4, dpi=100)
self._canvas.setObjectName("canvas")
self._toolbar = PamhyrPlotToolbar(
self._canvas, self,
items=["home", "zoom", "save", "iso", "back/forward", "move"]
)
self._plot_layout = self.find(QVBoxLayout, "verticalLayout")
self._plot_layout.addWidget(self._toolbar)
self._plot_layout.addWidget(self._canvas)
self._plot = Plot(
canvas=self._canvas,
data=self._profile,
trad=self._trad,
toolbar=self._toolbar,
table=self.find(QTableView, "tableView"),
parent=self
)
self._plot.draw()
self._tablemodel.blockSignals(False)
def setup_connections(self):
if self._study.is_read_only():
actions = {}
else:
actions = {
"action_sort_asc": self.sort_X_ascending,
"action_sort_des": self.sort_X_descending,
"action_up": self.move_up,
"action_down": self.move_down,
"action_add": self.add,
"action_delete": self.delete,
"action_purge": self.purge,
"action_reverse": self.reverse,
}
for action in actions:
self.find(QAction, action)\
.triggered.connect(actions[action])
table = self.find(QTableView, f"tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update_points_selection)
self._tablemodel.dataChanged.connect(self.update)
def update_points_selection(self):
rows = self.index_selected_rows()
self._plot.select_points_from_indices(rows)
def update(self):
self.update_plot()
self._propagate_update(key=Modules.GEOMETRY)
def _update(self, redraw=False, propagate=True):
if redraw:
self.update_plot()
if propagate:
self._propagate_update(key=Modules.GEOMETRY)
def update_plot(self):
self._tablemodel.blockSignals(True)
self._plot.update()
self._tablemodel.blockSignals(False)
def _propagated_update(self, key=Modules(0)):
if Modules.GEOMETRY not in key:
return
self._tablemodel.layoutChanged.emit()
self._update(redraw=True, propagate=False)
def index_selected_row(self):
table = self.find(QTableView, "tableView")
rows = table.selectionModel()\
.selectedRows()
if len(rows) == 0:
return 0
return rows[0].row()
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
map(
lambda r: r.row(),
table.selectionModel().selectedRows()
)
)
def add(self):
table = self.find(QTableView, "tableView")
if len(table.selectedIndexes()) == 0:
self._tablemodel.insert_row(self._tablemodel.rowCount())
else:
row = self.index_selected_row()
self._tablemodel.insert_row(row + 1)
self.update()
def delete(self):
table = self.find(QTableView, "tableView")
rows = sorted(
list(
set(
[index.row() for index in table.selectedIndexes()]
)
)
)
if len(rows) > 0:
self._tablemodel.remove_rows(rows)
self.update()
def sort_X_ascending(self):
self._tablemodel.sort('x', order=Qt.AscendingOrder)
self.update()
def sort_X_descending(self):
self._tablemodel.sort('x', order=Qt.DescendingOrder)
self.update()
def sort_Y_ascending(self):
self._tablemodel.sort('y', order=Qt.AscendingOrder)
self.update()
def sort_Y_descending(self):
self._tablemodel.sort('y', order=Qt.DescendingOrder)
self.update()
def move_down(self):
rows = list(
set(
[index.row() for index in
self.find(QTableView, "tableView").selectedIndexes()]
)
)
for row in rows:
if row < self._tablemodel.rowCount() - 1:
self._tablemodel.move_down(row)
self.update()
def move_up(self):
rows = list(
set(
[index.row() for index in
self.find(QTableView, "tableView").selectedIndexes()]
)
)
for row in rows:
if 0 < row:
self._tablemodel.move_up(row)
self.update()
def purge(self):
self._tablemodel.purge()
self.update()
def purge(self):
try:
dlg = PurgeDialog(
trad=self._trad,
parent=self
)
if dlg.exec():
self._tablemodel.purge(dlg.np_purge)
self._plot.draw()
except Exception as e:
logger_exception(e)
return
def reverse(self):
self._tablemodel.reverse()
self.update()
def _copy(self):
table = self.find(QTableView, "tableView")
rows = table.selectionModel().selectedRows()
data = []
data.append(["x", "y", "z", "name"])
for row in rows:
point = self._profile.point(row.row())
data.append(
[
point.x, point.y, point.z, point.name
]
)
self.copyTableIntoClipboard(data)
def _paste(self):
header, data = self.parseClipboardTable()
if len(data) == 0:
return
if len(header) != 0:
header.append("profile")
# for row in data:
# row.append(self._profile)
row = self.index_selected_row()
self._tablemodel.paste(row, header, data)
self.update()
def _undo(self):
self._tablemodel.undo()
self.update()
def _redo(self):
self._tablemodel.redo()
self.update()