# Simple implementation of an undo-redo mechanism for ruby-gtk2 # Action to take when user clicks on the UNDO button. # We pop the last element from our undo actions Array. This element has a # closure and arguments to call this closure; when executing the closure, # the undo action is performed; the undo closure return value is a closure to # perform the corresponding redo. We then store this closure in the redo # actions array, together with the undo closure (so that we can perform # undo-redo-undo). $undo.signal_connect('clicked') { todo = $undo_actions.pop redo_closure = todo[:closure].call(*todo[:params]) $redo_actions << { :redo => redo_closure, :undo => todo } $redo.sensitive = true if $undo_actions.empty? $undo.sensitive = false end } # Action to take when user clicks on the REDO button. # We pop the last element from our redo actions Array. It contains a closure # to perform the redo, and another one to perform the undo again. $redo.signal_connect('clicked') { redo_ = $redo_actions.pop redo_[:redo].call $undo_actions << redo_[:undo] $undo.sensitive = true if $redo_actions.empty? $redo.sensitive = false end } # Helper method to "save an undo". Typically called on certains callbacks # of certain widgets which want to provide the undo functionality. The # first parameter is a closure that performs the undo (and returns a closure # to perform the redo), and the following parameter(s) are arguments to # the closure. def save_undo(closure, *params) $undo_actions << { :closure => closure, :params => params } $undo.sensitive = true end # Example use of "save an undo". We will be saving for a TextView modification. # We choose to connect to focus-in-event for saving a "candidate for undo", and # to key-release-event to really save as long as we see a modification. We save # an undo by creating a closure which will set the old text back. candidate_undo_text = nil textview.signal_connect('focus-in-event') { |w, event| candidate_undo_text = textview.buffer.text } textview.signal_connect('key-release-event') { |w, event| if candidate_undo_text && candidate_undo_text != textview.buffer.text save_undo(Proc.new { |text| save_text = textview.buffer.text textview.buffer.text = text textview.grab_focus Proc.new { textview.buffer.text = save_text textview.grab_focus } }, candidate_undo_text) candidate_undo_text = nil end }