KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > contentassist > CompletionProposalPopup


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  * Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=116454
11  *******************************************************************************/

12 package org.eclipse.jface.text.contentassist;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.custom.BusyIndicator;
19 import org.eclipse.swt.events.ControlEvent;
20 import org.eclipse.swt.events.ControlListener;
21 import org.eclipse.swt.events.DisposeEvent;
22 import org.eclipse.swt.events.DisposeListener;
23 import org.eclipse.swt.events.FocusEvent;
24 import org.eclipse.swt.events.FocusListener;
25 import org.eclipse.swt.events.KeyAdapter;
26 import org.eclipse.swt.events.KeyEvent;
27 import org.eclipse.swt.events.KeyListener;
28 import org.eclipse.swt.events.MouseAdapter;
29 import org.eclipse.swt.events.MouseEvent;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.events.SelectionListener;
32 import org.eclipse.swt.events.VerifyEvent;
33 import org.eclipse.swt.graphics.Color;
34 import org.eclipse.swt.graphics.Font;
35 import org.eclipse.swt.graphics.FontData;
36 import org.eclipse.swt.graphics.Image;
37 import org.eclipse.swt.graphics.Point;
38 import org.eclipse.swt.graphics.Rectangle;
39 import org.eclipse.swt.layout.GridData;
40 import org.eclipse.swt.layout.GridLayout;
41 import org.eclipse.swt.widgets.Control;
42 import org.eclipse.swt.widgets.Display;
43 import org.eclipse.swt.widgets.Event;
44 import org.eclipse.swt.widgets.Label;
45 import org.eclipse.swt.widgets.Listener;
46 import org.eclipse.swt.widgets.Shell;
47 import org.eclipse.swt.widgets.Table;
48 import org.eclipse.swt.widgets.TableItem;
49
50 import org.eclipse.core.runtime.Assert;
51
52 import org.eclipse.jface.text.AbstractInformationControlManager;
53 import org.eclipse.jface.text.BadLocationException;
54 import org.eclipse.jface.text.DocumentEvent;
55 import org.eclipse.jface.text.IDocument;
56 import org.eclipse.jface.text.IDocumentListener;
57 import org.eclipse.jface.text.IEditingSupport;
58 import org.eclipse.jface.text.IEditingSupportRegistry;
59 import org.eclipse.jface.text.IRegion;
60 import org.eclipse.jface.text.IRewriteTarget;
61 import org.eclipse.jface.text.ITextViewer;
62 import org.eclipse.jface.text.ITextViewerExtension;
63 import org.eclipse.jface.text.TextUtilities;
64 import org.eclipse.jface.text.AbstractInformationControlManager.Anchor;
65
66 import org.eclipse.jface.bindings.keys.KeySequence;
67 import org.eclipse.jface.bindings.keys.SWTKeySupport;
68 import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
69 import org.eclipse.jface.resource.JFaceResources;
70 import org.eclipse.jface.util.Geometry;
71
72
73 /**
74  * This class is used to present proposals to the user. If additional
75  * information exists for a proposal, then selecting that proposal
76  * will result in the information being displayed in a secondary
77  * window.
78  *
79  * @see org.eclipse.jface.text.contentassist.ICompletionProposal
80  * @see org.eclipse.jface.text.contentassist.AdditionalInfoController
81  */

82 class CompletionProposalPopup implements IContentAssistListener {
83     /**
84      * Set to <code>true</code> to use a Table with SWT.VIRTUAL.
85      * XXX: This is a workaround for: https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321
86      * More details see also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c36
87      * @since 3.1
88      */

89     private static final boolean USE_VIRTUAL= !"motif".equals(SWT.getPlatform()); //$NON-NLS-1$
90

91     /**
92      * The empty proposal displayed if there is nothing else to show.
93      *
94      * @since 3.2
95      */

96     private static final class EmptyProposal implements ICompletionProposal, ICompletionProposalExtension {
97
98         String JavaDoc fDisplayString;
99         int fOffset;
100         /*
101          * @see ICompletionProposal#apply(IDocument)
102          */

103         public void apply(IDocument document) {
104         }
105
106         /*
107          * @see ICompletionProposal#getSelection(IDocument)
108          */

109         public Point getSelection(IDocument document) {
110             return new Point(fOffset, 0);
111         }
112
113         /*
114          * @see ICompletionProposal#getContextInformation()
115          */

116         public IContextInformation getContextInformation() {
117             return null;
118         }
119
120         /*
121          * @see ICompletionProposal#getImage()
122          */

123         public Image getImage() {
124             return null;
125         }
126
127         /*
128          * @see ICompletionProposal#getDisplayString()
129          */

130         public String JavaDoc getDisplayString() {
131             return fDisplayString;
132         }
133
134         /*
135          * @see ICompletionProposal#getAdditionalProposalInfo()
136          */

137         public String JavaDoc getAdditionalProposalInfo() {
138             return null;
139         }
140
141         /*
142          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#apply(org.eclipse.jface.text.IDocument, char, int)
143          */

144         public void apply(IDocument document, char trigger, int offset) {
145         }
146
147         /*
148          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#isValidFor(org.eclipse.jface.text.IDocument, int)
149          */

150         public boolean isValidFor(IDocument document, int offset) {
151             return false;
152         }
153
154         /*
155          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters()
156          */

157         public char[] getTriggerCharacters() {
158             return null;
159         }
160
161         /*
162          * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition()
163          */

164         public int getContextInformationPosition() {
165             return -1;
166         }
167     }
168
169     private final class ProposalSelectionListener implements KeyListener {
170         public void keyPressed(KeyEvent e) {
171             if (!Helper.okToUse(fProposalShell))
172                 return;
173
174             if (e.character == 0 && e.keyCode == SWT.MOD1) {
175                 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
176
int index= fProposalTable.getSelectionIndex();
177                 if (index >= 0)
178                     selectProposal(index, true);
179             }
180         }
181
182         public void keyReleased(KeyEvent e) {
183             if (!Helper.okToUse(fProposalShell))
184                 return;
185
186             if (e.character == 0 && e.keyCode == SWT.MOD1) {
187                 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
188
int index= fProposalTable.getSelectionIndex();
189                 if (index >= 0)
190                     selectProposal(index, false);
191             }
192         }
193     }
194
195     private final class CommandKeyListener extends KeyAdapter {
196         private KeySequence fCommandSequence;
197         
198         private CommandKeyListener(KeySequence keySequence) {
199             fCommandSequence= keySequence;
200         }
201         
202         public void keyPressed(KeyEvent e) {
203             if (!Helper.okToUse(fProposalShell))
204                 return;
205             
206             int accelerator= SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
207             KeySequence sequence= KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator));
208             if (sequence.equals(fCommandSequence))
209                 if (fContentAssistant.isPrefixCompletionEnabled())
210                     incrementalComplete();
211                 else
212                     showProposals(false);
213             
214         }
215     }
216     
217     /** The associated text viewer. */
218     private ITextViewer fViewer;
219     /** The associated content assistant. */
220     private ContentAssistant fContentAssistant;
221     /** The used additional info controller. */
222     private AdditionalInfoController fAdditionalInfoController;
223     /** The closing strategy for this completion proposal popup. */
224     private PopupCloser fPopupCloser= new PopupCloser();
225     /** The popup shell. */
226     private Shell fProposalShell;
227     /** The proposal table. */
228     private Table fProposalTable;
229     /** Indicates whether a completion proposal is being inserted. */
230     private boolean fInserting= false;
231     /** The key listener to control navigation. */
232     private ProposalSelectionListener fKeyListener;
233     /** List of document events used for filtering proposals. */
234     private List JavaDoc fDocumentEvents= new ArrayList JavaDoc();
235     /** Listener filling the document event queue. */
236     private IDocumentListener fDocumentListener;
237     /** The filter list of proposals. */
238     private ICompletionProposal[] fFilteredProposals;
239     /** The computed list of proposals. */
240     private ICompletionProposal[] fComputedProposals;
241     /** The offset for which the proposals have been computed. */
242     private int fInvocationOffset;
243     /** The offset for which the computed proposals have been filtered. */
244     private int fFilterOffset;
245     /**
246      * The most recently selected proposal.
247      * @since 3.0
248      */

