001: /*
002: * IncrementalFindTextFieldHandler.java
003: *
004: * Copyright (C) 1998-2004 Peter Graves
005: * $Id: IncrementalFindTextFieldHandler.java,v 1.5 2004/07/15 19:20:11 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: import java.awt.event.KeyEvent;
025: import javax.swing.SwingUtilities;
026: import javax.swing.undo.CompoundEdit;
027:
028: public final class IncrementalFindTextFieldHandler extends
029: DefaultTextFieldHandler implements Constants {
030: private final Display display;
031: private final Buffer buffer;
032:
033: private Search search;
034: private CompoundEdit edit;
035: private boolean inHistory;
036: private KeyMap keyMap;
037:
038: // Initial state.
039: private final Position initialDot;
040: private final int initialDotCol;
041: private final Position initialMark;
042:
043: public IncrementalFindTextFieldHandler(Editor editor,
044: HistoryTextField textField) {
045: super (editor, textField);
046: display = editor.getDisplay();
047: buffer = editor.getBuffer();
048: search = new Search();
049: if (buffer.supportsUndo) {
050: edit = new CompoundEdit();
051: edit.addEdit(new UndoFold(editor));
052: edit.addEdit(new UndoMove(editor));
053: }
054: initializeKeyMap();
055: // Save initial state.
056: initialDot = editor.getDotCopy();
057: initialDotCol = display.getCaretCol() + display.getShift();
058: if (editor.getMark() != null)
059: initialMark = editor.getMark().copy();
060: else
061: initialMark = null;
062: }
063:
064: public void initializeKeyMap() {
065: keyMap = new KeyMap();
066: keyMap.addMappingsForCommand("incrementalFind", KeyMap
067: .getGlobalKeyMap());
068: keyMap.addMappingsForCommand("incrementalFind", buffer
069: .getKeyMapForMode());
070: keyMap.addMappingsForCommand("findNext", KeyMap
071: .getGlobalKeyMap());
072: keyMap.addMappingsForCommand("findNext", buffer
073: .getKeyMapForMode());
074: keyMap.addMappingsForCommand("findPrev", KeyMap
075: .getGlobalKeyMap());
076: keyMap.addMappingsForCommand("findPrev", buffer
077: .getKeyMapForMode());
078: }
079:
080: public void escape() {
081: restoreInitialState();
082: editor.ensureActive();
083: editor.setFocusToDisplay();
084: editor.updateLocation();
085: // Erase "not found" message (if any).
086: editor.status("");
087: }
088:
089: private void restoreInitialState() {
090: editor.updateDotLine();
091: editor.setDot(initialDot);
092: editor.updateDotLine();
093: editor.setMark(initialMark);
094: if (initialMark != null
095: && initialMark.getLine() != initialDot.getLine())
096: editor.setUpdateFlag(REPAINT);
097: display.setCaretCol(initialDotCol - display.getShift());
098: editor.setUpdateFlag(REFRAME);
099: editor.updateDisplay();
100: }
101:
102: public void keyPressed(KeyEvent e) {
103: final char keyChar = e.getKeyChar();
104: final int keyCode = e.getKeyCode();
105: // Mask off the bits we don't care about (Java 1.4).
106: final int modifiers = e.getModifiers() & 0x0f;
107: switch (keyCode) {
108: case KeyEvent.VK_ESCAPE:
109: escape();
110: e.consume();
111: return;
112: case KeyEvent.VK_BACK_SPACE:
113: backspace();
114: e.consume();
115: return;
116: case KeyEvent.VK_N:
117: if (modifiers == CTRL_MASK) {
118: e.consume();
119: retrieveHistory(1);
120: return;
121: }
122: break;
123: case KeyEvent.VK_P:
124: if (modifiers == CTRL_MASK) {
125: e.consume();
126: retrieveHistory(-1);
127: return;
128: }
129: break;
130: case KeyEvent.VK_W:
131: if (modifiers == CTRL_MASK) {
132: e.consume();
133: yankWord();
134: }
135: break;
136: case KeyEvent.VK_ENTER:
137: e.consume(); // Fall through...
138: case KeyEvent.VK_LEFT:
139: case KeyEvent.VK_KP_LEFT:
140: case KeyEvent.VK_RIGHT:
141: case KeyEvent.VK_KP_RIGHT:
142: case KeyEvent.VK_UP:
143: case KeyEvent.VK_KP_UP:
144: case KeyEvent.VK_DOWN:
145: case KeyEvent.VK_KP_DOWN:
146: case KeyEvent.VK_HOME:
147: case KeyEvent.VK_END:
148: if (modifiers == 0)
149: finish(e);
150: return;
151: default:
152: break;
153: }
154: KeyMapping mapping = keyMap.lookup(keyChar, keyCode, modifiers);
155: if (mapping != null) {
156: Object command = mapping.getCommand();
157: if (command == "incrementalFind" || command == "findNext") {
158: e.consume();
159: findNext();
160: return;
161: }
162: if (command == "findPrev") {
163: e.consume();
164: findPrev();
165: return;
166: }
167: }
168: }
169:
170: private void retrieveHistory(int direction) {
171: History history = textField.getHistory();
172: if (history == null)
173: return;
174: if (!inHistory)
175: history.reset();
176: String s = direction < 0 ? history.getPrevious() : history
177: .getNext();
178: if (s != null && s.length() != 0) {
179: inHistory = true;
180: search.setPattern(s);
181: textField.setText(s);
182: textField.setCaretPosition(s.length());
183: return;
184: }
185: }
186:
187: private void yankWord() {
188: inHistory = false;
189: int begin = editor.getDotOffset();
190: int end = begin + search.getPatternLength();
191: final Line dotLine = editor.getDotLine();
192: final int limit = dotLine.length();
193: final Mode mode = buffer.getMode();
194: // Grab non-word chars at end of search pattern.
195: while (end < limit
196: && !mode.isIdentifierPart(dotLine.charAt(end)))
197: ++end;
198: // Grab next word.
199: while (end < limit
200: && mode.isIdentifierPart(dotLine.charAt(end)))
201: ++end;
202: String s = dotLine.substring(begin, end);
203: // If the current pattern is all lower case, convert the new pattern
204: // to lower case so the search will continue to be case insensitive.
205: if (Utilities.isLowerCase(search.getPattern()))
206: s = s.toLowerCase();
207: if (!s.equals(search.getPattern())) {
208: search.setPattern(s);
209: editor.markFoundPattern(search);
210: editor.updateDisplay();
211: }
212: }
213:
214: private void finish(KeyEvent e) {
215: if (search.getPattern() != null
216: && search.getPatternLength() > 0) {
217: editor.setLastSearch(search);
218: History history = textField.getHistory();
219: if (history != null) {
220: history.append(search.getPattern());
221: history.save();
222: }
223: if (edit != null) {
224: edit.end();
225: buffer.addEdit(edit);
226: }
227: }
228: final int keyCode = e.getKeyCode();
229: if (keyCode == KeyEvent.VK_ENTER) {
230: String pattern = textField.getText();
231: if (pattern == null || pattern.length() == 0) {
232: editor.updateLocation();
233: FindDialog.find(editor);
234: editor.updateDisplay();
235: return;
236: }
237: } else {
238: editor.handleKeyEvent(e.getKeyChar(), keyCode, e
239: .getModifiers());
240: // We may need to do a horizontal reframe (home, end).
241: editor.updateDisplay();
242: }
243: editor.ensureActive();
244: editor.setFocusToDisplay();
245: editor.updateLocation();
246: }
247:
248: public void keyReleased(KeyEvent e) {
249: textField.setText(search.getPattern());
250: textField.setCaretPosition(search.getPatternLength());
251: }
252:
253: public void keyTyped(KeyEvent e) {
254: // Mask off the bits we don't care about (Java 1.4).
255: final int modifiers = e.getModifiers() & 0x0f;
256: if (modifiers == 0 || modifiers == SHIFT_MASK)
257: handleKeyEvent(e);
258: }
259:
260: private void handleKeyEvent(KeyEvent e) {
261: // Mask off bits we don't care about (Java 1.4).
262: int modifiers = e.getModifiers() & 0x0f;
263: if (modifiers != 0 && modifiers != KeyEvent.SHIFT_MASK)
264: return;
265: char c = e.getKeyChar();
266: if (c == KeyEvent.CHAR_UNDEFINED)
267: return;
268: if (c == 8) {
269: e.consume();
270: return;
271: }
272: inHistory = false;
273: search.appendCharToPattern(c);
274: // Convention is to ignore case unless pattern is mixed case or
275: // all caps.
276: search
277: .setIgnoreCase(Utilities.isLowerCase(search
278: .getPattern()));
279: Position pos = search.findString(buffer, editor.getDot(), true);
280: if (pos != null)
281: found(pos);
282: else
283: search.notFound(editor);
284: }
285:
286: private void backspace() {
287: String s = textField.getText();
288: if (s.length() > 0) {
289: s = s.substring(0, s.length() - 1);
290: textField.setText(s);
291: search.setPattern(s);
292: search.setIgnoreCase(Utilities.isLowerCase(s));
293: if (s.length() > 0) {
294: Position pos = search.findString(buffer, initialDot,
295: true);
296: if (pos != null) {
297: found(pos);
298: // Erase "not found" message (if any).
299: editor.status("");
300: } else
301: search.notFound(editor);
302: } else
303: restoreInitialState();
304: }
305: }
306:
307: private void findNext() {
308: if (search.getPatternLength() > 0) {
309: // Only advance dot if we're really searching for the same pattern
310: // again. We might be doing the first search on a pattern
311: // retrieved from history.
312: if (!inHistory) {
313: if (editor.getDotOffset() < editor.getDotLine()
314: .length())
315: editor.getDot().skip(1);
316: else if (editor.getDotLine().next() != null)
317: editor.setDot(editor.getDotLine().next(), 0);
318: else
319: // Wrap buffer.
320: editor.setDot(buffer.getFirstLine(), 0);
321: }
322: } else {
323: // Recall last pattern.
324: History history = textField.getHistory();
325: if (history != null) {
326: history.reset();
327: String s = history.getPrevious();
328: if (s == null)
329: s = "";
330: search.setPattern(s);
331: if (s.length() == 0)
332: return;
333: textField.setText(s);
334: textField.setCaretPosition(s.length());
335: }
336: }
337: inHistory = false;
338: // Convention is to ignore case unless pattern is mixed case or all
339: // caps.
340: search
341: .setIgnoreCase(Utilities.isLowerCase(search
342: .getPattern()));
343: Position pos = search.findString(buffer, editor.getDot(), true);
344: if (pos != null)
345: found(pos);
346: }
347:
348: private void findPrev() {
349: if (search.getPatternLength() > 0) {
350: Position start;
351: if (editor.getMark() != null)
352: start = new Region(editor).getBegin();
353: else
354: start = editor.getDotCopy();
355: boolean wrapped = false;
356: if (!start.prev()) {
357: start = buffer.getEnd();
358: wrapped = true;
359: }
360: Position pos = search.reverseFindString(buffer, start);
361: if (pos == null && !wrapped)
362: pos = search.reverseFindString(buffer, buffer.getEnd());
363: if (pos != null)
364: found(pos);
365: }
366: }
367:
368: private boolean dirty;
369:
370: private void found(Position pos) {
371: if (editor.getMark() != null)
372: if (editor.getMarkLine() != editor.getDotLine())
373: editor.setUpdateFlag(REPAINT);
374: editor.updateDotLine();
375: editor.getDot().moveTo(pos);
376: editor.updateDotLine();
377: editor.setUpdateFlag(REFRAME);
378: dirty = true;
379: SwingUtilities.invokeLater(foundRunnable);
380: }
381:
382: private final Runnable foundRunnable = new Runnable() {
383: public void run() {
384: if (dirty) {
385: editor.markFoundPattern(search);
386: editor.updateDisplay();
387: dirty = false;
388: }
389: }
390: };
391: }
|