Source code: org/jext/search/FindReplace.java
1 /*
2 * 03/01/2002 - 19:53:59
3 *
4 * FindReplace.java - The Jext's find dialog
5 * Copyright (C) 1998-2001 Romain Guy
6 * romain.guy@jext.org
7 * www.jext.org
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24 package org.jext.search;
25
26 import gnu.regexp.*;
27
28 import java.awt.*;
29 import java.awt.event.*;
30
31 import javax.swing.*;
32
33 import org.jext.*;
34 import org.jext.gui.*;
35
36 /**
37 * The <code>FindReplace</code> class is a component which displays
38 * a dialog for either finding either replacing text. It provides
39 * two combo lists, which holds latest patterns, and many buttons
40 * or check boxes for options.
41 * @author Romain Guy
42 */
43
44 public class FindReplace extends JDialog implements ActionListener
45 {
46 // Constant declarations
47 /** Defines a search only dialog */
48 public static final int SEARCH = 1;
49 /** Defines a search and replace dialog */
50 public static final int REPLACE = 2;
51
52 // Private declarations
53 private int type;
54 private JextFrame parent;
55 private JComboBox fieldSearch;
56 private JComboBox fieldReplace;
57 private JTextField fieldSearchEditor, fieldReplaceEditor, script;
58 private JextHighlightButton btnFind, btnReplace, btnReplaceAll, btnCancel;
59 private JextCheckBox checkIgnoreCase, saveStates, useRegexp, allFiles, scripted;
60
61 // This method is used to easily build the GridBagLayout
62
63 private void buildConstraints(GridBagConstraints agbc, int agx, int agy, int agw, int agh,
64 int awx, int awy)
65 {
66 agbc.gridx = agx;
67 agbc.gridy = agy;
68 agbc.gridwidth = agw;
69 agbc.gridheight = agh;
70 agbc.weightx = awx;
71 agbc.weighty = awy;
72 agbc.insets = new Insets(2, 2, 2, 2);
73 }
74
75 /**
76 * Constructs a new find dialog according to the specified
77 * type of dialog requested. The dialog can be either a
78 * FIND dialog, either a REPLACE dialog. In both cases, components
79 * displayed remain the sames, but the ones specific to replace
80 * feature are grayed out.
81 * @param parent The window holder
82 * @param type The type of the dialog: <code>FindReplace.FIND</code>
83 * or <code>FindReplace.REPLACE</code>
84 * @param modal Displays dialog as a modal window if true
85 */
86
87 public FindReplace(JextFrame parent, int type, boolean modal)
88 {
89 super(parent, type == REPLACE ? Jext.getProperty("replace.title") :
90 Jext.getProperty("find.title"), modal);
91 this.parent = parent;
92 this.type = type;
93
94 fieldSearch = new JComboBox();
95 fieldSearch.setRenderer(new ModifiedCellRenderer());
96 fieldSearch.setEditable(true);
97 fieldReplace = new JComboBox();
98 fieldReplace.setRenderer(new ModifiedCellRenderer());
99 fieldReplace.setEditable(true);
100 KeyHandler handler = new KeyHandler();
101 fieldSearchEditor = (JTextField) fieldSearch.getEditor().getEditorComponent();
102 fieldSearchEditor.addKeyListener(handler);
103 fieldReplaceEditor = (JTextField) fieldReplace.getEditor().getEditorComponent();
104 fieldReplaceEditor.addKeyListener(handler);
105
106 GridBagLayout gridbag = new GridBagLayout();
107 GridBagConstraints constraints = new GridBagConstraints();
108 getContentPane().setLayout(gridbag);
109 ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
110
111 JLabel findLabel = new JLabel(Jext.getProperty("find.label"));
112 buildConstraints(constraints, 0, 0, 1, 1, 25, 50);
113 constraints.anchor = GridBagConstraints.WEST;
114 gridbag.setConstraints(findLabel, constraints);
115 getContentPane().add(findLabel);
116
117 buildConstraints(constraints, 1, 0, 1, 1, 25, 50);
118 constraints.fill = GridBagConstraints.HORIZONTAL;
119 constraints.anchor = GridBagConstraints.CENTER;
120 gridbag.setConstraints(fieldSearch, constraints);
121 getContentPane().add(fieldSearch);
122
123 btnFind = new JextHighlightButton(Jext.getProperty("find.button"));
124 btnFind.setToolTipText(Jext.getProperty("find.tip"));
125 btnFind.setMnemonic(Jext.getProperty("find.mnemonic").charAt(0));
126 btnFind.addActionListener(this);
127 buildConstraints(constraints, 2, 0, 1, 1, 25, 50);
128 constraints.anchor = GridBagConstraints.CENTER;
129 gridbag.setConstraints(btnFind, constraints);
130 getContentPane().add(btnFind);
131 getRootPane().setDefaultButton(btnFind);
132
133 btnCancel = new JextHighlightButton(Jext.getProperty("general.cancel.button"));
134 btnCancel.setMnemonic(Jext.getProperty("general.cancel.mnemonic").charAt(0));
135 btnCancel.addActionListener(this);
136 buildConstraints(constraints, 3, 0, 1, 1, 25, 50);
137 constraints.anchor = GridBagConstraints.CENTER;
138 gridbag.setConstraints(btnCancel, constraints);
139 getContentPane().add(btnCancel);
140
141 JLabel replaceLabel = new JLabel(Jext.getProperty("replace.label"));
142 buildConstraints(constraints, 0, 1, 1, 1, 25, 50);
143 constraints.anchor = GridBagConstraints.WEST;
144 gridbag.setConstraints(replaceLabel, constraints);
145 getContentPane().add(replaceLabel);
146
147 // patch added by gandalf march 25 2003
148 if (type != REPLACE)
149 replaceLabel.setEnabled(false);
150 // patch added by gandalf march 25 2003
151
152 buildConstraints(constraints, 1, 1, 1, 1, 25, 50);
153 constraints.fill = GridBagConstraints.HORIZONTAL;
154 constraints.anchor = GridBagConstraints.CENTER;
155 gridbag.setConstraints(fieldReplace, constraints);
156 getContentPane().add(fieldReplace);
157
158 if (type != REPLACE)
159 fieldReplace.setEnabled(false);
160 btnReplace = new JextHighlightButton(Jext.getProperty("replace.button"));
161 btnReplace.setToolTipText(Jext.getProperty("replace.tip"));
162 btnReplace.setMnemonic(Jext.getProperty("replace.mnemonic").charAt(0));
163 if (type != REPLACE)
164 btnReplace.setEnabled(false);
165 btnReplace.addActionListener(this);
166 buildConstraints(constraints, 2, 1, 1, 1, 25, 50);
167 constraints.anchor = GridBagConstraints.CENTER;
168 gridbag.setConstraints(btnReplace, constraints);
169 getContentPane().add(btnReplace);
170
171 btnReplaceAll = new JextHighlightButton(Jext.getProperty("replace.all.button"));
172 btnReplaceAll.setToolTipText(Jext.getProperty("replace.all.tip"));
173 btnReplaceAll.setMnemonic(Jext.getProperty("replace.all.mnemonic").charAt(0));
174 if (type != REPLACE)
175 btnReplaceAll.setEnabled(false);
176 btnReplaceAll.addActionListener(this);
177 buildConstraints(constraints, 3, 1, 1, 1, 25, 50);
178 constraints.anchor = GridBagConstraints.CENTER;
179 gridbag.setConstraints(btnReplaceAll, constraints);
180 getContentPane().add(btnReplaceAll);
181
182 scripted = new JextCheckBox(Jext.getProperty("replace.script"), Search.getPythonScript());
183 if (type != REPLACE)
184 scripted.setEnabled(false);
185 else
186 {
187 fieldReplace.setEnabled(!scripted.isSelected());
188 scripted.addActionListener(this);
189 }
190 buildConstraints(constraints, 0, 2, 1, 1, 50, 50);
191 constraints.anchor = GridBagConstraints.WEST;
192 gridbag.setConstraints(scripted, constraints);
193 getContentPane().add(scripted);
194
195 script = new JTextField();
196 if (type != REPLACE)
197 script.setEnabled(false);
198 else
199 script.setEnabled(scripted.isSelected());
200 script.setText(Search.getPythonScriptString());
201 buildConstraints(constraints, 1, 2, 1, 1, 50, 50);
202 constraints.anchor = GridBagConstraints.CENTER;
203 gridbag.setConstraints(script, constraints);
204 getContentPane().add(script);
205
206 checkIgnoreCase = new JextCheckBox(Jext.getProperty("find.ignorecase.label"), Search.getIgnoreCase());
207 buildConstraints(constraints, 0, 3, 1, 1, 25, 50);
208 constraints.anchor = GridBagConstraints.WEST;
209 gridbag.setConstraints(checkIgnoreCase, constraints);
210 getContentPane().add(checkIgnoreCase);
211
212 JPanel cPane = new JPanel();
213 saveStates = new JextCheckBox(Jext.getProperty("find.savevalues.label"),
214 Jext.getBooleanProperty("savestates"));
215 allFiles = new JextCheckBox(Jext.getProperty("find.allFiles.label"),
216 Jext.getBooleanProperty("allfiles"));
217 cPane.add(saveStates);
218 cPane.add(allFiles);
219
220 buildConstraints(constraints, 1, 3, 1, 1, 25, 50);
221 constraints.anchor = GridBagConstraints.WEST;
222 gridbag.setConstraints(cPane, constraints);
223 getContentPane().add(cPane);
224
225 useRegexp = new JextCheckBox(Jext.getProperty("find.useregexp.label"), Search.getRegexp());
226 buildConstraints(constraints, 2, 3, 2, 1, 50, 50);
227 constraints.anchor = GridBagConstraints.WEST;
228 gridbag.setConstraints(useRegexp, constraints);
229 getContentPane().add(useRegexp);
230
231 load();
232
233 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
234 addKeyListener(new AbstractDisposer(this));
235 addWindowListener(new WindowAdapter()
236 {
237 public void windowClosing(WindowEvent e)
238 {
239 exit();
240 }
241 });
242
243 FontMetrics fm = getFontMetrics(getFont());
244 fieldSearch.setPreferredSize(new Dimension(18 * fm.charWidth('m'),
245 (int) fieldSearch.getPreferredSize().height));
246 fieldReplace.setPreferredSize(new Dimension(18 * fm.charWidth('m'),
247 (int) fieldReplace.getPreferredSize().height));
248
249 pack();
250 setResizable(false);
251 Utilities.centerComponentChild(parent, this);
252
253 //patch by MJB 8/1/2002
254 btnFind.addKeyListener(handler);
255 btnReplace.addKeyListener(handler);
256 btnReplaceAll.addKeyListener(handler);
257 btnCancel.addKeyListener(handler);
258 checkIgnoreCase.addKeyListener(handler);
259 saveStates.addKeyListener(handler);
260 useRegexp.addKeyListener(handler);
261 allFiles.addKeyListener(handler);
262 scripted.addKeyListener(handler);
263 script.addKeyListener(handler);
264 //end MJB patch
265
266 show();
267 }
268
269 // load the search and replace histories from user
270 // properties. It also selects latest pattern from
271 // the list.
272
273 private void load()
274 {
275 String s;
276 for (int i = 0; i < 25; i++)
277 {
278 s = Jext.getProperty("search.history." + i);
279 if (s != null)
280 fieldSearch.addItem(s);
281 else
282 break;
283 }
284
285 JextTextArea textArea = parent.getTextArea();
286 if (!Jext.getBooleanProperty("use.selection"))
287 {
288 s = Search.getFindPattern();
289 if (s != null)
290 {
291 addSearchHistory(s);
292 fieldSearch.setSelectedItem(s);
293 }
294 } else if ((s = textArea.getSelectedText()) != null) {
295
296 char c = '\0';
297 StringBuffer buf = new StringBuffer(s.length());
298 out: for (int i = 0; i < s.length(); i++)
299 {
300 switch (c = s.charAt(i))
301 {
302 case '\n':
303 break out;
304 default:
305 buf.append(c);
306 }
307 }
308
309 s = buf.toString();
310 addSearchHistory(s);
311 fieldSearch.setSelectedItem(s);
312 }
313
314 if (type == REPLACE)
315 {
316 for (int i = 0; i < 25; i++)
317 {
318 s = Jext.getProperty("replace.history." + i);
319 if (s != null)
320 fieldReplace.addItem(s);
321 else
322 break;
323 }
324
325 s = Search.getReplacePattern();
326 if (s != null)
327 {
328 addReplaceHistory(s);
329 fieldReplace.setSelectedItem(s);
330 }
331 }
332
333 // selects contents
334 fieldSearchEditor.selectAll();
335 }
336
337 // exits the dialog after having saved the search and
338 // replace histories.
339
340 private void exit()
341 {
342 if (saveStates.isSelected())
343 {
344 for (int i = 0; i < fieldSearch.getItemCount(); i++)
345 Jext.setProperty("search.history." + i, (String) fieldSearch.getItemAt(i));
346 for (int i = fieldSearch.getItemCount(); i < 25; i++)
347 Jext.unsetProperty("search.history." + i);
348
349 if (type == REPLACE)
350 {
351 for (int i = 0; i < fieldReplace.getItemCount(); i++)
352 Jext.setProperty("replace.history." + i, (String) fieldReplace.getItemAt(i));
353 for (int i = fieldReplace.getItemCount(); i < 25; i++)
354 Jext.unsetProperty("replace.history." + i);
355 }
356 }
357
358 Jext.setProperty("savestates", (saveStates.isSelected() ? "on" : "off"));
359 Jext.setProperty("allfiles", (allFiles.isSelected() ? "on" : "off"));
360
361 // patch added by gandalf march 25 2003
362 Search.setIgnoreCase(checkIgnoreCase.isSelected() ? true : false);
363 Search.setRegexp(useRegexp.isSelected() ? true : false);
364 // patch added by gandalf march 25 2003
365
366 dispose();
367 }
368
369 // adds current search pattern in the search history list
370
371 private void addSearchHistory()
372 {
373 addSearchHistory(fieldSearchEditor.getText());
374 }
375
376 // adds a pattern in the search history list
377 // the pattern to be added is specified by the param c
378
379 private void addSearchHistory(String c)
380 {
381 if (c == null)
382 return;
383
384 for (int i = 0; i < fieldSearch.getItemCount(); i++)
385 {
386 if (((String) fieldSearch.getItemAt(i)).equals(c))
387 return;
388 }
389
390 fieldSearch.insertItemAt(c, 0);
391 if (fieldSearch.getItemCount() > 25)
392 {
393 //for (int i = 24; i < fieldSearch.getItemCount(); i++)
394
395 // patch added by gandalf march 25 2003
396 for (int i = 25; i < fieldSearch.getItemCount();)
397 // patch added by gandalf march 25 2003
398 fieldSearch.removeItemAt(i);
399 }
400
401 //Search.setFindPattern(fieldSearchEditor.getText());
402 fieldSearchEditor.setText((String) fieldSearch.getItemAt(0));
403 }
404
405 // adds current replace pattern in the replace history list
406
407 private void addReplaceHistory()
408 {
409 addReplaceHistory(fieldReplaceEditor.getText());
410 }
411
412 // adds a pattern in the replace history list
413 // the pattern to be added is given by the param c
414
415 private void addReplaceHistory(String c)
416 {
417 if (c == null)
418 return;
419
420 for (int i = 0; i < fieldReplace.getItemCount(); i++)
421 {
422 if (((String) fieldReplace.getItemAt(i)).equals(c))
423 return;
424 }
425
426 fieldReplace.insertItemAt(c, 0);
427 if (fieldReplace.getItemCount() > 25)
428 {
429 //for (int i = 24; i < fieldReplace.getItemCount(); i++)
430
431 // patch added by gandalf march 25 2003
432 for (int i = 25; i < fieldReplace.getItemCount();)
433 // patch added by gandalf march 25 2003
434 fieldReplace.removeItemAt(i);
435 }
436
437 //Search.setReplacePattern(fieldReplaceEditor.getText());
438 fieldReplaceEditor.setText((String) fieldReplace.getItemAt(0));
439 }
440
441 // Catch the action performed and then look for its source
442 // According to the source object we call appropriate methods
443
444 public void actionPerformed(ActionEvent evt)
445 {
446 Object source = evt.getSource();
447 if (source == btnCancel)
448 exit();
449 else if (source == btnFind)
450 doFind();
451 else if (source == btnReplace)
452 doReplace();
453 else if (source == btnReplaceAll)
454 doReplaceAll();
455 else if (source == scripted)
456 {
457 script.setEnabled(scripted.isSelected());
458 fieldReplace.setEnabled(!scripted.isSelected());
459 }
460 }
461
462 private void setSettings()
463 {
464 Search.setFindPattern(fieldSearchEditor.getText());
465 Search.setIgnoreCase(checkIgnoreCase.isSelected());
466 Search.setRegexp(useRegexp.isSelected());
467 if (type == REPLACE)
468 {
469 Search.setReplacePattern(fieldReplaceEditor.getText());
470 Search.setPythonScript(scripted.isSelected());
471 Search.setPythonScriptString(script.getText());
472 }
473 }
474
475 // replace all the occurences of search pattern by
476 // the replace one. If 'All Files' is checked, this is
477 // done in all the opened file in the component 'parent'
478
479 private void doReplaceAll()
480 {
481 Utilities.setCursorOnWait(this, true);
482 addReplaceHistory();
483 addSearchHistory();
484
485 try
486 {
487
488 if (allFiles.isSelected())
489 {
490 parent.setBatchMode(true);
491
492 JextTextArea textArea;
493 JextTextArea[] areas = parent.getTextAreas();
494
495 for (int i = 0; i < areas.length; i ++)
496 {
497 textArea = areas[i];
498 setSettings();
499 Search.replaceAll(textArea, 0, textArea.getLength());
500 }
501
502 parent.setBatchMode(false);
503 } else {
504 JextTextArea textArea = parent.getTextArea();
505 setSettings();
506 if (Search.replaceAll(textArea, 0, textArea.getLength()) == 0)
507 {
508 Utilities.beep();
509 }
510 }
511
512 } catch (Exception e) {
513 // nothing
514 } finally {
515 Utilities.setCursorOnWait(this, false);
516 }
517 }
518
519 // replaces specified search pattern by the replace one.
520 // this is done only if a match is found.
521
522 private void doReplace()
523 {
524 Utilities.setCursorOnWait(this, true);
525 addReplaceHistory();
526 addSearchHistory();
527
528 try
529 {
530
531 JextTextArea textArea = parent.getTextArea();
532 setSettings();
533
534 if (!Search.replace(textArea))
535 {
536 Utilities.beep();
537 } else
538 find(textArea);
539
540 } catch (Exception e) {
541 // nothing
542 } finally {
543 Utilities.setCursorOnWait(this, false);
544 }
545 }
546
547 // finds the next occurence of current search pattern
548 // the search is done in current text area
549
550 private void doFind()
551 {
552 Utilities.setCursorOnWait(this, true);
553
554 addSearchHistory();
555 find(parent.getTextArea());
556
557 Utilities.setCursorOnWait(this, false);
558 }
559
560 // finds the next occurence of the search pattern in a
561 // a given text area. if match is not found, and if user
562 // don't ask to start over from beginning, then the method
563 // calls itself by specifying next opened text area.
564
565 private void find(JextTextArea textArea)
566 {
567 setSettings();
568
569 try
570 {
571 if (!Search.find(textArea, textArea.getCaretPosition()))
572 {
573 String[] args = { textArea.getName() };
574 int response = JOptionPane.showConfirmDialog(null,
575 Jext.getProperty("find.matchnotfound", args),
576 Jext.getProperty("find.title"),
577 (allFiles.isSelected() ?
578 JOptionPane.YES_NO_CANCEL_OPTION : JOptionPane.YES_NO_OPTION),
579 JOptionPane.QUESTION_MESSAGE);
580
581 switch (response)
582 {
583 case JOptionPane.YES_OPTION:
584 textArea.setCaretPosition(0);
585 find(textArea);
586 break;
587 case JOptionPane.NO_OPTION:
588 if (allFiles.isSelected())
589 {
590 JextTabbedPane pane = parent.getTabbedPane();
591 int index = pane.indexOfComponent(textArea);
592
593 Component c = null;
594 while (c == null && !(c instanceof JextTextArea))
595 {
596 index++;
597 if (index == pane.getTabCount())
598 index = 0;
599 c = pane.getComponentAt(index);
600 }
601
602 JextTextArea area = (JextTextArea) c;
603 if (area != textArea)
604 find(area);
605 }
606 break;
607 case JOptionPane.CANCEL_OPTION:
608 return;
609 }
610 }
611 } catch (Exception e) { }
612 }
613
614 class KeyHandler extends KeyAdapter
615 {
616 public void keyPressed(KeyEvent evt)
617 {
618 switch (evt.getKeyCode())
619 {
620 case KeyEvent.VK_ENTER:
621 if (evt.getSource() == fieldSearchEditor)
622 doFind();
623 else if (evt.getSource() == fieldReplaceEditor)
624 doReplace();
625 break;
626 case KeyEvent.VK_ESCAPE:
627 exit();
628 }
629 }
630 }
631
632 /***************************************************************************
633 Patch
634 -> Memory management improvements : it may help the garbage collector.
635 -> Author : Julien Ponge (julien@izforge.com)
636 -> Date : 23, May 2001
637 ***************************************************************************/
638 protected void finalize() throws Throwable
639 {
640 super.finalize();
641
642 parent = null;
643 fieldSearch = null;
644 fieldReplace = null;
645 fieldSearchEditor = null;
646 fieldReplaceEditor = null;
647 btnFind = null;
648 btnReplace = null;
649 btnReplaceAll = null;
650 btnCancel = null;
651 checkIgnoreCase = null;
652 saveStates = null;
653 useRegexp = null;
654 allFiles = null;
655 }
656 // End of patch
657 }
658
659 // End of FindReplace.java