Compare commits

...

18 Commits

Author SHA1 Message Date
Dylan Jeannin f86cd4602c Merge branch 'scenarios' into dev_dylan 2026-05-22 14:38:52 +02:00
Dylan Jeannin ee28afac86 HydraulicStructures: Fix hydraulic structures plot with missing input section 2026-05-22 12:57:34 +02:00
Dylan Jeannin 81ff6372e9 LateralSources: Debug of crash on RK selection 2026-05-22 12:45:27 +02:00
Pierre-Antoine 61156f1c3e Scenario: Fix scenario duplication. 2026-05-22 11:51:18 +02:00
Pierre-Antoine e26b170b05 Network: Fix reach selection where the study is read only. 2026-05-22 10:57:49 +02:00
Dylan Jeannin ddbaa0fb94 Pollutants: Fixed pollutant deletion removing the absolute index instead of the relative index 2026-05-22 10:53:07 +02:00
Pierre-Antoine 64d2b2678a Geometry: Profiles: Fix shift methode. 2026-05-22 10:39:10 +02:00
Pierre-Antoine 3a3b5ea2dd PamhyrDB: Minor change. 2026-05-22 10:09:34 +02:00
Pierre-Antoine 7cb3ac8a80 Clean code: Remove some print function call. 2026-05-22 09:42:32 +02:00
Pierre-Antoine 190ca5d171 Scenario: Add some save dialog. 2026-05-22 09:22:35 +02:00
Pierre-Antoine 5d8ec51417 Scenario: Remove posibility to remove non leaf scenario. 2026-05-22 09:21:55 +02:00
Pierre-Antoine d922436ada Scenario: Add Discard option to scenario switching dialog. 2026-05-21 16:26:51 +02:00
Pierre-Antoine 4537a829af WaitingDialog: Try fix deadlock. 2026-05-21 16:26:47 +02:00
Pierre-Antoine 363b9c7132 Scenario: Add HS to memory clean and minor comment change. 2026-05-21 15:07:18 +02:00
Pierre-Antoine f9d83ac68c Scenario: Add memory clean for Pamhyr list, dict and reaches. 2026-05-21 11:17:45 +02:00
Dylan Jeannin 63b5cbaba7 debug pollutants model and save in db 2026-05-20 17:17:51 +02:00
Dylan Jeannin 1989914aef error in the name of a db table 2026-05-20 10:48:37 +02:00
Dylan Jeannin 2c0b155baf error in the name of a db table 2026-05-20 10:40:11 +02:00
28 changed files with 264 additions and 103 deletions

View File

@ -214,7 +214,7 @@ class InternalMeshing(AMeshingTool):
ratio = (alpha[j0] - beta[j0-1]) \ ratio = (alpha[j0] - beta[j0-1]) \
/ (beta[j0] - beta[j0-1]) / (beta[j0] - beta[j0-1])
if ratio < 0.0: if ratio < 0.0:
print(f"ratio négatif {ratio}") logger.warning(f"ratio négatif {ratio}")
# on double le point a gauche # on double le point a gauche
p = sect2.point(start2+j0-1).copy() p = sect2.point(start2+j0-1).copy()
sect2.insert_point(start2+j0-1, p) sect2.insert_point(start2+j0-1, p)

View File

