Newer
Older
# MainWindow.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
from PyQt5.QtGui import (
QKeySequence,
)
Pierre-Antoine Rouby
committed
from PyQt5.QtCore import (
QTranslator, QEvent
)
QMainWindow, QApplication, QAction,
QFileDialog, QShortcut, QMenu, QToolBar,
from View.Tools.ASubWindow import WindowToolKit
from View.Tools.ListedSubWindow import ListedSubWindow
from View.DummyWindow import DummyWindow
from View.Configure.Window import ConfigureWindow
from View.Study.Window import NewStudyWindow
from View.About.Window import AboutWindow
from View.Network.Window import NetworkWindow
# from View.Geometry.Window import GeometryWindow
# from View.BoundaryCondition.Window import BoundaryConditionWindow
# from View.LateralContribution.Window import LateralContributionWindow
# from View.InitialConditions.Window import InitialConditionsWindow
# from View.Stricklers.Window import StricklersWindow
# from View.Frictions.Window import FrictionsWindow
# from View.SedimentLayers.Window import SedimentLayersWindow
# from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow
# from View.SolverParameters.Window import SolverParametersWindow
# from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow
# from View.CheckList.Window import CheckListWindow
# from View.Results.Window import ResultsWindow
from View.Debug.Window import ReplWindow
logger = logging.getLogger()
no_model_action = [
"action_menu_new", "action_menu_open", "action_menu_import_mage",
"action_menu_import_rubarbe", "action_toolBar_open",
]
model_action = [
"action_menu_close", "action_menu_edit", "action_menu_save",
"action_menu_save_as", "action_toolBar_close", "action_toolBar_save",
"action_menu_numerical_parameter",
]
other_model_action = [
"action_toolBar_run_solver", "action_toolBar_kill_solver"
]
define_model_action = [
"action_toolBar_network", "action_toolBar_geometry",
"action_toolBar_mesh", "action_toolBar_run_meshing_tool",
"action_toolBar_boundary_cond", "action_toolBar_lateral_contrib",
"action_toolBar_spills", "action_toolBar_frictions",
"action_toolBar_stricklers", "action_toolBar_building",
"action_menu_run_solver", "action_menu_numerical_parameter",
"action_menu_edit_network", "action_menu_edit_geometry",
"action_menu_boundary_conditions", "action_menu_initial_conditions",
"action_menu_edit_friction", "action_menu_edit_lateral_contribution",
"action_menu_run_solver", "action_menu_sediment_layers",
"action_menu_edit_reach_sediment_layers"
]
action = (
no_model_action + model_action + define_model_action
)
class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
def __init__(self, conf=None):
super(ApplicationWindow, self).__init__()
self._close_question = False
# App Configuration
self.conf = conf
self._study = None
# Results
self._last_results = None
self.ui = loadUi(
os.path.join(os.path.dirname(__file__), "ui", "MainWindow.ui"),
self.setup_sc()
self.setup_connection()
self.default_style()
Pierre-Antoine Rouby
committed
self.trans = QTranslator(self)
if self.conf.last_study != "" and not self.conf.close_correctly:
self.dialog_reopen_study()
title = "(dbg) " if self.conf.debug else ""
if self._study is not None:
title += f"Pamhyr2 - {self._study.name}"
self.setWindowTitle(title)
title += "Pamhyr2"
self.setWindowTitle(title)
def enable_actions(self, action:str, enable:bool):
"""Enable of disable an action componant
Args:
action: Action to enable/disable
enable: True to Enable, or False to disable
Returns:
Nothing
"""
self.findChild(QAction, action).setEnabled(enable)
def setup_sc(self):
# self._run_sc = QShortcut(QKeySequence("F5"), self)
return
def setup_connection(self):
"""Connect action to callback function
Returns:
Nothing
"""
actions = {
# Menu action
"action_menu_config": self.open_configure,
"action_menu_new": self.open_new_study,
"action_menu_edit": self.open_edit_study,
"action_menu_open": self.open_model,
"action_menu_save": self.save_study,
"action_menu_save_as": self.save_as_study,
"action_menu_numerical_parameter": self.open_solver_parameters,
"action_menu_edit_network": self.open_network,
"action_menu_edit_geometry": self.open_geometry,
"action_menu_boundary_conditions": self.open_boundary_cond,
"action_menu_initial_conditions": self.open_initial_conditions,
"action_menu_edit_friction": self.open_frictions,
"action_menu_edit_lateral_contribution": self.open_lateral_contrib,
"action_menu_run_solver": self.run_solver,
"action_menu_sediment_layers": self.open_sediment_layers,
"action_menu_edit_reach_sediment_layers": self.open_reach_sediment_layers,
## Help
"action_menu_about": self.open_about,
"action_toolBar_quit": self.close,
"action_toolBar_open": self.open_model,
"action_toolBar_save": self.save_study,
"action_toolBar_close": self.close_model,
"action_toolBar_run_solver": self.run_solver,
"action_toolBar_network": self.open_network,
Pierre-Antoine Rouby
committed
"action_toolBar_geometry": self.open_geometry,
"action_toolBar_mesh": lambda: self.open_dummy("Mesh"),
"action_toolBar_run_meshing_tool": self.open_solver_parameters,
"action_toolBar_boundary_cond": self.open_boundary_cond,
"action_toolBar_lateral_contrib": self.open_lateral_contrib,
"action_toolBar_spills": lambda: self.open_dummy("Deversement"),
"action_toolBar_stricklers": self.open_stricklers,
"action_toolBar_frictions": self.open_frictions,
"action_toolBar_building": lambda: self.open_dummy("Ouvrages"),
"action_toolBar_initial_cond": self.open_initial_conditions,
}
for action in actions:
logger.debug("Setup connection : " + action)
self.findChild(QAction, action)\
.triggered.connect(actions[action])
# action.triggered.connect(actions[action])
# self._run_sc.activated.connect(self.run_solver)
Pierre-Antoine Rouby
committed
def changeEvent(self, event):
if event.type() == QEvent.LanguageChange:
self.retranslateUi()
super(ApplicationWindow, self).changeEvent(event)
if self._study is not None and not self._study.is_saved:
self._close_question = True
if self.dialog_close():
# PAMHYR is close correctly (no crash)
self.conf.set_close_correctly()
super(ApplicationWindow, self).close()
else:
self._close_question = False
else:
# PAMHYR is close correctly (no crash)
self.conf.set_close_correctly()
super(ApplicationWindow, self).close()
def closeEvent(self, event):
if not self._close_question:
if self._study is not None and not self._study.is_saved:
if self.dialog_close(cancel = False):
# PAMHYR is close correctly (no crash)
self.conf.set_close_correctly()
super(ApplicationWindow, self).closeEvent(event)
else:
super(ApplicationWindow, self).closeEvent(event)
def default_style(self):
"""Set default window style
Returns:
Nothing
"""
self.update_enable_action()
# Maximise window
self.showMaximized()
def set_debug_lvl(self, debug = True):
if debug:
logger.setLevel(logging.DEBUG)
logger.info("Set logging level to DEBUG")
else:
logger.setLevel(logging.INFO)
logger.info("Set logging level to INFO")
def setup_debug_mode(self, init = False):
menu = self.findChild(QMenu, "menu_help")
if init:
self.debug_action = QAction("Debug", self)
self.debug_action.setStatusTip("Debug")
self.debug_action.triggered.connect(self.open_debug)
self.debug_sqlite_action = QAction("Debug SQLite", self)
self.debug_sqlite_action.setStatusTip("Open SQLite debuging tool (sqlitebrowser)")
self.debug_sqlite_action.triggered.connect(self.open_sqlite)
if self.conf.debug:
menu.addAction(self.debug_action)
menu.addAction(self.debug_sqlite_action)
self.set_debug_lvl(debug = True)
else:
if self.conf.debug:
menu.addAction(self.debug_action)
menu.addAction(self.debug_sqlite_action)
self.set_debug_lvl(debug = True)
else:
menu.removeAction(self.debug_action)
menu.removeAction(self.debug_sqlite_action)
self.set_debug_lvl(debug = False)
#########
# MODEL #
#########
def get_model(self):
return self._study
def set_model(self, model):
self._study = model
self.update_enable_action()
self.conf.set_last_study(self._study.filename)
def close_model(self):
self._study = None
self.update_enable_action()
self.conf.set_close_correctly()
def update_enable_action(self):
"""Update status of action componante
Update status of action componant, enable or disable in
function of model state
Returns:
Nothing
"""
no_model = self._study is None
for action in no_model_action:
self.enable_actions(action, no_model)
for action in define_model_action + other_model_action:
self.enable_actions(action, not no_model)
for action in model_action:
self.enable_actions(action, not no_model)
def set_results(self, results):
self._last_results = results
############
# FEATURES #
############
def open_study(self, filename):
"""Open a study
Args:
filename: The study path
Returns:
Nothing
"""
self.set_model(Study.open(filename))
logger.info(f"Open Study - {self._study.name}")
self.set_title()
def save_study(self):
"""Save current study
Save current study, if study as no associate file, open a
file dialog.
Returns:
Nothing
"""
if self._study.filename is None or self._study.filename == "":
file_name, _ = QFileDialog.getSaveFileName(
self, "Save File",
"", "Pamhyr(*.pamhyr)"
if file_name.rsplit(".", 1)[-1] == "pamhyr":
self._study.filename = file_name
self._study.filename = file_name + ".pamhyr"
if self._study.is_saved:
logger.info("Save...")
self._study.save()
def save_as_study(self):
"""Save current study as new file
Save current study as new file, if study as no associate file,
open a file dialog.
Returns:
Nothing
"""
file_name, _ = QFileDialog.getSaveFileName(
self, "Save File",
"", "Pamhyr(*.pamhyr)"
if file_name[-4:] == ".pamhyr":
self._study.filename = file_name
self._study.filename = file_name + ".pamhyr"
self._study.save()
##################
# MSG AND DIALOG #
##################
def msg_select_reach(self):
self.message_box("Please select a reach",
"Geometry edition need a reach selected "
"into river network window to work on it")
def dialog_reopen_study(self):
dlg = QMessageBox(self)
dlg.setWindowTitle("Last open study")
dlg.setText("Do you want to open again the last open study?")
opt = QMessageBox.Cancel | QMessageBox.Ok #| QMessageBox.Open
dlg.setStandardButtons(opt)
dlg.setIcon(QMessageBox.Question)
res = dlg.exec()
if res == QMessageBox.Ok:
self.open_study(self.conf.last_study)
return True
elif res == QMessageBox.Open:
self.open_model()
return True
elif res == QMessageBox.Cancel:
return False
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
def dialog_close(self, cancel = True):
dlg = QMessageBox(self)
dlg.setWindowTitle("Close PAMHYR without saving study")
dlg.setText("Do you want to save current study before PAMHYR close ?")
opt = QMessageBox.Save | QMessageBox.Ignore
if cancel:
opt |= QMessageBox.Cancel
dlg.setStandardButtons(opt)
dlg.setIcon(QMessageBox.Warning)
res = dlg.exec()
if res == QMessageBox.Save:
self.save_study()
return True
elif res == QMessageBox.Ignore:
return True
elif res == QMessageBox.Cancel:
return False
#############
# SUBWINDOW #
#############
def open_configure(self):
"""Open configure window
Open PamHyr configure window
Returns:
Nothing
"""
self.config = ConfigureWindow(config=self.conf, parent=self)
self.config.show()
def open_about(self):
"""Open about window
Open a new window with information about PamHyr
Returns:
Nothing
"""
self.about = AboutWindow(parent=self)
self.about.show()
def open_model(self):
"""Open file dialog to select saved model
Returns:
Nothing
"""
if self._study is None:
dialog = QFileDialog(self)
dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
dialog.setDefaultSuffix(".pamhyr")
#dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
dialog.setNameFilters(['PamHyr (*.pamhyr)'])
dialog.setDirectory(os.path.dirname(self.conf.last_study))
if dialog.exec_():
file_name = dialog.selectedFiles()
self.open_study(file_name[0])
def open_new_study(self):
"""Open dialog to set new study
Returns:
Nothing
"""
if self._study is None:
self.new_study = NewStudyWindow(parent=self)
self.new_study.show()
def open_edit_study(self):
"""Open dialog to set new study
Returns:
Nothing
"""
if not self._study is None:
self.new_study = NewStudyWindow(study=self._study, parent=self)
def open_network(self):
"""Open network dialog
Returns:
Nothing
"""
if self._study is not None:
if not self.sub_win_exists("River network"):
self.network = NetworkWindow(study=self._study, parent=self)
self.network.show()
else:
self.network.activateWindow()
Pierre-Antoine Rouby
committed
def open_geometry(self):
"""Open geometry window
Returns:
Nothing
"""
if (self._study is not None and self._study.river.has_current_reach()):
geometry = self.sub_win_filter_first(
"Geometry",
contain = [self._study.river.current_reach().name]
geometry = GeometryWindow(model=self._study, parent=self)
geometry.show()
else:
geometry.activateWindow()
self.msg_select_reach()
Pierre-Antoine Rouby
committed
def open_boundary_cond(self):
bound = self.sub_win_filter_first(
"Boundary conditions",
contain = []
)
if bound is None:
bound = BoundaryConditionWindow(study = self._study, parent = self)
bound.show()
else:
bound.activateWindow()
def open_lateral_contrib(self):
lateral = self.sub_win_filter_first(
"Lateral contribution",
contain = []
)
if lateral is None:
lateral = LateralContributionWindow(study = self._study, parent = self)
lateral.show()
else:
lateral.activateWindow()
strick = self.sub_win_filter_first(
"Stricklers",
contain = []
if strick is None:
strick = StricklersWindow(
study = self._study,
config = self.conf,
parent = self
)
strick.show()
else:
strick.activateWindow()
def open_frictions(self):
if (self._study is not None and
self._study.river.has_current_reach()):
frictions = self.sub_win_filter_first(
"Frictions",
contain = [self._study.river.current_reach().name]
if frictions is None:
frictions = FrictionsWindow(
study = self._study,
parent = self
)
frictions.show()
else:
frictions.activateWindow()
else:
self.msg_select_reach()
def open_initial_conditions(self):
if self._study.river.has_current_reach():
initial = self.sub_win_filter_first(
"Initial condition",
contain = [self._study.river.current_reach().name]
if initial is None:
initial = InitialConditionsWindow(
study = self._study,
parent = self
)
initial.show()
else:
initial.activateWindow()
else:
self.msg_select_reach()
def open_solver_parameters(self):
params = self.sub_win_filter_first(
"Solver parameters",
contain = []
if params is None:
params = SolverParametersWindow(
study = self._study,
parent = self
)
params.show()
else:
params.activateWindow()
sl = SedimentLayersWindow(
study = self._study,
parent = self
)
sl.show()
def open_reach_sediment_layers(self):
sl = ReachSedimentLayersWindow(
study = self._study,
parent = self
)
sl.show()
if self._study is None:
run = SelectSolverWindow(
study = self._study,
Pierre-Antoine Rouby
committed
config = self.conf,
Pierre-Antoine Rouby
committed
if run.exec():
solver = run.solver
study = self._study,
config = self.conf,
solver = solver,
parent = self
)
check.show()
def solver_log(self, solver):
sol = SolverLogWindow(
study = self._study,
config = self.conf,
solver = solver,
parent = self
)
sol.show()
def open_solver_results(self, solver, results = None):
# If no specific results, get last results
if results is None:
results = self._last_results
# No results available
if results is None:
return
# Windows already opened
res = self.sub_win_filter_first(
"Results",
contain = [solver.name, results.date]
)
if res is None:
res = ResultsWindow(
study = self._study,
solver = solver,
results = results,
parent = self
)
res.show()
else:
res.activateWindow()
#########
# DEBUG #
#########
def open_debug(self):
repl = ReplWindow(
study = self._study,
config = self.conf,
parent = self
)
repl.show()
if self._study is None:
logger.debug("No study open for sql debuging...")
return
file = self._study.filename
_ = subprocess.Popen(
f"sqlitebrowser {file}",
shell=True
)
# TODO: Delete me !
###############
# DUMMY STUFF #
###############
def open_dummy(self, title="Dummy"):
self.dummy = DummyWindow(
title=title if type(title) is str else "Dummy",
parent=self
)
self.dummy.show()