249     private ICompletionProposal fLastProposal;
250     /**
251      * The content assist subject control.
252      * This replaces <code>fViewer</code>
253      *
254      * @since 3.0
255      */

256     private IContentAssistSubjectControl fContentAssistSubjectControl;
257     /**
258      * The content assist subject control adapter.
259      * This replaces <code>fViewer</code>
260      *
261      * @since 3.0
262      */

263     private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter;
264     /**
265      * Remembers the size for this completion proposal popup.
266      * @since 3.0
267      */

268     private Point fSize;
269     /**
270      * Editor helper that communicates that the completion proposal popup may
271      * have focus while the 'logical focus' is still with the editor.
272      * @since 3.1
273      */

274     private IEditingSupport fFocusHelper;
275     /**
276      * Set to true by {@link #computeFilteredProposals(int, DocumentEvent)} if
277      * the returned proposals are a subset of {@link #fFilteredProposals},
278      * <code>false</code> if not.
279      * @since 3.1
280      */

281     private boolean fIsFilteredSubset;
282     /**
283      * The filter runnable.
284      *
285      * @since 3.1.1
286      */

287     private final Runnable JavaDoc fFilterRunnable= new Runnable JavaDoc() {
288         public void run() {
289             if (!fIsFilterPending)
290                 return;
291             
292             fIsFilterPending= false;
293
294             if (!Helper.okToUse(fContentAssistSubjectControlAdapter.getControl()))
295                 return;
296
297             int offset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
298             ICompletionProposal[] proposals= null;
299             try {
300                 if (offset > -1) {
301                     DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents);
302                     proposals= computeFilteredProposals(offset, event);
303                 }
304             } catch (BadLocationException x) {
305             } finally {
306                 fDocumentEvents.clear();
307             }
308             fFilterOffset= offset;
309
310             if (proposals != null && proposals.length > 0)
311                 setProposals(proposals, fIsFilteredSubset);
312             else
313                 hide();
314         }
315     };
316     /**
317      * <code>true</code> if <code>fFilterRunnable</code> has been
318      * posted, <code>false</code> if not.
319      *
320      * @since 3.1.1
321      */

322     private boolean fIsFilterPending= false;
323     /**
324      * The info message at the bottom of the popup, or <code>null</code> for no popup (if
325      * ContentAssistant does not provide one).
326      *
327      * @since 3.2
328      */

329     private Label fMessageText;
330     /**
331      * The font used for <code>fMessageText</code> or null; dispose when done.
332      *
333      * @since 3.2
334      */

335     private Font fMessageTextFont;
336     /**
337      * The most recent completion offset (used to determine repeated invocation)
338      *
339      * @since 3.2
340      */

341     private int fLastCompletionOffset;
342     /**
343      * The (reusable) empty proposal.
344      *
345      * @since 3.2
346      */

347     private final EmptyProposal fEmptyProposal= new EmptyProposal();
348     /**
349      * The text for the empty proposal, or <code>null</code> to use the default text.
350      *
351      * @since 3.2
352      */

353     private String JavaDoc fEmptyMessage= null;
354
355     /**
356      * Creates a new completion proposal popup for the given elements.
357      *
358      * @param contentAssistant the content assistant feeding this popup
359      * @param viewer the viewer on top of which this popup appears
360      * @param infoController the information control collaborating with this popup
361      * @since 2.0
362      */

363     public CompletionProposalPopup(ContentAssistant contentAssistant, ITextViewer viewer, AdditionalInfoController infoController) {
364         fContentAssistant= contentAssistant;
365         fViewer= viewer;
366         fAdditionalInfoController= infoController;
367         fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer);
368     }
369
370     /**
371      * Creates a new completion proposal popup for the given elements.
372      *
373      * @param contentAssistant the content assistant feeding this popup
374      * @param contentAssistSubjectControl the content assist subject control on top of which this popup appears
375      * @param infoController the information control collaborating with this popup
376      * @since 3.0
377      */

378     public CompletionProposalPopup(ContentAssistant contentAssistant, IContentAssistSubjectControl contentAssistSubjectControl, AdditionalInfoController infoController) {
379         fContentAssistant= contentAssistant;
380         fContentAssistSubjectControl= contentAssistSubjectControl;
381         fAdditionalInfoController= infoController;
382         fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl);
383     }
384
385     /**
386      * Computes and presents completion proposals. The flag indicates whether this call has
387      * be made out of an auto activation context.
388      *
389      * @param autoActivated <code>true</code> if auto activation context
390      * @return an error message or <code>null</code> in case of no error
391      */

392     public String JavaDoc showProposals(final boolean autoActivated) {
393
394         if (fKeyListener == null)
395             fKeyListener= new ProposalSelectionListener();
396
397         final Control control= fContentAssistSubjectControlAdapter.getControl();
398
399         if (!Helper.okToUse(fProposalShell) && control != null && !control.isDisposed()) {
400             // add the listener before computing the proposals so we don't move the caret
401
// when the user types fast.
402
fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
403
404             BusyIndicator.showWhile(control.getDisplay(), new Runnable JavaDoc() {
405                 public void run() {
406
407                     fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
408                     fFilterOffset= fInvocationOffset;
409                     fLastCompletionOffset= fFilterOffset;
410                     fComputedProposals= computeProposals(fInvocationOffset);
411
412                     int count= (fComputedProposals == null ? 0 : fComputedProposals.length);
413                     if (count == 0 && hideWhenNoProposals(autoActivated))
414                         return;
415                     
416                     if (count == 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) {
417                         insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
418                         hide();
419                     } else {
420                         createProposalSelector();
421                         setProposals(fComputedProposals, false);
422                         displayProposals();
423                     }
424                 }
425             });
426         } else {
427             fLastCompletionOffset= fFilterOffset;
428             handleRepeatedInvocation();
429         }
430
431         return getErrorMessage();
432     }
433     
434     /**
435      * Hides the popup and returns <code>true</code> if the popup is configured
436      * to never display an empty list. Returns <code>false</code> otherwise.
437      *
438      * @param autoActivated whether the invocation was auto-activated
439      * @return <code>false</code> if an empty list should be displayed, <code>true</code> otherwise
440      * @since 3.2
441      */

