diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5f12c3e7..7b58f4f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -98,6 +98,8 @@ build-users-doc: artifacts: paths: - doc/users/documentation.pdf + - doc/users/documentation.html + - doc/users/images/ build-developers-doc: stage: build @@ -109,9 +111,19 @@ build-developers-doc: script: - cd ./doc/dev/ - ./build.sh + # Create html documentation + - cd ../../src + - pdoc --html . + # Create archive + - tar cf html.tar ./html + - cd ../doc/dev + - mv ../../src/html.tar ./ artifacts: paths: - doc/dev/documentation.pdf + - doc/dev/documentation.html + - doc/dev/html.tar + - doc/dev/images/ build-linux: stage: build @@ -167,8 +179,10 @@ build-linux: - cp ../tests_cases/Saar/Saar.pamhyr pamhyr/tests_cases/Saar/ # Copy documentations - mkdir -p pamhyr/doc/ + - mkdir -p pamhyr/doc/html/ - cp ../doc/users/documentation.pdf pamhyr/doc/Pamhyr2-users.pdf - cp ../doc/dev/documentation.pdf pamhyr/doc/Pamhyr2-dev.pdf + - cp ../doc/dev/documentation.html pamhyr/doc/html/dev.html # Create running script - echo "#!/bin/sh" > pamhyr/Pamhyr2 - echo "./pamhyr/pamhyr $@" >> pamhyr/Pamhyr2 diff --git a/doc/dev/build.sh b/doc/dev/build.sh index 476dd5ad..2e8deb56 100755 --- a/doc/dev/build.sh +++ b/doc/dev/build.sh @@ -1,10 +1,18 @@ #! /bin/sh -# Build the PDF - -emacs -Q -q -nw --batch \ +echo " PDF documentation.org" +emacs -nw --batch \ --eval "(require 'org)" \ --eval "(require 'ox-latex)" \ --eval "(load-file \"../tools/setup.el\")" \ --file ./documentation.org \ -f org-latex-export-to-pdf + +echo " HTML documentation.org" +emacs -nw --batch \ + --eval "(require 'org)" \ + --eval "(require 'ox-latex)" \ + --eval "(require 'ox-html)" \ + --eval "(load-file \"../tools/setup.el\")" \ + --file ./documentation.org \ + -f org-html-export-to-html diff --git a/doc/dev/documentation.org b/doc/dev/documentation.org index e245b959..e50fe58e 100644 --- a/doc/dev/documentation.org +++ b/doc/dev/documentation.org @@ -73,7 +73,7 @@ https://doc.qt.io/qt-5/model-view-programming.html (last access [fn:org] The org-mode website: https://orgmode.org/ (last access 2023-09-15) -* TODO Architecture +* Architecture Pamhyr2's architecture is based on Qt Model/View, see Figure [[graph-architecture]]. It is made up of several different components: the @@ -97,35 +97,68 @@ simulation results to model data. #+name: graph-architecture #+header: :results drawer #+header: :exports results -#+header: :post attr_wrap(width="9cm", 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") +#+header: :post attr_wrap(width="12cm", 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 { bgcolor="transparent"; node[colorscheme=set19,shape=box,style="filled",fillcolor=white]; + edge[colorscheme=set19,color=0]; + + root[style=invis]; + + subgraph cluster0 { + label="File System" + style=dashed; + + save[label="Pamhyr save",fillcolor="9",shape=note]; + sbin[label="Solver binary",fillcolor="9",shape=note]; + configfile[label="Pamhyr configuration file",fillcolor="9",shape=note]; + } - save[label="Pamhyr save",fillcolor="9",shape=note]; - model[label="Model",fillcolor="2"]; - view[label="View",fillcolor="1"]; - delegate[label="Delegate",fillcolor="3"]; - action[label="Action",fillcolor="3"]; - solver[label="Solver",fillcolor="6"]; - undocommand[label="Command",fillcolor="4"]; user[label="User",shape=ellipse]; - model -> save[label="Save"]; - save -> model[label="Load"]; + subgraph cluster1 { + label="Pamhyr2"; + + config[label="Configuration",fillcolor="5"]; + model[label="Model",fillcolor="2"]; + view[label="View",fillcolor="1"]; + delegate[label="Delegate",fillcolor="3"]; + action[label="Action",fillcolor="3"]; + solver[label="Solver",fillcolor="6"]; + undocommand[label="Command",fillcolor="4"]; + } + + root -> model[style=invis]; + root -> config[style=invis]; + + model -> save[dir=both,label="Save/Load"]; + config -> configfile[dir=both,label="Save/Load"]; + + undocommand -> solver[style=invis]; + action -> solver[style=invis]; + delegate -> solver[style=invis]; model -> view[label="Rendering"]; view -> delegate[label="Rendering"]; delegate -> undocommand[label="Create"]; - action -> undocommand[label="Create"]; + action -> undocommand[label="Create/use"]; action -> solver[label="Run"]; - solver -> model[label="Add results"]; + solver -> model[dir=both,label="Export/Results",labelangle=0,labelfloat=true,constraint=true]; + solver -> sbin[dir=both,label="Execute/Results"]; undocommand -> model[label="Modify"]; view -> user[label="Vizualize"]; user -> delegate[label="Modify"]; - user -> action[label="Activate"]; + user -> action[label="Triggere"]; + + config -> solver[label="Create",style=dashed,labelfloat=true,constraint=true]; + //model -> config[style=invis]; + config -> model[style=invis]; + delegate -> action[style=invis]; + save -> configfile[style=invis]; + root -> solver[style=invis]; + user -> solver[style=invis]; } #+end_src @@ -134,130 +167,684 @@ All the model source code are in the directory {{{file(src/Model)}}} delegate and command are in {{{file(src/View)}}} (see section [[View]]). Solvers classes are in {{{file(src/Solver)}}} (see section [[Solver]]). +The following sub section show examples of main {{{pamhyr}}} internal +class for view componants, but this documentation is not exhaustive, +be free to watch existing code for more details and examples. In, +addition some features are not factorise and must be implemented from +scratch (directly with Qt for example). + [fn:qt-mv] The Qt Model/View documentation web page: https://doc.qt.io/qt-5/model-view-programming.html -** TODO Model +** Model -- Model de donnée Python -- Correspond à une sauvegarde SQL +The model is a set of Python classes. In Pamhyr2, this classes must +respect some constraint. Each model class must inherits +=Model.Tools.SQLSubModel= abstract class, except the =Model.Study= +class who inherits =Model.Tools.SQLModel= (see [[SQL]]). + +The model entry point is the Study class. It contains infomation about +the study: their name, description, time system, and so on. Their +contains a River object too. This river object inherits the network +graph and contains a list of =RiverNode= and a list of =RiverReach= +(an edge who contains a source node, and destination node). +=RiverReach= contrains geometry, so, the river network (node and edge) +associated with the geometry forms the basis of the model, and the +other components are linked to one of these basic components. #+name: graph-model #+header: :results drawer #+header: :exports results -#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 model class dependencies", float="t") +#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 model class dependencies (A -> B means A can contain references to B)", float="t") #+begin_src dot :file "images/graph-model.png" :cache no digraph { - bgcolor="transparent"; - node[colorscheme=set19,shape=box,style="filled",fillcolor="2"]; + bgcolor="transparent"; + node[colorscheme=set19,shape=box,style="filled",fillcolor="2"]; - study[label="Study"]; - river[label="River"]; + //subgraph cluster0 { + // style=dashed; + study[label="Study"]; + river[label="River"]; - subgraph cluster00 { - label="Network" - rnode[label="RiverNode"]; - redge[label="RiverReach"]; - } + subgraph cluster00 { + style=solid; + label="Network" + rnode[label="RiverNode"]; + redge[label="RiverReach"]; + } - frictionlist[label="FrictionList"]; + subgraph cluster06 { + style=solid; + label="Greometry" + georeach[label="Reach"]; + geocrosssection[label="Cross-section"]; + geopoint[label="Point"]; + } + //} - 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"]; - } + //subgraph cluster1 { + // style=dashed; + frictionlist[label="FrictionList"]; - solverparameterslist[label="SolverParametersList"]; + subgraph cluster01 { + style=solid; + label="Stricklers"; + stricklers[label="Stricklers"]; + stricklerslist[label="StricklersList"]; + } + subgraph cluster02 { + style=solid; + label="BoundaryCondition"; + boundaryconditionlist[label="BoundaryConditionList"]; + boundarycondition[label="BoundaryCondition"]; + } + subgraph cluster03 { + style=solid; + label="LateralContribution"; + lateralcontributionlist[label="LateralContributionList"]; + lateralcontribution[label="LateralContribution"]; + } + subgraph cluster04 { + style=solid; + label="InitialConditions"; + initialconditionsdict[label="InitialConditionsDict"]; + initialconditions[label="InitialConditions"]; + } - subgraph cluster05 { - label="Sediment"; - sedimentlayerlist[label="SedimentLayerList"]; - sedimentlayer[label="SedimentLayer"]; - layer[label="Layer"]; - } + solverparameterslist[label="SolverParametersList"]; - subgraph cluster06 { - label="Greometry" - georeach[label="Reach"]; - geocrosssection[label="Cross-section"]; - geopoint[label="Point"]; - } + subgraph cluster05 { + style=solid; + label="Sediment"; + sedimentlayerlist[label="SedimentLayerList"]; + sedimentlayer[label="SedimentLayer"]; + layer[label="Layer"]; + } + //} - subgraph cluster07 { - label="Results" - results[label="Results"] - rriver[label="River"]; - rreach[label="Reach"]; - rcrosssection[label="Cross-section"]; - } + subgraph cluster2 { + style=dashed; + label="Results" + results[label="Results"] + rriver[label="River"]; + rreach[label="Reach"]; + rcrosssection[label="Cross-section"]; + } - 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; + 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; - results -> study[style="dashed"]; - results -> rriver; - rriver -> river[style="dashed"]; - rriver -> rreach; - rreach -> georeach[style="dashed"]; - rreach -> rcrosssection; - rcrosssection -> geocrosssection[style="dashed"]; + results -> study; + results -> rriver; + rriver -> river; + rriver -> rreach; + rreach -> georeach; + rreach -> rcrosssection; + rcrosssection -> geocrosssection; + + // river -> boundaryconditionlist -> boundarycondition -> results[style=invis]; + // river -> lateralcontributionlist -> lateralcontribution -> results[style=invis]; + // river -> initialconditionsdict -> initialconditions -> results[style=invis]; + // initialconditions -> results[style=invis]; + // river -> stricklerslist -> stricklers -> results[style=invis]; + // river -> solverparameterslist -> results[style=invis]; + // river -> sedimentlayerlist -> sedimentlayer -> layer -> results[style=invis]; + + geopoint -> boundaryconditionlist[style=invis]; + geopoint -> lateralcontributionlist[style=invis]; + geopoint -> initialconditionsdict[style=invis]; + geopoint -> initialconditions[style=invis]; + geopoint -> stricklerslist[style=invis]; + geopoint -> solverparameterslist[style=invis]; + geopoint -> sedimentlayerlist[style=invis]; } #+end_src -*** TODO SQL study file -*** TODO List class -*** TODO Dict class -** TODO View -*** TODO UI file -*** TODO Translate -*** TODO Window -*** TODO UndoCommand -*** TODO Table -*** TODO Plot -*** TODO Mainwindow -** TODO Solver +*** SQL + +The model must be export to a database file to create a study save +file. This file use SQLite3[fn:sqlite] format and the extention +=.pamhyr=. So, each model componante must be register into this study +file. To create, update, set and get information into SQLite database +we use SQL command. The database use version number and some +modification could be perform to update database. For each model +componante, correspond one or more SQL table to store information. To +normalize the interaction with database we made two classes, SQLModel +and SQLSubModel. The Study class use SQLModel because is the top of +the model hierachy. The rest of model class inherits to SQLSubModel. + +A class who inherits SQLSubModel, must implement some methods: +- =_sql_create=: Class method to create the database scheme +- =_sql_update=: Class method to update the database scheme if necessary +- =_sql_load=: Class method to load data from DB +- =_sql_save=: Method to save current object into DB + +Class method take in arguments: The class (=cls=), a function to +execute SQL command into the database (=execute=). In addition, the +update method take the previous version of database, load method take +an optional arguments =data= if additional infomation ar needed, and +who can contains whatever you want. The method save take in arguments +the current object (=self=), a function to execute SQL command into +the database (=execute=), and optional data (=data=). + +The class who inherits SQLSubModel can also define an class attribute +=_sub_classes= to set a formal class dependencies into database. This +attribute is use at database creation to create all table, and at +update to update all the database table. Let see examples of +SQLSubModel usage for two classes Foo and Bar with Foo contains list +of Bar (Listing [[sql-bar]] and [[sql-foo]]). + +#+NAME: sql-bar +#+CAPTION: Exemple of class Bar inherits SQLSubModel. +#+begin_src python :python python3 :results output :noweb yes + from Model.Tools.PamhyrDB import SQLSubModel + + class Bar(SQLSubModel): + _id_cnt = 0 + def __init__(self, id = -1, x = 0, y = 0): + self._x = x + self._y = y + if id == -1: + self.id = Bar._id_cnt + 1 + else: + self.id = id + Bar._id_cnt = max(id, Bar._id_cnt+1) + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE bar ( + id INTEGER NOT NULL PRIMARY KEY, + x INTEGER NOT NULL, + foo_id INTEGER NOT NULL, + FOREIGN KEY(foo_id) REFERENCES foo(id), + )""") + return True + + @classmethod + def _sql_update(cls, execute, version): + # If version is lesser than 0.0.2, add column to bar table + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + execute("ALTER TABLE bar ADD COLUMN y INTEGER") + return True + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + table = execute( + f"SELECT id, x, y FROM bar WHERE foo_id = {data['id']}" + ) + for row in table: + bar = cls( + id = row[0], x = row[1], y = row[2], + ) + new.append(bar) + return new + + def _sql_save(self, execute, data = None): + execute("INSERT INTO bar (id,x,y,foo_id) VALUES " + + f"({self.id}, {self._x}, {self._y}, {data['id']})") +#+end_src + +#+NAME: sql-foo +#+CAPTION: Exemple of class Foo inherits SQLSubModel and contains a list of Bar object (Listing [[sql-bar]]). +#+begin_src python :python python3 :results output :noweb yes + class Foo(SQLSubModel): + _id_cnt = 0 + _sub_classes = [Bar] + + def __init__(self, id = -1, name = ""): + self._name = name + self._bar = [] + # ... + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE foo ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + ) + """) + return cls._create_submodel(execute) + + @classmethod + def _sql_update(cls, excute, version): + return cls._update_submodel(execute, version) + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + table = execute( + "SELECT id, name FROM foo" + ) + for row in table: + foo = cls( + id = row[0], + name = row[1], + ) + data = { + "id": row[0], # Current Foo ID + } + foo._bar = Bar._sql_load(execute, data=data) + new.append(foo) + return new + + def _sql_save(self, execute, data = None): + execute(f"DELETE FROM foo WHERE id = {self.id}") + execute(f"DELETE FROM bar WHERE foo_id = {self.id}") + # Save new data + execute(f"INSERT INTO bar (id,name) VALUES ({self.id}, {self._name})") + data = {"id": self.id} + for bar in self._bar: + bar._sql_save(execute, data=data) +#+end_src + +[fn:sqlite] The SQLite web site: https://www.sqlite.org/index.html +(last access 2023-09-20) + +*** List class + +A abstract class PamhyrModelList is available and provide some of +basic methods for object list in Model. This abstract class implement +some classic action in View like: insert new object, delete object, +sort, move object up, move object down, and so on. An variant exists +for multiple list with same type of object, each sublist is called +tab, because in View, this kind of list si prensented in different +table PamhyrModelListWithTab. + +*** Dict class + +A abstract class PamhyrModelDict is available and provide some of +basic methods for object dictionary in Model. This class is like +PamhyrModelList but use a dictionary instead of list. + +** View + +Pamhyr2 use Qt as graphical user interface library with the +application "Qt designer" for windows or widget creation (see [[UI file]]) +and "Qt linguist" for interface translate (see [[Translate]]). In +addition, we use matplotlib as ploting library (see [[Plot]]). + +Typically, each model componant have an associated window in +application to add, delete or edit this componant. At top level of +View directory we found the =MainWindow.py= file and some +sub-directories. A view sub-directory contains: A =Window.py= file, a +=Table.py= file with table model definition if nessessary, one or more +=Plot*.py= file with plot class definition, a =translate.py= file with +componant translate, and possible other files or sub-directories. + +*** UI file + +We define as possible all Pamhyr2 windows and custom widgets with "Qt +designer". This application generate UI file who describes interface +organisation with table, layout, button, etc. This method is faster +than hand made windows and widget creation, and saves us some purely +descriptive code. The UI files are saved into =src/View/ui= for +window, and =/src/View/ui/Widgets= for custom widget. + +*** Translate + +#+NAME: pamhyr-trad +#+CAPTION: Example of =PamhyrTranslate= class implementation with a global traduction for /FooBar/ and a additional dictionary =table_headers= +#+begin_src python :python python3 :results output :noweb yes + from PyQt5.QtCore import QCoreApplication + from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate + + class MyTranslate(PamhyrTranslate): + def __init__(self): + super(MyTranslate, self).__init__() + + # Add traduction to global dictionary + self._dict["My"] = _translate("My", "FooBar") + # Add an additional translate dictionary + self._sub_dict["table_headers"] = { + "foo": _translate("My", "Foo"), + "bar": _translate("My", "Bar"), + "baz": _translate("My", "Baz"), + } +#+end_src + +*** Window + +The abstract class PamhyrWindow and PamhyrDialog are used for most of +Pamhyr2 window. These class allow to create an window for Pamhyr2 GUI +and implemente some useful methods. The super class method difine some +generic value from optional parameters, for examples: +- =self._study=: The study giving in constructor parameters =study= + (typically a =Model.Study= class object) +- =self._config=: The configuration giving in constructor parameters + =config= (typically a =Config= class object) +- =self._trad=: The traductor dictionary giving in constructor + parameters =trad= (typically a =Model.Tools.PamhyrTranslate= class + object) + +#+NAME: window +#+CAPTION: Example of Pamhyr2 window +#+begin_src python :python python3 :results output :noweb yes + from View.Tools.PamhyrWindow import PamhyrWindow + from View.My.Translate import MyTranslate + from View.My.Table import MyTableModel + + class MyWindow(PamhyrWindow): + _pamhyr_ui = "MyUI" + _pamhyr_name = "My window" + + def __init__(self, my_data=None, + study=None, config=None, + parent=None): + self._my_data = my_data + + super(MyWindow, self).__init__( + # Window title + title = self._pamhyr_name + " - " + study.name, + # Window standard data + study = study, config = config, + trad = MyTranslate(), + parent = parent, + # Activate undo/redo and copy/paste shortcut + options = ["undo", "copy"] + ) + + # Add custom data to hash window computation + self._hash_data.append(self._my_data) + + # Setup custom window components + self.setup_table() + self.setup_connections() + + def setup_table(self): + # Init table(s)... + + def setup_connections(self): + # Init action connection(s)... + + # ... +#+end_src + +Typically we called method =setup_*=, the method to initialize some +window componants or connections. + +*** Table + +An abstract class PamhyrTableModel is available to define a simple +QAbstractTableModel shortly. In simple cases, there are only =data= +and =setData= methode to implement, but the constructor needs more +information than a classic QAbstractTableModel class. + +#+NAME: table-src +#+CAPTION: Definition of a table model from =PamhyrTableModel= in a file =View/My/Table.py=. +#+begin_src python :python python3 :results output :noweb yes + from View.Tools.PamhyrTable import PamhyrTableModel + + class MyTableModel(PamhyrTableModel): + def data(self, index, role): + # Retrun data at INDEX... + + @pyqtSlot() + def setData(self, index, value, role=Qt.EditRole): + # Set VALUE at INDEX... +#+end_src + +#+CAPTION: Using the table model defined in Listing [[table-src]] in window funtion =setup_table= defined Listing [[window]]. +#+begin_src python :python python3 :results output :noweb yes + # Table model creation (Window.py: setup_table) + table_headers = self._trad.get_dict("table_headers") + self._model = MyTableModel( + table_view = table, # The table view object + table_headers = table_headers, # The table column headers dict + # (with traduction) + editable_headers = ["foo", "bar"], # List of editable column name + delegates = { + "bar": self.my_delegate, # Custom delegate for column 'bar' + }, + data = self._my_lst, # The data + undo = self._undo_stack, # The window undo command stack + ) +#+end_src + +*** UndoCommand + +All model modification must be done by an QUndoCommand, this command +allow to undo and redo an action. This a Qt class wi can inherit to +define custom undo command (see example Listing [[undo-cmd]]) + +#+NAME: undo-cmd +#+CAPTION: Example of custom UndoCommand, this command allow to add a node to graph in river network window (method redo), and delete it to graph with undo method +#+begin_src python :python python3 :results output :noweb yes +class AddNodeCommand(QUndoCommand): + def __init__(self, graph, node): + QUndoCommand.__init__(self) + + self._graph = graph + self._node = node + + def undo(self): + self._graph.remove_node(self._node.name) + + def redo(self): + self._graph.insert_node(self._node) +#+end_src + +All undo command must be push into a =QUndoStack= (see Listing +[[undo-cmd-push]]) to perform the action and allow user undo and redo this +action. In PamhyrWindow (and PamhyrDialog) the undo stack is +automatically create if the option ="undo"= is activate at window +creation, this stack is accessible at =self._undo_stack=. + +#+NAME: undo-cmd-push +#+CAPTION: Example of UndoCommand push into an undo stack. +#+begin_src python :python python3 :results output :noweb yes + self._undo_stack.push( + AddNodeCommand( + self._graph, + node + ) + ) +#+end_src + +*** Plot + +To define a new plot you can create a class who inherit to APlot. The +creator need threee argument: + - A =canvas= of type =MplCanvas= + - A =data= used in =draw= and =update= to create and update the plot + - A optional =toolbar= of type =PamhyrToolbar= +This class must implement two method =draw= and =update=, the first +method to draw the plot from scratch, the second to update the plot if +data has changed. + +#+begin_src python :python python3 :results output :noweb yes + from View.Tools.PamhyrPlot import PamhyrPlot + + class MyPlot(APlot): + def __init__(self, canvas=None, data=None, toolbar=None): + super(MyPlot, self).__init__( + canvas=canvas, + data=data, + toolbar=toolbar + ) + + def draw(self): + # Draw function code... + + def update(self, ind = None): + # Update function code... +#+end_src + +** Solver + +The Pamhyr2 architecture allow to define multiple solver. A solver is +define by a: + - type + - name + - description, + - path + - command line pattern + - (optional) input formater path + - (optional) input formater command line + - (optional) output formater path + - (optional) output formater command line + +Let see Figure [[graph-multi-solver]], the application can implement +different solver type, this solver type implement the code for export +study to solver input format, and read the solver output to study +results. There exists a generic solver with a generic input and output +format, the type could be use to use a solver not implemented in +Pamhyr2, but this solver must can read/write input and output generic +format or use external script. There is possible to define different +solver with the same type, for example two differents version of the +same solver. Finaly, with input and output formater is possible to +execute a code on distant computer, for example, over ssh. + +#+name: graph-multi-solver +#+header: :results drawer +#+header: :exports results +#+header: :post attr_wrap(width="12cm", data=*this*, name="graph-multi-solver", caption="Scheme of multiple solver configured, one Rubarbe solver and two Mage solver with one on local machine and one on a distant machine accessed over ssh", float="t") +#+begin_src dot :file "images/graph-multi-solver.png" :cache no + digraph { + bgcolor="transparent"; + node[colorscheme=set19,shape=box,style="filled",fillcolor=9]; + edge[colorscheme=set19,color=0]; + + subgraph cluster00 { + label="User personal computer"; + style=solid; + + subgraph cluster0 { + label="Pamhyr2"; + style=solid; + + // subgraph cluster01 { + // label="Core"; + //model[label="Model", fillcolor=2]; + //view[label="View", fillcolor=1]; + config[label="Configuration", fillcolor=5]; + + // view -> model -> view; + // } + + subgraph cluster02 { + label="Solver"; + style=dashed; + subgraph cluster021 { + label="Solver Classes"; + classSolverM7[label="Mage7", fillcolor=6]; + classSolverM8[label="Mage8", fillcolor=6]; + classSolverR[label="RubarBE", fillcolor=6]; + } + //classSolverX[label="Solver X Binding", fillcolor=6]; + + subgraph cluster022 { + label="Solver Object"; + solverM[label="Mage", fillcolor=6]; + solverM2[label="Mage over ssh", fillcolor=6]; + solverR[label="RubarBE", fillcolor=6]; + //solverX[label="Solver X", fillcolor=6]; + } + + classSolverM8 -> solverM [style=dashed]; + classSolverM8 -> solverM2[style=dashed]; + classSolverR -> solverR[style=dashed]; + //classSolverX -> solverX[style=dashed]; + } + + //config -> solverM[style="dotted"]; + //config -> solverR[style="dotted"]; + //config -> solverX[style="dotted"]; + + //model -> solverM; + //model -> solverM2; + //model -> solverR; + //model -> solverX; + } + + subgraph cluster1 { + label="File System"; + style=dashed; + + mage[label="Mage Binary",shape=note]; + //X[label="Solver X Binary"]; + rubarbe[label="RubarBE Binary",shape=note]; + ssh[label="ssh",shape=note]; + } + } + + //config -> X[style=invis]; + //model -> config[style=invis]; + config -> solverM[label="",constraint=true]; + //config -> solverX[label="",constraint=true]; + config -> solverR[label="",constraint=true]; + config -> solverM2[label="",constraint=true]; + + subgraph cluster2 { + label="Distant server"; + style=solid; + + sshd[label="sshd"]; + + subgraph cluster21 { + label="File System"; + style=dashed; + + mage2[label="Mage Binary",shape=note]; + } + } + + solverM -> mage[label="", color=1]; + mage -> solverM[label="", color=2]; + + //solverX -> X[label="", color=1]; + //X -> solverX[label="", color=2]; + + solverR -> rubarbe[label="", color=1]; + rubarbe -> solverR[label="", color=2]; + + solverM2 -> ssh -> sshd -> mage2[label="", color=1]; + mage2 -> sshd -> ssh -> solverM2[label="", color=2]; + } +#+end_src + +Let see Figure [[graph-pipeline]] the temporal order of action to run a +solver and get results: + - (1) Write solver input file(s) using the study data + - (2) Run the solver + - (2.1) The solver read the input file(s) + - (2.2) The solver compute results and write it to solver output + file(s) + - (3) Pamhyr2 create a Reuslts + - (3.1) The Pamhyr2 solver class read solver output file(s) and + complete Results with readed data #+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") +#+header: :post attr_wrap(width="10cm", 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 { bgcolor="transparent"; node[colorscheme=set19,shape=box,style="filled",fillcolor=9]; + edge[colorscheme=set19,color=0]; subgraph cluster0 { label="Pamhyr2" - config[label="Config",fillcolor=5]; + config[label="Configuration",fillcolor=5]; model[label="Model",fillcolor=2]; obj[label="Solver",fillcolor=6]; results[label="Results",fillcolor=2]; @@ -271,26 +858,130 @@ https://doc.qt.io/qt-5/model-view-programming.html obj -> model[style="dashed"]; subgraph cluster1{ - label="System"; - in[label="Solver input files",shape=note]; - out[label="Solver output files",shape=note]; + label="File System"; + style=dashed; + in[label="Solver input file(s)",shape=note,fillcolor=white]; + out[label="Solver output file(s)",shape=note,fillcolor=white]; bin[label="Solver binary",shape=note]; } - obj -> in[label="Write (1)"]; - obj -> bin[label="Execute (2)"]; - bin -> in[label="Read (2.1)"]; - bin -> out[label="Write (2.2)"]; - obj -> results[label="Create (3)"]; - obj -> out[label="Read (3.1)"]; + obj -> in[label="Write (1)",color=1]; + obj -> bin[label="Execute (2)",color=1]; + bin -> in[label="Read (2.1)",color=1]; + bin -> out[label="Write (2.2)",color=2]; + obj -> results[label="Create (3)",color=2]; + obj -> out[label="Read (3.1)", color=2]; view -> model[style="dashed"]; view -> results[style="dashed"]; } #+end_src -** TODO Unit tests -** TODO The debug mode -* TODO Build the project +In case of generic solver (or a solver with input and output formater) +the temporal order of action is prensented in Figure +[[graph-pipeline-generic]]. + +#+name: graph-pipeline-generic +#+header: :results drawer +#+header: :exports results +#+header: :post attr_wrap(width="12cm", data=*this*, name="graph-pipeline-generic", caption="Pamhyr2 generic solver execution pipeline architecture scheme", float="t") +#+begin_src dot :file "images/graph-pipeline-generic.png" :cache no + digraph { + bgcolor="transparent"; + node[colorscheme=set19,shape=box,style="filled",fillcolor=9]; + edge[colorscheme=set19,color=0]; + + subgraph cluster0 { + label="Pamhyr2" + config[label="Configuration",fillcolor=5]; + model[label="Model",fillcolor=2]; + obj[label="Generic solver",fillcolor=6]; + results[label="Results",fillcolor=2]; + view[label="View",fillcolor=1]; + + results -> model[style="dashed"]; + results -> obj[style="dashed"]; + } + + config -> obj[label=""]; + obj -> model[style="dashed"]; + + subgraph cluster1{ + label="File System"; + style=dashed; + + gin[label="Generic input file", shape=note,fillcolor=white]; + ibin[label="Input formater", shape=note]; + in[label="Solver input file(s)",shape=note,fillcolor=white]; + out[label="Solver output file(s)",shape=note,fillcolor=white]; + gout[label="Generic results file",shape=note,fillcolor=white]; + obin[label="Output formater", shape=note]; + bin[label="Solver binary",shape=note]; + } + + gin -> ibin[style=invis]; + ibin -> bin -> obin[style=invis]; + in -> bin[style=invis]; + obin -> gout[style=invis]; + + // Input format + obj -> gin[label="Write (1)",color=1]; + obj -> ibin[label="Execute (2)",color=1,style=dashed]; + ibin -> gin[label="Read (2.1)",color=1]; + ibin -> in[label="Write (2.2)",color=1]; + + // Solve + obj -> bin[label="Execute (3)",color=1,style=dashed]; + bin -> in[label="Read (3.1)",color=1]; + bin -> out[label="Write (3.2)",color=2]; + + // Output format + obj -> obin[label="Execute (4)",color=2,style=dashed]; + obin -> out[label="Read (4.1)",color=2]; + obin -> gout[label="Write (4.2)",color=2]; + + // Read results + obj -> results[label="Create (5)",color=2]; + obj -> gout[label="Read (5.1)", color=2]; + view -> model[style="dashed"]; + view -> results[style="dashed"]; + } +#+end_src + +To implement a Solver in Pamhyr2, there exists a abstract class +=Solver.AbstractSolver=. A class who herits this class, must implement +different methods: + - =export=: Export the study to solver input file(s) + - =input_param=: Return the solver input parameter(s) as string + - =log_file=: Return the solver log file name as string + - =results=: Read the solver output file(s) and return a + =Model.Results= object. + +** Unit tests + +The unit tests is actually not implemented in Pamhyr2, it is a *work +in progress*. + +** The debug mode + +To activate an deactivate the Pamhyr2 debug mode you can open the +configuration window and type "Ctrl+G" or run Pamhyr2 with command +line: +#+begin_src shell +./Pamhyr2 debug +#+end_src + +This mode add some log and add two action in main window menu: +"About/Debug" open a window with Python Repl in current Python +environement, and "About/Debug SQLite" who open the application +SQLiteBrowser (if installed) on current Study to explore the study +data base file. + +#+NAME: debug-repl +#+ATTR_LATEX: :width 12cm +#+CAPTION: Pamhyr2 debug Python REPL +[[./images/python-debug-repl.png]] + +* Build the project The project uses gitlab-ci runners to build packages, but it is possible to build packages manually. @@ -335,7 +1026,45 @@ winetricks installed. [fn:nsis] The NSIS web site: https://sourceforge.net/projects/nsis/ -** TODO Setup the CI environment +** Setup the CI environment + +Pamhyr2 need a Linux ci-runner and a Windows ci-runner for building +package. The windows ci-runner could run on a Wine environement. + +*** Linux + +The Linux ci-runner need some software and dependencies in addtion of +gitlab-ci. + +#+begin_src shell + sudo apt install \ + emacs emacs-goodies-el \ + texlive-full \ + python3.8 python3.8-venv + sudo python3 -m pip install pyinstaller +#+end_src + +*** Windows (Wine) + +The ci-runner environment for Wine need at least wine version 8, let +[[https://www.numetopia.fr/comment-installer-wine-sur-ubuntu-ou-linux-mint/][see who to add wine official depot to your linux distribution]]. + +#+begin_src shell +sudo apt install wine-stable winetricks +#+end_src + +In addition, the environment need windows version of: + - [[https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe][Python 3.8.10]] + - Git + - PowerShell + - Gitlab-ci + - [[https://freefr.dl.sourceforge.net/project/nsis/NSIS%203/3.08/nsis-3.08-setup.exe][Nsis]] + +Now, we can install =pyinstaller= on this windows environment: +#+begin_src shell +wine python -m pip install pyinstaller +#+end_src + * Documentation files This document and the user documentation are org files. This text file diff --git a/doc/dev/images/python-debug-repl.png b/doc/dev/images/python-debug-repl.png new file mode 100644 index 00000000..847f4afc Binary files /dev/null and b/doc/dev/images/python-debug-repl.png differ diff --git a/doc/users/build.sh b/doc/users/build.sh index 476dd5ad..2e8deb56 100755 --- a/doc/users/build.sh +++ b/doc/users/build.sh @@ -1,10 +1,18 @@ #! /bin/sh -# Build the PDF - -emacs -Q -q -nw --batch \ +echo " PDF documentation.org" +emacs -nw --batch \ --eval "(require 'org)" \ --eval "(require 'ox-latex)" \ --eval "(load-file \"../tools/setup.el\")" \ --file ./documentation.org \ -f org-latex-export-to-pdf + +echo " HTML documentation.org" +emacs -nw --batch \ + --eval "(require 'org)" \ + --eval "(require 'ox-latex)" \ + --eval "(require 'ox-html)" \ + --eval "(load-file \"../tools/setup.el\")" \ + --file ./documentation.org \ + -f org-html-export-to-html diff --git a/doc/users/images/.gitkeep b/doc/users/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/linux.sh b/packages/linux.sh index 215047e8..9a5787a7 100755 --- a/packages/linux.sh +++ b/packages/linux.sh @@ -43,18 +43,21 @@ cd $OLD_PWD echo " *** COPY DATA" -mkdir -p dist/pamhyr/View/ui -mkdir -p dist/pamhyr/View/ui/Widgets -cp -r ../src/View/ui/ressources/ dist/pamhyr/View/ui/ -cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/View/ui/Widgets/ -cp -r ../src/View/ui/*.ui dist/pamhyr/View/ui/ +mkdir -p dist/pamhyr/_internal/View/ui +mkdir -p dist/pamhyr/_internal/View/ui/Widgets +cp -r ../src/View/ui/ressources/ dist/pamhyr/_internal/View/ui/ +cp -r ../src/View/ui/Widgets/*.ui dist/pamhyr/_internal/View/ui/Widgets/ +cp -r ../src/View/ui/*.ui dist/pamhyr/_internal/View/ui/ mkdir -p dist/pamhyr/lang cp -r ../src/lang/*.qm dist/pamhyr/lang/ -cp ../VERSION dist/pamhyr/ -cp ../AUTHORS dist/pamhyr/ -cp ../LICENSE dist/pamhyr/ +mkdir dist/pamhyr/_internal/ +#cp ../VERSION dist/pamhyr/_internal/ +VERSION=$(cat ../VERSION) +echo "local-build-$VERSION" > dist/pamhyr/_internal/VERSION +cp ../AUTHORS dist/pamhyr/_internal/ +cp ../LICENSE dist/pamhyr/_internal/ mkdir -p dist/pamhyr/mage/ cp ../mage/mage dist/pamhyr/mage/ @@ -65,6 +68,18 @@ mkdir -p dist/pamhyr/tests_cases/ mkdir -p dist/pamhyr/tests_cases/Saar cp ../tests_cases/Saar/Saar.pamhyr dist/pamhyr/tests_cases/Saar/ +mkdir -p dist/pamhyr/doc/ +cp ../doc/dev/documentation.pdf dist/pamhyr/doc/Pamhyr2-dev.pdf +cp ../doc/dev/documentation.html dist/pamhyr/doc/Pamhyr2-dev.html + +cp ../doc/users/documentation.pdf dist/pamhyr/doc/Pamhyr2-users.pdf +cp ../doc/users/documentation.html dist/pamhyr/doc/Pamhyr2-users.html + +mkdir -p dist/pamhyr/doc/images +cp ../doc/users/images/* dist/pamhyr/doc/images/ +cp ../doc/dev/images/* dist/pamhyr/doc/images/ +cp ../doc/images/* dist/pamhyr/doc/images/ + echo " *** MAKE SRC PACKAGE" OLD_PWD=$PWD @@ -76,11 +91,11 @@ mv ../pamhyr-src.tar.gz ./ echo " *** MAKE BIN PACKAGE" -OLD_PWD=$PWD -cd dist/ -tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100 -cd $OLD_PWD +# OLD_PWD=$PWD +# cd dist/ +# tar --xz -cf pamhyr-gnulinux-amd64.tar.xz pamhyr --checkpoint=.100 +# cd $OLD_PWD -mv dist/pamhyr-gnulinux-amd64.tar.xz ./ +# mv dist/pamhyr-gnulinux-amd64.tar.xz ./ echo " *** DONE" diff --git a/requirements.txt b/requirements.txt index 729dbbe4..01d9a29a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ PyQt5==5.15.4 PyQt5-Qt5==5.15.2 -PyQt5-sip==12.8.1 +PyQt5-sip==12.12.2 +PyQtWebEngine==5.15.6 pyqtgraph>=0.12.1 matplotlib>=3.4.1 numpy>=1.24.2 diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py index d8137932..606b2e29 100644 --- a/src/Model/Geometry/Profile.py +++ b/src/Model/Geometry/Profile.py @@ -56,6 +56,9 @@ class Profile(object): self._profile_type = _type + def __len__(self): + return len(self._points) + @property def number_points(self): return len(self._points) diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index 0c641478..36fca25f 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -42,7 +42,7 @@ class Reach(SQLSubModel): ] def __init__(self, status=None, parent=None): - self.id = parent.id + self.id = parent.id if parent is not None else 0 self._status = status self._parent = parent self._profiles: List[Profile] = [] @@ -106,6 +106,9 @@ class Reach(SQLSubModel): return self._parent.name + def __len__(self): + return len(self._profiles) + @property def profiles(self): return self._profiles.copy() diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index 67698f4e..3258b0e1 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -43,6 +43,10 @@ class Results(object): def river(self): return self._river + @property + def study(self): + return self._study + def set(self, key, value): self._meta_data[key] = value diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index 9f72f731..80cd8450 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -26,6 +26,9 @@ class Profile(object): self._profile = profile # Source profile in the study self._data = {} # Dict of dict {: {: , ...}, ...} + def __len__(self): + return len(self._data) + @property def name(self): return self._profile.name @@ -66,6 +69,9 @@ class Reach(object): ) ) + def __len__(self): + return len(self._profiles) + @property def name(self): return self._reach.name @@ -91,6 +97,9 @@ class River(object): # Dict with timestamps as key self._reachs = [] + def __len__(self): + return len(self._reachs) + @property def reachs(self): return self._reachs.copy() diff --git a/src/Scripts/Hello.py b/src/Scripts/Hello.py index b1c27fe5..c7333b84 100644 --- a/src/Scripts/Hello.py +++ b/src/Scripts/Hello.py @@ -1,4 +1,4 @@ -# help.py -- Pamhyr +# Hello.py -- Pamhyr # Copyright (C) 2023 INRAE # # This program is free software: you can redistribute it and/or modify diff --git a/src/Scripts/P3DST.py b/src/Scripts/P3DST.py new file mode 100644 index 00000000..55a99928 --- /dev/null +++ b/src/Scripts/P3DST.py @@ -0,0 +1,115 @@ +# P3DST.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 . + +# -*- coding: utf-8 -*- + +import sys +import logging + +from matplotlib import pyplot as plt +from numpy import mean + +from Scripts.AScript import AScript + +from Model.Saved import SavedStatus +from Model.Geometry.Reach import Reach + +logger = logging.getLogger() + +class Script3DST(AScript): + name = "3DST" + description = "Display a 3D plot of a river reach from ST file" + + def usage(self): + logger.info(f"Usage : {self._args[0]} 3DST ") + + def set_axes_equal(self, ax): + """Make axes of 3D plot have equal scale + + Make axes of 3D plot have equal scale so that spheres appear + as spheres, cubes as cubes, etc.. This is one possible + solution to Matplotlib's ax.set_aspect('equal') and + ax.axis('equal') not working for 3D. + + Args: + ax: a matplotlib axis, e.g., as output from plt.gca() + + Returns: + The input axis + """ + x_limits = ax.get_xlim3d() + y_limits = ax.get_ylim3d() + z_limits = ax.get_zlim3d() + + x_range = abs(x_limits[1] - x_limits[0]) + x_middle = mean(x_limits) + y_range = abs(y_limits[1] - y_limits[0]) + y_middle = mean(y_limits) + z_range = abs(z_limits[1] - z_limits[0]) + z_middle = mean(z_limits) + + # The plot bounding box is a sphere in the sense of the infinity + # norm, hence I call half the max range the plot radius. + plot_radius = 0.5*max([x_range, y_range, z_range]) + + ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) + ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) + ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) + + return ax + + def run(self): + try: + st_file = self._args[2] + logger.info(f"Use ST file: {st_file}") + except Exception as e: + logger.error(f"Arguments parcing: {str(e)}") + return 1 + + try: + status = SavedStatus() + + my_reach = Reach(status = status) + my_reach.import_geometry(st_file) + my_reach.compute_guidelines() + + ax = plt.figure().add_subplot(projection="3d") + logger.info(my_reach.get_x()) + for x, y, z in zip( + my_reach.get_x(), + my_reach.get_y(), + my_reach.get_z() + ): + ax.plot(x, y, z, color='r', lw=1.) + + for x, y, z in zip( + my_reach.get_guidelines_x(), + my_reach.get_guidelines_y(), + my_reach.get_guidelines_z() + ): + ax.plot(x, y, z, color='b', lw=1.) + + ax.set_xlabel('X') + ax.set_ylabel('Y') + ax.set_zlabel('Z') + plt.tight_layout() + self.set_axes_equal(ax) + plt.show() + + return 0 + except Exception as e: + logger.error(str(e)) + return 1 diff --git a/src/Scripts/plot_3DST.py b/src/Scripts/plot_3DST.py deleted file mode 100644 index bcadf1f1..00000000 --- a/src/Scripts/plot_3DST.py +++ /dev/null @@ -1,68 +0,0 @@ -# plot_3DST.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 . - -# -*- coding: utf-8 -*- - -# a lancer depuis src -import sys -from matplotlib import pyplot as plt -from Model.Geometry.Reach import Reach -from numpy import mean - -def set_axes_equal(ax): - '''Make axes of 3D plot have equal scale so that spheres appear as spheres, - cubes as cubes, etc.. This is one possible solution to Matplotlib's - ax.set_aspect('equal') and ax.axis('equal') not working for 3D. - - Input - ax: a matplotlib axis, e.g., as output from plt.gca(). - ''' - - x_limits = ax.get_xlim3d() - y_limits = ax.get_ylim3d() - z_limits = ax.get_zlim3d() - - x_range = abs(x_limits[1] - x_limits[0]) - x_middle = mean(x_limits) - y_range = abs(y_limits[1] - y_limits[0]) - y_middle = mean(y_limits) - z_range = abs(z_limits[1] - z_limits[0]) - z_middle = mean(z_limits) - - # The plot bounding box is a sphere in the sense of the infinity - # norm, hence I call half the max range the plot radius. - plot_radius = 0.5*max([x_range, y_range, z_range]) - - ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) - ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) - ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) - -st_file = sys.argv[1] -my_reach = Reach(None) -my_reach.import_geometry(st_file) -my_reach.compute_guidelines() - -ax = plt.figure().add_subplot(projection="3d") -for x, y, z in zip(my_reach.get_x(), my_reach.get_y(), my_reach.get_z()): - ax.plot(x, y, z, color='r', lw=1.) -for x, y, z in zip(my_reach.get_guidelines_x(), my_reach.get_guidelines_y(), my_reach.get_guidelines_z()): - ax.plot(x, y, z, color='b', lw=1.) -ax.set_xlabel('X') -ax.set_ylabel('Y') -ax.set_zlabel('Z') -plt.tight_layout() -set_axes_equal(ax) -plt.show() diff --git a/src/Solver/Solvers.py b/src/Solver/Solvers.py index 221f1ff4..514e06c6 100644 --- a/src/Solver/Solvers.py +++ b/src/Solver/Solvers.py @@ -24,9 +24,9 @@ from Solver.Mage import Mage7, Mage8 _translate = QCoreApplication.translate solver_long_name = { - "generic": _translate("Solvers", "Generic"), - # "mage7": _translate("Solvers", "Mage version 7"), - "mage8": _translate("Solvers", "Mage version 8"), + "generic": "Generic", + # "mage7": "Mage v7", + "mage8": "Mage v8", } solver_type_list = { diff --git a/src/View/About/Window.py b/src/View/About/Window.py index 7c269aaf..218869b5 100644 --- a/src/View/About/Window.py +++ b/src/View/About/Window.py @@ -19,14 +19,17 @@ import os import logging -from View.ASubWindow import ASubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtCore import QCoreApplication _translate = QCoreApplication.translate logger = logging.getLogger() -class AboutWindow(ASubWindow): +class AboutWindow(PamhyrDialog): + _pamhyr_ui = "about" + _pamhyr_name = "About" + def _path_file(self, filename): return os.path.abspath( os.path.join( @@ -35,10 +38,14 @@ class AboutWindow(ASubWindow): ) ) - - def __init__(self, title="About", parent=None): - super(AboutWindow, self).__init__(name=title, ui="about", parent=parent) - self.ui.setWindowTitle(title) + def __init__(self, study = None, config = None, parent=None): + super(AboutWindow, self).__init__( + title = _translate("About", "About"), + study = study, + config = config, + options = [], + parent = parent + ) # Version with open(self._path_file("VERSION"), "r") as f: diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py index b27380a5..e5e80c1e 100644 --- a/src/View/BoundaryCondition/Edit/Plot.py +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -21,27 +21,31 @@ import logging from datetime import datetime from tools import timer, trace -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication ) -from View.BoundaryCondition.Edit.translate import * +from View.BoundaryCondition.Edit.translate import BCETranslate _translate = QCoreApplication.translate logger = logging.getLogger() -class Plot(APlot): - def __init__(self, canvas=None, data=None, - mode = "time", toolbar=None): +class Plot(PamhyrPlot): + def __init__(self, mode = "time", data=None, + trad=None, canvas=None, toolbar=None, + parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) + self._table_headers = self._trad.get_dict("table_headers") self._mode = mode def custom_ticks(self): @@ -109,10 +113,10 @@ class Plot(APlot): # Plot label header = self.data.header self.canvas.axes.set_xlabel( - table_headers[header[0]], color='black', fontsize=10 + self._table_headers[header[0]], color='black', fontsize=10 ) self.canvas.axes.set_ylabel( - table_headers[header[1]], color='black', fontsize=10 + self._table_headers[header[1]], color='black', fontsize=10 ) self.canvas.axes.autoscale_view(True, True, True) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index 35360400..7f86fc8a 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -23,8 +23,6 @@ from datetime import date, time, datetime, timedelta from tools import trace, timer -from View.ASubWindow import ASubMainWindow, AWidget -from View.ListedSubWindow import ListedSubWindow from View.Tools.PamhyrTable import PamhyrTableModel from PyQt5.QtCore import ( @@ -48,7 +46,6 @@ from View.BoundaryCondition.Edit.UndoCommand import ( SortCommand, MoveCommand, PasteCommand, DuplicateCommand, ) -from View.BoundaryCondition.Edit.translate import * _translate = QCoreApplication.translate diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 6e0d6131..5661d6bc 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -20,8 +20,8 @@ import logging from tools import timer, trace -from View.ASubWindow import ASubMainWindow, AWidget -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow +from View.Tools.PamhyrWidget import PamhyrWidget from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate from PyQt5.QtGui import ( @@ -41,11 +41,10 @@ from PyQt5.QtWidgets import ( QHeaderView, QDoubleSpinBox, QVBoxLayout, ) -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar -from View.BoundaryCondition.translate import long_types -from View.BoundaryCondition.Edit.translate import table_headers, retranslate +from View.BoundaryCondition.Edit.translate import BCETranslate from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand from View.BoundaryCondition.Edit.Table import TableModel from View.BoundaryCondition.Edit.Plot import Plot @@ -54,16 +53,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class WD50Sigma(AWidget): +class WD50Sigma(PamhyrWidget): + _pamhyr_ui = "d50sigma" + d50Changed = pyqtSignal(float) sigmaChanged = pyqtSignal(float) def __init__(self, parent=None): super(WD50Sigma, self).__init__( - ui="d50sigma", parent=parent ) - self.parent = parent self.spinBox_d50 = self.find(QDoubleSpinBox, "doubleSpinBox_d50") self.spinBox_sigma = self.find(QDoubleSpinBox, "doubleSpinBox_sigma") @@ -95,46 +94,39 @@ class WD50Sigma(AWidget): def valueChangedSigma(self, value): self.sigmaChanged.emit(value) -class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Edit boundary condition", - data=None, study=None, parent=None): - self._data = data - self._study = study - self._title = title +class EditBoundaryConditionWindow(PamhyrWindow): + _pamhyr_ui = "EditBoundaryConditions" + _pamhyr_name = "Edit Boundary Conditions" - self.compute_title() + def __init__(self, data=None, study=None, config=None, parent=None): + self._data = data + trad = BCETranslate() + self._long_types = trad.get_dict("long_types") + + name = self._pamhyr_name + if self._data is not None: + node_name = (self._data.node.name if self._data.node is not None + else _translate("BoundaryCondition", "Not associate")) + name = ( + _translate("Edit boundary condition", self._pamhyr_name) + + f" - {study.name} " + + f" - {self._data.name} ({self._data.id}) " + + f"({self._long_types[self._data.bctype]} - {node_name})" + ) super(EditBoundaryConditionWindow, self).__init__( - name=self._title, ui="EditBoundaryConditions", parent=parent + title = name, + study = study, + config = config, + trad = trad, + parent = parent ) - self.ui.setWindowTitle(self._title) - - self.setup_sc() self.setup_table() self.setup_plot() self.setup_data() self.setup_connections() - def compute_title(self): - if self._data is not None: - node_name = (self._data.node.name if self._data.node is not None - else _translate("BoundaryCondition", "Not associate")) - self._title = ( - _translate("Edit boundary condition", self._title) + - f" - {self._study.name} " + - f" - {self._data.name} ({self._data.id}) " + - f"({long_types[self._data.bctype]} - {node_name})" - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_data(self): self._is_solid = self._data.bctype == "SL" @@ -147,8 +139,8 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self._d50sigma.set_sigma(self._data.sigma) def setup_table(self): - retranslate() headers = {} + table_headers = self._trad.get_dict("table_headers") for h in self._data.header: headers[h] = table_headers[h] @@ -189,21 +181,16 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): canvas = self.canvas, data = self._data, mode = self._study.time_system, + trad = self._trad, toolbar = self.toolbar, ) self.plot.draw() - def setup_connections(self): self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - self._table.dataChanged.connect(self.update) if self._is_solid: @@ -252,7 +239,6 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): ) ) - def add(self): rows = self.index_selected_rows() if len(self._data) == 0 or len(rows) == 0: @@ -285,7 +271,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.plot.update() - def copy(self): + def _copy(self): rows = self.index_selected_rows() table = [] @@ -297,7 +283,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.copyTableIntoClipboard(table) - def paste(self): + def _paste(self): header, data = self.parseClipboardTable() if len(data) == 0: @@ -311,12 +297,12 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self._table.paste(row, header, data) self.plot.update() - def undo(self): + def _undo(self): self._table.undo() self.plot.update() self.widget_update() - def redo(self): + def _redo(self): self._table.redo() self.plot.update() self.widget_update() diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index 28b6ec98..d97ac6de 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -18,23 +18,22 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + +from View.BoundaryCondition.translate import BCTranslate + _translate = QCoreApplication.translate -table_headers = { - "x": _translate("BoundaryCondition", "X"), - "y": _translate("BoundaryCondition", "Y"), - "time": _translate("BoundaryCondition", "Time"), - "date": _translate("BoundaryCondition", "Date"), - "discharge": _translate("BoundaryCondition", "Discharge (m³/s)"), - "z": _translate("BoundaryCondition", "Z (m)"), - "solid": _translate("BoundaryCondition", "Solid (kg/s)"), -} +class BCETranslate(BCTranslate): + def __init__(self): + super(BCETranslate, self).__init__() -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)") + self._sub_dict["table_headers"] = { + "x": _translate("BoundaryCondition", "X"), + "y": _translate("BoundaryCondition", "Y"), + "time": _translate("BoundaryCondition", "Time"), + "date": _translate("BoundaryCondition", "Date"), + "discharge": _translate("BoundaryCondition", "Discharge (m³/s)"), + "z": _translate("BoundaryCondition", "Z (m)"), + "solid": _translate("BoundaryCondition", "Solid (kg/s)"), + } diff --git a/src/View/BoundaryCondition/Table.py b/src/View/BoundaryCondition/Table.py index 377d9aab..77141dff 100644 --- a/src/View/BoundaryCondition/Table.py +++ b/src/View/BoundaryCondition/Table.py @@ -46,19 +46,24 @@ from View.BoundaryCondition.UndoCommand import ( AddCommand, DelCommand, SortCommand, MoveCommand, PasteCommand, DuplicateCommand, ) -from View.BoundaryCondition.translate import * +from View.BoundaryCondition.translate import BC_types logger = logging.getLogger() _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, mode="type", tab="", parent=None): + def __init__(self, data=None, mode="type", tab="", trad=None, parent=None): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._mode = mode self._tab = tab + self._trad = trad + + self._long_types = {} + if self._trad is not None: + self._long_types = self._trad.get_dict("long_types") def createEditor(self, parent, option, index): self.editor = QComboBox(parent) @@ -66,7 +71,7 @@ class ComboBoxDelegate(QItemDelegate): if self._mode == "type": lst = list( map( - lambda k: long_types[k], + lambda k: self._long_types[k], filter( lambda k: self._tab in BC_types[k].compatibility(), BC_types.keys() @@ -107,6 +112,13 @@ class ComboBoxDelegate(QItemDelegate): class TableModel(PamhyrTableModel): + def __init__(self, **kwargs): + super(TableModel, self).__init__(**kwargs) + + self._long_types = {} + if self._trad is not None: + self._long_types = self._trad.get_dict("long_types") + def _setup_lst(self): self._lst = self._data.boundary_condition self._tab = self._opt_data @@ -125,7 +137,7 @@ class TableModel(PamhyrTableModel): return self._lst.get(self._tab, row).name elif self._headers[column] == "type": t = self._lst.get(self._tab, row).bctype - return long_types[t] + return self._long_types[t] elif self._headers[column] == "node": n = self._lst.get(self._tab, row).node if n is None: @@ -149,7 +161,7 @@ class TableModel(PamhyrTableModel): ) ) 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 self._long_types.items() if v == value) self._undo.push( SetTypeCommand( self._lst, self._tab,row, BC_types[key] diff --git a/src/View/BoundaryCondition/Window.py b/src/View/BoundaryCondition/Window.py index 7f8c13e2..99758c5a 100644 --- a/src/View/BoundaryCondition/Window.py +++ b/src/View/BoundaryCondition/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -56,51 +55,49 @@ from View.BoundaryCondition.Table import ( ) from View.Network.GraphWidget import GraphWidget -from View.BoundaryCondition.translate import table_headers, retranslate +from View.BoundaryCondition.translate import BCTranslate from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow _translate = QCoreApplication.translate logger = logging.getLogger() -class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Boundary conditions", study=None, parent=None): - self._title = title + " - " + study.name +class BoundaryConditionWindow(PamhyrWindow): + _pamhyr_ui = "BoundaryConditions" + _pamhyr_name = "Boundary conditions" + + def __init__(self, study=None, config=None, parent=None): + name = self._pamhyr_name + " - " + study.name super(BoundaryConditionWindow, self).__init__( - name=title, ui="BoundaryConditions", parent=parent + title = name, + study = study, + config = config, + trad = BCTranslate(), + parent=parent ) - self._study = study self._bcs = self._study.river.boundary_condition - self.setup_sc() self.setup_table() self.setup_graph() self.setup_connections() self.ui.setWindowTitle(self._title) - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() self._table = {} for t in ["liquid", "solid", "suspenssion"]: self._delegate_type = ComboBoxDelegate( + trad = self._trad, data = self._study.river, mode = "type", tab = t, parent=self ) self._delegate_node = ComboBoxDelegate( + trad = self._trad, data = self._study.river, mode = "node", tab = t, @@ -110,12 +107,13 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): table = self.find(QTableView, f"tableView_{t}") self._table[t] = TableModel( table_view = table, - table_headers = table_headers, + table_headers = self._trad.get_dict("table_headers"), editable_headers = ["name", "type", "node"], delegates = { "type": self._delegate_type, "node": self._delegate_node, }, + trad = self._trad, data = self._study.river, undo = self._undo_stack, opt_data = t, @@ -141,11 +139,6 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) self.find(QAction, "action_sort").triggered.connect(self.sort) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - def current_tab(self): return self.find(QTabWidget, "tabWidget")\ .currentWidget()\ @@ -202,17 +195,17 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): row = self.index_selected_row() self._table[tab].move_down(row) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): tab = self.current_tab() self._table[tab].undo() - def redo(self): + def _redo(self): tab = self.current_tab() self._table[tab].redo() diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py index 78aaec07..00077f5f 100644 --- a/src/View/BoundaryCondition/translate.py +++ b/src/View/BoundaryCondition/translate.py @@ -18,6 +18,8 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, TimeOverZ, TimeOverDischarge, ZOverDischarge, @@ -26,21 +28,6 @@ from Model.BoundaryCondition.BoundaryConditionTypes import ( _translate = QCoreApplication.translate -long_types = { - "ND": _translate("BoundaryCondition", "Not defined"), - "PC": _translate("BoundaryCondition", "Ponctual contribution"), - "TZ": _translate("BoundaryCondition", "TimeZ"), - "TD": _translate("BoundaryCondition", "TimeDischarge"), - "ZD": _translate("BoundaryCondition", "ZDischarge"), - "SL": _translate("BoundaryCondition", "Solid"), -} - -table_headers = { - "name": _translate("BoundaryCondition", "Name"), - "type": _translate("BoundaryCondition", "Type"), - "node": _translate("BoundaryCondition", "Node") -} - BC_types = { "ND": NotDefined, "PC": PonctualContribution, @@ -50,7 +37,21 @@ BC_types = { "SL": Solid, } -def retranslate(): - table_headers["name"] = _translate("BoundaryCondition", "Name") - table_headers["type"] = _translate("BoundaryCondition", "Type") - table_headers["node"] = _translate("BoundaryCondition", "Node") +class BCTranslate(PamhyrTranslate): + def __init__(self): + super(BCTranslate, self).__init__() + + self._sub_dict["long_types"] = { + "ND": _translate("BoundaryCondition", "Not defined"), + "PC": _translate("BoundaryCondition", "Ponctual contribution"), + "TZ": _translate("BoundaryCondition", "TimeZ"), + "TD": _translate("BoundaryCondition", "TimeDischarge"), + "ZD": _translate("BoundaryCondition", "ZDischarge"), + "SL": _translate("BoundaryCondition", "Solid"), + } + + self._sub_dict["table_headers"] = { + "name": _translate("BoundaryCondition", "Name"), + "type": _translate("BoundaryCondition", "Type"), + "node": _translate("BoundaryCondition", "Node") + } diff --git a/src/View/CheckList/Table.py b/src/View/CheckList/Table.py index 5810c4be..b355eb6c 100644 --- a/src/View/CheckList/Table.py +++ b/src/View/CheckList/Table.py @@ -35,25 +35,12 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + _translate = QCoreApplication.translate -class TableModel(QAbstractTableModel): - def __init__(self, data=None): - super(QAbstractTableModel, self).__init__() - self._headers = ["name", "status"] - self._data = data - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - 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): row = index.row() column = index.column() @@ -81,12 +68,3 @@ class TableModel(QAbstractTableModel): return self._data[row].summary return QVariant() - - def headerData(self, section, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return self._headers[section] - - return QVariant() - - def update(self): - self.layoutChanged.emit() diff --git a/src/View/CheckList/Translate.py b/src/View/CheckList/Translate.py new file mode 100644 index 00000000..b3ce856d --- /dev/null +++ b/src/View/CheckList/Translate.py @@ -0,0 +1,32 @@ +# 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 . + +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from View.Tools.PamhyrTranslate import PamhyrTranslate + +_translate = QCoreApplication.translate + +class CheckListTranslate(PamhyrTranslate): + def __init__(self): + super(CheckListTranslate, self).__init__() + + self._sub_dict["table_headers"] = { + "name": _translate("CheckList", "Name"), + "status": _translate("CheckList", "Status"), + } diff --git a/src/View/CheckList/Window.py b/src/View/CheckList/Window.py index 4c40ad1f..baf99983 100644 --- a/src/View/CheckList/Window.py +++ b/src/View/CheckList/Window.py @@ -18,8 +18,7 @@ from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -41,33 +40,36 @@ from PyQt5.QtWidgets import ( from View.CheckList.Table import TableModel from View.CheckList.Worker import Worker +from View.CheckList.Translate import CheckListTranslate _translate = QCoreApplication.translate -class CheckListWindow(ASubMainWindow, ListedSubWindow): +class CheckListWindow(PamhyrWindow): + _pamhyr_ui = "CheckList" + _pamhyr_name = "Check list" signalStatus = pyqtSignal(str) - def __init__(self, title="Check list", - autorun:bool=True, + def __init__(self, autorun:bool=True, study=None, config=None, solver=None, parent=None): - self._title = title + " - " + study.name - self._autorun = autorun - - self._study = study - self._config = config self._solver = solver - self._parent = parent + + name = self._pamhyr_name + " - " + study.name super(CheckListWindow, self).__init__( - name=self._title, ui="CheckList", parent=parent + title = name, + study = study, + config = config, + trad = CheckListTranslate(), + options = [], + parent = parent ) - self.ui.setWindowTitle(self._title) self._checker_list = ( - self._study.checkers() + self._solver.checkers() + self._study.checkers() + + self._solver.checkers() ) self.setup_table() @@ -78,16 +80,12 @@ class CheckListWindow(ASubMainWindow, ListedSubWindow): def setup_table(self): table = self.find(QTableView, f"tableView") - self._table = TableModel( + table_view = table, + table_headers = self._trad.get_dict("table_headers"), data = self._checker_list, ) - table.setModel(self._table) - table.setSelectionBehavior(QAbstractItemView.SelectRows) - table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - table.setAlternatingRowColors(True) - def setup_progress_bar(self): self._progress = self.find(QProgressBar, f"progressBar") self._p = 0 # Progress current step diff --git a/src/View/Configure/Solver/Window.py b/src/View/Configure/Solver/Window.py index c295481f..369ff40a 100644 --- a/src/View/Configure/Solver/Window.py +++ b/src/View/Configure/Solver/Window.py @@ -16,7 +16,7 @@ # -*- coding: utf-8 -*- -from View.ASubWindow import ASubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from Solver.Solvers import solver_type_list from Solver.GenericSolver import GenericSolver @@ -24,12 +24,22 @@ from PyQt5.QtWidgets import ( QPushButton, ) -class ConfigureSolverWindow(ASubWindow): - def __init__(self, data=None, title="Configuration : Add Solver", parent=None): +class ConfigureSolverWindow(PamhyrDialog): + _pamhyr_ui = "ConfigureAddSolverDialog" + _pamhyr_name = "Add/Edit Solver" + + def __init__(self, data=None, config=None, parent=None): + if data is not None: + name = f"Edit Solver - {data.name}" + else: + name = "Add a new Solver" + super(ConfigureSolverWindow, self).__init__( - name=title, ui="ConfigureAddSolverDialog", parent=parent + title = name, + config = config, + options = [], + parent=parent ) - self.ui.setWindowTitle(title) # Combo box item for solver in solver_type_list: @@ -37,10 +47,10 @@ class ConfigureSolverWindow(ASubWindow): # Data to return self.data = data - if not self.data is None: + if self.data is not None: self.copy_data() - self.connect() + self.setup_connection() def copy_data(self): self.set_combobox_text("comboBox_solver", self.data.type) @@ -53,7 +63,7 @@ class ConfigureSolverWindow(ASubWindow): self.set_line_edit_text("lineEdit_output", self.data._path_output) self.set_line_edit_text("lineEdit_output_cmd", self.data._cmd_output) - def connect(self): + def setup_connection(self): # File button buttons = { "pushButton_input": (lambda: self.file_dialog( diff --git a/src/View/Configure/Window.py b/src/View/Configure/Window.py index 23a371ac..83db81f6 100644 --- a/src/View/Configure/Window.py +++ b/src/View/Configure/Window.py @@ -21,8 +21,7 @@ import logging from copy import deepcopy from config import Config -from View.ASubWindow import ASubWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from View.Stricklers.Table import TableModel from View.Stricklers.translate import * @@ -95,50 +94,42 @@ class SolverTableModel(QAbstractTableModel): self.layoutChanged.emit() -class ConfigureWindow(ASubWindow, ListedSubWindow): - def __init__(self, conf=None, title="Configure", parent=None): +class ConfigureWindow(PamhyrDialog): + _pamhyr_ui = "ConfigureDialog" + _pamhyr_name = "Configure" + + def __init__(self, config=None, parent=None): + if config is None: + config = Config() + super(ConfigureWindow, self).__init__( - name=title, ui="ConfigureDialog", parent=parent + title = self._pamhyr_name, + config = config, + options = [], + parent=parent ) - self.ui.setWindowTitle(title) - if conf is None: - self.conf = Config() - else: - self.conf = conf + self.setup_custom_sc() + self.setup_solver() + self.setup_stricklers() + self.setup_data() + self.setup_connection() - self.setup_sc() - - # Solver + def setup_solver(self): table = self.find(QTableView, "tableView_solver") self.solver_table_model = SolverTableModel( headers = ["name", "type", "description"], - rows = conf.solvers + rows = self._config.solvers ) table.setModel(self.solver_table_model) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) + table.resizeColumnsToContents() - # Meshing_Tool - self.set_line_edit_text("lineEdit_meshing_tool", self.conf.meshing_tool) - - # Const - self.set_line_edit_text("lineEdit_segment", str(self.conf.segment)) - self.set_line_edit_text("lineEdit_max_listing", str(self.conf.max_listing)) - - # Backup - self.set_check_box("checkBox_backup", self.conf.backup_enable) - self.set_line_edit_text("lineEdit_backup_path", self.conf.backup_path) - self.set_time_edit("timeEdit_backup_frequence", self.conf.backup_frequence) - self.set_spin_box("spinBox_backup_max", self.conf.backup_max) - - self.find(QTableView, "tableView_solver").resizeColumnsToContents() - self.connect() - - # Stricklers + def setup_stricklers(self): table = self.find(QTableView, f"tableView_stricklers") - self._stricklers = deepcopy(self.conf.stricklers) + self._stricklers = deepcopy(self._config.stricklers) self._stricklers_table = TableModel( data = self._stricklers, undo = self._undo_stack, @@ -148,26 +139,35 @@ class ConfigureWindow(ASubWindow, ListedSubWindow): table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) + def setup_data(self): + # Meshing_Tool + self.set_line_edit_text("lineEdit_meshing_tool", self._config.meshing_tool) + + # Const + self.set_line_edit_text("lineEdit_segment", str(self._config.segment)) + self.set_line_edit_text("lineEdit_max_listing", str(self._config.max_listing)) + + # Backup + self.set_check_box("checkBox_backup", self._config.backup_enable) + self.set_line_edit_text("lineEdit_backup_path", self._config.backup_path) + self.set_time_edit("timeEdit_backup_frequence", self._config.backup_frequence) + self.set_spin_box("spinBox_backup_max", self._config.backup_max) + # Editor - self.set_line_edit_text("lineEdit_editor_cmd", str(self.conf.editor)) + self.set_line_edit_text("lineEdit_editor_cmd", str(self._config.editor)) # Language languages = Config.languages() for lang in languages: self.combobox_add_item("comboBox_language", lang) - if self.conf.lang == languages[lang]: + if self._config.lang == languages[lang]: self.set_combobox_text("comboBox_language", lang) - def setup_sc(self): - self._undo_stack = QUndoStack() + def setup_custom_sc(self): + self._debug_sc = QShortcut(QKeySequence("Ctrl+G"), self) + self._debug_sc.activated.connect(self.set_debug) - self.debug_sc = QShortcut(QKeySequence("Ctrl+G"), self) - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - - def connect(self): + def setup_connection(self): buttons = { # Solvers "pushButton_solver_add": self.add_solver, @@ -195,33 +195,31 @@ class ConfigureWindow(ASubWindow, ListedSubWindow): for button in buttons: self.find(QPushButton, button).clicked.connect(buttons[button]) - self.debug_sc.activated.connect(self.set_debug) - def accept(self): # Solvers - self.conf.solvers = self.solver_table_model.rows.copy() + self._config.solvers = self.solver_table_model.rows.copy() # Meshing_Tool - self.conf.meshing_tool = self.get_line_edit_text("lineEdit_meshing_tool") + self._config.meshing_tool = self.get_line_edit_text("lineEdit_meshing_tool") # Const - self.conf.segment = self.get_line_edit_text("lineEdit_segment") - self.conf.max_listing = self.get_line_edit_text("lineEdit_max_listing") + self._config.segment = self.get_line_edit_text("lineEdit_segment") + self._config.max_listing = self.get_line_edit_text("lineEdit_max_listing") # Backup - self.conf.backup_enable = self.get_check_box("checkBox_backup") - self.conf.backup_path = self.get_line_edit_text("lineEdit_backup_path") - self.conf.backup_frequence = self.get_time_edit("timeEdit_backup_frequence") - self.conf.backup_max = self.get_spin_box("spinBox_backup_max") + self._config.backup_enable = self.get_check_box("checkBox_backup") + self._config.backup_path = self.get_line_edit_text("lineEdit_backup_path") + self._config.backup_frequence = self.get_time_edit("timeEdit_backup_frequence") + self._config.backup_max = self.get_spin_box("spinBox_backup_max") # Stricklers - self.conf.stricklers = deepcopy(self._stricklers) + self._config.stricklers = deepcopy(self._stricklers) # Editor - self.conf.editor = self.get_line_edit_text("lineEdit_editor_cmd") + self._config.editor = self.get_line_edit_text("lineEdit_editor_cmd") # Language - self.conf.lang = Config.languages()[self.get_combobox_text("comboBox_language")] + self._config.lang = Config.languages()[self.get_combobox_text("comboBox_language")] self.end() @@ -230,13 +228,13 @@ class ConfigureWindow(ASubWindow, ListedSubWindow): self.end() def end(self): - self.conf.save() + self._config.save() self.close() # Debug def set_debug(self): - self.conf.debug = not self.conf.debug - logger.info(f"Debug mode set : {self.conf.debug}") + self._config.debug = not self._config.debug + logger.info(f"Debug mode set : {self._config.debug}") self.parent.setup_debug_mode() # Solvers @@ -246,6 +244,7 @@ class ConfigureWindow(ASubWindow, ListedSubWindow): for index in indexes: self.edit_solver = ConfigureSolverWindow( data=self.solver_table_model.rows[index.row()], + config=self._config, parent=self ) if self.edit_solver.exec_(): diff --git a/src/View/Debug/Window.py b/src/View/Debug/Window.py index 9b14aa20..2e99dadb 100644 --- a/src/View/Debug/Window.py +++ b/src/View/Debug/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -45,20 +44,19 @@ logger = logging.getLogger() _translate = QCoreApplication.translate -class ReplWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Debug REPL", - study=None, config=None, +class ReplWindow(PamhyrWindow): + _pamhyr_ui = "DebugRepl" + _pamhyr_name = "Debug REPL" + + def __init__(self, study=None, config=None, solver=None, parent=None): - self._title = title - - self._study = study - self._config = config - self._parent = parent - super(ReplWindow, self).__init__( - name=self._title, ui="DebugRepl", parent=parent + title = self._pamhyr_name, + study = study, + config = config, + options = [], + parent = parent ) - self.ui.setWindowTitle(self._title) self.__debug_exec_result__ = None self._history = [] diff --git a/src/View/Doc/Window.py b/src/View/Doc/Window.py new file mode 100644 index 00000000..3c39dca0 --- /dev/null +++ b/src/View/Doc/Window.py @@ -0,0 +1,97 @@ +# Window.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# -*- coding: utf-8 -*- + +import os +import logging +import subprocess + +from View.Tools.PamhyrWindow import PamhyrWindow + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate +logger = logging.getLogger() + + +from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QAction +from PyQt5.QtCore import QUrl +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings + +class DocWindow(PamhyrWindow): + _pamhyr_ui = "WebView" + _pamhyr_name = "Doc" + + def _path_file(self, filename): + return os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", "..", "..", "doc", filename + ) + ) + + def __init__(self, filename=None, + study=None, config=None, + parent=None): + super(DocWindow, self).__init__( + title = self._pamhyr_name, + study = study, + config = config, + options = [], + parent = parent + + ) + + self._path = self._path_file(filename) + + self.setup_web_engine() + self.setup_url(self._path) + self.setup_connection() + + def setup_connection(self): + self.findChild(QAction, "action_back").triggered.connect(self.back) + self.findChild(QAction, "action_forward").triggered.connect(self.forward) + + self.findChild(QAction, "action_firefox").triggered.connect(self.open_in_firefox) + + + def setup_web_engine(self): + vl = self.find(QVBoxLayout, "verticalLayout") + self._web_view = QWebEngineView() + + settings = self._web_view.settings() + settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) + settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True) + settings.setAttribute(QWebEngineSettings.PdfViewerEnabled, True) + + vl.addWidget(self._web_view) + + def setup_url(self, filename): + logger.info(f"Open documentation : {filename}") + self._web_view.setUrl(QUrl(f"file://{self._path_file(filename)}")) + + def back(self): + self._web_view.back() + + def forward(self): + self._web_view.forward() + + def open_in_firefox(self): + _ = subprocess.Popen( + f"firefox {self._path}", + shell = True + ) diff --git a/src/View/DummyWindow.py b/src/View/DummyWindow.py index 05117bca..768d6fbc 100644 --- a/src/View/DummyWindow.py +++ b/src/View/DummyWindow.py @@ -16,9 +16,14 @@ # -*- coding: utf-8 -*- -from View.ASubWindow import ASubWindow +from View.Tools.PamhyrWindow import PamhyrWindow -class DummyWindow(ASubWindow): - def __init__(self, title="Dummy", parent=None): - super(DummyWindow, self).__init__(name=title, ui="dummy", parent=parent) - self.ui.setWindowTitle(title) +class DummyWindow(PamhyrWindow): + __ui = "Dummy" + __name = "Pamhyr Dummy Window" + + def __init__(self, parent=None): + super(DummyWindow, self).__init__( + title = self.__name, + parent = parent, + ) diff --git a/src/View/Frictions/PlotStricklers.py b/src/View/Frictions/PlotStricklers.py index 71ff6e6f..c6b58dcd 100644 --- a/src/View/Frictions/PlotStricklers.py +++ b/src/View/Frictions/PlotStricklers.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from tools import timer, flatten -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -25,14 +25,7 @@ from PyQt5.QtCore import ( _translate = QCoreApplication.translate -class PlotStricklers(APlot): - def __init__(self, canvas=None, data=None, toolbar=None): - super(PlotStricklers, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar - ) - +class PlotStricklers(PamhyrPlot): def draw_frictions(self, frictions, color="r"): lst = frictions lst.sort(key = lambda s: s.begin_kp) diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index ef9dc32f..f4ad4f8e 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -41,6 +41,8 @@ from View.Frictions.UndoCommand import ( MoveCommand, PasteCommand, DuplicateCommand, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from View.Frictions.translate import * logger = logging.getLogger() @@ -93,26 +95,10 @@ class ComboBoxDelegate(QItemDelegate): self.commitData.emit(self.sender()) -class TableModel(QAbstractTableModel): - def __init__(self, data=None, study=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._data = data - self._study = study - self._undo = undo - self._frictions = self._data.frictions - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._frictions) - - def columnCount(self, parent): - return len(self._headers) +class TableModel(PamhyrTableModel): + def _setup_lst(self): + self._lst = self._data.frictions + self._study = self._opt_data def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: @@ -122,30 +108,24 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._frictions.get(row).name + return self._lst.get(row).name elif self._headers[column] == "begin_kp": - return self._frictions.get(row).begin_kp + return self._lst.get(row).begin_kp elif self._headers[column] == "end_kp": - return self._frictions.get(row).end_kp + return self._lst.get(row).end_kp elif self._headers[column] == "begin_strickler": - value = self._frictions.get(row).begin_strickler + value = self._lst.get(row).begin_strickler if value == None: return _translate("Frictions", "Not defined") return str(value) elif self._headers[column] == "end_strickler": - value = self._frictions.get(row).end_strickler + value = self._lst.get(row).end_strickler if value == None: return _translate("Frictions", "Not defined") return str(value) return QVariant() - def headerData(self, friction, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[friction]] - - return QVariant() - def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or role != Qt.EditRole: return False @@ -157,31 +137,31 @@ class TableModel(QAbstractTableModel): if self._headers[column] == "name": self._undo.push( SetNameCommand( - self._frictions, row, value + self._lst, row, value ) ) elif self._headers[column] == "begin_kp": self._undo.push( SetBeginCommand( - self._frictions, row, value + self._lst, row, value ) ) elif self._headers[column] == "end_kp": self._undo.push( SetEndCommand( - self._frictions, row, value + self._lst, row, value ) ) elif self._headers[column] == "begin_strickler": self._undo.push( SetBeginStricklerCommand( - self._frictions, row, self._study.river.strickler(value) + self._lst, row, self._study.river.strickler(value) ) ) elif self._headers[column] == "end_strickler": self._undo.push( SetEndStricklerCommand( - self._frictions, row, self._study.river.strickler(value) + self._lst, row, self._study.river.strickler(value) ) ) except Exception as e: @@ -196,7 +176,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._frictions, row, self._data + self._lst, row, self._data ) ) @@ -208,7 +188,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._frictions, rows + self._lst, rows ) ) @@ -220,7 +200,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SortCommand( - self._frictions, False + self._lst, False ) ) @@ -237,7 +217,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._frictions, "up", row + self._lst, "up", row ) ) @@ -245,7 +225,7 @@ class TableModel(QAbstractTableModel): self.layoutChanged.emit() def move_down(self, index, parent=QModelIndex()): - if row > len(self._frictions): + if row > len(self._lst): return target = row @@ -254,7 +234,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._frictions, "down", row + self._lst, "down", row ) ) diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py index 060be330..74d96b63 100644 --- a/src/View/Frictions/Window.py +++ b/src/View/Frictions/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -48,63 +47,49 @@ from View.Frictions.Table import ( TableModel, ComboBoxDelegate ) -from View.Plot.MplCanvas import MplCanvas +from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Geometry.PlotKPZ import PlotKPZ from View.Frictions.PlotStricklers import PlotStricklers -from View.Frictions.translate import table_headers, retranslate +from View.Frictions.translate import FrictionsTranslate from View.Stricklers.Window import StricklersWindow -_translate = QCoreApplication.translate - logger = logging.getLogger() -class FrictionsWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Frictions", study=None, parent=None): - self._study = study - self._reach = self._study.river.current_reach() +class FrictionsWindow(PamhyrWindow): + _pamhyr_ui = "Frictions" + _pamhyr_name = "Edit frictions" + + def __init__(self, reach=None, study=None, config=None, parent=None): + if reach is not None: + self._reach = reach + else: + self._reach = study.river.current_reach() + self._frictions = self._reach.frictions - self.setup_title(title) - - super(FrictionsWindow, self).__init__( - name=title, ui="Frictions", parent=parent + name = ( + self._pamhyr_name + " - " + + study.name + " - " + + self._reach.name + ) + + super(FrictionsWindow, self).__init__( + title = name, + study = study, + config = config, + trad = FrictionsTranslate(), + parent = parent ) - self.setup_sc() self.setup_table() self.setup_graph() self.setup_connections() - self.ui.setWindowTitle(self._title) - - def setup_title(self, title): - self._title = ( - title + " - " - + self._study.name + " - " - + self._reach.name - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) def setup_table(self): - retranslate() self._table = {} - table = self.find(QTableView, f"tableView") - self._table = TableModel( - data = self._reach, - study = self._study, - undo = self._undo_stack, - ) - table.setModel(self._table) - self._delegate_stricklers = ComboBoxDelegate( data = self._reach, study = self._study, @@ -112,13 +97,23 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow): parent=self ) - table.setItemDelegateForColumn( - 3, self._delegate_stricklers + table = self.find(QTableView, f"tableView") + self._table = TableModel( + table_view = table, + table_headers = self._trad.get_dict("table_headers"), + editable_headers = [ + "name", "begin_kp", "end_kp", + "begin_strickler", "end_strickler" + ], + delegates = { + "begin_strickler": self._delegate_stricklers, + "end_strickler": self._delegate_stricklers, + }, + data = self._reach, + undo = self._undo_stack, + opt_data = self._study ) - table.setItemDelegateForColumn( - 4, self._delegate_stricklers - ) - + table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) @@ -156,11 +151,6 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_sort").triggered.connect(self.sort) self.find(QAction, "action_edit_stricklers").triggered.connect(self.edit_stricklers) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - table = self.find(QTableView, f"tableView") table.selectionModel()\ .selectionChanged\ @@ -234,16 +224,16 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow): row = self.index_selected_row() self._table.move_down(row) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() - def redo(self): + def _redo(self): self._table.redo() def edit_stricklers(self): diff --git a/src/View/Frictions/translate.py b/src/View/Frictions/translate.py index 68bebb96..1014d8c1 100644 --- a/src/View/Frictions/translate.py +++ b/src/View/Frictions/translate.py @@ -18,21 +18,19 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers = { - "name": _translate("Frictions", "Name"), - # "edge": _translate("Frictions", "Reach"), - "begin_kp": _translate("Frictions", "Begin kp (m)"), - "end_kp": _translate("Frictions", "End kp (m)"), - "begin_strickler": _translate("Frictions", "Begin strickler"), - "end_strickler": _translate("Frictions", "End strickler"), -} +class FrictionsTranslate(PamhyrTranslate): + def __init__(self): + super(FrictionsTranslate, self).__init__() -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") + self._sub_dict["table_headers"] = { + "name": _translate("Frictions", "Name"), + # "edge": _translate("Frictions", "Reach"), + "begin_kp": _translate("Frictions", "Begin kp (m)"), + "end_kp": _translate("Frictions", "End kp (m)"), + "begin_strickler": _translate("Frictions", "Begin strickler"), + "end_strickler": _translate("Frictions", "End strickler"), + } diff --git a/src/View/Geometry/PlotAC.py b/src/View/Geometry/PlotAC.py index e43f39e9..974706aa 100644 --- a/src/View/Geometry/PlotAC.py +++ b/src/View/Geometry/PlotAC.py @@ -19,7 +19,7 @@ import logging from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -29,12 +29,15 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotAC(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, plot_xy=None): +class PlotAC(PamhyrPlot): + def __init__(self, canvas=None, trad=None, data=None, toolbar=None, + plot_xy=None, parent=None): super(PlotAC, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self.plot_xy = plot_xy diff --git a/src/View/Geometry/PlotKPZ.py b/src/View/Geometry/PlotKPZ.py index eb7d5e28..f216443c 100644 --- a/src/View/Geometry/PlotKPZ.py +++ b/src/View/Geometry/PlotKPZ.py @@ -19,7 +19,7 @@ import logging from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -29,13 +29,15 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotKPZ(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, - display_current=True): +class PlotKPZ(PamhyrPlot): + def __init__(self, canvas=None, trad=None, data=None, toolbar=None, + display_current=True, parent=None): super(PlotKPZ, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self.display_current = display_current diff --git a/src/View/Geometry/PlotXY.py b/src/View/Geometry/PlotXY.py index 54e056a9..ce93fa24 100644 --- a/src/View/Geometry/PlotXY.py +++ b/src/View/Geometry/PlotXY.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from tools import timer, trace -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -25,12 +25,15 @@ from PyQt5.QtCore import ( _translate = QCoreApplication.translate -class PlotXY(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, display_current=True): +class PlotXY(PamhyrPlot): + def __init__(self, canvas=None, trad=None, data=None, toolbar=None, + display_current=True, parent=None): super(PlotXY, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self.display_current = display_current @@ -60,11 +63,11 @@ class PlotXY(APlot): # Axes self.canvas.axes.set_xlabel( - _translate("MainWindow_reach", "X (m)"), + _translate("Geometry", "X (m)"), color='green', fontsize=12 ) self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Y (m)"), + _translate("Geometry", "Y (m)"), color='green', fontsize=12 ) self.canvas.axes.axis("equal") diff --git a/src/View/Geometry/Profile/Plot.py b/src/View/Geometry/Profile/Plot.py index 076c2703..12bb9aee 100644 --- a/src/View/Geometry/Profile/Plot.py +++ b/src/View/Geometry/Profile/Plot.py @@ -19,8 +19,8 @@ import logging from tools import timer, trace -from View.Plot.APlot import APlot -from View.Plot.mpl_canvas_onpick_event import OnpickEvent +from View.Tools.PamhyrPlot import PamhyrPlot +from View.Tools.Plot.OnPickEvent import OnpickEvent from PyQt5.QtCore import ( QCoreApplication @@ -30,12 +30,15 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class Plot(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, table=None): +class Plot(PamhyrPlot): + def __init__(self, canvas=None, trad=None, data=None, toolbar=None, + table=None, parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self._table = table diff --git a/src/View/Geometry/Profile/Table.py b/src/View/Geometry/Profile/Table.py index 2de665aa..d1a670ac 100644 --- a/src/View/Geometry/Profile/Table.py +++ b/src/View/Geometry/Profile/Table.py @@ -29,10 +29,11 @@ from PyQt5.QtWidgets import ( QMessageBox, QStyledItemDelegate, QLineEdit ) from PyQt5.QtCore import ( - QModelIndex, Qt, QAbstractTableModel, - QVariant, QCoreApplication + QModelIndex, Qt, QVariant, QCoreApplication ) +from View.Tools.PamhyrTable import PamhyrTableModel + from Model.Geometry.PointXYZ import PointXYZ from Model.Geometry.ProfileXYZ import ProfileXYZ @@ -43,43 +44,22 @@ logger = logging.getLogger() _translate = QCoreApplication.translate -class TableEditableModel(QAbstractTableModel): - def __init__(self, profile: ProfileXYZ, header=None, undo=None): - QAbstractTableModel.__init__(self) - - self._undo_stack = undo - self._profile = profile - - if header is None: - self._header = [ - "X (m)", "Y (m)", "Z (m)", - _translate("MainWindowProfile", "Nom"), - _translate("MainWindowProfile", "Abs en travers (m)") - ] - else: - self._header = header - - def rowCount(self, parent=QModelIndex()): - return self._profile.number_points - - def columnCount(self, parent=QModelIndex()): - return len(self._header) - +class GeometryProfileTableModel(PamhyrTableModel): def data(self, index, role=Qt.DisplayRole): if index.isValid(): if role == Qt.DisplayRole: value = "" if index.column() == 0: - value = self._profile.point(index.row()).x + value = self._data.point(index.row()).x elif index.column() == 1: - value = self._profile.point(index.row()).y + value = self._data.point(index.row()).y elif index.column() == 2: - value = self._profile.point(index.row()).z + value = self._data.point(index.row()).z elif index.column() == 3: - value = self._profile.point(index.row()).name + value = self._data.point(index.row()).name elif index.column() == 4: - station = self._profile.get_station() + station = self._data.get_station() if station is None: return "-" else: @@ -95,25 +75,25 @@ class TableEditableModel(QAbstractTableModel): return Qt.AlignHCenter | Qt.AlignVCenter if index.column() == 2: - value = self._profile.point(index.row()).z + value = self._data.point(index.row()).z if role == Qt.ForegroundRole: - if value == self._profile.z_min(): + if value == self._data.z_min(): return QColor("red") - elif value == self._profile.z_max(): + elif value == self._data.z_max(): return QColor("blue") if role == Qt.ToolTipRole: - if value == self._profile.z_min(): + if value == self._data.z_min(): return _translate("MainWindowProfile", "La cote du fond", "Z minimale") - elif value == self._profile.z_max(): + elif value == self._data.z_max(): return _translate("MainWindowProfile", "La cote maximale", "Z maximale") if index.column() == 3: - value = self._profile.point(index.row()).name + value = self._data.point(index.row()).name if value.strip().upper() in ["RG", "RD"]: if role == Qt.FontRole: @@ -132,21 +112,6 @@ class TableEditableModel(QAbstractTableModel): return QVariant() - def headerData(self, section, orientation, role=Qt.DisplayRole): - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self._header[section] - elif orientation == Qt.Vertical and role == Qt.DisplayRole: - return str(section + 1) - - if role == Qt.ToolTipRole and section == 4: - return _translate( - "MainWindowProfile", - "Abscisse en travers calculée en projétant les points" - " \nsur le plan défini par les deux points nommés extrêmes " - ) - - return QVariant() - def setData(self, index, value, role=Qt.EditRole): row = index.row() column = index.column() @@ -154,34 +119,34 @@ class TableEditableModel(QAbstractTableModel): if role == Qt.EditRole: try: if column == 0: - self._undo_stack.push( + self._undo.push( SetXCommand( - self._profile, row, - self._profile.point(row).x, + self._data, row, + self._data.point(row).x, value ) ) elif column == 1: - self._undo_stack.push( + self._undo.push( SetYCommand( - self._profile, row, - self._profile.point(row).y, + self._data, row, + self._data.point(row).y, value ) ) elif column == 2: - self._undo_stack.push( + self._undo.push( SetZCommand( - self._profile, row, - self._profile.point(row).z, + self._data, row, + self._data.point(row).z, value ) ) elif column == 3: - self._undo_stack.push( + self._undo.push( SetNameCommand( - self._profile, row, - self._profile.point(row).name, + self._data, row, + self._data.point(row).name, value ) ) @@ -213,9 +178,9 @@ class TableEditableModel(QAbstractTableModel): def insert_row(self, row, parent=QModelIndex()): self.beginInsertRows(parent, row, row - 1) - self._undo_stack.push( + self._undo.push( AddCommand( - self._profile, row + self._data, row ) ) @@ -225,9 +190,9 @@ class TableEditableModel(QAbstractTableModel): def remove_rows(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) - self._undo_stack.push( + self._undo.push( DelCommand( - self._profile, rows + self._data, rows ) ) @@ -239,15 +204,15 @@ class TableEditableModel(QAbstractTableModel): reverse = (order != Qt.AscendingOrder) - self._undo_stack.push( + self._undo.push( SortCommand( - self._profile, column, reverse + self._data, column, reverse ) ) self.layoutChanged.emit() - def move_row_up(self, row, parent=QModelIndex()): + def move_up(self, row, parent=QModelIndex()): if row <= 0: return @@ -255,26 +220,26 @@ class TableEditableModel(QAbstractTableModel): self.beginMoveRows(parent, row - 1, row - 1, parent, target) - self._undo_stack.push( + self._undo.push( MoveCommand( - self._profile, "up", row + self._data, "up", row ) ) self.endMoveRows() self.layoutChanged.emit() - def move_row_down(self, row_to_move, parent=QModelIndex()): - if row > self._profile.number_points: + def move_down(self, row_to_move, parent=QModelIndex()): + if row > self._data.number_points: return target = row self.beginMoveRows(parent, row + 1, row + 1, parent, target) - self._undo_stack.push( + self._undo.push( MoveCommand( - self._profile, "down", row + self._data, "down", row ) ) @@ -282,7 +247,7 @@ class TableEditableModel(QAbstractTableModel): self.layoutChanged.emit() def paste(self, row, header, data): - if row > self._profile.number_points: + if row > self._data.number_points: return if len(data) == 0: @@ -290,12 +255,12 @@ class TableEditableModel(QAbstractTableModel): self.layoutAboutToBeChanged.emit() - self._undo_stack.push( + self._undo.push( PasteCommand( - self._profile, row, + self._data, row, list( map( - lambda d: self._profile.point_from_data(header, d), + lambda d: self._data.point_from_data(header, d), data ) ) @@ -306,32 +271,9 @@ class TableEditableModel(QAbstractTableModel): self.layoutChanged.emit() def undo(self): - self._undo_stack.undo() + self._undo.undo() self.layoutChanged.emit() def redo(self): - self._undo_stack.redo() + self._undo.redo() self.layoutChanged.emit() - - -class Delegate(QStyledItemDelegate): - def __init__(self, parent=None, setModelDataEvent=None): - super(Delegate, self).__init__(parent) - self.setModelDataEvent = setModelDataEvent - - 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()) - - if not self.setModelDataEvent is None: - self.setModelDataEvent() - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) diff --git a/src/View/Geometry/Profile/Translate.py b/src/View/Geometry/Profile/Translate.py new file mode 100644 index 00000000..148426aa --- /dev/null +++ b/src/View/Geometry/Profile/Translate.py @@ -0,0 +1,36 @@ +# 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 . + +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from View.Tools.PamhyrTranslate import PamhyrTranslate +from View.Geometry.Translate import GeometryTranslate + +_translate = QCoreApplication.translate + +class GeometryProfileTranslate(GeometryTranslate): + def __init__(self): + super(GeometryProfileTranslate, self).__init__() + + self._sub_dict["table_headers"] = { + "x": _translate("Geometry", "X (m)"), + "y": _translate("Geometry", "Y (m)"), + "z": _translate("Geometry", "Z (m)"), + "name": _translate("Geometry", "Name"), + "abs": _translate("Geometry", "Traversal abs (m)"), + } diff --git a/src/View/Geometry/Profile/Window.py b/src/View/Geometry/Profile/Window.py index 54d1e011..e4112f40 100644 --- a/src/View/Geometry/Profile/Window.py +++ b/src/View/Geometry/Profile/Window.py @@ -31,205 +31,188 @@ from PyQt5.QtCore import ( ) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QCheckBox, - QUndoStack, QShortcut, + QUndoStack, QShortcut, QTableView, QAbstractItemView, + QHeaderView, QVBoxLayout, QAction, ) from Model.Geometry.Reach import Reach from Model.Geometry.ProfileXYZ import ProfileXYZ -from View.ASubWindow import ASubMainWindow -from View.Geometry.Profile.mainwindow_ui_profile import Ui_MainWindow +from View.Tools.PamhyrWindow import PamhyrWindow +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas + from View.Geometry.Profile.Plot import Plot -from View.Geometry.Profile.Table import * +from View.Geometry.Profile.Table import GeometryProfileTableModel +from View.Geometry.Profile.Translate import GeometryProfileTranslate _translate = QCoreApplication.translate -class ProfileWindow(ASubMainWindow): - def __init__(self, profile=None, title="Profile", parent=None): - self._title = title - self.parent = parent - super(ProfileWindow, self).__init__( - name=self._title, - parent=self.parent - ) - - self.ui = Ui_MainWindow() - self.ui.setupUi(self) +class ProfileWindow(PamhyrWindow): + _pamhyr_ui = "GeometryCrossSection" + _pamhyr_name = "Geometry cross-section" + def __init__(self, profile=None, study=None, config=None, parent=None): self._profile = profile - self._model = None - self.setup_window() - self.setup_sc() - self.setup_model() - self.setup_connections() - self.plot() - - self._model.dataChanged.connect(self.update_plot) - self.fileName = None - - # self.ui.tableView.installEventFilter(self) - # self._model.dataChanged.connect(self.tableview_is_modified) - - # self.ui.btn_go_back.setEnabled(False) - # self.ui.btn_check.setEnabled(False) - # self._model.dataChanged.connect(self.set_enable_cancel_btn) - # self._model.dataChanged.connect(self.set_enable_validate_changes_btn) - # self.ui.btn_reset.setEnabled(False) - # self._model.dataChanged.connect(self.set_enable_go_back_initial_state_btn) - - def setup_window(self): - header = _translate("MainWindowProfile", "Profile") - - name = self._profile.name - if (name is None) or (name == ""): - name = _translate("MainWindowProfile", "(no name)") - - self._title = ( - header + " - " + - f"{self._profile.reach.name}" + " - " + - f"{name} ({self._profile.kp})" + name = f"{self._pamhyr_name} - {self._profile.name} {self._profile.kp}" + super(ProfileWindow, self).__init__( + title = name, + study = study, + config = config, + trad = GeometryProfileTranslate(), + parent = parent ) - self.setWindowTitle(self._title) + self.setup_table() + self.setup_plot() + self.setup_connections() - def setup_sc(self): - self._undo_stack = QUndoStack() + def setup_table(self): + table_headers = self._trad.get_dict("table_headers") - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - - def setup_model(self): - self._model = TableEditableModel( - profile = self._profile, + table = self.find(QTableView, "tableView") + self._tablemodel = GeometryProfileTableModel( + table_view = table, + table_headers = table_headers, + editable_headers = ["name", "x", "y", "z"], + data = self._profile, undo = self._undo_stack ) + table.setModel(self._tablemodel) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) - self.ui.tableView.setModel(self._model) - self.ui.tableView.setItemDelegate(Delegate()) + def setup_plot(self): + self._tablemodel.blockSignals(True) - def setup_connections(self): - self.ui.btn_sort_asc_x.triggered.connect(self.sort_X_ascending) - self.ui.btn_sort_desc_x.triggered.connect(self.sort_X_descending) - self.ui.btn_sort_asc_y.triggered.connect(self.sort_Y_ascending) - self.ui.btn_sort_desc_y.triggered.connect(self.sort_Y_descending) - self.ui.btn_move_up.triggered.connect(self.move_row_up) - self.ui.btn_move_down.triggered.connect(self.move_row_down) - self.ui.btn_export.triggered.connect(self.handleSave) - self.ui.btn_add.triggered.connect(self.insert_row) - self.ui.btn_delete.triggered.connect(self.delete_row) - # self.ui.btn_copy.clicked.connect(self.copyTable) - # self.ui.btn_paste.clicked.connect(self.pasteTable) - # self.ui.btn_check.clicked.connect(self.validate_changes) - # self.ui.btn_go_back.clicked.connect(self.cancel_validate_changes) - # self.ui.btn_reset.clicked.connect(self.go_back_to_initial_state) - - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - - def plot(self): - self.ui.tableView.model().blockSignals(True) + self._canvas = MplCanvas(width=3, height=4, dpi=100) + self._canvas.setObjectName("canvas") + self._toolbar = PamhyrPlotToolbar( + self._canvas, self, + items = ["home", "zoom", "save", "iso", "back/forward", "move"] + ) + self._plot_layout = self.find(QVBoxLayout, "verticalLayout") + self._plot_layout.addWidget(self._toolbar) + self._plot_layout.addWidget(self._canvas) self._plot = Plot( - canvas = self.ui.canvas, + canvas = self._canvas, data = self._profile, - toolbar = None, - table = self.ui.tableView, + toolbar = self._toolbar, + table = self.find(QTableView, "tableView") ) self._plot.draw() - self.ui.tableView.model().blockSignals(False) + self._tablemodel.blockSignals(False) + + def setup_connections(self): + actions = { + "action_sort_asc": self.sort_X_ascending, + "action_sort_des": self.sort_X_descending, + "action_up": self.move_up, + "action_down": self.move_down, + "action_add": self.add, + "action_delete": self.delete, + } + + for action in actions: + self.find(QAction, action)\ + .triggered.connect(actions[action]) + + self._tablemodel.dataChanged.connect(self.update_plot) def update_plot(self): - self.ui.tableView.model().blockSignals(True) + self._tablemodel.blockSignals(True) # TODO: Do not rebuild all graph self._plot.update() - self.ui.tableView.model().blockSignals(False) + self._tablemodel.blockSignals(False) def index_selected_row(self): - rows = self.ui.tableView\ + rows = self._tablemodel\ .selectionModel()\ .selectedRows() if len(rows) == 0: return 0 - return self.ui.tableView\ + return self._tablemodel\ .selectionModel()\ .selectedRows()[0]\ .row() - def insert_row(self): - if len(self.ui.tableView.selectedIndexes()) == 0: - self._model.insert_row(self._model.rowCount()) + def add(self): + table = self.find(QTableView, "tableView") + if len(table.selectedIndexes()) == 0: + self._tablemodel.insert_row(self._tablemodel.rowCount()) else: row = self.index_selected_row() - self._model.insert_row(row + 1) + self._tablemodel.insert_row(row + 1) self.update_plot() - def delete_row(self): + def delete(self): + table = self.find(QTableView, "tableView") rows = sorted( list( set( - [index.row() for index in self.ui.tableView.selectedIndexes()] + [index.row() for index in table.selectedIndexes()] ) ) ) if len(rows) > 0: - self._model.remove_rows(rows) + self._tablemodel.remove_rows(rows) self.update_plot() def sort_X_ascending(self): - self._model.sort('x', order=Qt.AscendingOrder) + self._tablemodel.sort('x', order=Qt.AscendingOrder) self.update_plot() def sort_X_descending(self): - self._model.sort('x', order=Qt.DescendingOrder) + self._tablemodel.sort('x', order=Qt.DescendingOrder) self.update_plot() def sort_Y_ascending(self): - self._model.sort('y', order=Qt.AscendingOrder) + self._tablemodel.sort('y', order=Qt.AscendingOrder) self.update_plot() def sort_Y_descending(self): - self._model.sort('y', order=Qt.DescendingOrder) + self._tablemodel.sort('y', order=Qt.DescendingOrder) self.update_plot() - def move_row_down(self): + def move_down(self): rows = list( set( - [index.row() for index in self.ui.tableView.selectedIndexes()] + [index.row() for index in + self.find(QTableView, "tableView").selectedIndexes()] ) ) for row in rows: - if row < self._model.rowCount() - 1: - self._model.move_row_down(row) + if row < self._tablemodel.rowCount() - 1: + self._tablemodel.move_down(row) self.update_plot() - def move_row_up(self): + def move_up(self): rows = list( set( - [index.row() for index in self.ui.tableView.selectedIndexes()] + [index.row() for index in + self.find(QTableView, "tableView").selectedIndexes()] ) ) for row in rows: if 0 < row: - self._model.move_row_up(row) + self._tablemodel.move_up(row) self.update_plot() def copy(self): - rows = self.ui.tableView\ + rows = self._tablemodel\ .selectionModel()\ .selectedRows() table = [] @@ -257,136 +240,13 @@ class ProfileWindow(ASubMainWindow): row.append(self._profile) row = self.index_selected_row() - self._model.paste(row, header, data) + self._tablemodel.paste(row, header, data) self.update_plot() def undo(self): - self._model.undo() + self._tablemodel.undo() self.update_plot() def redo(self): - self._model.redo() + self._tablemodel.redo() self.update_plot() - - def handleSave(self): - if self.fileName is None or self.fileName == '': - self.fileName, self.filters = QFileDialog.getSaveFileName( - self, filter="CSV files (*.csv)" - ) - - if self.fileName != '': - with open(self.fileName, 'w') as stream: - csvout = csv.writer(stream, delimiter='\t', quotechar=' ', - escapechar=None, - quoting=csv.QUOTE_NONNUMERIC, - lineterminator='\n') - - for row in range(self._model.rowCount(QModelIndex())): - rowdata = [] - for column in range(self._model.columnCount(QModelIndex())): - item = self._model.index( - row, column, QModelIndex() - ).data(Qt.DisplayRole) - - if item is not None: - rowdata.append(item) - - if item == 'nan': - rowdata.remove(item) - - csvout.writerow(rowdata) - - def handleOpen(self): - self.fileName, self.filterName = QFileDialog.getOpenFileName(self) - - if self.fileName != '': - with open(self.fileName, 'r') as f: - reader = csv.reader(f, delimiter='\t') - header = next(reader) - - buf = [] - for row in reader: - row[0] = QCheckBox("-") - buf.append(row) - - self._model = None - self._model = TableEditableModel(buf) - self.ui.tableView.setModel(self._model) - self.fileName = '' - - def set_enable_validate_changes_btn(self): - self.ui.btn_check.setEnabled(True) - - def set_enable_cancel_btn(self): - self.ui.btn_go_back.setEnabled(True) - - def set_enable_go_back_initial_state_btn(self): - self.ui.btn_reset.setEnabled(True) - - def delete_empty_rows(self): - if self._model.data_contains_nan(): - buttonReply = QtWidgets.QMessageBox.question( - self, - _translate("MainWindowProfile", - "Suppression les lignes incomplètes"), - _translate("MainWindowProfile", - "Supprimer les lignes des cellules" - " non renseignées ?"), - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - - if buttonReply == QtWidgets.QMessageBox.Yes: - self._model.delete_empty_rows() - - if buttonReply == QtWidgets.QMessageBox.No: - pass - - def remove_duplicates_point_names(self): - counter_list = [] - list_deleted_names = [] - - for ind, name_point in enumerate(self._model.name): - if name_point not in counter_list: - counter_list.append(name_point) - elif len(name_point.strip()) > 0 and name_point in counter_list: - if name_point not in list_deleted_names: - list_deleted_names.append(name_point) - - if list_deleted_names: - self.msg_box_check_duplication_names(list_deleted_names) - - def msg_box_check_duplication_names(self, list_deleted_names): - if len(list_deleted_names) == 1: - text = _translate("MainWindowProfile", - "Le nom ''{}'' est dupliqué." - " \n\nYes : Ne garder que la première occurrence. \nNo :" - " Annuler la suppression.".format(list_deleted_names[0])) - else: - text = _translate("MainWindowProfile", - "Les noms suivants : \n{} sont dupliqués" - " \n\nYes : Ne garder que la première occurrence de " - "chaque nom. \nNo :" - " Annuler la suppression.".format(list_deleted_names)) - - buttonReply = QtWidgets.QMessageBox.question( - self, _translate("MainWindowProfile", - "Suppression des noms répétés"), - text, - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - - if buttonReply == QtWidgets.QMessageBox.Yes: - self._model.remove_duplicates_names() - - def ask_quit(self): - choice = QtWidgets.QMessageBox.question( - self, - _translate("MainWindowProfile", "Quittez ?"), - _translate("MainWindowProfile", - "Etes-vous sûr de vouloir quitter ?"), - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - return choice == QtWidgets.QMessageBox.Yes - - def initial_state_model(self, profile_initial_state): - return profile_initial_state diff --git a/src/View/Geometry/Profile/mainwindow_ui_profile.py b/src/View/Geometry/Profile/mainwindow_ui_profile.py deleted file mode 100644 index a6844e55..00000000 --- a/src/View/Geometry/Profile/mainwindow_ui_profile.py +++ /dev/null @@ -1,260 +0,0 @@ -# mainwindow_ui_profile.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 . - -# -*- coding: utf-8 -*- - -import os.path - -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import ( - QTableView, QAbstractItemView, QHeaderView, - QToolBar, -) - -from View.Plot import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar - -file_path = os.path.abspath(os.path.dirname(__file__)) -icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/../View/ui/ressources/" - -class Ui_MainWindow(object): - def setupUi(self, MainWindowProfile): - MainWindowProfile.setObjectName("MainWindowProfile") - MainWindowProfile.resize(1089, 699) - self.centralwidget = QtWidgets.QWidget(MainWindowProfile) - self.centralwidget.setObjectName("centralwidget") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.splitter = QtWidgets.QSplitter(self.centralwidget) - self.splitter.setOrientation(Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.widget = QtWidgets.QWidget(self.splitter) - self.widget.setObjectName("widget") - self.verticalLayout_left = QtWidgets.QVBoxLayout(self.widget) - self.verticalLayout_left.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_left.setObjectName("verticalLayout_left") - - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - - self.toolBar = QToolBar(MainWindowProfile) - self.toolBar.setOrientation(Qt.Vertical) - self.toolBar.setEnabled(True) - MainWindowProfile.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) - - btn_size = QSize(40, 28) - - self.btn_add = QtWidgets.QAction(self.widget) - self.btn_add.setObjectName("btn_add") - icon_btn_add = QtGui.QIcon() - icon_btn_add.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-add.png")) - self.btn_add.setIcon(icon_btn_add) - self.toolBar.addAction(self.btn_add) - - self.btn_delete = QtWidgets.QAction(self.widget) - self.btn_delete.setObjectName("btn_delete") - icon_btn_delete = QtGui.QIcon() - icon_btn_delete.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-remove.png")) - self.btn_delete.setIcon(icon_btn_delete) - self.toolBar.addAction(self.btn_delete) - - self.btn_sort_asc_x = QtWidgets.QAction(self.widget) - self.btn_sort_asc_x.setObjectName("btn_sort_asc_x") - icon_btn_sort_asc_x = QtGui.QIcon() - icon_btn_sort_asc_x.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-ascending.png")) - self.btn_sort_asc_x.setIcon(icon_btn_sort_asc_x) - self.toolBar.addAction(self.btn_sort_asc_x) - - self.btn_sort_desc_x = QtWidgets.QAction(self.widget) - self.btn_sort_desc_x.setObjectName("btn_sort_desc_x") - icon_btn_sort_desc_x = QtGui.QIcon() - icon_btn_sort_desc_x.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-descending.png")) - self.btn_sort_desc_x.setIcon(icon_btn_sort_desc_x) - self.toolBar.addAction(self.btn_sort_desc_x) - - self.btn_sort_asc_y = QtWidgets.QAction(self.widget) - self.btn_sort_asc_y.setObjectName("btn_sort_asc_y") - icon_btn_sort_asc_y = QtGui.QIcon() - icon_btn_sort_asc_y.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-ascending.png")) - self.btn_sort_asc_y.setIcon(icon_btn_sort_asc_y) - self.toolBar.addAction(self.btn_sort_asc_y) - - self.btn_sort_desc_y = QtWidgets.QAction(self.widget) - self.btn_sort_desc_y.setObjectName("btn_sort_desc_y") - icon_btn_sort_desc_y = QtGui.QIcon() - icon_btn_sort_desc_y.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-descending.png")) - self.btn_sort_desc_y.setIcon(icon_btn_sort_desc_y) - self.toolBar.addAction(self.btn_sort_desc_y) - - self.btn_move_up = QtWidgets.QAction(self.widget) - self.btn_move_up.setObjectName("btn_move_up") - icon_btn_move_up = QtGui.QIcon() - icon_btn_move_up.addPixmap( - QtGui.QPixmap(f"{icon_path}up.png")) - self.btn_move_up.setIcon(icon_btn_move_up) - self.toolBar.addAction(self.btn_move_up) - - self.btn_move_down = QtWidgets.QAction(self.widget) - self.btn_move_down.setObjectName("btn_move_down") - icon_btn_move_down = QtGui.QIcon() - icon_btn_move_down.addPixmap( - QtGui.QPixmap(f"{icon_path}down.png")) - self.btn_move_down.setIcon(icon_btn_move_down) - self.toolBar.addAction(self.btn_move_down) - - self.btn_export = QtWidgets.QAction(self.widget) - self.btn_export.setObjectName("btn_export") - icon_btn_export = QtGui.QIcon() - icon_btn_export.addPixmap( - QtGui.QPixmap(f"{icon_path}export.png")) - self.btn_export.setIcon(icon_btn_export) - self.toolBar.addAction(self.btn_export) - - self.btn_copy = QtWidgets.QAction(self.widget) - self.btn_copy.setObjectName("btn_copy") - icon_btn_copy = QtGui.QIcon() - icon_btn_copy.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-copy.png")) - self.btn_copy.setIcon(icon_btn_copy) - self.toolBar.addAction(self.btn_copy) - - self.btn_paste = QtWidgets.QAction(self.widget) - self.btn_paste.setObjectName("btn_paste") - icon_btn_paste = QtGui.QIcon() - icon_btn_paste.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-paste.png")) - self.btn_paste.setIcon(icon_btn_paste) - self.toolBar.addAction(self.btn_paste) - - self.btn_check = QtWidgets.QAction(self.widget) - self.btn_check.setObjectName("btn_check") - icon_btn_check = QtGui.QIcon() - icon_btn_check.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-apply.png")) - self.btn_check.setIcon(icon_btn_check) - self.toolBar.addAction(self.btn_check) - - self.btn_go_back = QtWidgets.QAction(self.widget) - self.btn_go_back.setObjectName("btn_go_back") - icon_btn_go_back = QtGui.QIcon() - icon_btn_go_back.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-undo.png")) - self.btn_go_back.setIcon(icon_btn_go_back) - self.toolBar.addAction(self.btn_go_back) - - self.btn_reset = QtWidgets.QAction(self.widget) - self.btn_reset.setObjectName("btn_reset") - icon_btn_reset = QtGui.QIcon() - icon_btn_reset.addPixmap( - QtGui.QPixmap(f"{icon_path}cancel.png")) - self.btn_reset.setIcon(icon_btn_reset) - self.toolBar.addAction(self.btn_reset) - - self.verticalLayout_left.addLayout(self.horizontalLayout) - - self.tableView = QTableView(self.widget) - self.tableView.setObjectName("tableView") - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) - self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - self.tableView.resizeColumnsToContents() - self.tableView.resizeRowsToContents() - self.tableView.setAlternatingRowColors(True) - self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.verticalLayout_left.addWidget(self.tableView) - - self.widget1 = QtWidgets.QWidget(self.splitter) - self.widget1.setObjectName("widget1") - - self.verticalLayout_right = QtWidgets.QVBoxLayout(self.widget1) - self.verticalLayout_right.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_right.setObjectName("verticalLayout_right") - - self.label_graphic_help = QtWidgets.QLabel(self.widget1) - self.label_graphic_help.setMaximumSize(QtCore.QSize(16777215, 25)) - self.label_graphic_help.setBaseSize(QtCore.QSize(0, 30)) - self.label_graphic_help.setObjectName("label_graphic_help") - self.verticalLayout_right.addWidget(self.label_graphic_help) - - self.canvas = MplCanvas.MplCanvas(width=5, height=4, dpi=100) - self.canvas.setObjectName("canvas") - - self.my_toolbar = PamhyrPlotToolbar( - self.canvas, self.widget1 - ) - - self.verticalLayout_right.addWidget(self.my_toolbar) - self.verticalLayout_right.addWidget(self.canvas) - self.horizontalLayout_2.addWidget(self.splitter) - - MainWindowProfile.setCentralWidget(self.centralwidget) - - self.menubar = QtWidgets.QMenuBar(MainWindowProfile) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1089, 20)) - self.menubar.setObjectName("menubar") - MainWindowProfile.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindowProfile) - self.statusbar.setObjectName("statusbar") - MainWindowProfile.setStatusBar(self.statusbar) - - self.retranslateUi(MainWindowProfile) - QtCore.QMetaObject.connectSlotsByName(MainWindowProfile) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindowProfile", "MainWindowProfile")) - self.btn_add.setToolTip(_translate("MainWindowProfile", "Insérer un point")) - self.btn_delete.setToolTip( - _translate("MainWindowProfile", "Supprimer le/les point(s) sélectionnés")) - self.btn_sort_asc_x.setToolTip( - _translate("MainWindowProfile", "Trier les points par ordre croissant de X")) - self.btn_sort_desc_x.setToolTip( - _translate("MainWindowProfile", "Trier les points par ordre décroissant de X")) - self.btn_sort_asc_y.setToolTip(_translate("MainWindowProfile", - "Trier les points par ordre croissant de Y" - )) - self.btn_sort_desc_y.setToolTip(_translate("MainWindowProfile", - "Trier les points par ordre décroissant de Y")) - self.btn_move_up.setToolTip(_translate("MainWindowProfile", - "Décaler le point sélectionné vers le haut")) - self.btn_move_down.setToolTip(_translate("MainWindowProfile", - "Décaler le point sélectionné vers le bas")) - self.btn_export.setToolTip(_translate("MainWindowProfile", - "Exporter (dans un fichier) les points du profil au format tabulé")) - self.btn_copy.setToolTip( - _translate("MainWindowProfile", "Copier la sélection au format tabulé")) - self.btn_paste.setToolTip(_translate("MainWindowProfile", - "Coller la sélection depuis le presse-papier au format tabulé")) - self.btn_check.setToolTip(_translate("MainWindowProfile", - "Vérifier la validité de la saisie et garder ou pas les modifications " - "apportées")) - self.btn_go_back.setToolTip(_translate("MainWindowProfile", - "Annuler toutes les modifications depuis la dernière validation")) - self.btn_reset.setToolTip(_translate("MainWindowProfile", - "Annuler toutes les modifications et revenir à l\'état initial")) - self.label_graphic_help.setToolTip(_translate("MainWindowProfile", - "Ligne d'eau : \n \tZ : Cote (m) \n \tA : Aire mouillée (" - "m\u00B2) \n \tp : " - "Périmètre mouillé (m) \n \tL : Largeur au miroir (m)")) - self.label_graphic_help.setText( - _translate("MainWindowProfile", "\'Maj + Clic\' : Ligne d\'eau & \'Ctrl + Clic\' : " - "Sélectionner des points")) diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py index 1b055209..e9f6eac6 100644 --- a/src/View/Geometry/Table.py +++ b/src/View/Geometry/Table.py @@ -35,6 +35,8 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from Model.Geometry import Reach from Model.Geometry.ProfileXYZ import ProfileXYZ from View.Geometry.UndoCommand import * @@ -44,53 +46,26 @@ logger = logging.getLogger() _translate = QCoreApplication.translate -class TableEditableModel(QAbstractTableModel): - def __init__(self, reach, headers=None, undo=None): - QAbstractTableModel.__init__(self) - data_list = [] - - self._undo_stack = undo - self._reach = reach - - # Hack for qtlinguist - _ = _translate("Geometry", "Name") - _ = _translate("Geometry", "Kp (m)") - _ = _translate("Geometry", "Type") - - if headers is None: - self.headers = [ - "Name", - "Kp (m)", - "Type" - ] - else: - self.headers = headers - - def rowCount(self, parent=QModelIndex()): - return self._reach.number_profiles - - def columnCount(self, parent=QModelIndex()): - return len(self.headers) - +class GeometryReachTableModel(PamhyrTableModel): def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return QVariant() if role == Qt.DisplayRole and index.column() == 0: - return self._reach.profile(index.row()).name + return self._data.profile(index.row()).name if role == Qt.DisplayRole and index.column() == 1: - kp = self._reach.profile(index.row()).kp + kp = self._data.profile(index.row()).kp return f"{kp:.4f}" if role == Qt.DisplayRole and index.column() == 2: - return self._reach.profile(index.row()).profile_type + return self._data.profile(index.row()).profile_type if role == Qt.TextAlignmentRole: return Qt.AlignHCenter | Qt.AlignVCenter if role == Qt.ForegroundRole and index.column() == 0: - name = self._reach.profile(index.row()).name\ + name = self._data.profile(index.row()).name\ .strip()\ .lower() if (name == "upstream" or name == "up" or @@ -102,16 +77,6 @@ class TableEditableModel(QAbstractTableModel): return QVariant() - def headerData(self, section, orientation, role=Qt.DisplayRole): - if role == Qt.DisplayRole: - if orientation == Qt.Horizontal: - if section < len(self.headers): - return _translate("Geometry", self.headers[section]) - else: - return str(section + 1) - - return QVariant() - def setData(self, index, value, role=Qt.EditRole): row = index.row() column = index.column() @@ -119,19 +84,19 @@ class TableEditableModel(QAbstractTableModel): if role == Qt.EditRole and index.column() != 2: try: if index.column() == 0: - self._undo_stack.push( + self._undo.push( SetNameCommand( - self._reach, index.row(), - self._reach.profile(index.row()).name, + self._data, index.row(), + self._data.profile(index.row()).name, value ) ) if index.column() == 1: - self._undo_stack.push( + self._undo.push( SetKPCommand( - self._reach, index.row(), - self._reach.profile(index.row()).kp, + self._data, index.row(), + self._data.profile(index.row()).kp, value ) ) @@ -149,39 +114,25 @@ class TableEditableModel(QAbstractTableModel): return False - def index(self, row, column, parent=QModelIndex()): - if not self.hasIndex(row, column, parent): - return QModelIndex() - - return self.createIndex(row, column, QModelIndex()) - - def flags(self, index): - flg = Qt.ItemIsEnabled | Qt.ItemIsSelectable - - if index.column() == 2: - return flg - else: - return Qt.ItemIsEditable | flg - # @QtCore.pyqtSlot() - def insert_row(self, row, parent=QModelIndex()): + def add(self, row, parent=QModelIndex()): self.beginInsertRows(parent, row, row - 1) - self._undo_stack.push( + self._undo.push( AddCommand( - self._reach, row + self._data, row ) ) self.endInsertRows() self.layoutChanged.emit() - def remove_rows(self, rows, parent=QModelIndex()): + def delete(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) - self._undo_stack.push( + self._undo.push( DelCommand( - self._reach, rows + self._data, rows ) ) @@ -191,9 +142,9 @@ class TableEditableModel(QAbstractTableModel): def sort_profiles(self, _reverse): self.layoutAboutToBeChanged.emit() - self._undo_stack.push( + self._undo.push( SortCommand( - self._reach, _reverse + self._data, _reverse ) ) @@ -201,7 +152,7 @@ class TableEditableModel(QAbstractTableModel): self.layoutChanged.emit() - def move_row_up(self, row, parent=QModelIndex()): + def move_up(self, row, parent=QModelIndex()): if row <= 0: return @@ -209,26 +160,26 @@ class TableEditableModel(QAbstractTableModel): self.beginMoveRows(parent, row - 1, row - 1, parent, target) - self._undo_stack.push( + self._undo.push( MoveCommand( - self._reach, "up", row + self._data, "up", row ) ) self.endMoveRows() self.layoutChanged.emit() - def move_row_down(self, row, parent=QModelIndex()): - if row > self._reach.number_profiles: + def move_down(self, row, parent=QModelIndex()): + if row > self._data.number_profiles: return target = row self.beginMoveRows(parent, row + 1, row + 1, parent, target) - self._undo_stack.push( + self._undo.push( MoveCommand( - self._reach, "down", row + self._data, "down", row ) ) @@ -238,9 +189,9 @@ class TableEditableModel(QAbstractTableModel): def duplicate(self, rows, profiles): self.layoutAboutToBeChanged.emit() - self._undo_stack.push( + self._undo.push( DuplicateCommand( - self._reach, rows, + self._data, rows, profiles ) ) @@ -250,7 +201,7 @@ class TableEditableModel(QAbstractTableModel): def paste(self, row, header, data): - if row > self._reach.number_profiles: + if row > self._data.number_profiles: return if len(data) == 0: @@ -258,9 +209,9 @@ class TableEditableModel(QAbstractTableModel): self.layoutAboutToBeChanged.emit() - self._undo_stack.push( + self._undo.push( PasteCommand( - self._reach, row, + self._data, row, list( map( lambda d: ProfileXYZ.from_data(header, d), @@ -272,71 +223,3 @@ class TableEditableModel(QAbstractTableModel): self.layoutAboutToBeChanged.emit() self.layoutChanged.emit() - - - def undo(self): - self._undo_stack.undo() - self.layoutChanged.emit() - - def redo(self): - self._undo_stack.redo() - self.layoutChanged.emit() - - -class Delegate(QStyledItemDelegate): - def __init__(self, parent=None, setModelDataEvent=None): - super(Delegate, self).__init__(parent) - self.setModelDataEvent = setModelDataEvent - - 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()) - - if not self.setModelDataEvent is None: - self.setModelDataEvent() - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - -class Delegate1(QStyledItemDelegate): - def __init__(self, owner, choices): - super().__init__(owner) - self.items = choices - - def paint(self, painter, option, index): - if isinstance(self.parent(), QAbstractItemView): - self.parent().openPersistentEditor(index) - super(Delegate1, self).paint(painter, option, index) - - def createEditor(self, parent, option, index): - editor = QComboBox(parent) - # editor.currentIndexChanged.connect(self.commit_editor) - editor.addItems(self.items) - return editor - - def setEditorData(self, editor, index): - editor.blockSignals(True) - text = index.model().data(index, Qt.DisplayRole) - try: - i = self.items.index(text) - except ValueError: - i = 0 - editor.setCurrentIndex(i) - editor.blockSignals(False) - - def setModelData(self, editor, model, index): - model.setData(index, editor.currentText(), Qt.DisplayRole) - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - - @pyqtSlot() - def currentIndexChanged(self): - self.commitData.emit(self.sender()) diff --git a/src/View/Geometry/Translate.py b/src/View/Geometry/Translate.py new file mode 100644 index 00000000..c583732d --- /dev/null +++ b/src/View/Geometry/Translate.py @@ -0,0 +1,45 @@ +# 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 . + +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from View.Tools.PamhyrTranslate import PamhyrTranslate + +_translate = QCoreApplication.translate + +class GeometryTranslate(PamhyrTranslate): + def __init__(self): + super(GeometryTranslate, self).__init__() + + self._dict["open_file"] = _translate("Geometry", "Open a file") + self._dict["file_st"] = _translate("Geometry", "File mage geometry (*.ST)") + self._dict["file_m"] = _translate("Geometry", "File mage meshed geometry (*.M)") + self._dict["file_all"] = _translate("Geometry", "All file (*)") + + self._dict["reach"] = _translate("Geometry", "reach") + self._dict["reachs"] = _translate("Geometry", "reachs") + + self._dict["cross_section"] = _translate("Geometry", "cross-section") + self._dict["cross_sections"] = _translate("Geometry", "cross-sections") + self._dict["profile"] = _translate("Geometry", "cross-section") + self._dict["profiles"] = _translate("Geometry", "cross-sections") + + self._sub_dict["table_headers"] = { + "name": _translate("Geometry", "Name"), + "kp": _translate("Geometry", "KP"), + } diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 1c8cb5da..516bf66f 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -34,117 +34,167 @@ from PyQt5.QtCore import ( ) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QCheckBox, - QUndoStack, QShortcut, + QUndoStack, QShortcut, QTableView, QHeaderView, + QAction, QSlider, QPushButton, QVBoxLayout, + QLabel, QAbstractItemView, ) -from View.Geometry.PlotXY import PlotXY -from View.Geometry.PlotKPZ import PlotKPZ -from View.Geometry.PlotAC import PlotAC +from View.Tools.PamhyrWindow import PamhyrWindow +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas -from View.ASubWindow import ASubMainWindow, WindowToolKit -from View.ListedSubWindow import ListedSubWindow -from View.Geometry.mainwindow_ui_reach import Ui_MainWindow -from View.Geometry.Table import * +from View.Geometry.Table import GeometryReachTableModel +from View.Geometry.PlotXY import PlotXY +from View.Geometry.PlotAC import PlotAC +from View.Geometry.PlotKPZ import PlotKPZ +from View.Geometry.Translate import GeometryTranslate from View.Geometry.Profile.Window import ProfileWindow _translate = QCoreApplication.translate -class GeometryWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, model=None, title="Geometry", parent=None): - self._title = title - self.parent = parent +class GeometryWindow(PamhyrWindow): + _pamhyr_ui = "GeometryReach" + _pamhyr_name = "Geometry" + + def __init__(self, reach=None, study=None, config=None, parent=None): + if reach is None: + self._reach = study.river.current_reach().reach + else: + self._reach = reach + + name = f"{self._pamhyr_name} - {self._reach.name}" + super(GeometryWindow, self).__init__( - name=self._title, - parent=parent + title = name, + study = study, + config = config, + trad = GeometryTranslate(), + parent = parent ) - self._model = model - self._reach = model.river.current_reach().reach - - self.ui = Ui_MainWindow() - self.ui.setupUi(self) - - self.tableView = self.ui.tableView - self.tableView_header = self.ui.tableView_header - self._tablemodel = None self._profile_window = [] - self._clipboard = None - self.setup_window() - self.setup_sc() - self.setup_model() + self.setup_table() self.setup_plots() + self.setup_statusbar() self.setup_connections() self.changed_slider_value() - def setup_window(self): - self._title = f"{self.ui.mainwindow_title} - {self._reach.name}" - self.setWindowTitle(self._title) + def setup_table(self): + table_headers = self._trad.get_dict("table_headers") - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - - def setup_model(self): - self._tablemodel = TableEditableModel( - headers = self.ui.tableView_header, - reach = self._reach, + table = self.find(QTableView, "tableView") + self._tablemodel = GeometryReachTableModel( + table_view = table, + table_headers = table_headers, + editable_headers = ["name", "kp"], + data = self._reach, undo = self._undo_stack ) - self.tableView.setModel(self._tablemodel) - self.tableView.setItemDelegate(Delegate()) + table.setModel(self._tablemodel) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) def setup_plots(self): + self._canvas_xy = MplCanvas(width=3, height=4, dpi=100) + self._canvas_xy.setObjectName("canvas_xy") + self._toolbar_xy = PamhyrPlotToolbar( + self._canvas_xy, self, + items = ["home", "zoom", "save", "iso", "back/forward", "move"] + ) + self._plot_layout_xy = self.find(QVBoxLayout, "verticalLayout") + self._plot_layout_xy.addWidget(self._toolbar_xy) + self._plot_layout_xy.addWidget(self._canvas_xy) self.plot_xy() + + self._canvas_kpc = MplCanvas(width=6, height=4, dpi=100) + self._canvas_kpc.setObjectName("canvas_kpc") + self._toolbar_kpc = PamhyrPlotToolbar( + self._canvas_kpc, self, + items = ["home", "zoom", "save", "iso", "back/forward", "move"] + ) + self._plot_layout_kpc = self.find(QVBoxLayout, "verticalLayout_2") + self._plot_layout_kpc.addWidget(self._toolbar_kpc) + self._plot_layout_kpc.addWidget(self._canvas_kpc) self.plot_kpc() + + self._canvas_ac = MplCanvas(width=9, height=4, dpi=100) + self._canvas_ac.setObjectName("canvas_ac") + self._toolbar_ac = PamhyrPlotToolbar( + self._canvas_ac, self, + items = ["home", "zoom", "save", "iso", "back/forward", "move"] + ) + self._plot_layout_ac = self.find(QVBoxLayout, "verticalLayout_3") + self._plot_layout_ac.addWidget(self._toolbar_ac) + self._plot_layout_ac.addWidget(self._canvas_ac) self.plot_ac() + def _compute_status_label(self): + row = self.index_selected_row() + profile = self._reach.profile(row) + + name = profile.name + " " + str(profile.kp) + + return ( + f"{self._trad['reach']}: {self._reach.name}" + " - " + f"{self._trad['cross_section']}: {name}" + ) + + def setup_statusbar(self): + txt = "" + self._status_label = QLabel(txt) + self.statusbar.addPermanentWidget(self._status_label) + + def update_statusbar(self): + txt = self._compute_status_label() + self._status_label.setText(txt) + def setup_connections(self): - self.ui.btn_open.triggered.connect(self.open_file_dialog) - self.ui.btn_sort_asc.triggered.connect(self.sort_ascending) - self.ui.btn_sort_desc.triggered.connect(self.sort_descending) - self.ui.btn_move_up.triggered.connect(self.move_row_up) - self.ui.btn_move_down.triggered.connect(self.move_row_down) - # self.ui.btn_end_editing.triggered.connect(self.handleSave) - self.ui.btn_add.triggered.connect(self.insert_row) - self.ui.btn_delete.triggered.connect(self.delete_rows) - self.ui.btn_edit.triggered.connect(self.edit_profile) - self.ui.verticalSlider.valueChanged.connect(self.changed_slider_value) + actions = { + "action_import": self.import_from_file, + # "action_export": self.export_to_file, + "action_sort_asc": self.sort_ascending, + "action_sort_des": self.sort_descending, + "action_up": self.move_up, + "action_down": self.move_down, + "action_add": self.add, + "action_delete": self.delete, + "action_edit": self.edit_profile, + } - self.ui.btn_slider_up.clicked.connect(self.decrement_value_slider) - self.ui.btn_slider_down.clicked.connect(self.increment_value_slider) - self.ui.btn_move_up.triggered.connect(self.changed_profile_slot) + for action in actions: + self.find(QAction, action)\ + .triggered.connect(actions[action]) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) + self.find(QSlider, "verticalSlider").valueChanged.connect(self.changed_slider_value) + self.find(QPushButton, "pushButton_up").clicked.connect(self.decrement_value_slider) + self.find(QPushButton, "pushButton_down").clicked.connect(self.increment_value_slider) # Profile selection when line change in table - self.tableView.selectionModel()\ - .selectionChanged\ - .connect(self.select_current_profile) + self.find(QTableView, "tableView").selectionModel()\ + .selectionChanged\ + .connect(self.select_current_profile) - def open_file_dialog(self): + + def import_from_file(self): options = QFileDialog.Options() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', ) options |= QFileDialog.DontUseNativeDialog + file_types = [ + self._trad["file_st"], + self._trad["file_m"], + self._trad["file_all"], + ] + filename, _ = QtWidgets.QFileDialog.getOpenFileName( self, - _translate("MainWindow_reach", "Ouvrir un fichier"), + self._trad["open_file"], "", - _translate("MainWindow_reach", "Fichiers .ST (*.ST)") + - ";; " + - _translate("MainWindow_reach", "Fichiers .M (*.M)") + - ";; " + - _translate("MainWindow_reach", "Tous les fichiers (*)"), + ";; ".join(file_types), options=options ) @@ -158,20 +208,6 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.plot_kpc() self.plot_ac() - def messagebox_profile_editing(self): - msg_box = QtWidgets.QMessageBox() - msg_box.setIcon(QtWidgets.QMessageBox.Information) - msg_box.setWindowTitle(_translate("MainWindow_reach", - "Édition des profils sélectionnés")) - msg_box.setText(_translate("MainWindow_reach", - "Vous avez sélectionné plus de 5 profils." - " \nSeuls les 5 premiers seront édités.")) - msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) - - return_value = msg_box.exec() - # if return_value == QtWidgets.QMessageBox.Ok: - # print('OK clicked') - def edit_profile(self): self.tableView.model().blockSignals(True) @@ -214,9 +250,9 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.tableView.model().blockSignals(True) self._plot_xy = PlotXY( - canvas = self.ui.canvas_1, + canvas = self._canvas_xy, data = self._reach, - toolbar = self.ui.toolbar_1 + toolbar = self._toolbar_xy ) self._plot_xy.draw() @@ -231,9 +267,9 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.tableView.model().blockSignals(True) self._plot_kpc = PlotKPZ( - canvas = self.ui.canvas_2, + canvas = self._canvas_kpc, data = self._reach, - toolbar = self.ui.toolbar_2 + toolbar = self._toolbar_kpc ) self._plot_kpc.draw() @@ -248,9 +284,9 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.tableView.model().blockSignals(True) self._plot_ac = PlotAC( - canvas = self.ui.canvas_3, + canvas = self._canvas_ac, data = self._reach, - toolbar = self.ui.toolbar_3, + toolbar = self._toolbar_ac, plot_xy = self._plot_xy ) self._plot_ac.draw() @@ -303,7 +339,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): if len(self.tableView.selectedIndexes()) > 0: row = self.index_selected_row() - self.ui.verticalSlider.setValue(row) + self.find(QSlider, "verticalSlider").setValue(row) self.select_plot_xy(row) self.select_plot_kpc(row) self.select_plot_ac(row) @@ -314,41 +350,37 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.tableView.model().blockSignals(True) if self._tablemodel.rowCount() != 0: - self.ui.verticalSlider.setMaximum(self._tablemodel.rowCount() - 1) + slider = self.find(QSlider, "verticalSlider") + slider.setMaximum(self._tablemodel.rowCount() - 1) - slider_value = self.ui.verticalSlider.value() + slider_value = slider.value() kp = self._reach.profile(slider_value).kp - self.ui.vertical_slider_label.setText( - _translate("MainWindow_reach", "Kp : ") + - f"{kp}" + "\n" + - _translate("MainWindow_reach", - "Profil N° : ") + - f"{slider_value + 1}" - ) - self.select_plot_xy(slider_value) self.select_plot_kpc(slider_value) self.select_row_profile_slider(slider_value) + self.update_statusbar() self.tableView.model().blockSignals(False) def increment_value_slider(self): - if 0 <= self.ui.verticalSlider.value() < self._tablemodel.rowCount() - 1: - self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) + slider = self.find(QSlider, "verticalSlider") + if 0 <= slider.value() < self._tablemodel.rowCount() - 1: + slider.setValue(slider.value() + 1) def decrement_value_slider(self): - if 0 < self.ui.verticalSlider.value() < self._tablemodel.rowCount(): - self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) + slider = self.find(QSlider, "verticalSlider") + if 0 < slider.value() < self._tablemodel.rowCount(): + slider.setValue(slider.value() - 1) - def insert_row(self): + def add(self): if len(self.tableView.selectedIndexes()) == 0: - self._tablemodel.insert_row(self._tablemodel.rowCount()) + self._tablemodel.add(self._tablemodel.rowCount()) else: row = self.index_selected_row() - self._tablemodel.insert_row(row + 1) + self._tablemodel.add(row + 1) - def delete_rows(self): + def delete(self): rows = sorted( list( set( @@ -358,7 +390,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): ) if len(rows) > 0: - self._tablemodel.remove_rows(rows) + self._tablemodel.delete(rows) self.update_plot_xy() self.select_current_profile() @@ -373,24 +405,24 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): .row() def sort_ascending(self): - self._tablemodel.sort_profiles(True) + self._tablemodel.sort_profiles(False) self.select_current_profile() self.changed_slider_value() def sort_descending(self): - self._tablemodel.sort_profiles(False) + self._tablemodel.sort_profiles(True) self.select_current_profile() self.changed_slider_value() - def move_row_up(self): + def move_up(self): row = self.index_selected_row() - self._tablemodel.move_row_up(row) + self._tablemodel.move_up(row) self.select_current_profile() - def move_row_down(self): + def move_down(self): row = self.index_selected_row() - self._tablemodel.move_row_down(row) + self._tablemodel.move_down(row) self.select_current_profile() def duplicate(self): @@ -411,7 +443,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self._tablemodel.duplicate(rows, profiles) self.select_current_profile() - def copy(self): + def _copy(self): rows = self.tableView\ .selectionModel()\ .selectedRows() @@ -427,7 +459,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self.copyTableIntoClipboard(table) - def paste(self): + def _paste(self): header, data = self.parseClipboardTable() if len(data) == 0: @@ -442,19 +474,19 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): self._tablemodel.paste(row, header, data) self.select_current_profile() - def undo(self): + def _undo(self): self._tablemodel.undo() self.select_current_profile() self.update_plot_xy() self.update_plot_kpc() - def redo(self): + def _redo(self): self._tablemodel.redo() self.select_current_profile() self.update_plot_xy() self.update_plot_kpc() - def handleSave(self): + def export_to_file(self): options = QFileDialog.Options() DEFAULT_DIRECTORY = '/home/' settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', ) @@ -463,32 +495,11 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): filename, filters = QFileDialog.getSaveFileName( self, - filter=_translate("MainWindow_reach", - "Files .ST(*.ST or *.st)") - + ";; " + - _translate("MainWindow_reach", "All files " - "(*)"), - options=options + filter = self._trad["file_st"] + ";; " + self._trad["file_all"], + options = options ) current_dir = os.path.split(filename)[0] or DEFAULT_DIRECTORY if filename != '': self._tablemodel.export_reach(filename) - - def handleOpen(self): - filename, filterName = QFileDialog.getOpenFileName(self) - - if filename != '': - with open(filename, 'r') as f: - reader = csv.reader(f, delimiter='\t') - header = next(reader) - buf = [] - for row in reader: - row[0] = QCheckBox("-") - buf.append(row) - - self._tablemodel = None - self._tablemodel = TableEditableModel(buf) - self.tableView.setModel(self._tablemodel) - filename = '' diff --git a/src/View/Geometry/mainwindow_ui_reach.py b/src/View/Geometry/mainwindow_ui_reach.py deleted file mode 100644 index ff0797ab..00000000 --- a/src/View/Geometry/mainwindow_ui_reach.py +++ /dev/null @@ -1,357 +0,0 @@ -# mainwindow_ui_reach.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 . - -# -*- coding: utf-8 -*- - -import sys -import os.path - -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtCore import QSize, Qt -from PyQt5.QtWidgets import ( - QAbstractItemView, QHeaderView, QToolBar, -) - -from View.Plot import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar - - -file_path = os.path.abspath(os.path.dirname(__file__)) -icon_path = f"{os.path.dirname(os.path.dirname(file_path))}/View/ui/ressources/" - -class Ui_MainWindow(object): - def setupUi(self, MainWindow_reach): - MainWindow_reach.setObjectName("MainWindow_reach") - MainWindow_reach.resize(1400, 800) - self.centralwidget = QtWidgets.QWidget(MainWindow_reach) - self.centralwidget.setObjectName("centralwidget") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.splitter_3 = QtWidgets.QSplitter(self.centralwidget) - self.splitter_3.setOrientation(Qt.Horizontal) - self.splitter_3.setObjectName("splitter_3") - self.widget = QtWidgets.QWidget(self.splitter_3) - self.widget.setObjectName("widget") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - - # self.label_section_order = QtWidgets.QLabel(self.widget) - # self.label_section_order.setLayoutDirection(Qt.LeftToRight) - # self.label_section_order.setObjectName("label_section_order") - - # self.verticalLayout.addWidget(self.label_section_order) - - self.toolBar = QToolBar(MainWindow_reach) - self.toolBar.setEnabled(True) - MainWindow_reach.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) - - btn_size = QSize(40, 28) - - self.btn_open = QtWidgets.QAction(self.widget) - self.btn_open.setObjectName("btn_open") - icon_btn_open = QtGui.QIcon() - icon_btn_open.addPixmap( - QtGui.QPixmap(f"{icon_path}open.png")) - self.btn_open.setIcon(icon_btn_open) - self.toolBar.addAction(self.btn_open) - - self.btn_add = QtWidgets.QAction(self.widget) - self.btn_add.setObjectName("btn_add") - icon_btn_add = QtGui.QIcon() - icon_btn_add.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk_add.png")) - self.btn_add.setIcon(icon_btn_add) - self.toolBar.addAction(self.btn_add) - - self.btn_delete = QtWidgets.QAction(self.widget) - self.btn_delete.setObjectName("btn_delete") - icon_btn_delete = QtGui.QIcon() - icon_btn_delete.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-remove.png")) - self.btn_delete.setIcon(icon_btn_delete) - self.toolBar.addAction(self.btn_delete) - - self.btn_edit = QtWidgets.QAction(self.widget) - self.btn_edit.setObjectName("btn_edit") - icon_btn_edit = QtGui.QIcon() - icon_btn_edit.addPixmap( - QtGui.QPixmap(f"{icon_path}gnome-stock-edit.png")) - self.btn_edit.setIcon(icon_btn_edit) - self.toolBar.addAction(self.btn_edit) - - # self.btn_copy_selected_profile = QtWidgets.QAction(self.widget) - # self.btn_copy_selected_profile.setObjectName("btn_copy_selected_profile") - # icon_btn_copy_selected_profile = QtGui.QIcon() - # icon_btn_copy_selected_profile.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-copy.png")) - # self.btn_copy_selected_profile.setIcon(icon_btn_copy_selected_profile) - # self.toolBar.addAction(self.btn_copy_selected_profile) - - # self.btn_paste_selected_profile = QtWidgets.QAction(self.widget) - # self.btn_paste_selected_profile.setObjectName("btn_paste_selected_profile") - # icon_btn_paste_selected_profile = QtGui.QIcon() - # icon_btn_paste_selected_profile.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-paste.png")) - # self.btn_paste_selected_profile.setIcon(icon_btn_paste_selected_profile) - # self.toolBar.addAction(self.btn_paste_selected_profile) - - # self.btn_duplicate_selected_profile = QtWidgets.QAction(self.widget) - # self.btn_duplicate_selected_profile.setObjectName("btn_duplicate_selected_profile") - # icon_btn_duplicate_selected_profile = QtGui.QIcon() - # icon_btn_duplicate_selected_profile.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-dnd-multiple.png")) - # self.btn_duplicate_selected_profile.setIcon(icon_btn_duplicate_selected_profile) - # self.toolBar.addAction(self.btn_duplicate_selected_profile) - - self.btn_sort_asc = QtWidgets.QAction(self.widget) - self.btn_sort_asc.setObjectName("btn_sort_asc") - icon_btn_sort_asc = QtGui.QIcon() - icon_btn_sort_asc.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-ascending.png")) - self.btn_sort_asc.setIcon(icon_btn_sort_asc) - self.toolBar.addAction(self.btn_sort_asc) - - self.btn_sort_desc = QtWidgets.QAction(self.widget) - self.btn_sort_desc.setObjectName("btn_sort_desc") - icon_btn_sort_desc = QtGui.QIcon() - icon_btn_sort_desc.addPixmap( - QtGui.QPixmap(f"{icon_path}gtk-sort-descending.png")) - self.btn_sort_desc.setIcon(icon_btn_sort_desc) - self.toolBar.addAction(self.btn_sort_desc) - - self.btn_move_up = QtWidgets.QAction(self.widget) - self.btn_move_up.setObjectName("btn_move_up") - icon_btn_move_up = QtGui.QIcon() - icon_btn_move_up.addPixmap( - QtGui.QPixmap(f"{icon_path}up.png")) - self.btn_move_up.setIcon(icon_btn_move_up) - self.toolBar.addAction(self.btn_move_up) - - self.btn_move_down = QtWidgets.QAction(self.widget) - self.btn_move_down.setObjectName("btn_move_down") - icon_btn_move_down = QtGui.QIcon() - icon_btn_move_down.addPixmap( - QtGui.QPixmap(f"{icon_path}down.png")) - self.btn_move_down.setIcon(icon_btn_move_down) - self.toolBar.addAction(self.btn_move_down) - - # self.btn_end_editing = QtWidgets.QAction(self.widget) - # self.btn_end_editing.setObjectName("btn_end_editing") - # icon_btn_save = QtGui.QIcon() - # icon_btn_save.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-apply.png")) - # self.btn_end_editing.setIcon(icon_btn_save) - # self.toolBar.addAction(self.btn_end_editing) - - self.verticalLayout.addLayout(self.horizontalLayout) - self.tableView = QtWidgets.QTableView(self.widget) - self.tableView.setObjectName("tableView") - self.verticalLayout.addWidget(self.tableView) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) - self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.tableView.setAlternatingRowColors(True) - self.tableView.verticalHeader().hide() - self.tableView.resizeColumnsToContents() - self.tableView.resizeRowsToContents() - - self.widget1 = QtWidgets.QWidget(self.splitter_3) - self.widget1.setObjectName("widget1") - self.gridLayout = QtWidgets.QGridLayout(self.widget1) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.splitter_2 = QtWidgets.QSplitter(self.widget1) - self.splitter_2.setOrientation(Qt.Vertical) - self.splitter_2.setObjectName("splitter_2") - self.splitter = QtWidgets.QSplitter(self.splitter_2) - self.splitter.setOrientation(Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.verticalLayoutWidget_3 = QtWidgets.QWidget(self.splitter) - self.verticalLayoutWidget_3.setObjectName("verticalLayoutWidget_3") - self.verticalLayout_canvas_1 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_3) - self.verticalLayout_canvas_1.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_canvas_1.setObjectName("verticalLayout_canvas_1") - - self.canvas_1 = MplCanvas.MplCanvas(width=5, height=4, dpi=100) - self.canvas_1.setObjectName("canvas_1") - - self.toolbar_1 = PamhyrPlotToolbar( - self.canvas_1, self.centralwidget, - items = ["home", "zoom", "save", "iso", "back/forward", "move"] - ) - - self.verticalLayout_canvas_1.addWidget(self.toolbar_1) - self.verticalLayout_canvas_1.addWidget(self.canvas_1) - - self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.splitter) - self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2") - self.verticalLayout_canvas_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2) - self.verticalLayout_canvas_2.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_canvas_2.setObjectName("verticalLayout_canvas_2") - - self.canvas_2 = MplCanvas.MplCanvas(width=5, height=4, dpi=100) - self.canvas_2.setObjectName("canvas_2") - - self.toolbar_2 = PamhyrPlotToolbar( - self.canvas_2, self.centralwidget, - items = ["home", "zoom", "save", "iso", "back/forward", "move"] - ) - - self.verticalLayout_canvas_2.addWidget(self.toolbar_2) - self.verticalLayout_canvas_2.addWidget(self.canvas_2) - self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter_2) - self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") - self.verticalLayout_canvas_3 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) - self.verticalLayout_canvas_3.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_canvas_3.setObjectName("verticalLayout_canvas_3") - - self.canvas_3 = MplCanvas.MplCanvas(width=5, height=4, dpi=100) - self.canvas_3.setObjectName("canvas_3") - - self.toolbar_3 = PamhyrPlotToolbar( - self.canvas_3, self.centralwidget, - items = ["home", "zoom", "save", "iso", "back/forward", "move"] - ) - - self.verticalLayout_canvas_3.addWidget(self.toolbar_3) - self.verticalLayout_canvas_3.addWidget(self.canvas_3) - - self.gridLayout.addWidget(self.splitter_2, 0, 0, 3, 3) - self.verticalSlider = QtWidgets.QSlider(self.widget1) - self.verticalSlider.setMinimumSize(QtCore.QSize(30, 0)) - self.verticalSlider.setOrientation(Qt.Vertical) - self.verticalSlider.setInvertedAppearance(True) - self.verticalSlider.setInvertedControls(False) - self.verticalSlider.setObjectName("verticalSlider") - self.gridLayout.addWidget(self.verticalSlider, 0, 3, 1, 1) - - self.btn_slider_up = QtWidgets.QPushButton(self.widget1) - self.btn_slider_up.setMaximumSize(btn_size) - self.btn_slider_up.setText("") - self.btn_slider_up.setObjectName("btn_slider_up") - icon_btn_slider_up = QtGui.QIcon() - icon_btn_slider_up.addPixmap( - QtGui.QPixmap(f"{icon_path}go-up2.png")) - self.btn_slider_up.setIcon(icon_btn_slider_up) - #self.btn_slider_up.setIconSize(btn_size) - self.gridLayout.addWidget(self.btn_slider_up, 1, 3, 1, 1) - - self.btn_slider_down = QtWidgets.QPushButton(self.widget1) - self.btn_slider_down.setMaximumSize(btn_size) - self.btn_slider_down.setText("") - self.btn_slider_down.setObjectName("btn_slider_down") - icon_btn_slider_down = QtGui.QIcon() - icon_btn_slider_down.addPixmap( - QtGui.QPixmap(f"{icon_path}go-down1.png")) - self.btn_slider_down.setIcon(icon_btn_slider_down) - #self.btn_slider_down.setIconSize(btn_size) - self.gridLayout.addWidget(self.btn_slider_down, 2, 3, 1, 1) - - self.vertical_slider_label = QtWidgets.QLabel(self.widget1) - self.vertical_slider_label.setObjectName("vertical_slider_label") - self.gridLayout.addWidget(self.vertical_slider_label, 3, 2, 2, 1) - self.vertical_slider_label.setAlignment(Qt.AlignRight) - - # self.btn_slider_go_back = QtWidgets.QPushButton(self.widget1) - # self.btn_slider_go_back.setMaximumSize(btn_size) - # self.btn_slider_go_back.setText("") - # self.btn_slider_go_back.setObjectName("btn_slider_go_back") - # icon_btn_slider_go_back = QtGui.QIcon() - # icon_btn_slider_go_back.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-go-back.png")) - # self.btn_slider_go_back.setIcon(icon_btn_slider_go_back) - # #self.btn_slider_go_back.setIconSize(btn_size) - # self.gridLayout.addWidget(self.btn_slider_go_back, 5, 0, 1, 1) - # self.btn_slider_go_back.setEnabled(False) - - # self.btn_slider_go_forward = QtWidgets.QPushButton(self.widget1) - # self.btn_slider_go_forward.setMaximumSize(btn_size) - # self.btn_slider_go_forward.setText("") - # self.btn_slider_go_forward.setObjectName("btn_slider_go_forward") - # icon_btn_slider_go_forward = QtGui.QIcon() - # icon_btn_slider_go_forward.addPixmap( - # QtGui.QPixmap(f"{icon_path}gtk-go-forward.png")) - # self.btn_slider_go_forward.setIcon(icon_btn_slider_go_forward) - # #self.btn_slider_go_forward.setIconSize(btn_size) - # self.gridLayout.addWidget(self.btn_slider_go_forward, 5, 1, 1, 1) - # self.btn_slider_go_forward.setEnabled(False) - - # self.horizontalSlider = QtWidgets.QSlider(self.widget1) - # self.horizontalSlider.setMinimumSize(QtCore.QSize(0, 30)) - # self.horizontalSlider.setOrientation(Qt.Horizontal) - # self.horizontalSlider.setObjectName("horizontalSlider") - # self.gridLayout.addWidget(self.horizontalSlider, 5, 2, 1, 1) - # self.horizontalSlider.setEnabled(False) - - self.horizontalLayout_2.addWidget(self.splitter_3) - MainWindow_reach.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow_reach) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1123, 20)) - self.menubar.setObjectName("menubar") - MainWindow_reach.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow_reach) - self.statusbar.setObjectName("statusbar") - MainWindow_reach.setStatusBar(self.statusbar) - - self.retranslateUi(MainWindow_reach) - QtCore.QMetaObject.connectSlotsByName(MainWindow_reach) - - def retranslateUi(self, MainWindow_reach): - _translate = QtCore.QCoreApplication.translate - self.name_reach = "" - self.mainwindow_title = _translate("MainWindow_reach", "Geometry reach edit") - MainWindow_reach.setWindowTitle(self.mainwindow_title + f"{self.name_reach}") - # self.label_section_order.setText(f"{self.text_section_order}") - self.vertical_slider_value = "" - self.vertical_slider_label.setText(_translate("MainWindow_reach", "Kp = ") + f"{self.vertical_slider_value}") - self.btn_open.setToolTip(_translate("MainWindow_reach", " Import geometry")) - self.btn_add.setToolTip(_translate("MainWindow_reach", " New profile")) - self.btn_delete.setToolTip(_translate("MainWindow_reach", " Delete selected profile")) - self.btn_edit.setToolTip(_translate("MainWindow_reach", " Editer selected profile")) - - self.btn_sort_asc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre croissant des Pk")) - self.btn_sort_desc.setToolTip(_translate("MainWindow_reach", " Trier les profils par ordre décroissant des Pk")) - self.btn_move_up.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le " - "profil sélectionné vers le haut)")) - self.btn_move_down.setToolTip(_translate("MainWindow_reach", " Changer l'ordre des profils (en décalant le " - "profil sélectionné vers le bas)")) - # self.btn_end_editing.setToolTip(_translate("MainWindow_reach", " Terminer l'édition")) - self.tableView_header = [_translate("MainWindow_reach", "Name"), - _translate("MainWindow_reach", "Kp (m)"), - _translate("MainWindow_reach", "Type")] - - self.toolbar_1._actions["isometric_view"].setShortcut(_translate("MainWindow_reach", "Alt+Z")) - self.toolbar_1._actions["isometric_view"].setToolTip(_translate("MainWindow_reach", "Vue isométrique (Alt+Z)")) - - self.toolbar_2._actions["isometric_view"].setShortcut(_translate("MainWindow_reach", "Alt+E")) - self.toolbar_2._actions["isometric_view"].setToolTip(_translate("MainWindow_reach", "Vue isométrique (Alt+E)")) - - self.toolbar_3._actions["isometric_view"].setShortcut(_translate("MainWindow_reach", "Alt+R")) - self.toolbar_3._actions["isometric_view"].setToolTip(_translate("MainWindow_reach", "Vue isométrique (Alt+R)")) - - self.toolbar_1._actions["non_isometric_view"].setShortcut("Alt+S") - self.toolbar_1._actions["non_isometric_view"].setToolTip( - _translate("MainWindow_reach", "Vue globale automatique (Alt+S)")) - - self.toolbar_2._actions["non_isometric_view"].setShortcut("Alt+D") - self.toolbar_2._actions["non_isometric_view"].setToolTip( - _translate("MainWindow_reach", "Vue globale automatique (Alt+D)")) - - self.toolbar_3._actions["non_isometric_view"].setShortcut("Alt+F") - self.toolbar_3._actions["non_isometric_view"].setToolTip( - _translate("MainWindow_reach", "Vue globale automatique (Alt+F)")) diff --git a/src/View/InitialConditions/DialogDischarge.py b/src/View/InitialConditions/DialogDischarge.py index f8e7e32a..7be75baf 100644 --- a/src/View/InitialConditions/DialogDischarge.py +++ b/src/View/InitialConditions/DialogDischarge.py @@ -16,8 +16,7 @@ # -*- coding: utf-8 -*- -from View.ASubWindow import ASubWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtGui import ( QKeySequence, @@ -32,10 +31,15 @@ from PyQt5.QtWidgets import ( QDoubleSpinBox, ) -class DischargeDialog(ASubWindow, ListedSubWindow): +class DischargeDialog(PamhyrDialog): + _pamhyr_ui = "InitialConditions_Dialog_Generator_Discharge" + _pamhyr_name = "Discharge" + def __init__(self, title="Discharge", parent=None): super(DischargeDialog, self).__init__( - name=title, ui="InitialConditions_Dialog_Generator_Discharge", parent=parent + title = self._pamhyr_name, + options = [], + parent = parent ) self.value = None diff --git a/src/View/InitialConditions/DialogHeight.py b/src/View/InitialConditions/DialogHeight.py index 18ad67c8..8a2a3c12 100644 --- a/src/View/InitialConditions/DialogHeight.py +++ b/src/View/InitialConditions/DialogHeight.py @@ -16,8 +16,7 @@ # -*- coding: utf-8 -*- -from View.ASubWindow import ASubWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtGui import ( QKeySequence, @@ -32,10 +31,15 @@ from PyQt5.QtWidgets import ( QDoubleSpinBox, ) -class HeightDialog(ASubWindow, ListedSubWindow): - def __init__(self, title="Height", parent=None): +class HeightDialog(PamhyrDialog): + _pamhyr_ui = "InitialConditions_Dialog_Generator_Height" + _pamhyr_name = "Height" + + def __init__(self, parent=None): super(HeightDialog, self).__init__( - name=title, ui="InitialConditions_Dialog_Generator_Height", parent=parent + title = self._pamhyr_name, + options = [], + parent = parent ) self.value = None diff --git a/src/View/InitialConditions/PlotDKP.py b/src/View/InitialConditions/PlotDKP.py index 58a83219..d604179a 100644 --- a/src/View/InitialConditions/PlotDKP.py +++ b/src/View/InitialConditions/PlotDKP.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -25,14 +25,7 @@ from PyQt5.QtCore import ( _translate = QCoreApplication.translate -class PlotDKP(APlot): - def __init__(self, canvas=None, data=None, toolbar=None): - super(PlotDKP, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar - ) - +class PlotDKP(PamhyrPlot): @timer def draw(self, highlight=None): self.canvas.axes.cla() @@ -42,11 +35,11 @@ class PlotDKP(APlot): return self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Elevation (m)"), + self._trad["elevation"], color='green', fontsize=11 ) self.canvas.axes.set_xlabel( - _translate("MainWindow_reach", "KP (m)"), + self._trad["kp"], color='green', fontsize=11 ) diff --git a/src/View/InitialConditions/PlotDischarge.py b/src/View/InitialConditions/PlotDischarge.py index 1fe05def..1ad2a266 100644 --- a/src/View/InitialConditions/PlotDischarge.py +++ b/src/View/InitialConditions/PlotDischarge.py @@ -17,22 +17,9 @@ # -*- coding: utf-8 -*- from tools import timer -from View.Plot.APlot import APlot - -from PyQt5.QtCore import ( - QCoreApplication -) - -_translate = QCoreApplication.translate - -class PlotDischarge(APlot): - def __init__(self, canvas=None, data=None, toolbar=None): - super(PlotDischarge, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar - ) +from View.Tools.PamhyrPlot import PamhyrPlot +class PlotDischarge(PamhyrPlot): @timer def draw(self, highlight=None): self.canvas.axes.cla() @@ -42,11 +29,11 @@ class PlotDischarge(APlot): return self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Discharge (m^3/s)"), + self._trad["discharge"], color='green', fontsize=11 ) self.canvas.axes.set_xlabel( - _translate("MainWindow_reach", "KP (m)"), + self._trad["kp"], color='green', fontsize=11 ) diff --git a/src/View/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index 5e704ba9..f7ad1020 100644 --- a/src/View/InitialConditions/Table.py +++ b/src/View/InitialConditions/Table.py @@ -33,14 +33,14 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from View.InitialConditions.UndoCommand import ( SetCommand, AddCommand, DelCommand, SortCommand, MoveCommand, PasteCommand, DuplicateCommand, GenerateCommand, ) -from View.InitialConditions.translate import * - logger = logging.getLogger() _translate = QCoreApplication.translate @@ -87,29 +87,13 @@ class ComboBoxDelegate(QItemDelegate): self.commitData.emit(self.sender()) -class TableModel(QAbstractTableModel): - def __init__(self, river=None, reach=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._river = river +class InitialConditionTableModel(PamhyrTableModel): + def __init__(self, reach=None, **kwargs): self._reach = reach - self._undo = undo - self._ics = self._river.initial_conditions.get(reach) + super(InitialConditionTableModel, self).__init__(**kwargs) - def flags(self, index): - column = index.column() - - options = Qt.ItemIsSelectable - if self._headers[column] != "speed": - options |= Qt.ItemIsEnabled | Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._ics) - - def columnCount(self, parent): - return len(self._headers) + def _setup_lst(self): + self._lst = self._data.river.initial_conditions.get(self._reach) def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: @@ -119,16 +103,10 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] not in ["name", "comment"]: - v = self._ics.get(row)[self._headers[column]] + v = self._lst.get(row)[self._headers[column]] return f"{v:.4f}" else: - return self._ics.get(row)[self._headers[column]] - - return QVariant() - - def headerData(self, section, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[section]] + return self._lst.get(row)[self._headers[column]] return QVariant() @@ -143,7 +121,7 @@ class TableModel(QAbstractTableModel): if self._headers[column] is not None: self._undo.push( SetCommand( - self._ics, row, self._headers[column], value + self._lst, row, self._headers[column], value ) ) except Exception as e: @@ -158,7 +136,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._ics, row + self._lst, row ) ) @@ -170,7 +148,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._ics, rows + self._lst, rows ) ) @@ -182,7 +160,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SortCommand( - self._ics, False + self._lst, False ) ) @@ -199,7 +177,7 @@ class TableModel(QAbstractTableModel): self._undo.push( MoveCommand( - self._ics, "up", row + self._lst, "up", row ) ) @@ -207,7 +185,7 @@ class TableModel(QAbstractTableModel): self.layoutChanged.emit() def move_down(self, index, parent=QModelIndex()): - if row > len(self._ics): + if row > len(self._lst): return target = row @@ -216,7 +194,7 @@ class TableModel(QAbstractTableModel): self._undo.push( MoveCommand( - self._ics, "down", row + self._lst, "down", row ) ) @@ -234,7 +212,7 @@ class TableModel(QAbstractTableModel): def generate(self, generator, param): self._undo.push( GenerateCommand( - self._ics, generator, param + self._lst, generator, param ) ) self.layoutChanged.emit() diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 00e7b121..90cf8e08 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -46,14 +45,14 @@ from View.InitialConditions.UndoCommand import ( DuplicateCommand, ) -from View.InitialConditions.Table import TableModel, ComboBoxDelegate +from View.InitialConditions.Table import InitialConditionTableModel, ComboBoxDelegate -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.InitialConditions.PlotDKP import PlotDKP from View.InitialConditions.PlotDischarge import PlotDischarge -from View.InitialConditions.translate import table_headers, retranslate +from View.InitialConditions.translate import ICTranslate from View.InitialConditions.DialogHeight import HeightDialog from View.InitialConditions.DialogDischarge import DischargeDialog @@ -61,67 +60,62 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class InitialConditionsWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Initial condition", - study=None, parent=None): - self._study = study - self._reach = study.river.current_reach() - self._ics = self._study.river.initial_conditions.get(self._reach) +class InitialConditionsWindow(PamhyrWindow): + _pamhyr_ui = "InitialConditions" + _pamhyr_name = "Initial condition" - self.setup_title(title) + def __init__(self, study=None, config=None, reach=None, parent=None): + if reach is not None: + self._reach = reach + else: + self._reach = study.river.current_reach() - super(InitialConditionsWindow, self).__init__( - name=self._title, ui="InitialConditions", parent=parent + name = ( + self._pamhyr_name + + " - " + study.name + + " - " + self._reach.name ) - self.setup_sc() + super(InitialConditionsWindow, self).__init__( + title = name, + study = study, + config = config, + trad = ICTranslate(), + parent = parent + ) + + self._ics = study.river.initial_conditions.get(self._reach) + self.setup_table() - self.setup_graph() + self.setup_plot() self.setup_connections() self.ui.setWindowTitle(self._title) - def setup_title(self, title): - self._title = ( - title + " - " - + self._study.name + " - " - + self._reach.name - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() - table = self.find(QTableView, f"tableView") - self._table = TableModel( - river = self._study.river, - reach = self._reach, - undo = self._undo_stack, - ) - table.setModel(self._table) - self._delegate_kp = ComboBoxDelegate( reach = self._reach, - parent=self + parent = self ) - table.setItemDelegateForColumn( - list(table_headers).index("kp"), - self._delegate_kp + self._table = InitialConditionTableModel( + reach = self._reach, + table_view = table, + table_headers = self._trad.get_dict("table_headers"), + editable_headers = ["kp", "discharge", "elevation", "height"], + delegates = {"kp": self._delegate_kp}, + data = self._study, + undo = self._undo_stack, + trad = self._trad ) + table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) - def setup_graph(self): + def setup_plot(self): self.canvas_1 = MplCanvas(width=5, height=4, dpi=100) self.canvas_1.setObjectName("canvas_1") self.toolbar_1 = PamhyrPlotToolbar( @@ -134,7 +128,9 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow): self.plot_1 = PlotDKP( canvas = self.canvas_1, data = self._ics, + trad = self._trad, toolbar = self.toolbar_1, + parent = self ) self.plot_1.draw() @@ -150,7 +146,9 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow): self.plot_2 = PlotDischarge( canvas = self.canvas_2, data = self._ics, + trad = self._trad, toolbar = self.toolbar_2, + parent = self ) self.plot_2.draw() @@ -167,11 +165,6 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow): self.generate_discharge ) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - self._table.dataChanged.connect(self._update_plot) def index_selected_row(self): @@ -227,19 +220,19 @@ class InitialConditionsWindow(ASubMainWindow, ListedSubWindow): self._table.move_down(row) self._update_plot() - def copy(self): + def _copy(self): logger.info("TODO: copy") self._update_plot() - def paste(self): + def _paste(self): logger.info("TODO: paste") self._update_plot() - def undo(self): + def _undo(self): self._table.undo() self._update_plot() - def redo(self): + def _redo(self): self._table.redo() self._update_plot() diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py index 54f194e9..f65306cb 100644 --- a/src/View/InitialConditions/translate.py +++ b/src/View/InitialConditions/translate.py @@ -18,23 +18,24 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers = { - # "name": _translate("LateralContribution", "Name"), - "kp": _translate("LateralContribution", "KP (m)"), - # "speed": _translate("LateralContribution", "Speed (m/s)"), - "discharge": _translate("LateralContribution", "Discharge (m³/s)"), - "elevation": _translate("LateralContribution", "Elevation (m)"), - "height": _translate("LateralContribution", "Height (m)"), - # "comment": _translate("LateralContribution", "Comment"), -} +class ICTranslate(PamhyrTranslate): + def __init__(self): + super(ICTranslate, self).__init__() -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") + self._dict["elevation"] = _translate("InitialCondition", "Elevation (m)") + self._dict["discharge"] = _translate("InitialCondition", "Discharge (m³/s)") + self._dict["kp"] = _translate("InitialCondition", "KP (m)") + + self._sub_dict["table_headers"] = { + # "name": _translate("InitialCondition", "Name"), + "kp": _translate("InitialCondition", "KP (m)"), + # "speed": _translate("InitialCondition", "Speed (m/s)"), + "discharge": _translate("InitialCondition", "Discharge (m³/s)"), + "elevation": _translate("InitialCondition", "Elevation (m)"), + "height": _translate("InitialCondition", "Height (m)"), + # "comment": _translate("InitialCondition", "Comment"), + } diff --git a/src/View/LateralContribution/Edit/Plot.py b/src/View/LateralContribution/Edit/Plot.py index 5843ee56..ea1c0454 100644 --- a/src/View/LateralContribution/Edit/Plot.py +++ b/src/View/LateralContribution/Edit/Plot.py @@ -19,7 +19,7 @@ from datetime import datetime from tools import timer, trace -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -29,15 +29,19 @@ from View.LateralContribution.Edit.translate import * _translate = QCoreApplication.translate -class Plot(APlot): - def __init__(self, canvas=None, data=None, - mode = "time", toolbar=None): +class Plot(PamhyrPlot): + def __init__(self, mode = "time", data=None, + canvas=None, trad=None, toolbar=None, + parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) + self._table_headers = self._trad.get_dict("table_headers") self._mode = mode def custom_ticks(self): @@ -105,10 +109,10 @@ class Plot(APlot): # Plot label header = self.data.header self.canvas.axes.set_xlabel( - table_headers[header[0]], color='black', fontsize=10 + self._table_headers[header[0]], color='black', fontsize=10 ) self.canvas.axes.set_ylabel( - table_headers[header[1]], color='black', fontsize=10 + self._table_headers[header[1]], color='black', fontsize=10 ) self.canvas.axes.autoscale_view(True, True, True) diff --git a/src/View/LateralContribution/Edit/Table.py b/src/View/LateralContribution/Edit/Table.py index 2fff0895..8b43602d 100644 --- a/src/View/LateralContribution/Edit/Table.py +++ b/src/View/LateralContribution/Edit/Table.py @@ -22,8 +22,6 @@ from datetime import date, time, datetime, timedelta from tools import trace, timer -from View.ASubWindow import ASubMainWindow, AWidget -from View.ListedSubWindow import ListedSubWindow from View.Tools.PamhyrTable import PamhyrTableModel from PyQt5.QtCore import ( diff --git a/src/View/LateralContribution/Edit/Window.py b/src/View/LateralContribution/Edit/Window.py index 53576a6b..55f1307f 100644 --- a/src/View/LateralContribution/Edit/Window.py +++ b/src/View/LateralContribution/Edit/Window.py @@ -18,8 +18,7 @@ from tools import timer, trace -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate from PyQt5.QtGui import ( @@ -37,67 +36,60 @@ from PyQt5.QtWidgets import ( QHeaderView, ) -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar -from View.LateralContribution.translate import long_types -from View.LateralContribution.Edit.translate import table_headers, retranslate +from View.LateralContribution.Edit.translate import LCETranslate from View.LateralContribution.Edit.Table import TableModel from View.LateralContribution.Edit.Plot import Plot _translate = QCoreApplication.translate -class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Edit lateral contribution", - data=None, study=None, parent=None): - self._data = data - self._study = study - self._title = title +class EditLateralContributionWindow(PamhyrWindow): + _pamhyr_ui = "EditLateralContribution" + _pamhyr_name = "Edit lateral contribution" - self.compute_title() + def __init__(self, data=None, + study=None, config=None, + parent=None): + self._data = data + trad = LCETranslate() + + name = self._pamhyr_name + if self._data is not None: + edge_name = (self._data.edge.name if self._data.edge is not None + else _translate("LateralContribution", "Not associate")) + name = ( + _translate("Edit lateral contribution", self._pamhyr_name) + + f" - {study.name} " + + f" - {self._data.name} ({self._data.id}) " + + f"({trad.get_dict('long_types')[self._data.lctype]} - {edge_name})" + ) super(EditLateralContributionWindow, self).__init__( - name=title, ui="EditLateralContribution", parent=parent + title = name, + study = study, + config = config, + trad = trad, + parent = parent ) - self.ui.setWindowTitle(self._title) - - self.setup_sc() self.setup_table() self.setup_plot() self.setup_connections() - def compute_title(self): - if self._data is not None: - edge_name = (self._data.edge.name if self._data.edge is not None - else _translate("LateralContribution", "Not associate")) - self._title = ( - _translate("Edit lateral contribution", self._title) + - f" - {self._study.name} " + - f" - {self._data.name} ({self._data.id}) " + - f"({long_types[self._data.lctype]} - {edge_name})" - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() - headers = {} - for h in self._data.header: - headers[h] = table_headers[h] - self._delegate_time = PamhyrExTimeDelegate( data = self._data, mode = self._study.time_system, parent = self ) + headers = {} + table_headers = self._trad.get_dict("table_headers") + for h in self._data.header: + headers[h] = table_headers[h] + table = self.find(QTableView, "tableView") self._table = TableModel( table_view = table, @@ -108,6 +100,7 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): }, data = self._data, undo = self._undo_stack, + trad = self._trad, opt_data = self._study.time_system ) @@ -130,20 +123,16 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): data = self._data, mode = self._study.time_system, toolbar = self.toolbar, + trad = self._trad, + parent = self ) self.plot.draw() - def setup_connections(self): self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - self._table.dataChanged.connect(self.update) def update(self): @@ -199,8 +188,7 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): self._table.move_down(row) self.plot.update() - - def copy(self): + def _copy(self): rows = self.index_selected_rows() table = [] @@ -212,7 +200,7 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): self.copyTableIntoClipboard(table) - def paste(self): + def _paste(self): header, data = self.parseClipboardTable() if len(data) == 0: @@ -226,10 +214,10 @@ class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): self._table.paste(row, header, data) self.plot.update() - def undo(self): + def _undo(self): self._table.undo() self.plot.update() - def redo(self): + def _redo(self): self._table.redo() self.plot.update() diff --git a/src/View/LateralContribution/Edit/translate.py b/src/View/LateralContribution/Edit/translate.py index f04534e0..247cdc4e 100644 --- a/src/View/LateralContribution/Edit/translate.py +++ b/src/View/LateralContribution/Edit/translate.py @@ -18,21 +18,20 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate +from View.LateralContribution.translate import LCTranslate + _translate = QCoreApplication.translate -table_headers = { - "x": _translate("LateralContribution", "X"), - "y": _translate("LateralContribution", "Y"), - "time": _translate("LateralContribution", "Time"), - "date": _translate("LateralContribution", "Date"), - "discharge": _translate("LateralContribution", "Discharge (m³/s)"), - "z": _translate("LateralContribution", "Z (m)") -} +class LCETranslate(LCTranslate): + def __init__(self): + super(LCETranslate, self).__init__() -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)") + self._sub_dict["table_headers"] = { + "x": _translate("LateralContribution", "X"), + "y": _translate("LateralContribution", "Y"), + "time": _translate("LateralContribution", "Time"), + "date": _translate("LateralContribution", "Date"), + "discharge": _translate("LateralContribution", "Discharge (m³/s)"), + "z": _translate("LateralContribution", "Z (m)") + } diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 2a3f4196..3c5f5cb7 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -46,22 +46,24 @@ from Model.LateralContribution.LateralContributionTypes import ( ) from View.Tools.PamhyrTable import PamhyrTableModel -from View.LateralContribution.translate import * +from View.LateralContribution.translate import LC_types logger = logging.getLogger() _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, mode="type", tab="", parent=None): + def __init__(self, data=None, mode="type", tab="", trad = None, parent=None): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._mode = mode self._tab = tab + self._trad = trad def createEditor(self, parent, option, index): self.editor = QComboBox(parent) + long_types = self._trad.get_dict("long_types") if self._mode == "type": lst = list( @@ -108,6 +110,7 @@ class TableModel(PamhyrTableModel): def _setup_lst(self): self._lst = self._data.lateral_contribution self._tab = self._opt_data + self._long_types = self._trad.get_dict("long_types") def rowCount(self, parent): return self._lst.len(self._tab) @@ -123,7 +126,7 @@ class TableModel(PamhyrTableModel): return self._lst.get(self._tab, row).name elif self._headers[column] == "type": t = self._lst.get(self._tab, row).lctype - return long_types[t] + return self._long_types[t] elif self._headers[column] == "edge": n = self._lst.get(self._tab, row).edge if n is None: @@ -151,7 +154,7 @@ class TableModel(PamhyrTableModel): ) ) 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 self._long_types.items() if v == value) self._undo.push( SetTypeCommand( self._lst, self._tab, row, LC_types[key] diff --git a/src/View/LateralContribution/Window.py b/src/View/LateralContribution/Window.py index 31f82906..467f2a4b 100644 --- a/src/View/LateralContribution/Window.py +++ b/src/View/LateralContribution/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -54,46 +53,37 @@ from View.LateralContribution.Table import ( TableModel, ComboBoxDelegate ) -from View.Plot.MplCanvas import MplCanvas +from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Geometry.PlotXY import PlotXY from View.LateralContribution.translate import ( - long_types, table_headers, LC_types, - retranslate, + LC_types, LCTranslate, ) from View.LateralContribution.Edit.Window import EditLateralContributionWindow -_translate = QCoreApplication.translate - logger = logging.getLogger() -class LateralContributionWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Lateral contribution", study=None, parent=None): - self._title = title + " - " + study.name +class LateralContributionWindow(PamhyrWindow): + _pamhyr_ui = "LateralContributions" + _pamhyr_name = "Lateral contribution" + + def __init__(self, study=None, config=None, parent=None): + name = self._pamhyr_name + " - " + study.name super(LateralContributionWindow, self).__init__( - name=title, ui="LateralContributions", parent=parent + title = name, + study = study, + trad = LCTranslate(), + config = config, + parent=parent ) - self._study = study self._lcs = self._study.river.lateral_contribution - self.setup_sc() self.setup_table() self.setup_graph() self.setup_connections() - self.ui.setWindowTitle(self._title) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() self._table = {} for t in ["liquid", "solid", "suspenssion"]: @@ -101,19 +91,21 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow): data = self._study.river, mode = "type", tab = t, + trad = self._trad, parent=self ) self._delegate_edge = ComboBoxDelegate( data = self._study.river, mode = "edge", tab = t, + trad = self._trad, parent=self ) table = self.find(QTableView, f"tableView_{t}") self._table[t] = TableModel( table_view = table, - table_headers = table_headers, + table_headers = self._trad.get_dict("table_headers"), editable_headers = True, delegates = { "type": self._delegate_type, @@ -121,6 +113,7 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow): }, data = self._study.river, undo = self._undo_stack, + trad = self._trad, opt_data = t, ) table.setModel(self._table[t]) @@ -146,11 +139,6 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) self.find(QAction, "action_sort").triggered.connect(self.sort) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - for t in ["liquid", "solid", "suspenssion"]: table = self.find(QTableView, f"tableView_{t}") table.selectionModel()\ @@ -247,20 +235,20 @@ class LateralContributionWindow(ASubMainWindow, ListedSubWindow): self._table[tab].move_down(row) self._set_current_reach() - def copy(self): + def _copy(self): logger.info("TODO: copy") self._set_current_reach() - def paste(self): + def _paste(self): logger.info("TODO: paste") self._set_current_reach() - def undo(self): + def _undo(self): tab = self.current_tab() self._table[tab].undo() self._set_current_reach() - def redo(self): + def _redo(self): tab = self.current_tab() self._table[tab].redo() self._set_current_reach() diff --git a/src/View/LateralContribution/translate.py b/src/View/LateralContribution/translate.py index cf430824..aaf44798 100644 --- a/src/View/LateralContribution/translate.py +++ b/src/View/LateralContribution/translate.py @@ -18,27 +18,14 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + from Model.LateralContribution.LateralContributionTypes import ( NotDefined, LateralContrib, Rain, Evaporation, ) _translate = QCoreApplication.translate -long_types = { - "ND": _translate("LateralContribution", "Not defined"), - "LC": _translate("LateralContribution", "Lateral contribution"), - "RA": _translate("LateralContribution", "Rain"), - "EV": _translate("LateralContribution", "Evaporation"), -} - -table_headers = { - "name": _translate("LateralContribution", "Name"), - "type": _translate("LateralContribution", "Type"), - "edge": _translate("LateralContribution", "Reach"), - "begin_kp": _translate("LateralContribution", "Begin kp (m)"), - "end_kp": _translate("LateralContribution", "End kp (m)") -} - LC_types = { "ND": NotDefined, "LC": LateralContrib, @@ -46,14 +33,21 @@ LC_types = { "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)") +class LCTranslate(PamhyrTranslate): + def __init__(self): + super(LCTranslate, self).__init__() - 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") + self._sub_dict["long_types"] = { + "ND": _translate("LateralContribution", "Not defined"), + "LC": _translate("LateralContribution", "Lateral contribution"), + "RA": _translate("LateralContribution", "Rain"), + "EV": _translate("LateralContribution", "Evaporation"), + } + + self._sub_dict["table_headers"] = { + "name": _translate("LateralContribution", "Name"), + "type": _translate("LateralContribution", "Type"), + "edge": _translate("LateralContribution", "Reach"), + "begin_kp": _translate("LateralContribution", "Begin kp (m)"), + "end_kp": _translate("LateralContribution", "End kp (m)") + } diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index d5a9d78d..3c74158d 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -36,8 +36,8 @@ from PyQt5.QtWidgets import ( ) from PyQt5.uic import loadUi -from View.ASubWindow import WindowToolKit -from View.ListedSubWindow import ListedSubWindow +from View.Tools.ASubWindow import WindowToolKit +from View.Tools.ListedSubWindow import ListedSubWindow from View.DummyWindow import DummyWindow from View.Configure.Window import ConfigureWindow @@ -56,6 +56,7 @@ from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow from View.CheckList.Window import CheckListWindow from View.Results.Window import ResultsWindow +from View.Doc.Window import DocWindow from View.Debug.Window import ReplWindow from Model.Study import Study @@ -108,9 +109,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.conf = conf # Model - self.model = None + self._study = None # Results + self._last_solver = None self._last_results = None # UI @@ -133,8 +135,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def set_title(self): title = "(dbg) " if self.conf.debug else "" - if self.model is not None: - title += f"Pamhyr2 - {self.model.name}" + if self._study is not None: + title += f"Pamhyr2 - {self._study.name}" self.setWindowTitle(title) else: title += "Pamhyr2" @@ -150,6 +152,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ + logger.debug(f"Set {action} to {enable}") self.findChild(QAction, action).setEnabled(enable) def setup_sc(self): @@ -181,7 +184,12 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_sediment_layers": self.open_sediment_layers, "action_menu_edit_reach_sediment_layers": self.open_reach_sediment_layers, "action_menu_close": self.close_model, + "action_menu_results_last": self.open_last_results, ## Help + "action_menu_pamhyr_users_pdf": lambda: self.open_doc_user(ext="pdf"), + "action_menu_pamhyr_developers_pdf": lambda: self.open_doc_dev(ext="pdf"), + "action_menu_pamhyr_users_html": lambda: self.open_doc_user(ext="html"), + "action_menu_pamhyr_developers_html": lambda: self.open_doc_dev(ext="html"), "action_menu_about": self.open_about, # ToolBar action "action_toolBar_quit": self.close, @@ -217,7 +225,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): super(ApplicationWindow, self).changeEvent(event) def close(self): - if self.model is not None and not self.model.is_saved: + if self._study is not None and not self._study.is_saved: self._close_question = True if self.dialog_close(): # PAMHYR is close correctly (no crash) @@ -234,7 +242,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def closeEvent(self, event): if not self._close_question: - if self.model is not None and not self.model.is_saved: + if self._study is not None and not self._study.is_saved: if self.dialog_close(cancel = False): # PAMHYR is close correctly (no crash) self.conf.set_close_correctly() @@ -292,16 +300,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ######### def get_model(self): - return self.model + return self._study def set_model(self, model): - self.model = model + self._study = model self.update_enable_action() - self.conf.set_last_study(self.model.filename) + self.conf.set_last_study(self._study.filename) self.set_title() def close_model(self): - self.model = None + self._study = None self.update_enable_action() self.conf.set_close_correctly() self.set_title() @@ -315,7 +323,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - no_model = self.model is None + no_model = self._study is None for action in no_model_action: self.enable_actions(action, no_model) @@ -326,9 +334,12 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): for action in model_action: self.enable_actions(action, not no_model) - def set_results(self, results): + def set_results(self, solver, results): + self._last_solver = solver self._last_results = results + self.enable_actions("action_menu_results_last", True) + ############ # FEATURES # ############ @@ -343,7 +354,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Nothing """ self.set_model(Study.open(filename)) - logger.info(f"Open Study - {self.model.name}") + logger.info(f"Open Study - {self._study.name}") self.set_title() def save_study(self): @@ -355,22 +366,22 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if self.model.filename is None or self.model.filename == "": + if self._study.filename is None or self._study.filename == "": file_name, _ = QFileDialog.getSaveFileName( self, "Save File", "", "Pamhyr(*.pamhyr)" ) if file_name.rsplit(".", 1)[-1] == "pamhyr": - self.model.filename = file_name + self._study.filename = file_name else: - self.model.filename = file_name + ".pamhyr" + self._study.filename = file_name + ".pamhyr" - if self.model.is_saved: + if self._study.is_saved: return logger.info("Save...") - self.model.save() + self._study.save() def save_as_study(self): """Save current study as new file @@ -387,11 +398,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) if file_name[-4:] == ".pamhyr": - self.model.filename = file_name + self._study.filename = file_name else: - self.model.filename = file_name + ".pamhyr" + self._study.filename = file_name + ".pamhyr" - self.model.save() + self._study.save() ################## # MSG AND DIALOG # @@ -457,7 +468,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - self.config = ConfigureWindow(conf=self.conf, parent=self) + self.config = ConfigureWindow(config=self.conf, parent=self) self.config.show() def open_about(self): @@ -477,7 +488,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if self.model is None: + if self._study is None: dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.FileMode.ExistingFile) dialog.setDefaultSuffix(".pamhyr") @@ -495,7 +506,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if self.model is None: + if self._study is None: self.new_study = NewStudyWindow(parent=self) self.new_study.show() @@ -505,8 +516,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if not self.model is None: - self.new_study = NewStudyWindow(study=self.model, parent=self) + if not self._study is None: + self.new_study = NewStudyWindow(study=self._study, parent=self) self.new_study.show() def open_network(self): @@ -515,9 +526,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if self.model is not None: + if self._study is not None: if not self.sub_win_exists("River network"): - self.network = NetworkWindow(model=self.model, parent=self) + self.network = NetworkWindow(study=self._study, parent=self) self.network.show() else: self.network.activateWindow() @@ -528,14 +539,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): Returns: Nothing """ - if (self.model is not None and self.model.river.has_current_reach()): + if (self._study is not None and self._study.river.has_current_reach()): geometry = self.sub_win_filter_first( "Geometry", - contain = [self.model.river.current_reach().name] + contain = [self._study.river.current_reach().name] ) if geometry is None: - geometry = GeometryWindow(model=self.model, parent=self) + geometry = GeometryWindow(study=self._study, config=self.conf, parent=self) geometry.show() else: geometry.activateWindow() @@ -549,7 +560,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) if bound is None: - bound = BoundaryConditionWindow(study = self.model, parent = self) + bound = BoundaryConditionWindow(study = self._study, parent = self) bound.show() else: bound.activateWindow() @@ -561,7 +572,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) if lateral is None: - lateral = LateralContributionWindow(study = self.model, parent = self) + lateral = LateralContributionWindow(study = self._study, parent = self) lateral.show() else: lateral.activateWindow() @@ -574,7 +585,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if strick is None: strick = StricklersWindow( - study = self.model, + study = self._study, config = self.conf, parent = self ) @@ -583,17 +594,17 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): strick.activateWindow() def open_frictions(self): - if (self.model is not None and - self.model.river.has_current_reach()): + if (self._study is not None and + self._study.river.has_current_reach()): frictions = self.sub_win_filter_first( "Frictions", - contain = [self.model.river.current_reach().name] + contain = [self._study.river.current_reach().name] ) if frictions is None: frictions = FrictionsWindow( - study = self.model, + study = self._study, parent = self ) frictions.show() @@ -603,15 +614,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.msg_select_reach() def open_initial_conditions(self): - if self.model.river.has_current_reach(): + if self._study.river.has_current_reach(): initial = self.sub_win_filter_first( "Initial condition", - contain = [self.model.river.current_reach().name] + contain = [self._study.river.current_reach().name] ) if initial is None: initial = InitialConditionsWindow( - study = self.model, + study = self._study, + config = self.conf, parent = self ) initial.show() @@ -628,7 +640,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if params is None: params = SolverParametersWindow( - study = self.model, + study = self._study, parent = self ) params.show() @@ -637,31 +649,31 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def open_sediment_layers(self): sl = SedimentLayersWindow( - study = self.model, + study = self._study, parent = self ) sl.show() def open_reach_sediment_layers(self): sl = ReachSedimentLayersWindow( - study = self.model, + study = self._study, parent = self ) sl.show() def run_solver(self): - if self.model is None: + if self._study is None: return run = SelectSolverWindow( - study = self.model, + study = self._study, config = self.conf, parent = self ) if run.exec(): solver = run.solver check = CheckListWindow( - study = self.model, + study = self._study, config = self.conf, solver = solver, parent = self @@ -670,7 +682,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def solver_log(self, solver): sol = SolverLogWindow( - study = self.model, + study = self._study, config = self.conf, solver = solver, parent = self @@ -694,7 +706,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if res is None: res = ResultsWindow( - study = self.model, + study = self._study, solver = solver, results = results, parent = self @@ -703,24 +715,44 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): else: res.activateWindow() + def open_last_results(self): + if self._last_solver is None or self._last_results is None: + return + + self.open_solver_results(self._last_solver, self._last_results) + + def open_doc(self, filename): + doc = DocWindow( + filename = filename, + parent = self + ) + doc.show() + + + def open_doc_user(self, ext="pdf"): + self.open_doc(f"Pamhyr2-users.{ext}") + + def open_doc_dev(self, ext="pdf"): + self.open_doc(f"Pamhyr2-dev.{ext}") + ######### # DEBUG # ######### def open_debug(self): repl = ReplWindow( - study = self.model, + study = self._study, config = self.conf, parent = self ) repl.show() def open_sqlite(self): - if self.model is None: + if self._study is None: logger.debug("No study open for sql debuging...") return - file = self.model.filename + file = self._study.filename _ = subprocess.Popen( f"sqlitebrowser {file}", shell=True diff --git a/src/View/Network/Table.py b/src/View/Network/Table.py index 733a910d..da74d813 100644 --- a/src/View/Network/Table.py +++ b/src/View/Network/Table.py @@ -22,7 +22,6 @@ import traceback from Model.Network.Node import Node from Model.Network.Edge import Edge from Model.Network.Graph import Graph -from View.ASubWindow import ASubWindow from View.Network.GraphWidget import GraphWidget from View.Network.UndoCommand import * from View.Tools.PamhyrTable import PamhyrTableModel diff --git a/src/View/Network/Window.py b/src/View/Network/Window.py index 95e575ea..f0458596 100644 --- a/src/View/Network/Window.py +++ b/src/View/Network/Window.py @@ -16,6 +16,8 @@ # -*- coding: utf-8 -*- +import logging + from PyQt5.QtCore import QCoreApplication from PyQt5.QtGui import ( @@ -36,55 +38,47 @@ from PyQt5.QtWidgets import ( from Model.River import RiverNode, RiverReach, River -from View.ASubWindow import ASubMainWindow +from View.Tools.PamhyrWindow import PamhyrWindow from View.Network.GraphWidget import GraphWidget from View.Network.UndoCommand import * -from View.Network.translate import ( - table_headers_node, table_headers_edge, - retranslate, -) +from View.Network.translate import NetworkTranslate from View.Network.Table import ( ComboBoxDelegate, NodeTableModel, EdgeTableModel, ) +logger = logging.getLogger() + _translate = QCoreApplication.translate -class NetworkWindow(ASubMainWindow): - def __init__(self, model=None, title="River network", parent=None): - self._title = title - self._model = model - self._graph = self._model.river - - self.setup_title() +class NetworkWindow(PamhyrWindow): + _pamhyr_ui = "Network" + _pamhyr_name = "River network" + def __init__(self, study=None, config=None, parent=None): super(NetworkWindow, self).__init__( - name=title, ui="Network", parent=parent + title = self._pamhyr_name + " - " + study.name, + study = study, + config = config, + trad = NetworkTranslate(), + options = ['undo'], + parent=parent, ) - self.ui.setWindowTitle(self._title) - self.setup_sc() + self._graph = study.river + self._table_headers_node = self._trad.get_dict("table_headers_node") + self._table_headers_edge = self._trad.get_dict("table_headers_edge") + self.setup_graph() self.setup_table() self.setup_connections() - def setup_title(self): - self._title = self._title + " - " + self._model.name - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - def setup_table(self): - retranslate() - # Nodes table table = self.find(QTableView, "tableView_nodes") self._nodes_model = NodeTableModel( table_view = table, - table_headers = table_headers_node, + table_headers = self._table_headers_node, editable_headers = ["name"], data = self._graph, undo = self._undo_stack, @@ -103,7 +97,7 @@ class NetworkWindow(ASubMainWindow): self._reachs_model = EdgeTableModel( table_view = table, - table_headers = table_headers_edge, + table_headers = self._table_headers_edge, editable_headers = ["name", "node1", "node2"], delegates = { "node1": self.delegate_combobox, @@ -143,9 +137,6 @@ class NetworkWindow(ASubMainWindow): self.clicked_del ) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - def clicked_add(self): if self.get_action_checkable("action_toolBar_add"): self.set_action_checkable("action_toolBar_del", False) @@ -166,13 +157,15 @@ class NetworkWindow(ASubMainWindow): if key == Qt.Key_Escape: self._graph_widget.reset_selection - def undo(self): + # Redefine undo/redo method + + def _undo(self): self._undo_stack.undo() self._reachs_model.update() self._nodes_model.update() self._graph_widget.display_update() - def redo(self): + def _redo(self): self._undo_stack.redo() self._reachs_model.update() self._nodes_model.update() diff --git a/src/View/Network/translate.py b/src/View/Network/translate.py index 24935e1b..44e97cf5 100644 --- a/src/View/Network/translate.py +++ b/src/View/Network/translate.py @@ -18,23 +18,21 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers_node = { - "name": _translate("Network", "Name"), - "type": _translate("Network", "Type"), -} +class NetworkTranslate(PamhyrTranslate): + def __init__(self): + super(NetworkTranslate, self).__init__() -table_headers_edge = { - "name": _translate("Network", "Name"), - "node1": _translate("Network", "Source node"), - "node2": _translate("Network", "Destination node"), -} + self._sub_dict["table_headers_node"] = { + "name": _translate("Network", "Name"), + "type": _translate("Network", "Type"), + } -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") + self._sub_dict["table_headers_edge"] = { + "name": _translate("Network", "Name"), + "node1": _translate("Network", "Source node"), + "node2": _translate("Network", "Destination node"), + } diff --git a/src/View/Results/PlotAC.py b/src/View/Results/PlotAC.py index f0e4b0e2..ed3d99ad 100644 --- a/src/View/Results/PlotAC.py +++ b/src/View/Results/PlotAC.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -25,14 +25,16 @@ from PyQt5.QtCore import ( _translate = QCoreApplication.translate -class PlotAC(APlot): - def __init__(self, canvas=None, results=None, - reach_id=0, profile_id=0, - toolbar=None): +class PlotAC(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + parent=None): super(PlotAC, self).__init__( - canvas=canvas, - data=results, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self._current_timestamp = max(results.get("timestamps")) diff --git a/src/View/Results/PlotH.py b/src/View/Results/PlotH.py index 557c7cec..99a59f88 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -22,7 +22,7 @@ from functools import reduce from datetime import datetime from tools import timer, trace -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -32,14 +32,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotH(APlot): - def __init__(self, canvas=None, results=None, - reach_id=0, profile_id=0, - toolbar=None): +class PlotH(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + parent=None): super(PlotH, self).__init__( - canvas=canvas, - data=results, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self._mode = "time" diff --git a/src/View/Results/PlotKPC.py b/src/View/Results/PlotKPC.py index 68f6fb4e..61de649d 100644 --- a/src/View/Results/PlotKPC.py +++ b/src/View/Results/PlotKPC.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -25,14 +25,16 @@ from PyQt5.QtCore import ( _translate = QCoreApplication.translate -class PlotKPC(APlot): - def __init__(self, canvas=None, results=None, - reach_id=0, profile_id=0, - toolbar=None): +class PlotKPC(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + parent=None): super(PlotKPC, self).__init__( - canvas=canvas, - data=results, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self._current_timestamp = max(results.get("timestamps")) diff --git a/src/View/Results/PlotSedProfile.py b/src/View/Results/PlotSedProfile.py index 62f4e290..3163931b 100644 --- a/src/View/Results/PlotSedProfile.py +++ b/src/View/Results/PlotSedProfile.py @@ -5,7 +5,7 @@ import logging from functools import reduce from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -15,14 +15,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotSedProfile(APlot): - def __init__(self, canvas=None, study=None, results=None, - reach_id=0, profile_id=0, - toolbar=None): +class PlotSedProfile(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + parent=None): super(PlotSedProfile, self).__init__( - canvas=canvas, - data=study, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self._results = results diff --git a/src/View/Results/PlotSedReach.py b/src/View/Results/PlotSedReach.py index 657b70e8..c63c80ec 100644 --- a/src/View/Results/PlotSedReach.py +++ b/src/View/Results/PlotSedReach.py @@ -5,7 +5,7 @@ import logging from functools import reduce from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -15,14 +15,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotSedReach(APlot): - def __init__(self, canvas=None, study=None, results=None, - reach_id=0, profile_id=0, - toolbar=None): +class PlotSedReach(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + parent=None): super(PlotSedReach, self).__init__( - canvas=canvas, - data=study, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self._results = results diff --git a/src/View/Results/PlotXY.py b/src/View/Results/PlotXY.py index 3e7a88f3..42716776 100644 --- a/src/View/Results/PlotXY.py +++ b/src/View/Results/PlotXY.py @@ -21,7 +21,7 @@ import logging from functools import reduce from tools import timer, trace -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -31,14 +31,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotXY(APlot): - def __init__(self, canvas=None, results=None, - reach_id=0, profile_id=0, - toolbar=None, display_current=True): +class PlotXY(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + display_current=True, parent=None): super(PlotXY, self).__init__( - canvas=canvas, - data=results, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = results, + toolbar = toolbar, + parent = parent ) self.display_current = display_current diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py index 8e167459..f4d7b81a 100644 --- a/src/View/Results/Table.py +++ b/src/View/Results/Table.py @@ -34,6 +34,7 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel from View.Results.translate import * logger = logging.getLogger() @@ -41,33 +42,16 @@ logger = logging.getLogger() _translate = QCoreApplication.translate -class TableModel(QAbstractTableModel): - def __init__(self, results = None, study = None, mode = None, undo=None): - super(QAbstractTableModel, self).__init__() +class TableModel(PamhyrTableModel): + def _setup_lst(self): + _river = self._data.river + if self._opt_data == "reach": + self._lst = _river.reachs + else: + self._lst = _river.reach(0).profiles - self._results = results - self._study = study - self._mode = mode - self._undo_stack = undo - - self._table_headers = table_headers_reach - if mode != "reach": - self._table_headers = table_headers_profile - - self._headers = list(self._table_headers.keys()) - - self._selected = 0 - self._timestamp = 0 - - def rowCount(self, parent=QModelIndex()): - if self._mode == "reach": - return len(self._results.river.reachs) - - current_reach = self._results.river.reach(self._selected) - return len(current_reach.profiles) - - def columnCount(self, parent=QModelIndex()): - return len(self._headers) + def __init__(self, **kwargs): + super(TableModel, self).__init__(**kwargs) def data(self, index, role=Qt.DisplayRole): if role != Qt.ItemDataRole.DisplayRole: @@ -76,32 +60,23 @@ class TableModel(QAbstractTableModel): row = index.row() column = index.column() - if self._mode == "reach": + if self._opt_data == "reach": if self._headers[column] == "name": - v = self._results.river.reach(row).name + v = self._lst[row].name return str(v) else: - current_reach = self._results.river.reach(self._selected) if self._headers[column] == "name": - v = current_reach.profile(row).name + v = self._lst[row].name return str(v) elif self._headers[column] == "kp": - v = current_reach.profile(row).kp + v = self._lst[row].kp return f"{v:.4f}" return QVariant() - def headerData(self, section, orientation, role=Qt.DisplayRole): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return self._table_headers[self._headers[section]] - - return QVariant() - - def index(self, row, column, parent=QModelIndex()): - if not self.hasIndex(row, column, parent): - return QModelIndex() - - return self.createIndex(row, column, QModelIndex()) - - def flags(self, index): - return Qt.ItemIsEnabled | Qt.ItemIsSelectable + def update(self, reach): + _river = self._data.river + if self._opt_data == "reach": + self._lst = _river.reachs + else: + self._lst = _river.reach(reach).profiles diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 251f5a13..61e9f22b 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -21,8 +21,7 @@ from datetime import datetime from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -42,8 +41,8 @@ from PyQt5.QtWidgets import ( QSlider, QLabel, ) -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Results.PlotXY import PlotXY from View.Results.PlotAC import PlotAC @@ -53,74 +52,57 @@ from View.Results.PlotSedReach import PlotSedReach from View.Results.PlotSedProfile import PlotSedProfile from View.Results.Table import TableModel -from View.Results.translate import ( - table_headers_reach, table_headers_profile, - retranslate, -) +from View.Results.translate import ResultsTranslate from View.Stricklers.Window import StricklersWindow _translate = QCoreApplication.translate logger = logging.getLogger() -class ResultsWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Results", - study=None, solver=None, results=None, +class ResultsWindow(PamhyrWindow): + _pamhyr_ui = "Results" + _pamhyr_name = "Results" + + def __init__(self, study=None, config=None, + solver=None, results=None, parent=None): - self._study = study self._solver = solver self._results = results self._timestamps = sorted(list(self._results.get("timestamps"))) - self.setup_title(title) - - super(ResultsWindow, self).__init__( - name=title, ui="Results", parent=parent - ) - - self.setup_sc() - self.setup_table() - self.setup_graph() - self.setup_slider() - self.setup_statusbar() - self.setup_connections() - - self.ui.setWindowTitle(self._title) - - def setup_title(self, title): - self._title = ( - title + " - " - + self._study.name + " - " + name = ( + self._pamhyr_name + " - " + + study.name + " - " + self._solver.name + " - " + self._results.date ) - def setup_sc(self): - self._undo_stack = QUndoStack() + super(ResultsWindow, self).__init__( + title = name, + study = study, + config = config, + trad = ResultsTranslate(), + parent=parent + ) - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) + self.setup_table() + self.setup_plot() + self.setup_slider() + self.setup_statusbar() + self.setup_connections() def setup_table(self): - retranslate() self._table = {} - for t in ["reach", "profile"]: table = self.find(QTableView, f"tableView_{t}") self._table[t] = TableModel( - results = self._results, - study = self._study, - mode = t, + table_view = table, + table_headers = self._trad.get_dict(f"table_headers_{t}"), + data = self._results, undo = self._undo_stack, + opt_data = t ) - table.setModel(self._table[t]) - - table.setSelectionBehavior(QAbstractItemView.SelectRows) - table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - table.setAlternatingRowColors(True) def setup_slider(self): self._slider_profile = self.find(QSlider, f"verticalSlider_profile") @@ -132,7 +114,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self._slider_time.setMaximum(len(self._timestamps) - 1) self._slider_time.setValue(len(self._timestamps) - 1) - def setup_graph(self): + def setup_plot(self): self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas.setObjectName("canvas") self.toolbar = PamhyrPlotToolbar( @@ -219,7 +201,6 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self.plot_sed_reach = PlotSedReach( canvas = self.canvas_5, results = self._results, - study = self._study, reach_id = 0, profile_id = 0, toolbar = self.toolbar_5 @@ -239,7 +220,6 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self.plot_sed_profile = PlotSedProfile( canvas = self.canvas_6, results = self._results, - study = self._study, reach_id = 0, profile_id = 0, toolbar = self.toolbar_6 @@ -289,11 +269,6 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self._status_label.setText(txt) def setup_connections(self): - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - fun = { "reach": self._set_current_reach, "profile": self._set_current_profile, @@ -411,14 +386,14 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): timestamp = self._timestamps[self._slider_time.value()] self.update(timestamp = timestamp) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() - def redo(self): + def _redo(self): self._table.redo() diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index 37351b66..14f55ba3 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -18,19 +18,19 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers_reach = { - "name": _translate("Results", "Reach name"), -} +class ResultsTranslate(PamhyrTranslate): + def __init__(self): + super(ResultsTranslate, self).__init__() -table_headers_profile = { - "name": _translate("Results", "Name"), - "kp": _translate("Results", "KP (m)"), -} + self._sub_dict["table_headers_reach"] = { + "name": _translate("Results", "Reach name"), + } -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)") + self._sub_dict["table_headers_profile"] = { + "name": _translate("Results", "Name"), + "kp": _translate("Results", "KP (m)"), + } diff --git a/src/View/RunSolver/Log/Window.py b/src/View/RunSolver/Log/Window.py index 93d42165..2a33311f 100644 --- a/src/View/RunSolver/Log/Window.py +++ b/src/View/RunSolver/Log/Window.py @@ -22,8 +22,7 @@ import os from queue import Queue from tools import trace, timer -from View.ASubWindow import ASubWindow, ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog, PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -45,24 +44,23 @@ from PyQt5.QtWidgets import ( _translate = QCoreApplication.translate -class SolverLogFileWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Solver logs", - file_name = None, +class SolverLogFileWindow(PamhyrWindow): + _pamhyr_ui = "SolverLogFile" + _pamhyr_name = "Solver logs" + + def __init__(self, file_name = None, study=None, config=None, solver=None, parent=None): - self._title = title - self._parent = parent - - self._study = study - self._config = config self._solver = solver - self._file_name = file_name super(SolverLogFileWindow, self).__init__( - name=self._title, ui="SolverLogFile", parent=parent + title = self._pamhyr_name, + study = study, + config = config, + options = [], + parent = parent ) - self.ui.setWindowTitle(self._title) self.setup_action() self.setup_connections() diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py index 226d4da5..62484308 100644 --- a/src/View/RunSolver/Window.py +++ b/src/View/RunSolver/Window.py @@ -23,8 +23,7 @@ import tempfile from queue import Queue from tools import trace, timer -from View.ASubWindow import ASubWindow, ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog, PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -56,20 +55,21 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class SelectSolverWindow(ASubWindow, ListedSubWindow): - def __init__(self, title="Select solver", - study=None, config=None, - parent=None): - self._title = title +class SelectSolverWindow(PamhyrDialog): + _pamhyr_ui = "SelectSolver" + _pamhyr_name = "Select solver" - self._study = study - self._config = config + def __init__(self, study=None, config=None, + parent=None): self._solver = None super(SelectSolverWindow, self).__init__( - name=self._title, ui="SelectSolver", parent=parent + title = self._pamhyr_name, + study = study, + config = config, + options = [], + parent = parent ) - self.ui.setWindowTitle(self._title) self.setup_combobox() self.setup_connections() @@ -101,24 +101,22 @@ class SelectSolverWindow(ASubWindow, ListedSubWindow): super(SelectSolverWindow, self).accept() +class SolverLogWindow(PamhyrWindow): + _pamhyr_ui = "SolverLog" + _pamhyr_name = "Solver Log" -class SolverLogWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Solver logs", - study=None, config=None, + def __init__(self, study=None, config=None, solver=None, parent=None): - self._title = title - self._parent = parent - - self._study = study - self._config = config self._solver = solver - self._results = None super(SolverLogWindow, self).__init__( - name=self._title, ui="SolverLog", parent=parent + title = self._pamhyr_name, + study = study, + config = config, + options = [], + parent = parent ) - self.ui.setWindowTitle(self._title) self.setup_action() self.setup_alarm() @@ -206,6 +204,11 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): if self._solver.log_file() != "": self.find(QAction, "action_log_file").setEnabled(True) + # Get results + if self._results is None: + self._results = self._solver.results(self._study, self._workdir, qlog = self._output) + self._parent.set_results(self._solver, self._results) + while self._output.qsize() != 0: s = self._output.get() if type(s) is str and "[ERROR]" in s: @@ -258,7 +261,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): if self._results is None: self._results = self._solver.results(self._study, self._workdir, qlog = self._output) - self._parent.set_results(self._results) + self._parent.set_results(self._solver, self._results) self._parent.open_solver_results(self._solver, self._results) def log_file(self): diff --git a/src/View/SedimentLayers/Edit/Plot.py b/src/View/SedimentLayers/Edit/Plot.py index e48ec5eb..a6fa2185 100644 --- a/src/View/SedimentLayers/Edit/Plot.py +++ b/src/View/SedimentLayers/Edit/Plot.py @@ -5,23 +5,19 @@ import logging from functools import reduce from tools import timer -from View.Plot.APlot import APlot - -from PyQt5.QtCore import ( - QCoreApplication -) - -_translate = QCoreApplication.translate +from View.Tools.PamhyrPlot import PamhyrPlot logger = logging.getLogger() -class Plot(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, - display_current=True): +class Plot(PamhyrPlot): + def __init__(self, canvas=None, trad=None, data=None, toolbar=None, + display_current=True, parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self._display_current = display_current @@ -34,13 +30,9 @@ class Plot(APlot): self.canvas.axes.cla() self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) - # self.canvas.axes.set_xlabel( - # _translate("MainWindow_reach", "X (m)"), - # color='green', fontsize=12 - # ) self.canvas.axes.axes.get_xaxis().set_visible(False) self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Height (m)"), + self._trad["height"], color='green', fontsize=12 ) diff --git a/src/View/SedimentLayers/Edit/Table.py b/src/View/SedimentLayers/Edit/Table.py index e1a41cb2..ea7cd934 100644 --- a/src/View/SedimentLayers/Edit/Table.py +++ b/src/View/SedimentLayers/Edit/Table.py @@ -15,32 +15,14 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from View.SedimentLayers.Edit.UndoCommand import * -from View.SedimentLayers.Edit.translate import * _translate = QCoreApplication.translate -class TableModel(QAbstractTableModel): - def __init__(self, study=None, sl=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._study = study - self._undo = undo - self._sl = sl - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._sl) - - def columnCount(self, parent): - return len(self._headers) - +class TableModel(PamhyrTableModel): def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: return QVariant() @@ -49,23 +31,17 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._sl.get(row).name + return self._data.get(row).name elif self._headers[column] == "type": - return self._sl.get(row).type + return self._data.get(row).type elif self._headers[column] == "height": - return self._sl.get(row).height + return self._data.get(row).height elif self._headers[column] == "d50": - return self._sl.get(row).d50 + return self._data.get(row).d50 elif self._headers[column] == "sigma": - return self._sl.get(row).sigma + return self._data.get(row).sigma elif self._headers[column] == "critical_constraint": - return self._sl.get(row).critical_constraint - - return QVariant() - - def headerData(self, friction, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[friction]] + return self._data.get(row).critical_constraint return QVariant() @@ -79,37 +55,37 @@ class TableModel(QAbstractTableModel): if self._headers[column] == "name": self._undo.push( SetNameCommand( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "type": self._undo.push( SetTypeCommand( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "height": self._undo.push( SetHeightCommand( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "d50": self._undo.push( SetD50Command( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "sigma": self._undo.push( SetSigmaCommand( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "critical_constraint": self._undo.push( SetCriticalConstraintCommand( - self._sl, row, value + self._data, row, value ) ) @@ -121,7 +97,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._sl, row + self._data, row ) ) @@ -133,7 +109,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._sl, rows + self._data, rows ) ) @@ -150,7 +126,7 @@ class TableModel(QAbstractTableModel): self._undo.push( MoveCommand( - self._sl, "up", row + self._data, "up", row ) ) @@ -158,7 +134,7 @@ class TableModel(QAbstractTableModel): self.layoutChanged.emit() def move_down(self, row, parent=QModelIndex()): - if row + 1 >= len(self._sl): + if row + 1 >= len(self._data): return target = row @@ -167,7 +143,7 @@ class TableModel(QAbstractTableModel): self._undo.push( MoveCommand( - self._sl, "down", row + self._data, "down", row ) ) diff --git a/src/View/SedimentLayers/Edit/Window.py b/src/View/SedimentLayers/Edit/Window.py index 29b9ddff..e5835c97 100644 --- a/src/View/SedimentLayers/Edit/Window.py +++ b/src/View/SedimentLayers/Edit/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -40,66 +39,58 @@ from PyQt5.QtWidgets import ( QComboBox, QVBoxLayout, QHeaderView, QTabWidget, ) -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.SedimentLayers.Edit.UndoCommand import * from View.SedimentLayers.Edit.Table import * from View.SedimentLayers.Edit.Plot import Plot -from View.SedimentLayers.Edit.translate import table_headers, retranslate +from View.SedimentLayers.Edit.translate import SedimentEditTranslate _translate = QCoreApplication.translate logger = logging.getLogger() -class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Edit Sediment Layers", - study=None, sl=None, parent=None): - self._study = study +class EditSedimentLayersWindow(PamhyrWindow): + _pamhyr_ui = "EditSedimentLayers" + _pamhyr_name = "Edit Sediment Layers" + + def __init__(self, study=None, config = None, + sl=None, parent=None): self._sl = sl - self.setup_title(title) - - super(EditSedimentLayersWindow, self).__init__( - name=self._title, ui="EditSedimentLayers", parent=parent - ) - - self.setup_sc() - self.setup_table() - self.setup_graph() - self.setup_connections() - - self.ui.setWindowTitle(self._title) - - def setup_title(self, title): name = self._sl.name if name == "": name = _translate("SedimentLayers", "(no name)") - self._title = ( - title + " - " + self._study.name + " - " + name + name = ( + self._pamhyr_name + " - " + study.name + " - " + name ) - def setup_sc(self): - self._undo_stack = QUndoStack() + super(EditSedimentLayersWindow, self).__init__( + title = name, + study = study, + config = config, + trad = SedimentEditTranslate(), + parent = parent + ) - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) + self.setup_table() + self.setup_graph() + self.setup_connections() def setup_table(self): - retranslate() - self._table = {} - + table_headers = self._trad.get_dict("table_headers") table = self.find(QTableView, f"tableView") self._table = TableModel( - study = self._study, - sl = self._sl, + table_view = table, + table_headers = table_headers, + editable_headers = list(table_headers), + data = self._sl, + trad = self._trad, undo = self._undo_stack, ) table.setModel(self._table) - table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) @@ -120,6 +111,7 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): canvas = self.canvas, data = self._sl, toolbar = self.toolbar, + trad = self._trad, display_current = False ) self.plot.draw() @@ -131,11 +123,6 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_move_up").triggered.connect(self.move_up) self.find(QAction, "action_move_down").triggered.connect(self.move_down) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - self._table.dataChanged.connect(self._set_plot) self._table.layoutChanged.connect(self._set_plot) @@ -179,14 +166,14 @@ class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): self._table.move_down(rows[0]) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() - def redo(self): + def _redo(self): self._table.redo() diff --git a/src/View/SedimentLayers/Edit/translate.py b/src/View/SedimentLayers/Edit/translate.py index a6b77897..658d4f23 100644 --- a/src/View/SedimentLayers/Edit/translate.py +++ b/src/View/SedimentLayers/Edit/translate.py @@ -2,21 +2,21 @@ from PyQt5.QtCore import QCoreApplication +from View.SedimentLayers.translate import SedimentTranslate + _translate = QCoreApplication.translate -table_headers = { - "name": _translate("SedimentLayers", "Name"), - # "type": _translate("SedimentLayers", "Type"), - "height": _translate("SedimentLayers", "Height"), - "d50": _translate("SedimentLayers", "D50"), - "sigma": _translate("SedimentLayers", "Sigma"), - "critical_constraint": _translate("SedimentLayers", "Critical constraint"), -} +class SedimentEditTranslate(SedimentTranslate): + def __init__(self): + super(SedimentEditTranslate, self).__init__() -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") + self._dict["height"] = _translate("SedimentLayers", "Height (m)"), + + self._sub_dict["table_headers"] = { + "name": _translate("SedimentLayers", "Name"), + # "type": _translate("SedimentLayers", "Type"), + "height": _translate("SedimentLayers", "Height"), + "d50": _translate("SedimentLayers", "D50"), + "sigma": _translate("SedimentLayers", "Sigma"), + "critical_constraint": _translate("SedimentLayers", "Critical constraint"), + } diff --git a/src/View/SedimentLayers/Reach/Plot.py b/src/View/SedimentLayers/Reach/Plot.py index a312a8ce..069a09d7 100644 --- a/src/View/SedimentLayers/Reach/Plot.py +++ b/src/View/SedimentLayers/Reach/Plot.py @@ -5,7 +5,7 @@ import logging from functools import reduce from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -15,13 +15,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class Plot(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, - display_current=True): +class Plot(PamhyrPlot): + def __init__(self, data=None, display_current=True, + canvas=None, trad=None, toolbar=None, + parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self._display_current = display_current @@ -41,11 +44,11 @@ class Plot(APlot): return self.canvas.axes.set_xlabel( - _translate("MainWindow_reach", "Kp (m)"), + self._trad["kp"], color='green', fontsize=12 ) self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Height (m)"), + self._trad["height"], color='green', fontsize=12 ) diff --git a/src/View/SedimentLayers/Reach/Profile/Plot.py b/src/View/SedimentLayers/Reach/Profile/Plot.py index 50b4bba2..01b2fe24 100644 --- a/src/View/SedimentLayers/Reach/Profile/Plot.py +++ b/src/View/SedimentLayers/Reach/Profile/Plot.py @@ -5,7 +5,7 @@ import logging from functools import reduce from tools import timer -from View.Plot.APlot import APlot +from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtCore import ( QCoreApplication @@ -15,13 +15,16 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class Plot(APlot): - def __init__(self, canvas=None, data=None, toolbar=None, - display_current=True): +class Plot(PamhyrPlot): + def __init__(self, data=None, display_current=True, + canvas=None, trad=None, toolbar=None, + parent=None): super(Plot, self).__init__( - canvas=canvas, - data=data, - toolbar=toolbar + canvas = canvas, + trad = trad, + data = data, + toolbar = toolbar, + parent = parent ) self._display_current = display_current diff --git a/src/View/SedimentLayers/Reach/Profile/Table.py b/src/View/SedimentLayers/Reach/Profile/Table.py index 102bec32..3550940b 100644 --- a/src/View/SedimentLayers/Reach/Profile/Table.py +++ b/src/View/SedimentLayers/Reach/Profile/Table.py @@ -20,6 +20,8 @@ from PyQt5.QtWidgets import ( from View.SedimentLayers.Reach.Profile.UndoCommand import * from View.SedimentLayers.Reach.Profile.translate import * +from View.Tools.PamhyrTable import PamhyrTableModel + _translate = QCoreApplication.translate logger = logging.getLogger() @@ -67,28 +69,10 @@ class ComboBoxDelegate(QItemDelegate): self.commitData.emit(self.sender()) -class TableModel(QAbstractTableModel): - def __init__(self, study=None, profile=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._study = study - self._undo = undo - self._profile = profile - - def flags(self, index): - column = index.column() - - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - if self._headers[column] == "sl": - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return self._profile.number_points - - def columnCount(self, parent): - return len(self._headers) +class TableModel(PamhyrTableModel): + def _setup_lst(self): + self._lst = self._data + self._study = self._opt_data def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: @@ -98,25 +82,19 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._profile.point(row).name + return self._data.point(row).name elif self._headers[column] == "sl": - value = self._profile.point(row).sl + value = self._data.point(row).sl if value == None: text = _translate("SedimentLayers", "Not defined") return text return str(value) elif self._headers[column] == "x": - return self._profile.point(row).x + return self._data.point(row).x elif self._headers[column] == "y": - return self._profile.point(row).y + return self._data.point(row).y elif self._headers[column] == "z": - return self._profile.point(row).z - - return QVariant() - - def headerData(self, friction, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[friction]] + return self._data.point(row).z return QVariant() @@ -139,7 +117,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SetSLCommand( - self._profile, row, new + self._data, row, new ) ) diff --git a/src/View/SedimentLayers/Reach/Profile/Window.py b/src/View/SedimentLayers/Reach/Profile/Window.py index eaae59d3..e2c2464b 100644 --- a/src/View/SedimentLayers/Reach/Profile/Window.py +++ b/src/View/SedimentLayers/Reach/Profile/Window.py @@ -4,8 +4,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -28,37 +27,42 @@ from View.SedimentLayers.Reach.Profile.UndoCommand import * from View.SedimentLayers.Reach.Profile.Table import * from View.SedimentLayers.Reach.Profile.Plot import Plot -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar -from View.SedimentLayers.Reach.Profile.translate import table_headers, retranslate +from View.SedimentLayers.Reach.Profile.translate import SedimentProfileTranslate from View.SedimentLayers.Window import SedimentLayersWindow _translate = QCoreApplication.translate logger = logging.getLogger() -class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Profile sediment layers", study=None, profile=None, parent=None): - self._study = study - self._sediment_layers = self._study.river.sediment_layers - self._profile = profile - self._reach = self._study.river.current_reach().reach +class ProfileSedimentLayersWindow(PamhyrWindow): + _pamhyr_ui = "ProfileSedimentLayers" + _pamhyr_name = "Profile sediment layers" - self.setup_title(title) + def __init__(self, study=None, config=None, profile=None, parent=None): + self._sediment_layers = study.river.sediment_layers + self._profile = profile + self._reach = profile.reach + + name = self.compute_name(study) super(ProfileSedimentLayersWindow, self).__init__( - name=self._title, ui="ProfileSedimentLayers", parent=parent + title = name, + study = study, + config = config, + trad = SedimentProfileTranslate(), + parent = parent ) - self.setup_sc() self.setup_table() self.setup_graph() self.setup_connections() self.ui.setWindowTitle(self._title) - def setup_title(self, title): + def compute_name(self, study): rname = self._reach.name if rname == "": rname = _translate("SedimentLayers", "(no name)") @@ -70,41 +74,32 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow): "(no name - @kp)").replace("@kp", str(self._profile.kp) ) - self._title = ( - title + " - " - + self._study.name + " - " + return ( + self._pamhyr_name + " - " + + study.name + " - " + rname + " - " + pname ) - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() + table_headers = self._trad.get_dict("table_headers") - table = self.find(QTableView, f"tableView") - self._table = TableModel( - study = self._study, - profile = self._profile, - undo = self._undo_stack, - ) - table.setModel(self._table) - - self._delegate_stricklers = ComboBoxDelegate( + self._delegate_sl = ComboBoxDelegate( study = self._study, parent=self ) - table.setItemDelegateForColumn( - list(table_headers).index("sl"), - self._delegate_stricklers + table = self.find(QTableView, f"tableView") + self._table = TableModel( + table_view = table, + data = self._profile, + opt_data = self._study, + table_headers = table_headers, + editable_headers = ["sl"], + delegates = {"sl": self._delegate_sl}, + trad = self._trad, + undo = self._undo_stack, ) - + table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) @@ -130,18 +125,9 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow): ) self.plot.draw() - def setup_connections(self): - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - - self._table.layoutChanged\ - .connect(self._update_plot) - self._table.dataChanged\ - .connect(self._update_plot) - + self._table.layoutChanged.connect(self._update_plot) + self._table.dataChanged.connect(self._update_plot) def index_selected_rows(self): table = self.find(QTableView, f"tableView") @@ -155,14 +141,14 @@ class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow): ) ) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() - def redo(self): + def _redo(self): self._table.redo() diff --git a/src/View/SedimentLayers/Reach/Profile/translate.py b/src/View/SedimentLayers/Reach/Profile/translate.py index da256cbe..b1c12405 100644 --- a/src/View/SedimentLayers/Reach/Profile/translate.py +++ b/src/View/SedimentLayers/Reach/Profile/translate.py @@ -2,19 +2,18 @@ from PyQt5.QtCore import QCoreApplication +from View.SedimentLayers.Reach.translate import SedimentReachTranslate + _translate = QCoreApplication.translate -table_headers = { - "x": _translate("SedimentLayers", "X (m)"), - "y": _translate("SedimentLayers", "Y (m)"), - "z": _translate("SedimentLayers", "Z (m)"), - "name": _translate("SedimentLayers", "Name"), - "sl": _translate("SedimentLayers", "Sediment layers"), -} +class SedimentProfileTranslate(SedimentReachTranslate): + def __init__(self): + super(SedimentProfileTranslate, self).__init__() -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") + self._sub_dict["table_headers"] = { + "x": _translate("SedimentLayers", "X (m)"), + "y": _translate("SedimentLayers", "Y (m)"), + "z": _translate("SedimentLayers", "Z (m)"), + "name": _translate("SedimentLayers", "Name"), + "sl": _translate("SedimentLayers", "Sediment layers"), + } diff --git a/src/View/SedimentLayers/Reach/SLDialog.py b/src/View/SedimentLayers/Reach/SLDialog.py index ce1e9794..c8a1efa0 100644 --- a/src/View/SedimentLayers/Reach/SLDialog.py +++ b/src/View/SedimentLayers/Reach/SLDialog.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from View.ASubWindow import ASubWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtGui import ( QKeySequence, @@ -20,12 +19,16 @@ from View.SedimentLayers.Reach.translate import * _translate = QCoreApplication.translate -class SLDialog(ASubWindow, ListedSubWindow): - def __init__(self, title="SL", study=None, parent=None): - self._study = study +class SLDialog(PamhyrDialog): + _pamhyr_ui = "SLDialog" + _pamhyr_name = "SL" + def __init__(self, study=None, config=None, parent=None): super(SLDialog, self).__init__( - name=title, ui="SLDialog", parent=parent + title = self._pamhyr_name, + study = study, + config = config, + parent=parent ) self.setup_combobox() diff --git a/src/View/SedimentLayers/Reach/Table.py b/src/View/SedimentLayers/Reach/Table.py index 8fdb8d7e..250a4735 100644 --- a/src/View/SedimentLayers/Reach/Table.py +++ b/src/View/SedimentLayers/Reach/Table.py @@ -18,23 +18,25 @@ from PyQt5.QtWidgets import ( ) from View.SedimentLayers.Reach.UndoCommand import * -from View.SedimentLayers.Reach.translate import * + +from View.Tools.PamhyrTable import PamhyrTableModel _translate = QCoreApplication.translate logger = logging.getLogger() class ComboBoxDelegate(QItemDelegate): - def __init__(self, study=None, parent=None): + def __init__(self, study=None, trad=None, parent=None): super(ComboBoxDelegate, self).__init__(parent) self._study = study + self._trad = trad def createEditor(self, parent, option, index): self.editor = QComboBox(parent) self.editor.addItems( - [_translate("SedimentLayers", "Not defined")] + + [self._trad["nd"]] + list( map( lambda sl: str(sl), @@ -67,28 +69,10 @@ class ComboBoxDelegate(QItemDelegate): self.commitData.emit(self.sender()) -class TableModel(QAbstractTableModel): - def __init__(self, study=None, reach=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._study = study - self._undo = undo - self._reach = reach - - def flags(self, index): - column = index.column() - - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - if self._headers[column] == "sl": - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return self._reach.number_profiles - - def columnCount(self, parent): - return len(self._headers) +class TableModel(PamhyrTableModel): + def _setup_lst(self): + self._lst = self._data + self._study = self._opt_data def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: @@ -98,11 +82,11 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._reach.profile(row).name + return self._data.profile(row).name if self._headers[column] == "kp": - return self._reach.profile(row).kp + return self._data.profile(row).kp if self._headers[column] == "sl": - value = self._reach.profile(row).sl + value = self._data.profile(row).sl if value == None: text = _translate("SedimentLayers", "Not defined") return text @@ -110,12 +94,6 @@ class TableModel(QAbstractTableModel): return QVariant() - def headerData(self, friction, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[friction]] - - return QVariant() - def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or role != Qt.EditRole: return False @@ -135,7 +113,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SetSLCommand( - self._reach, row, new + self._data, row, new ) ) @@ -145,7 +123,7 @@ class TableModel(QAbstractTableModel): def apply_sl_each_profile(self, sl): self._undo.push( ApplySLCommand( - self._reach, sl + self._data, sl ) ) self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/Reach/Window.py b/src/View/SedimentLayers/Reach/Window.py index d79312b2..829bf035 100644 --- a/src/View/SedimentLayers/Reach/Window.py +++ b/src/View/SedimentLayers/Reach/Window.py @@ -4,8 +4,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -29,10 +28,10 @@ from View.SedimentLayers.Reach.Table import * from View.SedimentLayers.Reach.Plot import Plot from View.SedimentLayers.Reach.SLDialog import SLDialog -from View.Plot.MplCanvas import MplCanvas -from View.Plot.PamhyrToolbar import PamhyrPlotToolbar +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar -from View.SedimentLayers.Reach.translate import table_headers, retranslate +from View.SedimentLayers.Reach.translate import SedimentReachTranslate from View.SedimentLayers.Window import SedimentLayersWindow from View.SedimentLayers.Reach.Profile.Window import ProfileSedimentLayersWindow @@ -40,64 +39,58 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Reach sediment layers", study=None, parent=None): - self._study = study - self._sediment_layers = self._study.river.sediment_layers - self._reach = self._study.river.current_reach().reach +class ReachSedimentLayersWindow(PamhyrWindow): + _pamhyr_ui = "ReachSedimentLayers" + _pamhyr_name = "Reach sediment layers" - self.setup_title(title) + def __init__(self, study=None, config=None, parent=None): + self._sediment_layers = study.river.sediment_layers + self._reach = study.river.current_reach().reach + + name = ( + self._pamhyr_name + " - " + + study.name + " - " + + self._reach.name + ) super(ReachSedimentLayersWindow, self).__init__( - name=self._title, ui="ReachSedimentLayers", parent=parent + title = name, + study = study, + config = config, + trad = SedimentReachTranslate(), + parent = parent ) - self.setup_sc() self.setup_table() - self.setup_graph() + self.setup_plot() self.setup_connections() - self.ui.setWindowTitle(self._title) - - def setup_title(self, title): - self._title = ( - title + " - " + self._study.name + " - " + self._reach.name - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() + table_headers = self._trad.get_dict("table_headers") - table = self.find(QTableView, f"tableView") - self._table = TableModel( - study = self._study, - reach = self._reach, - undo = self._undo_stack, - ) - table.setModel(self._table) - - self._delegate_stricklers = ComboBoxDelegate( + self._delegate_sl = ComboBoxDelegate( study = self._study, + trad = self._trad, parent=self ) - table.setItemDelegateForColumn( - list(table_headers).index("sl"), - self._delegate_stricklers + table = self.find(QTableView, f"tableView") + self._table = TableModel( + table_view = table, + data = self._reach, + opt_data = self._study, + table_headers = table_headers, + editable_headers = ["sl"], + delegates = {"sl": self._delegate_sl}, + trad = self._trad, + undo = self._undo_stack, ) - + table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) - def setup_graph(self): + def setup_plot(self): self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas.setObjectName("canvas") self.toolbar = PamhyrPlotToolbar( @@ -114,6 +107,7 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): canvas = self.canvas, data = self._reach, toolbar = self.toolbar, + trad = self._trad, display_current = False ) self.plot.draw() @@ -129,11 +123,6 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): .clicked\ .connect(self.apply_sl_each_profile) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - self._table.layoutChanged\ .connect(self._update_plot) self._table.dataChanged\ @@ -152,17 +141,17 @@ class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): ) ) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() self._update_plot() - def redo(self): + def _redo(self): self._table.redo() self._update_plot() diff --git a/src/View/SedimentLayers/Reach/translate.py b/src/View/SedimentLayers/Reach/translate.py index ef45f076..34785f32 100644 --- a/src/View/SedimentLayers/Reach/translate.py +++ b/src/View/SedimentLayers/Reach/translate.py @@ -2,15 +2,20 @@ from PyQt5.QtCore import QCoreApplication +from View.SedimentLayers.translate import SedimentTranslate + _translate = QCoreApplication.translate -table_headers = { - "name": _translate("SedimentLayers", "Name"), - "kp": _translate("SedimentLayers", "KP (m)"), - "sl": _translate("SedimentLayers", "Sediment layers"), -} +class SedimentReachTranslate(SedimentTranslate): + def __init__(self): + super(SedimentReachTranslate, self).__init__() -def retranslate(): - table_headers["name"] = _translate("SedimentLayers", "Name") - table_headers["kp"] = _translate("SedimentLayers", "KP (m)") - table_headers["sl"] = _translate("SedimentLayers", "Sediment layers") + self._dict["nd"] = _translate("SedimentLayers", "Not defined") + self._dict["kp"] = _translate("SedimentLayers", "Kp (m)") + self._dict["height"] = _translate("SedimentLayers", "Height (m)") + + self._sub_dict["table_headers"] = { + "name": _translate("SedimentLayers", "Name"), + "kp": _translate("SedimentLayers", "KP (m)"), + "sl": _translate("SedimentLayers", "Sediment layers"), + } diff --git a/src/View/SedimentLayers/Table.py b/src/View/SedimentLayers/Table.py index f375c318..2b7a00f6 100644 --- a/src/View/SedimentLayers/Table.py +++ b/src/View/SedimentLayers/Table.py @@ -15,32 +15,12 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from View.SedimentLayers.UndoCommand import * -from View.SedimentLayers.translate import * - -_translate = QCoreApplication.translate -class TableModel(QAbstractTableModel): - def __init__(self, study=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._study = study - self._undo = undo - self._sl = self._study.river.sediment_layers - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._sl) - - def columnCount(self, parent): - return len(self._headers) - +class TableModel(PamhyrTableModel): def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: return QVariant() @@ -49,15 +29,9 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._sl.get(row).name + return self._data.get(row).name elif self._headers[column] == "comment": - return self._sl.get(row).comment - - return QVariant() - - def headerData(self, friction, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return table_headers[self._headers[friction]] + return self._data.get(row).comment return QVariant() @@ -71,13 +45,13 @@ class TableModel(QAbstractTableModel): if self._headers[column] == "name": self._undo.push( SetNameCommand( - self._sl, row, value + self._data, row, value ) ) if self._headers[column] == "comment": self._undo.push( SetCommentCommand( - self._sl, row, value + self._data, row, value ) ) @@ -89,7 +63,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._sl, row + self._data, row ) ) @@ -101,7 +75,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._sl, rows + self._data, rows ) ) @@ -118,7 +92,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._sl, "up", row + self._data, "up", row ) ) @@ -126,7 +100,7 @@ class TableModel(QAbstractTableModel): self.layoutChanged.emit() def move_down(self, index, parent=QModelIndex()): - if row > len(self._sl): + if row > len(self._data): return target = row @@ -135,7 +109,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._sl, "down", row + self._data, "down", row ) ) diff --git a/src/View/SedimentLayers/Window.py b/src/View/SedimentLayers/Window.py index 6c14ae6c..3c394de0 100644 --- a/src/View/SedimentLayers/Window.py +++ b/src/View/SedimentLayers/Window.py @@ -1,11 +1,26 @@ +# Window.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + # -*- coding: utf-8 -*- import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -29,62 +44,53 @@ from View.SedimentLayers.Table import * from View.SedimentLayers.Edit.Plot import Plot -from View.Plot.MplCanvas import MplCanvas -from View.SedimentLayers.translate import table_headers, retranslate +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.SedimentLayers.translate import SedimentTranslate from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow -_translate = QCoreApplication.translate - logger = logging.getLogger() -class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="SedimentLayersList", study=None, parent=None): - self._study = study - self._sediment_layers = self._study.river.sediment_layers +class SedimentLayersWindow(PamhyrWindow): + _pamhyr_ui = "SedimentLayersList" + _pamhyr_name = "Sediment Layers List" - self.setup_title(title) + def __init__(self, study=None, config=None, parent=None): + self._sediment_layers = study.river.sediment_layers + + name = ( + self._pamhyr_name + " - " + study.name + ) super(SedimentLayersWindow, self).__init__( - name=self._title, ui="SedimentLayersList", parent=parent + title = name, + study = study, + config = config, + trad = SedimentTranslate(), + parent=parent ) - self.setup_sc() self.setup_table() - self.setup_graph() + self.setup_plot() self.setup_connections() - self.ui.setWindowTitle(self._title) - - def setup_title(self, title): - self._title = ( - title + " - " + self._study.name - ) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - def setup_table(self): - retranslate() - table = self.find(QTableView, f"tableView") self._table = TableModel( - study = self._study, + table_view = table, + table_headers = self._trad.get_dict("table_headers"), + editable_headers = ["name", "comment"], + data = self._study.river.sediment_layers, + trad = self._trad, undo = self._undo_stack, ) table.setModel(self._table) - table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) - def setup_graph(self): - self.canvas = MplCanvas(width=5, height=4, dpi=100) + def setup_plot(self): + self.canvas = MplCanvas(width=2, height=4, dpi=100) self.canvas.setObjectName("canvas") self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout.addWidget(self.canvas) @@ -94,11 +100,6 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_edit").triggered.connect(self.edit_sediment_layers) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - table = self.find(QTableView, f"tableView") table.selectionModel()\ .selectionChanged\ @@ -116,6 +117,7 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): self.plot = Plot( canvas = self.canvas, data = self._sediment_layers.get(rows[0]), + trad = self._trad, toolbar = None, display_current = False ) @@ -148,16 +150,16 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): self._table.delete(rows) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table.undo() - def redo(self): + def _redo(self): self._table.redo() def edit_sediment_layers(self): diff --git a/src/View/SedimentLayers/translate.py b/src/View/SedimentLayers/translate.py index 29053e17..42d59f92 100644 --- a/src/View/SedimentLayers/translate.py +++ b/src/View/SedimentLayers/translate.py @@ -2,13 +2,15 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers = { - "name": _translate("SedimentLayers", "Name"), - "comment": _translate("SedimentLayers", "Comment"), -} +class SedimentTranslate(PamhyrTranslate): + def __init__(self): + super(SedimentTranslate, self).__init__() -def retranslate(): - table_headers["name"] = _translate("SedimentLayers", "Name") - table_headers["comment"] = _translate("SedimentLayers", "Comment") + self._sub_dict["table_headers"] = { + "name": _translate("SedimentLayers", "Name"), + "comment": _translate("SedimentLayers", "Comment"), + } diff --git a/src/View/SolverParameters/Table.py b/src/View/SolverParameters/Table.py index 5aa488ab..47fa0ae9 100644 --- a/src/View/SolverParameters/Table.py +++ b/src/View/SolverParameters/Table.py @@ -34,37 +34,20 @@ from PyQt5.QtWidgets import ( QComboBox, ) +from View.Tools.PamhyrTable import PamhyrTableModel + from View.SolverParameters.UndoCommand import * -from View.SolverParameters import translate as tr from Solver.Solvers import solver_long_name, solver_type_list +logger = logging.getLogger() + _translate = QCoreApplication.translate -class TableModel(QAbstractTableModel): - def __init__(self, data=None, undo=None, tab=""): - super(QAbstractTableModel, self).__init__() - self._headers = list(tr.table_headers.keys()) - self._data = data - self._undo = undo - self._tab = tab - self._params = self._data.get_params(self._tab) - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - - column = index.column() - - if self._headers[column] == "value": - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._params) - - def columnCount(self, parent): - return len(self._headers) +class TableModel(PamhyrTableModel): + def _setup_lst(self): + self._tab = self._opt_data + self._lst = self._data.get_params(self._tab) def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: @@ -75,24 +58,18 @@ class TableModel(QAbstractTableModel): cname = self._headers[column] if cname == "name": - value = self._params.get(row)[cname] - if value in tr.names: - value = tr.names[value] + value = self._lst.get(row)[cname] + if value in self._trad.get_dict("names"): + value = self._trad.get_dict("names")[value] return value elif cname == "value": - value = self._params.get(row)[cname] - if value in tr.yes_no: - value = tr.yes_no[value] + value = self._lst.get(row)[cname] + if value in self._trad.get_dict("yes_no"): + value = self._trad.get_dict("yes_no")[value] return value return QVariant() - def headerData(self, section, orientation, role): - if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: - return tr.table_headers[self._headers[section]] - - return QVariant() - def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or role != Qt.EditRole: return False @@ -102,12 +79,12 @@ class TableModel(QAbstractTableModel): try: if self._headers[column] == "value": - if value in tr.r_yes_no: - value = tr.r_yes_no[value].lower() + if value in self._trad.get_dict("r_yes_no"): + value = self._trad.get_dict("r_yes_no")[value].lower() self._undo.push( SetCommand( - self._params, row, + self._lst, row, "value", value ) ) @@ -123,7 +100,7 @@ class TableModel(QAbstractTableModel): # self._undo.push( # AddCommand( - # self._params, row + # self._lst, row # ) # ) @@ -135,7 +112,7 @@ class TableModel(QAbstractTableModel): # self._undo.push( # DelCommand( - # self._params, rows + # self._lst, rows # ) # ) @@ -147,7 +124,7 @@ class TableModel(QAbstractTableModel): # self._undo.push( # SortCommand( - # self._params, False + # self._lst, False # ) # ) @@ -164,7 +141,7 @@ class TableModel(QAbstractTableModel): # self._undo_stack.push( # MoveCommand( - # self._params, "up", row + # self._lst, "up", row # ) # ) @@ -172,7 +149,7 @@ class TableModel(QAbstractTableModel): # self.layoutChanged.emit() # def move_down(self, index, parent=QModelIndex()): - # if row > len(self._params): + # if row > len(self._lst): # return # target = row @@ -181,7 +158,7 @@ class TableModel(QAbstractTableModel): # self._undo_stack.push( # MoveCommand( - # self._params, "down", row + # self._lst, "down", row # ) # ) diff --git a/src/View/SolverParameters/Window.py b/src/View/SolverParameters/Window.py index c04eebc7..b655cd53 100644 --- a/src/View/SolverParameters/Window.py +++ b/src/View/SolverParameters/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -43,7 +42,7 @@ from PyQt5.QtWidgets import ( from View.SolverParameters.UndoCommand import * from View.SolverParameters.Table import TableModel -from View.SolverParameters import translate as tr +from View.SolverParameters.translate import ParamTranslate from Solver.Solvers import solver_long_name, solver_type_list @@ -51,25 +50,25 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class SolverParametersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Solver parameters", study=None, parent=None): - # Init tanslate dictionary - tr.init() - - self._title = title + " - " + study.name +class SolverParametersWindow(PamhyrWindow): + _pamhyr_ui = "SolverParameters" + _pamhyr_name = "Solver parameters" + def __init__(self, study=None, config=None, parent=None): + name = self._pamhyr_name + " - " + study.name super(SolverParametersWindow, self).__init__( - name=title, ui="SolverParameters", parent=parent + title = name, + study = study, + config = config, + options = ['copy'], + trad = ParamTranslate(), + parent = parent ) - self._study = study self._params = self._study.river.parameters self.setup_sc() self.setup_table() - self.setup_connections() - - self.ui.setWindowTitle(self._title) def setup_sc(self): self._undo_stack = {} @@ -77,10 +76,10 @@ class SolverParametersWindow(ASubMainWindow, ListedSubWindow): for st in solver_type_list: self._undo_stack[st] = QUndoStack() - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) + self._undo_sc = QShortcut(QKeySequence.Undo, self) + self._redo_sc = QShortcut(QKeySequence.Redo, self) + self._undo_sc.activated.connect(self._undo) + self._redo_sc.activated.connect(self._redo) def setup_table(self): self._table = {} @@ -97,9 +96,13 @@ class SolverParametersWindow(ASubMainWindow, ListedSubWindow): # Create table model self._table[st] = TableModel( + table_view = table, data = self._study.river, + opt_data = st, + table_headers = self._trad.get_dict("table_headers"), + editable_headers = ["value"], + trad = self._trad, undo = self._undo_stack[st], - tab = st, ) table.setModel(self._table[st]) @@ -115,31 +118,24 @@ class SolverParametersWindow(ASubMainWindow, ListedSubWindow): # Create tab self._tab_widget.addTab(widget, f"{solver_long_name[st]}") - - def setup_connections(self): - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - def current_tab(self): return self.find(QTabWidget, "tabWidget")\ .currentWidget()\ .objectName()\ .replace("tab_", "") - def undo(self): + def _undo(self): tab = self.current_tab() self._table[tab].undo() self._set_current_reach() - def redo(self): + def _redo(self): tab = self.current_tab() self._table[tab].redo() self._set_current_reach() - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") diff --git a/src/View/SolverParameters/translate.py b/src/View/SolverParameters/translate.py index 2508629e..01c51a70 100644 --- a/src/View/SolverParameters/translate.py +++ b/src/View/SolverParameters/translate.py @@ -18,66 +18,60 @@ from PyQt5.QtCore import QCoreApplication +from View.Tools.PamhyrTranslate import PamhyrTranslate + _translate = QCoreApplication.translate -table_headers = None -yes_no = None -r_yes_no = None -names = None +class ParamTranslate(PamhyrTranslate): + def __init__(self): + super(ParamTranslate, self).__init__() -def init(): - global table_headers - table_headers = { - "name": _translate("SolverParameters", "Name"), - "value": _translate("SolverParameters", "Value") - } + self._sub_dict["table_headers"] = { + "name": _translate("SolverParameters", "Name"), + "value": _translate("SolverParameters", "Value") + } - # Used to translate user parameter with value yes or no - global yes_no - yes_no = { - "yes": _translate("SolverParameters", "Yes"), - "no": _translate("SolverParameters", "No"), - "y": _translate("SolverParameters", "Y"), - "n": _translate("SolverParameters", "N"), - } + self._sub_dict["yes_no"] = { + "yes": _translate("SolverParameters", "Yes"), + "no": _translate("SolverParameters", "No"), + "y": _translate("SolverParameters", "Y"), + "n": _translate("SolverParameters", "N"), + } - # Reverse - global r_yes_no - r_yes_no = { - _translate("SolverParameters", "Yes"): "yes", - _translate("SolverParameters", "No"): "no", - _translate("SolverParameters", "Y"): "y", - _translate("SolverParameters", "N"): "n", - } + self._sub_dict["r_yes_no"] = { + _translate("SolverParameters", "Yes"): "yes", + _translate("SolverParameters", "No"): "no", + _translate("SolverParameters", "Y"): "y", + _translate("SolverParameters", "N"): "n", + } - global names - names = { - "all_init_time": _translate("SolverParameters", "Initial time (jj:hh:mm:ss)"), - "all_final_time": _translate("SolverParameters", "Final time (jj:hh:mm:ss)"), - "all_timestep": _translate("SolverParameters", "Timestep (second)"), - "all_command_line_arguments": _translate("SolverParameters", "Command line arguments"), - # Mage specific parameters - "mage_min_timestep": _translate("SolverParameters", "Minimum timestep (second)"), - "mage_timestep_tra": _translate("SolverParameters", "Time step of writing on .TRA"), - "mage_timestep_bin": _translate("SolverParameters", "Time step of writing on .BIN"), - "mage_implicitation": _translate("SolverParameters", "Implicitation parameter"), - "mage_continuity_discretization": _translate("SolverParameters", "Continuity discretization type (S/L)"), - "mage_qsj_discretization": _translate("SolverParameters", "QSJ discretization (A/B)"), - "mage_stop_criterion_iterations": _translate("SolverParameters", "Stop criterion iterations (G/A/R)"), - "mage_iteration_type": _translate("SolverParameters", "Iteration type"), - "mage_smooth_coef": _translate("SolverParameters", "Smoothing coefficient"), - "mage_cfl_max": _translate("SolverParameters", "Maximun accepted number of CFL"), - "mage_min_height": _translate("SolverParameters", "Minimum water height (meter)"), - "mage_max_niter": _translate("SolverParameters", "Maximun number of iterations (< 100)"), - "mage_timestep_reduction_factor": _translate("SolverParameters", "Timestep reduction factor"), - "mage_precision_reduction_factor_Z": _translate("SolverParameters", "Reduction precision factor of Z"), - "mage_precision_reduction_factor_Q": _translate("SolverParameters", "Reduction precision factor of Q"), - "mage_precision_reduction_factor_r": _translate("SolverParameters", "Reduction precision factor of residue"), - "mage_niter_max_precision": _translate("SolverParameters", "Number of iteration at maximum precision"), - "mage_niter_before_switch": _translate("SolverParameters", "Number of iteration before switch"), - "mage_max_froude": _translate("SolverParameters", "Maximum accepted Froude number"), - "mage_diffluence_node_height_balance": _translate("SolverParameters", "Diffluence node height balance"), - "mage_compute_reach_volume_balance": _translate("SolverParameters", "Compute reach volume balance (Y/N)"), - "mage_max_reach_volume_balance": _translate("SolverParameters", "Maximum reach volume balance"), - "mage_min_reach_volume_to_check": _translate("SolverParameters", "Minimum reach volume to check"), - } + self._sub_dict["names"] = { + "all_init_time": _translate("SolverParameters", "Initial time (jj:hh:mm:ss)"), + "all_final_time": _translate("SolverParameters", "Final time (jj:hh:mm:ss)"), + "all_timestep": _translate("SolverParameters", "Timestep (second)"), + "all_command_line_arguments": _translate("SolverParameters", "Command line arguments"), + # Mage specific parameters + "mage_min_timestep": _translate("SolverParameters", "Minimum timestep (second)"), + "mage_timestep_tra": _translate("SolverParameters", "Time step of writing on .TRA"), + "mage_timestep_bin": _translate("SolverParameters", "Time step of writing on .BIN"), + "mage_implicitation": _translate("SolverParameters", "Implicitation parameter"), + "mage_continuity_discretization": _translate("SolverParameters", "Continuity discretization type (S/L)"), + "mage_qsj_discretization": _translate("SolverParameters", "QSJ discretization (A/B)"), + "mage_stop_criterion_iterations": _translate("SolverParameters", "Stop criterion iterations (G/A/R)"), + "mage_iteration_type": _translate("SolverParameters", "Iteration type"), + "mage_smooth_coef": _translate("SolverParameters", "Smoothing coefficient"), + "mage_cfl_max": _translate("SolverParameters", "Maximun accepted number of CFL"), + "mage_min_height": _translate("SolverParameters", "Minimum water height (meter)"), + "mage_max_niter": _translate("SolverParameters", "Maximun number of iterations (< 100)"), + "mage_timestep_reduction_factor": _translate("SolverParameters", "Timestep reduction factor"), + "mage_precision_reduction_factor_Z": _translate("SolverParameters", "Reduction precision factor of Z"), + "mage_precision_reduction_factor_Q": _translate("SolverParameters", "Reduction precision factor of Q"), + "mage_precision_reduction_factor_r": _translate("SolverParameters", "Reduction precision factor of residue"), + "mage_niter_max_precision": _translate("SolverParameters", "Number of iteration at maximum precision"), + "mage_niter_before_switch": _translate("SolverParameters", "Number of iteration before switch"), + "mage_max_froude": _translate("SolverParameters", "Maximum accepted Froude number"), + "mage_diffluence_node_height_balance": _translate("SolverParameters", "Diffluence node height balance"), + "mage_compute_reach_volume_balance": _translate("SolverParameters", "Compute reach volume balance (Y/N)"), + "mage_max_reach_volume_balance": _translate("SolverParameters", "Maximum reach volume balance"), + "mage_min_reach_volume_to_check": _translate("SolverParameters", "Minimum reach volume to check"), + } diff --git a/src/View/Stricklers/Window.py b/src/View/Stricklers/Window.py index 12620c4f..792ed4e3 100644 --- a/src/View/Stricklers/Window.py +++ b/src/View/Stricklers/Window.py @@ -20,8 +20,7 @@ import logging from tools import trace, timer -from View.ASubWindow import ASubMainWindow -from View.ListedSubWindow import ListedSubWindow +from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, @@ -44,40 +43,31 @@ from Model.Stricklers.Stricklers import Stricklers from View.Stricklers.UndoCommand import PasteCommand from View.Stricklers.Table import TableModel -from View.Stricklers.translate import table_headers, retranslate +from View.Stricklers.translate import StricklersTranslate _translate = QCoreApplication.translate logger = logging.getLogger() -class StricklersWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Stricklers", study=None, config=None, parent=None): - self._title = title + " - " + study.name +class StricklersWindow(PamhyrWindow): + _pamhyr_ui = "Stricklers" + _pamhyr_name = "Stricklers" + + def __init__(self, study=None, config=None, parent=None): + name = self._pamhyr_name + " - " + study.name super(StricklersWindow, self).__init__( - name=title, ui="Stricklers", parent=parent + title = name, + study = study, + config = config, + trad = StricklersTranslate(), + parent = parent ) - self._study = study - self._config = config - - self.setup_sc() self.setup_table() self.setup_connections() - self.ui.setWindowTitle(self._title) - - def setup_sc(self): - self._undo_stack = QUndoStack() - - self.undo_sc = QShortcut(QKeySequence.Undo, self) - self.redo_sc = QShortcut(QKeySequence.Redo, self) - self.copy_sc = QShortcut(QKeySequence.Copy, self) - self.paste_sc = QShortcut(QKeySequence.Paste, self) - - def setup_table(self): - retranslate() self._table = {} for t in ["app", "study"]: @@ -89,7 +79,7 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow): self._table[t] = TableModel( table_view = table, - table_headers = table_headers, + table_headers = self._trad.get_dict("table_headers"), editable_headers = ["name", "comment", "minor", "medium"], data = data, undo = self._undo_stack, @@ -105,11 +95,6 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) - self.undo_sc.activated.connect(self.undo) - self.redo_sc.activated.connect(self.redo) - self.copy_sc.activated.connect(self.copy) - self.paste_sc.activated.connect(self.paste) - def index_selected_rows(self): table = self.find(QTableView, f"tableView_study") return list( @@ -139,14 +124,14 @@ class StricklersWindow(ASubMainWindow, ListedSubWindow): def sort(self): self._table['study'].sort(False) - def copy(self): + def _copy(self): logger.info("TODO: copy") - def paste(self): + def _paste(self): logger.info("TODO: paste") - def undo(self): + def _undo(self): self._table['study'].undo() - def redo(self): + def _redo(self): self._table['study'].redo() diff --git a/src/View/Stricklers/translate.py b/src/View/Stricklers/translate.py index 9102ac6d..489094cf 100644 --- a/src/View/Stricklers/translate.py +++ b/src/View/Stricklers/translate.py @@ -18,21 +18,18 @@ from PyQt5.QtCore import QCoreApplication -from Model.LateralContribution.LateralContributionTypes import ( - NotDefined, LateralContrib, Rain, Evaporation, -) +from View.Tools.PamhyrTranslate import PamhyrTranslate _translate = QCoreApplication.translate -table_headers = { - "name": _translate("LateralContribution", "Name"), - "minor": _translate("LateralContribution", "Minor bed"), - "medium": _translate("LateralContribution", "Medium bed"), - "comment": _translate("LateralContribution", "Comment"), -} -def retranslate(): - table_headers["name"] = _translate("LateralContribution", "Name") - table_headers["minor"] = _translate("LateralContribution", "Minor bed") - table_headers["medium"] = _translate("LateralContribution", "Medium bed") - table_headers["comment"] = _translate("LateralContribution", "Comment") +class StricklersTranslate(PamhyrTranslate): + def __init__(self): + super(StricklersTranslate, self).__init__() + + self._sub_dict["table_headers"] = { + "name": _translate("LateralContribution", "Name"), + "minor": _translate("LateralContribution", "Minor bed"), + "medium": _translate("LateralContribution", "Medium bed"), + "comment": _translate("LateralContribution", "Comment"), + } diff --git a/src/View/Study/Window.py b/src/View/Study/Window.py index 7218f9d3..871d0545 100644 --- a/src/View/Study/Window.py +++ b/src/View/Study/Window.py @@ -17,7 +17,7 @@ # -*- coding: utf-8 -*- from Model.Study import Study -from View.ASubWindow import ASubWindow +from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtCore import QCoreApplication @@ -27,27 +27,37 @@ from PyQt5.QtWidgets import ( _translate = QCoreApplication.translate -class NewStudyWindow(ASubWindow): - def __init__(self, study=None, title="New Study", parent=None): - super(NewStudyWindow, self).__init__(name=title, ui="NewStudy", parent=parent) - self.ui.setWindowTitle(title) +class NewStudyWindow(PamhyrDialog): + _pamhyr_ui = "NewStudy" + _pamhyr_name = "Edit/New Study" - self.parent = parent - self.study = study + def __init__(self, study=None, config = None, parent=None): + if study is not None: + name = f"Edit study - {study.name}" + else: + name = "New study" - if not self.study is None: - self.set_line_edit_text("lineEdit_name", self.study.name) - self.set_text_edit_text("textEdit_description", self.study.description) - self.set_datetime_edit("dateTimeEdit_date", self.study.date) + super(NewStudyWindow, self).__init__( + title = name, + study = study, + config = config, + options = [], + parent = parent + ) + + if not self._study is None: + self.set_line_edit_text("lineEdit_name", self._study.name) + self.set_text_edit_text("textEdit_description", self._study.description) + self.set_datetime_edit("dateTimeEdit_date", self._study.date) self.find(QLabel, "label_creation_date_data").setText( - self.study.creation_date.isoformat(sep=" ") + self._study.creation_date.isoformat(sep=" ") ) self.find(QLabel, "label_last_modification_data").setText( - self.study.last_save_date.isoformat(sep=" ") + self._study.last_save_date.isoformat(sep=" ") ) - if self.study.time_system == "date": + if self._study.time_system == "date": self.set_radio_button("radioButton_date", True) self.find(QLabel, "label_date").setEnabled(True) self.find(QDateTimeEdit, "dateTimeEdit_date").setEnabled(True) @@ -76,18 +86,18 @@ class NewStudyWindow(ASubWindow): name = self.get_line_edit_text("lineEdit_name") description = self.get_text_edit_text("textEdit_description") - if self.study is None: + if self._study is None: study = Study.new(name, description) if self.get_radio_button("radioButton_date"): date = self.get_datetime_edit("dateTimeEdit_date") study.use_date(date) self.parent.set_model(study) else: - self.study.name = name - self.study.description = description + self._study.name = name + self._study.description = description if self.get_radio_button("radioButton_date"): date = self.get_datetime_edit("dateTimeEdit_date") - self.study.use_date(date) + self._study.use_date(date) else: - self.study.use_time() + self._study.use_time() self.done(True) diff --git a/src/View/ASubWindow.py b/src/View/Tools/ASubWindow.py similarity index 95% rename from src/View/ASubWindow.py rename to src/View/Tools/ASubWindow.py index 4e1c1327..b6adcc82 100644 --- a/src/View/ASubWindow.py +++ b/src/View/Tools/ASubWindow.py @@ -463,11 +463,16 @@ class ASubWindowFeatures(object): # Top level interface class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit): - def __init__(self, name="", ui="dummy", parent=None): + def __init__(self, name="", ui="dummy", parent=None, **kwargs): super(ASubMainWindow, self).__init__(parent=parent) if ui is not None: self.ui = loadUi( - os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"), + os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", "ui", f"{ui}.ui" + ) + ), self ) @@ -496,10 +501,15 @@ class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit): return self.ui.findChild(qtype, name) class ASubWindow(QDialog, ASubWindowFeatures, WindowToolKit): - def __init__(self, name="", ui="dummy", parent=None): + def __init__(self, name="", ui="dummy", parent=None, **kwargs): super(ASubWindow, self).__init__(parent=parent) self.ui = loadUi( - os.path.join(os.path.dirname(__file__), "ui", f"{ui}.ui"), + os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", "ui", f"{ui}.ui" + ) + ), self ) self.name = name @@ -530,7 +540,12 @@ class AWidget(QWidget, ASubWindowFeatures): def __init__(self, ui="", parent=None): super(AWidget, self).__init__(parent=parent) self.ui = loadUi( - os.path.join(os.path.dirname(__file__), "ui", "Widgets", f"{ui}.ui"), + os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "..", "ui", "Widgets", f"{ui}.ui" + ) + ), self ) self.parent = parent diff --git a/src/View/ListedSubWindow.py b/src/View/Tools/ListedSubWindow.py similarity index 90% rename from src/View/ListedSubWindow.py rename to src/View/Tools/ListedSubWindow.py index b4dfe416..6c93b84e 100644 --- a/src/View/ListedSubWindow.py +++ b/src/View/Tools/ListedSubWindow.py @@ -22,7 +22,7 @@ from functools import reduce logger = logging.getLogger() class ListedSubWindow(object): - def __init__(self): + def __init__(self, **kwargs): super(ListedSubWindow, self).__init__() self.sub_win_cnt = 0 self.sub_win_list = [] @@ -36,7 +36,10 @@ class ListedSubWindow(object): def sub_win_add(self, name, win): self.sub_win_list.append((name, win)) self.sub_win_cnt += 1 - logger.info(f"Open window: {name} ({self.sub_win_cnt})") + try: + logger.info(f"Open window: {name}: {self.sub_win_cnt}: {win.hash()}") + except: + logger.info(f"Open window: {name}: {self.sub_win_cnt}: X") def sub_win_del(self, name): self.sub_win_list = list( @@ -46,7 +49,7 @@ class ListedSubWindow(object): ) ) self.sub_win_cnt = len(self.sub_win_list) - logger.info(f"Close window: {name} ({self.sub_win_cnt})") + logger.info(f"Close window: {name}: {self.sub_win_cnt}") def _sub_win_exists(self, name): return reduce( diff --git a/src/View/Tools/PamhyrPlot.py b/src/View/Tools/PamhyrPlot.py new file mode 100644 index 00000000..b06ec2c2 --- /dev/null +++ b/src/View/Tools/PamhyrPlot.py @@ -0,0 +1,43 @@ +# PamhyrPlot.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 . + +# -*- coding: utf-8 -*- + +from View.Tools.Plot.APlot import APlot +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar + +class PamhyrPlot(APlot): + def __init__(self, data=None, trad=None, + canvas=None, toolbar=None, + parent=None): + if canvas is None: + canvas = MplCanvas() + + self._trad = trad + self._canvas = canvas + self._toolbar = toolbar + self._parent = parent + + super(PamhyrPlot, self).__init__(data = data) + + @property + def canvas(self): + return self._canvas + + @property + def toolbar(self): + return self._toolbar diff --git a/src/View/Tools/PamhyrTable.py b/src/View/Tools/PamhyrTable.py index 623ebd3b..d809dced 100644 --- a/src/View/Tools/PamhyrTable.py +++ b/src/View/Tools/PamhyrTable.py @@ -33,7 +33,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, - QComboBox, QStyledItemDelegate, + QComboBox, QStyledItemDelegate, QHeaderView, ) logger = logging.getLogger() @@ -75,13 +75,14 @@ class PamhyrTableModel(QAbstractTableModel): ) def __init__(self, - table_view=None, - table_headers={}, - editable_headers=[], + table_view = None, + table_headers = {}, + editable_headers = [], delegates = {}, - data=None, - undo=None, - opt_data=None): + trad = None, + data = None, + undo = None, + opt_data = None): super(PamhyrTableModel, self).__init__() self._table_view = table_view @@ -90,6 +91,7 @@ class PamhyrTableModel(QAbstractTableModel): self._headers = list(table_headers.keys()) self._editable_headers = editable_headers self._delegates = delegates + self._trad = trad self._data = data self._opt_data = opt_data @@ -98,10 +100,17 @@ class PamhyrTableModel(QAbstractTableModel): self._setup_delegates() self._setup_lst() + self._table_view_configure() def _setup_lst(self): self._lst = self._data + def _table_view_configure(self): + self._table_view.setModel(self) + self._table_view.setSelectionBehavior(QAbstractItemView.SelectRows) + self._table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self._table_view.setAlternatingRowColors(True) + def flags(self, index): column = index.column() @@ -113,10 +122,10 @@ class PamhyrTableModel(QAbstractTableModel): return options - def rowCount(self, parent): + def rowCount(self, parent=QModelIndex()): return len(self._lst) - def columnCount(self, parent): + def columnCount(self, parent=QModelIndex()): return len(self._headers) def headerData(self, section, orientation, role): @@ -138,3 +147,6 @@ class PamhyrTableModel(QAbstractTableModel): def redo(self): self._undo.redo() self.layoutChanged.emit() + + def update(self): + self.layoutChanged.emit() diff --git a/src/View/Tools/PamhyrTranslate.py b/src/View/Tools/PamhyrTranslate.py new file mode 100644 index 00000000..67befa23 --- /dev/null +++ b/src/View/Tools/PamhyrTranslate.py @@ -0,0 +1,58 @@ +# PamhyrTranslate.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 . + +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + + +class PamhyrTranslate(object): + def __init__(self): + # Module global dictionnary + self._dict = {} + # Module sub dictionnary + self._sub_dict = {} + + self._dict["pamhyr"] = _translate("Pamhyr", "Pamhyr2") + + def __getitem__(self, index): + if index not in self._dict: + return None + return self._dict[index] + + def _get_in_sub_dict(self, dictionary, index): + if dictionary not in self._sub_dict: + return None + return self._sub_dict[index] + + def get(self, dictionary, index): + if dictionary == "global": + return self[index] + + return self._get_in_sub_dict(dictionary, index) + + def get_dict(self, dictionary): + _dict = None + + if dictionary == "global": + _dict = self._dict + + if dictionary in self._sub_dict: + _dict = self._sub_dict[dictionary] + + return _dict diff --git a/src/View/Tools/PamhyrWidget.py b/src/View/Tools/PamhyrWidget.py index 60e06174..f906beed 100644 --- a/src/View/Tools/PamhyrWidget.py +++ b/src/View/Tools/PamhyrWidget.py @@ -18,6 +18,8 @@ import logging +from datetime import datetime, timedelta + from PyQt5.QtCore import ( QModelIndex, QRect, QTime, QDateTime, ) @@ -26,10 +28,21 @@ from PyQt5.QtWidgets import ( QSpinBox, QTimeEdit, QDateTimeEdit, QItemDelegate, ) -from View.ASubWindow import AWidget +from View.Tools.ASubWindow import AWidget logger = logging.getLogger() +class PamhyrWidget(AWidget): + _pamhyr_ui = None + _pamhyr_name = "PamhyrWidget" + + def __init__(self, parent=None): + super(PamhyrWidget, self).__init__( + ui = self._pamhyr_ui, + parent = parent + ) + + class PamhyrExtendedTimeEdit(AWidget): def __init__(self, parent=None): super(PamhyrExtendedTimeEdit, self).__init__( diff --git a/src/View/Tools/PamhyrWindow.py b/src/View/Tools/PamhyrWindow.py new file mode 100644 index 00000000..fb4bf1a1 --- /dev/null +++ b/src/View/Tools/PamhyrWindow.py @@ -0,0 +1,188 @@ +# PamhyrWindow.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 . + +# -*- coding: utf-8 -*- + +import logging + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtWidgets import ( + QUndoStack, QShortcut, +) + +from View.Tools.ASubWindow import ASubMainWindow, ASubWindow +from View.Tools.ListedSubWindow import ListedSubWindow + +logger = logging.getLogger() + +class PamhyrWindowTools(object): + def __init__(self, options = ["undo", "copy"], parent = None, **kwargs): + super(PamhyrWindowTools, self).__init__() + + self._hash_data = [] + self._undo_stack = None + + if "undo" in options: + self._init_undo() + + if "copy" in options: + self._init_copy() + + # Undo/Redo + + def _init_undo(self): + self._undo_stack = QUndoStack() + + self._undo_sc = QShortcut(QKeySequence.Undo, self) + self._redo_sc = QShortcut(QKeySequence.Redo, self) + + self._undo_sc.activated.connect(self._undo) + self._redo_sc.activated.connect(self._redo) + + def _undo(self): + if self._undo_stack is not None: + self._undo_stack.undo() + self._update() + + def _redo(self): + if self._undo_stack is not None: + self._undo_stack.redo() + self._update() + + # Copy/Paste + + def _init_copy(self): + self._copy_sc = QShortcut(QKeySequence.Copy, self) + self._paste_sc = QShortcut(QKeySequence.Paste, self) + + self._copy_sc.activated.connect(self._copy) + self._paste_sc.activated.connect(self._paste) + + def _copy(self): + if self._copy_stack is not None: + self._copy_stack.copy() + self._update() + + def _paste(self): + if self._copy_stack is not None: + self._copy_stack.redo() + self._update() + + # Display + + def _set_title(self): + """Apply `self._title` at current window title displayed + + Returns: + Nothing + """ + self.ui.setWindowTitle(self._title) + + def _update(self): + """Update window display component + + Returns: + Nothing + """ + self._set_title() + + # Hash methods + + @classmethod + def _hash(cls, data): + """Compute window hash + + Args: + data: window data parameters + + Returns: + The hash + """ + hash_str = cls._pamhyr_name + hash_str += cls._pamhyr_ui + + for el in data: + hash_str += repr(el) + + h = hash(hash_str) + logger.debug(f"Compute hash = {h} for window {cls._pamhyr_name}") + + return h + + def hash(self): + """Compute window hash + + Returns: + The hash + """ + return self._hash(self._hash_data) + + +class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools): + _pamhyr_ui = "dummy" + _pamhyr_name = "PamhyrWindow" + + def __init__(self, + title = "Pamhyr2", + study = None, config = None, trad = None, + options = ["undo", "copy"], + parent = None): + self._title = title + self._study = study + self._config = config + self._trad = trad + self._parent = parent + + super(PamhyrWindow, self).__init__( + name = self._pamhyr_name, + ui = self._pamhyr_ui, + parent = parent, + ) + + self._hash_data.append(self._study) + self._hash_data.append(self._config) + + self._set_title() + + +class PamhyrDialog(ASubWindow, ListedSubWindow, PamhyrWindowTools): + _pamhyr_ui = "dummy" + _pamhyr_name = "PamhyrWindow" + + def __init__(self, + title = "Pamhyr2", + study = None, config = None, trad = None, + options = ["undo", "copy"], + parent = None): + self._title = title + self._study = study + self._config = config + self._trad = trad + self._parent = parent + + super(PamhyrDialog, self).__init__( + name = self._pamhyr_name, + ui = self._pamhyr_ui, + parent = parent, + ) + + self._hash_data.append(self._study) + self._hash_data.append(self._config) + + self._set_title() diff --git a/src/View/Plot/APlot.py b/src/View/Tools/Plot/APlot.py similarity index 84% rename from src/View/Plot/APlot.py rename to src/View/Tools/Plot/APlot.py index a863f1e6..7d8de307 100644 --- a/src/View/Plot/APlot.py +++ b/src/View/Tools/Plot/APlot.py @@ -19,26 +19,16 @@ from Model.Except import NotImplementedMethodeError class APlot(object): - def __init__(self, canvas=None, data=None, toolbar=None): + def __init__(self, data=None): super(APlot, self).__init__() self._init = False - - self._canvas = canvas self._data = data - self._toolbar = toolbar - - @property - def canvas(self): - return self._canvas @property def data(self): return self._data - @property - def toolbar(self): - return self._toolbar def draw(self): """Draw plot diff --git a/src/View/Plot/mpl_canvas_onpick_event.py b/src/View/Tools/Plot/OnPickEvent.py similarity index 100% rename from src/View/Plot/mpl_canvas_onpick_event.py rename to src/View/Tools/Plot/OnPickEvent.py diff --git a/src/View/Plot/MplCanvas.py b/src/View/Tools/Plot/PamhyrCanvas.py similarity index 99% rename from src/View/Plot/MplCanvas.py rename to src/View/Tools/Plot/PamhyrCanvas.py index ee1f5373..c4bbf4d3 100644 --- a/src/View/Plot/MplCanvas.py +++ b/src/View/Tools/Plot/PamhyrCanvas.py @@ -17,7 +17,6 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure - class MplCanvas(FigureCanvasQTAgg): def __init__(self, width=5, height=4, dpi=100): fig = Figure( diff --git a/src/View/Plot/PamhyrToolbar.py b/src/View/Tools/Plot/PamhyrToolbar.py similarity index 98% rename from src/View/Plot/PamhyrToolbar.py rename to src/View/Tools/Plot/PamhyrToolbar.py index 6a729f04..c88dfacf 100644 --- a/src/View/Plot/PamhyrToolbar.py +++ b/src/View/Tools/Plot/PamhyrToolbar.py @@ -81,8 +81,6 @@ class PamhyrPlotToolbar(NavigationToolbar2QT): os.path.abspath(f"{file_path}/../ui/ressources/zoom.png") )) - logger.info(os.path.abspath(f"{file_path}/../ui/ressources/zoom.png")) - icons.append(("zoom",icon_zoom)) if "iso" in items: diff --git a/src/View/ui/DebugRepl.ui b/src/View/ui/DebugRepl.ui index 03494a0c..c0fc844e 100644 --- a/src/View/ui/DebugRepl.ui +++ b/src/View/ui/DebugRepl.ui @@ -21,7 +21,13 @@ - + + + + Monospace + + + @@ -44,6 +50,11 @@ + + + Monospace + + true diff --git a/src/View/ui/GeometryCrossSection.ui b/src/View/ui/GeometryCrossSection.ui new file mode 100644 index 00000000..67e06ced --- /dev/null +++ b/src/View/ui/GeometryCrossSection.ui @@ -0,0 +1,134 @@ + + + MainWindow + + + + 0 + 0 + 1120 + 630 + + + + MainWindow + + + + + + + Qt::Horizontal + + + + + + + + + + + + + 0 + 0 + 1120 + 22 + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + ressources/gtk-add.pngressources/gtk-add.png + + + add + + + Add a point on cross-section + + + + + + ressources/gtk-remove.pngressources/gtk-remove.png + + + delete + + + Delete selected point(s) + + + + + + ressources/up.pngressources/up.png + + + up + + + Move up selected point(s) + + + + + + ressources/down.pngressources/down.png + + + down + + + Mode down selected point(s) + + + + + + ressources/gtk-sort-ascending.pngressources/gtk-sort-ascending.png + + + sort_asc + + + Sort points by nearest neighbor + + + + + + ressources/gtk-sort-descending.pngressources/gtk-sort-descending.png + + + sort_des + + + Sort reversed points by nearest neighbor + + + + + + diff --git a/src/View/ui/GeometryReach.ui b/src/View/ui/GeometryReach.ui new file mode 100644 index 00000000..e09d9d35 --- /dev/null +++ b/src/View/ui/GeometryReach.ui @@ -0,0 +1,231 @@ + + + MainWindow + + + + 0 + 0 + 1280 + 720 + + + + MainWindow + + + + + + + + + + + + Qt::Horizontal + + + + + Qt::Vertical + + + + Qt::Horizontal + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + true + + + true + + + + + + + + + + + + + ressources/go-up2.pngressources/go-up2.png + + + + + + + + + + + ressources/go-down1.pngressources/go-down1.png + + + + + + + + + + + + + 0 + 0 + 1280 + 22 + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + + + import + + + Import geometry + + + + + export + + + Export geometry + + + + + + ressources/gtk-add.pngressources/gtk-add.png + + + add + + + Add a cross-section + + + + + + ressources/gtk-remove.pngressources/gtk-remove.png + + + delete + + + Delete selected cross-section(s) + + + + + + ressources/edit.pngressources/edit.png + + + edit + + + Edit selected cross section(s) + + + + + + ressources/gtk-sort-ascending.pngressources/gtk-sort-ascending.png + + + sort_asc + + + Sort cross-sections by ascending KP + + + + + + ressources/gtk-sort-descending.pngressources/gtk-sort-descending.png + + + sort_des + + + Sort cross-sections by descending KP + + + + + + ressources/up.pngressources/up.png + + + up + + + Move up selected cross-section(s) + + + + + + ressources/down.pngressources/down.png + + + down + + + Move down selected cross-section(s) + + + + + + diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui index ea77d698..951dadc9 100644 --- a/src/View/ui/MainWindow.ui +++ b/src/View/ui/MainWindow.ui @@ -141,12 +141,11 @@ - + - &Plots + &Results - - + @@ -161,8 +160,23 @@ &Help - - + + + Help + + + + Pamhyr2 + + + + + + + + + + @@ -178,7 +192,7 @@ - + @@ -597,12 +611,12 @@ Close - + false - Hydrograph + Visualize last results @@ -896,6 +910,41 @@ Edit reach sediment layers + + + Doc + + + + + Pamhyr2 developer + + + + + Mage + + + + + Users (pdf) + + + + + Users (html) + + + + + Developers (pdf) + + + + + Developers (html) + + diff --git a/src/View/ui/ProfileSedimentLayers.ui b/src/View/ui/ProfileSedimentLayers.ui index b9f5106d..05f6618e 100644 --- a/src/View/ui/ProfileSedimentLayers.ui +++ b/src/View/ui/ProfileSedimentLayers.ui @@ -39,20 +39,6 @@ - - - toolBar - - - TopToolBarArea - - - false - - - - - diff --git a/src/View/ui/WebView.ui b/src/View/ui/WebView.ui new file mode 100644 index 00000000..7d0a41bf --- /dev/null +++ b/src/View/ui/WebView.ui @@ -0,0 +1,77 @@ + + + MainWindow + + + + 0 + 0 + 800 + 450 + + + + MainWindow + + + + + + + + + + + + 0 + 0 + 800 + 22 + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + Firefox + + + Open document in Firefox + + + + + + ressources/gtk-go-back.pngressources/gtk-go-back.png + + + back + + + + + + ressources/gtk-go-forward.pngressources/gtk-go-forward.png + + + forward + + + + + + diff --git a/src/pamhyr.py b/src/pamhyr.py index b4bd401f..0d595df4 100755 --- a/src/pamhyr.py +++ b/src/pamhyr.py @@ -34,6 +34,7 @@ from tools import ( from View.MainWindow import ApplicationWindow from Model.Study import Study +from Scripts.P3DST import Script3DST from Scripts.Hello import ScriptHello from init import license, setup_lang @@ -42,6 +43,7 @@ logger = logging.getLogger() scripts = { "hello": ScriptHello, + "3DST": Script3DST, } def usage(argv):