Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/port80/eclipse/util/IncrementalFindTreeTarget.java


1   package com.port80.eclipse.util;
2   
3   import java.text.MessageFormat;
4   import java.util.ArrayList;
5   import java.util.List;
6   import java.util.Stack;
7   
8   import org.eclipse.jface.action.IStatusLineManager;
9   import org.eclipse.jface.viewers.AbstractTreeViewer;
10  import org.eclipse.jface.viewers.IBaseLabelProvider;
11  import org.eclipse.jface.viewers.IContentProvider;
12  import org.eclipse.jface.viewers.ILabelProvider;
13  import org.eclipse.jface.viewers.ITreeContentProvider;
14  import org.eclipse.jface.viewers.StructuredSelection;
15  import org.eclipse.jface.viewers.ViewerSorter;
16  import org.eclipse.swt.SWT;
17  import org.eclipse.swt.events.FocusEvent;
18  import org.eclipse.swt.events.FocusListener;
19  import org.eclipse.swt.events.KeyEvent;
20  import org.eclipse.swt.events.KeyListener;
21  import org.eclipse.swt.events.MouseEvent;
22  import org.eclipse.swt.events.MouseListener;
23  import org.eclipse.swt.widgets.Control;
24  
25  /**
26   * An incremental find target for StructuredViewer with an ILabelProvider. Replace is always disabled.
27   * 
28   * @since 2.0
29   * @author chrisl
30   */
31  public class IncrementalFindTreeTarget implements IIncrementalFindTreeTarget, KeyListener, MouseListener, FocusListener
32  //, ITextListener 
33  {
34  
35    ////////////////////////////////////////////////////////////////////////
36  
37    private static final String NAME = "StructuredIncrementalFindTarget";
38    /** The string representing rendered tab */
39    private final static String TAB = UtilPlugin.getString("FindIncremental.render.tab"); //$NON-NLS-1$
40    private final static String PATTERN_NOTFOUND = UtilPlugin.getString("FindIncremental.pattern.not_found"); //$NON-NLS-1$
41    private final static String PATTERN_FOUND = UtilPlugin.getString("FindIncremental.pattern.found"); //$NON-NLS-1$
42    private final static String PROMPT = UtilPlugin.getString("FindIncremental.prompt"); //$NON-NLS-1$
43    /** A constant representing a find-next operation */
44    private static final int NEXT = 1;
45    /** A constant representing a find-previous operation */
46    private static final int PREVIOUS = 2;
47    /** A constant representing adding a character to the find pattern */
48    private static final int CHAR = 3;
49    /** A constant representing a wrap operation */
50    private static final Object WRAPPED = new Object();
51    private static final boolean DEBUG = false;
52  
53    private static String fSavedString = "";
54  
55    ////////////////////////////////////////////////////////////////////////
56  
57    /** The text viewer to operate on */
58    private final AbstractTreeViewer fViewer;
59    /** The status line manager for output */
60    private final IStatusLineManager fStatusLine;
61    /** The find replace target to delegate find requests */
62    //private final IFindReplaceTarget fTarget;
63  
64    /** The current find string */
65    private StringBuffer fFindString = new StringBuffer();
66    /** Index of first upper case char. in the find string. -1 if no upper case.*/
67    private int fCasePosition;
68    /** The position to start the find from */
69    private int fBasePosition;
70    /** The position of the last successful find */
71    private int fCurrentIndex;
72    /** A flag indicating if last find was successful */
73    private boolean fFound;
74    private boolean fForward;
75    /** A flag indicating listeners are installed. */
76    private boolean fInstalled;
77    private Object[] fElements;
78    private ILabelProvider fLabelProvider;
79    private ITreeContentProvider fContentProvider;
80    private KeyListener fPrevKeyListener;
81    /** Index stack to backtrace when taking back characters.*/
82    private Stack fStack;
83  
84    ////////////////////////////////////////////////////////////////////////
85  
86    /**
87     * Creates an instance of an incremental find target.
88     * 
89     * @param viewer the text viewer to operate on
90     * @param manager the status line manager for output
91     */
92    public IncrementalFindTreeTarget(AbstractTreeViewer viewer, IStatusLineManager manager, KeyListener prev) {
93      fViewer = viewer;
94      fStatusLine = manager;
95      fPrevKeyListener = prev;
96      fStack = new Stack();
97    }
98  
99    // IIncrementalFindTarget interface ////////////////////////////////////
100   //
101 
102   /*
103    * @see IFindReplaceTarget#canPerformFind()
104    */
105   public boolean canPerformFind() {
106     return true;
107   }
108 
109   public boolean isEditable() {
110     return false;
111   }
112 
113   public StructuredSelection getSelection() {
114     return (StructuredSelection) fViewer.getSelection();
115   }
116 
117   /** Get text representation of the currently selected item.
118    */
119   public String getSelectionText() {
120     StructuredSelection selection = getSelection();
121     if (selection == null)
122       return null;
123     if (selection.isEmpty())
124       return null;
125     return fLabelProvider.getText(selection.getFirstElement());
126   }
127 
128   /*
129    */
130   public void setSelection(Object object) {
131     fViewer.setSelection(new StructuredSelection(object), true);
132     //fViewer.refresh();
133   }
134 
135   /** Find item with the given string and select it.
136    * 
137    * @return The index of the selected item or -1.
138    * @param start  The index to start search from inclusively.
139    * @param findString  The target string.
140    * @param forward
141    * @param caseSensitive
142    */
143   public int findAndSelect(int start, String findString, boolean forward, boolean caseSensitive) {
144     int index = -1;
145     if (!caseSensitive)
146       findString = findString.toLowerCase();
147     int step = forward ? 1 : -1;
148     if (!forward && start >= 0)
149       --start;
150     for (int i = start; i >= 0 && i < fElements.length; i += step) {
151       String s = fLabelProvider.getText(fElements[i]);
152       if (!caseSensitive)
153         s = s.toLowerCase();
154       if (s.indexOf(findString) != -1) {
155         index = i;
156         break;
157       }
158     }
159     if (index != -1)
160       setSelection(fElements[index]);
161     return index;
162   }
163 
164   /*
165    * @see IFindReplaceTargetExtension#beginSession()
166    */
167   public void beginSession(boolean forward, String defaultString) {
168     if (DEBUG)
169       msgDebug(".beginSession()");
170     IContentProvider cp = fViewer.getContentProvider();
171     if (cp instanceof ITreeContentProvider)
172       fContentProvider = (ITreeContentProvider) cp;
173     else
174       return;
175     IBaseLabelProvider p = fViewer.getLabelProvider();
176     if (p instanceof ILabelProvider)
177       fLabelProvider = (ILabelProvider) p;
178     else
179       return;
180     install();
181     //
182     fForward = forward;
183     if (defaultString != null)
184       fSavedString = defaultString;
185     fElements =
186       getTreeElements(new Object[] { fViewer.getInput()}, fViewer.getSorter(), new ArrayList())
187         .toArray();
188     //      getTreeElements(fContentProvider.getElements(null), fViewer.getSorter(), new ArrayList())
189     //        .toArray();
190     if (fElements.length == 0) {
191       UtilPlugin.log(NAME + ".beginSession(): no element found.");
192       return;
193     }
194     fCasePosition = -1;
195     if (DEBUG)
196       msgDebug(".beginSession(): " + fElements.length + ", saved=" + fSavedString);
197     fBasePosition = 0;
198     StructuredSelection selection = (StructuredSelection) fViewer.getSelection();
199     if (selection != null) {
200       Object a = selection.getFirstElement();
201       if (a != null) {
202         for (int i = 0; i < fElements.length; ++i) {
203           if (a == fElements[i]) {
204             fBasePosition = i;
205             break;
206           }
207         }
208       }
209     }
210     fCurrentIndex = fBasePosition;
211     fFound = true;
212     statusMessage(PROMPT);
213   }
214 
215   /*
216    * @see IFindReplaceTargetExtension#endSession()
217    */
218   public void endSession() {
219     if (DEBUG)
220       msgDebug(".endSession()");
221     if (fInstalled) {
222       if (fFindString.length() > 0)
223         fSavedString = fFindString.toString();
224       uninstall();
225     }
226     fFindString.setLength(0);
227     statusClear();
228   }
229 
230   ////////////////////////////////////////////////////////////////////////
231 
232   /**
233    * Installs this target. I.e. adds all required listeners.
234    */
235   private void install() {
236     if (fInstalled)
237       return;
238     Control control = fViewer.getControl();
239     if (fPrevKeyListener != null)
240       control.removeKeyListener(fPrevKeyListener);
241     control.addKeyListener(this);
242     control.addMouseListener(this);
243     control.addFocusListener(this);
244     fInstalled = true;
245   }
246 
247   /**
248    * Uninstalls itself. I.e. removes all listeners installed in <code>install</code>.
249    */
250   private void uninstall() {
251     if (!fInstalled)
252       return;
253     Control control = fViewer.getControl();
254     //control.removeFocusListener(this);
255     control.removeMouseListener(this);
256     control.removeKeyListener(this);
257     if (fPrevKeyListener != null)
258       control.addKeyListener(fPrevKeyListener);
259     fInstalled = false;
260   }
261 
262   ////////////////////////////////////////////////////////////////////////
263 
264   /**
265    * Returns whether the find string can be found using the given options.
266    * 
267    * @param forward the search direction
268    * @param position the start offset
269    * @param wrapSearch    should the search wrap to start/end if end/start is reached
270    * @param takeBack is the find string shortend
271    * @return <code>true</code> if find string can be found using the options
272    */
273   private boolean performFindNext(boolean forward, int start, boolean wrapSearch, boolean takeBack) {
274     String string = fFindString.toString();
275     if (string.length() == 0)
276       string = fSavedString;
277     if (string.length() == 0) {
278       fCurrentIndex = start;
279       return false;
280     }
281     // If current index is not valid, start from start.
282     if (start < 0 || start >= fElements.length)
283       start = 0;
284     int index = findIndex(string, start, forward, fCasePosition != -1, wrapSearch, takeBack);
285     if (index != -1) {
286       fCurrentIndex = index;
287       fFound = true;
288     } else {
289       fFound = false;
290     }
291     if (DEBUG)
292       System.out.println(
293         NAME
294           + ".performFindNext(): forward="
295           + forward
296           + ", string="
297           + string
298           + ", found="
299           + fFound
300           + ", match="
301           + getSelectionText());
302     return fFound;
303   }
304 
305   /**
306    * Retuns the offset at which the search string is found next using the given options
307    * 
308    * @param findString the string to search for
309    * @param startPosition the start offset
310    * @param forward the search direction
311    * @param caseSensitive is the search case sensitive
312    * @param wrapSearch    should the search wrap to start/end if end/start is reached
313    * @param takeBack is the find string shortend
314    * @return the offset of the next match or <code>-1</code>
315    */
316   private int findIndex(
317     String findString,
318     int startPosition,
319     boolean forward,
320     boolean caseSensitive,
321     boolean wrapSearch,
322     boolean takeBack) {
323 
324     int index = findAndSelect(startPosition, findString, forward, caseSensitive);
325     if (index != -1)
326       return index;
327     // Not found, beep once
328     if (!takeBack)
329       beep();
330     if (!wrapSearch)
331       return -1;
332     // Before wrap search, stop once.
333     if (fFound)
334       return -1;
335     // Wrap search
336     startPosition = forward ? 0 : fElements.length - 1;
337     index = findAndSelect(startPosition, findString, forward, caseSensitive);
338     return index;
339   }
340 
341   /**
342    * Updates the status line appropriate for the indicated success.
343    * @param found the success
344    */
345   private void updateStatus(boolean found) {
346     String string = fFindString.toString();
347     String prefix = ""; //$NON-NLS-1$
348     if (!found) {
349       statusError(MessageFormat.format(PATTERN_NOTFOUND, new Object[] { prefix, string }));
350     } else {
351       statusMessage(MessageFormat.format(PATTERN_FOUND, new Object[] { prefix, string }));
352     }
353   }
354 
355   ////////////////////////////////////////////////////////////////////////
356 
357   /**
358    * Perform increment find.  All non-printable characters need to be escaped.
359    * TODO: escape non-printable characters.
360    * 
361    * @see KeyListener#keyPressed(Event)
362    */
363   public void keyPressed(KeyEvent event) {
364     //NOTE:
365     // event.character=='' and event.keycode==0 for most keys and key combinations
366     // not named in SWT. eg. no keycode for keypad keys, window key ... etc!!!
367     if (DEBUG)
368       System.err.println(
369         NAME
370           + ".keyPressed(): state="
371           + event.stateMask
372           + ", char="
373           + event.character
374           + "keycode="
375           + event.keyCode);
376     if (event.stateMask == 0 && event.character == 0) {
377       switch (event.keyCode) {
378         case SWT.HOME :
379           fCurrentIndex = 0;
380           setSelection(fElements[0]);
381           break;
382         case SWT.END :
383           fCurrentIndex = fElements.length - 1;
384           setSelection(fElements[fCurrentIndex]);
385           break;
386         case SWT.ALT :
387         case SWT.ARROW_LEFT :
388         case SWT.ARROW_RIGHT :
389         case SWT.PAGE_DOWN :
390         case SWT.PAGE_UP :
391         case SWT.ARROW_DOWN :
392         case SWT.ARROW_UP :
393         default :
394           break;
395       }
396       updateStatus(fFound);
397       return;
398     }
399     // C-r,C-s are trapped.  For now, use 'M-C-r' and 'M-C-s' instead.
400     if ((event.stateMask & SWT.ALT) != 0) {
401       // if (event.stateMask == (SWT.CTRL | SWT.ALT | SWT.SHIFT) && event.character == 0x73) {
402       if (event.keyCode == SWT.ARROW_DOWN) {
403         // 'C-s' forward
404         if (fFindString.length() == 0)
405           fFindString.append(fSavedString);
406         fForward = true;
407         performFindNext(true, fCurrentIndex + 1, true, false);
408       } else if (event.keyCode == SWT.ARROW_UP) {
409         fForward = false;
410         if (fFindString.length() == 0)
411           fFindString.append(fSavedString);
412         performFindNext(false, fCurrentIndex, true, false);
413       }
414       updateStatus(fFound);
415       return;
416     }
417     // Characters.
418     switch (event.character) {
419       case 0x1B :
420       case 0x0D :
421         // ESC, CR = quit
422         endSession();
423         break;
424       case 0x08 :
425       case 0x7F :
426         // backspace and delete
427         if (fFindString.length() > 0) {
428           popChar();
429         } else if (fFindString.length() == 0) {
430           setSelection(fElements[fBasePosition]);
431         } else
432           beep();
433         updateStatus(fFound);
434         break;
435       default :
436         if (event.stateMask == 0 || event.stateMask == SWT.SHIFT) {
437           pushChar(event.character);
438           performFindNext(fForward, fCurrentIndex, false, false);
439         }
440         updateStatus(fFound);
441     }
442   }
443 
444   public void keyReleased(KeyEvent event) {
445     //    if ((event.stateMask & SWT.CTRL) != 0) {
446     //      // if (event.stateMask == (SWT.ALT | SWT.CTRL | SWT.SHIFT) && event.character == 0x72) {
447     //      // 'C-r' and 'M-C-r' keyPressed() are trapped by eclipse.
448     //      // For now, we use 'M-C-r' keyReleased() instead.
449     //      if (event.keyCode == SWT.ARROW_UP) {
450     //        fForward = false;
451     //        if (fFindString.length() == 0)
452     //          fFindString.append(fSavedString);
453     //        performFindNext(false, fCurrentIndex, true, false);
454     //        updateStatus(fFound);
455     //        return;
456     //      }
457     //    }
458   }
459 
460   /**
461    * Extends the incremental search by the given character.
462    * @param character the character to append to the searhc string
463    */
464   private void pushChar(char character) {
465     if (fCasePosition == -1
466       && Character.isUpperCase(character)
467       && Character.toLowerCase(character) != character)
468       fCasePosition = fFindString.length();
469     fFindString.append(character);
470     fStack.push(new Integer(fCurrentIndex));
471   }
472 
473   /**
474    * Delete a character from the find string.
475    * @return Old length of find string.
476    */
477   private int popChar() {
478     int len = fFindString.length();
479     if (len > 0) {
480       fFindString.deleteCharAt(--len);
481       if (fCasePosition == len)
482         fCasePosition = -1;
483     }
484     // When findString is empty, go back to base position regardless.
485     if (len == 0) {
486       fCurrentIndex = fBasePosition;
487       fStack.clear();
488     } else if (fStack.empty())
489       return -1;
490     else
491       fCurrentIndex = ((Integer) fStack.pop()).intValue();
492     setSelection(fElements[fCurrentIndex]);
493     return fCurrentIndex;
494   }
495 
496   private void beep() {
497     Control control = fViewer.getControl();
498     if (control != null && !control.isDisposed())
499       control.getDisplay().beep();
500   }
501 
502   ////////////////////////////////////////////////////////////////////////
503 
504   /*
505    * @see ITextListener#textChanged(TextEvent)
506    */
507   //  public void textChanged(TextEvent event) {
508   //    endSession();
509   //  }
510 
511   ////////////////////////////////////////////////////////////////////////
512 
513   /*
514    * @see MouseListener#mouseDoubleClick(MouseEvent)
515    */
516   public void mouseDoubleClick(MouseEvent e) {
517     endSession();
518   }
519 
520   /*
521    * @see MouseListener#mouseDown(MouseEvent)
522    */
523   public void mouseDown(MouseEvent e) {
524     endSession();
525   }
526 
527   /*
528    * @see MouseListener#mouseUp(MouseEvent)
529    */
530   public void mouseUp(MouseEvent e) {
531   }
532 
533   ////////////////////////////////////////////////////////////////////////
534 
535   /*
536    * @see FocusListener#focusGained(FocusEvent)
537    */
538   public void focusGained(FocusEvent e) {
539     if (false)
540       msgDebug(".focusGrained()");
541   }
542 
543   /*
544    * @see FocusListener#focusLost(FocusEvent)
545    */
546   public void focusLost(FocusEvent e) {
547     if (false)
548       msgDebug(".focusLost()");
549     //FIXME: 'M-C-r' caused advertise focus lost, so just ignore focus change for now.
550     //endSession();
551   }
552 
553   ////////////////////////////////////////////////////////////////////////
554 
555   /** Get an ordered list of elements in a tree. 
556    */
557   private List getTreeElements(Object[] parents, ViewerSorter sorter, List ret) {
558     Object[] aa = parents;
559     if (sorter != null)
560       sorter.sort(fViewer, aa);
561     for (int i = 0; i < aa.length; ++i) {
562       ret.add(aa[i]);
563       getTreeElements(fContentProvider.getChildren(aa[i]), sorter, ret);
564     }
565     return ret;
566   }
567 
568   ////////////////////////////////////////////////////////////////////////
569 
570   /**
571    * Sets the given string as status message, clears the status error message.
572    * @param string the status message
573    */
574   private void statusMessage(String string) {
575     fStatusLine.setErrorMessage(""); //$NON-NLS-1$
576     fStatusLine.setMessage(escapeTabs(string));
577   }
578 
579   /**
580    * Sets the status error message, clears the status message.
581    * @param string the status error message
582    */
583   private void statusError(String string) {
584     fStatusLine.setErrorMessage(escapeTabs(string));
585     fStatusLine.setMessage(""); //$NON-NLS-1$
586   }
587 
588   /**
589    * Clears the status message and the status error message.
590    */
591   private void statusClear() {
592     fStatusLine.setErrorMessage(""); //$NON-NLS-1$
593     fStatusLine.setMessage(" "); //$NON-NLS-1$
594   }
595 
596   /**
597    * Translates all tab characters into a proper status line presentation.
598    * @param string the string in which to translate the tabs
599    * @return the given string with all tab characters replace with a proper status line presentation
600    */
601   private String escapeTabs(String string) {
602     StringBuffer buffer = new StringBuffer();
603 
604     int begin = 0;
605     int end = string.indexOf('\t', begin);
606 
607     while (end >= 0) {
608       buffer.append(string.substring(begin, end));
609       buffer.append(TAB);
610       begin = end + 1;
611       end = string.indexOf('\t', begin);
612     }
613     buffer.append(string.substring(begin));
614 
615     return buffer.toString();
616   }
617 
618   private void msgDebug(String msg) {
619     System.err.println(NAME + msg);
620   }
621 }