442     private boolean hideWhenNoProposals(boolean autoActivated) {
443         if (autoActivated || !fContentAssistant.isShowEmptyList()) {
444             if (!autoActivated) {
445                 Control control= fContentAssistSubjectControlAdapter.getControl();
446                 if (control != null && !control.isDisposed())
447                     control.getDisplay().beep();
448             }
449             hide();
450             return true;
451         }
452         return false;
453     }
454
455     /**
456      * If content assist is set up to handle cycling, then the proposals are recomputed. Otherwise,
457      * nothing happens.
458      *
459      * @since 3.2
460      */

461     private void handleRepeatedInvocation() {
462         if (fContentAssistant.isRepeatedInvocationMode()) {
463             fComputedProposals= computeProposals(fFilterOffset);
464             setProposals(fComputedProposals, false);
465         }
466     }
467
468     /**
469      * Returns the completion proposal available at the given offset of the
470      * viewer's document. Delegates the work to the content assistant.
471      *
472      * @param offset the offset
473      * @return the completion proposals available at this offset
474      */

475     private ICompletionProposal[] computeProposals(int offset) {
476         if (fContentAssistSubjectControl != null)
477             return fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset);
478         return fContentAssistant.computeCompletionProposals(fViewer, offset);
479     }
480
481     /**
482      * Returns the error message.
483      *
484      * @return the error message
485      */

486     private String JavaDoc getErrorMessage() {
487         return fContentAssistant.getErrorMessage();
488     }
489
490     /**
491      * Creates the proposal selector.
492      */

493     private void createProposalSelector() {
494         if (Helper.okToUse(fProposalShell))
495             return;
496
497         Control control= fContentAssistSubjectControlAdapter.getControl();
498         fProposalShell= new Shell(control.getShell(), SWT.ON_TOP | SWT.RESIZE );
499         fProposalShell.setFont(JFaceResources.getDefaultFont());
500         if (USE_VIRTUAL) {
501             fProposalTable= new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL);
502
503             Listener listener= new Listener() {
504                 public void handleEvent(Event event) {
505                     handleSetData(event);
506                 }
507             };
508             fProposalTable.addListener(SWT.SetData, listener);
509         } else {
510             fProposalTable= new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL);
511         }
512
513         fProposalTable.setLocation(0, 0);
514         if (fAdditionalInfoController != null)
515             fAdditionalInfoController.setSizeConstraints(50, 10, true, true);
516
517         GridLayout layout= new GridLayout();
518         layout.marginWidth= 0;
519         layout.marginHeight= 0;
520         layout.verticalSpacing= 1;
521         fProposalShell.setLayout(layout);
522         
523         if (fContentAssistant.isStatusLineVisible()) {
524             createMessageText();
525         }
526
527         GridData data= new GridData(GridData.FILL_BOTH);
528
529         Point size= fContentAssistant.restoreCompletionProposalPopupSize();
530         if (size != null) {
531             fProposalTable.setLayoutData(data);
532             fProposalShell.setSize(size);
533         } else {
534             int height= fProposalTable.getItemHeight() * 10;
535             // use golden ratio as default aspect ratio
536
final double aspectRatio= (1 + Math.sqrt(5)) / 2;
537             int width= (int) (height * aspectRatio);
538             Rectangle trim= fProposalTable.computeTrim(0, 0, width, height);
539             data.heightHint= trim.height;
540             data.widthHint= trim.width;
541             fProposalTable.setLayoutData(data);
542             fProposalShell.pack();
543         }
544         fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
545
546         fProposalShell.addControlListener(new ControlListener() {
547
548             public void controlMoved(ControlEvent e) {}
549
550             public void controlResized(ControlEvent e) {
551                 if (fAdditionalInfoController != null) {
552                     // reset the cached resize constraints
553
fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
554                 }
555
556                 fSize= fProposalShell.getSize();
557             }
558         });
559         
560         if (!"carbon".equals(SWT.getPlatform())) //$NON-NLS-1$
561
fProposalShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_GRAY));
562
563         Color c= getBackgroundColor(control);
564         fProposalTable.setBackground(c);
565
566         c= getForegroundColor(control);
567         fProposalTable.setForeground(c);
568
569         fProposalTable.addSelectionListener(new SelectionListener() {
570
571             public void widgetSelected(SelectionEvent e) {}
572
573             public void widgetDefaultSelected(SelectionEvent e) {
574                 insertSelectedProposalWithMask(e.stateMask);
575             }
576         });
577
578         fPopupCloser.install(fContentAssistant, fProposalTable);
579
580         fProposalShell.addDisposeListener(new DisposeListener() {
581             public void widgetDisposed(DisposeEvent e) {
582                 unregister(); // but don't dispose the shell, since we're being called from its disposal event!
583
}
584         });
585
586         fProposalTable.setHeaderVisible(false);
587         
588         addCommandSupport(fProposalTable);
589     }
590
591     /**
592      * Returns the minimal required height for the proposal, may return 0 if the popup has not been
593      * created yet.
594      *
595      * @return the minimal height
596      * @since 3.3
597      */

598     int getMinimalHeight() {
599         int height= 0;
600         if (Helper.okToUse(fProposalTable)) {
601             int items= fProposalTable.getItemHeight() * 10;
602             Rectangle trim= fProposalTable.computeTrim(0, 0, SWT.DEFAULT, items);
603             height= trim.height;
604         }
605         if (Helper.okToUse(fMessageText))
606             height+= fMessageText.getSize().y + 1;
607         return height;
608     }
609
610     /**
611      * Adds command support to the given control.
612      *
613      * @param control the control to watch for focus
614      * @since 3.2
615      */

616     private void addCommandSupport(final Control control) {
617         final KeySequence commandSequence= fContentAssistant.getTriggerSequence();
618         if (commandSequence != null && !commandSequence.isEmpty() && fContentAssistant.isRepeatedInvocationMode()) {
619             control.addFocusListener(new FocusListener() {
620                 private CommandKeyListener fCommandKeyListener;
621                 public void focusGained(FocusEvent e) {
622                     if (Helper.okToUse(control)) {
623                         if (fCommandKeyListener == null) {
624                             fCommandKeyListener= new CommandKeyListener(commandSequence);
625                             fProposalTable.addKeyListener(fCommandKeyListener);
626                         }
627                     }
628                 }
629                 public void focusLost(FocusEvent e) {
630                     if (fCommandKeyListener != null) {
631                         control.removeKeyListener(fCommandKeyListener);
632                         fCommandKeyListener= null;
633                     }
634                 }
635             });
636         }
637     }
638
639     /**
640      * Returns the background color to use.
641      *
642      * @param control the control to get the display from
643      * @return the background color
644      * @since 3.2
645      */

646     private Color getBackgroundColor(Control control) {
647         Color c= fContentAssistant.getProposalSelectorBackground();
648         if (c == null)
649             c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
650         return c;
651     }
652
653     /**
654      * Returns the foreground color to use.
655      *
656      * @param control the control to get the display from
657      * @return the foreground color
658      * @since 3.2
659      */

660     private Color getForegroundColor(Control control) {
661         Color c= fContentAssistant.getProposalSelectorForeground();
662         if (c == null)
663             c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND);
664         return c;
665     }
666     
667     /**
668      * Creates the caption line under the proposal table.
669      *
670      * @since 3.2
671      */

