Compare commits

..
78 Commits
Author SHA1 Message Date
Pierre-Antoine Rouby 23e36ca3f7 ci: Fix pipeline release job. 2023-09-20 09:31:54 +02:00
Pierre-Antoine Rouby e92e00f5c8 ci: Disable tests. 2023-09-19 17:37:49 +02:00
Pierre-Antoine Rouby 0d547cc469 Scripts: Fix return value. 2023-09-19 17:25:30 +02:00
Pierre-Antoine Rouby 973e6c8080 ci: Fix dependencies. 2023-09-19 17:08:20 +02:00
Pierre-Antoine Rouby 5f6154c510 ci: tests: Add test-linux and test-windows. 2023-09-19 17:05:15 +02:00
Pierre-Antoine Rouby 2cad6f21ab ci: Add running script for GNU/Linux. 2023-09-19 16:46:02 +02:00
Pierre-Antoine Rouby 41a56c6e77 ci: Fix Linux build. 2023-09-19 16:17:19 +02:00
Pierre-Antoine Rouby 487045dbad ci: Add doc. 2023-09-19 16:07:48 +02:00
Pierre-Antoine Rouby c12c06a604 Merge branch 'doc' into refactoring. 2023-09-19 15:56:53 +02:00
Pierre-Antoine Rouby 6380e3110f doc: Add temporary water mark. 2023-09-19 15:55:37 +02:00
Pierre-Antoine Rouby 3fdc0349da SL: Fix tables translate. 2023-09-19 15:29:42 +02:00
Pierre-Antoine Rouby 0691c64ca9 IC: Fix table translate. 2023-09-19 15:23:04 +02:00
Pierre-Antoine Rouby f4545b0075 LC: Fix table translate. 2023-09-19 15:19:06 +02:00
Pierre-Antoine Rouby ecefb39265 BC: Fix table translate. 2023-09-19 15:13:21 +02:00
Pierre-Antoine Rouby 95288e6eef Friction: Fix talbe translate. 2023-09-19 15:02:33 +02:00
Pierre-Antoine Rouby ffebfbdddc Stricklers: Switch stricklers table to PamhyrTableModel and fix translate. 2023-09-19 14:59:47 +02:00
Pierre-Antoine Rouby 4f471d4d6f Script: Add app in script parameters. 2023-09-19 14:41:05 +02:00
Pierre-Antoine Rouby f5046858c8 trans: Fix Network and Results table translate. 2023-09-19 14:40:04 +02:00
Pierre-Antoine Rouby 47bcf8a748 trans: Update name PAMHYR to Pamhyr2 and minor change. 2023-09-19 14:06:14 +02:00
Pierre-Antoine Rouby 694c84a526 Merge branch 'master' into refactoring. 2023-09-19 13:55:16 +02:00
Pierre-Antoine Rouby c567a0b370 Network: Fix edge selection crash. 2023-09-19 11:56:08 +02:00
Pierre-Antoine Rouby a14dfba6b4 doc: dev: Add solver pipeline graph. 2023-09-18 17:25:21 +02:00
Pierre-Antoine Rouby ed006fe1c3 doc: dev: Add archi and model graph. 2023-09-18 16:49:51 +02:00
Pierre-Antoine Rouby a733e148e2 doc: dev: Complete contribution. 2023-09-18 15:03:30 +02:00
Pierre-Antoine Rouby 77c0dddee3 doc: tools: LaTeX class minor change. 2023-09-18 14:19:18 +02:00
Pierre-Antoine Rouby 8429d04b3a doc: dev: Continue contribution section. 2023-09-18 11:58:03 +02:00
Pierre-Antoine Rouby 4c953d29fe doc: dev: Minor change. 2023-09-18 10:39:03 +02:00
Pierre-Antoine Rouby 129e3f3f88 doc: dev: Add abstract. 2023-09-18 10:36:50 +02:00
Pierre-Antoine Rouby 436f4adaf6 Doc: Add GPL logo and minor change. 2023-09-18 09:55:53 +02:00
Pierre-Antoine Rouby 9f9dd508b1 doc: dev: Add install command for texlive. 2023-09-15 16:24:26 +02:00
Pierre-Antoine Rouby d772a44c67 doc: tools, dev: Add caption and box for verbatim. 2023-09-15 16:19:46 +02:00
Pierre-Antoine Rouby 100f98c56b doc: dev: Finish the documentations files section. 2023-09-15 16:09:10 +02:00
Pierre-Antoine Rouby 445de3b777 doc: dev: Introduction + minor change. 2023-09-15 15:29:03 +02:00
Pierre-Antoine Rouby 1c7ddea417 ci: Fix pipeline. 2023-09-13 15:40:40 +02:00
Pierre-Antoine Rouby ac756ec41e ci: Fix linux package. 2023-09-13 15:36:39 +02:00
Pierre-Antoine Rouby d33d6b166f ci: Some minor fix. 2023-09-13 14:05:13 +02:00
Pierre-Antoine Rouby 1384634aa1 ci: Fix windows exe script. 2023-09-13 13:24:58 +02:00
Pierre-Antoine Rouby 906b7f2d39 ci: Minor fix. 2023-09-13 13:02:50 +02:00
Pierre-Antoine Rouby bb662346fa ci: Minor change. 2023-09-13 12:21:08 +02:00
Pierre-Antoine Rouby efff8fe4dc ci: Minor fix. 2023-09-13 12:00:28 +02:00
Pierre-Antoine Rouby fe6cafba47 ci: Minor change. 2023-09-13 11:51:35 +02:00
Pierre-Antoine Rouby ab3a006e65 ci: Add windows script. 2023-09-13 11:45:29 +02:00
Pierre-Antoine Rouby 5197bce872 ci: Minor change of debug ci. 2023-09-13 11:37:45 +02:00
Pierre-Antoine Rouby 00b4292306 ci: Minor change. 2023-09-13 11:12:04 +02:00
Pierre-Antoine Rouby ace5053c1b ci: Fix circular dependencies. 2023-09-13 10:28:31 +02:00
Pierre-Antoine Rouby 24aebfbcf8 ci: Merge build and dir creation. 2023-09-13 10:26:47 +02:00
Pierre-Antoine Rouby 5d6ae31ee9 ci: Refactoring CI pipeline. 2023-09-13 09:42:47 +02:00
Pierre-Antoine Rouby c59edf4661 Script: Help: Minor change. 2023-09-12 16:16:47 +02:00
Pierre-Antoine Rouby 029e8bb210 Script: Help: Specify the debug mode is for developers. 2023-09-12 16:14:30 +02:00
Pierre-Antoine Rouby 1fc8540bf8 Mainwindow: Display debug in main window title. 2023-09-12 16:11:27 +02:00
Pierre-Antoine Rouby 0c0ac93061 Script: Add gui, help and debug commands. 2023-09-12 16:02:51 +02:00
Pierre-Antoine Rouby 4debe830b6 Script: Add config in script init parameter. 2023-09-12 15:44:42 +02:00
Pierre-Antoine Rouby 4de6784bec Pamhyr: Add 'gui' as dummy script. 2023-09-12 15:39:20 +02:00
Pierre-Antoine Rouby d4568ddbb5 Script: Add pamhyr2 script with exemple hello. 2023-09-12 13:04:27 +02:00
Pierre-Antoine Rouby 75066fa61a Plot: Fix missin import. 2023-09-12 12:57:28 +02:00
Pierre-Antoine Rouby e887df9aac Pamhyr: Add init.py. 2023-09-12 11:24:06 +02:00
Pierre-Antoine Rouby 1662ff1e24 git: Update the README with sections 'features' and 'install'. 2023-09-12 11:00:01 +02:00
Pierre-Antoine Rouby cbc7296132 git: Add editorconfig. 2023-09-12 10:43:08 +02:00
Pierre-Antoine Rouby 8004fbcdcc Solver, View: Add some comment. 2023-09-12 10:42:51 +02:00
Pierre-Antoine Rouby bb6292ce90 View: Plot: Minor change. 2023-09-11 16:27:29 +02:00
Pierre-Antoine Rouby af72e59830 Plot: Toolbar: Rename file and class. 2023-09-11 16:23:33 +02:00
Pierre-Antoine Rouby 45690690d1 View: BC, LC: Factorise time delegate and widget. 2023-09-11 15:49:10 +02:00
Pierre-Antoine Rouby 602593a6cb View: LC: Use PamhyrTableModel. 2023-09-11 15:20:59 +02:00
Pierre-Antoine Rouby 650df2b56c View: BC: Use PamhyrTableModel. 2023-09-11 14:57:55 +02:00
Pierre-Antoine Rouby c986674306 View: Network: Add missing tranlate file. 2023-09-11 14:43:07 +02:00
Pierre-Antoine Rouby 3eeaea14fa View: Tools: Add PamhyrTableModel and switch Network tables on it. 2023-09-11 14:13:40 +02:00
Pierre-Antoine Rouby 4b9f75b8d1 Tools: SQL: Minor change. 2023-09-11 14:13:19 +02:00
Pierre-Antoine Rouby 2139b42b77 Model: Tools: List, Dict: Minor change. 2023-09-11 14:12:49 +02:00
Pierre-Antoine Rouby 8c9478f403 Model: Delete unsed file. 2023-09-11 11:41:32 +02:00
Pierre-Antoine Rouby 973ea74622 Model: Move Model.DB into Model.Tools.PamhyrDB. 2023-09-11 11:40:26 +02:00
Pierre-Antoine Rouby e63b931dfb Model: Add PamhyrModelDict and use it in IC. 2023-09-11 11:29:18 +02:00
Pierre-Antoine Rouby 0b0c402baf Model: Add PamhyrModelListWithTab. Use it in BC and LC. 2023-09-11 11:20:08 +02:00
Pierre-Antoine Rouby 3aea790829 Model: Switch SedimentLayerList to PamhyrModelList. 2023-09-11 11:06:54 +02:00
Pierre-Antoine Rouby 460683703e Model: Switch SolverParametersList to PamhyrModelList. 2023-09-11 10:58:29 +02:00
Pierre-Antoine Rouby 89b9fd21ff Model: Add a Pamhyr List Abstract implementation and use it for
Stricklers and friction.
2023-09-11 10:53:58 +02:00
Pierre-Antoine Rouby 187135e62b doc: dev: minor change. 2023-09-11 09:31:11 +02:00
Pierre-Antoine Rouby f3ec60500d doc: Update dev doc with some org-mode tips. 2023-09-08 18:43:42 +02:00
Pierre-Antoine Rouby ae857a098a doc: Add user and developers documentation scheme. 2023-09-08 17:41:40 +02:00
96 changed files with 2726 additions and 1385 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# Pamhyr .editorconfig
root = true
[*.py]
charset = utf-8
indent_style = space
indent_size = 4
tab_width = 8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
+249 -115
View File
@@ -15,96 +15,55 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
stages:
- downloads
- build
- test
- package
- release
#############
# DOWNLOADS #
#############
dl-mage-linux:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_linux.tgz
- mkdir -p mage-linux
- cd mage-linux
- tar xvf ../mage.tgz
artifacts:
paths:
- mage-linux/mage
- mage-linux/mage_extraire
- mage-linux/mailleurPF
dl-mage-windows:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_windows.tgz
- mkdir -p mage-windows
- cd mage-windows
- tar xvf ../mage.tgz
artifacts:
paths:
- mage-windows/mage.exe
- mage-windows/mage_extraire.exe
- mage-windows/mailleurPF.exe
#########
# BUILD #
#########
build-mage-linux:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_linux.tgz
- mkdir -p mage
- cd mage
- tar xvf ../mage.tgz
artifacts:
paths:
- mage/mage
- mage/mage_extraire
- mage/mailleurPF
# build-mage-linux:
# stage: build
# tags:
# - linux
# rules:
# - if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
# variables:
# GIT_SUBMODULE_STRATEGY: recursive
# GIT_SUBMODULE_DEPTH: 20
# script:
# - cd mage/src/
# - mkdir -p build
# - cd build
# - cmake ..
# - make
# artifacts:
# paths:
# - mage/src/build/mage
# - mage/src/build/mage_extraire
# - mage/src/build/mailleurPF
build-mage-windows:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_windows.tgz
- mkdir -p mage
- cd mage
- tar xvf ../mage.tgz
artifacts:
paths:
- mage/mage.exe
- mage/mage_extraire.exe
- mage/mailleurPF.exe
# build-mage-windows:
# stage: build
# tags:
# - wine
# rules:
# - if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
# variables:
# GIT_SUBMODULE_STRATEGY: recursive
# GIT_SUBMODULE_DEPTH: 20
# script:
# - cd mage/src/
# - sed s/add_compile_definitions(win)/add_compile_definitions(windows)/ CMakeLists.txt > __tmp__
# - echo set(CMAKE_Fortran_COMPILER "C:/Program\ Files/gcc/bin/gfortran.exe") > CMakeLists.txt
# - echo set(CMAKE_MAKE_PROGRAM "C:/Program Files\ \(x86\)/GnuWin32/bin/make.exe") >> CMakeLists.txt
# - type __tmp__ >> CMakeLists.txt
# - mkdir build
# - cd build
# - cmake -G "MinGW Makefiles" ..
# - make
# artifacts:
# paths:
# - mage/src/build/mage.exe
# - mage/src/build/mage_extraire.exe
# - mage/src/build/mailleurPF.exe
build:
set-version:
stage: build
tags:
- linux
@@ -126,60 +85,214 @@ build-lang:
paths:
- src/lang/*.qm
build-users-doc:
stage: build
tags:
- linux
script:
- cd ./doc/users/
- ./build.sh
artifacts:
paths:
- doc/users/documentation.pdf
build-developers-doc:
stage: build
tags:
- linux
script:
- cd ./doc/dev/
- ./build.sh
artifacts:
paths:
- doc/dev/documentation.pdf
build-linux:
stage: build
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
needs:
- job: dl-mage-linux
artifacts: true
- job: set-version
artifacts: true
- job: build-lang
artifacts: true
- job: build-developers-doc
artifacts: true
- job: build-users-doc
artifacts: true
script:
- mkdir -p linux
- cd linux
# Setup virtual env
- python3 -m venv linux-venv
- . linux-venv/bin/activate
- pip3 install -r ../requirements.txt
# Run Pyinstaller
- pyinstaller -y --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
# Create directory
- mkdir -p pamhyr
- mkdir -p pamhyr/pamhyr
# Copy Pamhyr info
- cp -v ../VERSION pamhyr/pamhyr/
- cp -v ../AUTHORS pamhyr/pamhyr/
- cp -v ../LICENSE pamhyr/pamhyr/
# Copy mage
- mkdir -p pamhyr/mage
- cp -v ../mage-linux/* pamhyr/mage/
# Copy Pamhyr
- cp -r dist/pamhyr pamhyr/
# Copy Pamhyr resources
- mkdir -p pamhyr/pamhyr/View
- mkdir -p pamhyr/pamhyr/View/ui
- mkdir -p pamhyr/pamhyr/View/ui/Widgets
- cp -r ../src/View/ui/*.ui pamhyr/pamhyr/View/ui/
- cp -r ../src/View/ui/ressources/ pamhyr/pamhyr/View/ui
- cp -r ../src/View/ui/Widgets/*.ui pamhyr/pamhyr/View/ui/Widgets
# Copy lang
- mkdir -p pamhyr/pamhyr/lang
- cp -r ../src/lang/*.qm pamhyr/pamhyr/lang
# Copy tests cases
- mkdir -p pamhyr/tests_cases/
- mkdir -p pamhyr/tests_cases/Saar
- cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/
# Copy documentations
- mkdir -p pamhyr/doc/
- cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf
- cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf
# Create running script
- echo "#!/bin/sh" > pamhyr/Pamhyr2
- echo "./pamhyr/pamhyr $@" >> pamhyr/Pamhyr2
- chmod u+x pamhyr/Pamhyr2
artifacts:
paths:
- linux/pamhyr
build-windows:
stage: build
tags:
- wine
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
needs:
- job: dl-mage-windows
artifacts: true
- job: set-version
artifacts: true
- job: build-lang
artifacts: true
- job: build-developers-doc
artifacts: true
- job: build-users-doc
artifacts: true
script:
- mkdir windows
- cd windows
# Setup env
- python -m pip install -r ..\requirements.txt
# Run Pyinstaller
- pyinstaller -y ..\src\pamhyr.py
# Create directory
- mkdir pamhyr
- dir
- copy ..\packages\make-windows-dir.bat .\make-windows-dir.bat
- .\make-windows-dir.bat
artifacts:
paths:
- windows/pamhyr
#########
# TESTS #
#########
test:
stage: test
script:
- echo "TODO PAMHYR tests"
# test-windows:
# stage: test
# tags:
# - wine
# needs:
# - job: set-version
# artifacts: true
# - job: build-windows
# artifacts: true
# script:
# - cd windows\pamhyr
# - pamhyr\pamhyr.exe hello
# test-linux:
# stage: test
# tags:
# - linux
# needs:
# - job: set-version
# artifacts: true
# - job: build-linux
# artifacts: true
# script:
# - cd linux/pamhyr
# - ./Pamhyr2 hello
############
# PACKAGES #
############
linux-package:
linux-package-tar:
stage: package
tags:
- release
- linux
needs:
- job: build-lang
artifacts: true
- job: build
artifacts: true
- job: build-mage-linux
# - job: test-linux
- job: build-linux
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-gnulinux-amd64.tar.xz
- linux/pamhyr-gnulinux.tar.xz
script:
- cd packages
- ./linux.sh
- cd linux
- tar --xz -cf pamhyr-gnulinux.tar.xz ./pamhyr --checkpoint=.100
windows-package:
windows-package-zip:
stage: package
tags:
- release
- linux
needs:
# - job: test-windows
- job: build-windows
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
artifacts:
paths:
- windows/pamhyr-windows.zip
script:
- cd windows
- zip -r pamhyr-windows.zip pamhyr
windows-package-exe:
stage: package
tags:
- release
- wine
needs:
- job: build-lang
artifacts: true
- job: build
artifacts: true
- job: build-mage-windows
# - job: test-windows
- job: build-windows
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-win-amd64.exe
- windows/pamhyr-windows.exe
script:
- cd packages
- ./windows.bat
- cd windows
- copy ..\packages\make-windows-exe.bat make-windows-exe.bat
- copy ..\packages\pamhyr.nsi pamhyr.nsi
- .\make-windows-exe.bat
###########
# RELEASE #
@@ -191,16 +304,25 @@ tag-release:
- release
- linux
needs:
- job: linux-package
- job: linux-package-tar
artifacts: true
- job: windows-package
- job: windows-package-zip
artifacts: true
- job: windows-package-exe
artifacts: true
- job: build-users-doc
artifacts: true
- job: build-developers-doc
artifacts: true
rules:
- if: $CI_COMMIT_TAG
artifacts:
paths:
- packages/pamhyr-gnulinux-amd64.tar.xz
- packages/pamhyr-win-amd64.exe
- linux/pamhyr-gnulinux.tar.xz
- windows/pamhyr-windows.zip
- windows/pamhyr-windows.exe
- doc/dev/documentation.pdf
- doc/users/documentation.pdf
script:
- cd packages
release:
@@ -210,11 +332,23 @@ tag-release:
ref: '$CI_COMMIT_SHA'
assets:
links:
- name: 'GNU/Linux amd64 (tar.xz)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-gnulinux-amd64.tar.xz'
filepath: '/packages/pamhyr-gnulinux-amd64.tar.xz'
- name: 'GNU/Linux (tar.xz)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/linux/pamhyr-gnulinux.tar.xz'
filepath: '/linux/pamhyr-gnulinux.tar.xz'
link_type: 'package'
- name: 'Windows amd64 (exe)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-win-amd64.exe'
filepath: '/packages/pamhyr-win-amd64.exe'
- name: 'Windows (exe)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.exe'
filepath: '/windows/pamhyr-windows.exe'
link_type: 'package'
- name: 'Windows (zip)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.zip'
filepath: '/windows/pamhyr-windows.zip'
link_type: 'package'
- name: 'User documentations (pdf)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/users/documentation.pdf'
filepath: '/doc/users/documentation.pdf'
link_type: 'other'
- name: 'Dev documentations (pdf)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/doc/dev/documentation.pdf'
filepath: '/doc/dev/documentation.pdf'
link_type: 'other'
-1
View File
@@ -47,5 +47,4 @@ pip3 install -r requirements.txt
# Run pamhyr
./src/pamhyr.py
```
+10
View File
@@ -0,0 +1,10 @@
#! /bin/sh
# Build the PDF
emacs -Q -q -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-latex-export-to-pdf
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
+551
View File
@@ -0,0 +1,551 @@
# documentation.org -- Pamhyr developers documentation
# 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 -*-
#+STARTUP: indent
#+INCLUDE: ../tools/macro.org
#+INCLUDE: ../tools/latex.org
#+TITLE: Developers documentation
#+SUBTITLE: Version: {{{version}}}
#+AUTHOR: {{{INRAE}}}
#+OPTIONS: toc:t
#+LANGUAGE: UKenglish
#+NAME: attr_wrap
#+HEADER: :var width="\\textwidth"
#+HEADER: :var caption=""
#+HEADER: :var smallcaption=""
#+HEADER: :var name=""
#+HEADER: :var data=""
#+HEADER: :var float="nil"
#+BEGIN_SRC sh :results output :exports none
echo "#+CAPTION[$smallcaption]: $caption"
echo "#+NAME: $name"
echo "#+ATTR_LATEX: :width $width :float $float"
echo "$data"
#+END_SRC
#+BEGIN_abstract
This document is for the use of developers. It describes the project
architecture, the tools available to assist development and
debugging. It also describes the procedures for creating packages and
the configurations required to set up the gitlab runners. Finally,
this document explains how documentation is written and modified, and
how to contribute to the project by modifying, improving or adding
documentation, translations or code.
#+END_abstract
* Introduction
Pamhyr2 is free and open source software (FOSS) graphical user
interface (GUI) for 1D hydro-sedimentary modelling of rivers developed
in Python (with version 3.8). It use PyQt at version 5 and matplotlib
in version 3.4.1 or later for the user insterface (see
{{{file(/requirements.txt)}}} for details). The architecture of
project code follow the Qt Model/View architecture [fn:qt-arch] (see
details in section [[Architecture]]). Pamhyr2 packages can be build
manually (see section [[Building packages]]), but there are automatically
build with the gitlab-ci (see the section [[Setup the CI
environment]]). Documentation files are written with org-mode[fn:org],
let see section [[Documentation files]]. Finally, to see the contribution
rules, see the section [[How to contribute?]].
[fn:qt-arch] Qt Model/View documentation:
https://doc.qt.io/qt-5/model-view-programming.html (last access
2023-09-15)
[fn:org] The org-mode website: https://orgmode.org/ (last access
2023-09-15)
* TODO Architecture
Pamhyr2 architecture is based on Qt Model/View programming[fn:qt-mv] architecture.
#+name: graph-pipeline
#+header: :results drawer
#+header: :exports results
#+header: :post attr_wrap(width="12cm", data=*this*, name="graph-pipeline", caption="Pamhyr2 solver execution pipeline architecture scheme", float="t")
#+begin_src dot :file "images/graph-pipeline.png" :cache no
digraph {
node[colorscheme=set19,shape=box,style="filled",fillcolor=9];
subgraph cluster0 {
label="Pamhyr2"
config[label="Config",fillcolor=5];
model[label="Model",fillcolor=2];
obj[label="Solver",fillcolor=6];
results[label="Results",fillcolor=2];
view[label="View",fillcolor=1];
results -> model[style="dotted"];
results -> obj[style="dotted"];
}
config -> obj[label=""];
obj -> model;
subgraph cluster1{
label="System";
in[label="Solver input files",shape=note];
out[label="Solver output files",shape=note];
bin[label="Solver binary",shape=note];
}
obj -> in[label="Write"];
bin -> in[label="Read"];
obj -> bin[label="Execute"];
bin -> out[label="Write"];
obj -> results[label="Create"];
results -> out[label="Read"];
view -> model;
view -> results;
}
#+end_src
#+name: graph-architecture
#+header: :results drawer
#+header: :exports results
#+header: :post attr_wrap(width="7cm", data=*this*, name="graph-architecture", caption="Pamhyr2 Model/View architecture scheme (inspired by Qt Model/View architecture [[https://doc.qt.io/qt-5/model-view-programming.html]])", float="t")
#+begin_src dot :file "images/graph-architecture.png" :cache no
digraph {
node[colorscheme=set19,shape=box,style="filled",fillcolor=white];
save[label="Pamhyr save",fillcolor="9",shape=note];
model[label="Model",fillcolor="2"];
view[label="View",fillcolor="1"];
delegate[label="Delegate",fillcolor="3"];
undocommand[label="UndoCommand",fillcolor="4"];
user[label="User",shape=ellipse];
model -> save[label="Save"];
save -> model[label="Load"];
model -> view[label="Rendering"];
view -> delegate[label="Rendering"];
delegate -> undocommand[label="Create"];
undocommand -> model[label="Modify"]
view -> user[label="Vizualize"];
user -> delegate[label="Modify"];
}
#+end_src
#+name: graph-model
#+header: :results drawer
#+header: :exports results
#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 Model architecture scheme", float="t")
#+begin_src dot :file "images/graph-model.png" :cache no
digraph {
node[colorscheme=set19,shape=box,style="filled",fillcolor="2"];
study[label="Study"];
river[label="River"];
subgraph cluster00 {
label="Network"
rnode[label="RiverNode"];
redge[label="RiverReach"];
}
frictionlist[label="FrictionList"];
subgraph cluster01 {
label="Stricklers";
stricklers[label="Stricklers"];
stricklerslist[label="StricklersList"];
}
subgraph cluster02 {
label="BoundaryCondition";
boundaryconditionlist[label="BoundaryConditionList"];
boundarycondition[label="BoundaryCondition"];
}
subgraph cluster03 {
label="LateralContribution";
lateralcontributionlist[label="LateralContributionList"];
lateralcontribution[label="LateralContribution"];
}
subgraph cluster04 {
label="InitialConditions";
initialconditionsdict[label="InitialConditionsDict"];
initialconditions[label="InitialConditions"];
}
solverparameterslist[label="SolverParametersList"];
subgraph cluster05 {
label="Sediment";
sedimentlayerlist[label="SedimentLayerList"];
sedimentlayer[label="SedimentLayer"];
layer[label="Layer"];
}
subgraph cluster06 {
label="Greometry"
georeach[label="Reach"];
geocrosssection[label="Cross-section"];
geopoint[label="Point"];
}
study -> river;
river -> rnode;
river -> redge;
redge -> rnode;
river -> boundaryconditionlist -> boundarycondition -> rnode;
river -> lateralcontributionlist -> lateralcontribution -> redge;
river -> initialconditionsdict -> initialconditions;
initialconditions -> redge;
river -> stricklerslist -> stricklers;
river -> solverparameterslist;
river -> sedimentlayerlist -> sedimentlayer -> layer;
redge -> frictionlist -> stricklers;
redge -> georeach -> geocrosssection -> geopoint;
geocrosssection -> sedimentlayer;
geopoint -> sedimentlayer;
}
#+end_src
[fn:qt-mv] The Qt Model/View documentation web page:
https://doc.qt.io/qt-5/model-view-programming.html
** TODO Model
** TODO View
*** TODO UI file
*** TODO Window class
** TODO UndoCommand
** TODO Table
** TODO Plot
** TODO Solver
** TODO Unit tests
** TODO The debug mode
* TODO Build the project
The project uses gitlab-ci runners to build packages, but it is possible
to build packages manually.
** Building packages
If you need an hand made package, you can script available in
{{{file(packages)}}} directory.
*** GNU/Linux
On GNU/Linux building GNU/Linux packages is easy, you just need python
in version 3.8 must be installed with venv and pyinstaller packages
(see Listing [[linux-env-deb]] for Debian and derived system). Finally,
run the {{{file(linux.sh)}}} script (see Listing [[linux-pkg]]).
#+NAME: linux-env-deb
#+CAPTION: Install environment on GNU/Linux
#+begin_src shell
sudo apt install python3.8
python3 -m pip install venv
python3 -m pip install pyinstaller
#+end_src
#+NAME: linux-pkg
#+CAPTION: Build GNU/Linux package
#+begin_src shell
cd packages
./linux.sh
#+end_src
*** Windows
To make the Windows packages you have two choice: If you use Windows
you can use the script {{{file(packages/windows.bat)}}}, other else
you can use the script {{{file(packages/wine.sh)}}}. Each script need
a specific software environment.
On windows, you needs python on version 3.8, pyinstaller and
NSIS[fn:nsis] installed. On GNU/Linux you need wget, wine and
winetricks installed.
[fn:nsis] The NSIS web site: https://sourceforge.net/projects/nsis/
** TODO Setup the CI environment
* Documentation files
This document and the user documentation are org files. This text file
format is formatted so that it can be exported in different formats:
PDF (with latex), ODT, HTML, etc. It was originally designed for the
GNUEmacs[fn:emacs] text editor, but can be edited with any text editor. Here we
take a look at the different features used in these documents.
[fn:org] The org-mode website: https://orgmode.org/ (last access
2023-09-15)
[fn:emacs] The GNUEmacs project website: https://gnu.org/s/emacs/
(last access 2023-09-15)
** Org-mode
*** Document structure
Org uses the =*= character to define a new document section. To add a
sub-section, you can add an additional =*= to the current section[fn::
See document structure documentation:
https://orgmode.org/org.html#Headlines (last access 2023-09-15)].
#+BEGIN_EXAMPLE
* Top level headline
** Second level
*** Third level
some text
*** Third level
more text
* Another top level headline
#+END_EXAMPLE
*** Format
Org-mode is a markup file, using markup in the text to modify the
appearance of a portion of text[fn:: See markup documentation:
https://orgmode.org/org.html#Emphasis-and-Monospace (last access
2023-09-15)].
| Markup | Results |
|--------------------+------------------|
| =*Bolt*= | *Bolt* |
| =/Italic/= | /Italic/ |
| =_underline_= | _underline_ |
| ==verbatim== | =verbatim= |
| =~code~= | ~code~ |
| =+strike-through+= | +strike-through+ |
*** Source code blocks
You can add some code blocks[fn:: See org-mode documentation for
source code: https://orgmode.org/org.html#Working-with-Source-Code
(last access 2023-09-15)] in the document.
Here is an example for python source code:
#+BEGIN_EXAMPLE
#+CAPTION: Get os type name in Python code
#+begin_src python
import os
print(f"Document build on system: {os.name}")
#+end_src
#+END_EXAMPLE
If you use GNUEmacs, it is also possible to run the code inside a
block and export (or not) the reuslts in the document.
#+OPTIONS: float:nil
#+CAPTION: Get os type name in Python code
#+begin_src python :python python3 :results output :exports both :noweb yes
import os
print(f"Document build on system: {os.name}")
#+end_src
#+RESULTS:
: Document build on system: posix
*** LaTeX
If we export the file to PDF, org-mode use {{{latex}}}. So we can add
some piece of {{{latex}}} into the document[fn:: See {{{latex}}} part
in documentation: https://orgmode.org/org.html#Embedded-LaTeX (last
access 2023-09-15)]. For exemple, we can add math formula like
=$E=mc^2$= ($E=mc^2$) or =\[E=mc^2\]=:
\[E=mc^2\]
But we can also add every type of {{{latex}}}:
#+BEGIN_EXAMPLE
# Add latex in line
#+LATEX: <my line of latex>
# Add multiple line of LaTeX
#+BEGIN_EXPORT latex
<my latex here>
#+END_EXPORT
#+END_EXAMPLE
It is also possible to add specific {{{latex}}} file header with
=#+LATEX_HEADER=. In this document we use the file
{{{file(doc/tools/latex.org)}}} for all {{{latex}}} headers.
*** Macro
In this document, we use a few macros[fn:: See marcos documentation
https://orgmode.org/org.html#Macro-Replacement (last access
2023-09-15)] to simplify writing. They allow you to define sequences
of text to be replaced, so that the macro name is replaced by its
value. They are defined in the {{{file(doc/tools/macro.org)}}}
file. Once defined, they can be used in the document as follows:
={{{<macro-name>}}}=. You can also have macros with arguments, in this
case: ={{{<macro-name>(arg1,...)}}}=. Les macros peuvent aussi
utiliser du code emacs-lisp.
#+BEGIN_EXAMPLE
# Exemple of macro définition
#+MACRO: toto tata
#+MACRO: add \(($1 + $2)\)
#+MACRO: emacs-version (eval (nth 2 (split-string (emacs-version))))
#+END_EXAMPLE
#+MACRO: toto tata
#+MACRO: add \(($1 + $2)\)
#+MACRO: emacs-version (eval (nth 2 (split-string (emacs-version))))
Macro apply:
- Marco ={{{toto}}}=: {{{toto}}}
- Marco ={{{add(x,y)}}}=: {{{add(x,y)}}}
- Marco ={{{emacs-version}}}=: {{{emacs-version}}}
*** Footnotes
Footnote in org-mode is define with marker =[fn:...]=[fn:: Create
footnotes in org-mode documentation
https://orgmode.org/org.html#Creating-Footnotes (last access
2023-09-15)]:
#+BEGIN_EXAMPLE
The Org website[fn:1] now looks a lot better than it used to.
...
[fn:1] The link is: https://orgmode.org
#+END_EXAMPLE
or:
#+BEGIN_EXAMPLE
The Org website[fn:: The link is: https://orgmode.org] now looks
a lot better than it used to.
...
#+END_EXAMPLE
*** References
The references use the {{{latex}}} bibtex tools. The bib file is in
{{{file(/doc/tools/ref.bib)}}} and use for developers and user
documentation. In document, use ={{{cite(<name>)}}}= to cite a paper.
** Export
To export the files, a {{{file(build.sh)}}} script is available in the org
files directories. On GNU/Linux system you can build the documentation
PDF file with the command =./build.sh=. Texlive package must be
installed, you can install only needed packages or all texlive
packages, for example on Debian (and some derived system) use command
Listing [[texlive-install]].
#+NAME: texlive-install
#+CAPTION: Installation command for texlive full on Debian system
#+begin_src shell
sudo apt install texlive-full
#+end_src
Some org-mode configuration used in documentations files are define in
=/doc/tools/=:
- {{{file(PamhyrDoc.cls)}}}: The {{{latex}}} document class
- {{{file(macro.org)}}}: Available macro
- {{{file(latex.org)}}}: {{{latex}}} configutation for documentations
files
- {{{file(setup.el)}}}: GNUEmacs configuration to build documentations
- {{{file(ref.bib)}}}: Bibtex files for documentations files
* How to contribute?
Pamhyr2 is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License[fn:license], either
version 3 of the License, or any later version.
[fn:license] The GPLv3 web page:
https://www.gnu.org/licenses/gpl-3.0.en.html
** Guidelines
To contribute to Pamhyr2, we expect a minimum of respect between
contributors. We therefore ask you to respect the following rules
regarding communication and contribution content:
+ No gender, racial, religious or social discrimination
+ No insults, personal attacks or potentially offensive remarks
+ Pamhyr2 is free software, and intended to remain so, so take care
with the licensing of libraries and external content you want to add
to the project
+ Humour or hidden easter eggs are welcome if they respect the
previous rules
** Make a contribution
There are several ways to contribute: you can report a bug by creating
an issue on the project's gitlab page[fn:p2-gitlab], or you can create
a merge request on the same page with the changes you have made to the
code, translation or documentation.
The Pamhyr2 copyright is owned by INRAE[fn:inrae], but we keep a
record of each contributors. If you made a modification to pamhyr2
software, please add your name at the end of {{{file(AUTHORS)}}} file
and respect the Listing [[auth-format]] format. You can update this file
information for following contribution.
#+NAME: auth-format
#+CAPTION: =AUTHORS= file format
#+begin_src text
<first name> <last name> [(optional) email], <organisation>, <years>
#+end_src
#+CAPTION: Current =AUTHORS= file
#+INCLUDE: "../../AUTHORS" src text
[fn:p2-gitlab] The Pamhyr2 Gitlab project page:
https://gitlab.irstea.fr/theophile.terraz/pamhyr
[fn:inrae] The INRAE web site: https://www.inrae.fr/
** Translate
You can improve or add translation for the project. To contribute to
Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open
Qt-linguist and edite the translation ({{{file(.ts)}}}) file, finally,
commit the new version of file and make a merge request.
If you want add a new language, edit the script
{{{file(src/lang/create_ts.sh)}}} like Listing [[ts-it]]. Run the script
and open the new file with Qt-linguist, setup target language (Figure
[[qt-linguist-setup]]) and complete translation. Finally, commit the new
file and make a merge request.
#+NAME: ts-it
#+CAPTION: Example of modified {{{file(src/lang/create_ts.sh)}}} to add italian (it) translate for Pamhyr2
#+begin_src shell
...
LANG="fr it"
...
#+end_src
#+NAME: qt-linguist-setup
#+ATTR_HTML: :width 8cm
#+ATTR_LATEX: :width 8cm
#+CAPTION: Qt linguist lang setup example with italian.
[[../images/Qt-linguist-setup-lang.png]]
[fn:qt-linguist] The Qt linguist documentation web page:
https://doc.qt.io/qt-5/qtlinguist-index.html (last access 2023-09-18)
** Code contribution
If you are developper you can improve and/or add features to
Pamhyr2. Please, follow the architecture described in section
[[Architecture]] as closely as possible. Keep the code simple, clear and
efficient as possible. The master branch is reserved for the project
maintainer; you can create a new branch or fork the project before the
request.
{{{biblio}}}
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+163
View File
@@ -0,0 +1,163 @@
\ProvidesClass{Pamhyr}[Pamhyr class]
\NeedsTeXFormat{LaTeX2e}
\ProcessOptions\relax
\LoadClass[11pt,a4paper]{article}
\usepackage[hmargin=2cm, vmargin=3cm]{geometry}
\usepackage[british,UKenglish,USenglish,english,american]{babel}
\usepackage[T1]{fontenc}
\usepackage{times}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage[absolute]{textpos}
\usepackage{titlesec}
\usepackage{amssymb}
\usepackage{tcolorbox}
\usepackage{enumitem}
\setlist[enumerate]{itemsep=-0.5em}
\setlist[itemize]{itemsep=-0.5em}
%%
%% Bibtex
%%
\bibliographystyle{plain}
%%
%% Colors
%%
\definecolor{primary}{HTML}{212121}
\definecolor{lightheadings}{HTML}{5c5c5c}
\definecolor{headings}{HTML}{212121}
\definecolor{links}{HTML}{e12121}
%%
%% Title
%%
\setlength{\columnsep}{20pt}
\setlength{\parskip}{0pt} %% inter paragraph space
\renewcommand{\maketitle}{
{
\centering
{
\fontsize{35}{40}
\selectfont
\scshape
Pamhyr2 \\
\medskip
}
{
%\centering
\scshape
\fontsize{20pt}{24pt}
\selectfont
A graphical user interface for 1D hydro-sedimentary modelling
of rivers \\
\medskip
}
\rule{3cm}{0.4pt}\\
\vspace{0.3cm}
{
%\centering
\scshape
\fontsize{26pt}{24pt}
\selectfont
\@title \\
Document licence: GPLv3 \\
\medskip
\includegraphics[width=3cm]{../images/GPLv3_Logo.png} \\
\medskip
\fontsize{14pt}{16pt}
\selectfont
© \@author \\
\medskip
}
{
%\centering
%\scshape
\fontsize{12pt}{14pt}
\selectfont
\@date
%\medskip
}\\
\vspace{0.3cm}
\rule{3cm}{0.4pt}\\
\vspace{0.3cm}
{
\begin{minipage}[h]{0.24\textwidth}
\includegraphics[width=4cm]{../../src/View/ui/ressources/Logo-INRAE.png}
\end{minipage}
\begin{minipage}[h]{0.24\textwidth}
\includegraphics[width=4cm]{../images/riverly.png}
\end{minipage}
}\\
\vspace{0.3cm}
%%\rule{\textwidth}{0.4pt}
%%\newpage
}
}
%%
%% Texttt
%%
\usepackage[htt]{hyphenat} % Make texttt split at end of line
%%
%% Verbatim
%%
\AtBeginEnvironment{verbatim}{\footnotesize}
%% Add box arrond of verbatim
\BeforeBeginEnvironment{verbatim}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
\AfterEndEnvironment{verbatim}{\end{tcolorbox}}%
\makeatletter
\AtBeginEnvironment{verbatim}{\dontdofcolorbox}
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
\makeatother
%%
%% Source code blocks
%%
%% Use minted
\usepackage{minted}
\usemintedstyle{tango}
\setminted[c]{fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[c++]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[python]{breaklines,fontsize=\footnotesize,encoding=utf8,linenos}
\setminted[shell]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[scheme]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[commun-lisp]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[text]{breaklines,fontsize=\footnotesize,encoding=utf8}
\setminted[llvm]{breaklines,fontsize=\footnotesize,encoding=utf8}
%% Add box arrond of minted
\BeforeBeginEnvironment{minted}{\begin{tcolorbox}[boxsep=0pt, left=0.1cm, right=0.1cm, arc=0pt, boxrule=0.5pt, colback=white]}%
\AfterEndEnvironment{minted}{\end{tcolorbox}}%
\makeatletter
\AtBeginEnvironment{minted}{\dontdofcolorbox}
\def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}}
\makeatother
%%
%% Water mark
%%
%% FIXME: Delete me for the first realease
\usepackage{draftwatermark}
\SetWatermarkLightness{0.8}
\SetWatermarkAngle{25}
\SetWatermarkScale{3}
\SetWatermarkFontSize{1cm}
\SetWatermarkText{Work in progress}
+19
View File
@@ -0,0 +1,19 @@
# latex.org -- Pamhyr documentation latex config
# 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 -*-
#+LaTeX_CLASS: PamhyrDoc
+51
View File
@@ -0,0 +1,51 @@
# macro.org -- Pamhyr documentation macro
# 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 -*-
# Constant name
#+MACRO: oldPamhyr PAMHyR
#+MACRO: Pamhyr Pamhyr2
#+MACRO: Mage Mage
#+MACRO: Rubarbe RubarBE
#+MACRO: Inrae INRAE
#+MACRO: latex \LaTeX
# Information
#+MACRO: version (eval (pamhyr-version))
#+MACRO: current-file (eval (pamhyr-current-file))
# Special format
#+MACRO: file =$1=
# Biblio
#+MACRO: cite [cite:$1]
#+MACRO: biblio \bibliography{documentation}
# Wrapper
#+NAME: attr_wrap
#+HEADER: :var width="\\textwidth"
#+HEADER: :var caption=""
#+HEADER: :var smallcaption=""
#+HEADER: :var name=""
#+HEADER: :var data=""
#+HEADER: :var float="nil"
#+BEGIN_SRC sh :results output :exports none
echo "#+CAPTION[$smallcaption]: $caption"
echo "#+NAME: $name"
echo "#+ATTR_LATEX: :width $width :float $float"
echo "$data"
#+END_SRC
View File
Whitespace-only changes.
+66
View File
@@ -0,0 +1,66 @@
(require 'org)
(require 'subr-x)
;; LaTeX config
(add-to-list
'org-latex-classes
'("PamhyrDoc"
"\\documentclass{../tools/PamhyrDoc}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(setq org-confirm-babel-evaluate nil)
(setq org-latex-caption-above nil)
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(shell . t)))
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(dot . t)))
(org-babel-do-load-languages
'org-babel-load-languages
(add-to-list 'org-babel-load-languages '(python . t)))
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
(setq org-src-fontify-natively t)
(setq org-latex-pdf-process
'("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"bibtex documentation"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
;; Citations
(org-add-link-type
"cite" 'ebib
(lambda (path desc format)
(cond
((eq format 'html)
(format "(<cite>%s</cite>)" path))
((eq format 'latex)
(if (or (not desc) (equal 0 (search "cite:" desc)))
(format "\\cite{%s}" path)
(format "\\cite[%s][%s]{%s}"
(cadr (split-string desc ";"))
(car (split-string desc ";")) path))))))
;; Functools
(defun pamhyr-version ()
"Return the contents of the pamhyr version file."
(with-temp-buffer
(insert-file-contents "../../VERSION")
(buffer-string)))
(defun pamhyr-current-file ()
(let ((lst (split-string (buffer-file-name) "/")))
(string-join (nthcdr (- (length lst) 3) lst) "/")))
+10
View File
@@ -0,0 +1,10 @@
#! /bin/sh
# Build the PDF
emacs -Q -q -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-latex-export-to-pdf
+1
View File
@@ -0,0 +1 @@
../tools/ref.bib
+20
View File
@@ -0,0 +1,20 @@
#+STARTUP: indent
#+INCLUDE: ../tools/macro.org
#+INCLUDE: ../tools/latex.org
#+TITLE: User documentation
#+SUBTITLE: Version: {{{version}}}
#+AUTHOR: {{{INRAE}}}
#+OPTIONS: toc:t
#+LANGUAGE: UKenglish
* TODO Over view
* TODO Get started
** Installation
*** Linux
*** Windows
* TODO Usage
{{{biblio}}}
+54
View File
@@ -0,0 +1,54 @@
rem windows.bat -- Pamhyr Windows batch for windows version building
rem Copyright (C) 2023 INRAE
rem
rem This program is free software: you can redistribute it and/or modify
rem it under the terms of the GNU General Public License as published by
rem the Free Software Foundation, either version 3 of the License, or
rem (at your option) any later version.
rem
rem This program is distributed in the hope that it will be useful,
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
rem GNU General Public License for more details.
rem
rem You should have received a copy of the GNU General Public License
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
@ECHO ON
mkdir pamhyr\pamhyr
xcopy /s /e /y dist\pamhyr pamhyr\pamhyr
rem Copy data
mkdir pamhyr\pamhyr\lang
mkdir pamhyr\pamhyr\View\ui\ressources
mkdir pamhyr\pamhyr\View\ui\Widgets
rem UI
copy /y ..\src\View\ui\ressources\ pamhyr\pamhyr\View\ui\ressources
copy /y ..\src\View\ui\Widgets\*.ui pamhyr\pamhyr\View\ui\Widgets
copy /y ..\src\View\ui\*.ui pamhyr\pamhyr\View\ui\
rem Lang
copy /y ..\src\lang\*.qm pamhyr\pamhyr\lang\
rem Information
copy /y ..\VERSION pamhyr\pamhyr
copy /y ..\AUTHORS pamhyr\pamhyr
copy /y ..\LICENSE pamhyr\pamhyr
rem MAGE
mkdir pamhyr\mage
copy /y ..\mage-windows\mage.exe pamhyr\mage\
copy /y ..\mage-windows\mage_extraire.exe pamhyr\mage\
copy /y ..\mage-windows\mailleurPF.exe pamhyr\mage\
rem Copy tests_cases
mkdir pamhyr\tests_cases
mkdir pamhyr\tests_cases\Saar
copy /y ..\tests_cases\Saar\Saar.pamhyr pamhyr\tests_cases\Saar\
rem Documentations
mkdir pamhyr\doc
copy /y ..\doc\users\documentation.pdf pamhyr\doc\Pamhyr2-users.pdf
copy /y ..\doc\dev\documentation.pdf pamhyr\doc\Pamhyr2-dev.pdf
+20
View File
@@ -0,0 +1,20 @@
rem windows.bat -- Pamhyr Windows batch for windows version building
rem Copyright (C) 2023 INRAE
rem
rem This program is free software: you can redistribute it and/or modify
rem it under the terms of the GNU General Public License as published by
rem the Free Software Foundation, either version 3 of the License, or
rem (at your option) any later version.
rem
rem This program is distributed in the hope that it will be useful,
rem but WITHOUT ANY WARRANTY; without even the implied warranty of
rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
rem GNU General Public License for more details.
rem
rem You should have received a copy of the GNU General Public License
rem along with this program. If not, see <https://www.gnu.org/licenses/>.
@ECHO ON
rem Make installer
"C:\Program Files (x86)\NSIS\makensis.exe" pamhyr.nsi
+18 -18
View File
@@ -3,17 +3,17 @@
!define LIC_NAME "LICENSE"
!define APP_NAME "PAMHYR"
Name "PAMHYR"
OutFile "pamhyr-win-amd64.exe"
LicenseData "..\LICENSE"
Name "Pamhyr2"
OutFile "pamhyr-windows.exe"
LicenseData "pamhyr\pamhyr\LICENSE"
LicenseText "I Agree"
RequestExecutionLevel admin
Unicode True
InstallDir $PROGRAMFILES\PAMHYR
InstallDir $PROGRAMFILES\Pamhyr2
InstallDirRegKey HKLM "Software\PAMHYR" "Install_Dir"
InstallDirRegKey HKLM "Software\Pamhyr2" "Install_Dir"
Page license
Page components
@@ -23,35 +23,35 @@ Page instfiles
UninstPage uninstConfirm
UninstPage instfiles
Section "PAMHYR"
Section "Pamhyr2"
SectionIn RO
SetOutPath $INSTDIR
File /r "dist\pamhyr\*"
File /r "pamhyr\*"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "DisplayName" "NSIS PAMHYR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoRepair" 1
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "DisplayName" "NSIS Pamhyr2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoRepair" 1
WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd
Section "Start Menu Shortcuts"
CreateDirectory "$SMPROGRAMS\PAMHYR"
CreateShortcut "$SMPROGRAMS\PAMHYR\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortcut "$SMPROGRAMS\PAMHYR\PAMHYR.lnk" "$INSTDIR\pamhyr.exe"
CreateDirectory "$SMPROGRAMS\Pamhyr2"
CreateShortcut "$SMPROGRAMS\Pamhyr2\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortcut "$SMPROGRAMS\Pamhyr2\Pamhyr2.lnk" "$INSTDIR\pamhyr\pamhyr.exe"
SectionEnd
Section "Uninstall"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR"
DeleteRegKey HKLM SOFTWARE\NSIS_PAMHYR
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2"
DeleteRegKey HKLM SOFTWARE\NSIS_Pamhyr2
Delete "$SMPROGRAMS\PAMHYR\*.lnk"
Delete "$SMPROGRAMS\Pamhyr2\*.lnk"
RMDir "$SMPROGRAMS\PAMHYR"
RMDir "$SMPROGRAMS\Pamhyr2"
RMDir /R "$INSTDIR"
SectionEnd
-10
View File
@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
class AbstractModelComponent(object):
def __init__(self):
super(AbstractModelComponent, self).__init__()
def new(self, filename):
def export(self, filename):
@@ -20,7 +20,7 @@ import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
@@ -30,30 +30,12 @@ from Model.BoundaryCondition.BoundaryConditionTypes import (
Solid,
)
class BoundaryConditionList(SQLSubModel):
class BoundaryConditionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [
BoundaryCondition,
]
def __init__(self, status = None):
super(BoundaryConditionList, self).__init__()
self._status = status
self._tabs = {
"liquid" : [],
"solid" : [],
"suspenssion" : []
}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
@@ -82,66 +64,12 @@ class BoundaryConditionList(SQLSubModel):
return True
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n)
self._status.modified()
return n
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, bcs):
for bc in bcs:
self._tabs[lst].remove(bc)
self._status.modified()
def delete_i(self, lst, indexes):
bcs = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, bcs)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
def __copy__(self):
new = BoundaryConditionList()
+1 -1
View File
@@ -18,7 +18,7 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Friction(SQLSubModel):
def __init__(self, name:str = "", status = None):
+8 -67
View File
@@ -21,26 +21,18 @@ import logging
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Friction.Friction import Friction
logger = logging.getLogger()
class FrictionList(SQLSubModel):
class FrictionList(PamhyrModelList):
_sub_classes = [
Friction
]
def __init__(self, status = None):
super(FrictionList, self).__init__()
self._status = status
self._frictions = []
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update_0_0_1(cls, execute, version):
execute("ALTER TABLE `section` RENAME TO `friction`")
@@ -57,7 +49,7 @@ class FrictionList(SQLSubModel):
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
new._frictions = Friction._sql_load(
new._lst = Friction._sql_load(
execute, data
)
@@ -69,70 +61,19 @@ class FrictionList(SQLSubModel):
ok = True
ind = 0
for friction in self._frictions:
for friction in self._lst:
data["ind"] = ind
ok &= friction._sql_save(execute, data = data)
ind += 1
return ok
def __len__(self):
return len(self._frictions)
@property
def frictions(self):
return self._frictions.copy()
def get(self, row):
return self._frictions[row]
def set(self, row, new):
self._frictions[row] = new
self._status.modified()
return self.lst
def new(self, index):
n = Friction(status = self._status)
self._frictions.insert(index, n)
self._lst.insert(index, n)
self._status.modified()
return n
def insert(self, index, new):
self._frictions.insert(index, new)
self._status.modified()
def delete(self, frictions):
for friction in frictions:
self._frictions.remove(friction)
self._status.modified()
def delete_i(self, indexes):
frictions = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._frictions)
)
)
)
self.delete(frictions)
def sort(self, reverse=False, key=None):
self._frictions.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._frictions):
next = index - 1
l = self._frictions
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._frictions
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+1 -1
View File
@@ -19,7 +19,7 @@
from math import dist
import numpy as np
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Point import Point
class PointXYZ(Point, SQLSubModel):
+1 -1
View File
@@ -21,7 +21,7 @@ from typing import List
from tools import timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import ClipboardFormatError
from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ
+1 -1
View File
@@ -27,7 +27,7 @@ from functools import reduce
from tools import flatten, timer, trace
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ
@@ -20,7 +20,7 @@ from copy import copy, deepcopy
from tools import trace, timer
from functools import reduce
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Data(SQLSubModel):
def __init__(self, name:str = "",
@@ -19,29 +19,14 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.InitialConditions.InitialConditions import InitialConditions
class InitialConditionsDict(SQLSubModel):
class InitialConditionsDict(PamhyrModelDict):
_sub_classes = [
InitialConditions,
]
def __init__(self, status = None):
super(InitialConditionsDict, self).__init__()
self._status = status
self._reach = {}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
@@ -55,7 +40,7 @@ class InitialConditionsDict(SQLSubModel):
)
if ic is not None:
new._reach[reach] = ic
new._dict[reach] = ic
return new
@@ -66,30 +51,12 @@ class InitialConditionsDict(SQLSubModel):
execute("DELETE FROM initial_conditions")
for reach in self._reach:
for reach in self._dict:
data["reach"] = reach
ok &= self._reach[reach]._sql_save(execute, data)
ok &= self._dict[reach]._sql_save(execute, data)
return ok
def __len__(self):
return len(self._reach)
def is_defined(self, reach):
return reach in self._reach
def get(self, reach):
if reach in self._reach:
return self._reach[reach]
new = self.new(reach)
self.set(reach, new)
return new
def set(self, reach, new):
self._reach[reach] = new
self._status.modified()
def new(self, reach):
new = InitialConditions(reach = reach, status = self._status)
self.set(reach, new)
@@ -20,7 +20,7 @@ import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.LateralContribution.LateralContribution import LateralContribution
@@ -27,30 +27,12 @@ from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation,
)
class LateralContributionList(SQLSubModel):
class LateralContributionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [
LateralContribution,
]
def __init__(self, status = None):
super(LateralContributionList, self).__init__()
self._status = status
self._tabs = {
"liquid" : [],
"solid" : [],
"suspenssion" : []
}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data['status'])
@@ -79,66 +61,12 @@ class LateralContributionList(SQLSubModel):
return True
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n)
self._status.modified()
return n
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, bcs):
for bc in bcs:
self._tabs[lst].remove(bc)
self._status.modified()
def delete_i(self, lst, indexes):
bcs = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, bcs)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
def __copy__(self):
new = LateralContributionList()
+1 -5
View File
@@ -18,7 +18,7 @@
from tools import flatten
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node
from Model.Network.Edge import Edge
@@ -311,10 +311,6 @@ class River(Graph, SQLSubModel):
self._save_submodel(execute, objs, data)
return True
@property
def frictions(self):
return self._frictions
@property
def boundary_condition(self):
return self._boundary_condition
+1 -1
View File
@@ -2,7 +2,7 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
class Layer(SQLSubModel):
+5 -66
View File
@@ -2,35 +2,18 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrList import PamhyrModelList
from Model.SedimentLayer.SedimentLayer import SedimentLayer
class SedimentLayerList(SQLSubModel):
class SedimentLayerList(PamhyrModelList):
_sub_classes = [SedimentLayer]
def __init__(self, status = None):
super(SedimentLayerList, self).__init__()
self._status = status
self._sl = []
def __len__(self):
return len(self._sl)
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
new._sl = SedimentLayer._sql_load(execute, data)
new._lst = SedimentLayer._sql_load(execute, data)
return new
@@ -41,61 +24,17 @@ class SedimentLayerList(SQLSubModel):
execute("DELETE FROM sedimentary_layer")
execute("DELETE FROM sedimentary_layer_layer")
for sl in self._sl:
for sl in self._lst:
ok &= sl._sql_save(execute, data)
return ok
@property
def sediment_layers(self):
return self._sl.copy()
def get(self, index):
return self._sl[index]
def set(self, index, new):
self._sl[index] = new
self._status.modified()
def insert(self, index, new):
self._sl.insert(index, new)
self._status.modified()
return self.lst
def new(self, index):
n = SedimentLayer(status = self._status)
self.insert(index, n)
self._status.modified()
return n
def delete(self, els):
for el in els:
self._sl.remove(el)
self._status.modified()
def delete_i(self, indexes):
els = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._sl)
)
)
)
self.delete(els)
def move_up(self, index):
if index < len(self._sl):
next = index - 1
l = self._sl
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._sl
l[index], l[prev] = l[prev], l[index]
self._status.modified()
@@ -20,7 +20,7 @@ from copy import copy
from tools import trace, timer
from Solver.Solvers import solver_type_list
from Model.DB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelList
class Parameter():
def __init__(self,
@@ -63,13 +63,13 @@ class Parameter():
return new
class SolverParametersList(SQLSubModel):
class SolverParametersList(PamhyrModelList):
def __init__(self, solver_type = None, status = None):
super(SolverParametersList, self).__init__()
self._status = status
self._solver = solver_type
self._parameters = list(
self._lst = list(
map(
lambda t: Parameter.from_tuple(t, self._status),
solver_type.default_parameters()
@@ -194,7 +194,7 @@ class SolverParametersList(SQLSubModel):
)
ind = 0
for param in self._parameters:
for param in self._lst:
sql = (
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) "+
@@ -210,39 +210,36 @@ class SolverParametersList(SQLSubModel):
return True
def __len__(self):
return len(self._parameters)
@property
def parameters(self):
return self._parameters.copy()
return self.lst
def get(self, index):
return self._parameters[index]
return self._lst[index]
def get_by_key(self, key):
try:
return next(
filter(
lambda p: p["name"] == key,
self._parameters
self._lst
)
)["value"]
except:
return None
def set(self, index, new):
self._parameters[index] = new
self._lst[index] = new
self._status.modified()
def set_value(self, key, value):
for p in self._parameters:
for p in self._lst:
if p["name"] == key:
p["value"] = value
self._status.modified()
return
self._parameters.append(
self._lst.append(
Parameter(
name = key,
value = value,
@@ -253,47 +250,6 @@ class SolverParametersList(SQLSubModel):
def new(self, index):
n = Parameter(status = self._status)
self._parameters.insert(index, n)
self._lst.insert(index, n)
self._status.modified()
return n
def insert(self, index, new):
self._parameters.insert(index, new)
self._status.modified()
def delete(self, parameters):
for parameter in parameters:
self._parameters.remove(parameter)
self._status.modified()
def delete_i(self, indexes):
parameters = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._parameters)
)
)
)
self.delete(parameters)
def sort(self, reverse=False, key=None):
self._parameters.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._parameters):
next = index - 1
l = self._parameters
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._parameters
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+1 -1
View File
@@ -18,7 +18,7 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Tools.PamhyrDB import SQLSubModel
class Stricklers(SQLSubModel):
_id_cnt = 0
+8 -49
View File
@@ -18,22 +18,15 @@
from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Saved import SavedStatus
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Stricklers.Stricklers import Stricklers
class StricklersList(SQLSubModel):
class StricklersList(PamhyrModelList):
_sub_classes = [
Stricklers,
]
def __init__(self, status = None):
if status is None:
status = SavedStatus()
self._status = status
self._stricks = []
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@@ -46,7 +39,7 @@ class StricklersList(SQLSubModel):
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"])
new._stricks = Stricklers._sql_load(
new._lst = Stricklers._sql_load(
execute,
data = data
)
@@ -56,60 +49,26 @@ class StricklersList(SQLSubModel):
def _sql_save(self, execute, data = None):
execute("DELETE FROM stricklers")
objs = self._stricks
objs = self._lst
return self._save_submodel(execute, objs, data)
def __len__(self):
return len(self._stricks)
@property
def stricklers(self):
return self._stricks.copy()
return self.lst
def get(self, index):
if 0 <= index < len(self._stricks):
return self._stricks[index]
return None
def insert(self, index, strick):
self._stricks.insert(index, strick)
self._status.modified()
def add(self, index):
def new(self, index):
s = Stricklers(status = self._status)
self.insert(index, s)
return s
def delete(self, stricks):
self._stricks = list(
filter(
lambda s: s not in stricks,
self._stricks
)
)
self._status.modified()
def delete_i(self, indexes):
stricks = set(
map(
lambda e: e[1],
filter(
lambda e: e[0] in indexes,
enumerate(self._stricks)
)
)
)
self.delete(stricks)
@timer
def sort(self, reverse:bool = False, key=None):
f = lambda st: st.name
if key is not None:
f = key
self._stricks = sorted(
self._stricks,
self._lst = sorted(
self._lst,
key = f,
reverse = reverse,
)
+1 -1
View File
@@ -23,7 +23,7 @@ from datetime import datetime
from tools import timer, timestamp
from Model.DB import SQLModel
from Model.Tools.PamhyrDB import SQLModel
from Model.Saved import SavedStatus
from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError
@@ -1,4 +1,4 @@
# DB.py -- Pamhyr abstract model database classes
# PamhyrDB.py -- Pamhyr abstract model database classes
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
+74
View File
@@ -0,0 +1,74 @@
# PamhyrDict.py -- Pamhyr abstract dict for model classes
# 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 logging
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class PamhyrModelDict(SQLSubModel):
_sub_classes = []
def __init__(self, status = None):
super(PamhyrModelDict, self).__init__()
self._status = status
self._dict = {}
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
def __len__(self):
return len(self._dict)
def is_defined(self, key):
return key in self._dict
def set(self, key, new):
self._dict[key] = new
self._status.modified()
def get(self, key):
if key in self._dict:
return self._dict[key]
new = self.new(key)
self.set(key, new)
return new
def new(self, key):
raise NotImplementedMethodeError(self, self.new)
+229
View File
@@ -0,0 +1,229 @@
# PamhyrList.py -- Pamhyr Abstract List object for the Model
# 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 logging
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class PamhyrModelList(SQLSubModel):
_sub_classes = [
]
def __init__(self, status = None):
super(PamhyrModelList, self).__init__()
self._status = status
self._lst = []
#######
# SQL #
#######
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
################
# MODEL METHOD #
################
def __len__(self):
return len(self._lst)
@property
def lst(self):
return self._lst.copy()
def get(self, row):
return self._lst[row]
def set(self, row, new):
self._lst[row] = new
self._status.modified()
def new(self, index):
"""Create new elements and add it to list
Args:
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, index, new):
self._lst.insert(index, new)
self._status.modified()
def delete(self, lst):
for el in lst:
self._lst.remove(el)
self._status.modified()
def delete_i(self, indexes):
lst = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._lst)
)
)
)
self.delete(lst)
def sort(self, reverse=False, key=None):
self._lst.sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, index):
if index < len(self._lst):
next = index - 1
l = self._lst
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
l = self._lst
l[index], l[prev] = l[prev], l[index]
self._status.modified()
class PamhyrModelListWithTab(SQLSubModel):
_tabs_list = []
_sub_classes = [
]
def __init__(self, status = None):
super(PamhyrModelListWithTab, self).__init__()
self._status = status
self._tabs = {}
for tab in self._tabs_list:
self._tabs[tab] = []
#######
# SQL #
#######
@classmethod
def _sql_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _sql_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _sql_load(cls, execute, data = None):
raise NotImplementedMethodeError(cls, cls._sql_load)
def _sql_save(self, execute, data = None):
raise NotImplementedMethodeError(self, self._sql_save)
################
# MODEL METHOD #
################
def len(self, lst):
return len(self._tabs[lst])
def get_tab(self, lst):
return self._tabs[lst].copy()
def get(self, lst, row):
return self._tabs[lst][row]
def set(self, lst, row, new):
self._tabs[lst][row] = new
self._status.modified()
def new(self, lst, index):
"""Create new elements and add it to list
Args:
lst: The tab name
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, lst, index, new):
self._tabs[lst].insert(index, new)
self._status.modified()
def delete(self, lst, els):
for el in els:
self._tabs[lst].remove(el)
self._status.modified()
def delete_i(self, lst, indexes):
els = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, els)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
l = self._tabs[lst]
l[index], l[next] = l[next], l[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
l = self._tabs[lst]
l[index], l[prev] = l[prev], l[index]
self._status.modified()
+36
View File
@@ -0,0 +1,36 @@
# AScript.py -- Pamhyr abstract script class
# 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 -*-
from Model.Except import NotImplementedMethodeError
class AScript(object):
name = ""
description = ""
def __init__(self, app, conf, args):
super(AScript, self).__init__()
self._app = app
self._conf = conf
self._args = args.copy()
def usage(self):
raise NotImplementedMethodeError(self, self.usage)
def run(self):
raise NotImplementedMethodeError(self, self.run)
+34
View File
@@ -0,0 +1,34 @@
# help.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 logging
from Scripts.AScript import AScript
logger = logging.getLogger()
class ScriptHello(AScript):
name = "Hello"
description = "Display hello"
def usage(self):
logger.info(f"Usage : {self._args[0]} hello")
def run(self):
logger.info("Hello from Pamhyr2 script !")
return 0
+4 -2
View File
@@ -22,6 +22,7 @@ import logging
from tools import timer
try:
# Installation allow Unix-like signal
from signal import SIGTERM, SIGSTOP, SIGCONT
_signal = True
except:
@@ -201,7 +202,7 @@ class AbstractSolver(object):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
".."
"..", ".."
)
)
@@ -216,7 +217,6 @@ class AbstractSolver(object):
The executable and list of arguments
"""
# HACK: Works in most case... Trust me i'm an engineer
cmd = cmd.replace("@install_dir", self._install_dir())
cmd = cmd.replace("@path", path.replace(" ", "\ "))
cmd = cmd.replace("@input", self.input_param())
@@ -358,6 +358,7 @@ class AbstractSolver(object):
def start(self, study, process = None):
if _signal:
# Solver is PAUSED, so continue execution
if self._status == STATUS.PAUSED:
os.kill(self._process.pid(), SIGCONT)
self._status = STATUS.RUNNING
@@ -371,6 +372,7 @@ class AbstractSolver(object):
if self._process is None:
return False
# Send SIGSTOP to PAUSED solver
os.kill(self._process.pid(), SIGSTOP)
self._status = STATUS.PAUSED
return True
+3 -135
View File
@@ -25,6 +25,7 @@ from tools import trace, timer
from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
@@ -53,125 +54,7 @@ _translate = QCoreApplication.translate
logger = logging.getLogger()
class ExtendedTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedTimeEdit, self).__init__(
ui="extendedTimeEdit",
parent=parent
)
self.parent = parent
self.spinBox_days = self.find(QSpinBox, "spinBox_days")
self.timeEdit = self.find(QTimeEdit, "timeEdit")
def set_time(self, time):
days = 0
stime = time
# if ',' in time, time format is 'DD days, HH:MM:SS',
# otherelse is 'HH:MM:SS'
if "," in time:
s = time.strip().split(" ")
days = int(s[0])
stime = s[-1]
qtime = QTime.fromString(
stime,
"h:mm:ss"
)
self.spinBox_days.setValue(days)
self.timeEdit.setTime(qtime)
def get_time(self):
days = self.spinBox_days.value()
time = self.timeEdit.time().toPyTime()
secs = (
(time.hour * 3600) +
(time.minute * 60) +
time.second
)
return timedelta(days=days, seconds=secs)
class ExtendedDateTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedDateTimeEdit, self).__init__(
ui="extendedDateTimeEdit",
parent=parent
)
self.parent = parent
self.dateTimeEdit = self.find(QDateTimeEdit, "dateTimeEdit")
def set_time(self, time):
qdatetime = QDateTime.fromString(
time,
"yyyy-MM-dd hh:mm:ss"
)
self.dateTimeEdit.setDateTime(qdatetime)
def get_time(self):
time = self.dateTimeEdit.dateTime().toPyDateTime()
return time
class ExTimeDelegate(QItemDelegate):
def __init__(self, data=None, mode="time", parent=None):
super(ExTimeDelegate, self).__init__(parent)
self._data = data
self._mode = mode
def createEditor(self, parent, option, index):
if self._mode == "time":
self.editor = ExtendedTimeEdit(parent=parent)
else:
self.editor = ExtendedDateTimeEdit(parent=parent)
value = index.data(Qt.DisplayRole)
self.editor.set_time(value)
logger.debug(str(value))
return self.editor
def setModelData(self, editor, model, index):
time = editor.get_time()
if self._mode == "time":
model.setData(index, int(time.total_seconds()))
else:
logger.debug(str(time.timestamp()))
model.setData(index, int(time.timestamp()))
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, mode="time", undo=None):
super(QAbstractTableModel, self).__init__()
self._headers = data.header
self._data = data
self._mode = mode
self._undo = undo
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def data(self, index, role):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
@@ -189,7 +72,7 @@ class TableModel(QAbstractTableModel):
if self._data.get_type_column(column) == float:
value = f"{v:.4f}"
elif self._data.header[column] == "time":
if self._mode == "time":
if self._opt_data == "time":
t0 = datetime.fromtimestamp(0)
t = datetime.fromtimestamp(v)
value = str(t - t0)
@@ -200,12 +83,6 @@ class TableModel(QAbstractTableModel):
return value
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -315,12 +192,3 @@ class TableModel(QAbstractTableModel):
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+29 -17
View File
@@ -16,10 +16,13 @@
# -*- coding: utf-8 -*-
import logging
from tools import timer, trace
from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
from PyQt5.QtGui import (
QKeySequence,
@@ -39,15 +42,18 @@ from PyQt5.QtWidgets import (
)
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.BoundaryCondition.translate import long_types
from View.BoundaryCondition.Edit.translate import table_headers, retranslate
from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand
from View.BoundaryCondition.Edit.Table import TableModel, ExTimeDelegate
from View.BoundaryCondition.Edit.Table import TableModel
from View.BoundaryCondition.Edit.Plot import Plot
_translate = QCoreApplication.translate
logger = logging.getLogger()
class WD50Sigma(AWidget):
d50Changed = pyqtSignal(float)
sigmaChanged = pyqtSignal(float)
@@ -141,23 +147,29 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self._d50sigma.set_sigma(self._data.sigma)
def setup_table(self):
table = self.find(QTableView, "tableView")
self._table = TableModel(
retranslate()
headers = {}
for h in self._data.header:
headers[h] = table_headers[h]
self._delegate_time = PamhyrExTimeDelegate(
data = self._data,
undo = self._undo_stack,
mode = self._study.time_system
mode = self._study.time_system,
parent = self
)
if self._data.header[0] == "time":
self._delegate_time = ExTimeDelegate(
data = self._data,
mode = self._study.time_system,
parent = self
)
table.setItemDelegateForColumn(
0, self._delegate_time
)
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view = table,
table_headers = headers,
editable_headers = self._data.header,
delegates = {
"time": self._delegate_time,
},
data = self._data,
undo = self._undo_stack,
opt_data = self._study.time_system
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -167,7 +179,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.verticalLayout.addWidget(self.toolbar)
@@ -29,3 +29,12 @@ table_headers = {
"z": _translate("BoundaryCondition", "Z (m)"),
"solid": _translate("BoundaryCondition", "Solid (kg/s)"),
}
def retranslate():
table_headers["x"] = _translate("BoundaryCondition", "X")
table_headers["y"] = _translate("BoundaryCondition", "Y")
table_headers["time"] = _translate("BoundaryCondition", "Time")
table_headers["date"] = _translate("BoundaryCondition", "Date")
table_headers["discharge"] = _translate("BoundaryCondition", "Discharge (m³/s)")
table_headers["z"] = _translate("BoundaryCondition", "Z (m)")
table_headers["solid"] = _translate("BoundaryCondition", "Solid (kg/s)")
+19 -36
View File
@@ -39,6 +39,8 @@ from Model.BoundaryCondition.BoundaryConditionTypes import (
TimeOverZ, TimeOverDischarge, ZOverDischarge
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.BoundaryCondition.UndoCommand import (
SetNameCommand, SetNodeCommand, SetTypeCommand,
AddCommand, DelCommand, SortCommand,
@@ -104,26 +106,13 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, undo=None, tab=""):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._data = data
self._undo = undo
self._tab = tab
self._bcs = self._data.boundary_condition
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.boundary_condition
self._tab = self._opt_data
def rowCount(self, parent):
return self._bcs.len(self._tab)
def columnCount(self, parent):
return len(self._headers)
return self._lst.len(self._tab)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
@@ -133,24 +122,18 @@ class TableModel(QAbstractTableModel):
column = index.column()
if self._headers[column] == "name":
return self._bcs.get(self._tab, row).name
return self._lst.get(self._tab, row).name
elif self._headers[column] == "type":
t = self._bcs.get(self._tab, row).bctype
t = self._lst.get(self._tab, row).bctype
return long_types[t]
elif self._headers[column] == "node":
n = self._bcs.get(self._tab, row).node
n = self._lst.get(self._tab, row).node
if n is None:
return _translate("BoundaryCondition", "Not associate")
return n.name
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -162,20 +145,20 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._bcs, self._tab,row, value
self._lst, self._tab,row, value
)
)
elif self._headers[column] == "type":
key = next(k for k, v in long_types.items() if v == value)
self._undo.push(
SetTypeCommand(
self._bcs, self._tab,row, BC_types[key]
self._lst, self._tab,row, BC_types[key]
)
)
elif self._headers[column] == "node":
self._undo.push(
SetNodeCommand(
self._bcs, self._tab,row, self._data.node(value)
self._lst, self._tab,row, self._data.node(value)
)
)
except Exception as e:
@@ -190,7 +173,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
AddCommand(
self._bcs, self._tab,row
self._lst, self._tab,row
)
)
@@ -202,7 +185,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
DelCommand(
self._bcs, self._tab,rows
self._lst, self._tab, rows
)
)
@@ -214,7 +197,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
SortCommand(
self._bcs, self._tab,False
self._lst, self._tab, False
)
)
@@ -231,7 +214,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._bcs, self._tab,"up", row
self._lst, self._tab, "up", row
)
)
@@ -239,7 +222,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._bcs):
if row > len(self._lst):
return
target = row
@@ -248,7 +231,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._bcs, self._tab,"down", row
self._lst, self._tab, "down", row
)
)
+15 -15
View File
@@ -56,7 +56,7 @@ from View.BoundaryCondition.Table import (
)
from View.Network.GraphWidget import GraphWidget
from View.BoundaryCondition.translate import *
from View.BoundaryCondition.translate import table_headers, retranslate
from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow
_translate = QCoreApplication.translate
@@ -90,17 +90,10 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
for t in ["liquid", "solid", "suspenssion"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
data = self._study.river,
undo = self._undo_stack,
tab = t,
)
table.setModel(self._table[t])
self._delegate_type = ComboBoxDelegate(
data = self._study.river,
mode = "type",
@@ -114,13 +107,20 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
parent=self
)
table.setItemDelegateForColumn(
1, self._delegate_type
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
table_view = table,
table_headers = table_headers,
editable_headers = ["name", "type", "node"],
delegates = {
"type": self._delegate_type,
"node": self._delegate_node,
},
data = self._study.river,
undo = self._undo_stack,
opt_data = t,
)
table.setItemDelegateForColumn(
2, self._delegate_node
)
table.setModel(self._table[t])
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
+5
View File
@@ -49,3 +49,8 @@ BC_types = {
"ZD": ZOverDischarge,
"SL": Solid,
}
def retranslate():
table_headers["name"] = _translate("BoundaryCondition", "Name")
table_headers["type"] = _translate("BoundaryCondition", "Type")
table_headers["node"] = _translate("BoundaryCondition", "Node")
+2 -1
View File
@@ -37,10 +37,11 @@ class Worker(QObject):
self.signalStatus.emit('end')
def _compute(self):
# The computation loop
for checker in self._checker_list:
self.signalStatus.emit(checker.name)
# time.sleep(1)
# Run checker
checker.run(self._study)
self.signalStatus.emit("progress")
+4
View File
@@ -92,7 +92,9 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
self.set_plaintext_edit_text("plainTextEdit", "")
self._history_ind = 0
# Code to rich_code
rich_code = code.strip().split("\n")
# Add return variable for results display at last row
rich_code[-1] = "self.__debug_exec_result__ = " + rich_code[-1]
rich_code = "\n".join(rich_code)
logger.debug(f"User debug command : {rich_code}")
@@ -102,6 +104,8 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
except Exception as e:
value = f"<font color=\"red\">" + str(e) + "</font>"
# Display code
msg = f"<font color=\"grey\"> # " + code + " #</font>"
self.find(QTextEdit, "textEdit").append(msg)
# Display results
self.find(QTextEdit, "textEdit").append(str(value))
+2 -1
View File
@@ -51,7 +51,7 @@ from View.Frictions.Table import (
from View.Plot.MplCanvas import MplCanvas
from View.Geometry.PlotKPZ import PlotKPZ
from View.Frictions.PlotStricklers import PlotStricklers
from View.Frictions.translate import *
from View.Frictions.translate import table_headers, retranslate
from View.Stricklers.Window import StricklersWindow
@@ -94,6 +94,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
table = self.find(QTableView, f"tableView")
+8
View File
@@ -28,3 +28,11 @@ table_headers = {
"begin_strickler": _translate("Frictions", "Begin strickler"),
"end_strickler": _translate("Frictions", "End strickler"),
}
def retranslate():
table_headers["name"] = _translate("Frictions", "Name")
# table_headers["edge"] = _translate("Frictions", "Reach")
table_headers["begin_kp"] = _translate("Frictions", "Begin kp (m)")
table_headers["end_kp"] = _translate("Frictions", "End kp (m)")
table_headers["begin_strickler"] = _translate("Frictions", "Begin strickler")
table_headers["end_strickler"] = _translate("Frictions", "End strickler")
@@ -25,7 +25,8 @@ from PyQt5.QtWidgets import (
QToolBar,
)
from View.Plot import MplCanvas, navigation_toolbar_2qt
from View.Plot import MplCanvas
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
file_path = os.path.abspath(os.path.dirname(__file__))
icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/../View/ui/ressources/"
@@ -197,7 +198,7 @@ class Ui_MainWindow(object):
self.canvas = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.my_toolbar = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT(
self.my_toolbar = PamhyrPlotToolbar(
self.canvas, self.widget1
)
+6 -6
View File
@@ -25,9 +25,9 @@ from PyQt5.QtWidgets import (
QAbstractItemView, QHeaderView, QToolBar,
)
from View.Plot import (
MplCanvas, navigation_toolbar_2qt
)
from View.Plot import MplCanvas
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
file_path = os.path.abspath(os.path.dirname(__file__))
icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/View/ui/ressources/"
@@ -190,7 +190,7 @@ class Ui_MainWindow(object):
self.canvas_1 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_1.setObjectName("canvas_1")
self.toolbar_1 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT(
self.toolbar_1 = PamhyrPlotToolbar(
self.canvas_1, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"]
)
@@ -207,7 +207,7 @@ class Ui_MainWindow(object):
self.canvas_2 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT(
self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"]
)
@@ -223,7 +223,7 @@ class Ui_MainWindow(object):
self.canvas_3 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_3.setObjectName("canvas_3")
self.toolbar_3 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT(
self.toolbar_3 = PamhyrPlotToolbar(
self.canvas_3, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"]
)
+6 -4
View File
@@ -49,11 +49,11 @@ from View.InitialConditions.UndoCommand import (
from View.InitialConditions.Table import TableModel, ComboBoxDelegate
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.InitialConditions.PlotDKP import PlotDKP
from View.InitialConditions.PlotDischarge import PlotDischarge
from View.InitialConditions.translate import *
from View.InitialConditions.translate import table_headers, retranslate
from View.InitialConditions.DialogHeight import HeightDialog
from View.InitialConditions.DialogDischarge import DischargeDialog
@@ -97,6 +97,8 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView")
self._table = TableModel(
river = self._study.river,
@@ -122,7 +124,7 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self):
self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_1.setObjectName("canvas_1")
self.toolbar_1 = PamHyrNavigationToolbar2QT(
self.toolbar_1 = PamhyrPlotToolbar(
self.canvas_1, self
)
self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1")
@@ -138,7 +140,7 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamHyrNavigationToolbar2QT(
self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self
)
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
+9
View File
@@ -29,3 +29,12 @@ table_headers = {
"height": _translate("LateralContribution", "Height (m)"),
# "comment": _translate("LateralContribution", "Comment"),
}
def retranslate():
# table_headers["name"] = _translate("LateralContribution", "Name")
table_headers["kp"] = _translate("LateralContribution", "KP (m)")
# table_headers["speed"] = _translate("LateralContribution", "Speed (m/s)")
table_headers["discharge"] = _translate("LateralContribution", "Discharge (m³/s)")
table_headers["elevation"] = _translate("LateralContribution", "Elevation (m)")
table_headers["height"] = _translate("LateralContribution", "Height (m)")
# table_headers["comment"] = _translate("LateralContribution", "Comment")
+3 -134
View File
@@ -24,6 +24,7 @@ from tools import trace, timer
from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
@@ -51,125 +52,8 @@ _translate = QCoreApplication.translate
logger = logging.getLogger()
class ExtendedTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedTimeEdit, self).__init__(
ui="extendedTimeEdit",
parent=parent
)
self.parent = parent
self.spinBox_days = self.find(QSpinBox, "spinBox_days")
self.timeEdit = self.find(QTimeEdit, "timeEdit")
def set_time(self, time):
days = 0
stime = time
# if ',' in time, time format is 'DD days, HH:MM:SS',
# otherelse is 'HH:MM:SS'
if "," in time:
s = time.strip().split(" ")
days = int(s[0])
stime = s[-1]
qtime = QTime.fromString(
stime,
"h:mm:ss"
)
self.spinBox_days.setValue(days)
self.timeEdit.setTime(qtime)
def get_time(self):
days = self.spinBox_days.value()
time = self.timeEdit.time().toPyTime()
secs = (
(time.hour * 3600) +
(time.minute * 60) +
time.second
)
return timedelta(days=days, seconds=secs)
class ExtendedDateTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedDateTimeEdit, self).__init__(
ui="extendedDateTimeEdit",
parent=parent
)
self.parent = parent
self.dateTimeEdit = self.find(QDateTimeEdit, "dateTimeEdit")
def set_time(self, time):
qdatetime = QDateTime.fromString(
time,
"yyyy-MM-dd hh:mm:ss"
)
self.dateTimeEdit.setDateTime(qdatetime)
def get_time(self):
time = self.dateTimeEdit.dateTime().toPyDateTime()
return time
class ExTimeDelegate(QItemDelegate):
def __init__(self, data=None, mode="time", parent=None):
super(ExTimeDelegate, self).__init__(parent)
self._data = data
self._mode = mode
def createEditor(self, parent, option, index):
if self._mode == "time":
self.editor = ExtendedTimeEdit(parent=parent)
else:
self.editor = ExtendedDateTimeEdit(parent=parent)
value = index.data(Qt.DisplayRole)
self.editor.set_time(value)
logger.debug(str(value))
return self.editor
def setModelData(self, editor, model, index):
time = editor.get_time()
if self._mode == "time":
model.setData(index, int(time.total_seconds()))
else:
logger.debug(str(time.timestamp()))
model.setData(index, int(time.timestamp()))
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, mode="time", undo=None):
super(QAbstractTableModel, self).__init__()
self._headers = data.header
self._data = data
self._mode = mode
self._undo = undo
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def data(self, index, role):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
@@ -187,7 +71,7 @@ class TableModel(QAbstractTableModel):
if self._data.get_type_column(column) == float:
value = f"{v:.4f}"
elif self._data.header[column] == "time":
if self._mode == "time":
if self._opt_data == "time":
t0 = datetime.fromtimestamp(0)
t = datetime.fromtimestamp(v)
value = str(t - t0)
@@ -198,12 +82,6 @@ class TableModel(QAbstractTableModel):
return value
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
@@ -313,12 +191,3 @@ class TableModel(QAbstractTableModel):
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+25 -17
View File
@@ -20,6 +20,7 @@ from tools import timer, trace
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
from PyQt5.QtGui import (
QKeySequence,
@@ -37,10 +38,11 @@ from PyQt5.QtWidgets import (
)
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.LateralContribution.translate import long_types
from View.LateralContribution.Edit.Table import TableModel, ExTimeDelegate
from View.LateralContribution.Edit.translate import table_headers, retranslate
from View.LateralContribution.Edit.Table import TableModel
from View.LateralContribution.Edit.Plot import Plot
_translate = QCoreApplication.translate
@@ -85,23 +87,29 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
table = self.find(QTableView, "tableView")
self._table = TableModel(
retranslate()
headers = {}
for h in self._data.header:
headers[h] = table_headers[h]
self._delegate_time = PamhyrExTimeDelegate(
data = self._data,
undo = self._undo_stack,
mode = self._study.time_system
mode = self._study.time_system,
parent = self
)
if self._data.header[0] == "time":
self._delegate_time = ExTimeDelegate(
data = self._data,
mode = self._study.time_system,
parent = self
)
table.setItemDelegateForColumn(
0, self._delegate_time
)
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view = table,
table_headers = headers,
editable_headers = self._data.header,
delegates = {
"time": self._delegate_time,
},
data = self._data,
undo = self._undo_stack,
opt_data = self._study.time_system
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -111,7 +119,7 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow):
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.verticalLayout.addWidget(self.toolbar)
@@ -28,3 +28,11 @@ table_headers = {
"discharge": _translate("LateralContribution", "Discharge (m³/s)"),
"z": _translate("LateralContribution", "Z (m)")
}
def retranslate():
table_headers["x"] = _translate("LateralContribution", "X")
table_headers["y"] = _translate("LateralContribution", "Y")
table_headers["time"] = _translate("LateralContribution", "Time")
table_headers["date"] = _translate("LateralContribution", "Date")
table_headers["discharge"] = _translate("LateralContribution", "Discharge (m³/s)")
table_headers["z"] = _translate("LateralContribution", "Z (m)")
+23 -49
View File
@@ -44,6 +44,8 @@ from View.LateralContribution.UndoCommand import (
from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.LateralContribution.translate import *
logger = logging.getLogger()
@@ -102,26 +104,13 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel):
def __init__(self, data=None, undo=None, tab=""):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._data = data
self._undo = undo
self._tab = tab
self._lcs = self._data.lateral_contribution
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.lateral_contribution
self._tab = self._opt_data
def rowCount(self, parent):
return self._lcs.len(self._tab)
def columnCount(self, parent):
return len(self._headers)
return self._lst.len(self._tab)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
@@ -131,26 +120,19 @@ class TableModel(QAbstractTableModel):
column = index.column()
if self._headers[column] == "name":
return self._lcs.get(self._tab, row).name
return self._lst.get(self._tab, row).name
elif self._headers[column] == "type":
t = self._lcs.get(self._tab, row).lctype
t = self._lst.get(self._tab, row).lctype
return long_types[t]
elif self._headers[column] == "edge":
n = self._lcs.get(self._tab, row).edge
n = self._lst.get(self._tab, row).edge
if n is None:
return _translate("LateralContribution", "Not associate")
return n.name
elif self._headers[column] == "begin_kp":
return self._lcs.get(self._tab, row).begin_kp
return self._lst.get(self._tab, row).begin_kp
elif self._headers[column] == "end_kp":
return self._lcs.get(self._tab, row).end_kp
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return self._lst.get(self._tab, row).end_kp
return QVariant()
@@ -165,32 +147,32 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._lcs, self._tab, row, value
self._lst, self._tab, row, value
)
)
elif self._headers[column] == "type":
key = next(k for k, v in long_types.items() if v == value)
self._undo.push(
SetTypeCommand(
self._lcs, self._tab, row, LC_types[key]
self._lst, self._tab, row, LC_types[key]
)
)
elif self._headers[column] == "edge":
self._undo.push(
SetEdgeCommand(
self._lcs, self._tab, row, self._data.edge(value)
self._lst, self._tab, row, self._data.edge(value)
)
)
elif self._headers[column] == "begin_kp":
self._undo.push(
SetBeginCommand(
self._lcs, self._tab, row, value
self._lst, self._tab, row, value
)
)
elif self._headers[column] == "end_kp":
self._undo.push(
SetEndCommand(
self._lcs, self._tab, row, value
self._lst, self._tab, row, value
)
)
except Exception as e:
@@ -205,7 +187,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
AddCommand(
self._lcs, self._tab, row
self._lst, self._tab, row
)
)
@@ -217,7 +199,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
DelCommand(
self._lcs, self._tab, rows
self._lst, self._tab, rows
)
)
@@ -229,7 +211,7 @@ class TableModel(QAbstractTableModel):
self._undo.push(
SortCommand(
self._lcs, self._tab, False
self._lst, self._tab, False
)
)
@@ -246,7 +228,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._lcs, self._tab,"up", row
self._lst, self._tab, "up", row
)
)
@@ -254,7 +236,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._lcs):
if row > len(self._lst):
return
target = row
@@ -263,17 +245,9 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push(
MoveCommand(
self._lcs, self._tab,"down", row
self._lst, self._tab, "down", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+18 -15
View File
@@ -56,7 +56,10 @@ from View.LateralContribution.Table import (
from View.Plot.MplCanvas import MplCanvas
from View.Geometry.PlotXY import PlotXY
from View.LateralContribution.translate import *
from View.LateralContribution.translate import (
long_types, table_headers, LC_types,
retranslate,
)
from View.LateralContribution.Edit.Window import EditLateralContributionWindow
_translate = QCoreApplication.translate
@@ -90,17 +93,10 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
for t in ["liquid", "solid", "suspenssion"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
data = self._study.river,
undo = self._undo_stack,
tab = t,
)
table.setModel(self._table[t])
self._delegate_type = ComboBoxDelegate(
data = self._study.river,
mode = "type",
@@ -114,13 +110,20 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow):
parent=self
)
table.setItemDelegateForColumn(
1, self._delegate_type
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
table_view = table,
table_headers = table_headers,
editable_headers = True,
delegates = {
"type": self._delegate_type,
"edge": self._delegate_edge,
},
data = self._study.river,
undo = self._undo_stack,
opt_data = t,
)
table.setItemDelegateForColumn(
2, self._delegate_edge
)
table.setModel(self._table[t])
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
+12
View File
@@ -45,3 +45,15 @@ LC_types = {
"RA": Rain,
"EV": Evaporation,
}
def retranslate():
table_headers["name"] = _translate("LateralContribution", "Name")
table_headers["type"] = _translate("LateralContribution", "Type")
table_headers["edge"] = _translate("LateralContribution", "Reach")
table_headers["begin_kp"] = _translate("LateralContribution", "Begin kp (m)")
table_headers["end_kp"] = _translate("LateralContribution", "End kp (m)")
long_types["ND"] = _translate("LateralContribution", "Not defined")
long_types["LC"] = _translate("LateralContribution", "Lateral contribution")
long_types["RA"] = _translate("LateralContribution", "Rain")
long_types["EV"] = _translate("LateralContribution", "Evaporation")
+7 -3
View File
@@ -131,10 +131,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self.dialog_reopen_study()
def set_title(self):
title = "(dbg) " if self.conf.debug else ""
if self.model is not None:
self.setWindowTitle(f"PAMHYR - {self.model.name}")
title += f"Pamhyr2 - {self.model.name}"
self.setWindowTitle(title)
else:
self.setWindowTitle("PAMHYR")
title += "Pamhyr2"
self.setWindowTitle(title)
def enable_actions(self, action:str, enable:bool):
"""Enable of disable an action componant
@@ -255,9 +259,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
logger.setLevel(logging.INFO)
logger.info("Set logging level to INFO")
def setup_debug_mode(self, init = False):
menu = self.findChild(QMenu, "menu_help")
self.set_title()
if init:
self.debug_action = QAction("Debug", self)
+9 -6
View File
@@ -584,7 +584,7 @@ class GraphWidget(QGraphicsView):
if item:
item.update()
except Exception as e:
logger.debug(str(e))
logger.warning(str(e))
def selected_new_edge_src_node(self):
"""The current node item selected to add new edge
@@ -626,12 +626,15 @@ class GraphWidget(QGraphicsView):
Returns:
Nothing
"""
previous_edge = self._current_edge
self._current_edge = edge
self.graph.set_current_reach(edge.edge)
try:
previous_edge = self._current_edge
self._current_edge = edge
self.graph.set_current_reach(edge.edge)
if previous_edge:
previous_edge.update()
if previous_edge:
previous_edge.update()
except Exception as e:
logger.warning(str(e))
def reset_selection(self):
"""Reset all the selected items
+72 -117
View File
@@ -25,6 +25,7 @@ from Model.Network.Graph import Graph
from View.ASubWindow import ASubWindow
from View.Network.GraphWidget import GraphWidget
from View.Network.UndoCommand import *
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import (
Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal,
@@ -69,151 +70,105 @@ class ComboBoxDelegate(QItemDelegate):
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TrueFalseComboBoxDelegate(QItemDelegate):
def __init__(self, parent=None):
super(TrueFalseComboBoxDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
self.editor.addItems(["true", "false"])
self.editor.setCurrentText("true" if index.data(Qt.DisplayRole) else "false")
return self.editor
def setEditorData(self, editor, index):
value = str(index.data(Qt.DisplayRole))
self.editor.currentTextChanged.connect(self.currentItemChanged)
def setModelData(self, editor, model, index):
value = str(editor.currentText()) == "true"
model.setData(index, value)
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class GraphTableModel(QAbstractTableModel):
def __init__(self, headers=[], graph=None, undo = None, rows_type="nodes"):
super(QAbstractTableModel, self).__init__()
self.headers = headers
self.graph = graph
self._type = rows_type
self._undo = undo
if self._type == "nodes":
self.rows = graph.nodes()
elif self._type == "edges":
self.rows = graph.edges()
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if self.headers[index.column()] != "type":
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self.rows)
def columnCount(self, parent):
return len(self.headers)
class NodeTableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.nodes()
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
if self.headers[index.column()] == "type":
node = self.rows[index.row()]
if self._headers[index.column()] == "type":
node = self._lst[index.row()]
ret = "internal"
if not self.graph.is_enable_node(node):
if not self._data.is_enable_node(node):
ret = "disable"
elif self.graph.is_upstream_node(node):
elif self._data.is_upstream_node(node):
ret = "upstream"
elif self.graph.is_downstream_node(node):
elif self._data.is_downstream_node(node):
ret = "downstream"
return ret
return self.rows[index.row()][self.headers[index.column()]]
def headerData(self, section, orientation, role):
if (role == Qt.ItemDataRole.DisplayRole and
orientation == Qt.Orientation.Horizontal):
return self.headers[section].capitalize()
return QVariant()
return self._lst[index.row()][self._headers[index.column()]]
@pyqtSlot()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
try:
if (self.headers[index.column()] == "node1" or
self.headers[index.column()] == "node2"):
node = self.graph.node(value)
self._undo.push(
SetNodeCommand(
self.graph,
self.rows[index.row()],
self.headers[index.column()],
node
)
)
# elif self.headers[index.column()] == "enable":
# self._undo.push(
# EnableEdgeCommand(
# self.rows[index.row()], value
# )
# )
else:
self._undo.push(
SetCommand(
self.rows[index.row()],
self.headers[index.column()],
value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
if not index.isValid():
return False
if role == Qt.EditRole:
try:
self._undo.push(
SetCommand(
self._lst[index.row()],
self._headers[index.column()],
value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index, [Qt.DisplayRole])
self.layoutChanged.emit()
return True
self.dataChanged.emit(index, index)
else:
return False
def update(self):
if self._type == "nodes":
self.rows = self.graph.nodes()
elif self._type == "edges":
self.rows = self.graph.edges()
self._lst = self._data.nodes()
self.layoutChanged.emit()
def reverse_edge(self, index):
if self._type == "edges":
tmp = self.rows[index.row()].node1
self.rows[index.row()].node1 = self.rows[index.row()].node2
self.rows[index.row()].node2 = tmp
class EdgeTableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.edges()
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
return self._lst[index.row()][self._headers[index.column()]]
@pyqtSlot()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role != Qt.EditRole:
return QVariant()
try:
if (self._headers[index.column()] == "node1" or
self._headers[index.column()] == "node2"):
node = self.graph.node(value)
self._undo.push(
SetNodeCommand(
self._data,
self._lst[index.row()],
self._headers[index.column()],
node
)
)
else:
self._undo.push(
SetCommand(
self._lst[index.row()],
self._headers[index.column()],
value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index, [Qt.DisplayRole])
self.layoutChanged.emit()
return True
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
self.dataChanged.emit(index, index)
def redo(self):
self._undo.redo()
def update(self):
self._lst = self._data.edges()
self.layoutChanged.emit()
+31 -24
View File
@@ -16,6 +16,8 @@
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import (
QKeySequence,
)
@@ -37,9 +39,16 @@ from Model.River import RiverNode, RiverReach, River
from View.ASubWindow import ASubMainWindow
from View.Network.GraphWidget import GraphWidget
from View.Network.UndoCommand import *
from View.Network.Table import (
GraphTableModel, ComboBoxDelegate, TrueFalseComboBoxDelegate,
from View.Network.translate import (
table_headers_node, table_headers_edge,
retranslate,
)
from View.Network.Table import (
ComboBoxDelegate, NodeTableModel, EdgeTableModel,
)
_translate = QCoreApplication.translate
class NetworkWindow(ASubMainWindow):
def __init__(self, model=None, title="River network", parent=None):
@@ -69,43 +78,41 @@ class NetworkWindow(ASubMainWindow):
self.redo_sc = QShortcut(QKeySequence.Redo, self)
def setup_table(self):
# Nodes table
retranslate()
self._nodes_model = GraphTableModel(
headers = ["name", "type"],
graph = self._graph,
rows_type = "nodes",
# Nodes table
table = self.find(QTableView, "tableView_nodes")
self._nodes_model = NodeTableModel(
table_view = table,
table_headers = table_headers_node,
editable_headers = ["name"],
data = self._graph,
undo = self._undo_stack,
)
table = self.find(QTableView, "tableView_nodes")
table.setModel(self._nodes_model)
#table.resizeColumnsToContents()
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# Edges table
self._reachs_model = GraphTableModel(
headers = ["name", # "enable",
"node1", "node2"],
graph = self._graph,
rows_type = "edges",
undo = self._undo_stack,
)
table = self.find(QTableView, "tableView_reachs")
self.delegate_combobox = ComboBoxDelegate(
graph = self._graph,
parent = self,
)
self.delegate_true_false_combobox = TrueFalseComboBoxDelegate(
parent = self,
)
table = self.find(QTableView, "tableView_reachs")
self._reachs_model = EdgeTableModel(
table_view = table,
table_headers = table_headers_edge,
editable_headers = ["name", "node1", "node2"],
delegates = {
"node1": self.delegate_combobox,
"node2": self.delegate_combobox,
},
data = self._graph,
undo = self._undo_stack,
)
table.setModel(self._reachs_model)
# table.setItemDelegateForColumn(1, self.delegate_true_false_combobox)
table.setItemDelegateForColumn(1, self.delegate_combobox)
table.setItemDelegateForColumn(2, self.delegate_combobox)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
#table.resizeColumnsToContents()
+40
View File
@@ -0,0 +1,40 @@
# translate.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 -*-
from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
table_headers_node = {
"name": _translate("Network", "Name"),
"type": _translate("Network", "Type"),
}
table_headers_edge = {
"name": _translate("Network", "Name"),
"node1": _translate("Network", "Source node"),
"node2": _translate("Network", "Destination node"),
}
def retranslate():
table_headers_node["name"] = _translate("Network", "Name")
table_headers_node["type"] = _translate("Network", "Type")
table_headers_edge["name"] = _translate("Network", "Name")
table_headers_edge["node1"] = _translate("Network", "Source node")
table_headers_edge["node2"] = _translate("Network", "Destination node")
+2
View File
@@ -16,6 +16,8 @@
# -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError
class APlot(object):
def __init__(self, canvas=None, data=None, toolbar=None):
super(APlot, self).__init__()
@@ -1,4 +1,4 @@
# navigation_toolbar_2qt.py -- Pamhyr
# PamhyrToolbar.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
@@ -19,10 +19,12 @@
import os
import logging
import matplotlib as mpl
from matplotlib.backends import qt_compat
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QAction
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
from PyQt5.QtCore import pyqtSignal, QSize
_translate = QtCore.QCoreApplication.translate
@@ -31,21 +33,21 @@ logger = logging.getLogger()
file_path = os.path.abspath(os.path.dirname(__file__))
class PamHyrNavigationToolbar2QT(NavigationToolbar2QT):
class PamhyrPlotToolbar(NavigationToolbar2QT):
"""
Cette classe est une personnalisation de la toolbar (NavigationToolbar2QT). Elle permet l'internationnalisation et
l'ajout d'autres boutons à la toolbar initiale
MatPlotLib plot toolbar for Pamhyr
"""
isometric_signal = pyqtSignal(str)
def __init__(self, canvas, parent, items = ["home", "move", "zoom", "save"]):
"""
Args:
canvas: canvas de matplotlib
parent: parent du canvas
"""
self.my_canvas = canvas
"""PamhyrPlotToolbar
Args:
canvas: MatPlotLib canvas
parent: parent Qt object
items: Enables items (default: "home", "move", "zoom", "save")
"""
self._canvas = canvas
self._items = items
self.toolitems = [
(None, None, None, None),
@@ -118,7 +120,7 @@ class PamHyrNavigationToolbar2QT(NavigationToolbar2QT):
self.toolitems.append((None, None, None, None))
NavigationToolbar2QT.__init__(self, canvas, parent)
btn_size = QSize(30, 30)
btn_size = QSize(40, 28)
actions = self.findChildren(QAction)
for a, i in icons:
@@ -161,18 +163,15 @@ class PamHyrNavigationToolbar2QT(NavigationToolbar2QT):
QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton
)
def cursor1(self):
logger.info("TODO: cursor1")
def isometric_view(self):
self.my_canvas.axes.axis("equal")
self.my_canvas.figure.canvas.draw_idle()
self._canvas.axes.axis("equal")
self._canvas.figure.canvas.draw_idle()
self.isometric_signal[str].emit("vue iso")
def non_isometric_view(self):
self.my_canvas.axes.axis("tight")
self.my_canvas.toolbar.update()
self.my_canvas.figure.canvas.draw_idle()
self._canvas.axes.axis("tight")
self._canvas.toolbar.update()
self._canvas.figure.canvas.draw_idle()
def toolitems_translation(self):
self._actions['home'].setToolTip(_translate("Toolbar", "Default view"))
+12 -8
View File
@@ -43,7 +43,7 @@ from PyQt5.QtWidgets import (
)
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Results.PlotXY import PlotXY
from View.Results.PlotAC import PlotAC
@@ -53,7 +53,10 @@ from View.Results.PlotSedReach import PlotSedReach
from View.Results.PlotSedProfile import PlotSedProfile
from View.Results.Table import TableModel
from View.Results.translate import *
from View.Results.translate import (
table_headers_reach, table_headers_profile,
retranslate,
)
from View.Stricklers.Window import StricklersWindow
_translate = QCoreApplication.translate
@@ -102,6 +105,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
for t in ["reach", "profile"]:
@@ -131,7 +135,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
@@ -150,7 +154,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamHyrNavigationToolbar2QT(
self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self
)
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
@@ -168,7 +172,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_3 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_3.setObjectName("canvas_3")
self.toolbar_3 = PamHyrNavigationToolbar2QT(
self.toolbar_3 = PamhyrPlotToolbar(
self.canvas_3, self
)
self.plot_layout_3 = self.find(QVBoxLayout, "verticalLayout_3")
@@ -186,7 +190,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_4 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_4.setObjectName("canvas_4")
self.toolbar_4 = PamHyrNavigationToolbar2QT(
self.toolbar_4 = PamhyrPlotToolbar(
self.canvas_4, self
)
self.plot_layout_4 = self.find(QVBoxLayout, "verticalLayout_hydrograph")
@@ -204,7 +208,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_5 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_5.setObjectName("canvas_5")
self.toolbar_5 = PamHyrNavigationToolbar2QT(
self.toolbar_5 = PamhyrPlotToolbar(
self.canvas_5, self
)
self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach")
@@ -224,7 +228,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.canvas_6 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_6.setObjectName("canvas_6")
self.toolbar_6 = PamHyrNavigationToolbar2QT(
self.toolbar_6 = PamhyrPlotToolbar(
self.canvas_6, self
)
self.plot_layout_6 = self.find(QVBoxLayout, "verticalLayout_sed_profile")
+6
View File
@@ -28,3 +28,9 @@ table_headers_profile = {
"name": _translate("Results", "Name"),
"kp": _translate("Results", "KP (m)"),
}
def retranslate():
table_headers_reach["name"] = _translate("Results", "Reach name")
table_headers_profile["name"] = _translate("Results", "Name")
table_headers_profile["kp"] = _translate("Results", "KP (m)")
+4 -3
View File
@@ -41,12 +41,12 @@ from PyQt5.QtWidgets import (
)
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.SedimentLayers.Edit.UndoCommand import *
from View.SedimentLayers.Edit.Table import *
from View.SedimentLayers.Edit.Plot import Plot
from View.SedimentLayers.Edit.translate import *
from View.SedimentLayers.Edit.translate import table_headers, retranslate
_translate = QCoreApplication.translate
@@ -89,6 +89,7 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
table = self.find(QTableView, f"tableView")
@@ -106,7 +107,7 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
@@ -12,3 +12,11 @@ table_headers = {
"sigma": _translate("SedimentLayers", "Sigma"),
"critical_constraint": _translate("SedimentLayers", "Critical constraint"),
}
def retranslate():
table_headers["name"] = _translate("SedimentLayers", "Name")
# table_headers["type"] = _translate("SedimentLayers", "Type")
table_headers["height"] = _translate("SedimentLayers", "Height")
table_headers["d50"] = _translate("SedimentLayers", "D50")
table_headers["sigma"] = _translate("SedimentLayers", "Sigma")
table_headers["critical_constraint"] = _translate("SedimentLayers", "Critical constraint")
@@ -29,9 +29,9 @@ from View.SedimentLayers.Reach.Profile.Table import *
from View.SedimentLayers.Reach.Profile.Plot import Plot
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.SedimentLayers.Reach.Profile.translate import *
from View.SedimentLayers.Reach.Profile.translate import table_headers, retranslate
from View.SedimentLayers.Window import SedimentLayersWindow
_translate = QCoreApplication.translate
@@ -85,6 +85,8 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView")
self._table = TableModel(
study = self._study,
@@ -110,7 +112,7 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
@@ -11,3 +11,10 @@ table_headers = {
"name": _translate("SedimentLayers", "Name"),
"sl": _translate("SedimentLayers", "Sediment layers"),
}
def retranslate():
table_headers["x"] = _translate("SedimentLayers", "X (m)")
table_headers["y"] = _translate("SedimentLayers", "Y (m)")
table_headers["z"] = _translate("SedimentLayers", "Z (m)")
table_headers["name"] = _translate("SedimentLayers", "Name")
table_headers["sl"] = _translate("SedimentLayers", "Sediment layers")
+5 -3
View File
@@ -30,9 +30,9 @@ from View.SedimentLayers.Reach.Plot import Plot
from View.SedimentLayers.Reach.SLDialog import SLDialog
from View.Plot.MplCanvas import MplCanvas
from View.Plot.navigation_toolbar_2qt import PamHyrNavigationToolbar2QT
from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.SedimentLayers.Reach.translate import *
from View.SedimentLayers.Reach.translate import table_headers, retranslate
from View.SedimentLayers.Window import SedimentLayersWindow
from View.SedimentLayers.Reach.Profile.Window import ProfileSedimentLayersWindow
@@ -73,6 +73,8 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView")
self._table = TableModel(
study = self._study,
@@ -98,7 +100,7 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT(
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2")
@@ -9,3 +9,8 @@ table_headers = {
"kp": _translate("SedimentLayers", "KP (m)"),
"sl": _translate("SedimentLayers", "Sediment layers"),
}
def retranslate():
table_headers["name"] = _translate("SedimentLayers", "Name")
table_headers["kp"] = _translate("SedimentLayers", "KP (m)")
table_headers["sl"] = _translate("SedimentLayers", "Sediment layers")
+3 -1
View File
@@ -30,7 +30,7 @@ from View.SedimentLayers.Table import *
from View.SedimentLayers.Edit.Plot import Plot
from View.Plot.MplCanvas import MplCanvas
from View.SedimentLayers.translate import *
from View.SedimentLayers.translate import table_headers, retranslate
from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow
@@ -70,6 +70,8 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView")
self._table = TableModel(
study = self._study,
+4
View File
@@ -8,3 +8,7 @@ table_headers = {
"name": _translate("SedimentLayers", "Name"),
"comment": _translate("SedimentLayers", "Comment"),
}
def retranslate():
table_headers["name"] = _translate("SedimentLayers", "Name")
table_headers["comment"] = _translate("SedimentLayers", "Comment")
+4 -27
View File
@@ -34,37 +34,19 @@ from PyQt5.QtWidgets import (
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Stricklers.UndoCommand import (
SetNameCommand, SetCommentCommand,
SetMinorCommand, SetMediumCommand,
AddCommand, DelCommand, SortCommand,
)
from View.Stricklers.translate import *
logger = logging.getLogger()
_translate = QCoreApplication.translate
class TableModel(QAbstractTableModel):
def __init__(self, data=None, undo=None, tab=""):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._data = data
self._undo = undo
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self._headers)
class TableModel(PamhyrTableModel):
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
@@ -83,12 +65,7 @@ class TableModel(QAbstractTableModel):
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
@pyqtSlot()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
+1 -1
View File
@@ -100,7 +100,7 @@ class AddCommand(QUndoCommand):
def redo(self):
if self._new is None:
self._new = self._data.add(self._index)
self._new = self._data.new(self._index)
else:
self._data.insert(self._index, self._new)
+6
View File
@@ -44,6 +44,7 @@ from Model.Stricklers.Stricklers import Stricklers
from View.Stricklers.UndoCommand import PasteCommand
from View.Stricklers.Table import TableModel
from View.Stricklers.translate import table_headers, retranslate
_translate = QCoreApplication.translate
@@ -74,7 +75,9 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow):
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
retranslate()
self._table = {}
for t in ["app", "study"]:
@@ -85,6 +88,9 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow):
data = self._config.stricklers
self._table[t] = TableModel(
table_view = table,
table_headers = table_headers,
editable_headers = ["name", "comment", "minor", "medium"],
data = data,
undo = self._undo_stack,
)
+6
View File
@@ -30,3 +30,9 @@ table_headers = {
"medium": _translate("LateralContribution", "Medium bed"),
"comment": _translate("LateralContribution", "Comment"),
}
def retranslate():
table_headers["name"] = _translate("LateralContribution", "Name")
table_headers["minor"] = _translate("LateralContribution", "Minor bed")
table_headers["medium"] = _translate("LateralContribution", "Medium bed")
table_headers["comment"] = _translate("LateralContribution", "Comment")
+70
View File
@@ -0,0 +1,70 @@
# PamhyrDelegate.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 logging
from PyQt5.QtCore import (
Qt, QRect, QTime, QDateTime, pyqtSlot,
)
from PyQt5.QtWidgets import (
QSpinBox, QTimeEdit, QDateTimeEdit, QItemDelegate,
)
from View.Tools.PamhyrWidget import (
PamhyrExtendedTimeEdit, PamhyrExtendedDateTimeEdit
)
logger = logging.getLogger()
class PamhyrExTimeDelegate(QItemDelegate):
def __init__(self, data=None, mode="time", parent=None):
super(PamhyrExTimeDelegate, self).__init__(parent)
self._data = data
self._mode = mode
def createEditor(self, parent, option, index):
if self._mode == "time":
self.editor = PamhyrExtendedTimeEdit(parent=parent)
else:
self.editor = PamhyrExtendedDateTimeEdit(parent=parent)
value = index.data(Qt.DisplayRole)
self.editor.set_time(value)
logger.debug(str(value))
return self.editor
def setModelData(self, editor, model, index):
time = editor.get_time()
if self._mode == "time":
model.setData(index, int(time.total_seconds()))
else:
logger.debug(str(time.timestamp()))
model.setData(index, int(time.timestamp()))
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
+140
View File
@@ -0,0 +1,140 @@
# PamhyrTable.py -- Pamhyr abstract table model
# 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 logging
import traceback
from tools import trace, timer
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,
)
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 _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 = {},
data=None,
undo=None,
opt_data=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._data = data
self._opt_data = opt_data
self._undo = undo
self._lst = []
self._setup_delegates()
self._setup_lst()
def _setup_lst(self):
self._lst = self._data
def flags(self, index):
column = index.column()
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if (self._editable_headers or
self._headers[column] in self._editable_headers):
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._lst)
def columnCount(self, parent):
return len(self._headers)
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self._table_headers[self._headers[section]]
return QVariant()
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()
+92
View File
@@ -0,0 +1,92 @@
# PamhyrWidget.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 logging
from PyQt5.QtCore import (
QModelIndex, QRect, QTime, QDateTime,
)
from PyQt5.QtWidgets import (
QSpinBox, QTimeEdit, QDateTimeEdit, QItemDelegate,
)
from View.ASubWindow import AWidget
logger = logging.getLogger()
class PamhyrExtendedTimeEdit(AWidget):
def __init__(self, parent=None):
super(PamhyrExtendedTimeEdit, self).__init__(
ui="extendedTimeEdit",
parent=parent
)
self.parent = parent
self.spinBox_days = self.find(QSpinBox, "spinBox_days")
self.timeEdit = self.find(QTimeEdit, "timeEdit")
def set_time(self, time):
days = 0
stime = time
# if ',' in time, time format is 'DD days, HH:MM:SS',
# otherelse is 'HH:MM:SS'
if "," in time:
s = time.strip().split(" ")
days = int(s[0])
stime = s[-1]
qtime = QTime.fromString(
stime,
"h:mm:ss"
)
self.spinBox_days.setValue(days)
self.timeEdit.setTime(qtime)
def get_time(self):
days = self.spinBox_days.value()
time = self.timeEdit.time().toPyTime()
secs = (
(time.hour * 3600) +
(time.minute * 60) +
time.second
)
return timedelta(days=days, seconds=secs)
class PamhyrExtendedDateTimeEdit(AWidget):
def __init__(self, parent=None):
super(ExtendedDateTimeEdit, self).__init__(
ui="extendedDateTimeEdit",
parent=parent
)
self.parent = parent
self.dateTimeEdit = self.find(QDateTimeEdit, "dateTimeEdit")
def set_time(self, time):
qdatetime = QDateTime.fromString(
time,
"yyyy-MM-dd hh:mm:ss"
)
self.dateTimeEdit.setDateTime(qdatetime)
def get_time(self):
time = self.dateTimeEdit.dateTime().toPyDateTime()
return time
+1 -1
View File
@@ -39,7 +39,7 @@
</font>
</property>
<property name="text">
<string>About PAMHYR</string>
<string>About Pamhyr2</string>
</property>
</widget>
</item>
+2 -2
View File
@@ -96,7 +96,7 @@ class Config(SQL):
INSERT INTO solver VALUES (
'mage8',
'default-mage',
'Default PAMHYR mage 8 version',
'Default Pamhyr2 mage 8 version',
'', '', '',
@@ -265,7 +265,7 @@ class Config(SQL):
posix = os.name == 'posix'
ctor = solver_type_list["mage8"]
new = ctor("default-mage")
new._description = "Default PAMHYR mage 8 version"
new._description = "Default Pamhyr2 mage 8 version"
new._cmd_solver = f""""@install_dir/mage/mage{"" if posix else ".exe"}" -fp=1 @input"""
self._solvers.append(new)
+109
View File
@@ -0,0 +1,109 @@
# init.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 sys, os
import locale
import logging
from PyQt5.QtCore import QTranslator
from tools import (
reset_timers, display_timers, timer,
logger_color_blue, logger_color_red, logger_color_green, logger_color_reset
)
from config import Config
from View.MainWindow import ApplicationWindow
from Model.Study import Study
# Configure logger
logging.basicConfig(
level=logging.DEBUG,
format=(f'[{logger_color_blue()}PAMHYR{logger_color_reset()}]' +
f'[{logger_color_green()}%(levelname)s{logger_color_reset()}]' +
' %(message)s')
)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
try:
log = os.path.join(
os.path.dirname(Config.filename()), "log.txt"
)
logfile = open(log, "w+")
handler = logging.StreamHandler(logfile)
formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
except:
logger.error("Failed to create logfile...")
def license():
"Display in stdout Pamhyr infomation about licence and version"
blue = lambda s: logger.info(f"{logger_color_blue()}{s}{logger_color_reset()}")
blue("""`7MM\"""Mq. db `7MMM. ,MMF'`7MMF' `7MMF'`YMM' `MM'`7MM\"""Mq.""")
blue(""" MM `MM. ;MM: MMMb dPMM MM MM VMA ,V MM `MM.""")
blue(""" MM ,M9 ,V^MM. M YM ,M MM MM MM VMA ,V MM ,M9 pd*"*b.""")
blue(""" MMmmdM9 ,M `MM M Mb M' MM MMmmmmmmMM VMMP MMmmdM9 (O) j8""")
blue(""" MM AbmmmqMA M YM.P' MM MM MM MM MM YM. ,;j9""")
blue(""" MM A' VML M `YM' MM MM MM MM MM `Mb. ,-='""")
blue(""".JMML. .AMA. .AMMA..JML. `' .JMML..JMML. .JMML. .JMML. .JMML. .JMM. Ammmmmmm""")
with open(os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"VERSION"
)
), "r") as f:
version = f.readline().strip()
logger.info(f"version: {logger_color_green()}{version}{logger_color_reset()}")
logger.info("license: pamhyr Copyright (C) 2023 INRAE")
logger.info("license: This program comes with ABSOLUTELY NO WARRANTY.")
logger.info("license: This is free software, and you are welcome to redistribute it")
logger.info("license: under certain conditions.")
def setup_lang(app, conf:Config):
"Return QTranslator configured for current language"
lang_file = ""
translator = QTranslator()
if conf.lang == "":
# System language
sys_lang = locale.getdefaultlocale()
if "fr" in sys_lang[0]:
lang_file = os.path.dirname(__file__) + "/lang/fr.qm"
elif conf.lang == "fr":
# French
lang_file = os.path.dirname(__file__) + "/lang/fr.qm"
else:
# English default language
lang_file = ""
if lang_file != "":
logger.info(f"Load lang file: {lang_file}")
ok = translator.load(lang_file)
if not ok:
logger.error("Failed to load translate file")
return translator
+122 -83
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR" sourcelanguage="en_150">
<!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="en_150">
<context>
<name>About</name>
<message>
@@ -12,7 +11,7 @@
<context>
<name>BoundaryCondition</name>
<message>
<location filename="../View/BoundaryCondition/Table.py" line="143"/>
<location filename="../View/BoundaryCondition/Table.py" line="132"/>
<source>Not associate</source>
<translation>Non associer</translation>
</message>
@@ -78,7 +77,7 @@
</message>
<message>
<location filename="../View/BoundaryCondition/Edit/translate.py" line="12"/>
<source>Discharge (m³/s)</source>
<source>Discharge (m&#xb3;/s)</source>
<translation type="obsolete">Débit (m³/s)</translation>
</message>
<message>
@@ -106,6 +105,11 @@
<source>ZDischarge</source>
<translation>HauteurDébit</translation>
</message>
<message encoding="UTF-8">
<location filename="../View/BoundaryCondition/Edit/translate.py" line="28"/>
<source>Discharge (m³/s)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Checker</name>
@@ -486,12 +490,12 @@
<message>
<location filename="../View/ui/about.ui" line="42"/>
<source>About PAMHYR</source>
<translation>À propos de PAMHYR</translation>
<translation type="obsolete">À propos de PAMHYR</translation>
</message>
<message>
<location filename="../View/ui/about.ui" line="56"/>
<source>Version: @version</source>
<translation type="unfinished"></translation>
<translation>Version: @version</translation>
</message>
<message>
<location filename="../View/ui/about.ui" line="63"/>
@@ -518,6 +522,11 @@
<source>Sigma</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/ui/about.ui" line="42"/>
<source>About Pamhyr2</source>
<translation>À propos de Pamhyr2</translation>
</message>
</context>
<context>
<name>Frictions</name>
@@ -603,7 +612,7 @@
<translation>Commentaire</translation>
</message>
<message>
<location filename="../View/LateralContribution/Table.py" line="141"/>
<location filename="../View/LateralContribution/Table.py" line="130"/>
<source>Not associate</source>
<translation>Non associer</translation>
</message>
@@ -699,9 +708,14 @@
</message>
<message>
<location filename="../View/InitialConditions/translate.py" line="11"/>
<source>Discharge (m³/s)</source>
<source>Discharge (m&#xb3;/s)</source>
<translation type="obsolete">Débit (m³/s)</translation>
</message>
<message encoding="UTF-8">
<location filename="../View/InitialConditions/translate.py" line="27"/>
<source>Discharge (m³/s)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
@@ -1408,7 +1422,7 @@
<message>
<location filename="../View/ui/SolverLog.ui" line="106"/>
<source>results</source>
<translation type="unfinished"></translation>
<translation>Résultats</translation>
</message>
<message>
<location filename="../View/ui/EditSedimentLayers.ui" line="63"/>
@@ -1510,28 +1524,28 @@
<source>Edit sediment layers</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="553"/>
<source>Édition des Tronçons</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="667"/>
<source>Ouvrir une étude</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="679"/>
<source>Enrégistrer étude en cours (Ctrl+S)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="694"/>
<source>Fermer étude en cours (Ctrl+F)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<context encoding="UTF-8">
<name>MainWindowProfile</name>
<message>
<location filename="../View/Geometry/Profile/Window.py" line="83"/>
@@ -1549,17 +1563,17 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="223"/>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="224"/>
<source>MainWindowProfile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="227"/>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="228"/>
<source>Trier les points par ordre croissant de X</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="231"/>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="232"/>
<source>Trier les points par ordre croissant de Y</source>
<translation type="unfinished"></translation>
</message>
@@ -1605,108 +1619,108 @@
<source>Cote (m)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="328"/>
<source>Suppression les lignes incomplètes</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="328"/>
<source>Supprimer les lignes des cellules non renseignées ?</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="371"/>
<source>Suppression des noms répétés</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="382"/>
<source>Etes-vous sûr de vouloir quitter ?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="224"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="225"/>
<source>Insérer un point</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="225"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="226"/>
<source>Supprimer le/les point(s) sélectionnés</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="229"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="230"/>
<source>Trier les points par ordre décroissant de X</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="234"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="235"/>
<source>Trier les points par ordre décroissant de Y</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="236"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="237"/>
<source>Décaler le point sélectionné vers le haut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="238"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="239"/>
<source>Décaler le point sélectionné vers le bas</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="240"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="241"/>
<source>Exporter (dans un fichier) les points du profil au format tabulé</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="242"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="243"/>
<source>Copier la sélection au format tabulé</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="244"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="245"/>
<source>Coller la sélection depuis le presse-papier au format tabulé</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="246"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="247"/>
<source>Vérifier la validité de la saisie et garder ou pas les modifications apportées</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="249"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="250"/>
<source>Annuler toutes les modifications depuis la dernière validation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="251"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="252"/>
<source>Annuler toutes les modifications et revenir à l&apos;état initial</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="253"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="254"/>
<source>Ligne d&apos;eau :
Z : Cote (m)
A : Aire mouillée (mu00B2)
p : Périmètre mouillé (m)
L : Largeur au miroir (m)</source>
<byte value="x9"/>Z : Cote (m)
<byte value="x9"/>A : Aire mouillée (mu00B2)
<byte value="x9"/>p : Périmètre mouillé (m)
<byte value="x9"/>L : Largeur au miroir (m)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="257"/>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="258"/>
<source>&apos;Maj + Clic&apos; : Ligne d&apos;eau &amp; &apos;Ctrl + Clic&apos; : Sélectionner des points</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Table.py" line="142"/>
<source>Abscisse en travers calculée en projétant les points
sur le plan défini par les deux points nommés extrêmes </source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<context encoding="UTF-8">
<name>MainWindow_reach</name>
<message>
<location filename="../View/Frictions/PlotStricklers.py" line="76"/>
@@ -1844,7 +1858,7 @@ sur le plan défini par les deux points nommés extrêmes </source>
<translation type="unfinished">Hauteur (m)</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="146"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="148"/>
<source>Select destination file</source>
<translation type="unfinished"></translation>
</message>
@@ -1898,58 +1912,58 @@ sur le plan défini par les deux points nommés extrêmes </source>
<source> Editer selected profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="164"/>
<source>Édition des profils sélectionnés</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="166"/>
<source>Vous avez sélectionné plus de 5 profils.
Seuls les 5 premiers seront édités.</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="322"/>
<source>Profil N° : </source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/PlotAC.py" line="179"/>
<source>Profil précédent</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/PlotAC.py" line="180"/>
<source>Profil sélectionné</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="328"/>
<source> Trier les profils par ordre décroissant des Pk</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/>
<source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le haut)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="331"/>
<source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le bas)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="339"/>
<source>Vue isométrique (Alt+Z)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
<source>Vue isométrique (Alt+E)</source>
<translation type="unfinished"></translation>
</message>
<message>
<message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
<source>Vue isométrique (Alt+R)</source>
<translation type="unfinished"></translation>
@@ -1958,40 +1972,60 @@ Seuls les 5 premiers seront édités.</source>
<context>
<name>Network</name>
<message>
<location filename="../View/Network/GraphWidget.py" line="809"/>
<location filename="../View/Network/GraphWidget.py" line="812"/>
<source>Add node</source>
<translation>Ajouter un nœud</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="817"/>
<location filename="../View/Network/GraphWidget.py" line="820"/>
<source>Delete the node</source>
<translation>Supprimer un nœud</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="818"/>
<location filename="../View/Network/GraphWidget.py" line="821"/>
<source>Disable the node</source>
<translation>Déactiver un nœud</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="826"/>
<location filename="../View/Network/GraphWidget.py" line="829"/>
<source>Delete the reach</source>
<translation>Supprimer un bief</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="829"/>
<location filename="../View/Network/GraphWidget.py" line="832"/>
<source>Disable the reach</source>
<translation>Déactiver un bief</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="832"/>
<location filename="../View/Network/GraphWidget.py" line="835"/>
<source>Enable the reach</source>
<translation>Activer un bief</translation>
</message>
<message>
<location filename="../View/Network/GraphWidget.py" line="835"/>
<location filename="../View/Network/GraphWidget.py" line="838"/>
<source>Reverse the reach orientation</source>
<translation>Inverser l&apos;orientation du bief</translation>
</message>
<message>
<location filename="../View/Network/translate.py" line="29"/>
<source>Name</source>
<translation>Nom</translation>
</message>
<message>
<location filename="../View/Network/translate.py" line="25"/>
<source>Type</source>
<translation>Type</translation>
</message>
<message>
<location filename="../View/Network/translate.py" line="30"/>
<source>Source node</source>
<translation>Nœud source</translation>
</message>
<message>
<location filename="../View/Network/translate.py" line="31"/>
<source>Destination node</source>
<translation>Nœud destination</translation>
</message>
</context>
<context>
<name>Results</name>
@@ -2037,9 +2071,14 @@ Seuls les 5 premiers seront édités.</source>
</message>
<message>
<location filename="../View/Results/PlotH.py" line="79"/>
<source>Discharge (m³/s)</source>
<source>Discharge (m&#xb3;/s)</source>
<translation type="obsolete">Débit (m³/s)</translation>
</message>
<message encoding="UTF-8">
<location filename="../View/Results/PlotH.py" line="79"/>
<source>Discharge (m³/s)</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SedimentLayers</name>
@@ -2303,42 +2342,42 @@ Seuls les 5 premiers seront édités.</source>
<context>
<name>Toolbar</name>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="182"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="181"/>
<source>Zoom</source>
<translation>Zoom</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="178"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="177"/>
<source>Default view</source>
<translation>Vue par défaut</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="179"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="178"/>
<source>Back to previous view</source>
<translation>Retour a la vue précédente</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="180"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="179"/>
<source>Return to next view</source>
<translation>Retour à la vue suivante</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="181"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="180"/>
<source>Axes panoramic</source>
<translation>Axes panoramique</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="184"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="183"/>
<source>Isometric view (Shift+W)</source>
<translation>Vue isométrique (Shift+W)</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="185"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="184"/>
<source>Auto scale view (Shift+X)</source>
<translation>Vue automatique (Shift+X)</translation>
</message>
<message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="183"/>
<location filename="../View/Plot/PamhyrToolbar.py" line="182"/>
<source>Save the figure</source>
<translation>Sauvegarder la figure</translation>
</message>
+47 -64
View File
@@ -34,85 +34,68 @@ from tools import (
from View.MainWindow import ApplicationWindow
from Model.Study import Study
logging.basicConfig(
level=logging.DEBUG,
format=(f'[{logger_color_blue()}PAMHYR{logger_color_reset()}]' +
f'[{logger_color_green()}%(levelname)s{logger_color_reset()}]' +
' %(message)s')
)
from Scripts.Hello import ScriptHello
from init import license, setup_lang
logger = logging.getLogger()
logger.setLevel(logging.INFO)
try:
log = os.path.join(
os.path.dirname(Config.filename()), "log.txt"
)
logfile = open(log, "w+")
handler = logging.StreamHandler(logfile)
formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
except:
logger.error("Failed to create logfile...")
scripts = {
"hello": ScriptHello,
}
def license():
blue = lambda s: logger.info(f"{logger_color_blue()}{s}{logger_color_reset()}")
def usage(argv):
logger.info("")
logger.info(f"Usage: {argv[0]} <script> <args...>")
logger.info("")
logger.info(f"Available scripts:")
logger.info(f"\thelp\t\tDisplay this message")
logger.info(f"\tgui\t\tRun Pamhyr graphics user interface (by default)")
logger.info(f"\tdebug\t\tRun Pamhyr graphics user interface as debug mode (for developers)")
for s in scripts:
logger.info(f"\t{s}\t\t{scripts[s].description}")
logger.info("")
blue("""`7MM\"""Mq. db `7MMM. ,MMF'`7MMF' `7MMF'`YMM' `MM'`7MM\"""Mq.""")
blue(""" MM `MM. ;MM: MMMb dPMM MM MM VMA ,V MM `MM.""")
blue(""" MM ,M9 ,V^MM. M YM ,M MM MM MM VMA ,V MM ,M9 pd*"*b.""")
blue(""" MMmmdM9 ,M `MM M Mb M' MM MMmmmmmmMM VMMP MMmmdM9 (O) j8""")
blue(""" MM AbmmmqMA M YM.P' MM MM MM MM MM YM. ,;j9""")
blue(""" MM A' VML M `YM' MM MM MM MM MM `Mb. ,-='""")
blue(""".JMML. .AMA. .AMMA..JML. `' .JMML..JMML. .JMML. .JMML. .JMML. .JMM. Ammmmmmm""")
def gui(app:QApplication, conf:Config):
application = ApplicationWindow(conf=conf)
application.show()
with open(os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"VERSION"
)
), "r") as f:
version = f.readline().strip()
logger.info(f"version: {logger_color_green()}{version}{logger_color_reset()}")
logger.info("license: pamhyr Copyright (C) 2023 INRAE")
logger.info("license: This program comes with ABSOLUTELY NO WARRANTY.")
logger.info("license: This is free software, and you are welcome to redistribute it")
logger.info("license: under certain conditions.")
return app.exec_()
def main():
conf = Config.load()
app = QApplication(sys.argv)
translator = QTranslator()
tr = setup_lang(app, conf)
app.installTranslator(tr)
license()
lang_file = ""
if conf.lang == "":
# System language
sys_lang = locale.getdefaultlocale()
if "fr" in sys_lang[0]:
lang_file = os.path.dirname(__file__) + "/lang/fr.qm"
elif conf.lang == "fr":
# French
lang_file = os.path.dirname(__file__) + "/lang/fr.qm"
if len(sys.argv) > 1:
# Run a script
script = sys.argv[1]
if script == "help":
ret = usage(sys.argv)
elif script == "gui":
ret = gui(app, conf)
elif script == "debug":
conf.debug = True
ret = gui(app, conf)
else:
if script not in scripts:
logger.error(f"{logger_color_red()}Invalid script name '{sys.argv[1]}'{logger_color_reset()}")
usage(sys.argv)
sys.exit(-1)
application = scripts[script](app, conf, sys.argv)
ret = application.run()
if ret != 0:
application.usage()
else:
# English default language
lang_file = ""
# No args, run Pamhyr2 interface
ret = gui(app, conf)
if lang_file != "":
ok = translator.load(lang_file)
if not ok:
logger.error("Failed to load translate file")
app.installTranslator(translator)
application = ApplicationWindow(conf=conf)
application.show()
ret = app.exec_()
display_timers()
sys.exit(ret)
+1
View File
@@ -247,6 +247,7 @@ class SQL(object):
self._init_db_file(filename)
def commit(self):
logger.debug("SQL - commit")
self._db.commit()
def _fetch_string(self, s):