001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext;
015:
016: import java.awt.Dialog;
017: import java.awt.Dimension;
018: import java.awt.Rectangle;
019: import java.awt.Toolkit;
020: import java.awt.event.ActionEvent;
021: import java.awt.event.ActionListener;
022: import java.awt.event.ItemEvent;
023: import java.awt.event.ItemListener;
024: import java.awt.event.KeyEvent;
025: import java.awt.event.KeyListener;
026: import java.awt.event.WindowAdapter;
027: import java.awt.event.WindowEvent;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.Map;
033:
034: import javax.swing.JButton;
035: import javax.swing.JComboBox;
036: import javax.swing.JPanel;
037: import javax.swing.SwingUtilities;
038: import javax.swing.Timer;
039: import javax.swing.text.BadLocationException;
040: import javax.swing.text.JTextComponent;
041:
042: import org.netbeans.editor.DialogSupport;
043: import org.netbeans.editor.EditorState;
044: import org.netbeans.editor.FindSupport;
045: import org.netbeans.editor.GuardedException;
046: import org.netbeans.editor.LocaleSupport;
047: import org.netbeans.editor.SettingsNames;
048: import org.netbeans.editor.SettingsUtil;
049: import org.netbeans.editor.Utilities;
050: import org.netbeans.editor.WeakTimerListener;
051:
052: /**
053: * Support for displaying find and replace dialogs
054: *
055: * @author Miloslav Metelka
056: * @version 1.00
057: */
058:
059: public class FindDialogSupport extends WindowAdapter implements
060: ActionListener {
061:
062: private static final String HISTORY_KEY_BASE = "FindDialogSupport."; // NOI18N
063: /** The EditorSettings key storing the last location of the dialog. */
064: private static final String BOUNDS_FIND = HISTORY_KEY_BASE
065: + "bounds-find"; // NOI18N
066: private static final String BOUNDS_REPLACE = HISTORY_KEY_BASE
067: + "bounds-replace"; // NOI18N
068:
069: /**
070: * This lock is used to create a barrier between showing/hiding/changing the
071: * dialog and testing if the dialog is already shown. it is used to make
072: * test-and-change / test-and-display actions atomic. It covers the
073: * following four fields: findDialog, isReplaceDialog, findPanel,
074: * findButtons
075: */
076: private Object dialogLock = new Object();
077:
078: /** Whether the currently visible dialog is for replace */
079: private boolean isReplaceDialog;
080:
081: /** The buttons used in the visible dialog */
082: private JButton findButtons[];
083:
084: /** The FindPanel used inside the visible dialog */
085: private FindPanel findPanel;
086:
087: /** Currently visible dialog */
088: private Dialog findDialog;
089:
090: protected Timer incSearchTimer;
091:
092: private static final String MNEMONIC_SUFFIX = "-mnemonic"; // NOI18N
093: private static final String A11Y_PREFIX = "ACSD_"; // NOI18N
094:
095: public FindDialogSupport() {
096: int delay = SettingsUtil.getInteger(null,
097: SettingsNames.FIND_INC_SEARCH_DELAY, 200);
098: incSearchTimer = new Timer(delay, new WeakTimerListener(this ));
099: incSearchTimer.setRepeats(false);
100: }
101:
102: private JButton[] createFindButtons() {
103: JButton[] buttons = new JButton[] {
104: new JButton(LocaleSupport.getString("find-button-find")), // NOI18N
105: new JButton(LocaleSupport
106: .getString("find-button-replace")), // NOI18N
107: new JButton(LocaleSupport
108: .getString("find-button-replace-all")), // NOI18N
109: new JButton(LocaleSupport
110: .getString("find-button-cancel")) // NOI18N
111: };
112: /*
113: * buttons[0].setMnemonic( LocaleSupport.getChar( "find-button-find" +
114: * MNEMONIC_SUFFIX, 'F' ) ); // NOI18N
115: */
116: buttons[1].setMnemonic(LocaleSupport.getChar(
117: "find-button-replace" + MNEMONIC_SUFFIX, 'R')); // NOI18N
118:
119: buttons[2].setMnemonic(LocaleSupport.getChar(
120: "find-button-replace-all" + MNEMONIC_SUFFIX, 'A')); // NOI18N
121:
122: buttons[0].getAccessibleContext().setAccessibleDescription(
123: LocaleSupport.getString(A11Y_PREFIX
124: + "find-button-find")); // NOI18N
125: buttons[1].getAccessibleContext().setAccessibleDescription(
126: LocaleSupport.getString(A11Y_PREFIX
127: + "find-button-replace")); // NOI18N
128: buttons[2].getAccessibleContext().setAccessibleDescription(
129: LocaleSupport.getString(A11Y_PREFIX
130: + "find-button-replace-all")); // NOI18N
131: buttons[3].getAccessibleContext().setAccessibleDescription(
132: LocaleSupport.getString(A11Y_PREFIX
133: + "find-button-cancel")); // NOI18N
134:
135: return buttons;
136: }
137:
138: private void loadBounds(Dialog d, String key) {
139: d.pack();
140: // Position the dialog according to the history
141: Rectangle lastBounds = (Rectangle) EditorState.get(key);
142: if (lastBounds != null) {
143: d.setBounds(lastBounds);
144: } else { // no history, center it on the screen
145: Dimension dim = d.getPreferredSize();
146: Dimension screen = Toolkit.getDefaultToolkit()
147: .getScreenSize();
148: int x = Math.max(0, (screen.width - dim.width) / 2);
149: int y = Math.max(0, (screen.height - dim.height) / 2);
150: d.setLocation(x, y);
151: }
152: }
153:
154: private void saveBounds(Dialog d, String key) {
155: EditorState.put(key, d.getBounds());
156: }
157:
158: private Dialog createFindDialog(JPanel findPanel,
159: final JButton[] buttons, final ActionListener l) {
160: Dialog d = DialogSupport.createDialog(
161: isReplaceDialog ? LocaleSupport
162: .getString("replace-title") : LocaleSupport
163: .getString("find-title"), // NOI18N
164: findPanel, false, // non-modal
165: buttons, true, // sidebuttons,
166: 0, // defaultIndex = 0 => findButton
167: 3, // cancelIndex = 3 => cancelButton
168: l // listener
169: );
170:
171: return d;
172: }
173:
174: private void showFindDialogImpl(boolean isReplace) {
175: synchronized (dialogLock) {
176: if (findDialog != null) { // we have a dialog, change or raise
177: if (isReplaceDialog == isReplace) { // raise only
178: findDialog.toFront();
179: } else { // change (and raise ?)
180: saveBounds(findDialog,
181: isReplaceDialog ? BOUNDS_REPLACE
182: : BOUNDS_FIND);
183: isReplaceDialog = isReplace;
184: findButtons[1].setVisible(isReplace);
185: findButtons[2].setVisible(isReplace);
186: if (isReplace) {
187: findDialog.setTitle(LocaleSupport
188: .getString("replace-title")); // NOI18N
189: findPanel.updateReplace();
190: } else {
191: findDialog.setTitle(LocaleSupport
192: .getString("find-title")); // NOI18N
193: findPanel.updateFind();
194: }
195: loadBounds(findDialog, isReplace ? BOUNDS_REPLACE
196: : BOUNDS_FIND);
197: }
198: } else { // create and show new dialog of reqiured type.
199: isReplaceDialog = isReplace;
200: findButtons = createFindButtons();
201: findButtons[1].setVisible(isReplace);
202: findButtons[2].setVisible(isReplace);
203: findPanel = new FindPanel();
204: if (isReplace) {
205: findPanel.updateReplace();
206: } else {
207: findPanel.updateFind();
208: }
209: findDialog = createFindDialog(findPanel, findButtons,
210: this );
211: loadBounds(findDialog, isReplace ? BOUNDS_REPLACE
212: : BOUNDS_FIND);
213:
214: findDialog.addWindowListener(this );
215: }
216: } // end of synchronized section
217:
218: findDialog.setVisible(true);
219: findDialog.requestFocus();
220: findPanel.updateFocus();
221: }
222:
223: public void windowActivated(WindowEvent evt) {
224: incSearchTimer.start();
225: }
226:
227: public void windowDeactivated(WindowEvent evt) {
228: incSearchTimer.stop();
229: FindSupport.getFindSupport().incSearchReset();
230: }
231:
232: public void windowClosing(WindowEvent evt) {
233: hideDialog();
234: }
235:
236: public void windowClosed(WindowEvent evt) {
237: incSearchTimer.stop();
238: FindSupport.getFindSupport().incSearchReset();
239: Utilities.returnFocus();
240: }
241:
242: public void showFindDialog() {
243: showFindDialogImpl(false);
244: }
245:
246: public void showReplaceDialog() {
247: showFindDialogImpl(true);
248: }
249:
250: private void hideDialog() {
251: synchronized (dialogLock) {
252: if (findDialog != null) {
253: saveBounds(findDialog, isReplaceDialog ? BOUNDS_REPLACE
254: : BOUNDS_FIND);
255: findDialog.dispose();
256: findButtons = null; // let it gc()
257: findPanel = null;
258: findDialog = null;
259: }
260: }
261: }
262:
263: public void actionPerformed(ActionEvent evt) {
264: JButton[] fb = findButtons;
265: if (fb == null)
266: return;
267:
268: Object src = evt.getSource();
269: FindSupport fSup = FindSupport.getFindSupport();
270: if (src == fb[0]) { // Find button
271: findPanel.updateFindHistory();
272: findPanel.save();
273: if (fSup.find(null, false)) { // found
274: }
275: if (!isReplaceDialog) {
276: hideDialog();
277: }
278: } else if (src == fb[1]) { // Replace button
279: findPanel.updateReplaceHistory();
280: findPanel.save();
281:
282: try {
283: if (fSup.replace(null, false)) { // replaced
284: fSup.find(null, false);
285: }
286: } catch (GuardedException e) {
287: // replace in guarded block
288: } catch (BadLocationException e) {
289: e.printStackTrace();
290: }
291: } else if (src == fb[2]) { // Replace All button
292: findPanel.updateReplaceHistory();
293: findPanel.save();
294: fSup.replaceAll(null);
295: } else if (src == fb[3]) { // Cancel button
296: hideDialog();
297:
298: // fix for issue 13502
299: // canceling dialog must scroll back to the caret position in the
300: // document
301: // (in case the visible area of document has changed because of
302: // incremental search)
303: JTextComponent c = Utilities.getLastActiveComponent();
304: if (c != null)
305: c.getCaret().setDot(c.getCaret().getDot());
306:
307: } else if (src == incSearchTimer) {
308: fSup.incSearch(findPanel.getFindProps());
309: } else {
310: // fix for issue 13502
311: // canceling dialog must scroll back to the caret position in the
312: // document
313: // (in case the visible area of document has changed because of
314: // incremental search)
315: JTextComponent c = Utilities.getLastActiveComponent();
316: if (c != null)
317: c.getCaret().setDot(c.getCaret().getDot());
318: }
319: }
320:
321: /** Panel that holds the find logic */
322: public class FindPanel extends FindDialogPanel implements
323: ItemListener, KeyListener, ActionListener {
324:
325: Map findProps = Collections.synchronizedMap(new HashMap(20));
326:
327: Map objToProps = Collections.synchronizedMap(new HashMap(20));
328:
329: FindSupport findSupport = FindSupport.getFindSupport();
330:
331: static final long serialVersionUID = 917425125419841466L;
332:
333: FindPanel() {
334: objToProps.put(findWhat, SettingsNames.FIND_WHAT);
335: objToProps
336: .put(replaceWith, SettingsNames.FIND_REPLACE_WITH);
337: objToProps.put(highlightSearch,
338: SettingsNames.FIND_HIGHLIGHT_SEARCH);
339: objToProps.put(incSearch, SettingsNames.FIND_INC_SEARCH);
340: objToProps.put(matchCase, SettingsNames.FIND_MATCH_CASE);
341: objToProps.put(smartCase, SettingsNames.FIND_SMART_CASE);
342: objToProps.put(wholeWords, SettingsNames.FIND_WHOLE_WORDS);
343: objToProps.put(regExp, SettingsNames.FIND_REG_EXP);
344: objToProps.put(bwdSearch,
345: SettingsNames.FIND_BACKWARD_SEARCH);
346: objToProps.put(wrapSearch, SettingsNames.FIND_WRAP_SEARCH);
347:
348: regExp.setEnabled(false); // !!! remove when regexp search is fine
349: regExp.setVisible(false);
350:
351: load();
352: findWhat.getEditor().getEditorComponent().addKeyListener(
353: this );
354: findWhat.addActionListener(this );
355: replaceWith.getEditor().getEditorComponent()
356: .addKeyListener(this );
357: replaceWith.addActionListener(this );
358: highlightSearch.addItemListener(this );
359: incSearch.addItemListener(this );
360: matchCase.addItemListener(this );
361: smartCase.addItemListener(this );
362: wholeWords.addItemListener(this );
363: regExp.addItemListener(this );
364: bwdSearch.addItemListener(this );
365: wrapSearch.addItemListener(this );
366: }
367:
368: protected Map getFindProps() {
369: return findProps;
370: }
371:
372: void putProperty(Object component, Object value) {
373: String prop = (String) objToProps.get(component);
374: if (prop != null) {
375: findProps.put(prop, value);
376: incSearchTimer.restart();
377: // findSupport.incSearch(findProps);
378: }
379: }
380:
381: Object getProperty(Object component) {
382: String prop = (String) objToProps.get(component);
383: return (prop != null) ? findProps.get(prop) : null;
384: }
385:
386: boolean getBooleanProperty(Object component) {
387: Object prop = getProperty(component);
388: return (prop != null) ? ((Boolean) prop).booleanValue()
389: : false;
390: }
391:
392: private void changeVisibility(boolean v) {
393: replaceWith.setVisible(v);
394: replaceWithLabel.setVisible(v);
395: }
396:
397: protected void updateFocus() {
398: SwingUtilities.invokeLater(new Runnable() {
399: public void run() {
400: JTextComponent c = Utilities
401: .getLastActiveComponent();
402: if (c != null) {
403: String selText = c.getSelectedText();
404: if (selText != null) {
405: int n = selText.indexOf('\n');
406: if (n >= 0)
407: selText = selText.substring(0, n);
408: findPanel.updateFindWhat(selText.trim());
409: } else {
410: if (getProperty(findWhat) != null) {
411: findWhat.getEditor().setItem(
412: getProperty(findWhat));
413: }
414: }
415: }
416:
417: findWhat.getEditor().getEditorComponent()
418: .requestFocus();
419: findWhat.requestFocus();
420: findWhat.getEditor().selectAll();
421: }
422: });
423: }
424:
425: protected void updateFind() {
426: changeVisibility(false);
427: }
428:
429: protected void updateReplace() {
430: changeVisibility(true);
431: }
432:
433: private List getHistory(String identifier) {
434: List history = (List) EditorState.get(HISTORY_KEY_BASE
435: + identifier + "-history"); // NOI18N
436: if (history == null) {
437: history = new ArrayList();
438: }
439: return history;
440: }
441:
442: private void putHistory(String identifier, List history) {
443: EditorState.put(HISTORY_KEY_BASE + identifier + "-history",
444: history); // NOI18N
445: }
446:
447: private void updateHistory(JComboBox c, String identifier) {
448: Object item = c.getEditor().getItem();
449:
450: List history = getHistory(identifier);
451: if (item != null) {
452: int index = history.indexOf(item);
453: if (index >= 0) {
454: history.remove(index);
455: }
456: history.add(0, item);
457: }
458: putHistory(identifier, history);
459:
460: javax.swing.DefaultComboBoxModel m = new javax.swing.DefaultComboBoxModel(
461: history.toArray());
462: c.setModel(m);
463: }
464:
465: protected void updateFindHistory() {
466: updateHistory(findWhat, "find"); // NOI18N
467: }
468:
469: protected void updateReplaceHistory() {
470: updateHistory(replaceWith, "replace"); // NOI18N
471: }
472:
473: protected void updateFindWhat(String selectedText) {
474: findWhat.getEditor().setItem(selectedText);
475: }
476:
477: /** Load the current find properties from those in FindSupport */
478: void load() {
479: findProps.putAll(findSupport.getFindProperties());
480:
481: java.util.List history = getHistory("find"); // NOI18N
482: javax.swing.DefaultComboBoxModel m = new javax.swing.DefaultComboBoxModel(
483: history.toArray());
484: findWhat.setModel(m);
485:
486: history = getHistory("replace"); // NOI18N
487: m = new javax.swing.DefaultComboBoxModel(history.toArray());
488: replaceWith.setModel(m);
489:
490: findWhat.getEditor().setItem(getProperty(findWhat));
491: replaceWith.getEditor().setItem(getProperty(replaceWith));
492: highlightSearch
493: .setSelected(getBooleanProperty(highlightSearch));
494: incSearch.setSelected(getBooleanProperty(incSearch));
495: matchCase.setSelected(getBooleanProperty(matchCase));
496: smartCase.setSelected(getBooleanProperty(smartCase));
497: wholeWords.setSelected(getBooleanProperty(wholeWords));
498: regExp.setSelected(getBooleanProperty(regExp));
499: bwdSearch.setSelected(getBooleanProperty(bwdSearch));
500: wrapSearch.setSelected(getBooleanProperty(wrapSearch));
501: }
502:
503: /** Save the current find properties into those in FindSupport */
504: void save() {
505: findSupport.putFindProperties(findProps);
506: }
507:
508: void changeFindWhat() {
509: Object old = getProperty(findWhat);
510: Object cur = findWhat.getEditor().getItem();
511: if (old == null || !old.equals(cur)) {
512: putProperty(findWhat, cur);
513: }
514: }
515:
516: void changeReplaceWith() {
517: Object old = getProperty(replaceWith);
518: Object cur = replaceWith.getEditor().getItem();
519: if (old == null || !old.equals(cur)) {
520: putProperty(replaceWith, cur);
521: }
522: }
523:
524: private void postChangeCombos() {
525: SwingUtilities.invokeLater(new Runnable() {
526: public void run() {
527: changeFindWhat();
528: changeReplaceWith();
529: if (regExp.isSelected()) {
530:
531: }
532: }
533: });
534: }
535:
536: public void keyPressed(KeyEvent evt) {
537: postChangeCombos();
538: }
539:
540: public void keyReleased(KeyEvent evt) {
541: SwingUtilities.invokeLater(new Runnable() {
542: public void run() {
543: changeFindWhat();
544: changeReplaceWith();
545: }
546: });
547: }
548:
549: public void keyTyped(KeyEvent evt) {
550: if (evt.getKeyChar() == '\n') {
551: FindDialogSupport.this .actionPerformed(new ActionEvent(
552: findButtons[0], 0, null));
553: }
554: }
555:
556: public void itemStateChanged(ItemEvent evt) {
557: Boolean val = (evt.getStateChange() == ItemEvent.SELECTED) ? Boolean.TRUE
558: : Boolean.FALSE;
559: putProperty(evt.getSource(), val);
560: }
561:
562: public void actionPerformed(ActionEvent evt) {
563: postChangeCombos();
564: }
565:
566: }
567:
568: }
|