672     private void createMessageText() {
673         if (fMessageText == null) {
674             fMessageText= new Label(fProposalShell, SWT.RIGHT);
675             GridData textData= new GridData(SWT.FILL, SWT.BOTTOM, true, false);
676             fMessageText.setLayoutData(textData);
677             fMessageText.setText(fContentAssistant.getStatusMessage() + " "); //$NON-NLS-1$
678
if (fMessageTextFont == null) {
679                 Font font= fMessageText.getFont();
680                 Display display= fProposalShell.getDisplay();
681                 FontData[] fontDatas= font.getFontData();
682                 for (int i= 0; i < fontDatas.length; i++)
683                     fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
684                 fMessageTextFont= new Font(display, fontDatas);
685             }
686             fMessageText.setFont(fMessageTextFont);
687             fMessageText.setBackground(getBackgroundColor(fProposalShell));
688             fMessageText.setForeground(getForegroundColor(fProposalShell));
689
690             if (fContentAssistant.isRepeatedInvocationMode()) {
691                 fMessageText.setCursor(fProposalShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
692                 fMessageText.addMouseListener(new MouseAdapter() {
693                     public void mouseUp(MouseEvent e) {
694                         fLastCompletionOffset= fFilterOffset;
695                         fProposalTable.setFocus();
696                         handleRepeatedInvocation();
697                     }
698
699                     public void mouseDown(MouseEvent e) {
700                     }
701                 });
702             }
703         }
704     }
705
706     /*
707      * @since 3.1
708      */

709     private void handleSetData(Event event) {
710         TableItem item= (TableItem) event.item;
711         int index= fProposalTable.indexOf(item);
712
713         if (0 <= index && index < fFilteredProposals.length) {
714             ICompletionProposal current= fFilteredProposals[index];
715
716             item.setText(current.getDisplayString());
717             item.setImage(current.getImage());
718             item.setData(current);
719         } else {
720             // this should not happen, but does on win32
721
}
722     }
723
724     /**
725      * Returns the proposal selected in the proposal selector.
726      *
727      * @return the selected proposal
728      * @since 2.0
729      */

730     private ICompletionProposal getSelectedProposal() {
731         /* Make sure that there is no filter runnable pending.
732          * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=31427
733          */

734         if (fIsFilterPending)
735             fFilterRunnable.run();
736         
737         // filter runnable may have hidden the proposals
738
if (!Helper.okToUse(fProposalTable))
739             return null;
740         
741         int i= fProposalTable.getSelectionIndex();
742         if (fFilteredProposals == null || i < 0 || i >= fFilteredProposals.length)
743             return null;
744         return fFilteredProposals[i];
745     }
746
747     /**
748      * Takes the selected proposal and applies it.
749      *
750      * @param stateMask the state mask
751      * @since 3.2
752      */

753     private void insertSelectedProposalWithMask(int stateMask) {
754         ICompletionProposal p= getSelectedProposal();
755         hide();
756         if (p != null)
757             insertProposal(p, (char) 0, stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
758     }
759
760     /**
761      * Applies the given proposal at the given offset. The given character is the
762      * one that triggered the insertion of this proposal.
763      *
764      * @param p the completion proposal
765      * @param trigger the trigger character
766      * @param stateMask the state mask
767      * @param offset the offset
768      * @since 2.1
769      */

770     private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) {
771
772         fInserting= true;
773         IRewriteTarget target= null;
774         IEditingSupport helper= new IEditingSupport() {
775
776             public boolean isOriginator(DocumentEvent event, IRegion focus) {
777                 return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset;
778             }
779
780             public boolean ownsFocusShell() {
781                 return false;
782             }
783
784         };
785
786         try {
787
788             IDocument document= fContentAssistSubjectControlAdapter.getDocument();
789
790             if (fViewer instanceof ITextViewerExtension) {
791                 ITextViewerExtension extension= (ITextViewerExtension) fViewer;
792                 target= extension.getRewriteTarget();
793             }
794
795             if (target != null)
796                 target.beginCompoundChange();
797
798             if (fViewer instanceof IEditingSupportRegistry) {
799                 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
800                 registry.register(helper);
801             }
802
803
804             if (p instanceof ICompletionProposalExtension2 && fViewer != null) {
805                 ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
806                 e.apply(fViewer, trigger, stateMask, offset);
807             } else if (p instanceof ICompletionProposalExtension) {
808                 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
809                 e.apply(document, trigger, offset);
810             } else {
811                 p.apply(document);
812             }
813
814             Point selection= p.getSelection(document);
815             if (selection != null) {
816                 fContentAssistSubjectControlAdapter.setSelectedRange(selection.x, selection.y);
817                 fContentAssistSubjectControlAdapter.revealRange(selection.x, selection.y);
818             }
819
820             IContextInformation info= p.getContextInformation();
821             if (info != null) {
822
823                 int contextInformationOffset;
824                 if (p instanceof ICompletionProposalExtension) {
825                     ICompletionProposalExtension e= (ICompletionProposalExtension) p;
826                     contextInformationOffset= e.getContextInformationPosition();
827                 } else {
828                     if (selection == null)
829                         selection= fContentAssistSubjectControlAdapter.getSelectedRange();
830                     contextInformationOffset= selection.x + selection.y;
831                 }
832
833                 fContentAssistant.showContextInformation(info, contextInformationOffset);
834             } else
835                 fContentAssistant.showContextInformation(null, -1);
836
837
838         } finally {
839             if (target != null)
840                 target.endCompoundChange();
841
842             if (fViewer instanceof IEditingSupportRegistry) {
843                 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
844                 registry.unregister(helper);
845             }
846             fInserting= false;
847         }
848     }
849
850     /**
851      * Returns whether this popup has the focus.
852      *
853      * @return <code>true</code> if the popup has the focus
854      */

855     public boolean hasFocus() {
856         if (Helper.okToUse(fProposalShell))
857             return (fProposalShell.isFocusControl() || fProposalTable.isFocusControl());
858
859         return false;
860     }
861
862     /**
863      * Hides this popup.
864      */

865     public void hide() {
866
867         unregister();
868
869         if (fViewer instanceof IEditingSupportRegistry) {
870             IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
871             registry.unregister(fFocusHelper);
872         }
873
874         if (Helper.okToUse(fProposalShell)) {
875
876             fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR);
877
878             fPopupCloser.uninstall();
879             fProposalShell.setVisible(false);
880             fProposalShell.dispose();
881             fProposalShell= null;
882         }
883         
884         if (fMessageTextFont != null) {
885             fMessageTextFont.dispose();
886             fMessageTextFont= null;
887         }
888         
889         if (fMessageText != null) {
890             fMessageText= null;
891         }
892         
893         fEmptyMessage= null;
894         
895         fLastCompletionOffset= -1;
896         
897         fContentAssistant.fireSessionEndEvent();
898     }
899
900     /**
901      * Unregister this completion proposal popup.
902      *
903      * @since 3.0
904      */

