Skip to content

Commit 056f2e5

Browse files
committed
Strong refactoring, trying to disentangle imports from models views and controllers
1 parent 35f1908 commit 056f2e5

File tree

14 files changed

+606
-543
lines changed

14 files changed

+606
-543
lines changed

src/petab_gui/controllers/mother_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
process_file,
4343
)
4444
from ..views import TaskBar
45-
from ..views.other_views import NextStepsPanel
45+
from ..views.dialogs import NextStepsPanel
4646
from .logger_controller import LoggerController
4747
from .sbml_controller import SbmlController
4848
from .table_controllers import (

src/petab_gui/controllers/sbml_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from ..C import DEFAULT_ANTIMONY_TEXT
1111
from ..models.sbml_model import SbmlViewerModel
12-
from ..utils import sbmlToAntimony
12+
from ..models.sbml_utils import sbml_to_antimony
1313
from ..views.sbml_view import SbmlViewer
1414

1515

@@ -67,7 +67,7 @@ def reset_to_original_model(self):
6767
self.model.sbml_text = libsbml.writeSBMLToString(
6868
self.model._sbml_model_original.sbml_model.getSBMLDocument()
6969
)
70-
self.model.antimony_text = sbmlToAntimony(self.model.sbml_text)
70+
self.model.antimony_text = sbml_to_antimony(self.model.sbml_text)
7171
self.view.sbml_text_edit.setPlainText(self.model.sbml_text)
7272
self.view.antimony_text_edit.setPlainText(self.model.antimony_text)
7373

src/petab_gui/controllers/table_controllers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,21 @@
2323
PandasTableFilterProxy,
2424
PandasTableModel,
2525
)
26+
from ..resources.whats_this import WHATS_THIS
2627
from ..settings_manager import settings_manager
2728
from ..utils import (
2829
CaptureLogHandler,
29-
ConditionInputDialog,
3030
get_selected,
3131
process_file,
3232
)
33-
from ..views.other_views import DoseTimeDialog
33+
from ..views.dialogs import ConditionInputDialog, DoseTimeDialog
3434
from ..views.table_view import (
3535
ColumnSuggestionDelegate,
3636
ComboBoxDelegate,
3737
ParameterIdSuggestionDelegate,
3838
SingleSuggestionDelegate,
3939
TableViewer,
4040
)
41-
from ..views.whats_this import WHATS_THIS
4241
from .utils import linter_wrapper, prompt_overwrite_or_append, save_petab_table
4342

4443

File renamed without changes.

src/petab_gui/models/pandas_table_model.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
Signal,
1111
)
1212
from PySide6.QtGui import QBrush, QColor, QPalette
13-
from PySide6.QtWidgets import QApplication
1413

1514
from ..C import COLUMNS
1615
from ..commands import (
@@ -19,16 +18,42 @@
1918
ModifyRowCommand,
2019
RenameIndexCommand,
2120
)
22-
from ..controllers.default_handler import DefaultHandlerModel
21+
from ..resources.whats_this import column_whats_this
2322
from ..settings_manager import settings_manager
2423
from ..utils import (
2524
create_empty_dataframe,
2625
get_selected,
27-
is_invalid,
28-
validate_value,
2926
)
30-
from ..views.whats_this import column_whats_this
27+
from .default_handler import DefaultHandlerModel
3128
from .tooltips import cell_tip, header_tip
29+
from .validators import is_invalid, validate_value
30+
31+
32+
def _get_system_palette_color(role):
33+
"""Get system palette color, with fallback if Qt is not available.
34+
35+
Args:
36+
role: QPalette color role (e.g., QPalette.Highlight)
37+
38+
Returns:
39+
QColor: The system color or a fallback color
40+
"""
41+
try:
42+
# Try to get system palette from QApplication
43+
from PySide6.QtWidgets import QApplication
44+
45+
app = QApplication.instance()
46+
if app:
47+
return app.palette().color(role)
48+
except (ImportError, RuntimeError):
49+
pass
50+
51+
# Fallback colors when Qt is not available or no QApplication
52+
fallback_colors = {
53+
QPalette.Highlight: QColor(51, 153, 255, 100), # Light blue
54+
QPalette.HighlightedText: QColor(255, 255, 255), # White
55+
}
56+
return fallback_colors.get(role, QColor(0, 0, 0))
3257

3358