@ -304,8 +304,10 @@ class BoundaryConditionAdisTS(SQLSubModel):
if pol_id not in pid_pol: if pol_id not in pid_pol:
# ⚠️ cas important : probablement déjà migré # ⚠️ cas important : probablement déjà migré
print(f"[WARN] pol_id {pol_id} not in pid_pol " + logger.warning(
f"→ probably already migrated") f"pol_id {pol_id} not in pid_pol " +
"→ probably already migrated"
)
continue continue
execute( execute(

View File

@ -338,6 +338,13 @@ class PointXYZ(Point):
self._z = float(value) self._z = float(value)
self.modified() self.modified()
def shift(self, x, y, z):
self.x += x
self.y += y
self.z += z
self.modified()
def get_coordinate(self): def get_coordinate(self):
return (self._x, self._y, self._z) return (self._x, self._y, self._z)

View File

@ -24,7 +24,7 @@ from typing import List
from functools import reduce from functools import reduce
from dataclasses import dataclass from dataclasses import dataclass
from tools import timer, flatten from tools import trace, timer, flatten
from shapely import geometry from shapely import geometry
from Model.Tools.PamhyrDB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
@ -396,7 +396,6 @@ class ProfileXYZ(Profile, SQLSubModel):
for point in self._points: for point in self._points:
p = point.copy() p = point.copy()
print(p)
new_p._points.append(p) new_p._points.append(p)
new_p.modified() new_p.modified()
@ -1074,16 +1073,10 @@ class ProfileXYZ(Profile, SQLSubModel):
def shift(self, x, y, z): def shift(self, x, y, z):
for p in self._points: for p in self._points:
p.x = p.x + x p.shift(x, y, z)
p.y = p.y + y
p.z = p.z + z
self.modified() self.modified()
def modified(self):
self.tab_up_to_date = False
self.station_up_to_date = False
def add_npoints(self, npoints): def add_npoints(self, npoints):
# add npoints in a profile # add npoints in a profile
for k in range(npoints): for k in range(npoints):

View File

@ -52,6 +52,11 @@ class Reach(SQLSubModel):
self._guidelines_is_valid = False self._guidelines_is_valid = False
self._guidelines = {} self._guidelines = {}
super(Reach, self).__init__(
id=parent.pamhyr_id,
status=status,
)
@property @property
def pamhyr_id(self): def pamhyr_id(self):
return self._parent.pamhyr_id return self._parent.pamhyr_id
@ -60,6 +65,10 @@ class Reach(SQLSubModel):
def _pamhyr_id(self): def _pamhyr_id(self):
return self._parent.pamhyr_id return self._parent.pamhyr_id
@_pamhyr_id.setter
def _pamhyr_id(self, id):
return
@classmethod @classmethod
def _db_create(cls, execute): def _db_create(cls, execute):
return cls._create_submodel(execute) return cls._create_submodel(execute)
@ -103,6 +112,9 @@ class Reach(SQLSubModel):
predicate=lambda obj, data: True, predicate=lambda obj, data: True,
modifier=lambda obj, data: None, modifier=lambda obj, data: None,
data={}): data={}):
if predicate(self, data):
modifier(self, data)
for p in self._profiles: for p in self._profiles:
p._data_traversal(predicate, modifier, data) p._data_traversal(predicate, modifier, data)
@ -199,13 +211,12 @@ class Reach(SQLSubModel):
else: else:
gi = self.get_global_profil_index(index) gi = self.get_global_profil_index(index)
profile.num = gi profile.num = gi
print(f"gi = {gi}")
self._profiles.insert(gi, profile) self._profiles.insert(gi, profile)
self.modified() self.modified()
def delete(self, indexes): def delete(self, indexes):
"""Delete some elements in profile list """Set some elements as deleted in profile list
Args: Args:
indexes: The list of index to delete indexes: The list of index to delete
@ -247,7 +258,7 @@ class Reach(SQLSubModel):
self.modified() self.modified()
def delete_profiles(self, profiles): def delete_profiles(self, profiles):
"""Delete some elements in profile list """Set profiles list as deleted
Args: Args:
profiles: The list of profile to delete profiles: The list of profile to delete

View File

@ -183,7 +183,6 @@ class BasicHS(SQLSubModel):
bhs._data = BHSValue._db_load( bhs._data = BHSValue._db_load(
execute, data execute, data
) )
print(f"{bhs_pid} : {deleted}")
if deleted: if deleted:
bhs.set_as_deleted() bhs.set_as_deleted()

View File

@ -588,8 +588,6 @@ class InitialConditions(SQLSubModel):
for data in self._data: for data in self._data:
data_height[data["rk"].rk] = data["height"] data_height[data["rk"].rk] = data["height"]
print(data_height)
incline = self._reach.reach.get_incline_median_mean() incline = self._reach.reach.get_incline_median_mean()
logger.debug(f"incline = {incline}") logger.debug(f"incline = {incline}")

View File