905     private void unregister() {
906         if (fDocumentListener != null) {
907             IDocument document= fContentAssistSubjectControlAdapter.getDocument();
908             if (document != null)
909                 document.removeDocumentListener(fDocumentListener);
910             fDocumentListener= null;
911         }
912         fDocumentEvents.clear();
913
914         if (fKeyListener != null && fContentAssistSubjectControlAdapter.getControl() != null && !fContentAssistSubjectControlAdapter.getControl().isDisposed()) {
915             fContentAssistSubjectControlAdapter.removeKeyListener(fKeyListener);
916             fKeyListener= null;
917         }
918
919         if (fLastProposal != null) {
920             if (fLastProposal instanceof ICompletionProposalExtension2 && fViewer != null) {
921                 ICompletionProposalExtension2 extension= (ICompletionProposalExtension2) fLastProposal;
922                 extension.unselected(fViewer);
923             }
924             fLastProposal= null;
925         }
926
927         fFilteredProposals= null;
928         fComputedProposals= null;
929
930         fContentAssistant.possibleCompletionsClosed();
931     }
932
933     /**
934      *Returns whether this popup is active. It is active if the proposal selector is visible.
935      *
936      * @return <code>true</code> if this popup is active
937      */

938     public boolean isActive() {
939         return fProposalShell != null && !fProposalShell.isDisposed();
940     }
941
942     /**
943      * Initializes the proposal selector with these given proposals.
944      * @param proposals the proposals
945      * @param isFilteredSubset if <code>true</code>, the proposal table is
946      * not cleared, but the proposals that are not in the passed array
947      * are removed from the displayed set
948      */

949     private void setProposals(ICompletionProposal[] proposals, boolean isFilteredSubset) {
950         ICompletionProposal[] oldProposals= fFilteredProposals;
951         ICompletionProposal oldProposal= getSelectedProposal(); // may trigger filtering and a reentrant call to setProposals()
952
if (oldProposals != fFilteredProposals) // reentrant call was first - abort
953
return;
954
955         if (Helper.okToUse(fProposalTable)) {
956             if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
957                 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
958             
959             if (proposals == null || proposals.length == 0) {
960                 fEmptyProposal.fOffset= fFilterOffset;
961                 fEmptyProposal.fDisplayString= fEmptyMessage != null ? fEmptyMessage : JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"); //$NON-NLS-1$
962
proposals= new ICompletionProposal[] { fEmptyProposal };
963             }
964
965             fFilteredProposals= proposals;
966             final int newLen= proposals.length;
967             if (USE_VIRTUAL) {
968                 fProposalTable.setItemCount(newLen);
969                 fProposalTable.clearAll();
970             } else {
971                 fProposalTable.setRedraw(false);
972                 fProposalTable.setItemCount(newLen);
973                 TableItem[] items= fProposalTable.getItems();
974                 for (int i= 0; i < items.length; i++) {
975                     TableItem item= items[i];
976                     ICompletionProposal proposal= proposals[i];
977                     item.setText(proposal.getDisplayString());
978                     item.setImage(proposal.getImage());
979                     item.setData(proposal);
980                 }
981                 fProposalTable.setRedraw(true);
982             }
983
984             Point currentLocation= fProposalShell.getLocation();
985             Point newLocation= getLocation();
986             if ((newLocation.x < currentLocation.x && newLocation.y == currentLocation.y) || newLocation.y < currentLocation.y)
987                 fProposalShell.setLocation(newLocation);
988
989             selectProposal(0, false);
990         }
991     }
992
993     /**
994      * Returns the graphical location at which this popup should be made visible.
995      *
996      * @return the location of this popup
997      */

998     private Point getLocation() {
999         int caret= fContentAssistSubjectControlAdapter.getCaretOffset();
1000        Rectangle location= fContentAssistant.getLayoutManager().computeBoundsBelowAbove(fProposalShell, fSize == null ? fProposalShell.getSize() : fSize, caret, this);
1001        return Geometry.getLocation(location);
1002    }
1003
1004    /**
1005     * Returns the size of this completion proposal popup.
1006     *
1007     * @return a Point containing the size
1008     * @since 3.0
1009     */

1010    Point getSize() {
1011        return fSize;
1012    }
1013
1014    /**
1015     * Displays this popup and install the additional info controller, so that additional info
1016     * is displayed when a proposal is selected and additional info is available.
1017     */

1018    private void displayProposals() {
1019
1020        if (!Helper.okToUse(fProposalShell) || !Helper.okToUse(fProposalTable))
1021            return;
1022
1023        if (fContentAssistant.addContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR)) {
1024
1025            ensureDocumentListenerInstalled();
1026            
1027            if (fFocusHelper == null) {
1028                fFocusHelper= new IEditingSupport() {
1029
1030                    public boolean isOriginator(DocumentEvent event, IRegion focus) {
1031                        return false; // this helper just covers the focus change to the proposal shell, no remote editions
1032
}
1033
1034                    public boolean ownsFocusShell() {
1035                        return true;
1036                    }
1037
1038                };
1039            }
1040            if (fViewer instanceof IEditingSupportRegistry) {
1041                IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
1042                registry.register(fFocusHelper);
1043            }
1044
1045
1046            /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646
1047             * on GTK, setVisible and such may run the event loop
1048             * (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511)
1049             * Since the user may have already canceled the popup or selected
1050             * an entry (ESC or RETURN), we have to double check whether
1051             * the table is still okToUse. See comments below
1052             */

1053            fProposalShell.setVisible(true); // may run event loop on GTK
1054
// transfer focus since no verify key listener can be attached
1055
if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && Helper.okToUse(fProposalShell))
1056                fProposalShell.setFocus(); // may run event loop on GTK ??
1057

1058            if (fAdditionalInfoController != null && Helper.okToUse(fProposalTable)) {
1059                fAdditionalInfoController.install(fProposalTable);
1060                fAdditionalInfoController.handleTableSelectionChanged();
1061            }
1062        } else
1063            hide();
1064    }
1065
1066    /**
1067     * Installs the document listener if not already done.
1068     *
1069     * @since 3.2
1070     */

1071    private void ensureDocumentListenerInstalled() {
1072        if (fDocumentListener == null) {
1073            fDocumentListener= new IDocumentListener() {
1074                public void documentAboutToBeChanged(DocumentEvent event) {
1075                    if (!fInserting)
1076                        fDocumentEvents.add(event);
1077                }
1078
1079                public void documentChanged(DocumentEvent event) {
1080                    if (!fInserting)
1081                        filterProposals();
1082                }
1083            };
1084            IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1085            if (document != null)
1086                document.addDocumentListener(fDocumentListener);
1087        }
1088    }
1089
1090    /*
1091     * @see IContentAssistListener#verifyKey(VerifyEvent)
1092     */