3459
class PandasTableModel(QAbstractTableModel):
@@ -88,6 +113,13 @@ def __init__(
88113
self.config = settings_manager.get_table_defaults(table_type)
89114
self.default_handler = DefaultHandlerModel(self, self.config)
90115
self.undo_stack = undo_stack
116+
# Cache colors to avoid runtime dependency on QApplication
117+
self._highlight_bg_color = _get_system_palette_color(
118+
QPalette.Highlight
119+
)
120+
self._highlight_fg_color = _get_system_palette_color(
121+
QPalette.HighlightedText
122+
)
91123

92124
def rowCount(self, parent=None):
93125
"""Return the number of rows in the model.
@@ -159,9 +191,9 @@ def data(self, index, role=Qt.DisplayRole):
159191
if role == Qt.BackgroundRole:
160192
return self.determine_background_color(row, column)
161193
if role == Qt.ForegroundRole:
162-
# Return yellow text if this cell is a match
194+
# Return highlighted text color if this cell is a match
163195
if (row, column) in self.highlighted_cells:
164-
return QApplication.palette().color(QPalette.HighlightedText)
196+
return self._highlight_fg_color
165197
return QBrush(QColor(0, 0, 0)) # Default black text
166198
if role == Qt.ToolTipRole:
167199
if row == self._data_frame.shape[0]:
@@ -880,7 +912,7 @@ def determine_background_color(self, row, column):
880912
if (row, column) == (self._data_frame.shape[0], 0):
881913
return QColor(144, 238, 144, 150)
882914
if (row, column) in self.highlighted_cells:
883-
return QApplication.palette().color(QPalette.Highlight)
915+
return self._highlight_bg_color
884916
if (row, column) in self._invalid_cells:
885917
return QColor(255, 100, 100, 150)
886918
if row % 2 == 0:

src/petab_gui/models/sbml_model.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from PySide6.QtCore import QObject, QSignalBlocker, Signal
88

99
from ..C import DEFAULT_ANTIMONY_TEXT
10-
from ..utils import antimonyToSBML, sbmlToAntimony
10+
from .sbml_utils import antimony_to_sbml, sbml_to_antimony
1111

1212

1313
class SbmlViewerModel(QObject):
@@ -30,20 +30,20 @@ def __init__(self, sbml_model: petab.models.Model, parent=None):
3030
self.sbml_text = libsbml.writeSBMLToString(
3131
self._sbml_model_original.sbml_model.getSBMLDocument()
3232
)
33-
self.antimony_text = sbmlToAntimony(self.sbml_text)
33+
self.antimony_text = sbml_to_antimony(self.sbml_text)
3434
else:
3535
self.antimony_text = DEFAULT_ANTIMONY_TEXT
3636
with QSignalBlocker(self):
3737
self.convert_antimony_to_sbml()
3838
self.model_id = self._get_model_id()
3939

4040
def convert_sbml_to_antimony(self):
41-
self.antimony_text = sbmlToAntimony(self.sbml_text)
41+
self.antimony_text = sbml_to_antimony(self.sbml_text)
4242
self.model_id = self._get_model_id()
4343
self.something_changed.emit(True)
4444

4545
def convert_antimony_to_sbml(self):
46-
self.sbml_text = antimonyToSBML(self.antimony_text)
46+
self.sbml_text = antimony_to_sbml(self.antimony_text)
4747
self.model_id = self._get_model_id()
4848
self.something_changed.emit(True)
4949

src/petab_gui/models/sbml_utils.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""SBML and Antimony conversion utilities."""
2+
3+
import logging
4+
import os
5+
6+
import antimony
7+
8+
9+
def _check_antimony_return_code(code):
10+
"""Helper for checking the antimony response code.
11+
12+
Raises Exception if error in antimony.
13+
14+
Args:
15+
code: antimony response code
16+
17+
Raises:
18+
Exception: If antimony encountered an error
19+
"""
20+
if code < 0:
21+
raise Exception(f"Antimony: {antimony.getLastError()}")
22+
23+
24+
def sbml_to_antimony(sbml):
25+
"""Convert SBML to antimony string.
26+
27+
Args:
28+
sbml: SBML string or file path
29+
30+
Returns:
31+
str: Antimony representation
32+
"""
33+
antimony.clearPreviousLoads()
34+
antimony.freeAll()
35+
isfile = False
36+
try:
37+
isfile = os.path.isfile(sbml)
38+
except Exception as e:
39+
logging.warning(f"Error checking if {sbml} is a file: {str(e)}")
40+
isfile = False
41+
if isfile:
42+
code = antimony.loadSBMLFile(sbml)
43+
else:
44+
code = antimony.loadSBMLString(str(sbml))
45+
_check_antimony_return_code(code)
46+
return antimony.getAntimonyString(None)
47+
48+
49+
def antimony_to_sbml(ant):
50+
"""Convert Antimony to SBML string.
51+
52+
Args:
53+
ant: Antimony string or file path
54+
55+
Returns:
56+
str: SBML representation
57+
"""
58+
antimony.clearPreviousLoads()
59+
antimony.freeAll()
60+
try:
61+
isfile = os.path.isfile(ant)
62+
except ValueError:
63+
isfile = False
64+
if isfile:
65+
code = antimony.loadAntimonyFile(ant)
66+
else:
67+
code = antimony.loadAntimonyString(ant)
68+
_check_antimony_return_code(code)
69+
mid = antimony.getMainModuleName()
70+
return antimony.getSBMLString(mid)

src/petab_gui/models/validators.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Validation utilities for PEtab data."""
2+
3+
import math
4+
5+
import numpy as np
6+
7+
8+
def validate_value(value, expected_type):
9+
"""Validate and convert a value to the expected type.
10+
11+
Args:
12+
value: The value to validate and convert
13+
expected_type: The numpy type to convert the value to
14+
15+
Returns:
16+
tuple: A tuple containing:
17+
- The converted value, or None if conversion failed
18+
- An error message if conversion failed, or None if successful
19+
"""
20+
try:
21+
if expected_type == np.object_:
22+
value = str(value)
23+
elif expected_type == np.float64:
24+
value = float(value)
25+
except ValueError as e:
26+
return None, str(e)
27+
return value, None
28+
29+
30+
def is_invalid(value):
31+
"""Check if a value is invalid.
32+
33+
Args:
34+
value: The value to check
35+
36+
Returns:
37+
bool: True if the value is invalid (None, NaN, or infinity)
38+
"""
39+
if value is None: # None values are invalid
40+
return True
41+
if isinstance(value, str): # Strings can always be displayed
42+
return False
43+
try:
44+
return not math.isfinite(value)
45+
except TypeError:
46+
return True

0 commit comments

Comments
 (0)