• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:UndoManagerを使用した文字列選択ペーストの動作を変更する
#navi(../)
RIGHT:Posted by [[aterai]] at 2012-10-15
*UndoManagerを使用した文字列選択ペーストの動作を変更する [#g8638a3f]
JTextFieldなどにUndoManagerを設定し、文字列を選択してペーストした後のUndoの動作を変更します。
---
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: https://java-swing-tips.blogspot.com/2014/06/merge-replaceeditremove-and.html
    lang: en
---
* 概要 [#summary]
`JTextField`などに`UndoManager`を設定し、文字列を選択してペーストした後の`Undo`の動作を変更します。

-&jnlp;
-&jar;
-&zip;
#download(https://lh5.googleusercontent.com/-GEc9R-QZvos/UKt2czK61tI/AAAAAAAABXk/vqH8TKxkqCM/s800/ReplaceUndoableEdit.png)

//#screenshot
#ref(https://lh4.googleusercontent.com/-7Otr-GEBDF8/UHuZ373ZxtI/AAAAAAAABUQ/ASmTdTbdv3o/s800/ReplaceUndoableEdit.png)
* サンプルコード [#sourcecode]
#code(link){{
class CustomUndoPlainDocument extends PlainDocument {
  private CompoundEdit compoundEdit;
  @Override protected void fireUndoableEditUpdate(UndoableEditEvent e) {
    if (compoundEdit == null) {
      super.fireUndoableEditUpdate(e);
    } else {
      compoundEdit.addEdit(e.getEdit());
    }
  }

**サンプルコード [#g8b58a74]
#code(link){{
Document doc = new PlainDocument() {
  @Override public void replace(
      int offset, int length, String text, AttributeSet attrs)
        throws BadLocationException {
     undoManager.undoableEditHappened(
         new UndoableEditEvent(
             this, new ReplaceUndoableEdit(offset, length, text)));
     replaceIgnoringUndo(offset, length, text, attrs);
   }
  private void replaceIgnoringUndo(
      int offset, int length, String text, AttributeSet attrs)
        throws BadLocationException {
    removeUndoableEditListener(undoManager);
    super.replace(offset, length, text, attrs);
    addUndoableEditListener(undoManager);
      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;
    }
  }
  class ReplaceUndoableEdit extends AbstractUndoableEdit {
    private final String oldValue;
    private final String newValue;
    private int offset;
    public ReplaceUndoableEdit(int offset, int length, String newValue) {
      String txt;
      try{
        txt = getText(offset, length);
      }catch(BadLocationException e) {
        txt = null;
}
}}

* 解説 [#explanation]
- 上: `Default`
-- `JTextComponent#setText(String)`メソッドや文字列を選択してペーストした場合、`Document#replace(...)`で実行される`Document#remove(...)`と`Document#insertString(...)`が別々に`UndoManager`に登録される仕様なので`2`回`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(...)`で追加
#code{{
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;
      }
      this.oldValue = txt;
      this.newValue = newValue;
      this.offset = offset;
    }
    @Override public void undo() throws CannotUndoException {
       try{
         replaceIgnoringUndo(offset, newValue.length(), oldValue, null);
       }catch(BadLocationException ex) {
         throw new CannotUndoException();
       }
     }
    @Override public void redo() throws CannotRedoException {
       try{
         replaceIgnoringUndo(offset, oldValue.length(), newValue, null);
       }catch(BadLocationException ex) {
         throw new CannotUndoException();
       }
     }
    @Override public boolean canUndo() {
      return true;
    }
    @Override public boolean canRedo() {
      return true;
    }
  };

  public DocumentFilter getDocumentFilter() {
    return undoFilter;
  }
};

  @Override public void undoableEditHappened(UndoableEditEvent e) {
    Optional.ofNullable(compoundEdit).orElse(this).addEdit(e.getEdit());
  }
}
}}

**解説 [#c90ce0a6]
- 上: デフォルト
-- JTextComponent#setText(String)や、文字列を選択してペーストした場合、Document#replace(...)で実行される Document#remove(...)とDocument#insertString(...)が別々にUndoManagerに登録される仕様?なので、二回Undoしないとペースト前の状態に戻らない
- 下
-- Document#replace(...)をオーバーライドし、直接UndoManager#undoableEditHappened(...)を使って取り消し可能な編集を登録
-- 実際の置換は、removeUndoableEditListener(...)でUndoManagerを一時的に削除した後に行う(直後にaddUndoableEditListener()で再登録)
-- 登録するUndoableEditでのUndo, Redo 時の置換もUndoManagerを一時的に削除して行う
-- メモ: このサンプルでは選択状態を復元していない

**参考リンク [#z0683651]
* 参考リンク [#reference]
- [https://community.oracle.com/thread/1509622 Undo two or more actions at once | Oracle Community]
- [http://www.java2s.com/Code/Java/Swing-JFC/Undomanager.htm Undo manager : Undo Redo « Swing JFC « Java]
- [http://tips4java.wordpress.com/2008/10/27/compound-undo-manager/ Compound Undo Manager ≪ Java Tips Weblog]
- [https://tips4java.wordpress.com/2008/10/27/compound-undo-manager/ Compound Undo Manager ≪ Java Tips Weblog]
- [http://java-sl.com/tip_merge_undo_edits.html Merging UndoableEdits in one to be undone all together in JEditorPane.]
- [http://stackoverflow.com/questions/12844520/undo-in-jtextfield-and-settext java - Undo in JTextField and setText - Stack Overflow]
- [http://www.ne.jp/asahi/hishidama/home/tech/java/swing/UndoManager.html Java Swing「UndoManager」メモ(Hishidama's Swing-UndoManager Memo)]
- [https://stackoverflow.com/questions/24433089/jtextarea-settext-undomanager java - JTextArea setText() & UndoManager - Stack Overflow]
- [https://stackoverflow.com/questions/12844520/undo-in-jtextfield-and-settext java - Undo in JTextField and setText - Stack Overflow]
- [https://www.ne.jp/asahi/hishidama/home/tech/java/swing/UndoManager.html Java Swing「UndoManager」メモ(Hishidama's Swing-UndoManager Memo)]
- [http://kyopc.at.webry.info/201007/article_1.html Java Swingで複数のJTextFieldに対してUndo、Redoを行う(その2)-解決編 kyoはパソコンMaster or Slave?/ウェブリブログ]
- [http://bakagaiku.hsbt.org/entry/20100316 バカが征く on Rails 2010年03月16日()]
- [[UndoManagerでJTextFieldのUndo、Redoを行う>Swing/UndoManager]]

**コメント [#pda09258]
* コメント [#comment]
#comment
#comment