1093    public boolean verifyKey(VerifyEvent e) {
1094        if (!Helper.okToUse(fProposalShell))
1095            return true;
1096
1097        char key= e.character;
1098        if (key == 0) {
1099            int newSelection= fProposalTable.getSelectionIndex();
1100            int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
1101            boolean smartToggle= false;
1102            switch (e.keyCode) {
1103
1104                case SWT.ARROW_LEFT :
1105                case SWT.ARROW_RIGHT :
1106                    filterProposals();
1107                    return true;
1108
1109                case SWT.ARROW_UP :
1110                    newSelection -= 1;
1111                    if (newSelection < 0)
1112                        newSelection= fProposalTable.getItemCount() - 1;
1113                    break;
1114
1115                case SWT.ARROW_DOWN :
1116                    newSelection += 1;
1117                    if (newSelection > fProposalTable.getItemCount() - 1)
1118                        newSelection= 0;
1119                    break;
1120
1121                case SWT.PAGE_DOWN :
1122                    newSelection += visibleRows;
1123                    if (newSelection >= fProposalTable.getItemCount())
1124                        newSelection= fProposalTable.getItemCount() - 1;
1125                    break;
1126
1127                case SWT.PAGE_UP :
1128                    newSelection -= visibleRows;
1129                    if (newSelection < 0)
1130                        newSelection= 0;
1131                    break;
1132
1133                case SWT.HOME :
1134                    newSelection= 0;
1135                    break;
1136
1137                case SWT.END :
1138                    newSelection= fProposalTable.getItemCount() - 1;
1139                    break;
1140
1141                default :
1142                    if (e.keyCode != SWT.CAPS_LOCK && e.keyCode != SWT.MOD1 && e.keyCode != SWT.MOD2 && e.keyCode != SWT.MOD3 && e.keyCode != SWT.MOD4)
1143                        hide();
1144                    return true;
1145            }
1146
1147            selectProposal(newSelection, smartToggle);
1148
1149            e.doit= false;
1150            return false;
1151
1152        }
1153
1154        // key != 0
1155
switch (key) {
1156            case 0x1B: // Esc
1157
e.doit= false;
1158                hide();
1159                break;
1160
1161            case '\n': // Ctrl-Enter on w2k
1162
case '\r': // Enter
1163
e.doit= false;
1164                insertSelectedProposalWithMask(e.stateMask);
1165                break;
1166
1167            case '\t':
1168                e.doit= false;
1169                fProposalShell.setFocus();
1170                return false;
1171
1172            default:
1173                ICompletionProposal p= getSelectedProposal();
1174                if (p instanceof ICompletionProposalExtension) {
1175                    ICompletionProposalExtension t= (ICompletionProposalExtension) p;
1176                    char[] triggers= t.getTriggerCharacters();
1177                    if (contains(triggers, key)) {
1178                        e.doit= false;
1179                        hide();
1180                        insertProposal(p, key, e.stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
1181                    }
1182            }
1183        }
1184
1185        return true;
1186    }
1187
1188    /**
1189     * Selects the entry with the given index in the proposal selector and feeds
1190     * the selection to the additional info controller.
1191     *
1192     * @param index the index in the list
1193     * @param smartToggle <code>true</code> if the smart toggle key has been pressed
1194     * @since 2.1
1195     */

1196    private void selectProposal(int index, boolean smartToggle) {
1197
1198        ICompletionProposal oldProposal= getSelectedProposal();
1199        if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
1200            ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
1201
1202        if (fFilteredProposals == null) {
1203            fireSelectionEvent(null, smartToggle);
1204            return;
1205        }
1206        
1207        ICompletionProposal proposal= fFilteredProposals[index];
1208        if (proposal instanceof ICompletionProposalExtension2 && fViewer != null)
1209            ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
1210        
1211        fireSelectionEvent(proposal, smartToggle);
1212
1213        fLastProposal= proposal;
1214
1215        fProposalTable.setSelection(index);
1216        fProposalTable.showSelection();
1217        if (fAdditionalInfoController != null)
1218            fAdditionalInfoController.handleTableSelectionChanged();
1219    }
1220
1221    /**
1222     * Fires a selection event, see {@link ICompletionListener}.
1223     *
1224     * @param proposal the selected proposal, possibly <code>null</code>
1225     * @param smartToggle true if the smart toggle is on
1226     * @since 3.2
1227     */

1228    private void fireSelectionEvent(ICompletionProposal proposal, boolean smartToggle) {
1229        fContentAssistant.fireSelectionEvent(proposal, smartToggle);
1230    }
1231
1232    /**
1233     * Returns whether the given character is contained in the given array of
1234     * characters.
1235     *
1236     * @param characters the list of characters
1237     * @param c the character to look for in the list
1238     * @return <code>true</code> if character belongs to the list
1239     * @since 2.0
1240     */

1241    private boolean contains(char[] characters, char c) {
1242
1243        if (characters == null)
1244            return false;
1245
1246        for (int i= 0; i < characters.length; i++) {
1247            if (c == characters[i])
1248                return true;
1249        }
1250
1251        return false;
1252    }
1253
1254    /*
1255     * @see IEventConsumer#processEvent(VerifyEvent)
1256     */

1257    public void processEvent(VerifyEvent e) {
1258    }
1259
1260    /**
1261     * Filters the displayed proposal based on the given cursor position and the
1262     * offset of the original invocation of the content assistant.
1263     */

1264    private void filterProposals() {
1265        if (!fIsFilterPending) {
1266            fIsFilterPending= true;
1267            Control control= fContentAssistSubjectControlAdapter.getControl();
1268            control.getDisplay().asyncExec(fFilterRunnable);
1269        }
1270    }
1271
1272    /**
1273     * Computes the subset of already computed proposals that are still valid for
1274     * the given offset.
1275     *
1276     * @param offset the offset
1277     * @param event the merged document event
1278     * @return the set of filtered proposals
1279     * @since 3.0
1280     */

1281    private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event) {
1282
1283        if (offset == fInvocationOffset && event == null) {
1284            fIsFilteredSubset= false;
1285            return fComputedProposals;
1286        }
1287
1288        if (offset < fInvocationOffset) {
1289            fIsFilteredSubset= false;
1290            fInvocationOffset= offset;
1291            fComputedProposals= computeProposals(fInvocationOffset);
1292            return fComputedProposals;
1293        }
1294
1295        ICompletionProposal[] proposals;
1296        if (offset < fFilterOffset) {
1297            proposals= fComputedProposals;
1298            fIsFilteredSubset= false;
1299        } else {
1300            proposals= fFilteredProposals;
1301            fIsFilteredSubset= true;
1302        }
1303
1304        if (proposals == null) {
1305            fIsFilteredSubset= false;
1306            return null;
1307        }
1308
1309        IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1310        int length= proposals.length;
1311        List JavaDoc filtered= new ArrayList JavaDoc(length);
1312        for (int i= 0; i < length; i++) {
1313
1314            if (proposals[i] instanceof ICompletionProposalExtension2) {
1315
1316                ICompletionProposalExtension2 p= (ICompletionProposalExtension2) proposals[i];
1317                if (p.validate(document, offset, event))
1318                    filtered.add(p);
1319
1320            } else if (proposals[i] instanceof ICompletionProposalExtension) {
1321
1322                ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i];
1323                if (p.isValidFor(document, offset))
1324                    filtered.add(p);
1325
1326            } else {
1327                // restore original behavior
1328
fIsFilteredSubset= false;
1329                fInvocationOffset= offset;
1330                fComputedProposals= computeProposals(fInvocationOffset);
1331                return fComputedProposals;
1332            }
1333        }
1334
1335        return (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]);
1336    }
1337
1338    /**
1339     * Requests the proposal shell to take focus.
1340     *
1341     * @since 3.0
1342     */

