@@ -189,18 +189,70 @@ def handle_final_shape(self, shape):
189189 super (EllipseTool , self ).handle_final_shape (shape )
190190
191191
192+ class DeleteCmd (QUndoCommand ):
193+ """Instance of revertable delete command."""
194+
195+ def __init__ (self , plot , mode , items_to_remove ):
196+ """Initialize the delete command."""
197+ super (DeleteCmd , self ).__init__ ()
198+ self .plot = plot
199+ self .mode = mode
200+ self .items_to_remove = items_to_remove
201+
202+ def redo (self ):
203+ """Delete the selected items."""
204+ self .plot .unselect_all ()
205+ if self .mode == 'align' :
206+ for item in self .items_to_remove :
207+ item .parent .roi_list .remove (item )
208+ self .plot .del_items (self .items_to_remove )
209+ self .plot .replot ()
210+
211+ def undo (self ):
212+ """Add the previously removed items back."""
213+ if self .mode == 'align' :
214+ for item in self .items_to_remove :
215+ item .parent .roi_list .append (item )
216+ for item in self .items_to_remove :
217+ self .plot .add_item (item )
218+ self .plot .select_some_items (self .items_to_remove )
219+ self .plot .replot ()
220+
221+
222+ class UpdateROIShapeCmd (QUndoCommand ):
223+ """Instance of revertable ROI shape update command."""
224+
225+ def __init__ (self , plot , roi_to_update , new_points ):
226+ """Initialize the update_roi command."""
227+ super (UpdateROIShapeCmd , self ).__init__ ()
228+ self .plot = plot
229+ self .roi_to_update = roi_to_update
230+ self .new_points = new_points
231+ self .old_points = self .roi_to_update .get_points ()
232+
233+ def redo (self ):
234+ """Update the ROI object's points to the new values."""
235+ self .roi_to_update .set_points (self .new_points )
236+ self .plot .replot ()
237+
238+ def undo (self ):
239+ """Revert the ROI object's points to the original values."""
240+ self .roi_to_update .set_points (self .old_points )
241+ self .plot .replot ()
242+
243+
192244class RoiBuddy (QMainWindow , Ui_ROI_Buddy ):
193245 """Instance of the ROI Buddy Qt interface."""
194- def __init__ (self ):
195- """
196- Initialize the application
197- """
198246
247+ def __init__ (self ):
248+ """Initialize the application."""
199249 # initialize the UI and parent class
200250 QMainWindow .__init__ (self )
201251 self .setupUi (self )
202252 self .setWindowTitle ('ROI Buddy' )
203253
254+ self .undoStack = QUndoStack (self )
255+
204256 # define actions for menu bar and ROI manager
205257 self .define_actions ()
206258
@@ -296,6 +348,7 @@ def arrow_press_event(self, event):
296348 self .plot .replot ()
297349
298350 def create_menu (self ):
351+ """Initialize the file menu."""
299352 self .file_menu = self .menuBar ().addMenu ("&File" )
300353
301354 qthelpers .add_actions (self .file_menu , [self .add_tseries_action ,
@@ -310,16 +363,20 @@ def create_menu(self):
310363 self .edit_tags_action ,
311364 self .update_roi_action ,
312365 None ,
366+ self .undo_action ,
367+ self .redo_action ,
368+ None ,
313369 self .merge_rois ,
314370 self .unmerge_rois ,
315371 None ,
316372 self .quit_action ])
317373
318374 def create_status_bar (self ):
375+ """Initialize the status bar."""
319376 self .statusBar ().addWidget (QLabel ("" ), 1 )
320377
321378 def define_actions (self ):
322- # File menu actions
379+ """ File menu actions."""
323380 self .quit_action = qthelpers .create_action (
324381 self , "&Quit" , triggered = self .close , shortcut = "Ctrl+Q" ,
325382 tip = "Close ROI Buddy" )
@@ -336,7 +393,8 @@ def define_actions(self):
336393 self , "&Save current ROIs" ,
337394 triggered = lambda : self .save ([self .tSeries_list .currentItem ()]),
338395 shortcut = "Ctrl+S" ,
339- icon = QIcon (os .path .join (icon_filepath ,"document-save-5.png" )),
396+ icon = QIcon (os .path .join (icon_filepath ,
397+ "document-save-5.png" )),
340398 tip = "Save the current ROI set to file." )
341399
342400 self .save_all_action = qthelpers .create_action (
@@ -377,7 +435,6 @@ def define_actions(self):
377435 self .update_roi_action = qthelpers .create_action (
378436 self , "&Update" , triggered = self .update_roi , shortcut = "B" ,
379437 tip = 'Redraw the selected ROI with new ROI' )
380- # self.addAction(self.update_roi)
381438
382439 self .merge_rois = qthelpers .create_action (
383440 self , "&Merge" , triggered = self .merge_ROIs , shortcut = "M" ,
@@ -416,11 +473,22 @@ def define_actions(self):
416473 shortcut = 'Ctrl+A' )
417474 self .addAction (self .select_all_action )
418475
476+ self .undo_action = qthelpers .create_action (
477+ self , "&Undo Action" , triggered = self .undoStack .undo ,
478+ shortcut = "Z" , tip = "Undo deletion of an object." )
479+ self .addAction (self .undo_action )
480+
481+ self .redo_action = qthelpers .create_action (
482+ self , "&Redo Action" , triggered = self .undoStack .redo ,
483+ shortcut = "Y" , tip = "Redo deletion of an object." )
484+ self .addAction (self .redo_action )
485+
419486 self .debug_action = qthelpers .create_action (
420487 self , "&Debug" , triggered = debug_trace , shortcut = "F10" )
421488 self .addAction (self .debug_action )
422489
423490 def connectSignals (self ):
491+ """Connect events to their respective slots."""
424492
425493 # toggle the edit or align mdoe
426494 self .modeSelection .buttonClicked .connect (self .toggle_mode )
@@ -660,12 +728,10 @@ def delete(self):
660728 items = self .plot .get_selected_items ()
661729 # Don't delete the base image
662730 items = [item for item in items if not isinstance (item , ImageItem )]
663- self .plot .unselect_all ()
664- if self .mode == 'align' :
665- for item in items :
666- item .parent .roi_list .remove (item )
667- self .plot .del_items (items )
668- self .plot .replot ()
731+ cmd = DeleteCmd (self .plot ,
732+ self .mode ,
733+ items )
734+ self .undoStack .push (cmd )
669735
670736 def activate_freeform_tool (self ):
671737 if self .mode == 'edit' :
@@ -1110,8 +1176,10 @@ def edit_tags(self):
11101176
11111177 def update_roi (self ):
11121178 """
1113- Update the shape of an ROI with the shape of newly drawn ROI by
1114- grabbing the shape of the new ROI drawn from the active drawing tool
1179+ Update the shape of an ROI.
1180+
1181+ The ROI's shape is set to the the shape of newly drawn ROI by
1182+ the active drawing tool.
11151183 """
11161184 if self .mode is 'edit' :
11171185 new_points = self .viewer .active_tool .shape .get_points ()
@@ -1129,7 +1197,9 @@ def update_roi(self):
11291197 QMessageBox .Ok )
11301198 return
11311199 roi_to_update = roi_to_update [0 ]
1132- roi_to_update .set_points (new_points )
1200+ cmd = UpdateROIShapeCmd (self .plot , roi_to_update ,
1201+ new_points )
1202+ self .undoStack .push (cmd )
11331203 self .plot .del_item (self .viewer .active_tool .shape )
11341204 self .viewer .active_tool .reset ()
11351205 self .plot .unselect_all ()
0 commit comments