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

Quick Search    Search Deep

Source code: com/port80/swt/widgets/GraphViewIncrementalFinder.java


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