1343    public void setFocus() {
1344        if (Helper.okToUse(fProposalShell)) {
1345            fProposalShell.setFocus();
1346        }
1347    }
1348    
1349    /**
1350     * Returns <code>true</code> if <code>proposal</code> should be auto-inserted,
1351     * <code>false</code> otherwise.
1352     *
1353     * @param proposal the single proposal that might be automatically inserted
1354     * @return <code>true</code> if <code>proposal</code> can be inserted automatically,
1355     * <code>false</code> otherwise
1356     * @since 3.1
1357     */

1358    private boolean canAutoInsert(ICompletionProposal proposal) {
1359        if (fContentAssistant.isAutoInserting()) {
1360            if (proposal instanceof ICompletionProposalExtension4) {
1361                ICompletionProposalExtension4 ext= (ICompletionProposalExtension4) proposal;
1362                return ext.isAutoInsertable();
1363            }
1364            return true; // default behavior before ICompletionProposalExtension4 was introduced
1365
}
1366        return false;
1367    }
1368
1369    /**
1370     * Completes the common prefix of all proposals directly in the code. If no
1371     * common prefix can be found, the proposal popup is shown.
1372     *
1373     * @return an error message if completion failed.
1374     * @since 3.0
1375     */

1376    public String JavaDoc incrementalComplete() {
1377        if (Helper.okToUse(fProposalShell) && fFilteredProposals != null) {
1378            if (fLastCompletionOffset == fFilterOffset) {
1379                handleRepeatedInvocation();
1380            } else {
1381                fLastCompletionOffset= fFilterOffset;
1382                completeCommonPrefix();
1383            }
1384        } else {
1385            final Control control= fContentAssistSubjectControlAdapter.getControl();
1386
1387            if (fKeyListener == null)
1388                fKeyListener= new ProposalSelectionListener();
1389
1390            if (!Helper.okToUse(fProposalShell) && !control.isDisposed())
1391                fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
1392
1393            BusyIndicator.showWhile(control.getDisplay(), new Runnable JavaDoc() {
1394                public void run() {
1395
1396                    fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
1397                    fFilterOffset= fInvocationOffset;
1398                    fLastCompletionOffset= fFilterOffset;
1399                    fFilteredProposals= computeProposals(fInvocationOffset);
1400
1401                    int count= (fFilteredProposals == null ? 0 : fFilteredProposals.length);
1402                    if (count == 0 && hideWhenNoProposals(false))
1403                        return;
1404                    
1405                    if (count == 1 && canAutoInsert(fFilteredProposals[0])) {
1406                        insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset);
1407                        hide();
1408                    } else {
1409                        ensureDocumentListenerInstalled();
1410                        if (count > 0 && completeCommonPrefix())
1411                            hide();
1412                        else {
1413                            fComputedProposals= fFilteredProposals;
1414                            createProposalSelector();
1415                            setProposals(fComputedProposals, false);
1416                            displayProposals();
1417                        }
1418                    }
1419                }
1420            });
1421        }
1422        return getErrorMessage();
1423    }
1424
1425    /**
1426     * Acts upon <code>fFilteredProposals</code>: if there is just one valid
1427     * proposal, it is inserted, otherwise, the common prefix of all proposals
1428     * is inserted into the document. If there is no common prefix, nothing
1429     * happens and <code>false</code> is returned.
1430     *
1431     * @return <code>true</code> if a single proposal was inserted and the
1432     * selector can be closed, <code>false</code> otherwise
1433     * @since 3.0
1434     */

1435    private boolean completeCommonPrefix() {
1436
1437        // 0: insert single proposals
1438
if (fFilteredProposals.length == 1) {
1439            if (canAutoInsert(fFilteredProposals[0])) {
1440                insertProposal(fFilteredProposals[0], (char) 0, 0, fFilterOffset);
1441                hide();
1442                return true;
1443            }
1444            return false;
1445        }
1446
1447        // 1: extract pre- and postfix from all remaining proposals
1448
IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1449
1450        // contains the common postfix in the case that there are any proposals matching our LHS
1451
StringBuffer JavaDoc rightCasePostfix= null;
1452        List JavaDoc rightCase= new ArrayList JavaDoc();
1453
1454        // whether to check for non-case compatible matches. This is initially true, and stays so
1455
// as long as there are i) no case-sensitive matches and ii) all proposals share the same
1456
// (although not corresponding with the document contents) common prefix.
1457
boolean checkWrongCase= true;
1458        // the prefix of all case insensitive matches. This differs from the document
1459
// contents and will be replaced.
1460
CharSequence JavaDoc wrongCasePrefix= null;
1461        int wrongCasePrefixStart= 0;
1462        // contains the common postfix of all case-insensitive matches
1463
StringBuffer JavaDoc wrongCasePostfix= null;
1464        List JavaDoc wrongCase= new ArrayList JavaDoc();
1465
1466        for (int i= 0; i < fFilteredProposals.length; i++) {
1467            ICompletionProposal proposal= fFilteredProposals[i];
1468            CharSequence JavaDoc insertion= getPrefixCompletion(proposal);
1469            int start= getPrefixCompletionOffset(proposal);
1470            try {
1471                int prefixLength= fFilterOffset - start;
1472                int relativeCompletionOffset= Math.min(insertion.length(), prefixLength);
1473                String JavaDoc prefix= document.get(start, prefixLength);
1474                if (insertion.toString().startsWith(prefix)) {
1475                    checkWrongCase= false;
1476                    rightCase.add(proposal);
1477                    CharSequence JavaDoc newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length());
1478                    if (rightCasePostfix == null)
1479                        rightCasePostfix= new StringBuffer JavaDoc(newPostfix.toString());
1480                    else
1481                        truncatePostfix(rightCasePostfix, newPostfix);
1482                } else if (checkWrongCase) {
1483                    CharSequence JavaDoc newPrefix= insertion.subSequence(0, relativeCompletionOffset);
1484                    if (isPrefixCompatible(wrongCasePrefix, wrongCasePrefixStart, newPrefix, start, document)) {
1485                        wrongCasePrefix= newPrefix;
1486                        wrongCasePrefixStart= start;
1487                        CharSequence JavaDoc newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length());
1488                        if (wrongCasePostfix == null)
1489                            wrongCasePostfix= new StringBuffer JavaDoc(newPostfix.toString());
1490                        else
1491                            truncatePostfix(wrongCasePostfix, newPostfix);
1492                        wrongCase.add(proposal);
1493                    } else {
1494                        checkWrongCase= false;
1495                    }
1496                }
1497            } catch (BadLocationException e2) {
1498                // bail out silently
1499
return false;
1500            }
1501
1502            if (rightCasePostfix != null && rightCasePostfix.length() == 0 && rightCase.size() > 1)
1503                return false;
1504        }
1505
1506        // 2: replace single proposals
1507

1508        if (rightCase.size() == 1) {
1509            ICompletionProposal proposal= (ICompletionProposal) rightCase.get(0);
1510            if (canAutoInsert(proposal) && rightCasePostfix.length() > 0) {
1511                insertProposal(proposal, (char) 0, 0, fInvocationOffset);
1512                hide();
1513                return true;
1514            }
1515            return false;
1516        } else if (checkWrongCase && wrongCase.size() == 1) {
1517            ICompletionProposal proposal= (ICompletionProposal) wrongCase.get(0);
1518            if (canAutoInsert(proposal)) {
1519                insertProposal(proposal, (char) 0, 0, fInvocationOffset);
1520                hide();
1521            return true;
1522            }
1523            return false;
1524        }
1525
1526        // 3: replace post- / prefixes
1527

