概要

JTextFieldなどにUndoManagerを設定し、文字列を選択してペーストした後のUndoの動作を変更します。

サンプルコード

class CustomUndoPlainDocument extends PlainDocument {
  private CompoundEdit compoundEdit;
  @Override protected void fireUndoableEditUpdate(UndoableEditEvent e) {
    if (compoundEdit == null) {
      super.fireUndoableEditUpdate(e);
    } else {
      compoundEdit.addEdit(e.getEdit());
    }
  }

  @Override public void replace(
      int offset, int length, String text, AttributeSet attrs)
      throws BadLocationException {
    if (length == 0) { // insert
      System.out.println("insert");
      super.replace(offset, length, text, attrs);
    } else { // replace
      System.out.println("replace");
      compoundEdit = new CompoundEdit();
      super.fireUndoableEditUpdate(new UndoableEditEvent(this, compoundEdit));
      super.replace(offset, length, text, attrs);
      compoundEdit.end();
      compoundEdit = null;
    }
  }
}
View in GitHub: Java, Kotlin

解説

  • 上: Default
    • JTextComponent#setText(String)メソッドや文字列を選択してペーストした場合、Document#replace(...)で実行されるDocument#remove(...)Document#insertString(...)が別々にUndoManagerに登録される仕様なので2Undoしないとペースト前の状態まで戻らない
  • 中: Document#replace()+AbstractDocument#fireUndoableEditUpdate()
    • Document#replace(...)をオーバーライドし、直接UndoManager#undoableEditHappened(...)を使って取り消し可能な編集を登録 setText(...)での文字列の削除と追加をCompoundEditにまとめる
    • 実際の置換はremoveUndoableEditListener(...)UndoManagerを一時的に削除した後に行う(直後にaddUndoableEditListener()で再登録)
    • 登録するUndoableEditでのUndoRedo時の置換もUndoManagerを一時的に削除して行う
    • メモ: このサンプルでは選択状態を復元していない
  • 下: DocumentFilter#replace()+UndoableEditListener#undoableEditHappened()
    • DocumentFilter#replace(...)をオーバーライドし、文字列の置換で発生する削除と追加のUndoableEditを別途用意したCompoundEditにまとめてからUndoManager#addEdit(...)で追加
      class DocumentFilterUndoManager extends UndoManager {
        private CompoundEdit compoundEdit;
        private final transient DocumentFilter undoFilter = new DocumentFilter() {
          @Override public void replace(
              DocumentFilter.FilterBypass fb, int offset, int length,
              String text, AttributeSet attrs) throws BadLocationException {
            if (length == 0) {
              fb.insertString(offset, text, attrs);
            } else {
              compoundEdit = new CompoundEdit();
              fb.replace(offset, length, text, attrs);
              compoundEdit.end();
              addEdit(compoundEdit);
              compoundEdit = null;
            }
          }
        };
      
        public DocumentFilter getDocumentFilter() {
          return undoFilter;
        }
      
        @Override public void undoableEditHappened(UndoableEditEvent e) {
          Optional.ofNullable(compoundEdit).orElse(this).addEdit(e.getEdit());
        }
      }
      

参考リンク

コメント