44from PySide6 .QtGui import QColor
55
66from ..C import COLUMNS
7- from ..utils import validate_value , create_empty_dataframe , is_invalid , get_selected
7+ from ..utils import validate_value , create_empty_dataframe , is_invalid , \
8+ get_selected
89
910
1011class PandasTableModel (QAbstractTableModel ):
@@ -47,7 +48,7 @@ def data(self, index, role=Qt.DisplayRole):
4748 if column == 0 :
4849 value = self ._data_frame .index [row ]
4950 return str (value )
50- value = self ._data_frame .iloc [row , column - 1 ]
51+ value = self ._data_frame .iloc [row , column - 1 ]
5152 if is_invalid (value ):
5253 return ""
5354 return str (value )
@@ -118,7 +119,8 @@ def insertColumn(self, column_name: str):
118119 )
119120 position = self ._data_frame .shape [1 ]
120121 self .beginInsertColumns (QModelIndex (), position , position )
121- column_type = self ._allowed_columns .get (column_name , {"type" : "STRING" })["type" ]
122+ column_type = \
123+ self ._allowed_columns .get (column_name , {"type" : "STRING" })["type" ]
122124 default_value = "" if column_type == "STRING" else 0
123125 self ._data_frame [column_name ] = default_value
124126 self .endInsertColumns ()
@@ -148,7 +150,6 @@ def _set_data_single(self, index, value):
148150 if index .row () == self ._data_frame .shape [0 ]:
149151 # empty row at the end
150152 self .insertRows (index .row (), 1 )
151- self .layoutChanged .emit ()
152153 next_index = self .index (index .row (), 0 )
153154 self .inserted_row .emit (next_index )
154155 if index .column () == 0 and self ._has_named_index :
@@ -177,7 +178,8 @@ def _set_data_single(self, index, value):
177178 self .cell_needs_validation .emit (row , column )
178179 self .something_changed .emit (True )
179180 return True
180- if column_name in ["conditionId" , "simulationConditionId" , "preequilibrationConditionId" ]:
181+ if column_name in ["conditionId" , "simulationConditionId" ,
182+ "preequilibrationConditionId" ]:
181183 self ._data_frame .iloc [row , column - col_setoff ] = value
182184 self .dataChanged .emit (index , index , [Qt .DisplayRole ])
183185 self .relevant_id_changed .emit (value , old_value , "condition" )
@@ -214,11 +216,31 @@ def handle_named_index(self, index, value):
214216
215217 def replace_text (self , old_text : str , new_text : str ):
216218 """Replace text in the table."""
217- self ._data_frame .replace (old_text , new_text , inplace = True )
219+ # find all occurences of old_text and sae indices
220+ mask = self ._data_frame .eq (old_text )
221+ if mask .any ().any ():
222+ self ._data_frame .replace (old_text , new_text , inplace = True )
223+ # Get first and last modified cell for efficient `dataChanged` emit
224+ changed_cells = mask .stack ()[
225+ mask .stack ()].index .tolist () # Extract (row, col) pairs
226+ if changed_cells :
227+ first_row , first_col = changed_cells [0 ]
228+ last_row , last_col = changed_cells [- 1 ]
229+ if self ._has_named_index :
230+ first_col += 1
231+ last_col += 1
232+ top_left = self .index (first_row , first_col )
233+ bottom_right = self .index (last_row , last_col )
234+ self .dataChanged .emit (top_left , bottom_right , [Qt .DisplayRole ])
218235 # also replace in the index
219- if self ._has_named_index :
236+ if self ._has_named_index and old_text in self . _data_frame . index :
220237 self ._data_frame .rename (index = {old_text : new_text }, inplace = True )
221- self .layoutChanged .emit ()
238+ index_row = self ._data_frame .index .get_loc (new_text )
239+ index_top_left = self .index (index_row , 0 )
240+ index_bottom_right = self .index (index_row , 0 )
241+ self .dataChanged .emit (
242+ index_top_left , index_bottom_right , [Qt .DisplayRole ]
243+ )
222244
223245 def get_df (self ):
224246 """Return the DataFrame."""
@@ -332,10 +354,16 @@ def check_selection(self):
332354 return len (rows ) > 1 and len (cols ) == 1 , selected
333355
334356 def reset_invalid_cells (self ):
335- """Reset the invalid cells."""
336- self ._invalid_cells = set ()
337- self .layoutChanged .emit ()
357+ """Reset the invalid cells and update their background color."""
358+ if not self ._invalid_cells :
359+ return
360+
361+ invalid_cells = list (self ._invalid_cells )
362+ self ._invalid_cells .clear () # Clear invalid cells set
338363
364+ for row , col in invalid_cells :
365+ index = self .index (row , col )
366+ self .dataChanged .emit (index , index , [Qt .BackgroundRole ])
339367 def mimeData (self , rectangle , start_index ):
340368 """Return the data to be copied to the clipboard.
341369
@@ -528,6 +556,7 @@ def return_column_index(self, column_name):
528556
529557class ObservableModel (IndexedPandasTableModel ):
530558 """Table model for the observable data."""
559+
531560 def __init__ (self , data_frame , parent = None ):
532561 super ().__init__ (
533562 data_frame = data_frame ,
@@ -562,6 +591,7 @@ def fill_row(self, row_position: int, data: dict):
562591
563592class ParameterModel (IndexedPandasTableModel ):
564593 """Table model for the parameter data."""
594+
565595 def __init__ (self , data_frame , parent = None ):
566596 super ().__init__ (
567597 data_frame = data_frame ,
@@ -573,6 +603,7 @@ def __init__(self, data_frame, parent=None):
573603
574604class ConditionModel (IndexedPandasTableModel ):
575605 """Table model for the condition data."""
606+
576607 def __init__ (self , data_frame , parent = None ):
577608 super ().__init__ (
578609 data_frame = data_frame ,
@@ -610,36 +641,22 @@ def __init__(self, model, parent=None):
610641 super ().__init__ (parent )
611642 self .source_model = model
612643 self .setSourceModel (model )
613- self .column_filters = {} # Store filters for multiple columns
614-
615- def setFilterForColumn (self , column , pattern ):
616- """Set filter pattern for a specific column."""
617- if pattern :
618- self .column_filters [column ] = pattern # Add or update filter for the column
619- else :
620- self .column_filters .pop (column , None ) # Remove filter if pattern is empty
621- self .invalidateFilter () # Trigger the proxy to re-evaluate the filters
622644
623645 def filterAcceptsRow (self , source_row , source_parent ):
624- """Custom filtering logic to apply filters on multiple columns."""
646+ """Custom filtering logic to apply global filtering across all columns."""
625647 source_model = self .sourceModel ()
626648
627649 # Always accept the last row (for "add new row")
628650 if source_row == source_model .rowCount () - 1 :
629651 return True
630652
631- # Apply all column filters
632- for column , pattern in self .column_filters .items ():
633- index = source_model .index (source_row , column , source_parent )
634- value = source_model .data (index , Qt .DisplayRole )
635- if not self .valueMatchesFilter (value , pattern ):
636- return False # Reject the row if any column doesn't match its filter
637-
638- return True # Accept row if it matches all filters
639-
640- def valueMatchesFilter (self , value , pattern ):
641- """Check if the value matches the filter pattern."""
642- if pattern and pattern not in str (value ):
643- return False
644- return True
653+ regex = self .filterRegularExpression ()
654+ if regex .pattern () == "" :
655+ return True
645656
657+ for column in range (source_model .columnCount ()):
658+ index = source_model .index (source_row , column , QModelIndex ())
659+ data_str = str (source_model .data (index ) or "" )
660+ if regex .match (data_str ).hasMatch ():
661+ return True
662+ return False # No match found
0 commit comments