@@ -81,6 +81,7 @@ def connect_signals(self):
8181 view .button_load_measurement .clicked .connect (self .open_measurements )
8282 view .button_edit_measurement .clicked .connect (self .edit_measurement )
8383 view .button_remove_measurement .clicked .connect (self .remove_measurement )
84+ view .button_copy_measurement .clicked .connect (self .copy_measurement )
8485
8586 view .button_run_matcher .clicked .connect (self .run_matcher )
8687 view .button_edit_corrections .clicked .connect (self .edit_corrections )
@@ -158,6 +159,7 @@ def set_measurement_interaction_buttons_enabled(self, enabled: bool = True):
158159 view .button_edit_measurement ,
159160 view .button_run_matcher ,
160161 view .button_edit_corrections ,
162+ view .button_copy_measurement ,
161163 )
162164 for button in measurement_interaction_buttons :
163165 button .setEnabled (enabled )
@@ -261,6 +263,44 @@ def edit_measurement(self, measurement: OpticsMeasurement | None = None):
261263 if dialog .exec_ () == dialog .Accepted :
262264 LOGGER .debug ("Edit dialog closed. Updating measurement." )
263265
266+ @Slot ()
267+ def copy_measurement (self , measurement : OpticsMeasurement | None = None ):
268+ """ Create a copy of the given measurement and add it to the GUI.
269+ If no measurement is given, the currently selected measurement is copied.
270+
271+ Args:
272+ measurement (OpticsMeasurement | None, optional): The measurement to copy. Defaults to None.
273+ """
274+ if measurement is None :
275+ try :
276+ measurement = self .get_single_measurement ()
277+ except ValueError as e :
278+ LOGGER .warning (str (e ))
279+ return
280+
281+ if measurement .output_dir is None : # should not be possible, but better to catch
282+ LOGGER .error ("Cannot copy measurement without output directory." )
283+ return
284+
285+ LOGGER .debug (f"Copying { measurement .display ()} ." )
286+ new_measurement = measurement .copy ()
287+
288+ # might already have a counter from previous copy
289+ name = re .sub (r"_\d+$" , "" , measurement .output_dir .name )
290+
291+ for count in range (1 , 1000 ): # limit to avoid infinite loop
292+ new_measurement .output_dir = measurement .output_dir .with_name (f"{ name } _{ count :d} " )
293+ try :
294+ self .add_measurement (new_measurement )
295+ except ValueError :
296+ continue
297+ break
298+ else :
299+ LOGGER .error (
300+ "Could not copy measurement. Counter limit exeeded. Not sure what went wrong."
301+ )
302+ return
303+
264304 @Slot ()
265305 def remove_measurement (self , measurements : Sequence [OpticsMeasurement ] | None = None ):
266306 """ Remove measurements from the GUI.
@@ -298,6 +338,10 @@ def measurement_selection_changed(self, measurements: Sequence[OpticsMeasurement
298338 return
299339
300340 self .set_measurement_interaction_buttons_enabled (True )
341+ if len (measurements ) > 1 :
342+ view .button_edit_measurement .setEnabled (False )
343+ view .button_copy_measurement .setEnabled (False )
344+
301345 self .set_all_segment_buttons_enabled (True )
302346
303347 # Group the segments for the measurements into table-items when they have the same defintion ---
0 commit comments