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/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
stages: stages:
- downloads
- build - build
- test - test
- package - package
- release - 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 #
######### #########
build-mage-linux: set-version:
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:
stage: build stage: build
tags: tags:
- linux - linux
@@ -126,60 +85,214 @@ build-lang:
paths: paths:
- src/lang/*.qm - 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 # # TESTS #
######### #########
test: # test-windows:
stage: test # stage: test
script: # tags:
- echo "TODO PAMHYR tests" # - 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 # # PACKAGES #
############ ############
linux-package: linux-package-tar:
stage: package stage: package
tags: tags:
- release - release
- linux - linux
needs: needs:
- job: build-lang # - job: test-linux
artifacts: true - job: build-linux
- job: build
artifacts: true
- job: build-mage-linux
artifacts: true artifacts: true
rules: rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
artifacts: artifacts:
paths: paths:
- packages/pamhyr-gnulinux-amd64.tar.xz - linux/pamhyr-gnulinux.tar.xz
script: script:
- cd packages - cd linux
- ./linux.sh - 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 stage: package
tags: tags:
- release - release
- wine - wine
needs: needs:
- job: build-lang # - job: test-windows
artifacts: true - job: build-windows
- job: build
artifacts: true
- job: build-mage-windows
artifacts: true artifacts: true
rules: rules:
- if: $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
artifacts: artifacts:
paths: paths:
- packages/pamhyr-win-amd64.exe - windows/pamhyr-windows.exe
script: script:
- cd packages - cd windows
- ./windows.bat - copy ..\packages\make-windows-exe.bat make-windows-exe.bat
- copy ..\packages\pamhyr.nsi pamhyr.nsi
- .\make-windows-exe.bat
########### ###########
# RELEASE # # RELEASE #
@@ -191,16 +304,25 @@ tag-release:
- release - release
- linux - linux
needs: needs:
- job: linux-package - job: linux-package-tar
artifacts: true 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 artifacts: true
rules: rules:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
artifacts: artifacts:
paths: paths:
- packages/pamhyr-gnulinux-amd64.tar.xz - linux/pamhyr-gnulinux.tar.xz
- packages/pamhyr-win-amd64.exe - windows/pamhyr-windows.zip
- windows/pamhyr-windows.exe
- doc/dev/documentation.pdf
- doc/users/documentation.pdf
script: script:
- cd packages - cd packages
release: release:
@@ -210,11 +332,23 @@ tag-release:
ref: '$CI_COMMIT_SHA' ref: '$CI_COMMIT_SHA'
assets: assets:
links: links:
- name: 'GNU/Linux amd64 (tar.xz)' - name: 'GNU/Linux (tar.xz)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-gnulinux-amd64.tar.xz' url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/linux/pamhyr-gnulinux.tar.xz'
filepath: '/packages/pamhyr-gnulinux-amd64.tar.xz' filepath: '/linux/pamhyr-gnulinux.tar.xz'
link_type: 'package' link_type: 'package'
- name: 'Windows amd64 (exe)' - name: 'Windows (exe)'
url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/packages/pamhyr-win-amd64.exe' url: '${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/windows/pamhyr-windows.exe'
filepath: '/packages/pamhyr-win-amd64.exe' filepath: '/windows/pamhyr-windows.exe'
link_type: 'package' 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 # Run pamhyr
./src/pamhyr.py ./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 LIC_NAME "LICENSE"
!define APP_NAME "PAMHYR" !define APP_NAME "PAMHYR"
Name "PAMHYR" Name "Pamhyr2"
OutFile "pamhyr-win-amd64.exe" OutFile "pamhyr-windows.exe"
LicenseData "..\LICENSE" LicenseData "pamhyr\pamhyr\LICENSE"
LicenseText "I Agree" LicenseText "I Agree"
RequestExecutionLevel admin RequestExecutionLevel admin
Unicode True Unicode True
InstallDir $PROGRAMFILES\PAMHYR InstallDir $PROGRAMFILES\Pamhyr2
InstallDirRegKey HKLM "Software\PAMHYR" "Install_Dir" InstallDirRegKey HKLM "Software\Pamhyr2" "Install_Dir"
Page license Page license
Page components Page components
@@ -23,35 +23,35 @@ Page instfiles
UninstPage uninstConfirm UninstPage uninstConfirm
UninstPage instfiles UninstPage instfiles
Section "PAMHYR" Section "Pamhyr2"
SectionIn RO SectionIn RO
SetOutPath $INSTDIR 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\Pamhyr2" "DisplayName" "NSIS Pamhyr2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "UninstallString" "$INSTDIR\uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2" "NoRepair" 1
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd SectionEnd
Section "Start Menu Shortcuts" Section "Start Menu Shortcuts"
CreateDirectory "$SMPROGRAMS\PAMHYR" CreateDirectory "$SMPROGRAMS\Pamhyr2"
CreateShortcut "$SMPROGRAMS\PAMHYR\Uninstall.lnk" "$INSTDIR\uninstall.exe" CreateShortcut "$SMPROGRAMS\Pamhyr2\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortcut "$SMPROGRAMS\PAMHYR\PAMHYR.lnk" "$INSTDIR\pamhyr.exe" CreateShortcut "$SMPROGRAMS\Pamhyr2\Pamhyr2.lnk" "$INSTDIR\pamhyr\pamhyr.exe"
SectionEnd SectionEnd
Section "Uninstall" Section "Uninstall"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PAMHYR" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pamhyr2"
DeleteRegKey HKLM SOFTWARE\NSIS_PAMHYR DeleteRegKey HKLM SOFTWARE\NSIS_Pamhyr2
Delete "$SMPROGRAMS\PAMHYR\*.lnk" Delete "$SMPROGRAMS\Pamhyr2\*.lnk"
RMDir "$SMPROGRAMS\PAMHYR" RMDir "$SMPROGRAMS\Pamhyr2"
RMDir /R "$INSTDIR" RMDir /R "$INSTDIR"
SectionEnd 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 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 from Model.Except import NotImplementedMethodeError
logger = logging.getLogger() logger = logging.getLogger()
@@ -19,7 +19,7 @@
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
@@ -30,30 +30,12 @@ from Model.BoundaryCondition.BoundaryConditionTypes import (
Solid, Solid,
) )
class BoundaryConditionList(SQLSubModel): class BoundaryConditionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [ _sub_classes = [
BoundaryCondition, 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 @classmethod
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data['status']) new = cls(status = data['status'])
@@ -82,66 +64,12 @@ class BoundaryConditionList(SQLSubModel):
return True 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): def new(self, lst, index):
n = NotDefined(status=self._status) n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n) self._tabs[lst].insert(index, n)
self._status.modified() self._status.modified()
return n 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): def __copy__(self):
new = BoundaryConditionList() new = BoundaryConditionList()
+1 -1
View File
@@ -18,7 +18,7 @@
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
class Friction(SQLSubModel): class Friction(SQLSubModel):
def __init__(self, name:str = "", status = None): def __init__(self, name:str = "", status = None):
+8 -67
View File
@@ -21,26 +21,18 @@ import logging
from copy import copy from copy import copy
from tools import trace, timer 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 from Model.Friction.Friction import Friction
logger = logging.getLogger() logger = logging.getLogger()
class FrictionList(SQLSubModel): class FrictionList(PamhyrModelList):
_sub_classes = [ _sub_classes = [
Friction 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 @classmethod
def _sql_update_0_0_1(cls, execute, version): def _sql_update_0_0_1(cls, execute, version):
execute("ALTER TABLE `section` RENAME TO `friction`") execute("ALTER TABLE `section` RENAME TO `friction`")
@@ -57,7 +49,7 @@ class FrictionList(SQLSubModel):
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data['status']) new = cls(status = data['status'])
new._frictions = Friction._sql_load( new._lst = Friction._sql_load(
execute, data execute, data
) )
@@ -69,70 +61,19 @@ class FrictionList(SQLSubModel):
ok = True ok = True
ind = 0 ind = 0
for friction in self._frictions: for friction in self._lst:
data["ind"] = ind data["ind"] = ind
ok &= friction._sql_save(execute, data = data) ok &= friction._sql_save(execute, data = data)
ind += 1 ind += 1
return ok return ok
def __len__(self):
return len(self._frictions)
@property @property
def frictions(self): def frictions(self):
return self._frictions.copy() return self.lst
def get(self, row):
return self._frictions[row]
def set(self, row, new):
self._frictions[row] = new
self._status.modified()
def new(self, index): def new(self, index):
n = Friction(status = self._status) n = Friction(status = self._status)
self._frictions.insert(index, n) self._lst.insert(index, n)
self._status.modified() self._status.modified()
return n 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 from math import dist
import numpy as np import numpy as np
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Geometry.Point import Point from Model.Geometry.Point import Point
class PointXYZ(Point, SQLSubModel): class PointXYZ(Point, SQLSubModel):
+1 -1
View File
@@ -21,7 +21,7 @@ from typing import List
from tools import timer from tools import timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import ClipboardFormatError from Model.Except import ClipboardFormatError
from Model.Geometry.Profile import Profile from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ 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 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.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ from Model.Geometry.ProfileXYZ import ProfileXYZ
@@ -20,7 +20,7 @@ from copy import copy, deepcopy
from tools import trace, timer from tools import trace, timer
from functools import reduce from functools import reduce
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
class Data(SQLSubModel): class Data(SQLSubModel):
def __init__(self, name:str = "", def __init__(self, name:str = "",
@@ -19,29 +19,14 @@
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.InitialConditions.InitialConditions import InitialConditions from Model.InitialConditions.InitialConditions import InitialConditions
class InitialConditionsDict(SQLSubModel): class InitialConditionsDict(PamhyrModelDict):
_sub_classes = [ _sub_classes = [
InitialConditions, 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 @classmethod
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data["status"]) new = cls(status = data["status"])
@@ -55,7 +40,7 @@ class InitialConditionsDict(SQLSubModel):
) )
if ic is not None: if ic is not None:
new._reach[reach] = ic new._dict[reach] = ic
return new return new
@@ -66,30 +51,12 @@ class InitialConditionsDict(SQLSubModel):
execute("DELETE FROM initial_conditions") execute("DELETE FROM initial_conditions")
for reach in self._reach: for reach in self._dict:
data["reach"] = reach data["reach"] = reach
ok &= self._reach[reach]._sql_save(execute, data) ok &= self._dict[reach]._sql_save(execute, data)
return ok 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): def new(self, reach):
new = InitialConditions(reach = reach, status = self._status) new = InitialConditions(reach = reach, status = self._status)
self.set(reach, new) self.set(reach, new)
@@ -20,7 +20,7 @@ import logging
from tools import trace, timer, old_pamhyr_date_to_timestamp 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 from Model.Except import NotImplementedMethodeError
logger = logging.getLogger() logger = logging.getLogger()
@@ -19,7 +19,7 @@
from copy import copy from copy import copy
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.LateralContribution.LateralContribution import LateralContribution from Model.LateralContribution.LateralContribution import LateralContribution
@@ -27,30 +27,12 @@ from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation, NotDefined, LateralContrib, Rain, Evaporation,
) )
class LateralContributionList(SQLSubModel): class LateralContributionList(PamhyrModelListWithTab):
_tabs_list = ["liquid", "solid", "suspenssion"]
_sub_classes = [ _sub_classes = [
LateralContribution, 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 @classmethod
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data['status']) new = cls(status = data['status'])
@@ -79,66 +61,12 @@ class LateralContributionList(SQLSubModel):
return True 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): def new(self, lst, index):
n = NotDefined(status=self._status) n = NotDefined(status=self._status)
self._tabs[lst].insert(index, n) self._tabs[lst].insert(index, n)
self._status.modified() self._status.modified()
return n 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): def __copy__(self):
new = LateralContributionList() new = LateralContributionList()
+1 -5
View File
@@ -18,7 +18,7 @@
from tools import flatten from tools import flatten
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node from Model.Network.Node import Node
from Model.Network.Edge import Edge from Model.Network.Edge import Edge
@@ -311,10 +311,6 @@ class River(Graph, SQLSubModel):
self._save_submodel(execute, objs, data) self._save_submodel(execute, objs, data)
return True return True
@property
def frictions(self):
return self._frictions
@property @property
def boundary_condition(self): def boundary_condition(self):
return self._boundary_condition return self._boundary_condition
+1 -1
View File
@@ -2,7 +2,7 @@
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
class Layer(SQLSubModel): class Layer(SQLSubModel):
+5 -66
View File
@@ -2,35 +2,18 @@
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrList import PamhyrModelList
from Model.SedimentLayer.SedimentLayer import SedimentLayer from Model.SedimentLayer.SedimentLayer import SedimentLayer
class SedimentLayerList(SQLSubModel): class SedimentLayerList(PamhyrModelList):
_sub_classes = [SedimentLayer] _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 @classmethod
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data["status"]) new = cls(status = data["status"])
new._sl = SedimentLayer._sql_load(execute, data) new._lst = SedimentLayer._sql_load(execute, data)
return new return new
@@ -41,61 +24,17 @@ class SedimentLayerList(SQLSubModel):
execute("DELETE FROM sedimentary_layer") execute("DELETE FROM sedimentary_layer")
execute("DELETE FROM sedimentary_layer_layer") execute("DELETE FROM sedimentary_layer_layer")
for sl in self._sl: for sl in self._lst:
ok &= sl._sql_save(execute, data) ok &= sl._sql_save(execute, data)
return ok return ok
@property @property
def sediment_layers(self): def sediment_layers(self):
return self._sl.copy() return self.lst
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()
def new(self, index): def new(self, index):
n = SedimentLayer(status = self._status) n = SedimentLayer(status = self._status)
self.insert(index, n) self.insert(index, n)
self._status.modified() self._status.modified()
return n 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 tools import trace, timer
from Solver.Solvers import solver_type_list from Solver.Solvers import solver_type_list
from Model.DB import SQLSubModel from Model.Tools.PamhyrList import PamhyrModelList
class Parameter(): class Parameter():
def __init__(self, def __init__(self,
@@ -63,13 +63,13 @@ class Parameter():
return new return new
class SolverParametersList(SQLSubModel): class SolverParametersList(PamhyrModelList):
def __init__(self, solver_type = None, status = None): def __init__(self, solver_type = None, status = None):
super(SolverParametersList, self).__init__() super(SolverParametersList, self).__init__()
self._status = status self._status = status
self._solver = solver_type self._solver = solver_type
self._parameters = list( self._lst = list(
map( map(
lambda t: Parameter.from_tuple(t, self._status), lambda t: Parameter.from_tuple(t, self._status),
solver_type.default_parameters() solver_type.default_parameters()
@@ -194,7 +194,7 @@ class SolverParametersList(SQLSubModel):
) )
ind = 0 ind = 0
for param in self._parameters: for param in self._lst:
sql = ( sql = (
"INSERT INTO " + "INSERT INTO " +
"solver_parameter(ind, name, value, solver) "+ "solver_parameter(ind, name, value, solver) "+
@@ -210,39 +210,36 @@ class SolverParametersList(SQLSubModel):
return True return True
def __len__(self):
return len(self._parameters)
@property @property
def parameters(self): def parameters(self):
return self._parameters.copy() return self.lst
def get(self, index): def get(self, index):
return self._parameters[index] return self._lst[index]
def get_by_key(self, key): def get_by_key(self, key):
try: try:
return next( return next(
filter( filter(
lambda p: p["name"] == key, lambda p: p["name"] == key,
self._parameters self._lst
) )
)["value"] )["value"]
except: except:
return None return None
def set(self, index, new): def set(self, index, new):
self._parameters[index] = new self._lst[index] = new
self._status.modified() self._status.modified()
def set_value(self, key, value): def set_value(self, key, value):
for p in self._parameters: for p in self._lst:
if p["name"] == key: if p["name"] == key:
p["value"] = value p["value"] = value
self._status.modified() self._status.modified()
return return
self._parameters.append( self._lst.append(
Parameter( Parameter(
name = key, name = key,
value = value, value = value,
@@ -253,47 +250,6 @@ class SolverParametersList(SQLSubModel):
def new(self, index): def new(self, index):
n = Parameter(status = self._status) n = Parameter(status = self._status)
self._parameters.insert(index, n) self._lst.insert(index, n)
self._status.modified() self._status.modified()
return n 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 tools import trace, timer
from Model.DB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
class Stricklers(SQLSubModel): class Stricklers(SQLSubModel):
_id_cnt = 0 _id_cnt = 0
+8 -49
View File
@@ -18,22 +18,15 @@
from tools import trace, timer from tools import trace, timer
from Model.DB import SQLSubModel
from Model.Saved import SavedStatus from Model.Saved import SavedStatus
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Stricklers.Stricklers import Stricklers from Model.Stricklers.Stricklers import Stricklers
class StricklersList(SQLSubModel): class StricklersList(PamhyrModelList):
_sub_classes = [ _sub_classes = [
Stricklers, Stricklers,
] ]
def __init__(self, status = None):
if status is None:
status = SavedStatus()
self._status = status
self._stricks = []
@classmethod @classmethod
def _sql_create(cls, execute): def _sql_create(cls, execute):
return cls._create_submodel(execute) return cls._create_submodel(execute)
@@ -46,7 +39,7 @@ class StricklersList(SQLSubModel):
def _sql_load(cls, execute, data = None): def _sql_load(cls, execute, data = None):
new = cls(status = data["status"]) new = cls(status = data["status"])
new._stricks = Stricklers._sql_load( new._lst = Stricklers._sql_load(
execute, execute,
data = data data = data
) )
@@ -56,60 +49,26 @@ class StricklersList(SQLSubModel):
def _sql_save(self, execute, data = None): def _sql_save(self, execute, data = None):
execute("DELETE FROM stricklers") execute("DELETE FROM stricklers")
objs = self._stricks objs = self._lst
return self._save_submodel(execute, objs, data) return self._save_submodel(execute, objs, data)
def __len__(self):
return len(self._stricks)
@property @property
def stricklers(self): def stricklers(self):
return self._stricks.copy() return self.lst
def get(self, index): def new(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):
s = Stricklers(status = self._status) s = Stricklers(status = self._status)
self.insert(index, s) self.insert(index, s)
return 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 @timer
def sort(self, reverse:bool = False, key=None): def sort(self, reverse:bool = False, key=None):
f = lambda st: st.name f = lambda st: st.name
if key is not None: if key is not None:
f = key f = key
self._stricks = sorted( self._lst = sorted(
self._stricks, self._lst,
key = f, key = f,
reverse = reverse, reverse = reverse,
) )
+1 -1
View File
@@ -23,7 +23,7 @@ from datetime import datetime
from tools import timer, timestamp from tools import timer, timestamp
from Model.DB import SQLModel from Model.Tools.PamhyrDB import SQLModel
from Model.Saved import SavedStatus from Model.Saved import SavedStatus
from Model.Serializable import Serializable from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError 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 # Copyright (C) 2023 INRAE
# #
# This program is free software: you can redistribute it and/or modify # 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 from tools import timer
try: try:
# Installation allow Unix-like signal
from signal import SIGTERM, SIGSTOP, SIGCONT from signal import SIGTERM, SIGSTOP, SIGCONT
_signal = True _signal = True
except: except:
@@ -201,7 +202,7 @@ class AbstractSolver(object):
return os.path.abspath( return os.path.abspath(
os.path.join( os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
".." "..", ".."
) )
) )
@@ -216,7 +217,6 @@ class AbstractSolver(object):
The executable and list of arguments The executable and list of arguments
""" """
# HACK: Works in most case... Trust me i'm an engineer # HACK: Works in most case... Trust me i'm an engineer
cmd = cmd.replace("@install_dir", self._install_dir()) cmd = cmd.replace("@install_dir", self._install_dir())
cmd = cmd.replace("@path", path.replace(" ", "\ ")) cmd = cmd.replace("@path", path.replace(" ", "\ "))
cmd = cmd.replace("@input", self.input_param()) cmd = cmd.replace("@input", self.input_param())
@@ -358,6 +358,7 @@ class AbstractSolver(object):
def start(self, study, process = None): def start(self, study, process = None):
if _signal: if _signal:
# Solver is PAUSED, so continue execution
if self._status == STATUS.PAUSED: if self._status == STATUS.PAUSED:
os.kill(self._process.pid(), SIGCONT) os.kill(self._process.pid(), SIGCONT)
self._status = STATUS.RUNNING self._status = STATUS.RUNNING
@@ -371,6 +372,7 @@ class AbstractSolver(object):
if self._process is None: if self._process is None:
return False return False
# Send SIGSTOP to PAUSED solver
os.kill(self._process.pid(), SIGSTOP) os.kill(self._process.pid(), SIGSTOP)
self._status = STATUS.PAUSED self._status = STATUS.PAUSED
return True return True
+3 -135
View File
@@ -25,6 +25,7 @@ from tools import trace, timer
from View.ASubWindow import ASubMainWindow, AWidget from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, Qt, QVariant, QAbstractTableModel,
@@ -53,125 +54,7 @@ _translate = QCoreApplication.translate
logger = logging.getLogger() logger = logging.getLogger()
class ExtendedTimeEdit(AWidget): class TableModel(PamhyrTableModel):
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)
def data(self, index, role): def data(self, index, role):
if role == Qt.TextAlignmentRole: if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter return Qt.AlignHCenter | Qt.AlignVCenter
@@ -189,7 +72,7 @@ class TableModel(QAbstractTableModel):
if self._data.get_type_column(column) == float: if self._data.get_type_column(column) == float:
value = f"{v:.4f}" value = f"{v:.4f}"
elif self._data.header[column] == "time": elif self._data.header[column] == "time":
if self._mode == "time": if self._opt_data == "time":
t0 = datetime.fromtimestamp(0) t0 = datetime.fromtimestamp(0)
t = datetime.fromtimestamp(v) t = datetime.fromtimestamp(v)
value = str(t - t0) value = str(t - t0)
@@ -200,12 +83,6 @@ class TableModel(QAbstractTableModel):
return value 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): def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole: if not index.isValid() or role != Qt.EditRole:
return False return False
@@ -315,12 +192,3 @@ class TableModel(QAbstractTableModel):
self.layoutAboutToBeChanged.emit() self.layoutAboutToBeChanged.emit()
self.layoutChanged.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 -*- # -*- coding: utf-8 -*-
import logging
from tools import timer, trace from tools import timer, trace
from View.ASubWindow import ASubMainWindow, AWidget from View.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QKeySequence, QKeySequence,
@@ -39,15 +42,18 @@ from PyQt5.QtWidgets import (
) )
from View.Plot.MplCanvas import MplCanvas 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.translate import long_types
from View.BoundaryCondition.Edit.translate import table_headers, retranslate
from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand 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 from View.BoundaryCondition.Edit.Plot import Plot
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
logger = logging.getLogger()
class WD50Sigma(AWidget): class WD50Sigma(AWidget):
d50Changed = pyqtSignal(float) d50Changed = pyqtSignal(float)
sigmaChanged = pyqtSignal(float) sigmaChanged = pyqtSignal(float)
@@ -141,23 +147,29 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self._d50sigma.set_sigma(self._data.sigma) self._d50sigma.set_sigma(self._data.sigma)
def setup_table(self): def setup_table(self):
table = self.find(QTableView, "tableView") retranslate()
self._table = TableModel( headers = {}
for h in self._data.header:
headers[h] = table_headers[h]
self._delegate_time = PamhyrExTimeDelegate(
data = self._data, 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": table = self.find(QTableView, "tableView")
self._delegate_time = ExTimeDelegate( self._table = TableModel(
data = self._data, table_view = table,
mode = self._study.time_system, table_headers = headers,
parent = self editable_headers = self._data.header,
) delegates = {
"time": self._delegate_time,
table.setItemDelegateForColumn( },
0, self._delegate_time data = self._data,
) undo = self._undo_stack,
opt_data = self._study.time_system
)
table.setModel(self._table) table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -167,7 +179,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
def setup_plot(self): def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.verticalLayout.addWidget(self.toolbar) self.verticalLayout.addWidget(self.toolbar)
@@ -29,3 +29,12 @@ table_headers = {
"z": _translate("BoundaryCondition", "Z (m)"), "z": _translate("BoundaryCondition", "Z (m)"),
"solid": _translate("BoundaryCondition", "Solid (kg/s)"), "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 TimeOverZ, TimeOverDischarge, ZOverDischarge
) )
from View.Tools.PamhyrTable import PamhyrTableModel
from View.BoundaryCondition.UndoCommand import ( from View.BoundaryCondition.UndoCommand import (
SetNameCommand, SetNodeCommand, SetTypeCommand, SetNameCommand, SetNodeCommand, SetTypeCommand,
AddCommand, DelCommand, SortCommand, AddCommand, DelCommand, SortCommand,
@@ -104,26 +106,13 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender()) self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel): class TableModel(PamhyrTableModel):
def __init__(self, data=None, undo=None, tab=""): def _setup_lst(self):
super(QAbstractTableModel, self).__init__() self._lst = self._data.boundary_condition
self._headers = list(table_headers.keys()) self._tab = self._opt_data
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
def rowCount(self, parent): def rowCount(self, parent):
return self._bcs.len(self._tab) return self._lst.len(self._tab)
def columnCount(self, parent):
return len(self._headers)
def data(self, index, role): def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole: if role != Qt.ItemDataRole.DisplayRole:
@@ -133,24 +122,18 @@ class TableModel(QAbstractTableModel):
column = index.column() column = index.column()
if self._headers[column] == "name": 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": 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] return long_types[t]
elif self._headers[column] == "node": 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: if n is None:
return _translate("BoundaryCondition", "Not associate") return _translate("BoundaryCondition", "Not associate")
return n.name return n.name
return QVariant() 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): def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole: if not index.isValid() or role != Qt.EditRole:
return False return False
@@ -162,20 +145,20 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name": if self._headers[column] == "name":
self._undo.push( self._undo.push(
SetNameCommand( SetNameCommand(
self._bcs, self._tab,row, value self._lst, self._tab,row, value
) )
) )
elif self._headers[column] == "type": elif self._headers[column] == "type":
key = next(k for k, v in long_types.items() if v == value) key = next(k for k, v in long_types.items() if v == value)
self._undo.push( self._undo.push(
SetTypeCommand( SetTypeCommand(
self._bcs, self._tab,row, BC_types[key] self._lst, self._tab,row, BC_types[key]
) )
) )
elif self._headers[column] == "node": elif self._headers[column] == "node":
self._undo.push( self._undo.push(
SetNodeCommand( SetNodeCommand(
self._bcs, self._tab,row, self._data.node(value) self._lst, self._tab,row, self._data.node(value)
) )
) )
except Exception as e: except Exception as e:
@@ -190,7 +173,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
AddCommand( AddCommand(
self._bcs, self._tab,row self._lst, self._tab,row
) )
) )
@@ -202,7 +185,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
DelCommand( DelCommand(
self._bcs, self._tab,rows self._lst, self._tab, rows
) )
) )
@@ -214,7 +197,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
SortCommand( SortCommand(
self._bcs, self._tab,False self._lst, self._tab, False
) )
) )
@@ -231,7 +214,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push( self._undo_stack.push(
MoveCommand( MoveCommand(
self._bcs, self._tab,"up", row self._lst, self._tab, "up", row
) )
) )
@@ -239,7 +222,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()): def move_down(self, index, parent=QModelIndex()):
if row > len(self._bcs): if row > len(self._lst):
return return
target = row target = row
@@ -248,7 +231,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push( self._undo_stack.push(
MoveCommand( 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.Network.GraphWidget import GraphWidget
from View.BoundaryCondition.translate import * from View.BoundaryCondition.translate import table_headers, retranslate
from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -90,17 +90,10 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
for t in ["liquid", "solid", "suspenssion"]: 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( self._delegate_type = ComboBoxDelegate(
data = self._study.river, data = self._study.river,
mode = "type", mode = "type",
@@ -114,13 +107,20 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
parent=self parent=self
) )
table.setItemDelegateForColumn( table = self.find(QTableView, f"tableView_{t}")
1, self._delegate_type 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( table.setModel(self._table[t])
2, self._delegate_node
)
table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True) table.setAlternatingRowColors(True)
+5
View File
@@ -49,3 +49,8 @@ BC_types = {
"ZD": ZOverDischarge, "ZD": ZOverDischarge,
"SL": Solid, "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') self.signalStatus.emit('end')
def _compute(self): def _compute(self):
# The computation loop
for checker in self._checker_list: for checker in self._checker_list:
self.signalStatus.emit(checker.name) self.signalStatus.emit(checker.name)
# time.sleep(1) # Run checker
checker.run(self._study) checker.run(self._study)
self.signalStatus.emit("progress") self.signalStatus.emit("progress")
+4
View File
@@ -92,7 +92,9 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
self.set_plaintext_edit_text("plainTextEdit", "") self.set_plaintext_edit_text("plainTextEdit", "")
self._history_ind = 0 self._history_ind = 0
# Code to rich_code
rich_code = code.strip().split("\n") 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[-1] = "self.__debug_exec_result__ = " + rich_code[-1]
rich_code = "\n".join(rich_code) rich_code = "\n".join(rich_code)
logger.debug(f"User debug command : {rich_code}") logger.debug(f"User debug command : {rich_code}")
@@ -102,6 +104,8 @@ class ReplWindow(ASubMainWindow, ListedSubWindow):
except Exception as e: except Exception as e:
value = f"<font color=\"red\">" + str(e) + "</font>" value = f"<font color=\"red\">" + str(e) + "</font>"
# Display code
msg = f"<font color=\"grey\"> # " + code + " #</font>" msg = f"<font color=\"grey\"> # " + code + " #</font>"
self.find(QTextEdit, "textEdit").append(msg) self.find(QTextEdit, "textEdit").append(msg)
# Display results
self.find(QTextEdit, "textEdit").append(str(value)) 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.Plot.MplCanvas import MplCanvas
from View.Geometry.PlotKPZ import PlotKPZ from View.Geometry.PlotKPZ import PlotKPZ
from View.Frictions.PlotStricklers import PlotStricklers 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 from View.Stricklers.Window import StricklersWindow
@@ -94,6 +94,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
+8
View File
@@ -28,3 +28,11 @@ table_headers = {
"begin_strickler": _translate("Frictions", "Begin strickler"), "begin_strickler": _translate("Frictions", "Begin strickler"),
"end_strickler": _translate("Frictions", "End 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, 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__)) file_path = os.path.abspath(os.path.dirname(__file__))
icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/../View/ui/ressources/" 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 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.my_toolbar = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT( self.my_toolbar = PamhyrPlotToolbar(
self.canvas, self.widget1 self.canvas, self.widget1
) )
+6 -6
View File
@@ -25,9 +25,9 @@ from PyQt5.QtWidgets import (
QAbstractItemView, QHeaderView, QToolBar, QAbstractItemView, QHeaderView, QToolBar,
) )
from View.Plot import ( from View.Plot import MplCanvas
MplCanvas, navigation_toolbar_2qt from View.Plot.PamhyrToolbar import PamhyrPlotToolbar
)
file_path = os.path.abspath(os.path.dirname(__file__)) file_path = os.path.abspath(os.path.dirname(__file__))
icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/View/ui/ressources/" 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 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_1.setObjectName("canvas_1") self.canvas_1.setObjectName("canvas_1")
self.toolbar_1 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT( self.toolbar_1 = PamhyrPlotToolbar(
self.canvas_1, self.centralwidget, self.canvas_1, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"] 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 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2") self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT( self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self.centralwidget, self.canvas_2, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"] 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 = MplCanvas.MplCanvas(width=5, height=4, dpi=100)
self.canvas_3.setObjectName("canvas_3") self.canvas_3.setObjectName("canvas_3")
self.toolbar_3 = navigation_toolbar_2qt.PamHyrNavigationToolbar2QT( self.toolbar_3 = PamhyrPlotToolbar(
self.canvas_3, self.centralwidget, self.canvas_3, self.centralwidget,
items = ["home", "zoom", "save", "iso", "back/forward", "move"] 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.InitialConditions.Table import TableModel, ComboBoxDelegate
from View.Plot.MplCanvas import MplCanvas 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.PlotDKP import PlotDKP
from View.InitialConditions.PlotDischarge import PlotDischarge 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.DialogHeight import HeightDialog
from View.InitialConditions.DialogDischarge import DischargeDialog from View.InitialConditions.DialogDischarge import DischargeDialog
@@ -97,6 +97,8 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
self._table = TableModel( self._table = TableModel(
river = self._study.river, river = self._study.river,
@@ -122,7 +124,7 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self): def setup_graph(self):
self.canvas_1 = MplCanvas(width=5, height=4, dpi=100) self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_1.setObjectName("canvas_1") self.canvas_1.setObjectName("canvas_1")
self.toolbar_1 = PamHyrNavigationToolbar2QT( self.toolbar_1 = PamhyrPlotToolbar(
self.canvas_1, self self.canvas_1, self
) )
self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2") self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamHyrNavigationToolbar2QT( self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self self.canvas_2, self
) )
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2") self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
+9
View File
@@ -29,3 +29,12 @@ table_headers = {
"height": _translate("LateralContribution", "Height (m)"), "height": _translate("LateralContribution", "Height (m)"),
# "comment": _translate("LateralContribution", "Comment"), # "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.ASubWindow import ASubMainWindow, AWidget
from View.ListedSubWindow import ListedSubWindow from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, Qt, QVariant, QAbstractTableModel,
@@ -51,125 +52,8 @@ _translate = QCoreApplication.translate
logger = logging.getLogger() 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): def data(self, index, role):
if role == Qt.TextAlignmentRole: if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter return Qt.AlignHCenter | Qt.AlignVCenter
@@ -187,7 +71,7 @@ class TableModel(QAbstractTableModel):
if self._data.get_type_column(column) == float: if self._data.get_type_column(column) == float:
value = f"{v:.4f}" value = f"{v:.4f}"
elif self._data.header[column] == "time": elif self._data.header[column] == "time":
if self._mode == "time": if self._opt_data == "time":
t0 = datetime.fromtimestamp(0) t0 = datetime.fromtimestamp(0)
t = datetime.fromtimestamp(v) t = datetime.fromtimestamp(v)
value = str(t - t0) value = str(t - t0)
@@ -198,12 +82,6 @@ class TableModel(QAbstractTableModel):
return value 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): def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole: if not index.isValid() or role != Qt.EditRole:
return False return False
@@ -313,12 +191,3 @@ class TableModel(QAbstractTableModel):
self.layoutAboutToBeChanged.emit() self.layoutAboutToBeChanged.emit()
self.layoutChanged.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.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow from View.ListedSubWindow import ListedSubWindow
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QKeySequence, QKeySequence,
@@ -37,10 +38,11 @@ from PyQt5.QtWidgets import (
) )
from View.Plot.MplCanvas import MplCanvas 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.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 from View.LateralContribution.Edit.Plot import Plot
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -85,23 +87,29 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
table = self.find(QTableView, "tableView") retranslate()
self._table = TableModel( headers = {}
for h in self._data.header:
headers[h] = table_headers[h]
self._delegate_time = PamhyrExTimeDelegate(
data = self._data, 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": table = self.find(QTableView, "tableView")
self._delegate_time = ExTimeDelegate( self._table = TableModel(
data = self._data, table_view = table,
mode = self._study.time_system, table_headers = headers,
parent = self editable_headers = self._data.header,
) delegates = {
"time": self._delegate_time,
table.setItemDelegateForColumn( },
0, self._delegate_time data = self._data,
) undo = self._undo_stack,
opt_data = self._study.time_system
)
table.setModel(self._table) table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -111,7 +119,7 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow):
def setup_plot(self): def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.verticalLayout.addWidget(self.toolbar) self.verticalLayout.addWidget(self.toolbar)
@@ -28,3 +28,11 @@ table_headers = {
"discharge": _translate("LateralContribution", "Discharge (m³/s)"), "discharge": _translate("LateralContribution", "Discharge (m³/s)"),
"z": _translate("LateralContribution", "Z (m)") "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 ( from Model.LateralContribution.LateralContributionTypes import (
NotDefined, LateralContrib, Rain, Evaporation, NotDefined, LateralContrib, Rain, Evaporation,
) )
from View.Tools.PamhyrTable import PamhyrTableModel
from View.LateralContribution.translate import * from View.LateralContribution.translate import *
logger = logging.getLogger() logger = logging.getLogger()
@@ -102,26 +104,13 @@ class ComboBoxDelegate(QItemDelegate):
self.commitData.emit(self.sender()) self.commitData.emit(self.sender())
class TableModel(QAbstractTableModel): class TableModel(PamhyrTableModel):
def __init__(self, data=None, undo=None, tab=""): def _setup_lst(self):
super(QAbstractTableModel, self).__init__() self._lst = self._data.lateral_contribution
self._headers = list(table_headers.keys()) self._tab = self._opt_data
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
def rowCount(self, parent): def rowCount(self, parent):
return self._lcs.len(self._tab) return self._lst.len(self._tab)
def columnCount(self, parent):
return len(self._headers)
def data(self, index, role): def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole: if role != Qt.ItemDataRole.DisplayRole:
@@ -131,26 +120,19 @@ class TableModel(QAbstractTableModel):
column = index.column() column = index.column()
if self._headers[column] == "name": 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": 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] return long_types[t]
elif self._headers[column] == "edge": 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: if n is None:
return _translate("LateralContribution", "Not associate") return _translate("LateralContribution", "Not associate")
return n.name return n.name
elif self._headers[column] == "begin_kp": 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": elif self._headers[column] == "end_kp":
return self._lcs.get(self._tab, row).end_kp return self._lst.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 QVariant() return QVariant()
@@ -165,32 +147,32 @@ class TableModel(QAbstractTableModel):
if self._headers[column] == "name": if self._headers[column] == "name":
self._undo.push( self._undo.push(
SetNameCommand( SetNameCommand(
self._lcs, self._tab, row, value self._lst, self._tab, row, value
) )
) )
elif self._headers[column] == "type": elif self._headers[column] == "type":
key = next(k for k, v in long_types.items() if v == value) key = next(k for k, v in long_types.items() if v == value)
self._undo.push( self._undo.push(
SetTypeCommand( SetTypeCommand(
self._lcs, self._tab, row, LC_types[key] self._lst, self._tab, row, LC_types[key]
) )
) )
elif self._headers[column] == "edge": elif self._headers[column] == "edge":
self._undo.push( self._undo.push(
SetEdgeCommand( 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": elif self._headers[column] == "begin_kp":
self._undo.push( self._undo.push(
SetBeginCommand( SetBeginCommand(
self._lcs, self._tab, row, value self._lst, self._tab, row, value
) )
) )
elif self._headers[column] == "end_kp": elif self._headers[column] == "end_kp":
self._undo.push( self._undo.push(
SetEndCommand( SetEndCommand(
self._lcs, self._tab, row, value self._lst, self._tab, row, value
) )
) )
except Exception as e: except Exception as e:
@@ -205,7 +187,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
AddCommand( AddCommand(
self._lcs, self._tab, row self._lst, self._tab, row
) )
) )
@@ -217,7 +199,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
DelCommand( DelCommand(
self._lcs, self._tab, rows self._lst, self._tab, rows
) )
) )
@@ -229,7 +211,7 @@ class TableModel(QAbstractTableModel):
self._undo.push( self._undo.push(
SortCommand( SortCommand(
self._lcs, self._tab, False self._lst, self._tab, False
) )
) )
@@ -246,7 +228,7 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push( self._undo_stack.push(
MoveCommand( MoveCommand(
self._lcs, self._tab,"up", row self._lst, self._tab, "up", row
) )
) )
@@ -254,7 +236,7 @@ class TableModel(QAbstractTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()): def move_down(self, index, parent=QModelIndex()):
if row > len(self._lcs): if row > len(self._lst):
return return
target = row target = row
@@ -263,17 +245,9 @@ class TableModel(QAbstractTableModel):
self._undo_stack.push( self._undo_stack.push(
MoveCommand( MoveCommand(
self._lcs, self._tab,"down", row self._lst, self._tab, "down", row
) )
) )
self.endMoveRows() self.endMoveRows()
self.layoutChanged.emit() 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.Plot.MplCanvas import MplCanvas
from View.Geometry.PlotXY import PlotXY 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 from View.LateralContribution.Edit.Window import EditLateralContributionWindow
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -90,17 +93,10 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
for t in ["liquid", "solid", "suspenssion"]: 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( self._delegate_type = ComboBoxDelegate(
data = self._study.river, data = self._study.river,
mode = "type", mode = "type",
@@ -114,13 +110,20 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow):
parent=self parent=self
) )
table.setItemDelegateForColumn( table = self.find(QTableView, f"tableView_{t}")
1, self._delegate_type 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( table.setModel(self._table[t])
2, self._delegate_edge
)
table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True) table.setAlternatingRowColors(True)
+12
View File
@@ -45,3 +45,15 @@ LC_types = {
"RA": Rain, "RA": Rain,
"EV": Evaporation, "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() self.dialog_reopen_study()
def set_title(self): def set_title(self):
title = "(dbg) " if self.conf.debug else ""
if self.model is not None: if self.model is not None:
self.setWindowTitle(f"PAMHYR - {self.model.name}") title += f"Pamhyr2 - {self.model.name}"
self.setWindowTitle(title)
else: else:
self.setWindowTitle("PAMHYR") title += "Pamhyr2"
self.setWindowTitle(title)
def enable_actions(self, action:str, enable:bool): def enable_actions(self, action:str, enable:bool):
"""Enable of disable an action componant """Enable of disable an action componant
@@ -255,9 +259,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
logger.info("Set logging level to INFO") logger.info("Set logging level to INFO")
def setup_debug_mode(self, init = False): def setup_debug_mode(self, init = False):
menu = self.findChild(QMenu, "menu_help") menu = self.findChild(QMenu, "menu_help")
self.set_title()
if init: if init:
self.debug_action = QAction("Debug", self) self.debug_action = QAction("Debug", self)
+9 -6
View File
@@ -584,7 +584,7 @@ class GraphWidget(QGraphicsView):
if item: if item:
item.update() item.update()
except Exception as e: except Exception as e:
logger.debug(str(e)) logger.warning(str(e))
def selected_new_edge_src_node(self): def selected_new_edge_src_node(self):
"""The current node item selected to add new edge """The current node item selected to add new edge
@@ -626,12 +626,15 @@ class GraphWidget(QGraphicsView):
Returns: Returns:
Nothing Nothing
""" """
previous_edge = self._current_edge try:
self._current_edge = edge previous_edge = self._current_edge
self.graph.set_current_reach(edge.edge) self._current_edge = edge
self.graph.set_current_reach(edge.edge)
if previous_edge: if previous_edge:
previous_edge.update() previous_edge.update()
except Exception as e:
logger.warning(str(e))
def reset_selection(self): def reset_selection(self):
"""Reset all the selected items """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.ASubWindow import ASubWindow
from View.Network.GraphWidget import GraphWidget from View.Network.GraphWidget import GraphWidget
from View.Network.UndoCommand import * from View.Network.UndoCommand import *
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal, Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal,
@@ -69,151 +70,105 @@ class ComboBoxDelegate(QItemDelegate):
def currentItemChanged(self): def currentItemChanged(self):
self.commitData.emit(self.sender()) self.commitData.emit(self.sender())
class TrueFalseComboBoxDelegate(QItemDelegate): class NodeTableModel(PamhyrTableModel):
def __init__(self, parent=None): def _setup_lst(self):
super(TrueFalseComboBoxDelegate, self).__init__(parent) self._lst = self._data.nodes()
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)
def data(self, index, role): def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole: if role != Qt.ItemDataRole.DisplayRole:
return QVariant() return QVariant()
if self.headers[index.column()] == "type": if self._headers[index.column()] == "type":
node = self.rows[index.row()] node = self._lst[index.row()]
ret = "internal" ret = "internal"
if not self.graph.is_enable_node(node): if not self._data.is_enable_node(node):
ret = "disable" ret = "disable"
elif self.graph.is_upstream_node(node): elif self._data.is_upstream_node(node):
ret = "upstream" ret = "upstream"
elif self.graph.is_downstream_node(node): elif self._data.is_downstream_node(node):
ret = "downstream" ret = "downstream"
return ret return ret
return self.rows[index.row()][self.headers[index.column()]] return self._lst[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()
@pyqtSlot() @pyqtSlot()
def setData(self, index, value, role=Qt.EditRole): def setData(self, index, value, role=Qt.EditRole):
if index.isValid(): if not index.isValid():
if role == Qt.EditRole: return False
try:
if (self.headers[index.column()] == "node1" or if role == Qt.EditRole:
self.headers[index.column()] == "node2"): try:
node = self.graph.node(value) self._undo.push(
self._undo.push( SetCommand(
SetNodeCommand( self._lst[index.row()],
self.graph, self._headers[index.column()],
self.rows[index.row()], value
self.headers[index.column()], )
node )
) except Exception as e:
) logger.info(e)
# elif self.headers[index.column()] == "enable": logger.debug(traceback.format_exc())
# 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())
self.dataChanged.emit(index, index, [Qt.DisplayRole]) self.dataChanged.emit(index, index, [Qt.DisplayRole])
self.layoutChanged.emit() self.layoutChanged.emit()
return True return True
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
else:
return False
def update(self): def update(self):
if self._type == "nodes": self._lst = self._data.nodes()
self.rows = self.graph.nodes()
elif self._type == "edges":
self.rows = self.graph.edges()
self.layoutChanged.emit() self.layoutChanged.emit()
def reverse_edge(self, index): class EdgeTableModel(PamhyrTableModel):
if self._type == "edges": def _setup_lst(self):
tmp = self.rows[index.row()].node1 self._lst = self._data.edges()
self.rows[index.row()].node1 = self.rows[index.row()].node2
self.rows[index.row()].node2 = tmp 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.dataChanged.emit(index, index, [Qt.DisplayRole])
self.layoutChanged.emit() self.layoutChanged.emit()
return True
def undo(self): self.dataChanged.emit(index, index)
self._undo.undo()
self.layoutChanged.emit()
def redo(self): def update(self):
self._undo.redo() self._lst = self._data.edges()
self.layoutChanged.emit() self.layoutChanged.emit()
+31 -24
View File
@@ -16,6 +16,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QKeySequence, QKeySequence,
) )
@@ -37,9 +39,16 @@ from Model.River import RiverNode, RiverReach, River
from View.ASubWindow import ASubMainWindow from View.ASubWindow import ASubMainWindow
from View.Network.GraphWidget import GraphWidget from View.Network.GraphWidget import GraphWidget
from View.Network.UndoCommand import * from View.Network.UndoCommand import *
from View.Network.Table import ( from View.Network.translate import (
GraphTableModel, ComboBoxDelegate, TrueFalseComboBoxDelegate, table_headers_node, table_headers_edge,
retranslate,
) )
from View.Network.Table import (
ComboBoxDelegate, NodeTableModel, EdgeTableModel,
)
_translate = QCoreApplication.translate
class NetworkWindow(ASubMainWindow): class NetworkWindow(ASubMainWindow):
def __init__(self, model=None, title="River network", parent=None): def __init__(self, model=None, title="River network", parent=None):
@@ -69,43 +78,41 @@ class NetworkWindow(ASubMainWindow):
self.redo_sc = QShortcut(QKeySequence.Redo, self) self.redo_sc = QShortcut(QKeySequence.Redo, self)
def setup_table(self): def setup_table(self):
# Nodes table retranslate()
self._nodes_model = GraphTableModel( # Nodes table
headers = ["name", "type"], table = self.find(QTableView, "tableView_nodes")
graph = self._graph, self._nodes_model = NodeTableModel(
rows_type = "nodes", table_view = table,
table_headers = table_headers_node,
editable_headers = ["name"],
data = self._graph,
undo = self._undo_stack, undo = self._undo_stack,
) )
table = self.find(QTableView, "tableView_nodes")
table.setModel(self._nodes_model) table.setModel(self._nodes_model)
#table.resizeColumnsToContents()
table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# Edges table # Edges table
table = self.find(QTableView, "tableView_reachs")
self._reachs_model = GraphTableModel(
headers = ["name", # "enable",
"node1", "node2"],
graph = self._graph,
rows_type = "edges",
undo = self._undo_stack,
)
self.delegate_combobox = ComboBoxDelegate( self.delegate_combobox = ComboBoxDelegate(
graph = self._graph, graph = self._graph,
parent = self, 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.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.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
#table.resizeColumnsToContents() #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 -*- # -*- coding: utf-8 -*-
from Model.Except import NotImplementedMethodeError
class APlot(object): class APlot(object):
def __init__(self, canvas=None, data=None, toolbar=None): def __init__(self, canvas=None, data=None, toolbar=None):
super(APlot, self).__init__() super(APlot, self).__init__()
@@ -1,4 +1,4 @@
# navigation_toolbar_2qt.py -- Pamhyr # PamhyrToolbar.py -- Pamhyr
# Copyright (C) 2023 INRAE # Copyright (C) 2023 INRAE
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -19,10 +19,12 @@
import os import os
import logging import logging
import matplotlib as mpl import matplotlib as mpl
from matplotlib.backends import qt_compat from matplotlib.backends import qt_compat
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QAction from PyQt5.QtWidgets import QAction
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT
from PyQt5.QtCore import pyqtSignal, QSize from PyQt5.QtCore import pyqtSignal, QSize
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
@@ -31,21 +33,21 @@ logger = logging.getLogger()
file_path = os.path.abspath(os.path.dirname(__file__)) 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 MatPlotLib plot toolbar for Pamhyr
l'ajout d'autres boutons à la toolbar initiale
""" """
isometric_signal = pyqtSignal(str) isometric_signal = pyqtSignal(str)
def __init__(self, canvas, parent, items = ["home", "move", "zoom", "save"]): def __init__(self, canvas, parent, items = ["home", "move", "zoom", "save"]):
""" """PamhyrPlotToolbar
Args:
canvas: canvas de matplotlib
parent: parent du canvas
"""
self.my_canvas = canvas
Args:
canvas: MatPlotLib canvas
parent: parent Qt object
items: Enables items (default: "home", "move", "zoom", "save")
"""
self._canvas = canvas
self._items = items self._items = items
self.toolitems = [ self.toolitems = [
(None, None, None, None), (None, None, None, None),
@@ -118,7 +120,7 @@ class PamHyrNavigationToolbar2QT(NavigationToolbar2QT):
self.toolitems.append((None, None, None, None)) self.toolitems.append((None, None, None, None))
NavigationToolbar2QT.__init__(self, canvas, parent) NavigationToolbar2QT.__init__(self, canvas, parent)
btn_size = QSize(30, 30) btn_size = QSize(40, 28)
actions = self.findChildren(QAction) actions = self.findChildren(QAction)
for a, i in icons: for a, i in icons:
@@ -161,18 +163,15 @@ class PamHyrNavigationToolbar2QT(NavigationToolbar2QT):
QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton
) )
def cursor1(self):
logger.info("TODO: cursor1")
def isometric_view(self): def isometric_view(self):
self.my_canvas.axes.axis("equal") self._canvas.axes.axis("equal")
self.my_canvas.figure.canvas.draw_idle() self._canvas.figure.canvas.draw_idle()
self.isometric_signal[str].emit("vue iso") self.isometric_signal[str].emit("vue iso")
def non_isometric_view(self): def non_isometric_view(self):
self.my_canvas.axes.axis("tight") self._canvas.axes.axis("tight")
self.my_canvas.toolbar.update() self._canvas.toolbar.update()
self.my_canvas.figure.canvas.draw_idle() self._canvas.figure.canvas.draw_idle()
def toolitems_translation(self): def toolitems_translation(self):
self._actions['home'].setToolTip(_translate("Toolbar", "Default view")) 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.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.PlotXY import PlotXY
from View.Results.PlotAC import PlotAC 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.PlotSedProfile import PlotSedProfile
from View.Results.Table import TableModel 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 from View.Stricklers.Window import StricklersWindow
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -102,6 +105,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
for t in ["reach", "profile"]: for t in ["reach", "profile"]:
@@ -131,7 +135,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self): def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.plot_layout = self.find(QVBoxLayout, "verticalLayout") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2") self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamHyrNavigationToolbar2QT( self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self self.canvas_2, self
) )
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_3.setObjectName("canvas_3") self.canvas_3.setObjectName("canvas_3")
self.toolbar_3 = PamHyrNavigationToolbar2QT( self.toolbar_3 = PamhyrPlotToolbar(
self.canvas_3, self self.canvas_3, self
) )
self.plot_layout_3 = self.find(QVBoxLayout, "verticalLayout_3") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_4.setObjectName("canvas_4") self.canvas_4.setObjectName("canvas_4")
self.toolbar_4 = PamHyrNavigationToolbar2QT( self.toolbar_4 = PamhyrPlotToolbar(
self.canvas_4, self self.canvas_4, self
) )
self.plot_layout_4 = self.find(QVBoxLayout, "verticalLayout_hydrograph") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_5.setObjectName("canvas_5") self.canvas_5.setObjectName("canvas_5")
self.toolbar_5 = PamHyrNavigationToolbar2QT( self.toolbar_5 = PamhyrPlotToolbar(
self.canvas_5, self self.canvas_5, self
) )
self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach") 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 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_6.setObjectName("canvas_6") self.canvas_6.setObjectName("canvas_6")
self.toolbar_6 = PamHyrNavigationToolbar2QT( self.toolbar_6 = PamhyrPlotToolbar(
self.canvas_6, self self.canvas_6, self
) )
self.plot_layout_6 = self.find(QVBoxLayout, "verticalLayout_sed_profile") self.plot_layout_6 = self.find(QVBoxLayout, "verticalLayout_sed_profile")
+6
View File
@@ -28,3 +28,9 @@ table_headers_profile = {
"name": _translate("Results", "Name"), "name": _translate("Results", "Name"),
"kp": _translate("Results", "KP (m)"), "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.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.UndoCommand import *
from View.SedimentLayers.Edit.Table import * from View.SedimentLayers.Edit.Table import *
from View.SedimentLayers.Edit.Plot import Plot 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 _translate = QCoreApplication.translate
@@ -89,6 +89,7 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
@@ -106,7 +107,7 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self): def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
@@ -12,3 +12,11 @@ table_headers = {
"sigma": _translate("SedimentLayers", "Sigma"), "sigma": _translate("SedimentLayers", "Sigma"),
"critical_constraint": _translate("SedimentLayers", "Critical constraint"), "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.SedimentLayers.Reach.Profile.Plot import Plot
from View.Plot.MplCanvas import MplCanvas 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 from View.SedimentLayers.Window import SedimentLayersWindow
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -85,6 +85,8 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
self._table = TableModel( self._table = TableModel(
study = self._study, study = self._study,
@@ -110,7 +112,7 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self): def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
@@ -11,3 +11,10 @@ table_headers = {
"name": _translate("SedimentLayers", "Name"), "name": _translate("SedimentLayers", "Name"),
"sl": _translate("SedimentLayers", "Sediment layers"), "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.SedimentLayers.Reach.SLDialog import SLDialog
from View.Plot.MplCanvas import MplCanvas 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.Window import SedimentLayersWindow
from View.SedimentLayers.Reach.Profile.Window import ProfileSedimentLayersWindow from View.SedimentLayers.Reach.Profile.Window import ProfileSedimentLayersWindow
@@ -73,6 +73,8 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
self._table = TableModel( self._table = TableModel(
study = self._study, study = self._study,
@@ -98,7 +100,7 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow):
def setup_graph(self): def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamHyrNavigationToolbar2QT( self.toolbar = PamhyrPlotToolbar(
self.canvas, self self.canvas, self
) )
self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2") self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2")
@@ -9,3 +9,8 @@ table_headers = {
"kp": _translate("SedimentLayers", "KP (m)"), "kp": _translate("SedimentLayers", "KP (m)"),
"sl": _translate("SedimentLayers", "Sediment layers"), "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.SedimentLayers.Edit.Plot import Plot
from View.Plot.MplCanvas import MplCanvas 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 from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow
@@ -70,6 +70,8 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow):
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
self._table = TableModel( self._table = TableModel(
study = self._study, study = self._study,
+4
View File
@@ -8,3 +8,7 @@ table_headers = {
"name": _translate("SedimentLayers", "Name"), "name": _translate("SedimentLayers", "Name"),
"comment": _translate("SedimentLayers", "Comment"), "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, QComboBox,
) )
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Stricklers.UndoCommand import ( from View.Stricklers.UndoCommand import (
SetNameCommand, SetCommentCommand, SetNameCommand, SetCommentCommand,
SetMinorCommand, SetMediumCommand, SetMinorCommand, SetMediumCommand,
AddCommand, DelCommand, SortCommand, AddCommand, DelCommand, SortCommand,
) )
from View.Stricklers.translate import *
logger = logging.getLogger() logger = logging.getLogger()
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
class TableModel(QAbstractTableModel): class TableModel(PamhyrTableModel):
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)
def data(self, index, role): def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole: if role != Qt.ItemDataRole.DisplayRole:
return QVariant() return QVariant()
@@ -83,12 +65,7 @@ class TableModel(QAbstractTableModel):
return QVariant() return QVariant()
def headerData(self, section, orientation, role): @pyqtSlot()
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): def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole: if not index.isValid() or role != Qt.EditRole:
return False return False
+1 -1
View File
@@ -100,7 +100,7 @@ class AddCommand(QUndoCommand):
def redo(self): def redo(self):
if self._new is None: if self._new is None:
self._new = self._data.add(self._index) self._new = self._data.new(self._index)
else: else:
self._data.insert(self._index, self._new) 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.UndoCommand import PasteCommand
from View.Stricklers.Table import TableModel from View.Stricklers.Table import TableModel
from View.Stricklers.translate import table_headers, retranslate
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -74,7 +75,9 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow):
self.copy_sc = QShortcut(QKeySequence.Copy, self) self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self) self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self): def setup_table(self):
retranslate()
self._table = {} self._table = {}
for t in ["app", "study"]: for t in ["app", "study"]:
@@ -85,6 +88,9 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow):
data = self._config.stricklers data = self._config.stricklers
self._table[t] = TableModel( self._table[t] = TableModel(
table_view = table,
table_headers = table_headers,
editable_headers = ["name", "comment", "minor", "medium"],
data = data, data = data,
undo = self._undo_stack, undo = self._undo_stack,
) )
+6
View File
@@ -30,3 +30,9 @@ table_headers = {
"medium": _translate("LateralContribution", "Medium bed"), "medium": _translate("LateralContribution", "Medium bed"),
"comment": _translate("LateralContribution", "Comment"), "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> </font>
</property> </property>
<property name="text"> <property name="text">
<string>About PAMHYR</string> <string>About Pamhyr2</string>
</property> </property>
</widget> </widget>
</item> </item>
+2 -2
View File
@@ -96,7 +96,7 @@ class Config(SQL):
INSERT INTO solver VALUES ( INSERT INTO solver VALUES (
'mage8', 'mage8',
'default-mage', 'default-mage',
'Default PAMHYR mage 8 version', 'Default Pamhyr2 mage 8 version',
'', '', '', '', '', '',
@@ -265,7 +265,7 @@ class Config(SQL):
posix = os.name == 'posix' posix = os.name == 'posix'
ctor = solver_type_list["mage8"] ctor = solver_type_list["mage8"]
new = ctor("default-mage") 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""" new._cmd_solver = f""""@install_dir/mage/mage{"" if posix else ".exe"}" -fp=1 @input"""
self._solvers.append(new) 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"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="en_150">
<TS version="2.1" language="fr_FR" sourcelanguage="en_150">
<context> <context>
<name>About</name> <name>About</name>
<message> <message>
@@ -12,7 +11,7 @@
<context> <context>
<name>BoundaryCondition</name> <name>BoundaryCondition</name>
<message> <message>
<location filename="../View/BoundaryCondition/Table.py" line="143"/> <location filename="../View/BoundaryCondition/Table.py" line="132"/>
<source>Not associate</source> <source>Not associate</source>
<translation>Non associer</translation> <translation>Non associer</translation>
</message> </message>
@@ -78,7 +77,7 @@
</message> </message>
<message> <message>
<location filename="../View/BoundaryCondition/Edit/translate.py" line="12"/> <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> <translation type="obsolete">Débit (m³/s)</translation>
</message> </message>
<message> <message>
@@ -106,6 +105,11 @@
<source>ZDischarge</source> <source>ZDischarge</source>
<translation>HauteurDébit</translation> <translation>HauteurDébit</translation>
</message> </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>
<context> <context>
<name>Checker</name> <name>Checker</name>
@@ -486,12 +490,12 @@
<message> <message>
<location filename="../View/ui/about.ui" line="42"/> <location filename="../View/ui/about.ui" line="42"/>
<source>About PAMHYR</source> <source>About PAMHYR</source>
<translation>À propos de PAMHYR</translation> <translation type="obsolete">À propos de PAMHYR</translation>
</message> </message>
<message> <message>
<location filename="../View/ui/about.ui" line="56"/> <location filename="../View/ui/about.ui" line="56"/>
<source>Version: @version</source> <source>Version: @version</source>
<translation type="unfinished"></translation> <translation>Version: @version</translation>
</message> </message>
<message> <message>
<location filename="../View/ui/about.ui" line="63"/> <location filename="../View/ui/about.ui" line="63"/>
@@ -518,6 +522,11 @@
<source>Sigma</source> <source>Sigma</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../View/ui/about.ui" line="42"/>
<source>About Pamhyr2</source>
<translation>À propos de Pamhyr2</translation>
</message>
</context> </context>
<context> <context>
<name>Frictions</name> <name>Frictions</name>
@@ -603,7 +612,7 @@
<translation>Commentaire</translation> <translation>Commentaire</translation>
</message> </message>
<message> <message>
<location filename="../View/LateralContribution/Table.py" line="141"/> <location filename="../View/LateralContribution/Table.py" line="130"/>
<source>Not associate</source> <source>Not associate</source>
<translation>Non associer</translation> <translation>Non associer</translation>
</message> </message>
@@ -699,9 +708,14 @@
</message> </message>
<message> <message>
<location filename="../View/InitialConditions/translate.py" line="11"/> <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> <translation type="obsolete">Débit (m³/s)</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@@ -1408,7 +1422,7 @@
<message> <message>
<location filename="../View/ui/SolverLog.ui" line="106"/> <location filename="../View/ui/SolverLog.ui" line="106"/>
<source>results</source> <source>results</source>
<translation type="unfinished"></translation> <translation>Résultats</translation>
</message> </message>
<message> <message>
<location filename="../View/ui/EditSedimentLayers.ui" line="63"/> <location filename="../View/ui/EditSedimentLayers.ui" line="63"/>
@@ -1510,28 +1524,28 @@
<source>Edit sediment layers</source> <source>Edit sediment layers</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="553"/> <location filename="../View/ui/MainWindow.ui" line="553"/>
<source>Édition des Tronçons</source> <source>Édition des Tronçons</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="667"/> <location filename="../View/ui/MainWindow.ui" line="667"/>
<source>Ouvrir une étude</source> <source>Ouvrir une étude</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="679"/> <location filename="../View/ui/MainWindow.ui" line="679"/>
<source>Enrégistrer étude en cours (Ctrl+S)</source> <source>Enrégistrer étude en cours (Ctrl+S)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/ui/MainWindow.ui" line="694"/> <location filename="../View/ui/MainWindow.ui" line="694"/>
<source>Fermer étude en cours (Ctrl+F)</source> <source>Fermer étude en cours (Ctrl+F)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context encoding="UTF-8">
<name>MainWindowProfile</name> <name>MainWindowProfile</name>
<message> <message>
<location filename="../View/Geometry/Profile/Window.py" line="83"/> <location filename="../View/Geometry/Profile/Window.py" line="83"/>
@@ -1549,17 +1563,17 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<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> <source>MainWindowProfile</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<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> <source>Trier les points par ordre croissant de X</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<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> <source>Trier les points par ordre croissant de Y</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -1605,108 +1619,108 @@
<source>Cote (m)</source> <source>Cote (m)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="328"/> <location filename="../View/Geometry/Profile/Window.py" line="328"/>
<source>Suppression les lignes incomplètes</source> <source>Suppression les lignes incomplètes</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="328"/> <location filename="../View/Geometry/Profile/Window.py" line="328"/>
<source>Supprimer les lignes des cellules non renseignées ?</source> <source>Supprimer les lignes des cellules non renseignées ?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="371"/> <location filename="../View/Geometry/Profile/Window.py" line="371"/>
<source>Suppression des noms répétés</source> <source>Suppression des noms répétés</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Window.py" line="382"/> <location filename="../View/Geometry/Profile/Window.py" line="382"/>
<source>Etes-vous sûr de vouloir quitter ?</source> <source>Etes-vous sûr de vouloir quitter ?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="224"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="225"/>
<source>Insérer un point</source> <source>Insérer un point</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="225"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="226"/>
<source>Supprimer le/les point(s) sélectionnés</source> <source>Supprimer le/les point(s) sélectionnés</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="229"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="230"/>
<source>Trier les points par ordre décroissant de X</source> <source>Trier les points par ordre décroissant de X</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="234"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="235"/>
<source>Trier les points par ordre décroissant de Y</source> <source>Trier les points par ordre décroissant de Y</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="236"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="237"/>
<source>Décaler le point sélectionné vers le haut</source> <source>Décaler le point sélectionné vers le haut</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="238"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="239"/>
<source>Décaler le point sélectionné vers le bas</source> <source>Décaler le point sélectionné vers le bas</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="240"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="241"/>
<source>Exporter (dans un fichier) les points du profil au format tabulé</source> <source>Exporter (dans un fichier) les points du profil au format tabulé</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="242"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="243"/>
<source>Copier la sélection au format tabulé</source> <source>Copier la sélection au format tabulé</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="244"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="245"/>
<source>Coller la sélection depuis le presse-papier au format tabulé</source> <source>Coller la sélection depuis le presse-papier au format tabulé</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="246"/> <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> <source>Vérifier la validité de la saisie et garder ou pas les modifications apportées</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="249"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="250"/>
<source>Annuler toutes les modifications depuis la dernière validation</source> <source>Annuler toutes les modifications depuis la dernière validation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="251"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="252"/>
<source>Annuler toutes les modifications et revenir à l&apos;état initial</source> <source>Annuler toutes les modifications et revenir à l&apos;état initial</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="253"/> <location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="254"/>
<source>Ligne d&apos;eau : <source>Ligne d&apos;eau :
Z : Cote (m) <byte value="x9"/>Z : Cote (m)
A : Aire mouillée (mu00B2) <byte value="x9"/>A : Aire mouillée (mu00B2)
p : Périmètre mouillé (m) <byte value="x9"/>p : Périmètre mouillé (m)
L : Largeur au miroir (m)</source> <byte value="x9"/>L : Largeur au miroir (m)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/mainwindow_ui_profile.py" line="257"/> <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> <source>&apos;Maj + Clic&apos; : Ligne d&apos;eau &amp; &apos;Ctrl + Clic&apos; : Sélectionner des points</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Profile/Table.py" line="142"/> <location filename="../View/Geometry/Profile/Table.py" line="142"/>
<source>Abscisse en travers calculée en projétant les points <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> sur le plan défini par les deux points nommés extrêmes </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context encoding="UTF-8">
<name>MainWindow_reach</name> <name>MainWindow_reach</name>
<message> <message>
<location filename="../View/Frictions/PlotStricklers.py" line="76"/> <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> <translation type="unfinished">Hauteur (m)</translation>
</message> </message>
<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> <source>Select destination file</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -1898,58 +1912,58 @@ sur le plan défini par les deux points nommés extrêmes </source>
<source> Editer selected profile</source> <source> Editer selected profile</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="164"/> <location filename="../View/Geometry/Window.py" line="164"/>
<source>Édition des profils sélectionnés</source> <source>Édition des profils sélectionnés</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="166"/> <location filename="../View/Geometry/Window.py" line="166"/>
<source>Vous avez sélectionné plus de 5 profils. <source>Vous avez sélectionné plus de 5 profils.
Seuls les 5 premiers seront édités.</source> Seuls les 5 premiers seront édités.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/Window.py" line="322"/> <location filename="../View/Geometry/Window.py" line="322"/>
<source>Profil N° : </source> <source>Profil N° : </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/PlotAC.py" line="179"/> <location filename="../View/Geometry/PlotAC.py" line="179"/>
<source>Profil précédent</source> <source>Profil précédent</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/PlotAC.py" line="180"/> <location filename="../View/Geometry/PlotAC.py" line="180"/>
<source>Profil sélectionné</source> <source>Profil sélectionné</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="328"/> <location filename="../View/Geometry/mainwindow_ui_reach.py" line="328"/>
<source> Trier les profils par ordre décroissant des Pk</source> <source> Trier les profils par ordre décroissant des Pk</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="329"/> <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> <source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le haut)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="331"/> <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> <source> Changer l&apos;ordre des profils (en décalant le profil sélectionné vers le bas)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="339"/> <location filename="../View/Geometry/mainwindow_ui_reach.py" line="339"/>
<source>Vue isométrique (Alt+Z)</source> <source>Vue isométrique (Alt+Z)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/> <location filename="../View/Geometry/mainwindow_ui_reach.py" line="342"/>
<source>Vue isométrique (Alt+E)</source> <source>Vue isométrique (Alt+E)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message encoding="UTF-8">
<location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/> <location filename="../View/Geometry/mainwindow_ui_reach.py" line="345"/>
<source>Vue isométrique (Alt+R)</source> <source>Vue isométrique (Alt+R)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@@ -1958,40 +1972,60 @@ Seuls les 5 premiers seront édités.</source>
<context> <context>
<name>Network</name> <name>Network</name>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="809"/> <location filename="../View/Network/GraphWidget.py" line="812"/>
<source>Add node</source> <source>Add node</source>
<translation>Ajouter un nœud</translation> <translation>Ajouter un nœud</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="817"/> <location filename="../View/Network/GraphWidget.py" line="820"/>
<source>Delete the node</source> <source>Delete the node</source>
<translation>Supprimer un nœud</translation> <translation>Supprimer un nœud</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="818"/> <location filename="../View/Network/GraphWidget.py" line="821"/>
<source>Disable the node</source> <source>Disable the node</source>
<translation>Déactiver un nœud</translation> <translation>Déactiver un nœud</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="826"/> <location filename="../View/Network/GraphWidget.py" line="829"/>
<source>Delete the reach</source> <source>Delete the reach</source>
<translation>Supprimer un bief</translation> <translation>Supprimer un bief</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="829"/> <location filename="../View/Network/GraphWidget.py" line="832"/>
<source>Disable the reach</source> <source>Disable the reach</source>
<translation>Déactiver un bief</translation> <translation>Déactiver un bief</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="832"/> <location filename="../View/Network/GraphWidget.py" line="835"/>
<source>Enable the reach</source> <source>Enable the reach</source>
<translation>Activer un bief</translation> <translation>Activer un bief</translation>
</message> </message>
<message> <message>
<location filename="../View/Network/GraphWidget.py" line="835"/> <location filename="../View/Network/GraphWidget.py" line="838"/>
<source>Reverse the reach orientation</source> <source>Reverse the reach orientation</source>
<translation>Inverser l&apos;orientation du bief</translation> <translation>Inverser l&apos;orientation du bief</translation>
</message> </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>
<context> <context>
<name>Results</name> <name>Results</name>
@@ -2037,9 +2071,14 @@ Seuls les 5 premiers seront édités.</source>
</message> </message>
<message> <message>
<location filename="../View/Results/PlotH.py" line="79"/> <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> <translation type="obsolete">Débit (m³/s)</translation>
</message> </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>
<context> <context>
<name>SedimentLayers</name> <name>SedimentLayers</name>
@@ -2303,42 +2342,42 @@ Seuls les 5 premiers seront édités.</source>
<context> <context>
<name>Toolbar</name> <name>Toolbar</name>
<message> <message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="182"/> <location filename="../View/Plot/PamhyrToolbar.py" line="181"/>
<source>Zoom</source> <source>Zoom</source>
<translation>Zoom</translation> <translation>Zoom</translation>
</message> </message>
<message> <message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="178"/> <location filename="../View/Plot/PamhyrToolbar.py" line="177"/>
<source>Default view</source> <source>Default view</source>
<translation>Vue par défaut</translation> <translation>Vue par défaut</translation>
</message> </message>
<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> <source>Back to previous view</source>
<translation>Retour a la vue précédente</translation> <translation>Retour a la vue précédente</translation>
</message> </message>
<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> <source>Return to next view</source>
<translation>Retour à la vue suivante</translation> <translation>Retour à la vue suivante</translation>
</message> </message>
<message> <message>
<location filename="../View/Plot/navigation_toolbar_2qt.py" line="181"/> <location filename="../View/Plot/PamhyrToolbar.py" line="180"/>
<source>Axes panoramic</source> <source>Axes panoramic</source>
<translation>Axes panoramique</translation> <translation>Axes panoramique</translation>
</message> </message>
<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> <source>Isometric view (Shift+W)</source>
<translation>Vue isométrique (Shift+W)</translation> <translation>Vue isométrique (Shift+W)</translation>
</message> </message>
<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> <source>Auto scale view (Shift+X)</source>
<translation>Vue automatique (Shift+X)</translation> <translation>Vue automatique (Shift+X)</translation>
</message> </message>
<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> <source>Save the figure</source>
<translation>Sauvegarder la figure</translation> <translation>Sauvegarder la figure</translation>
</message> </message>
+47 -64
View File
@@ -34,85 +34,68 @@ from tools import (
from View.MainWindow import ApplicationWindow from View.MainWindow import ApplicationWindow
from Model.Study import Study from Model.Study import Study
logging.basicConfig( from Scripts.Hello import ScriptHello
level=logging.DEBUG,
format=(f'[{logger_color_blue()}PAMHYR{logger_color_reset()}]' + from init import license, setup_lang
f'[{logger_color_green()}%(levelname)s{logger_color_reset()}]' +
' %(message)s')
)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO)
try: scripts = {
log = os.path.join( "hello": ScriptHello,
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(): def usage(argv):
blue = lambda s: logger.info(f"{logger_color_blue()}{s}{logger_color_reset()}") 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.""") def gui(app:QApplication, conf:Config):
blue(""" MM `MM. ;MM: MMMb dPMM MM MM VMA ,V MM `MM.""") application = ApplicationWindow(conf=conf)
blue(""" MM ,M9 ,V^MM. M YM ,M MM MM MM VMA ,V MM ,M9 pd*"*b.""") application.show()
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( return app.exec_()
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 main(): def main():
conf = Config.load() conf = Config.load()
app = QApplication(sys.argv) app = QApplication(sys.argv)
translator = QTranslator() tr = setup_lang(app, conf)
app.installTranslator(tr)
license() license()
lang_file = "" if len(sys.argv) > 1:
if conf.lang == "": # Run a script
# System language script = sys.argv[1]
sys_lang = locale.getdefaultlocale()
if "fr" in sys_lang[0]: if script == "help":
lang_file = os.path.dirname(__file__) + "/lang/fr.qm" ret = usage(sys.argv)
elif conf.lang == "fr": elif script == "gui":
# French ret = gui(app, conf)
lang_file = os.path.dirname(__file__) + "/lang/fr.qm" 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: else:
# English default language # No args, run Pamhyr2 interface
lang_file = "" 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() display_timers()
sys.exit(ret) sys.exit(ret)
+1
View File
@@ -247,6 +247,7 @@ class SQL(object):
self._init_db_file(filename) self._init_db_file(filename)
def commit(self): def commit(self):
logger.debug("SQL - commit")
self._db.commit() self._db.commit()
def _fetch_string(self, s): def _fetch_string(self, s):