mirror of https://gitlab.com/pamhyr/pamhyr2
495 lines
15 KiB
Python
495 lines
15 KiB
Python
# Window.py -- Pamhyr
|
|
# Copyright (C) 2023 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 os
|
|
import pathlib
|
|
import sys
|
|
import time
|
|
|
|
from copy import deepcopy
|
|
from tools import timer, trace
|
|
|
|
from PyQt5 import QtWidgets
|
|
from PyQt5.QtGui import (
|
|
QKeySequence,
|
|
)
|
|
from PyQt5.QtCore import (
|
|
QModelIndex, Qt, QSettings, pyqtSlot,
|
|
QItemSelectionModel, QCoreApplication, QSize
|
|
)
|
|
from PyQt5.QtWidgets import (
|
|
QApplication, QMainWindow, QFileDialog, QCheckBox,
|
|
QUndoStack, QShortcut,
|
|
)
|
|
|
|
from View.Geometry.PlotXY import PlotXY
|
|
from View.Geometry.PlotKPZ import PlotKPZ
|
|
from View.Geometry.PlotAC import PlotAC
|
|
|
|
from View.ASubWindow import ASubMainWindow, WindowToolKit
|
|
from View.ListedSubWindow import ListedSubWindow
|
|
from View.Geometry.mainwindow_ui_reach import Ui_MainWindow
|
|
from View.Geometry.Table import *
|
|
from View.Geometry.Profile.Window import ProfileWindow
|
|
|
|
_translate = QCoreApplication.translate
|
|
|
|
|
|
class GeometryWindow(ASubMainWindow, ListedSubWindow):
|
|
def __init__(self, model=None, title="Geometry", parent=None):
|
|
self._title = title
|
|
self.parent = parent
|
|
super(GeometryWindow, self).__init__(
|
|
name=self._title,
|
|
parent=parent
|
|
)
|
|
|
|
self._model = model
|
|
self._reach = model.river.current_reach().reach
|
|
|
|
self.ui = Ui_MainWindow()
|
|
self.ui.setupUi(self)
|
|
|
|
self.tableView = self.ui.tableView
|
|
self.tableView_header = self.ui.tableView_header
|
|
|
|
self._tablemodel = None
|
|
self._profile_window = []
|
|
self._clipboard = None
|
|
|
|
self.setup_window()
|
|
self.setup_sc()
|
|
self.setup_model()
|
|
self.setup_plots()
|
|
self.setup_connections()
|
|
self.changed_slider_value()
|
|
|
|
def setup_window(self):
|
|
self._title = f"{self.ui.mainwindow_title} - {self._reach.name}"
|
|
self.setWindowTitle(self._title)
|
|
|
|
def setup_sc(self):
|
|
self._undo_stack = QUndoStack()
|
|
|
|
self.undo_sc = QShortcut(QKeySequence.Undo, self)
|
|
self.redo_sc = QShortcut(QKeySequence.Redo, self)
|
|
self.copy_sc = QShortcut(QKeySequence.Copy, self)
|
|
self.paste_sc = QShortcut(QKeySequence.Paste, self)
|
|
|
|
def setup_model(self):
|
|
self._tablemodel = TableEditableModel(
|
|
headers = self.ui.tableView_header,
|
|
reach = self._reach,
|
|
undo = self._undo_stack
|
|
)
|
|
self.tableView.setModel(self._tablemodel)
|
|
self.tableView.setItemDelegate(Delegate())
|
|
|
|
def setup_plots(self):
|
|
self.plot_xy()
|
|
self.plot_kpc()
|
|
self.plot_ac()
|
|
|
|
def setup_connections(self):
|
|
self.ui.btn_open.triggered.connect(self.open_file_dialog)
|
|
self.ui.btn_sort_asc.triggered.connect(self.sort_ascending)
|
|
self.ui.btn_sort_desc.triggered.connect(self.sort_descending)
|
|
self.ui.btn_move_up.triggered.connect(self.move_row_up)
|
|
self.ui.btn_move_down.triggered.connect(self.move_row_down)
|
|
self.ui.btn_end_editing.triggered.connect(self.handleSave)
|
|
self.ui.btn_add.triggered.connect(self.insert_row)
|
|
self.ui.btn_delete.triggered.connect(self.delete_rows)
|
|
self.ui.btn_edit.triggered.connect(self.edit_profile)
|
|
self.ui.verticalSlider.valueChanged.connect(self.changed_slider_value)
|
|
|
|
self.ui.btn_slider_up.clicked.connect(self.decrement_value_slider)
|
|
self.ui.btn_slider_down.clicked.connect(self.increment_value_slider)
|
|
self.ui.btn_move_up.triggered.connect(self.changed_profile_slot)
|
|
|
|
self.undo_sc.activated.connect(self.undo)
|
|
self.redo_sc.activated.connect(self.redo)
|
|
self.copy_sc.activated.connect(self.copy)
|
|
self.paste_sc.activated.connect(self.paste)
|
|
|
|
# Profile selection when line change in table
|
|
self.tableView.selectionModel()\
|
|
.selectionChanged\
|
|
.connect(self.select_current_profile)
|
|
|
|
def open_file_dialog(self):
|
|
options = QFileDialog.Options()
|
|
settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', )
|
|
options |= QFileDialog.DontUseNativeDialog
|
|
|
|
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
self,
|
|
_translate("MainWindow_reach", "Ouvrir un fichier"),
|
|
"",
|
|
_translate("MainWindow_reach", "Fichiers .ST (*.ST)") +
|
|
";; " +
|
|
_translate("MainWindow_reach", "Fichiers .M (*.M)") +
|
|
";; " +
|
|
_translate("MainWindow_reach", "Tous les fichiers (*)"),
|
|
options=options
|
|
)
|
|
|
|
if filename != "":
|
|
size = os.stat(filename).st_size
|
|
self._reach.import_geometry(filename)
|
|
self._tablemodel.layoutChanged.emit()
|
|
|
|
self.update_profile_windows()
|
|
self.plot_xy()
|
|
self.plot_kpc()
|
|
self.plot_ac()
|
|
|
|
def messagebox_profile_editing(self):
|
|
msg_box = QtWidgets.QMessageBox()
|
|
msg_box.setIcon(QtWidgets.QMessageBox.Information)
|
|
msg_box.setWindowTitle(_translate("MainWindow_reach",
|
|
"Édition des profils sélectionnés"))
|
|
msg_box.setText(_translate("MainWindow_reach",
|
|
"Vous avez sélectionné plus de 5 profils."
|
|
" \nSeuls les 5 premiers seront édités."))
|
|
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
|
|
|
return_value = msg_box.exec()
|
|
# if return_value == QtWidgets.QMessageBox.Ok:
|
|
# print('OK clicked')
|
|
|
|
def edit_profile(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
rows = list(
|
|
set(
|
|
(i.row() for i in self.tableView.selectedIndexes())
|
|
)
|
|
)
|
|
|
|
for row in rows:
|
|
profile = self._reach.profile(row)
|
|
|
|
win = self.sub_win_filter_first(
|
|
"Profile",
|
|
contain = [self._reach.name, str(profile.kp)]
|
|
)
|
|
|
|
if win is None:
|
|
win = ProfileWindow(
|
|
profile = profile,
|
|
parent = self,
|
|
)
|
|
self._profile_window.append(win)
|
|
win.show()
|
|
else:
|
|
win.activateWindow()
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
pyqtSlot(bool)
|
|
|
|
def changed_profile_slot(self, status):
|
|
self.update_view1 = status
|
|
|
|
def update_profile_windows(self):
|
|
self.list_second_window = []
|
|
self.list_row = []
|
|
|
|
def plot_xy(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
self._plot_xy = PlotXY(
|
|
canvas = self.ui.canvas_1,
|
|
data = self._reach,
|
|
toolbar = self.ui.toolbar_1
|
|
)
|
|
self._plot_xy.draw()
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def update_plot_xy(self):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_xy.update()
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def plot_kpc(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
self._plot_kpc = PlotKPZ(
|
|
canvas = self.ui.canvas_2,
|
|
data = self._reach,
|
|
toolbar = self.ui.toolbar_2
|
|
)
|
|
self._plot_kpc.draw()
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def update_plot_kpc(self):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_kpc.update()
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def plot_ac(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
self._plot_ac = PlotAC(
|
|
canvas = self.ui.canvas_3,
|
|
data = self._reach,
|
|
toolbar = self.ui.toolbar_3,
|
|
plot_xy = self._plot_xy
|
|
)
|
|
self._plot_ac.draw()
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def update_plot_ac(self, ind: int):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_ac.update(ind=ind)
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def get_station(self, ind: int):
|
|
return self._reach.profile(ind).get_station()
|
|
|
|
def get_elevation(self, ind: int):
|
|
return self._reach.profile(ind).z()
|
|
|
|
def select_plot_xy(self, ind: int):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_xy.update(ind=ind)
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def select_plot_kpc(self, ind: int):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_kpc.update(ind=ind)
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def select_plot_ac(self, ind: int):
|
|
self.tableView.model().blockSignals(True)
|
|
self._plot_ac.update(ind=ind)
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def select_row_profile_slider(self, ind: int = 0):
|
|
if self.tableView is not None:
|
|
selectionModel = self.tableView.selectionModel()
|
|
index = self.tableView.model().index(ind, 0)
|
|
|
|
selectionModel.select(
|
|
index,
|
|
QItemSelectionModel.Rows |
|
|
QItemSelectionModel.ClearAndSelect |
|
|
QItemSelectionModel.Select
|
|
)
|
|
|
|
self.tableView.scrollTo(index)
|
|
|
|
def select_current_profile(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
if len(self.tableView.selectedIndexes()) > 0:
|
|
row = self.index_selected_row()
|
|
|
|
self.ui.verticalSlider.setValue(row)
|
|
self.select_plot_xy(row)
|
|
self.select_plot_kpc(row)
|
|
self.select_plot_ac(row)
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def changed_slider_value(self):
|
|
self.tableView.model().blockSignals(True)
|
|
|
|
if self._tablemodel.rowCount() != 0:
|
|
self.ui.verticalSlider.setMaximum(self._tablemodel.rowCount() - 1)
|
|
|
|
slider_value = self.ui.verticalSlider.value()
|
|
kp = self._reach.profile(slider_value).kp
|
|
|
|
self.ui.vertical_slider_label.setText(
|
|
_translate("MainWindow_reach", "Kp : ") +
|
|
f"{kp}" + "\n" +
|
|
_translate("MainWindow_reach",
|
|
"Profil N° : ") +
|
|
f"{slider_value + 1}"
|
|
)
|
|
|
|
self.select_plot_xy(slider_value)
|
|
self.select_plot_kpc(slider_value)
|
|
self.select_row_profile_slider(slider_value)
|
|
|
|
self.tableView.model().blockSignals(False)
|
|
|
|
def increment_value_slider(self):
|
|
if 0 <= self.ui.verticalSlider.value() < self._tablemodel.rowCount() - 1:
|
|
self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1)
|
|
|
|
def decrement_value_slider(self):
|
|
if 0 < self.ui.verticalSlider.value() < self._tablemodel.rowCount():
|
|
self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1)
|
|
|
|
def insert_row(self):
|
|
if len(self.tableView.selectedIndexes()) == 0:
|
|
self._tablemodel.insert_row(self._tablemodel.rowCount())
|
|
else:
|
|
row = self.index_selected_row()
|
|
self._tablemodel.insert_row(row + 1)
|
|
|
|
def delete_rows(self):
|
|
rows = sorted(
|
|
list(
|
|
set(
|
|
[index.row() for index in self.tableView.selectedIndexes()]
|
|
)
|
|
)
|
|
)
|
|
|
|
if len(rows) > 0:
|
|
self._tablemodel.remove_rows(rows)
|
|
|
|
self.update_plot_xy()
|
|
self.select_current_profile()
|
|
|
|
self.plot_kpc()
|
|
self.changed_slider_value()
|
|
|
|
def index_selected_row(self):
|
|
return self.tableView\
|
|
.selectionModel()\
|
|
.selectedRows()[0]\
|
|
.row()
|
|
|
|
def sort_ascending(self):
|
|
self._tablemodel.sort_profiles(True)
|
|
self.select_current_profile()
|
|
self.changed_slider_value()
|
|
|
|
def sort_descending(self):
|
|
self._tablemodel.sort_profiles(False)
|
|
|
|
self.select_current_profile()
|
|
self.changed_slider_value()
|
|
|
|
def move_row_up(self):
|
|
row = self.index_selected_row()
|
|
self._tablemodel.move_row_up(row)
|
|
self.select_current_profile()
|
|
|
|
def move_row_down(self):
|
|
row = self.index_selected_row()
|
|
self._tablemodel.move_row_down(row)
|
|
self.select_current_profile()
|
|
|
|
def duplicate(self):
|
|
rows = [
|
|
row.row() for row in
|
|
self.tableView.selectionModel().selectedRows()
|
|
]
|
|
|
|
profiles = []
|
|
for row in rows:
|
|
profiles.append(
|
|
self._reach.profile(row)
|
|
)
|
|
|
|
if len(profiles) == 0:
|
|
return
|
|
|
|
self._tablemodel.duplicate(rows, profiles)
|
|
self.select_current_profile()
|
|
|
|
def copy(self):
|
|
rows = self.tableView\
|
|
.selectionModel()\
|
|
.selectedRows()
|
|
|
|
table = []
|
|
table.append(["name", "kp"])
|
|
|
|
for row in rows:
|
|
profile = self._reach.profile(row.row())
|
|
table.append(
|
|
[profile.name, profile.kp]
|
|
)
|
|
|
|
self.copyTableIntoClipboard(table)
|
|
|
|
def paste(self):
|
|
header, data = self.parseClipboardTable()
|
|
|
|
if len(data) == 0:
|
|
return
|
|
|
|
if len(header) != 0:
|
|
header.append("reach")
|
|
for row in data:
|
|
row.append(self._reach)
|
|
|
|
row = self.index_selected_row()
|
|
self._tablemodel.paste(row, header, data)
|
|
self.select_current_profile()
|
|
|
|
def undo(self):
|
|
self._tablemodel.undo()
|
|
self.select_current_profile()
|
|
self.update_plot_xy()
|
|
self.update_plot_kpc()
|
|
|
|
def redo(self):
|
|
self._tablemodel.redo()
|
|
self.select_current_profile()
|
|
self.update_plot_xy()
|
|
self.update_plot_kpc()
|
|
|
|
def handleSave(self):
|
|
options = QFileDialog.Options()
|
|
DEFAULT_DIRECTORY = '/home/'
|
|
settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', )
|
|
current_dir = settings.value('current_directory', DEFAULT_DIRECTORY, type=str)
|
|
options |= QFileDialog.DontUseNativeDialog
|
|
|
|
filename, filters = QFileDialog.getSaveFileName(
|
|
self,
|
|
filter=_translate("MainWindow_reach",
|
|
"Files .ST(*.ST or *.st)")
|
|
+ ";; " +
|
|
_translate("MainWindow_reach", "All files "
|
|
"(*)"),
|
|
options=options
|
|
)
|
|
|
|
current_dir = os.path.split(filename)[0] or DEFAULT_DIRECTORY
|
|
|
|
if filename != '':
|
|
self._tablemodel.export_reach(filename)
|
|
|
|
def handleOpen(self):
|
|
filename, filterName = QFileDialog.getOpenFileName(self)
|
|
|
|
if filename != '':
|
|
with open(filename, 'r') as f:
|
|
reader = csv.reader(f, delimiter='\t')
|
|
header = next(reader)
|
|
buf = []
|
|
for row in reader:
|
|
row[0] = QCheckBox("-")
|
|
buf.append(row)
|
|
|
|
self._tablemodel = None
|
|
self._tablemodel = TableEditableModel(buf)
|
|
self.tableView.setModel(self._tablemodel)
|
|
filename = ''
|