001:/*
002: * Copyright (c) 2000-2001, Jacob Smullyan.
003: *
004: * This is part of SkunkDAV, a WebDAV client. See http://skunkdav.sourceforge.net/
005: * for the latest version.
006: *
007: * SkunkDAV is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License as published
009: * by the Free Software Foundation; either version 2, or (at your option)
010: * any later version.
011: *
012: * SkunkDAV 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 GNU
015: * General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with SkunkDAV; see the file COPYING. If not, write to the Free
019: * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
020: * 02111-1307, USA.
021:*/
022:
023:package org.skunk.dav.client.gui.editor.action;
024:
025:import cmp.Boyer.Boyer;
026:import java.awt.Toolkit;
027:import java.awt.event.ActionEvent;
028:import javax.swing.AbstractAction;
029:import javax.swing.JOptionPane;
030:import javax.swing.text.BadLocationException;
031:import javax.swing.text.Caret;
032:import javax.swing.text.Document;
033:import javax.swing.text.Segment;
034:import gnu.regexp.RE;
035:import gnu.regexp.REException;
036:import gnu.regexp.REMatch;
037:import org.skunk.assert.Assertion;
038:import org.skunk.dav.client.gui.Buffer;
039:import org.skunk.dav.client.gui.ExplorerApp;
040:import org.skunk.dav.client.gui.ResourceManager;
041:import org.skunk.dav.client.gui.StateMonitor;
042:import org.skunk.dav.client.gui.View;
043:import org.skunk.dav.client.gui.editor.IncrementalSearchPanel;
044:import org.skunk.dav.client.gui.editor.ISearchPanel;
045:import org.skunk.dav.client.gui.editor.SimpleTextEditor;
046:import org.skunk.trace.Debug;
047:
048:
049:public class SearchAction extends AbstractAction
050:{
051: public static final String SEARCH_IN_PROGRESS_PROPERTY="search_in_progress";
052:
053: private int matchLength;
054:
055: public void actionPerformed(ActionEvent ae)
056: {
057: Object source=ae.getSource();
058: Assertion.assert((source!=null), "source of ActionEvent is not null");
059:
060: View currentView=ExplorerApp.getAppContext().getCurrentView();
061: Assertion.assert((currentView!=null), "current view is not null");
062:
063: Buffer buffy=currentView.getFocussedBuffer();
064: Assertion.assert((buffy!=null), "focussed buffer is not null");
065: if (!(buffy instanceof SimpleTextEditor))
066: {
067: Debug.trace(this , Debug.DP3, "search invoked on inappropriate buffer");
068: return;
069: }
070: if (source instanceof ISearchPanel)
071: {
072: //obtain search parameters
073: ISearchPanel panel=(ISearchPanel)source;
074: String command=ae.getActionCommand();
075: SimpleTextEditor editor=(SimpleTextEditor)buffy;
076: actOnCommand(command, panel, editor);
077: }
078: else
079: {
080: Debug.trace(this , Debug.DP3, "creating search panel");
081: ISearchPanel isp=createSearchPanel();
082: isp.focus();
083: StateMonitor.setProperty(SEARCH_IN_PROGRESS_PROPERTY, new Boolean(true), null);
084: showSearchPanel(currentView, isp);
085: }
086: }
087:
088: protected void showSearchPanel(View currentView, ISearchPanel panel)
089: {
090: currentView.dockStatus(panel.getComponent());
091: }
092:
093: protected void hideSearchPanel(View currentView, ISearchPanel panel)
094: {
095: currentView.undockStatus(panel.getComponent());
096: }
097:
098: private void endSearch(ISearchPanel panel, SimpleTextEditor editor)
099: {
100: Debug.trace(this , Debug.DP3, "search panel should now be undocked");
101: hideSearchPanel(ExplorerApp.getAppContext().getCurrentView(), panel);
102: panel.removeActionListener(this );
103: panel=null;
104: editor.getComponent().requestFocus();
105: StateMonitor.setProperty(SEARCH_IN_PROGRESS_PROPERTY, new Boolean(false), null);
106: }
107:
108: protected void actOnCommand(String command, ISearchPanel panel, SimpleTextEditor editor)
109: {
110: //obtain search parameters
111: String searchString=panel.getSearchText();
112: if (ISearchPanel.END_SEARCH.equals(command)
113: || searchString==null
114: || searchString.length()==0)
115: {
116: endSearch(panel, editor);
117: return;
118: }
119: else Debug.trace(this , Debug.DP3, "searchString is "+searchString);
120: Document doc=editor.getDocument();
121: Caret caret=editor.getCaret();
122: int searchMode=panel.getSearchMode();
123: boolean reverse=panel.isReverse();
124: boolean replacingNext=ISearchPanel.REPLACE_NEXT.equals(command);
125: boolean replacingAll=ISearchPanel.REPLACE_ALL.equals(command);
126:
127: do
128: {
129: int dot=Math.min(caret.getDot(), caret.getMark());
130: int mark=Math.max(caret.getDot(), caret.getMark());
131: if (replacingNext || replacingAll)
132: {
133: String replaceText=panel.getReplaceText();
134: editor.replaceSelection(replaceText);
135: mark=dot+replaceText.length();
136: //editor.select(dot, mark);
137: command=(reverse)
138: ? ISearchPanel.SEARCH_FROM_CARET
139: : ISearchPanel.SEARCH_FROM_SELECTION;
140: }
141:
142: //obtain object of search operation
143:
144: int offset, length;
145: Segment segment=new Segment();
146:
147: Assertion.assert((command!=null), "action command for search is not null");
148: boolean isCaretSearch=command.equals(ISearchPanel.SEARCH_FROM_CARET);
149: boolean isSelectionSearch=command.equals(ISearchPanel.SEARCH_FROM_SELECTION);
150: boolean isStartOrEndSearch=command.equals(ISearchPanel.SEARCH_FROM_START_OR_END);
151: if (isCaretSearch || isSelectionSearch || isStartOrEndSearch)
152: {
153: if (!reverse)
154: {
155: Debug.trace(this , Debug.DP3, "calculating segment for forward search");
156: if (isCaretSearch)
157: {
158: offset=dot;
159: }
160: else if (isSelectionSearch)
161: {
162: offset=mark;
163: }
164: else offset=0;
165: Debug.trace(this , Debug.DP3, "offset is "+offset);
166: length=doc.getLength() - offset;
167: Debug.trace(this , Debug.DP3, "length of segment is "+length);
168: }
169: else
170: {
171: Debug.trace(this , Debug.DP3, "calculating segment for backward search");
172: Debug.trace(this , Debug.DP3, "dot: "+dot + "; mark: "+mark);
173: if (isCaretSearch)
174: {
175: Debug.trace(this , Debug.DP3, "caret search");
176: length=dot;
177: }
178: else if (isSelectionSearch)
179: {
180: Debug.trace(this , Debug.DP3, "selection search");
181: length=mark;
182: }
183: else
184: {
185: Debug.trace(this , Debug.DP3, "start or end search");
186: length=doc.getLength();
187: }
188: offset=0;
189: Debug.trace(this , Debug.DP3, "offset is "+offset);
190: Debug.trace(this , Debug.DP3, "length of segment is "+length);
191: }
192: int[] found;
193: if (length<=0)
194: found=new int[] {-1, 0};
195: else
196: {
197: try
198: {
199: doc.getText(offset, length, segment);
200: }
201: catch (BadLocationException badLuck)
202: {
203: Debug.trace(this , Debug.DP2, badLuck);
204: }
205: //now performs search according to those parameters and manipulates the buffer component accordingly
206: found=search(searchString, searchMode, reverse, segment);
207: }
208: if (found[0]>=0)
209: {
210: found[0]=found[0]+offset;
211:
212: handleMatch(editor, found);
213: }
214: else
215: {
216: handleNoMatch(editor);
217: replacingAll=false;
218: }
219: }
220: }
221: while (replacingAll);
222: }
223:
224: protected void handleMatch(SimpleTextEditor editor, int[] found)
225: {
226: editor.select(found[0], found[0]+found[1]);
227: }
228:
229: protected void handleNoMatch(SimpleTextEditor editor)
230: {
231: String message=ResourceManager.getMessage(ResourceManager.SEARCH_FAILED_MESSAGE);
232: String title=ResourceManager.getMessage(ResourceManager.SEARCH_FAILED_TITLE);
233: JOptionPane.showMessageDialog(editor.getComponent(),
234: message,
235: title,
236: JOptionPane.INFORMATION_MESSAGE);
237: }
238:
239: protected ISearchPanel createSearchPanel()
240: {
241: ISearchPanel isp=new IncrementalSearchPanel();
242: isp.addActionListener(new SearchAction());
243: return isp;
244: }
245:
246: private int[] search(String searchString, int searchMode, boolean reverse, Segment segment)
247: {
248: if (searchMode==ISearchPanel.REGEXP_MODE)
249: {
250: try
251: {
252: RE mrRegex=new RE(searchString);
253: if (reverse)
254: {
255: Debug.trace(this , Debug.DP3, "in reverse regex");
256: char[] text=getSegmentChars(segment);
257: Debug.trace(this , Debug.DP3, "\n\tsearchString: {0}\n\ttext: {1}",
258: new Object[] {searchString, new String(text)});
259:
260: REMatch[] allMatches=mrRegex.getAllMatches(text);
261: if (allMatches!=null && allMatches.length>0)
262: {
263: REMatch lastMatch=allMatches[allMatches.length-1];
264: int start=lastMatch.getStartIndex();
265: int length=lastMatch.getEndIndex()-start;
266: return new int[] { start, length };
267: }
268: }
269: REMatch match=mrRegex.getMatch(getSegmentChars(segment));
270: if (match!=null)
271: {
272: int start=match.getStartIndex();
273: int length=match.getEndIndex()-start;
274: return new int[] {start, length};
275: }
276: }
277: catch (REException rex)
278: {
279: Toolkit.getDefaultToolkit().beep();
280: }
281:
282: return new int[] {-1, 0};
283: }
284: else
285: {
286: int matchLength=searchString.length();
287: if (reverse)
288: return new int[] {reverseSearch(searchString, searchMode, getSegmentChars(segment)), matchLength};
289: else return new int[] {forwardSearch(searchString, searchMode, getSegmentChars(segment)), matchLength};
290: }
291: }
292:
293: private char[] getSegmentChars(Segment segment)
294: {
295: char[] retArray=new char[segment.count];
296: System.arraycopy(segment.array, segment.offset, retArray, 0, segment.count);
297: return retArray;
298: }
299:
300: private int boyerMoore(String searchString, char[] text)
301: {
302: int i= Boyer.indexOf(text, searchString);
303: Debug.trace(this , Debug.DP3, "searchString {0} found at {1}",
304: new Object[] {searchString, new Integer(i)});
305: return i;
306: }
307:
308: private int forwardSearch(String searchString, int searchMode, char[] text)
309: {
310: switch (searchMode)
311: {
312: case ISearchPanel.LITERAL_MODE:
313: return boyerMoore(searchString, text);
314:
315: case ISearchPanel.CASE_MODE:
316: return boyerMoore(searchString.toLowerCase(), toLowerCase(text));
317:
318: case ISearchPanel.REGEXP_MODE:
319: //fall through
320:
321: default:
322: Assertion.assert(false, "this is not reached");
323: return -1;
324: }
325: }
326:
327: private char[] toLowerCase(char[] text)
328: {
329: char[] lowered=new char[text.length];
330: for (int i=0;i<text.length;i++)
331: lowered[i]=Character.toLowerCase(text[i]);
332: return lowered;
333: }
334:
335: private char[] reverse(char[] text)
336: {
337: int len=text.length;
338: char[] clonedText=new char[len];
339: for (int i=0;i<len;i++)
340: clonedText[i]=text[len-1-i];
341: return clonedText;
342: }
343:
344: private String reverse(String s)
345: {
346: StringBuffer sb=new StringBuffer(s);
347: sb.reverse();
348: return sb.toString();
349: }
350:
351: private int reverseSearch(String searchString, int searchMode, char[] text)
352: {
353: Assertion.assert((searchMode!=ISearchPanel.REGEXP_MODE), "not in regexp mode");
354: int reverseFind=forwardSearch(reverse(searchString), searchMode, reverse(text));
355: if (reverseFind>=0)
356: reverseFind=text.length-reverseFind-searchString.length();
357: return reverseFind;
358:
359: }
360:}
361:/* $Log: SearchAction.java,v $
362:/* Revision 1.8 2001/07/22 19:11:07 smulloni
363:/* replaced the ORO regexp package with gnu-regexp
364:/*
365:/* Revision 1.7 2000/12/19 22:36:06 smulloni
366:/* adjustments to preamble.
367:/*
368:/* Revision 1.6 2000/12/14 23:09:17 smulloni
369:/* fixes to search and replace; partial fix to adding custom properties bug.
370:/*
371:/* Revision 1.5 2000/12/14 06:36:26 smulloni
372:/* changes to search and replace in text editor.
373:/*
374:/* Revision 1.4 2000/12/08 22:25:06 smulloni
375:/* added non-incremental search, and forward and reverse regex search, using
376:/* the org.apache.oro regex package.
377:/* */
|