1528        CharSequence JavaDoc prefix;
1529        if (checkWrongCase)
1530            prefix= wrongCasePrefix;
1531        else
1532            prefix= ""; //$NON-NLS-1$
1533

1534        CharSequence JavaDoc postfix;
1535        if (checkWrongCase)
1536            postfix= wrongCasePostfix;
1537        else
1538            postfix= rightCasePostfix;
1539
1540        if (prefix == null || postfix == null)
1541            return false;
1542
1543        try {
1544            // 4: check if parts of the postfix are already in the document
1545
int to= Math.min(document.getLength(), fFilterOffset + postfix.length());
1546            StringBuffer JavaDoc inDocument= new StringBuffer JavaDoc(document.get(fFilterOffset, to - fFilterOffset));
1547            truncatePostfix(inDocument, postfix);
1548
1549            // 5: replace and reveal
1550
document.replace(fFilterOffset - prefix.length(), prefix.length() + inDocument.length(), prefix.toString() + postfix.toString());
1551
1552            fContentAssistSubjectControlAdapter.setSelectedRange(fFilterOffset + postfix.length(), 0);
1553            fContentAssistSubjectControlAdapter.revealRange(fFilterOffset + postfix.length(), 0);
1554            fFilterOffset+= postfix.length();
1555            fLastCompletionOffset= fFilterOffset;
1556
1557            return false;
1558        } catch (BadLocationException e) {
1559            // ignore and return false
1560
return false;
1561        }
1562    }
1563
1564    /*
1565     * @since 3.1
1566     */

1567    private boolean isPrefixCompatible(CharSequence JavaDoc oneSequence, int oneOffset, CharSequence JavaDoc twoSequence, int twoOffset, IDocument document) throws BadLocationException {
1568        if (oneSequence == null || twoSequence == null)
1569            return true;
1570
1571        int min= Math.min(oneOffset, twoOffset);
1572        int oneEnd= oneOffset + oneSequence.length();
1573        int twoEnd= twoOffset + twoSequence.length();
1574
1575        String JavaDoc one= document.get(oneOffset, min - oneOffset) + oneSequence + document.get(oneEnd, Math.min(fFilterOffset, fFilterOffset - oneEnd));
1576        String JavaDoc two= document.get(twoOffset, min - twoOffset) + twoSequence + document.get(twoEnd, Math.min(fFilterOffset, fFilterOffset - twoEnd));
1577
1578        return one.equals(two);
1579    }
1580
1581    /**
1582     * Truncates <code>buffer</code> to the common prefix of <code>buffer</code>
1583     * and <code>sequence</code>.
1584     *
1585     * @param buffer the common postfix to truncate
1586     * @param sequence the characters to truncate with
1587     */

1588    private void truncatePostfix(StringBuffer JavaDoc buffer, CharSequence JavaDoc sequence) {
1589        // find common prefix
1590
int min= Math.min(buffer.length(), sequence.length());
1591        for (int c= 0; c < min; c++) {
1592            if (sequence.charAt(c) != buffer.charAt(c)) {
1593                buffer.delete(c, buffer.length());
1594                return;
1595            }
1596        }
1597
1598        // all equal up to minimum
1599
buffer.delete(min, buffer.length());
1600    }
1601
1602    /**
1603     * Extracts the completion offset of an <code>ICompletionProposal</code>. If
1604     * <code>proposal</code> is a <code>ICompletionProposalExtension3</code>, its
1605     * <code>getCompletionOffset</code> method is called, otherwise, the invocation
1606     * offset of this popup is shown.
1607     *
1608     * @param proposal the proposal to extract the offset from
1609     * @return the proposals completion offset, or <code>fInvocationOffset</code>
1610     * @since 3.1
1611     */

1612    private int getPrefixCompletionOffset(ICompletionProposal proposal) {
1613        if (proposal instanceof ICompletionProposalExtension3)
1614            return ((ICompletionProposalExtension3) proposal).getPrefixCompletionStart(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
1615        return fInvocationOffset;
1616    }
1617
1618    /**
1619     * Extracts the replacement string from an <code>ICompletionProposal</code>.
1620     * If <code>proposal</code> is a <code>ICompletionProposalExtension3</code>, its
1621     * <code>getCompletionText</code> method is called, otherwise, the display
1622     * string is used.
1623     *
1624     * @param proposal the proposal to extract the text from
1625     * @return the proposals completion text
1626     * @since 3.1
1627     */

1628    private CharSequence JavaDoc getPrefixCompletion(ICompletionProposal proposal) {
1629        CharSequence JavaDoc insertion= null;
1630        if (proposal instanceof ICompletionProposalExtension3)
1631            insertion= ((ICompletionProposalExtension3) proposal).getPrefixCompletionText(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
1632
1633        if (insertion == null)
1634            insertion= proposal.getDisplayString();
1635
1636        return insertion;
1637    }
1638    
1639    /**
1640     * Sets the message for the repetition affordance text at the bottom of the proposal. Only has
1641     * an effect if {@link ContentAssistant#isRepeatedInvocationMode()} returns <code>true</code>.
1642     *
1643     * @param message the new caption
1644     * @since 3.2
1645     */

1646    void setMessage(String JavaDoc message) {
1647        Assert.isNotNull(message);
1648        if (isActive() && fMessageText != null)
1649            fMessageText.setText(message + " "); //$NON-NLS-1$
1650
}
1651
1652    /**
1653     * Sets the text to be displayed if no proposals are available. Only has an effect if
1654     * {@link ContentAssistant#isShowEmptyList()} returns <code>true</code>.
1655     *
1656     * @param message the empty message
1657     * @since 3.2
1658     */

1659    void setEmptyMessage(String JavaDoc message) {
1660        Assert.isNotNull(message);
1661        fEmptyMessage= message;
1662    }
1663
1664    /**
1665     * Enables or disables showing of the caption line. See also {@link #setMessage(String)}.
1666     *
1667     * @param show
1668     * @since 3.2
1669     */

1670    public void setStatusLineVisible(boolean show) {
1671        if (!isActive() || show == (fMessageText != null))
1672            return; // nothing to do
1673

1674        if (show) {
1675            createMessageText();
1676        } else {
1677            fMessageText.dispose();
1678            fMessageText= null;
1679        }
1680        fProposalShell.layout();
1681    }
1682
1683    /**
1684     * Informs the popup that it is being placed above the caret line instead of below.
1685     *
1686     * @param above <code>true</code> if the location of the popup is above the caret line, <code>false</code> if it is below
1687     * @since 3.3
1688     */

1689    void switchedPositionToAbove(boolean above) {
1690        if (fAdditionalInfoController != null) {
1691            fAdditionalInfoController.setFallbackAnchors(new Anchor[] {
1692                    AbstractInformationControlManager.ANCHOR_RIGHT,
1693                    AbstractInformationControlManager.ANCHOR_LEFT,
1694                    above ? AbstractInformationControlManager.ANCHOR_TOP : AbstractInformationControlManager.ANCHOR_BOTTOM
1695            });
1696        }
1697    }
1698}
1699
Popular Tags