アンドゥ・リドゥ

wxPython でツールを作るにあたり、最初からアンドゥ・リドゥのことを考慮したつくりにしておこうと、仕組みを用意しました。

# アンドゥ・リドゥの面倒を見るクラス
class CommandHistoryManager(object):
    def __init__(self):
        self.clear()
    # クリア
    def clear(self):
        self._history = []
        self._index = 0
        self._stored_index = 0
    # コマンドを登録して実行
    def do(self, cmd):
        cmd.do()
        # 現在のインデックスが履歴のサイズより小さい場合
        # =アンドゥした後に別の操作を行った場合
        if self._index < len(self._history):
            self._history = self._history[:self._index]
            self._stored_index = -1
        self._index += 1
        self._history.append(cmd)
    # アンドゥ
    def undo(self):
        if self.can_undo():
            self._index-=1
            self._history[self._index].undo()
    # リドゥ
    def redo(self):
        if self.can_redo():
            self._history[self._index].do()
            self._index+=1
    # アンドゥ操作名文字列取得
    def undo_name(self):
        if self.can_undo():
            return self._history[self._index-1].get_name()
    # リドゥ操作名文字列取得
    def redo_name(self):
        if self.can_redo():
            return self._history[self._index].get_name()
    # アンドゥ可能かどうか
    def can_undo(self):
        return self._index > 0
    # リドゥ可能かどうか
    def can_redo(self):
        return self._index < len(self._history)
    # 現在のインデックスを記録
    def store(self):
        self._stored_index = self._index
    # 編集されたかどうかを確認する
    def is_modified(self):
        return self._stored_index != self._index

do(実行),undo(戻す),gat_name(操作名文字列取得) の関数を持つオブジェクトをコマンドとして登録して、リストに登録しておいて実行します。get_name はメニューに表示したりするために利用。また、データを保存したときにstore() を呼び出しておくと、その後の操作が行われた後もコマンド履歴が保存した状態に戻ってきていれば編集されていないものとみなせるはず。これにより、is_modified() がTrue を返すかどうかでアプリケーション終了時や新規作成時などに保存の確認に分岐することができます。コマンドオブジェクトのdo, undo の中身には一切関知していません。

# テスト
if __name__=="__main__":
    cmdHisMgr = CommandHistoryManager()
    class A(object):
        def __init__(self, x):
            self.x = x
        def do(self):
            print "A do %d"%self.x
        def undo(self):
            print "A undo %d"%self.x
    class B(object):
        def __init__(self, x):
            self.x = x
        def do(self):
            print "B do %d"%self.x
        def undo(self):
            print "B undo %d"%self.x
    
    cmdHisMgr.do(A(0))
    cmdHisMgr.do(A(1))
    cmdHisMgr.do(B(0))
    cmdHisMgr.do(B(1))
    cmdHisMgr.do(B(2))
    cmdHisMgr.undo()
    cmdHisMgr.undo()
    cmdHisMgr.undo()
    cmdHisMgr.redo()
    cmdHisMgr.redo()
    cmdHisMgr.do(A(2))
    cmdHisMgr.do(A(3))
    cmdHisMgr.do(B(3))
    cmdHisMgr.undo()
    cmdHisMgr.undo()
    cmdHisMgr.redo()
    cmdHisMgr.redo()