001 /* 002 * LAPIS lightweight structured text processing system 003 * 004 * Copyright (C) 1998-2002 Carnegie Mellon University, 005 * Copyright (C) 2003 Massachusetts Institute of Technology. 006 * All rights reserved. 007 * 008 * This library is free software; you can redistribute it 009 * and/or modify it under the terms of the GNU General 010 * Public License as published by the Free Software 011 * Foundation, version 2. 012 * 013 * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/ 014 */ 015 016 package lapisx.swing; 017 018 import java.awt.event.*; 019 import javax.swing.text.*; 020 import javax.swing.event.*; 021 import javax.swing.undo.*; 022 023 /** 024 * This class coalesces multiple typed keystrokes into a single undoable event. 025 * To use it, just replace your undo event listener attachment call 026 * <tt>document.addUndoableEditListener (undoManager)</tt> with a call to 027 * <tt>CoalescingUndo.addUndoableEditListener (document, undoManager)</tt>. 028 */ 029 public abstract class CoalescingUndo { 030 031 /** 032 * Attaches an undo listener to a document in such a way that 033 * multiple typed keystrokes are coalesced into a single undoable edit. 034 * <b>Only one undo listener may be attached to a document with this call.</b> 035 * @param doc Document generating the edit events 036 * @param undo Listener receiving the edit events, usually an instance of UndoManager 037 */ 038 public static void setUndoableEditListener (Document doc, UndoableEditListener undo) { 039 if (!installed) 040 install (); 041 doc.addUndoableEditListener (undo); 042 doc.putProperty (UNDO_LISTENER, undo); 043 } 044 045 public static UndoableEditListener getUndoableEditListener (Document doc) { 046 Object undoObject = doc.getProperty (UNDO_LISTENER); 047 if (undoObject == null 048 || !(undoObject instanceof UndoableEditListener)) 049 return null; 050 return (UndoableEditListener) undoObject; 051 } 052 053 /* 054 * Replace the default key-typed action in the default keymap with our own 055 * KeyTypedAction. 056 */ 057 protected static boolean installed = false; 058 protected static void install () { 059 Keymap keymap = JTextComponent.getKeymap (JTextComponent.DEFAULT_KEYMAP); 060 keymap.setDefaultAction (new KeyTypedAction ()); 061 installed = true; 062 } 063 064 protected static final String UNDO_LISTENER = "lapisx.swing.CoalescingUndo.UndoableEditListener"; 065 066 /* 067 * Action when an unmapped key is pressed. If it's a character, we send a KeyTypedEdit edit 068 * to the undo listener which will absorb subsequent typed keystrokes. 069 */ 070 protected static class KeyTypedAction extends DefaultEditorKit.DefaultKeyTypedAction { 071 072 public void actionPerformed(ActionEvent e) { 073 KeyTypedEdit edit = coalesce (e); 074 super.actionPerformed (e); 075 if (edit != null) 076 edit.end (); 077 } 078 079 protected KeyTypedEdit coalesce (ActionEvent e) { 080 if (e == null) 081 return null; 082 083 JTextComponent target = getTextComponent(e); 084 if (target == null 085 || !target.isEditable () 086 || !target.isEnabled ()) 087 return null; 088 089 Document doc = target.getDocument (); 090 if (doc == null) 091 return null; 092 093 UndoableEditListener undo = getUndoableEditListener (doc); 094 if (undo == null) 095 return null; 096 097 int mod = e.getModifiers(); 098 if ((mod & ActionEvent.ALT_MASK) != 0 099 || (mod & ActionEvent.CTRL_MASK) != 0) 100 return null; 101 102 String content = e.getActionCommand(); 103 if (content == null 104 || content.length() == 0) 105 return null; 106 107 char c = content.charAt(0); 108 if (c < 0x20 109 || c == 0x7F) 110 return null; 111 112 // if we're doing pending delete, take care of that first 113 if (target.getSelectionStart () != target.getSelectionEnd ()) 114 target.replaceSelection (""); 115 116 KeyTypedEdit edit = new KeyTypedEdit (target); 117 UndoableEditEvent ue = new UndoableEditEvent (target, edit); 118 undo.undoableEditHappened (ue); 119 120 return edit; 121 } 122 } 123 124 /* 125 * Key typed undoable edit. Absorbs multiple typed keystrokes until a non-KeyTypedEdit 126 * edit occurs or until the caret moves. 127 */ 128 protected static class KeyTypedEdit extends CompoundEdit implements CaretListener { 129 JTextComponent editor; 130 boolean caretMoved = false; 131 132 public KeyTypedEdit (JTextComponent editor) { 133 this.editor = editor; 134 } 135 136 public boolean replaceEdit (UndoableEdit edit) { 137 if (edit instanceof KeyTypedEdit) { 138 KeyTypedEdit te = (KeyTypedEdit) edit; 139 if (!te.caretMoved) { 140 edits = te.edits; 141 editor.removeCaretListener (te); 142 return true; 143 } 144 } 145 146 return super.replaceEdit (edit); 147 } 148 149 public void end () { 150 editor.addCaretListener (this); 151 super.end (); 152 } 153 154 public void caretUpdate (CaretEvent evt) { 155 caretMoved = true; 156 editor.removeCaretListener (this); 157 } 158 } 159 } 160