• category: swing folder: ReplaceUndoableEdit title: UndoManagerを使用した文字列選択ペーストの動作を変更する tags: [JTextField, UndoManager, PlainDocument, JTextComponent, CompoundEdit, DocumentFilter, UndoableEdit] author: aterai pubdate: 2012-10-15T14:31:20+09:00 description: JTextFieldなどにUndoManagerを設定し、文字列を選択してペーストした後のUndoの動作を変更します。 image: https://lh5.googleusercontent.com/-GEc9R-QZvos/UKt2czK61tI/AAAAAAAABXk/vqH8TKxkqCM/s800/ReplaceUndoableEdit.png hreflang:
       href: http://java-swing-tips.blogspot.com/2014/06/merge-replaceeditremove-and.html
       lang: en

概要

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に登録される仕様なので、二回Undoしないとペースト前の状態まで戻らない
  • 中: Document#replace()+AbstractDocument#fireUndoableEditUpdate()
    • Document#replace(...)をオーバーライドし、直接UndoManager#undoableEditHappened(...)を使って取り消し可能な編集を登録 setText(...)での文字列の削除と追加をCompoundEditにまとめる
    • 実際の置換は、removeUndoableEditListener(...)UndoManagerを一時的に削除した後に行う(直後にaddUndoableEditListener()で再登録)
    • 登録するUndoableEditでのUndo, Redo時の置換も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());
  }
}

参考リンク

コメント