diff --git a/doc/tools/PamhyrDoc.cls b/doc/tools/PamhyrDoc.cls index 431f8942..6ef42acf 100644 --- a/doc/tools/PamhyrDoc.cls +++ b/doc/tools/PamhyrDoc.cls @@ -157,12 +157,12 @@ %% %% FIXME: Delete me for the first realease -\usepackage{draftwatermark} -\SetWatermarkLightness{0.8} -\SetWatermarkAngle{25} -\SetWatermarkScale{3} -\SetWatermarkFontSize{1cm} -\SetWatermarkText{Work in progress} +%% \usepackage{draftwatermark} +%% \SetWatermarkLightness{0.8} +%% \SetWatermarkAngle{25} +%% \SetWatermarkScale{3} +%% \SetWatermarkFontSize{1cm} +%% \SetWatermarkText{Work in progress} %% Icons diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 86f2cb6e..433da557 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -232,6 +232,8 @@ class BoundaryCondition(SQLSubModel): type TEXT NOT NULL, tab TEXT NOT NULL, node INTEGER, + d50 REAL DEFAULT 0.002, + sigma REAL DEFAULT 1, {Scenario.create_db_add_scenario()}, {Scenario.create_db_add_scenario_fk()}, FOREIGN KEY(node) REFERENCES river_node(pamhyr_id), @@ -258,6 +260,17 @@ class BoundaryCondition(SQLSubModel): "ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE" ) + if major == "0" and int(minor) <= 2: + if int(release) < 4: + execute( + "ALTER TABLE boundary_condition " + + "ADD COLUMN d50 REAL DEFAULT 0.002" + ) + execute( + "ALTER TABLE boundary_condition " + + "ADD COLUMN sigma REAL DEFAULT 1" + ) + return cls._update_submodel(execute, version, data) @classmethod @@ -316,7 +329,8 @@ class BoundaryCondition(SQLSubModel): return new table = execute( - "SELECT pamhyr_id, deleted, name, type, node, scenario " + + "SELECT pamhyr_id, deleted, name, type, node, d50, sigma, " + + "scenario " + "FROM boundary_condition " + f"WHERE tab = '{tab}' " + f"AND scenario = {scenario.id} " + @@ -331,6 +345,8 @@ class BoundaryCondition(SQLSubModel): name = next(it) t = next(it) node = next(it) + d50 = next(it) + sigma = next(it) owner_scenario = next(it) ctor = cls._get_ctor_from_type(t) @@ -340,9 +356,14 @@ class BoundaryCondition(SQLSubModel): status=data['status'], owner_scenario=owner_scenario ) + if deleted: bc.set_as_deleted() + if t == "SL": + bc.d50 = d50 + bc.sigma = sigma + bc.node = None if node != -1: bc.node = next(filter(lambda n: n.id == node, nodes), None) @@ -375,15 +396,22 @@ class BoundaryCondition(SQLSubModel): if self._node is not None: node = self._node.id + d50 = 0.002 + sigma = 1 + if self._type == "SL": + d50 = self._d50 + sigma = self._sigma + execute( "INSERT INTO " + "boundary_condition(" + - "pamhyr_id, deleted, name, type, tab, node, scenario" + + "pamhyr_id, deleted, name, type, tab, node, d50, sigma, scenario" + ") " + "VALUES (" + f"{self._pamhyr_id}, {self._db_format(self.is_deleted())}, " + f"'{self._db_format(self._name)}', " + f"'{self._db_format(self._type)}', '{tab}', {node}, " + + f"{d50}, {sigma}, " + f"{self._status.scenario_id}" + ")" ) @@ -468,6 +496,60 @@ class BoundaryCondition(SQLSubModel): def has_node(self): return self._node is not None + @property + def d50(self): + return self._d50 + + @d50.setter + def d50(self, value): + self._d50 = float(value) + self.modified() + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, value): + self._sigma = float(value) + self.modified() + + @property + def d50(self): + return self._d50 + + @d50.setter + def d50(self, value): + self._d50 = float(value) + self.modified() + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, value): + self._sigma = float(value) + self.modified() + + @property + def d50(self): + return self._d50 + + @d50.setter + def d50(self, value): + self._d50 = float(value) + self.modified() + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, value): + self._sigma = float(value) + self.modified() + @property def header(self): return self._header.copy() diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py index 24fa3cf2..a83d49b5 100644 --- a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py +++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py @@ -144,6 +144,7 @@ class Data(SQLSubModel): "SELECT pamhyr_id, deleted, data0, data1, scenario FROM " + "boundary_condition_data_adists " + f"WHERE bca = {bca.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + f"AND scenario = {scenario.id}" ) @@ -300,6 +301,12 @@ class BoundaryConditionAdisTS(SQLSubModel): if pol_id == -1: continue + if pol_id not in pid_pol: + # ⚠️ cas important : probablement déjà migré + print(f"[WARN] pol_id {pol_id} not in pid_pol " + + f"→ probably already migrated") + continue + execute( f"UPDATE boundary_condition_adists " + f"SET pollutant = {pid_pol[pol_id]} " + @@ -312,17 +319,21 @@ class BoundaryConditionAdisTS(SQLSubModel): status = data['status'] nodes = data['nodes'] scenario = data["scenario"] + pollutant = data.get("pollutant") loaded = data['loaded_pid'] if scenario is None: return new - table = execute( - "SELECT pamhyr_id, deleted, pollutant, type, node, scenario " + + sql = ( + "SELECT pamhyr_id, deleted, pollutant, type, node, scenario " "FROM boundary_condition_adists " + - f"WHERE scenario = {scenario.id} " + - f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + f"WHERE scenario = {scenario.id} " ) + if pollutant is not None: + sql += f"AND pollutant = {pollutant.id} " + + table = execute(sql) if table is not None: for row in table: @@ -347,7 +358,7 @@ class BoundaryConditionAdisTS(SQLSubModel): bc.type = bc_type bc.node = None - if row[3] != -1: + if node != -1: tmp = next( filter( lambda n: n.id == node, nodes @@ -451,7 +462,7 @@ class BoundaryConditionAdisTS(SQLSubModel): @node.setter def node(self, node): self._node = node - self._status.modified() + self.modified() @property def header(self): @@ -460,7 +471,7 @@ class BoundaryConditionAdisTS(SQLSubModel): @header.setter def header(self, header): self._header = header - self._status.modified() + self.modified() @property def pollutant(self): @@ -469,7 +480,7 @@ class BoundaryConditionAdisTS(SQLSubModel): @pollutant.setter def pollutant(self, pollutant): self._pollutant = pollutant - self._status.modified() + self.modified() @property def type(self): @@ -478,7 +489,7 @@ class BoundaryConditionAdisTS(SQLSubModel): @type.setter def type(self, type): self._type = type - self._status.modified() + self.modified() @property def data(self): @@ -511,14 +522,17 @@ class BoundaryConditionAdisTS(SQLSubModel): return (new_0, new_1) def add(self, index: int): - value = (self._default_0, self._default_1) + value = Data( + self._default_0, self._default_1, + status=self._status + ) self._data.insert(index, value) - self._status.modified() + self.modified() return value def insert(self, index: int, value): self._data.insert(index, value) - self._status.modified() + self.modified() def delete_i(self, indexes): self._data = list( @@ -530,14 +544,14 @@ class BoundaryConditionAdisTS(SQLSubModel): ) ) ) - self._status.modified() + self.modified() def sort(self, _reverse=False, key=None): if key is None: self._data.sort(reverse=_reverse) else: self._data.sort(reverse=_reverse, key=key) - self._status.modified() + self.modified() def index(self, bc): self._data.index(bc) @@ -552,10 +566,10 @@ class BoundaryConditionAdisTS(SQLSubModel): return lst def _set_i_c_v(self, index, column, value): - v = list(self._data[index]) + v = self._data[index] v[column] = self._types[column](value) - self._data[index] = tuple(v) - self._status.modified() + self._data[index] = v + self.modified() def set_i_0(self, index: int, value): self._set_i_c_v(index, 0, value) diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py index 0d1d729f..4bb90dbc 100644 --- a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py +++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py @@ -38,9 +38,13 @@ class BoundaryConditionsAdisTSList(PamhyrModelList): if data is None: data = {} - new._lst = BoundaryConditionAdisTS._db_load( - execute, data - ) + previous_pollutant = data.get("pollutant") + data.pop("pollutant", None) + + new._lst = BoundaryConditionAdisTS._db_load(execute, data) + + if previous_pollutant is not None: + data["pollutant"] = previous_pollutant return new diff --git a/src/Model/D90AdisTS/D90AdisTSSpec.py b/src/Model/D90AdisTS/D90AdisTSSpec.py index 92f58f60..5af1c4bb 100644 --- a/src/Model/D90AdisTS/D90AdisTSSpec.py +++ b/src/Model/D90AdisTS/D90AdisTSSpec.py @@ -167,7 +167,7 @@ class D90AdisTSSpec(SQLSubModel): owner_scenario=owner_scenario ) if deleted: - new_spec.is_deleted() + new_spec.set_as_deleted() new_spec.reach = reach new_spec.start_rk = start_rk @@ -196,12 +196,12 @@ class D90AdisTSSpec(SQLSubModel): "d90_default, name, reach, " + "start_rk, end_rk, d90, enabled, scenario) " + "VALUES (" + - f"{self.id}, {self._db_format(self.is_deleted())}" + + f"{self.id}, {self._db_format(self.is_deleted())}, " + f"{d90_default}, " + f"'{self._db_format(self._name_section)}', " + f"{self._reach}, " + f"{self._start_rk}, {self._end_rk}, " + - f"{self._d90}, {self._enabled}" + + f"{self._d90}, {self._enabled}, " + f"{self._status.scenario_id}" + ")" ) diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py index c0088779..98b63fd5 100644 --- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py +++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py @@ -267,7 +267,7 @@ class InitialConditionsAdisTS(SQLSubModel): @name.setter def name(self, name): self._name = name - self._status.modified() + self.modified() @property def pollutant(self): @@ -276,7 +276,7 @@ class InitialConditionsAdisTS(SQLSubModel): @pollutant.setter def pollutant(self, pollutant): self._pollutant = pollutant - self._status.modified() + self.modified() @property def concentration(self): @@ -285,7 +285,7 @@ class InitialConditionsAdisTS(SQLSubModel): @concentration.setter def concentration(self, concentration): self._concentration = concentration - self._status.modified() + self.modified() @property def eg(self): @@ -294,7 +294,7 @@ class InitialConditionsAdisTS(SQLSubModel): @eg.setter def eg(self, eg): self._eg = eg - self._status.modified() + self.modified() @property def em(self): @@ -303,7 +303,7 @@ class InitialConditionsAdisTS(SQLSubModel): @em.setter def em(self, em): self._em = em - self._status.modified() + self.modified() @property def ed(self): @@ -312,7 +312,7 @@ class InitialConditionsAdisTS(SQLSubModel): @ed.setter def ed(self, ed): self._ed = ed - self._status.modified() + self.modified() @property def enabled(self): @@ -321,28 +321,45 @@ class InitialConditionsAdisTS(SQLSubModel): @enabled.setter def enabled(self, enabled): self._enabled = enabled - self._status.modified() + self.modified() def new(self, index): n = ICAdisTSSpec(status=self._status) self._data.insert(index, n) - self._status.modified() + self.modified() return n def delete(self, data): - self._data = list( - filter( - lambda x: x not in data, - self._data + list( + map( + lambda x: x.set_as_deleted(), + data ) ) - self._status.modified() + self.modified() def delete_i(self, indexes): - for ind in indexes: - del self._data[ind] - self._status.modified() + list( + map( + lambda e: e[1].set_as_deleted(), + filter( + lambda e: e[0] in indexes, + enumerate(self._data) + ) + ) + ) + self.modified() def insert(self, index, data): - self._data.insert(index, data) - self._status.modified() + if data in self._data: + self.undelete([data]) + else: + self._data.insert(index, data) + + self.modified() + + def undelete(self, lst): + for x in lst: + x.set_as_not_deleted() + + self.modified() diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py index ed9cf0d3..e79c505a 100644 --- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py +++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py @@ -31,7 +31,7 @@ class ICAdisTSSpec(SQLSubModel): _sub_classes = [] def __init__(self, id: int = -1, name: str = "", - status=None): + status=None, owner_scenario=None): super(ICAdisTSSpec, self).__init__() self._status = status @@ -194,7 +194,7 @@ class ICAdisTSSpec(SQLSubModel): new_spec.rate = rate new_spec.enabled = enabled - loaded.add(pid) + # loaded.add(pid) new.append(new_spec) data["scenario"] = scenario.parent @@ -211,7 +211,7 @@ class ICAdisTSSpec(SQLSubModel): sql = ( "INSERT INTO " + - "initial_conditions_adists_spec(id, deleted, " + + "initial_conditions_adists_spec(pamhyr_id, deleted, " + "ic_default, name, reach, " + "start_rk, end_rk, concentration, eg, em, ed, rate, " + "enabled, scenario) " + diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py index 85b2e0ab..302093e9 100644 --- a/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py +++ b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py @@ -132,11 +132,13 @@ class Data(SQLSubModel): if scenario is None: return new + lca = data["lca"] + table = execute( - "SELECT pamhyr_id, deleted, data0, data1 " + + "SELECT pamhyr_id, deleted, data0, data1, scenario " + "FROM lateral_contribution_data_adists " + f"WHERE scenario = {scenario.id} " + - f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + f"AND lca = '{lca.id}'" ) @@ -150,16 +152,16 @@ class Data(SQLSubModel): data1 = next(it) owner_scenario = next(it) - data = cls( + lc = cls( data0, data1, id=pid, status=status, owner_scenario=owner_scenario ) if deleted: - data.set_as_deleted() + lc.set_as_deleted() loaded.add(pid) - new.append(data) + new.append(lc) data["scenario"] = scenario.parent new += cls._db_load(execute, data) @@ -175,7 +177,7 @@ class Data(SQLSubModel): execute( "INSERT INTO " + - "lateral_contribution_adists(pamhyr_id, deleted," + + "lateral_contribution_data_adists(pamhyr_id, deleted," + "data0, data1, lca, scenario) " + "VALUES (" + f"{self.id}, {self._db_format(self.is_deleted())}, " + @@ -372,16 +374,18 @@ class LateralContributionAdisTS(SQLSubModel): execute( "INSERT INTO " + - "lateral_contribution_adists(id, deleted," + + "lateral_contribution_adists(pamhyr_id, deleted, " + "pollutant, reach, begin_rk, end_rk, scenario) " + "VALUES (" + f"{self.id}, {self._db_format(self.is_deleted())}, " + f"{self._pollutant}, {self.reach}, " + - f"{self._begin_rk}, {self._end_rk}" + + f"{self._begin_rk}, {self._end_rk}, " + f"{self._status.scenario_id}" + ")" ) + data["lca"] = self + ind = 0 for d in self._data: data["ind"] = ind @@ -471,3 +475,57 @@ class LateralContributionAdisTS(SQLSubModel): def end_rk(self, end_rk): self._end_rk = end_rk self.modified() + + @property + def _default_0(self): + return self._types[0](0) + + @property + def _default_1(self): + return self._types[1](0.0) + + def add(self, index: int): + value = Data(self._default_0, self._default_1, status=self._status) + self._data.insert(index, value) + self.modified() + return value + + def insert(self, index: int, data: Data): + self._data.insert(index, data) + self.modified() + + def delete_i(self, indexes): + self._data = list( + map( + lambda e: e[1], + filter( + lambda e: e[0] not in indexes, + enumerate(self.data) + ) + ) + ) + self.modified() + + def index(self, bc): + self._data.index(bc) + + def get_i(self, index: int): + return self._data[index] + + def get_range(self, _range): + lst = [] + for r in _range: + lst.append(r) + return lst + + def _set_i_c_v(self, index: int, column: int, value): + v = self._data[index] + v[column] = self._types[column](value) + self._data[index] = v + self.modified() + + def set_i_0(self, index: int, value): + self._set_i_c_v(index, 0, value) + + def set_i_1(self, index: int, value): + self._set_i_c_v(index, 1, value) diff --git a/src/Model/Pollutants/Pollutants.py b/src/Model/Pollutants/Pollutants.py index ba5c3ac7..51872f1d 100644 --- a/src/Model/Pollutants/Pollutants.py +++ b/src/Model/Pollutants/Pollutants.py @@ -28,6 +28,9 @@ from tools import ( from Model.Tools.PamhyrDB import SQLSubModel from Model.Except import NotImplementedMethodeError from Model.Scenario import Scenario +from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS import ( + BoundaryConditionAdisTS, +) logger = logging.getLogger() @@ -275,7 +278,8 @@ class PollutantCharacteristics(SQLSubModel): class Pollutants(SQLSubModel): - _sub_classes = [PollutantCharacteristics] + _sub_classes = [PollutantCharacteristics, + BoundaryConditionAdisTS] def __init__(self, id: int = -1, name: str = "", status=None, owner_scenario=-1): @@ -293,6 +297,11 @@ class Pollutants(SQLSubModel): self._enabled = True self._data = [] + self._boundary_conditions_adists = [] + + @property + def boundary_conditions_adists(self): + return self._boundary_conditions_adists @property def name(self): @@ -415,6 +424,17 @@ class Pollutants(SQLSubModel): new_pollutant._data = PollutantCharacteristics._db_load( execute, data=data ) + + new_pollutant._boundary_conditions_adists = BoundaryConditionAdisTS._db_load( + execute, data=data + ) + + new_pollutant._boundary_conditions_adists = ( + BoundaryConditionAdisTS._db_load( + execute, + data=data + ) + ) loaded.add(pid) new.append(new_pollutant) @@ -455,6 +475,9 @@ class Pollutants(SQLSubModel): for d in self._data: ok &= d._db_save(execute, data) + for bc in self._boundary_conditions_adists: + ok &= bc._db_save(execute, data) + return ok def _data_traversal(self, diff --git a/src/Model/Study.py b/src/Model/Study.py index f32e90bf..23113ab9 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -37,7 +37,7 @@ logger = logging.getLogger() class Study(SQLModel): - _version = "0.2.3" + _version = "0.2.4" _sub_classes = [ Scenario, diff --git a/src/Model/Tools/PamhyrDB.py b/src/Model/Tools/PamhyrDB.py index 0a55cce5..9ca7257c 100644 --- a/src/Model/Tools/PamhyrDB.py +++ b/src/Model/Tools/PamhyrDB.py @@ -311,6 +311,12 @@ class SQLSubModel(PamhyrID): if node_id == -1: continue + if node_id not in nodes: + # ⚠️ cas important : probablement déjà migré + print(f"[WARN] node_id {node_id} not in nodes " + + f"→ probably already migrated") + continue + execute( f"UPDATE {table} " + f"SET node = {nodes[node_id]} " + diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py index ca0f753d..31b25f9b 100644 --- a/src/View/BoundaryCondition/Edit/Plot.py +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -54,7 +54,7 @@ class Plot(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False def custom_ticks(self): if self.data.header[0] != "time": diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Plot.py b/src/View/BoundaryConditionsAdisTS/Edit/Plot.py index 174e7018..b320be27 100644 --- a/src/View/BoundaryConditionsAdisTS/Edit/Plot.py +++ b/src/View/BoundaryConditionsAdisTS/Edit/Plot.py @@ -55,7 +55,7 @@ class Plot(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False def custom_ticks(self): if self.data.header[0] != "time": diff --git a/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py b/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py index a4170a20..e4858bb1 100644 --- a/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py +++ b/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py @@ -61,9 +61,7 @@ class AddCommand(QUndoCommand): def redo(self): if self._new is None: - self._new = self._data.insert(self._index, ( - self._data._types[0](0), self._data._types[1](0.0) - )) + self._new = self._data.add(self._index) else: self._data.insert(self._index, self._new) diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Window.py b/src/View/BoundaryConditionsAdisTS/Edit/Window.py index ca5e1eea..837f9f56 100644 --- a/src/View/BoundaryConditionsAdisTS/Edit/Window.py +++ b/src/View/BoundaryConditionsAdisTS/Edit/Window.py @@ -63,6 +63,16 @@ class EditBoundaryConditionWindow(PamhyrWindow): name = trad[self._pamhyr_name] + if self._data is not None: + n = self._data.node + node_name = next(filter( + lambda x: x.id == n, study.river._nodes + )).name + name += ( + f" - {study.name} " + + f"({node_name})" + ) + super(EditBoundaryConditionWindow, self).__init__( title=name, study=study, @@ -71,16 +81,6 @@ class EditBoundaryConditionWindow(PamhyrWindow): parent=parent ) - if self._data is not None: - n = self._data.node - node_name = next(filter( - lambda x: x.id == n, self._study.river._nodes - )).name - name += ( - f" - {study.name} " + - f"({node_name})" - ) - self._hash_data.append(data) self.setup_table() diff --git a/src/View/BoundaryConditionsAdisTS/Table.py b/src/View/BoundaryConditionsAdisTS/Table.py index dd38b136..34633e0b 100644 --- a/src/View/BoundaryConditionsAdisTS/Table.py +++ b/src/View/BoundaryConditionsAdisTS/Table.py @@ -112,18 +112,37 @@ class ComboBoxDelegate(QItemDelegate): class TableModel(PamhyrTableModel): - def __init__(self, pollutant=None, bc_list=None, trad=None, **kwargs): + def __init__(self, bc_list=None, pollutant_bc_list=None, + trad=None, **kwargs): self._trad = trad self._bc_list = bc_list - self._pollutant = pollutant + self._pollutant = pollutant_bc_list.id super(TableModel, self).__init__(trad=trad, **kwargs) - def rowCount(self, parent): - return len(self._bc_list) + def _setup_lst(self): + self._lst = list( + filter( + lambda bc: bc.pollutant == self._pollutant, + self._bc_list.lst + ) + ) + + def get(self, row): + if row < 0 or row >= len(self._lst): + return None + return self._lst[row] + + def _global_row(self, row): + bc = self.get(row) + if bc is None: + return None + return self._bc_list.index(bc) + + def rowCount(self, parent=QModelIndex()): + return len(self._lst) def data(self, index, role): - if role != Qt.ItemDataRole.DisplayRole: return QVariant() @@ -131,12 +150,12 @@ class TableModel(PamhyrTableModel): column = index.column() if self._headers[column] == "type": - n = self._bc_list.get(row).type + n = self._lst[row].type if n is None or n == "": return self._trad["not_associated"] return n elif self._headers[column] == "node": - n = self._bc_list.get(row).node + n = self._lst[row].node if n is None: return self._trad["not_associated"] tmp = next(filter(lambda x: x.id == n, self._data._nodes), None) @@ -144,18 +163,6 @@ class TableModel(PamhyrTableModel): return tmp.name else: return self._trad["not_associated"] - elif self._headers[column] == "pol": - n = self._bc_list.get(row).pollutant - if n is None or n == "not_associated" or n == "": - return self._trad["not_associated"] - tmp = next(filter(lambda x: x.id == n, - self._data._Pollutants.Pollutants_List - ), - None) - if tmp is not None: - return tmp.name - else: - return self._trad["not_associated"] return QVariant() @@ -168,33 +175,24 @@ class TableModel(PamhyrTableModel): try: if self._headers[column] == "type": + global_row = self._global_row(row) self._undo.push( SetTypeCommand( - self._bc_list, row, value + self._bc_list, global_row, value ) ) elif self._headers[column] == "node": + global_row = self._global_row(row) + node = next( + filter(lambda n: n.name == value, self._data.nodes()), + None + ) self._undo.push( SetNodeCommand( - self._bc_list, row, self._data.node(value) + self._bc_list, global_row, node ) ) - elif self._headers[column] == "pol": - if value == self._trad["not_associated"]: - self._undo.push( - SetPolCommand( - self._bc_list, row, None - ) - ) - else: - pol = next(filter(lambda x: x.name == value, - self._data._Pollutants.Pollutants_List) - ) - self._undo.push( - SetPolCommand( - self._bc_list, row, pol.id - ) - ) + except Exception as e: logger.info(e) logger.debug(traceback.format_exc()) @@ -203,33 +201,44 @@ class TableModel(PamhyrTableModel): return True def add(self, row, parent=QModelIndex()): - self.beginInsertRows(parent, row, row - 1) - + row = len(self._lst) + self.beginInsertRows(parent, row, row) self._undo.push( AddCommand( - self._pollutant, self._bc_list, row + self._pollutant, self._bc_list, len(self._bc_list) ) ) - + self._setup_lst() self.endInsertRows() self.layoutChanged.emit() def delete(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) + row_by_bc = { + id(bc): row for row, bc in enumerate(self._bc_list._lst) + } + global_rows = [ + row_by_bc[id(self._lst[row])] + for row in rows + if 0 <= row < len(self._lst) + ] self._undo.push( DelCommand( - self._bc_list, rows + self._bc_list, global_rows ) ) + self._setup_lst() self.endRemoveRows() self.layoutChanged.emit() def undo(self): self._undo.undo() + self._setup_lst() self.layoutChanged.emit() def redo(self): self._undo.redo() + self._setup_lst() self.layoutChanged.emit() diff --git a/src/View/BoundaryConditionsAdisTS/UndoCommand.py b/src/View/BoundaryConditionsAdisTS/UndoCommand.py index 9a8d8243..f1531d71 100644 --- a/src/View/BoundaryConditionsAdisTS/UndoCommand.py +++ b/src/View/BoundaryConditionsAdisTS/UndoCommand.py @@ -36,7 +36,7 @@ class SetNodeCommand(QUndoCommand): self._bcs = bcs self._index = index self._old = self._bcs.get(self._index).node - self._new = node.id + self._new = node.id if node is not None else None def undo(self): self._bcs.get(self._index).node = self._old diff --git a/src/View/BoundaryConditionsAdisTS/Window.py b/src/View/BoundaryConditionsAdisTS/Window.py index 47039e81..439e2acf 100644 --- a/src/View/BoundaryConditionsAdisTS/Window.py +++ b/src/View/BoundaryConditionsAdisTS/Window.py @@ -58,11 +58,22 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): _pamhyr_ui = "BoundaryConditionsAdisTS" _pamhyr_name = "Boundary conditions AdisTS" - def __init__(self, study=None, config=None, parent=None): + def __init__(self, data=None, pollutant_id=None, study=None, + config=None, parent=None): + self._data = data + self._pollutant_id = pollutant_id trad = BCAdisTSTranslate() + + self._pollutant_name = next( + (x.name for x in study._river._Pollutants.Pollutants_List + if x.id == self._pollutant_id), + None + ) + name = ( trad[self._pamhyr_name] + - " - " + study.name + " - " + study.name + + " - " + self._pollutant_name ) super(BoundaryConditionAdisTSWindow, self).__init__( @@ -73,7 +84,6 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): parent=parent ) - self._pollutants_lst = self._study._river._Pollutants self._bcs = self._study.river.boundary_conditions_adists self.setup_graph() @@ -95,12 +105,6 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): mode="node", parent=self ) - self._delegate_pol = ComboBoxDelegate( - trad=self._trad, - data=self._study.river, - mode="pol", - parent=self - ) table = self.find(QTableView, f"tableView") self._table = TableModel( @@ -110,10 +114,10 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): delegates={ "type": self._delegate_type, "node": self._delegate_node, - "pol": self._delegate_pol, }, trad=self._trad, bc_list=self._study.river.boundary_conditions_adists, + pollutant_bc_list=self._data, undo=self._undo_stack, data=self._study.river ) @@ -156,7 +160,7 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): ) def add(self): - self._table.add(len(self._bcs)) + self._table.add(self._table.rowCount()) def delete(self): rows = self.index_selected_rows() @@ -180,7 +184,7 @@ class BoundaryConditionAdisTSWindow(PamhyrWindow): def edit(self): rows = self.index_selected_rows() for row in rows: - data = self._bcs.get(row) + data = self._table.get(row) if data.node is None: continue diff --git a/src/View/BoundaryConditionsAdisTS/translate.py b/src/View/BoundaryConditionsAdisTS/translate.py index e6ab6099..590c3511 100644 --- a/src/View/BoundaryConditionsAdisTS/translate.py +++ b/src/View/BoundaryConditionsAdisTS/translate.py @@ -34,5 +34,4 @@ class BCAdisTSTranslate(MainTranslate): self._sub_dict["table_headers"] = { "type": self._dict["type"], "node": _translate("BoundaryCondition", "Node"), - "pol": _translate("BoundaryCondition", "Pollutant") } diff --git a/src/View/D90AdisTS/Table.py b/src/View/D90AdisTS/Table.py index 47390a71..090a5a8b 100644 --- a/src/View/D90AdisTS/Table.py +++ b/src/View/D90AdisTS/Table.py @@ -46,21 +46,21 @@ _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, ic_spec_lst=None, + def __init__(self, data=None, d90_spec_lst=None, trad=None, parent=None, mode="reaches"): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._mode = mode self._trad = trad - self._ic_spec_lst = ic_spec_lst + self._d90_spec_lst = d90_spec_lst def createEditor(self, parent, option, index): self.editor = QComboBox(parent) val = [] if self._mode == "rk": - reach_id = self._ic_spec_lst[index.row()].reach + reach_id = self._d90_spec_lst[index.row()].reach reach = next(filter( lambda edge: edge.id == reach_id, self._data.edges() @@ -118,7 +118,12 @@ class D90TableModel(PamhyrTableModel): self._data = data def _setup_lst(self): - self._lst = self._data._data + self._lst = list( + filter( + lambda d90: d90._deleted is False, + self._data._data + ) + ) def rowCount(self, parent): return len(self._lst) @@ -197,18 +202,25 @@ class D90TableModel(PamhyrTableModel): ) ) + self._setup_lst() self.endInsertRows() self.layoutChanged.emit() def delete(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) + data_rows = { + id(d90): i for i, d90 in enumerate(self._data._data) + } self._undo.push( DelCommand( - self._data, self._lst, rows + self._data, + self._data._data, + [data_rows[id(self._lst[row])] for row in rows] ) ) + self._setup_lst() self.endRemoveRows() self.layoutChanged.emit() diff --git a/src/View/D90AdisTS/UndoCommand.py b/src/View/D90AdisTS/UndoCommand.py index 050bc675..23a2b6ac 100644 --- a/src/View/D90AdisTS/UndoCommand.py +++ b/src/View/D90AdisTS/UndoCommand.py @@ -112,11 +112,11 @@ class SetCommandSpec(QUndoCommand): class AddCommand(QUndoCommand): - def __init__(self, data, ics_spec, index): + def __init__(self, data, d90_spec, index): QUndoCommand.__init__(self) self._data = data - self._ics_spec = ics_spec + self._d90_spec = d90_spec self._index = index self._new = None @@ -131,21 +131,20 @@ class AddCommand(QUndoCommand): class DelCommand(QUndoCommand): - def __init__(self, data, ics_spec, rows): + def __init__(self, data, d90_spec, rows): QUndoCommand.__init__(self) self._data = data - self._ics_spec = ics_spec + self._d90_spec = d90_spec self._rows = rows - # self._data = data - self._ic = [] + self._d90 = [] for row in rows: - self._ic.append((row, self._ics_spec[row])) - self._ic.sort() + self._d90.append((row, self._d90_spec[row])) + self._d90.sort() def undo(self): - for row, el in self._ic: + for row, el in self._d90: self._data.insert(row, el) def redo(self): diff --git a/src/View/D90AdisTS/Window.py b/src/View/D90AdisTS/Window.py index 9cfb4f62..cb36d690 100644 --- a/src/View/D90AdisTS/Window.py +++ b/src/View/D90AdisTS/Window.py @@ -138,14 +138,14 @@ class D90AdisTSWindow(PamhyrWindow): self._delegate_reach = ComboBoxDelegate( trad=self._trad, data=self._study.river, - ic_spec_lst=self._data[0]._data, + d90_spec_lst=self._data[0]._data, parent=self, mode="reaches" ) self._delegate_rk = ComboBoxDelegate( trad=self._trad, data=self._study.river, - ic_spec_lst=self._data[0]._data, + d90_spec_lst=self._data[0]._data, parent=self, mode="rk" ) @@ -227,15 +227,15 @@ class D90AdisTSWindow(PamhyrWindow): table = list( map( - lambda eic: list( + lambda ed90: list( map( - lambda k: eic[1][k], + lambda k: ed90[1][k], ["rk", "discharge", "elevation"] ) ), filter( - lambda eic: eic[0] in rows, - enumerate(self._ics.lst()) + lambda ed90: ed90[0] in rows, + enumerate(self._d90.lst()) ) ) ) diff --git a/src/View/Frictions/PlotRKZ.py b/src/View/Frictions/PlotRKZ.py index 0614a052..9e787cbc 100644 --- a/src/View/Frictions/PlotRKZ.py +++ b/src/View/Frictions/PlotRKZ.py @@ -43,7 +43,7 @@ class PlotRKZ(PlotRKC): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False self.parent = parent def onpick(self, event): diff --git a/src/View/Frictions/PlotStricklers.py b/src/View/Frictions/PlotStricklers.py index 7bdc3cd3..9abef064 100644 --- a/src/View/Frictions/PlotStricklers.py +++ b/src/View/Frictions/PlotStricklers.py @@ -50,7 +50,7 @@ class PlotStricklers(PamhyrPlot): self._auto_relim = False self._auto_relim_update = False - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self, highlight=None): diff --git a/src/View/Geometry/PlotAC.py b/src/View/Geometry/PlotAC.py index cd1e3f76..1b846c5b 100644 --- a/src/View/Geometry/PlotAC.py +++ b/src/View/Geometry/PlotAC.py @@ -38,7 +38,7 @@ class PlotAC(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False self.label_x = self._trad["transverse_abscissa"] self.label_y = self._trad["unit_elevation"] diff --git a/src/View/Geometry/ShiftDialog.py b/src/View/Geometry/ShiftDialog.py index 38744429..abc23590 100644 --- a/src/View/Geometry/ShiftDialog.py +++ b/src/View/Geometry/ShiftDialog.py @@ -17,6 +17,10 @@ # -*- coding: utf-8 -*- from View.Tools.PamhyrWindow import PamhyrDialog +from PyQt5.QtWidgets import QDoubleSpinBox +from View.Tools.FlexibleDoubleSpinBox import FlexibleDoubleSpinBox +from PyQt5.QtWidgets import QDoubleSpinBox +from View.Tools.FlexibleDoubleSpinBox import FlexibleDoubleSpinBox class ShiftDialog(PamhyrDialog): @@ -31,8 +35,36 @@ class ShiftDialog(PamhyrDialog): parent=parent ) + self._replace_spinboxes() self._init_default_values() + def _replace_spinboxes(self): + for name in ["doubleSpinBox_X", "doubleSpinBox_Y", "doubleSpinBox_Z"]: + old = self.find(QDoubleSpinBox, name) + + if old is None: + continue + + new = FlexibleDoubleSpinBox(old.parent()) + new.setObjectName(old.objectName()) + + # Copier propriétés utiles + new.setDecimals(old.decimals()) + new.setMinimum(old.minimum()) + new.setMaximum(old.maximum()) + new.setSingleStep(old.singleStep()) + new.setValue(old.value()) + new.setGeometry(old.geometry()) + + # Remplacement dans layout si présent + layout = old.parent().layout() + if layout is not None: + layout.replaceWidget(old, new) + + old.hide() + old.setParent(None) + old.deleteLater() + def _init_default_values(self): self._dx = 0.0 self._dy = 0.0 diff --git a/src/View/Geometry/Translate.py b/src/View/Geometry/Translate.py index e0b567ba..3a2528af 100644 --- a/src/View/Geometry/Translate.py +++ b/src/View/Geometry/Translate.py @@ -91,3 +91,9 @@ class GeometryTranslate(MainTranslate): self._dict["Shift"] = _translate( "Geometry", "Shift" ) + self._dict["warning"] = _translate( + "Geometry", "Warning" + ) + self._dict["format_not_exportable"] = _translate( + "Geometry", "The format of the file is not exportable." + ) diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 13f25e30..ce4bc954 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -20,6 +20,7 @@ import os import sys import time import pathlib +from pathlib import Path import logging from copy import deepcopy @@ -685,10 +686,24 @@ class GeometryWindow(PamhyrWindow): ) if filename != '' and filename is not None: - self._export_to_file_st(filename) + suffix = Path(filename).suffix + if suffix != "": + if suffix == ".st" or suffix == ".ST": + self._export_to_file_st(filename[:-3]) + else: + # Warning popup when the format is not exportable + win = QtWidgets.QMessageBox() + win.setIcon(QtWidgets.QMessageBox.Warning) + win.setWindowTitle(self._trad["warning"]) + win.setText(self._trad["format_not_exportable"]) + win.exec() + + pass + else: + self._export_to_file_st(filename) def _export_to_file_st(self, filename): - with open(filename, "w+") as f: + with open(filename+".st", "w+") as f: f.write("# Exported from Pamhyr2\n") self._export_to_file_st_reach(f, self._reach) diff --git a/src/View/HydraulicStructures/PlotAC.py b/src/View/HydraulicStructures/PlotAC.py index d8bfdb5b..7a51f66b 100644 --- a/src/View/HydraulicStructures/PlotAC.py +++ b/src/View/HydraulicStructures/PlotAC.py @@ -42,7 +42,7 @@ class PlotAC(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @property def river(self): diff --git a/src/View/HydraulicStructures/PlotRKC.py b/src/View/HydraulicStructures/PlotRKC.py index 8ccdf148..d7ee4faf 100644 --- a/src/View/HydraulicStructures/PlotRKC.py +++ b/src/View/HydraulicStructures/PlotRKC.py @@ -50,7 +50,7 @@ class PlotRKC(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False self.parent = parent self.anotate_lst = [] diff --git a/src/View/InitialConditions/PlotDRK.py b/src/View/InitialConditions/PlotDRK.py index 6b4e81ad..76125391 100644 --- a/src/View/InitialConditions/PlotDRK.py +++ b/src/View/InitialConditions/PlotDRK.py @@ -47,7 +47,7 @@ class PlotDRK(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self, highlight=None): diff --git a/src/View/InitialConditions/PlotDischarge.py b/src/View/InitialConditions/PlotDischarge.py index 82adb880..5dad6092 100644 --- a/src/View/InitialConditions/PlotDischarge.py +++ b/src/View/InitialConditions/PlotDischarge.py @@ -37,7 +37,7 @@ class PlotDischarge(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/InitialConditionsAdisTS/Table.py b/src/View/InitialConditionsAdisTS/Table.py index 531ce00b..b84beed1 100644 --- a/src/View/InitialConditionsAdisTS/Table.py +++ b/src/View/InitialConditionsAdisTS/Table.py @@ -117,7 +117,12 @@ class InitialConditionTableModel(PamhyrTableModel): self._data = data def _setup_lst(self): - self._lst = self._data._data + self._lst = list( + filter( + lambda ica: ica._deleted is False, + self._data._data + ) + ) def rowCount(self, parent): return len(self._lst) @@ -216,18 +221,25 @@ class InitialConditionTableModel(PamhyrTableModel): ) ) + self._setup_lst() self.endInsertRows() self.layoutChanged.emit() def delete(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) + data_rows = { + id(ica): i for i, ica in enumerate(self._data._data) + } self._undo.push( DelCommand( - self._data, self._lst, rows + self._data, + self._data._data, + [data_rows[id(self._lst[row])] for row in rows] ) ) + self._setup_lst() self.endRemoveRows() self.layoutChanged.emit() diff --git a/src/View/InitialConditionsAdisTS/UndoCommand.py b/src/View/InitialConditionsAdisTS/UndoCommand.py index 5ef0b50e..984be6e7 100644 --- a/src/View/InitialConditionsAdisTS/UndoCommand.py +++ b/src/View/InitialConditionsAdisTS/UndoCommand.py @@ -181,7 +181,6 @@ class DelCommand(QUndoCommand): self._data = data self._ics_spec = ics_spec self._rows = rows - # self._data = data self._ic = [] for row in rows: diff --git a/src/View/InitialConditionsAdisTS/Window.py b/src/View/InitialConditionsAdisTS/Window.py index dce32696..2f7ddacc 100644 --- a/src/View/InitialConditionsAdisTS/Window.py +++ b/src/View/InitialConditionsAdisTS/Window.py @@ -68,15 +68,22 @@ class InitialConditionsAdisTSWindow(PamhyrWindow): _pamhyr_ui = "InitialConditionsAdisTS" _pamhyr_name = "Initial condition AdisTS" - def __init__(self, data=None, study=None, config=None, parent=None): + def __init__(self, data=None, pollutant_id=None, study=None, + config=None, parent=None): self._data = [] self._data.append(data) + self._pollutant_id = pollutant_id trad = IcAdisTSTranslate() + self._pollutant_name = next( + (x.name for x in study._river._Pollutants.Pollutants_List + if x.id == self._pollutant_id), + None + ) name = ( trad[self._pamhyr_name] + " - " + study.name + - " - " + self._data[0].name + " - " + self._pollutant_name ) super(InitialConditionsAdisTSWindow, self).__init__( diff --git a/src/View/LateralContribution/Edit/Plot.py b/src/View/LateralContribution/Edit/Plot.py index ca0f753d..31b25f9b 100644 --- a/src/View/LateralContribution/Edit/Plot.py +++ b/src/View/LateralContribution/Edit/Plot.py @@ -54,7 +54,7 @@ class Plot(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False def custom_ticks(self): if self.data.header[0] != "time": diff --git a/src/View/LateralContributionsAdisTS/Edit/Plot.py b/src/View/LateralContributionsAdisTS/Edit/Plot.py index ca0f753d..31b25f9b 100644 --- a/src/View/LateralContributionsAdisTS/Edit/Plot.py +++ b/src/View/LateralContributionsAdisTS/Edit/Plot.py @@ -54,7 +54,7 @@ class Plot(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False def custom_ticks(self): if self.data.header[0] != "time": diff --git a/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py index 427edf56..88fe63ad 100644 --- a/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py +++ b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py @@ -34,29 +34,14 @@ class SetDataCommand(QUndoCommand): self._data = data self._index = index self._column = column - self._old = self._data._data[self._index][self._column] - _type = self._data._types[self._column] - self._new = _type(new_value) + self._old = self._data.get_i(self._index)[self._column] + self._new = new_value def undo(self): - if self._column == 0: - self._data._data[self._index] = ( - self._old, self._data._data[self._index][1] - ) - else: - self._data._data[self._index] = ( - self._data._data[self._index][0], self._old - ) + self._data._set_i_c_v(self._index, self._column, self._old) def redo(self): - if self._column == 0: - self._data._data[self._index] = ( - self._new, self._data._data[self._index][1] - ) - else: - self._data._data[self._index] = ( - self._data._data[self._index][0], self._new - ) + self._data._set_i_c_v(self._index, self._column, self._new) class AddCommand(QUndoCommand): @@ -72,11 +57,7 @@ class AddCommand(QUndoCommand): def redo(self): if self._new is None: - self._new = self._data._data.insert( - self._index, ( - self._data._types[0](0), self._data._types[1](0.0) - ) - ) + self._new = self._data.add(self._index) else: self._data._data.insert(self._index, self._new) diff --git a/src/View/LateralContributionsAdisTS/Edit/Window.py b/src/View/LateralContributionsAdisTS/Edit/Window.py index ded461bb..01aa64fb 100644 --- a/src/View/LateralContributionsAdisTS/Edit/Window.py +++ b/src/View/LateralContributionsAdisTS/Edit/Window.py @@ -50,14 +50,38 @@ class EditLateralContributionAdisTSWindow(PamhyrWindow): _pamhyr_ui = "EditLateralContributionAdisTS" _pamhyr_name = "Edit lateral contribution AdisTS" - def __init__(self, data=None, - study=None, config=None, - parent=None): + def __init__(self, data=None, study=None, config=None, parent=None): self._data = data - trad = LCETranslate() + name = trad[self._pamhyr_name] + if self._data is not None: + if self._data.reach is not None: + reach_name = next(filter( + lambda reach: reach.id == self._data.reach, + study.river.reachs() + )).name + else: + reach_name = trad['not_associated'] + + if self._data.begin_rk is not None: + begin_rk = self._data.begin_rk + else: + begin_rk = trad['not_associated'] + + if self._data.end_rk is not None: + end_rk = self._data.end_rk + else: + end_rk = trad['not_associated'] + + name += ( + f" - {study.name} - " + + f"{reach_name} - " + + f"({begin_rk} - " + + f"{end_rk})" + ) + super(EditLateralContributionAdisTSWindow, self).__init__( title=name, study=study, @@ -66,20 +90,6 @@ class EditLateralContributionAdisTSWindow(PamhyrWindow): parent=parent ) - if self._data is not None: - if self._data.edge is not None: - edge_name = next(filter( - lambda edge: edge.id == self._data.edge, - self._study.river.edges() - )).name - else: - edge_name = trad['not_associated'] - - name += ( - f"{study.name} - " + - f"{edge_name})" - ) - self._hash_data.append(data) self.setup_table() diff --git a/src/View/LateralContributionsAdisTS/Table.py b/src/View/LateralContributionsAdisTS/Table.py index ede41f0b..dc3b614d 100644 --- a/src/View/LateralContributionsAdisTS/Table.py +++ b/src/View/LateralContributionsAdisTS/Table.py @@ -116,10 +116,22 @@ class TableModel(PamhyrTableModel): self._pollutant = pollutant super(TableModel, self).__init__(trad=trad, **kwargs) + self._setup_lst() def _setup_lst(self): - self._lst = self._data.lateral_contributions_adists.lst - self._tab = self._opt_data + if self._lcs_list is not None: + self._lcs_pol_list = [ + lcs for lcs in self._lcs_list._lst + if lcs.pollutant == self._pollutant + ] + + self._lst = list( + filter( + lambda x: x._deleted is False, + self._lcs_pol_list + ) + ) + # self._tab = self._opt_data self._long_types = self._trad.get_dict("long_types") def rowCount(self, parent): @@ -190,17 +202,37 @@ class TableModel(PamhyrTableModel): ) ) + self._setup_lst() self.endInsertRows() self.layoutChanged.emit() def delete(self, rows, parent=QModelIndex()): self.beginRemoveRows(parent, rows[0], rows[-1]) + row_by_lc = { + id(lc): row for row, lc in enumerate(self._lcs_list._lst) + } + global_rows = [ + row_by_lc[id(self._lst[row])] + for row in rows + if 0 <= row < len(self._lst) + ] self._undo.push( DelCommand( - self._lst, rows + self._lcs_list, global_rows ) ) + self._setup_lst() self.endRemoveRows() self.layoutChanged.emit() + + def undo(self): + self._undo.undo() + self._setup_lst() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self._setup_lst() + self.layoutChanged.emit() diff --git a/src/View/LateralContributionsAdisTS/UndoCommand.py b/src/View/LateralContributionsAdisTS/UndoCommand.py index 04875d28..08cf408a 100644 --- a/src/View/LateralContributionsAdisTS/UndoCommand.py +++ b/src/View/LateralContributionsAdisTS/UndoCommand.py @@ -91,13 +91,13 @@ class AddCommand(QUndoCommand): self._new = None def undo(self): - del self._lcs[self._index] + self._lcs.delete_i(self._index) def redo(self): if self._new is None: self._new = self._lcs.new(self._index, self._pollutant) else: - self._lcs_lst.insert(self._index, self._new) + self._lcs.insert(self._index, self._new) class DelCommand(QUndoCommand): @@ -107,15 +107,14 @@ class DelCommand(QUndoCommand): self._lcs = lcs self._rows = rows - self._bc = [] + self._lc = [] for row in rows: - self._bc.append((row, self._lcs[row])) - self._bc.sort() + self._lc.append((row, self._lcs._lst[row])) + self._lc.sort() def undo(self): - for row, el in self._bc: + for row, el in self._lc: self._lcs.insert(row, el) def redo(self): - for row in self._rows: - del self._lcs[row] + self._lcs.delete_i(self._rows) diff --git a/src/View/LateralContributionsAdisTS/Window.py b/src/View/LateralContributionsAdisTS/Window.py index ded6a438..491c44f4 100644 --- a/src/View/LateralContributionsAdisTS/Window.py +++ b/src/View/LateralContributionsAdisTS/Window.py @@ -57,11 +57,24 @@ class LateralContributionAdisTSWindow(PamhyrWindow): _pamhyr_ui = "LateralContributionsAdisTS" _pamhyr_name = "Lateral contribution AdisTS" - def __init__(self, study=None, pollutant=None, config=None, parent=None): + def __init__(self, data=None, study=None, pollutant_id=None, + config=None, parent=None): + self._pollutant = pollutant_id + self._study = study + self._data = data trad = LCTranslate() - name = trad[self._pamhyr_name] + " - " + study.name - self._pollutant = pollutant + self._pollutant_name = next( + (x.name for x in study._river._Pollutants.Pollutants_List + if x.id == self._pollutant), + None + ) + + name = ( + trad[self._pamhyr_name] + + " - " + study.name + + " - " + self._pollutant_name + ) super(LateralContributionAdisTSWindow, self).__init__( title=name, @@ -110,7 +123,7 @@ class LateralContributionAdisTSWindow(PamhyrWindow): data=self._study.river, undo=self._undo_stack, trad=self._trad, - opt_data="liquid", + # opt_data="liquid", pollutant=self._pollutant, lcs_list=self._lcs, ) @@ -237,6 +250,9 @@ class LateralContributionAdisTSWindow(PamhyrWindow): def edit(self): rows = self.index_selected_rows() + if not rows: + return + for row in rows: data = self._lcs.lst[row] diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 6f91e293..83aa8014 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -901,7 +901,18 @@ class GraphWidget(QGraphicsView): painter.drawRect(sceneRect) def wheelEvent(self, event): - self.scaleView(math.pow(2.0, -event.angleDelta().y() / 240.0)) + factor = math.pow(2.0, event.angleDelta().y() / 240.0) + + old_pos = self.mapToScene(event.pos()) + + self.scaleView(factor) + + new_pos = self.mapToScene(event.pos()) + + delta = old_pos - new_pos + + # Compensation pour garder le point sous la souris fixe + self.translate(delta.x(), delta.y()) def scaleView(self, scaleFactor): factor = self.transform().scale( @@ -914,6 +925,9 @@ class GraphWidget(QGraphicsView): self.scale(scaleFactor, scaleFactor) def mousePressEvent(self, event): + if self._only_display or self.graph._status.is_read_only(): + return + pos = self.mapToScene(event.pos()) self.clicked = True diff --git a/src/View/PlotXY.py b/src/View/PlotXY.py index e51933ef..b498f3d4 100644 --- a/src/View/PlotXY.py +++ b/src/View/PlotXY.py @@ -68,7 +68,7 @@ class PlotXY(PamhyrPlot): self._isometric_axis = True self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/Pollutants/Window.py b/src/View/Pollutants/Window.py index ee03ec1a..75055462 100644 --- a/src/View/Pollutants/Window.py +++ b/src/View/Pollutants/Window.py @@ -217,26 +217,39 @@ class PollutantsWindow(PamhyrWindow): initial = InitialConditionsAdisTSWindow( study=self._study, parent=self, - data=ics_adists + data=ics_adists, + pollutant_id=pollutant_id ) initial.show() def boundary_conditions(self): - - if self.sub_window_exists( - BoundaryConditionAdisTSWindow, - data=[self._study, None] - ): - bound = self.get_sub_window( - BoundaryConditionAdisTSWindow, - data=[self._study, None] - ) + rows = self.index_selected_rows() + if len(rows) == 0: return - bound = BoundaryConditionAdisTSWindow( - study=self._study, parent=self - ) - bound.show() + for row in rows: + pollutant_id = self._pollutants_lst.get(row).id + + river = self._study.river + bclist = river.boundary_conditions_adists.BCs_AdisTS_List + bcs_adists = [ + x for x in bclist + if x.pollutant == pollutant_id + ] + self._data = self._study.river.Pollutants.get(row) + if self.sub_window_exists( + BoundaryConditionAdisTSWindow, + data=[self._study, None, bcs_adists] + ): + return + + bound = BoundaryConditionAdisTSWindow( + study=self._study, + parent=self, + data=self._data, + pollutant_id=pollutant_id + ) + bound.show() def lateral_contrib(self): rows = self.index_selected_rows() @@ -245,6 +258,9 @@ class PollutantsWindow(PamhyrWindow): pollutant_id = self._pollutants_lst.get(rows[0]).id + lclist = self._study.river.lateral_contributions_adists.Lat_Cont_List + lcs_adists = [x for x in lclist if x.pollutant == pollutant_id] + if self.sub_window_exists( LateralContributionAdisTSWindow, data=[self._study, pollutant_id, None] @@ -253,8 +269,9 @@ class PollutantsWindow(PamhyrWindow): lateral = LateralContributionAdisTSWindow( study=self._study, - pollutant=pollutant_id, - parent=self + parent=self, + data=lcs_adists, + pollutant_id=pollutant_id, ) lateral.show() diff --git a/src/View/Reservoir/Edit/Plot.py b/src/View/Reservoir/Edit/Plot.py index afb44ea2..9709c668 100644 --- a/src/View/Reservoir/Edit/Plot.py +++ b/src/View/Reservoir/Edit/Plot.py @@ -55,7 +55,7 @@ class Plot(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/Results/PlotAC.py b/src/View/Results/PlotAC.py index bb188472..b2324a9b 100644 --- a/src/View/Results/PlotAC.py +++ b/src/View/Results/PlotAC.py @@ -56,7 +56,7 @@ class PlotAC(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @property def results(self): diff --git a/src/View/Results/PlotRKC.py b/src/View/Results/PlotRKC.py index 065b42ca..2c09035c 100644 --- a/src/View/Results/PlotRKC.py +++ b/src/View/Results/PlotRKC.py @@ -59,7 +59,7 @@ class PlotRKC(PamhyrPlot): self._isometric_axis = False self._auto_relim_update = True - self._autoscale_update = True + self._autoscale_update = False @property def results(self): diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 822b64e0..9324f356 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -701,7 +701,8 @@ class ResultsWindow(PamhyrWindow): logger.info("TODO: paste") def _undo(self): - self._table.undo() + logger.info("TODO: undo") + # self._table.undo() def _redo(self): self._table.redo() diff --git a/src/View/Scenarios/GraphWidget.py b/src/View/Scenarios/GraphWidget.py index 7405bef0..0fff07d3 100644 --- a/src/View/Scenarios/GraphWidget.py +++ b/src/View/Scenarios/GraphWidget.py @@ -312,7 +312,18 @@ class GraphWidget(QGraphicsView): painter.drawRect(sceneRect) def wheelEvent(self, event): - self.scaleView(math.pow(2.0, -event.angleDelta().y() / 240.0)) + factor = math.pow(2.0, event.angleDelta().y() / 240.0) + + old_pos = self.mapToScene(event.pos()) + + self.scaleView(factor) + + new_pos = self.mapToScene(event.pos()) + + delta = old_pos - new_pos + + # Compensation pour garder le point sous la souris fixe + self.translate(delta.x(), delta.y()) def keyPressEvent(self, event): key = event.key() diff --git a/src/View/SedimentLayers/Edit/Plot.py b/src/View/SedimentLayers/Edit/Plot.py index 73a55b85..a9344333 100644 --- a/src/View/SedimentLayers/Edit/Plot.py +++ b/src/View/SedimentLayers/Edit/Plot.py @@ -49,7 +49,7 @@ class Plot(PamhyrPlot): self._auto_relim = False self._auto_relim_update = False - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/SedimentLayers/Reach/Plot.py b/src/View/SedimentLayers/Reach/Plot.py index 30adb2b7..0ab7aff4 100644 --- a/src/View/SedimentLayers/Reach/Plot.py +++ b/src/View/SedimentLayers/Reach/Plot.py @@ -53,7 +53,7 @@ class Plot(PamhyrPlot): self._auto_relim = False self._auto_relim_update = False - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/SedimentLayers/Reach/Profile/Plot.py b/src/View/SedimentLayers/Reach/Profile/Plot.py index 42d270e0..000295fd 100644 --- a/src/View/SedimentLayers/Reach/Profile/Plot.py +++ b/src/View/SedimentLayers/Reach/Profile/Plot.py @@ -53,7 +53,7 @@ class Plot(PamhyrPlot): self._auto_relim = False self._auto_relim_update = False - self._autoscale_update = True + self._autoscale_update = False @timer def draw(self): diff --git a/src/View/Tools/FlexibleDoubleSpinBox.py b/src/View/Tools/FlexibleDoubleSpinBox.py new file mode 100644 index 00000000..155f9016 --- /dev/null +++ b/src/View/Tools/FlexibleDoubleSpinBox.py @@ -0,0 +1,36 @@ +# PamhyrWindow.py -- Pamhyr +# Copyright (C) 2023-2025 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 + +from PyQt5.QtWidgets import QDoubleSpinBox + + +class FlexibleDoubleSpinBox(QDoubleSpinBox): + def keyPressEvent(self, event): + if event.text() == ".": + # Simule une virgule à la place du point + event = type(event)( + event.type(), + event.key(), + event.modifiers(), + "," + ) + + super().keyPressEvent(event) diff --git a/src/View/Tools/Plot/PamhyrToolbar.py b/src/View/Tools/Plot/PamhyrToolbar.py index 88c01951..593049a0 100644 --- a/src/View/Tools/Plot/PamhyrToolbar.py +++ b/src/View/Tools/Plot/PamhyrToolbar.py @@ -222,10 +222,11 @@ class PamhyrPlotToolbar(NavigationToolbar2QT): filters.append(new) filters = ';;'.join(filters) - file_name, _ = qt_compat._getSaveFileName( + file_name, _ = QtWidgets.QFileDialog.getSaveFileName( self.canvas.parent(), _translate("MainWindow_reach", "Select destination file"), - start, filters, + start, + filters, selected_filter )