@ -104,6 +104,12 @@ class PollutantCharacteristics(SQLSubModel):
self._ac = value self._ac = value
elif key == 8: elif key == 8:
self._bc = value self._bc = value
self.propagate_modified()
def propagate_modified(self):
self.modified()
if hasattr(self, "_pollutant") and self._pollutant:
self._pollutant.modified()
@classmethod @classmethod
def _db_create(cls, execute, ext=""): def _db_create(cls, execute, ext=""):
@ -256,6 +262,12 @@ class PollutantCharacteristics(SQLSubModel):
if not self.must_be_saved(): if not self.must_be_saved():
return True return True
execute(
"DELETE FROM pollutants_characteristics " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute( execute(
"INSERT INTO " + "INSERT INTO " +
"pollutants_characteristics(pamhyr_id, deleted, " + "pollutants_characteristics(pamhyr_id, deleted, " +
@ -312,7 +324,7 @@ class Pollutants(SQLSubModel):
@name.setter @name.setter
def name(self, name): def name(self, name):
self._name = name self._name = name
self._status.modified() self.modified()
@property @property
def data(self): def data(self):

View File

@ -19,7 +19,7 @@
from tools import trace, timer from tools import trace, timer
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrList import PamhyrModelList from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Pollutants.Pollutants import Pollutants from Model.Pollutants.Pollutants import Pollutants

View File

@ -263,8 +263,6 @@ class Reservoir(SQLSubModel):
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes) cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
Scenario.update_db_add_scenario(execute, table) Scenario.update_db_add_scenario(execute, table)
print(execute(f"SELECT * FROM {table}"))
cls._db_create(execute, ext="_tmp") cls._db_create(execute, ext="_tmp")
execute( execute(
f"INSERT INTO {table}_tmp " + f"INSERT INTO {table}_tmp " +

View File

@ -19,7 +19,7 @@
import os import os
import logging import logging
from tools import logger_exception from tools import logger_exception, timer, flatten
from Model.Tools.PamhyrDB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
@ -35,7 +35,7 @@ class Scenario(SQLSubModel):
"output_rk_adists", "output_rk_adists",
"boundary_condition_adists", "boundary_condition_data_adists", "boundary_condition_adists", "boundary_condition_data_adists",
"lateral_contribution_adists", "lateral_contribution_data_adists", "lateral_contribution_adists", "lateral_contribution_data_adists",
"initial_conditions_adists", "initial_conditions_adists_data", "initial_conditions_adists", "initial_conditions_adists_spec",
"d90_adists", "d90_adists_spec", "d90_adists", "d90_adists_spec",
"dif_adists_spec", "dif_adists_spec",
"pollutants", "pollutants_characteristics", "pollutants", "pollutants_characteristics",
@ -225,10 +225,13 @@ class Scenario(SQLSubModel):
return aux(self, []) return aux(self, [])
def drop_deleted_data(self, execute): @timer
def clean_deleted_data(self, execute):
tables = self.tables_with_deleted_column tables = self.tables_with_deleted_column
branch = self.get_parent_branch() branch = self.get_parent_branch()
all_ids = []
for table in tables: for table in tables:
if self.parent is None: if self.parent is None:
# This scenario is the default scenario, so we can # This scenario is the default scenario, so we can
@ -257,17 +260,23 @@ class Scenario(SQLSubModel):
if ids is None or len(ids) == 0: if ids is None or len(ids) == 0:
continue continue
ids = flatten(ids)
logger.debug( logger.debug(
f"(s{self.id}) Drop deleted data into '{table}' : {ids}" f"({self.name}) Drop deleted data into '{table}' : {ids}"
) )
execute( execute(
f"DELETE FROM {table} " + f"DELETE FROM {table} " +
f"WHERE scenario = {self.id} " + f"WHERE scenario = {self.id} " +
"AND pamhyr_id IN " + "AND pamhyr_id IN " +
f"({', '.join(map(lambda x: str(x[0]), ids))})" f"({', '.join(map(str, ids))})"
) )
all_ids += ids
return all_ids
@property @property
def id(self): def id(self):
return self._id return self._id

View File

@ -16,6 +16,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from functools import reduce
from tools import logger_exception from tools import logger_exception
from Model.Tools.PamhyrDict import PamhyrModelDict from Model.Tools.PamhyrDict import PamhyrModelDict
@ -78,3 +80,12 @@ class Scenarios(PamhyrModelDict):
self._dict[key].set_as_deleted() self._dict[key].set_as_deleted()
self._status.modified() self._status.modified()
def is_leaf(self, scenario):
return not reduce(
lambda acc, s: (
acc or (not s.is_deleted() and s.parent is scenario)
),
self._dict.values(),
False
)

View File

@ -24,12 +24,21 @@ from datetime import datetime
from tools import timer, timestamp from tools import timer, timestamp
from functools import reduce from functools import reduce
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.Tools.PamhyrDB import SQLModel from Model.Tools.PamhyrDB import SQLModel
from Model.Scenarios import Scenarios from Model.Scenarios import Scenarios
from Model.Scenario import Scenario from Model.Scenario import Scenario
from Model.Status import StudyStatus from Model.Status import StudyStatus
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.River import River from Model.River import River
from Model.Geometry.Reach import Reach
from Model.HydraulicStructures.HydraulicStructures import (
HydraulicStructure
)
from Model.HydraulicStructures.Basic.HydraulicStructures import (
BasicHS
)
from Checker.Study import * from Checker.Study import *
@ -450,14 +459,59 @@ class Study(SQLModel):
data=progress data=progress
) )
# Clear DB for each scenarios # Clear DB for current scenario
for scenar in self.scenarios.lst: ids = self.status.scenario.clean_deleted_data(self.execute)
scenar.drop_deleted_data(self.execute) self.memory_clean(ids)
progress() progress()
self.commit() self.commit()
@timer
def memory_clean(self, ids):
if len(ids) == 0:
return
reach_class = Reach
hs_classes = [HydraulicStructure, BasicHS]
list_classes = set(PamhyrModelList.__subclasses__())
dict_classes = set(PamhyrModelDict.__subclasses__())
def modifier(obj, data):
t = type(obj)
if t is reach_class:
obj._profiles = list(
filter(
lambda el: el.id not in ids,
obj._profiles
)
)
elif t in hs_classes:
obj._data = list(
filter(
lambda el: el.id not in ids,
obj._data
)
)
elif t in dict_classes:
new = {}
for key in obj._dict:
if obj._dict[key].id not in ids:
new[key] = obj._dict[key]
obj._dict = new
elif t in list_classes:
obj._lst = list(
filter(
lambda el: el.id not in ids,
obj._lst
)
)
self.river._data_traversal(
lambda o, d: True, modifier, {}
)
def sql_save_request_count(self, *args, **kargs): def sql_save_request_count(self, *args, **kargs):
return self._count() return self._count()

View File

@ -26,6 +26,8 @@ from functools import reduce
from SQL import SQL from SQL import SQL
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from tools import trace
from Model.Tools.PamhyrID import PamhyrID from Model.Tools.PamhyrID import PamhyrID
logger = logging.getLogger() logger = logging.getLogger()
@ -48,11 +50,9 @@ class SQLModel(SQL):
self._cur = self._db.cursor() self._cur = self._db.cursor()
if is_new: if is_new:
logger.info("Create database")
self._create() # Create db self._create() # Create db
# self._save() # Save # self._save() # Save
else: else:
logger.info("Update database")
self._update() # Update db scheme if necessary self._update() # Update db scheme if necessary
# self._load() # Load data # self._load() # Load data
@ -313,8 +313,10 @@ class SQLSubModel(PamhyrID):
if node_id not in nodes: if node_id not in nodes:
# ⚠️ cas important : probablement déjà migré # ⚠️ cas important : probablement déjà migré
print(f"[WARN] node_id {node_id} not in nodes " + logger.warning(
f"→ probably already migrated") f"node_id {node_id} not in nodes " +
"→ probably already migrated"
)
continue continue
execute( execute(

View File

@ -97,6 +97,9 @@ class PamhyrModelDict(SQLSubModel):
Nothing Nothing
""" """
if predicate(self, data):
modifier(self, data)
for key in self._dict: for key in self._dict:
self._dict[key]\ self._dict[key]\
._data_traversal(predicate, modifier, data) ._data_traversal(predicate, modifier, data)

View File

@ -100,6 +100,9 @@ class PamhyrModelList(SQLSubModel):
Nothing Nothing
""" """
if predicate(self, data):
modifier(self, data)
for el in self._lst: for el in self._lst:
el._data_traversal(predicate, modifier, data) el._data_traversal(predicate, modifier, data)

View File

@ -107,6 +107,9 @@ class PamhyrModelList(SQLSubModel):
Returns: Returns:
Nothing Nothing
""" """
if predicate(self, data):
modifier(self, data)
for el in self._lst: for el in self._lst:
el._data_traversal(predicate, modifier, data) el._data_traversal(predicate, modifier, data)

View File

@ -426,7 +426,6 @@ class Rubar3(CommandLineSolver):
coeff = coeff_min coeff = coeff_min
else: else:
for s in lst: for s in lst:
print(s.begin_rk, s.end_rk)
if (rk >= s.begin_rk and rk <= s.end_rk or if (rk >= s.begin_rk and rk <= s.end_rk or
rk <= s.begin_rk and rk >= s.end_rk): rk <= s.begin_rk and rk >= s.end_rk):
coeff = s.begin_strickler # TODO: inerpolate coeff = s.begin_strickler # TODO: inerpolate
@ -473,7 +472,6 @@ class Rubar3(CommandLineSolver):
last = profiles[-1] last = profiles[-1]
if first not in data or last not in data: if first not in data or last not in data:
print(data)
logger.error( logger.error(
"Study initial condition is not fully defined" "Study initial condition is not fully defined"
) )

View File

@ -251,7 +251,6 @@ class GeometryReachTableModel(PamhyrTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def meshing(self, mesher, data, tableView): def meshing(self, mesher, data, tableView):
new_profiles = mesher.meshing( new_profiles = mesher.meshing(
self._data, self._data,
**data **data
@ -287,7 +286,6 @@ class GeometryReachTableModel(PamhyrTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def purge(self, np_purge): def purge(self, np_purge):
self._undo.push( self._undo.push(
PurgeCommand( PurgeCommand(
self._data, np_purge self._data, np_purge
@ -296,7 +294,6 @@ class GeometryReachTableModel(PamhyrTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def shift(self, rows, dx, dy, dz): def shift(self, rows, dx, dy, dz):
self._undo.push( self._undo.push(
ShiftCommand( ShiftCommand(
self._data, rows, dx, dy, dz self._data, rows, dx, dy, dz
@ -305,7 +302,6 @@ class GeometryReachTableModel(PamhyrTableModel):
self.layoutChanged.emit() self.layoutChanged.emit()
def change_reach(self, new_reach, parent): def change_reach(self, new_reach, parent):
self._undo.push( self._undo.push(
ChangeReachCommand( ChangeReachCommand(
new_reach, new_reach,

View File

@ -411,13 +411,13 @@ class ShiftCommand(QUndoCommand):
def undo(self): def undo(self):
for i in self._rows: for i in self._rows:
profile = self._reach.profiles[i] profile = self._reach.profiles[i]
self._reach.profiles[i].shift( profile.shift(
-self._dx, -self._dy, -self._dz -self._dx, -self._dy, -self._dz
) )
def redo(self): def redo(self):
for i in self._rows: for i in self._rows:
profile = self._reach.profiles[i] profile = self._reach.profiles[i]
self._reach.profiles[i].shift( profile.shift(
self._dx, self._dy, self._dz self._dx, self._dy, self._dz
) )

View File

@ -136,13 +136,18 @@ class PlotRKC(PamhyrPlot):
hs_color = [] hs_color = []
index = self.parent.tableView.selectedIndexes() index = self.parent.tableView.selectedIndexes()
for i, hs in enumerate(lhs): for i, hs in enumerate(lhs):
section = hs.input_section
if section is None:
continue
if i == index[0].row(): if i == index[0].row():
hs_color.append("blue") color = "blue"
elif hs.enabled: elif hs.enabled:
hs_color.append("red") color = "red"
else: else:
hs_color.append("darkgrey") color = "darkgrey"
x = hs.input_section.rk
x = section.rk
if x is not None: if x is not None:
a = self.canvas.axes.annotate( a = self.canvas.axes.annotate(
" > " + hs.name, " > " + hs.name,
@ -151,12 +156,13 @@ class PlotRKC(PamhyrPlot):
verticalalignment='top', verticalalignment='top',
annotation_clip=True, annotation_clip=True,
fontsize=9, fontsize=9,
color=hs_color[-1], color=color,
) )
self.anotate_lst.append(a) self.anotate_lst.append(a)
vx.append(x) vx.append(x)
vymin.append(min(z_min)) vymin.append(min(z_min))
vymax.append(max(z_max)) vymax.append(max(z_max))
hs_color.append(color)
self.hs_vlines = self.canvas.axes.vlines( self.hs_vlines = self.canvas.axes.vlines(
x=vx, ymin=vymin, ymax=vymax, x=vx, ymin=vymin, ymax=vymax,

View File

@ -127,7 +127,7 @@ class ComboBoxDelegate(QItemDelegate):
) )
) )
value = profiles[0].rk if len(profiles) > 0 else None value = profiles[0].pamhyr_id if len(profiles) > 0 else None
else: else:
value = text value = text
@ -222,15 +222,25 @@ class TableModel(PamhyrTableModel):
) )
) )
elif self._headers[column] == "begin_rk": elif self._headers[column] == "begin_rk":
_edge = self._lst.get(self._tab, row).reach
_begin_rk = next(
p for p in _edge.reach.profiles
if p.pamhyr_id == value
)
self._undo.push( self._undo.push(
SetBeginCommand( SetBeginCommand(
self._lst, self._tab, row, value self._lst, self._tab, row, _begin_rk
) )
) )
elif self._headers[column] == "end_rk": elif self._headers[column] == "end_rk":
_edge = self._lst.get(self._tab, row).reach
_end_rk = next(
p for p in _edge.reach.profiles
if p.pamhyr_id == value
)
self._undo.push( self._undo.push(
SetEndCommand( SetEndCommand(
self._lst, self._tab, row, value self._lst, self._tab, row, _end_rk
) )
) )
except Exception as e: except Exception as e:

View File

@ -925,9 +925,11 @@ class GraphWidget(QGraphicsView):
self.scale(scaleFactor, scaleFactor) self.scale(scaleFactor, scaleFactor)
def mousePressEvent(self, event): def mousePressEvent(self, event):
if self._only_display or self.graph._status.is_read_only(): if self._only_display:
return return
locked = self.graph._status.is_read_only()
pos = self.mapToScene(event.pos()) pos = self.mapToScene(event.pos())
self.clicked = True self.clicked = True
@ -936,9 +938,6 @@ class GraphWidget(QGraphicsView):
super(GraphWidget, self).mousePressEvent(event) super(GraphWidget, self).mousePressEvent(event)
return return
if self.graph._status.is_read_only():
return
# Move item and select edge item # Move item and select edge item
if self._state == "move": if self._state == "move":
self._selected_new_edge_src_node = None self._selected_new_edge_src_node = None
@ -948,13 +947,15 @@ class GraphWidget(QGraphicsView):
edge = items[0] edge = items[0]
if edge: if edge:
self.set_current_edge(edge) self.set_current_edge(edge)
elif items and type(items[0]) is NodeItem: if not locked:
if items and type(items[0]) is NodeItem:
self._mouse_origin_x = pos.x() self._mouse_origin_x = pos.x()
self._mouse_origin_y = pos.y() self._mouse_origin_y = pos.y()
self._current_moved_node = items[0] self._current_moved_node = items[0]
if not locked:
# Add nodes and edges # Add nodes and edges
elif self._state == "add": if self._state == "add":
items = self.items(event.pos()) items = self.items(event.pos())
nodes = list(filter(lambda i: type(i) is NodeItem, items)) nodes = list(filter(lambda i: type(i) is NodeItem, items))
if not nodes: if not nodes:
@ -988,10 +989,12 @@ class GraphWidget(QGraphicsView):
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
self.clicked = False self.clicked = False
if self._only_display or self.graph._status.is_read_only(): if self._only_display:
return return
if self._state == "move": locked = self.graph._status.is_read_only()
if not locked and self._state == "move":
if self._current_moved_node is not None: if self._current_moved_node is not None:
pos = self.mapToScene(event.pos()) pos = self.mapToScene(event.pos())
self._undo.push( self._undo.push(
@ -1007,10 +1010,8 @@ class GraphWidget(QGraphicsView):
super(GraphWidget, self).mouseReleaseEvent(event) super(GraphWidget, self).mouseReleaseEvent(event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.graph._status.is_read_only():
return
pos = self.mapToScene(event.pos()) pos = self.mapToScene(event.pos())
locked = self.graph._status.is_read_only()
# Selecte item on the fly # Selecte item on the fly
items = self.items(event.pos()) items = self.items(event.pos())

View File

@ -48,7 +48,12 @@ _translate = QCoreApplication.translate
class TableModel(PamhyrTableModel): class TableModel(PamhyrTableModel):
def _setup_lst(self): def _setup_lst(self):
self._lst = self._data._Pollutants self._lst = list(
filter(
lambda dif: dif._deleted is False,
self._data._Pollutants._lst
)
)
def rowCount(self, parent): def rowCount(self, parent):
return len(self._lst) return len(self._lst)
@ -61,7 +66,7 @@ class TableModel(PamhyrTableModel):
column = index.column() column = index.column()
if self._headers[column] == "name": if self._headers[column] == "name":
return self._lst.get(row).name return self._lst[row].name
return QVariant() return QVariant()
@ -91,29 +96,36 @@ class TableModel(PamhyrTableModel):
self._undo.push( self._undo.push(
AddCommand( AddCommand(
self._lst, row, self._data.ic_adists self._data._Pollutants, row, self._data.ic_adists
) )
) )
self._setup_lst()
self.endInsertRows() self.endInsertRows()
self.layoutChanged.emit() self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()): def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1]) self.beginRemoveRows(parent, rows[0], rows[-1])
data_rows = {
id(pol): i for i, pol in enumerate(self._data._Pollutants._lst)
}
self._undo.push( self._undo.push(
DelCommand( DelCommand(
self._lst, rows, self._data.ic_adists self._data._Pollutants,
[data_rows[id(self._lst[row])] for row in rows
if 0 <= row < len(self._lst)],
self._data.ic_adists
) )
) )
self._setup_lst()
self.endRemoveRows() self.endRemoveRows()
self.layoutChanged.emit() self.layoutChanged.emit()
def enabled(self, row, enabled, parent=QModelIndex()): def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push( self._undo.push(
SetEnabledCommand( SetEnabledCommand(
self._lst, row, enabled self._data._Pollutants, row, enabled
) )
) )
self.layoutChanged.emit() self.layoutChanged.emit()

View File

@ -57,6 +57,8 @@ class DefaultMenu(AbstractMenu):
class ScenarioMenu(AbstractMenu): class ScenarioMenu(AbstractMenu):
def run(self): def run(self):
item = self._items[0] item = self._items[0]
scenarios = item.graph._study.scenarios
current_scenario = item.graph._study.status.scenario.id current_scenario = item.graph._study.status.scenario.id
select = self._menu.addAction(self._trad["menu_select_scenario"]) select = self._menu.addAction(self._trad["menu_select_scenario"])
@ -64,7 +66,9 @@ class ScenarioMenu(AbstractMenu):
delete = None delete = None
if item.scenario.id != 0: if item.scenario.id != 0:
if scenarios.is_leaf(item.scenario):
delete = self._menu.addAction(self._trad["menu_del_scenario"]) delete = self._menu.addAction(self._trad["menu_del_scenario"])
if item.scenario.id == current_scenario: if item.scenario.id == current_scenario:
duplicate = self._menu.addAction( duplicate = self._menu.addAction(
self._trad["menu_dup_scenario"] self._trad["menu_dup_scenario"]

View File

@ -426,30 +426,35 @@ class GraphWidget(QGraphicsView):
dlg.setWindowTitle(self._trad["mb_save_title"]) dlg.setWindowTitle(self._trad["mb_save_title"])
dlg.setText(self._trad["mb_save_msg"]) dlg.setText(self._trad["mb_save_msg"])
opt = QMessageBox.Save | QMessageBox.Cancel opt = QMessageBox.Cancel | QMessageBox.Save | QMessageBox.Discard
dlg.setStandardButtons(opt) dlg.setStandardButtons(opt)
dlg.setIcon(QMessageBox.Warning) dlg.setIcon(QMessageBox.Warning)
dlg.button(QMessageBox.Save).setText(self._trad["Save"]) dlg.button(QMessageBox.Save).setText(self._trad["Save"])
dlg.button(QMessageBox.Discard).setText(self._trad["Don't save"])
dlg.button(QMessageBox.Cancel).setText(self._trad["Cancel"]) dlg.button(QMessageBox.Cancel).setText(self._trad["Cancel"])
res = dlg.exec() res = dlg.exec()
if res == QMessageBox.Save: if res == QMessageBox.Save:
return True return "Save"
elif res == QMessageBox.Cancel: elif res == QMessageBox.Cancel:
return False return "Cancel"
else:
return "Discard"
def select_scenario(self, item): def select_scenario(self, item):
if type(item) is not ScenarioItem: if type(item) is not ScenarioItem:
return return
must_saved = self.dialog_save() must_save = self.dialog_save()
if must_save == "Cancel":
return
def fn(): def fn():
self._close_other_window() self._close_other_window()
if must_saved: if must_save == "Save":
self._study.save() self._study.save()
self._undo.push( self._undo.push(
@ -465,9 +470,16 @@ class GraphWidget(QGraphicsView):
self.changeScenario.emit(self.sender()) self.changeScenario.emit(self.sender())
def new_scenario(self, pos): def new_scenario(self, pos):
must_save = self.dialog_save()
if must_save == "Cancel":
return
def fn(): def fn():
self._close_other_window() self._close_other_window()
if must_save == "Save":
self._study.save() self._study.save()
self._undo.push( self._undo.push(
AddScenariosCommand( AddScenariosCommand(
self._study, self._study,
@ -495,9 +507,16 @@ class GraphWidget(QGraphicsView):
self.changeScenario.emit(self.sender()) self.changeScenario.emit(self.sender())
def duplicate_scenario(self, item): def duplicate_scenario(self, item):
must_save = self.dialog_save()
if must_save == "Cancel":
return
def fn(): def fn():
self._close_other_window() self._close_other_window()
# self._study.save()
if must_save == "Save":
self._study.save()
self._undo.push( self._undo.push(
DuplicateScenariosCommand( DuplicateScenariosCommand(
self._study, self._study,

View File

@ -281,6 +281,7 @@ class MainTranslate(UnitTranslate):
self._dict["No"] = _translate("MainWindow", "No") self._dict["No"] = _translate("MainWindow", "No")
self._dict["Cancel"] = _translate("MainWindow", "Cancel") self._dict["Cancel"] = _translate("MainWindow", "Cancel")
self._dict["Save"] = _translate("MainWindow", "Save") self._dict["Save"] = _translate("MainWindow", "Save")
self._dict["Don't save"] = _translate("MainWindow", "Don't save")
self._dict["Close"] = _translate("MainWindow", "Close") self._dict["Close"] = _translate("MainWindow", "Close")
self._dict["Solver"] = _translate("MainWindow", "Solver") self._dict["Solver"] = _translate("MainWindow", "Solver")

View File

@ -90,6 +90,7 @@ class WaitingDialog(PamhyrDialog):
options=[], options=[],
parent=parent parent=parent
) )
self._to_close = False
self._payload_fn = payload_fn self._payload_fn = payload_fn
@ -131,7 +132,7 @@ class WaitingDialog(PamhyrDialog):
) )
def end_worker(self): def end_worker(self):
self._worker_thread.terminate() self._worker_thread.quit()
self._worker_thread.wait() self._worker_thread.wait()
def close(self): def close(self):
@ -141,4 +142,12 @@ class WaitingDialog(PamhyrDialog):
except Exception as e: except Exception as e:
logger_exception(e) logger_exception(e)
self._to_close = True
super().close() super().close()
def closeEvent(self, event):
if self._to_close:
super().closeEvent(event)
else:
event.ignore()