129
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2000, 2008 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 * Port to the D programming language:
|
|
12 * Frank Benoit <benoit@tionex.de>
|
|
13 *******************************************************************************/
|
|
14 module dwtx.jface.internal.text.link.contentassist.CompletionProposalPopup2;
|
|
15
|
|
16 import dwt.dwthelper.utils;
|
|
17
|
|
18
|
|
19 import java.util.ArrayList;
|
|
20 import java.util.List;
|
|
21
|
|
22 import dwt.DWT;
|
|
23 import dwt.custom.StyleRange;
|
|
24 import dwt.custom.StyledText;
|
|
25 import dwt.events.ControlEvent;
|
|
26 import dwt.events.ControlListener;
|
|
27 import dwt.events.DisposeEvent;
|
|
28 import dwt.events.DisposeListener;
|
|
29 import dwt.events.KeyEvent;
|
|
30 import dwt.events.KeyListener;
|
|
31 import dwt.events.SelectionEvent;
|
|
32 import dwt.events.SelectionListener;
|
|
33 import dwt.events.VerifyEvent;
|
|
34 import dwt.graphics.Color;
|
|
35 import dwt.graphics.Point;
|
|
36 import dwt.layout.GridData;
|
|
37 import dwt.layout.GridLayout;
|
|
38 import dwt.widgets.Control;
|
|
39 import dwt.widgets.Shell;
|
|
40 import dwt.widgets.Table;
|
|
41 import dwt.widgets.TableItem;
|
|
42 import dwtx.jface.internal.text.TableOwnerDrawSupport;
|
|
43 import dwtx.jface.resource.JFaceResources;
|
|
44 import dwtx.jface.text.BadLocationException;
|
|
45 import dwtx.jface.text.DocumentEvent;
|
|
46 import dwtx.jface.text.IDocument;
|
|
47 import dwtx.jface.text.IDocumentListener;
|
|
48 import dwtx.jface.text.IEditingSupport;
|
|
49 import dwtx.jface.text.IEditingSupportRegistry;
|
|
50 import dwtx.jface.text.IRegion;
|
|
51 import dwtx.jface.text.IRewriteTarget;
|
|
52 import dwtx.jface.text.ITextViewer;
|
|
53 import dwtx.jface.text.ITextViewerExtension;
|
|
54 import dwtx.jface.text.TextUtilities;
|
|
55 import dwtx.jface.text.contentassist.ICompletionProposal;
|
|
56 import dwtx.jface.text.contentassist.ICompletionProposalExtension;
|
|
57 import dwtx.jface.text.contentassist.ICompletionProposalExtension2;
|
|
58 import dwtx.jface.text.contentassist.ICompletionProposalExtension6;
|
|
59 import dwtx.jface.text.contentassist.IContextInformation;
|
|
60 import dwtx.jface.viewers.StyledString;
|
|
61
|
|
62
|
|
63
|
|
64 /**
|
|
65 * This class is used to present proposals to the user. If additional
|
|
66 * information exists for a proposal, then selecting that proposal
|
|
67 * will result in the information being displayed in a secondary
|
|
68 * window.
|
|
69 *
|
|
70 * @see dwtx.jface.text.contentassist.ICompletionProposal
|
|
71 * @see dwtx.jface.internal.text.link.contentassist.AdditionalInfoController2
|
|
72 */
|
|
73 class CompletionProposalPopup2 : IContentAssistListener2 {
|
|
74
|
|
75 /** The associated text viewer */
|
|
76 private ITextViewer fViewer;
|
|
77 /** The associated content assistant */
|
|
78 private ContentAssistant2 fContentAssistant;
|
|
79 /** The used additional info controller */
|
|
80 private AdditionalInfoController2 fAdditionalInfoController;
|
|
81 /** The closing strategy for this completion proposal popup */
|
|
82 private PopupCloser2 fPopupCloser= new PopupCloser2();
|
|
83 /** The popup shell */
|
|
84 private Shell fProposalShell;
|
|
85 /** The proposal table */
|
|
86 private Table fProposalTable;
|
|
87 /** Indicates whether a completion proposal is being inserted */
|
|
88 private bool fInserting= false;
|
|
89 /** The key listener to control navigation */
|
|
90 private KeyListener fKeyListener;
|
|
91 /** List of document events used for filtering proposals */
|
|
92 private List fDocumentEvents= new ArrayList();
|
|
93 /** Listener filling the document event queue */
|
|
94 private IDocumentListener fDocumentListener;
|
|
95 /** Reentrance count for <code>filterProposals</code> */
|
|
96 private long fInvocationCounter= 0;
|
|
97 /** The filter list of proposals */
|
|
98 private ICompletionProposal[] fFilteredProposals;
|
|
99 /** The computed list of proposals */
|
|
100 private ICompletionProposal[] fComputedProposals;
|
|
101 /** The offset for which the proposals have been computed */
|
|
102 private int fInvocationOffset;
|
|
103 /** The offset for which the computed proposals have been filtered */
|
|
104 private int fFilterOffset;
|
|
105 /** The default line delimiter of the viewer's widget */
|
|
106 private String fLineDelimiter;
|
|
107 /** The most recently selected proposal. */
|
|
108 private ICompletionProposal fLastProposal;
|
|
109 /**
|
|
110 * Tells whether colored labels support is enabled.
|
|
111 * Only valid while the popup is active.
|
|
112 *
|
|
113 * @since 3.4
|
|
114 */
|
|
115 private bool fIsColoredLabelsSupportEnabled= false;
|
|
116
|
|
117 private final IEditingSupport fFocusEditingSupport= new IEditingSupport() {
|
|
118
|
|
119 public bool isOriginator(DocumentEvent event, IRegion focus) {
|
|
120 return false;
|
|
121 }
|
|
122
|
|
123 public bool ownsFocusShell() {
|
|
124 return Helper2.okToUse(fProposalShell) && fProposalShell.isFocusControl()
|
|
125 || Helper2.okToUse(fProposalTable) && fProposalTable.isFocusControl();
|
|
126 }
|
|
127
|
|
128 };
|
|
129 private final IEditingSupport fModificationEditingSupport= new IEditingSupport() {
|
|
130
|
|
131 public bool isOriginator(DocumentEvent event, IRegion focus) {
|
|
132 if (fViewer !is null) {
|
|
133 Point selection= fViewer.getSelectedRange();
|
|
134 return selection.x <= focus.getOffset() + focus.getLength() && selection.x + selection.y >= focus.getOffset();
|
|
135 }
|
|
136 return false;
|
|
137 }
|
|
138
|
|
139 public bool ownsFocusShell() {
|
|
140 return false;
|
|
141 }
|
|
142
|
|
143 };
|
|
144
|
|
145
|
|
146 /**
|
|
147 * Creates a new completion proposal popup for the given elements.
|
|
148 *
|
|
149 * @param contentAssistant the content assistant feeding this popup
|
|
150 * @param viewer the viewer on top of which this popup appears
|
|
151 * @param infoController the info control collaborating with this popup
|
|
152 * @since 2.0
|
|
153 */
|
|
154 public CompletionProposalPopup2(ContentAssistant2 contentAssistant, ITextViewer viewer, AdditionalInfoController2 infoController) {
|
|
155 fContentAssistant= contentAssistant;
|
|
156 fViewer= viewer;
|
|
157 fAdditionalInfoController= infoController;
|
|
158 }
|
|
159
|
|
160 /**
|
|
161 * Computes and presents completion proposals. The flag indicates whether this call has
|
|
162 * be made out of an auto activation context.
|
|
163 *
|
|
164 * @param autoActivated <code>true</code> if auto activation context
|
|
165 * @return an error message or <code>null</code> in case of no error
|
|
166 */
|
|
167 public String showProposals(final bool autoActivated) {
|
|
168
|
|
169 if (fKeyListener is null) {
|
|
170 fKeyListener= new KeyListener() {
|
|
171 public void keyPressed(KeyEvent e) {
|
|
172 if (!Helper2.okToUse(fProposalShell))
|
|
173 return;
|
|
174
|
|
175 if (e.character is 0 && e.keyCode is DWT.CTRL) {
|
|
176 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
|
|
177 int index= fProposalTable.getSelectionIndex();
|
|
178 if (index >= 0)
|
|
179 selectProposal(index, true);
|
|
180 }
|
|
181 }
|
|
182
|
|
183 public void keyReleased(KeyEvent e) {
|
|
184 if (!Helper2.okToUse(fProposalShell))
|
|
185 return;
|
|
186
|
|
187 if (e.character is 0 && e.keyCode is DWT.CTRL) {
|
|
188 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
|
|
189 int index= fProposalTable.getSelectionIndex();
|
|
190 if (index >= 0)
|
|
191 selectProposal(index, false);
|
|
192 }
|
|
193 }
|
|
194 };
|
|
195 }
|
|
196
|
|
197 final StyledText styledText= fViewer.getTextWidget();
|
|
198 if (styledText !is null && !styledText.isDisposed())
|
|
199 styledText.addKeyListener(fKeyListener);
|
|
200
|
|
201 // BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() {
|
|
202 // public void run() {
|
|
203
|
|
204 fInvocationOffset= fViewer.getSelectedRange().x;
|
|
205 // lazily compute proposals
|
|
206 // if (fComputedProposals is null) fComputedProposals= computeProposals(fContentAssistant.getCompletionPosition());
|
|
207 fComputedProposals= computeProposals(fInvocationOffset);
|
|
208
|
|
209 int count= (fComputedProposals is null ? 0 : fComputedProposals.length);
|
|
210 if (count is 0) {
|
|
211
|
|
212 if (!autoActivated)
|
|
213 styledText.getDisplay().beep();
|
|
214
|
|
215 } else {
|
|
216
|
|
217 if (count is 1 && !autoActivated && fContentAssistant.isAutoInserting())
|
|
218
|
|
219 insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
|
|
220
|
|
221 else {
|
|
222
|
|
223 if (fLineDelimiter is null)
|
|
224 fLineDelimiter= styledText.getLineDelimiter();
|
|
225
|
|
226 createProposalSelector();
|
|
227 setProposals(fComputedProposals);
|
|
228 resizeProposalSelector(true);
|
|
229 displayProposals();
|
|
230 }
|
|
231 }
|
|
232 // }
|
|
233 // });
|
|
234
|
|
235 return getErrorMessage();
|
|
236 }
|
|
237
|
|
238 /**
|
|
239 * Returns the completion proposal available at the given offset of the
|
|
240 * viewer's document. Delegates the work to the content assistant.
|
|
241 *
|
|
242 * @param offset the offset
|
|
243 * @return the completion proposals available at this offset
|
|
244 */
|
|
245 private ICompletionProposal[] computeProposals(int offset) {
|
|
246 return fContentAssistant.computeCompletionProposals(fViewer, offset);
|
|
247 }
|
|
248
|
|
249 /**
|
|
250 * Returns the error message.
|
|
251 *
|
|
252 * @return the error message
|
|
253 */
|
|
254 private String getErrorMessage() {
|
|
255 return fContentAssistant.getErrorMessage();
|
|
256 }
|
|
257
|
|
258 /**
|
|
259 * Creates the proposal selector.
|
|
260 */
|
|
261 private void createProposalSelector() {
|
|
262 if (Helper2.okToUse(fProposalShell))
|
|
263 return;
|
|
264
|
|
265 Control control= fViewer.getTextWidget();
|
|
266 fProposalShell= new Shell(control.getShell(), DWT.ON_TOP);
|
|
267 // fProposalShell= new Shell(control.getShell(), DWT.ON_TOP | DWT.RESIZE );
|
|
268 fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL);
|
|
269 // fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL);
|
|
270
|
|
271
|
|
272 fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled();
|
|
273 if (fIsColoredLabelsSupportEnabled)
|
|
274 TableOwnerDrawSupport.install(fProposalTable);
|
|
275
|
|
276 fProposalTable.setLocation(0, 0);
|
|
277 if (fAdditionalInfoController !is null)
|
|
278 fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
|
|
279
|
|
280 GridLayout layout= new GridLayout();
|
|
281 layout.marginWidth= 0;
|
|
282 layout.marginHeight= 0;
|
|
283 fProposalShell.setLayout(layout);
|
|
284
|
|
285 GridData data= new GridData(GridData.FILL_BOTH);
|
|
286 fProposalTable.setLayoutData(data);
|
|
287
|
|
288 fProposalShell.pack();
|
|
289
|
|
290 // set location
|
|
291 Point currentLocation= fProposalShell.getLocation();
|
|
292 Point newLocation= getLocation();
|
|
293 if ((newLocation.x < currentLocation.x && newLocation.y is currentLocation.y) || newLocation.y < currentLocation.y)
|
|
294 fProposalShell.setLocation(newLocation);
|
|
295
|
|
296 if (fAdditionalInfoController !is null) {
|
|
297 fProposalShell.addControlListener(new ControlListener() {
|
|
298
|
|
299 public void controlMoved(ControlEvent e) {}
|
|
300
|
|
301 public void controlResized(ControlEvent e) {
|
|
302 // resets the cached resize constraints
|
|
303 fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
|
|
304 }
|
|
305 });
|
|
306 }
|
|
307
|
|
308 fProposalShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_BLACK));
|
|
309
|
|
310 Color c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_BACKGROUND);
|
|
311 fProposalTable.setBackground(c);
|
|
312
|
|
313 c= control.getDisplay().getSystemColor(DWT.COLOR_INFO_FOREGROUND);
|
|
314 fProposalTable.setForeground(c);
|
|
315
|
|
316 fProposalTable.addSelectionListener(new SelectionListener() {
|
|
317
|
|
318 public void widgetSelected(SelectionEvent e) {}
|
|
319
|
|
320 public void widgetDefaultSelected(SelectionEvent e) {
|
|
321 selectProposalWithMask(e.stateMask);
|
|
322 }
|
|
323 });
|
|
324
|
|
325 fPopupCloser.install(fContentAssistant, fProposalTable);
|
|
326
|
|
327 fProposalShell.addDisposeListener(new DisposeListener() {
|
|
328 public void widgetDisposed(DisposeEvent e) {
|
|
329 unregister(); // but don't dispose the shell, since we're being called from its disposal event!
|
|
330 }
|
|
331 });
|
|
332
|
|
333 fProposalTable.setHeaderVisible(false);
|
|
334 fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant2.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
|
|
335 }
|
|
336
|
|
337 /**
|
|
338 * Returns the proposal selected in the proposal selector.
|
|
339 *
|
|
340 * @return the selected proposal
|
|
341 * @since 2.0
|
|
342 */
|
|
343 private ICompletionProposal getSelectedProposal() {
|
|
344 int i= fProposalTable.getSelectionIndex();
|
|
345 if (i < 0 || i >= fFilteredProposals.length)
|
|
346 return null;
|
|
347 return fFilteredProposals[i];
|
|
348 }
|
|
349
|
|
350 /**
|
|
351 * Takes the selected proposal and applies it.
|
|
352 *
|
|
353 * @param stateMask the state mask
|
|
354 * @since 2.1
|
|
355 */
|
|
356 private void selectProposalWithMask(int stateMask) {
|
|
357 ICompletionProposal p= getSelectedProposal();
|
|
358 hide();
|
|
359 if (p !is null)
|
|
360 insertProposal(p, (char) 0, stateMask, fViewer.getSelectedRange().x);
|
|
361 }
|
|
362
|
|
363 /**
|
|
364 * Applies the given proposal at the given offset. The given character is the
|
|
365 * one that triggered the insertion of this proposal.
|
|
366 *
|
|
367 * @param p the completion proposal
|
|
368 * @param trigger the trigger character
|
|
369 * @param stateMask the state mask of the keyboard event triggering the insertion
|
|
370 * @param offset the offset
|
|
371 * @since 2.1
|
|
372 */
|
|
373 private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) {
|
|
374
|
|
375 fInserting= true;
|
|
376 IRewriteTarget target= null;
|
|
377 IEditingSupportRegistry registry= null;
|
|
378
|
|
379 try {
|
|
380
|
|
381 IDocument document= fViewer.getDocument();
|
|
382
|
|
383 if (fViewer instanceof ITextViewerExtension) {
|
|
384 ITextViewerExtension extension= (ITextViewerExtension) fViewer;
|
|
385 target= extension.getRewriteTarget();
|
|
386 }
|
|
387
|
|
388 if (target !is null)
|
|
389 target.beginCompoundChange();
|
|
390
|
|
391 if (fViewer instanceof IEditingSupportRegistry) {
|
|
392 registry= (IEditingSupportRegistry) fViewer;
|
|
393 registry.register(fModificationEditingSupport);
|
|
394 }
|
|
395
|
|
396 if (p instanceof ICompletionProposalExtension2) {
|
|
397 ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
|
|
398 e.apply(fViewer, trigger, stateMask, offset);
|
|
399 } else if (p instanceof ICompletionProposalExtension) {
|
|
400 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
|
|
401 e.apply(document, trigger, offset);
|
|
402 } else {
|
|
403 p.apply(document);
|
|
404 }
|
|
405
|
|
406 Point selection= p.getSelection(document);
|
|
407 if (selection !is null) {
|
|
408 fViewer.setSelectedRange(selection.x, selection.y);
|
|
409 fViewer.revealRange(selection.x, selection.y);
|
|
410 }
|
|
411
|
|
412 IContextInformation info= p.getContextInformation();
|
|
413 if (info !is null) {
|
|
414
|
|
415 int position;
|
|
416 if (p instanceof ICompletionProposalExtension) {
|
|
417 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
|
|
418 position= e.getContextInformationPosition();
|
|
419 } else {
|
|
420 if (selection is null)
|
|
421 selection= fViewer.getSelectedRange();
|
|
422 position= selection.x + selection.y;
|
|
423 }
|
|
424
|
|
425 fContentAssistant.showContextInformation(info, position);
|
|
426 }
|
|
427
|
|
428 fContentAssistant.fireProposalChosen(p);
|
|
429
|
|
430 } finally {
|
|
431 if (target !is null)
|
|
432 target.endCompoundChange();
|
|
433
|
|
434 if (registry !is null)
|
|
435 registry.unregister(fModificationEditingSupport);
|
|
436
|
|
437 fInserting= false;
|
|
438 }
|
|
439 }
|
|
440
|
|
441 /**
|
|
442 * Returns whether this popup has the focus.
|
|
443 *
|
|
444 * @return <code>true</code> if the popup has the focus
|
|
445 */
|
|
446 public bool hasFocus() {
|
|
447 if (Helper2.okToUse(fProposalShell))
|
|
448 return (fProposalShell.isFocusControl() || fProposalTable.isFocusControl());
|
|
449
|
|
450 return false;
|
|
451 }
|
|
452
|
|
453 /**
|
|
454 * Hides this popup.
|
|
455 */
|
|
456 public void hide() {
|
|
457
|
|
458 unregister();
|
|
459
|
|
460 if (fViewer instanceof IEditingSupportRegistry) {
|
|
461 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
|
|
462 registry.unregister(fFocusEditingSupport);
|
|
463 }
|
|
464
|
|
465 if (Helper2.okToUse(fProposalShell)) {
|
|
466 fContentAssistant.removeContentAssistListener(this, ContentAssistant2.PROPOSAL_SELECTOR);
|
|
467
|
|
468 fPopupCloser.uninstall();
|
|
469 // see bug 47511: setVisible may run the event loop on GTK
|
|
470 // and trigger a rentrant call - have to make sure we don't
|
|
471 // dispose another shell that was already brought up in a
|
|
472 // reentrant call when calling setVisible()
|
|
473 Shell tempShell= fProposalShell;
|
|
474 fProposalShell= null;
|
|
475 tempShell.setVisible(false);
|
|
476 tempShell.dispose();
|
|
477 }
|
|
478 }
|
|
479
|
|
480 private void unregister() {
|
|
481 if (fDocumentListener !is null) {
|
|
482 IDocument document= fViewer.getDocument();
|
|
483 if (document !is null)
|
|
484 document.removeDocumentListener(fDocumentListener);
|
|
485 fDocumentListener= null;
|
|
486 }
|
|
487 fDocumentEvents.clear();
|
|
488
|
|
489 StyledText styledText= fViewer.getTextWidget();
|
|
490 if (fKeyListener !is null && styledText !is null && !styledText.isDisposed())
|
|
491 styledText.removeKeyListener(fKeyListener);
|
|
492
|
|
493 if (fLastProposal !is null) {
|
|
494 if (fLastProposal instanceof ICompletionProposalExtension2) {
|
|
495 ICompletionProposalExtension2 extension= (ICompletionProposalExtension2) fLastProposal;
|
|
496 extension.unselected(fViewer);
|
|
497 }
|
|
498
|
|
499 fLastProposal= null;
|
|
500 }
|
|
501
|
|
502 fFilteredProposals= null;
|
|
503
|
|
504 fContentAssistant.possibleCompletionsClosed();
|
|
505 }
|
|
506
|
|
507 /**
|
|
508 *Returns whether this popup is active. It is active if the propsal selector is visible.
|
|
509 *
|
|
510 * @return <code>true</code> if this popup is active
|
|
511 */
|
|
512 public bool isActive() {
|
|
513 return fProposalShell !is null && !fProposalShell.isDisposed();
|
|
514 }
|
|
515
|
|
516 /**
|
|
517 * Initializes the proposal selector with these given proposals.
|
|
518 *
|
|
519 * @param proposals the proposals
|
|
520 */
|
|
521 private void setProposals(ICompletionProposal[] proposals) {
|
|
522 if (Helper2.okToUse(fProposalTable)) {
|
|
523
|
|
524 ICompletionProposal oldProposal= getSelectedProposal();
|
|
525 if (oldProposal instanceof ICompletionProposalExtension2)
|
|
526 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
|
|
527
|
|
528 fFilteredProposals= proposals;
|
|
529
|
|
530 fProposalTable.setRedraw(false);
|
|
531 fProposalTable.removeAll();
|
|
532
|
|
533 Point selection= fViewer.getSelectedRange();
|
|
534 int endOffset;
|
|
535 endOffset= selection.x + selection.y;
|
|
536 IDocument document= fViewer.getDocument();
|
|
537 bool validate= false;
|
|
538 if (selection.y !is 0 && document !is null) validate= true;
|
|
539 int selectionIndex= 0;
|
|
540
|
|
541 TableItem item;
|
|
542 ICompletionProposal p;
|
|
543 for (int i= 0; i < proposals.length; i++) {
|
|
544 p= proposals[i];
|
|
545 item= new TableItem(fProposalTable, DWT.NULL);
|
|
546 if (p.getImage() !is null)
|
|
547 item.setImage(p.getImage());
|
|
548
|
|
549 String displayString;
|
|
550 StyleRange[] styleRanges= null;
|
|
551 if (fIsColoredLabelsSupportEnabled && p instanceof ICompletionProposalExtension6) {
|
|
552 StyledString styledString= ((ICompletionProposalExtension6)p).getStyledDisplayString();
|
|
553 displayString= styledString.getString();
|
|
554 styleRanges= styledString.getStyleRanges();
|
|
555 } else
|
|
556 displayString= p.getDisplayString();
|
|
557
|
|
558 item.setText(displayString);
|
|
559 if (fIsColoredLabelsSupportEnabled)
|
|
560 TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges);
|
|
561
|
|
562 item.setData(p);
|
|
563
|
|
564 if (validate && validateProposal(document, p, endOffset, null)) {
|
|
565 selectionIndex= i;
|
|
566 validate= false;
|
|
567 }
|
|
568 }
|
|
569
|
|
570 resizeProposalSelector(false);
|
|
571
|
|
572 selectProposal(selectionIndex, false);
|
|
573 fProposalTable.setRedraw(true);
|
|
574 }
|
|
575 }
|
|
576
|
|
577 private void resizeProposalSelector(bool adjustWidth) {
|
|
578 // in order to fill in the table items so size computation works correctly
|
|
579 // will cause flicker, though
|
|
580 fProposalTable.setRedraw(true);
|
|
581
|
|
582 int width= adjustWidth ? DWT.DEFAULT : ((GridData)fProposalTable.getLayoutData()).widthHint;
|
|
583 Point size= fProposalTable.computeSize(width, DWT.DEFAULT, true);
|
|
584
|
|
585 GridData data= new GridData(GridData.FILL_BOTH);
|
|
586 data.widthHint= adjustWidth ? Math.min(size.x, 300) : width;
|
|
587 data.heightHint= Math.min(getTableHeightHint(fProposalTable, fProposalTable.getItemCount()), getTableHeightHint(fProposalTable, 10));
|
|
588 fProposalTable.setLayoutData(data);
|
|
589
|
|
590 fProposalShell.layout(true);
|
|
591 fProposalShell.pack();
|
|
592
|
|
593 if (adjustWidth) {
|
|
594 fProposalShell.setLocation(getLocation());
|
|
595 }
|
|
596 }
|
|
597
|
|
598 /**
|
|
599 * Computes the table hight hint for <code>table</code>.
|
|
600 *
|
|
601 * @param table the table to compute the height for
|
|
602 * @param rows the number of rows to compute the height for
|
|
603 * @return the height hint for <code>table</code>
|
|
604 */
|
|
605 private int getTableHeightHint(Table table, int rows) {
|
|
606 if (table.getFont().equals(JFaceResources.getDefaultFont()))
|
|
607 table.setFont(JFaceResources.getDialogFont());
|
|
608 int result= table.getItemHeight() * rows;
|
|
609 if (table.getLinesVisible())
|
|
610 result+= table.getGridLineWidth() * (rows - 1);
|
|
611
|
|
612 // TODO adjust to correct size. +4 works on windows, but not others
|
|
613 // return result + 4;
|
|
614 return result;
|
|
615 }
|
|
616
|
|
617 private bool validateProposal(IDocument document, ICompletionProposal p, int offset, DocumentEvent event) {
|
|
618 // detect selected
|
|
619 if (p instanceof ICompletionProposalExtension2) {
|
|
620 ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
|
|
621 if (e.validate(document, offset, event))
|
|
622 return true;
|
|
623 } else if (p instanceof ICompletionProposalExtension) {
|
|
624 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
|
|
625 if (e.isValidFor(document, offset))
|
|
626 return true;
|
|
627 }
|
|
628 return false;
|
|
629 }
|
|
630
|
|
631 /**
|
|
632 * Returns the graphical location at which this popup should be made visible.
|
|
633 *
|
|
634 * @return the location of this popup
|
|
635 */
|
|
636 private Point getLocation() {
|
|
637 StyledText text= fViewer.getTextWidget();
|
|
638 Point selection= text.getSelection();
|
|
639 Point p= text.getLocationAtOffset(selection.x);
|
|
640 p.x -= fProposalShell.getBorderWidth();
|
|
641 if (p.x < 0) p.x= 0;
|
|
642 if (p.y < 0) p.y= 0;
|
|
643 p= new Point(p.x, p.y + text.getLineHeight(selection.x));
|
|
644 p= text.toDisplay(p);
|
|
645 return p;
|
|
646 }
|
|
647
|
|
648 /**
|
|
649 *Displays this popup and install the additional info controller, so that additional info
|
|
650 * is displayed when a proposal is selected and additional info is available.
|
|
651 */
|
|
652 private void displayProposals() {
|
|
653 if (fContentAssistant.addContentAssistListener(this, ContentAssistant2.PROPOSAL_SELECTOR)) {
|
|
654
|
|
655 if (fDocumentListener is null)
|
|
656 fDocumentListener= new IDocumentListener() {
|
|
657 public void documentAboutToBeChanged(DocumentEvent event) {
|
|
658 if (!fInserting)
|
|
659 fDocumentEvents.add(event);
|
|
660 }
|
|
661
|
|
662 public void documentChanged(DocumentEvent event) {
|
|
663 if (!fInserting)
|
|
664 filterProposals();
|
|
665 }
|
|
666 };
|
|
667 IDocument document= fViewer.getDocument();
|
|
668 if (document !is null)
|
|
669 document.addDocumentListener(fDocumentListener);
|
|
670
|
|
671
|
|
672 if (fViewer instanceof IEditingSupportRegistry) {
|
|
673 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
|
|
674 registry.register(fFocusEditingSupport);
|
|
675 }
|
|
676
|
|
677 fProposalShell.setVisible(true);
|
|
678 // see bug 47511: setVisible may run the event loop on GTK
|
|
679 // and trigger a rentrant call - have to check whether we are still
|
|
680 // visible
|
|
681 if (!Helper2.okToUse(fProposalShell))
|
|
682 return;
|
|
683
|
|
684
|
|
685 if (fAdditionalInfoController !is null) {
|
|
686 fAdditionalInfoController.install(fProposalTable);
|
|
687 fAdditionalInfoController.handleTableSelectionChanged();
|
|
688 }
|
|
689 }
|
|
690 }
|
|
691
|
|
692 /*
|
|
693 * @see IContentAssistListener#verifyKey(VerifyEvent)
|
|
694 */
|
|
695 public bool verifyKey(VerifyEvent e) {
|
|
696 if (!Helper2.okToUse(fProposalShell))
|
|
697 return true;
|
|
698
|
|
699 char key= e.character;
|
|
700 if (key is 0) {
|
|
701 int newSelection= fProposalTable.getSelectionIndex();
|
|
702 int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
|
|
703 bool smartToggle= false;
|
|
704 switch (e.keyCode) {
|
|
705
|
|
706 case DWT.ARROW_LEFT :
|
|
707 case DWT.ARROW_RIGHT :
|
|
708 filterProposals();
|
|
709 return true;
|
|
710
|
|
711 case DWT.ARROW_UP :
|
|
712 newSelection -= 1;
|
|
713 if (newSelection < 0)
|
|
714 newSelection= fProposalTable.getItemCount() - 1;
|
|
715 break;
|
|
716
|
|
717 case DWT.ARROW_DOWN :
|
|
718 newSelection += 1;
|
|
719 if (newSelection > fProposalTable.getItemCount() - 1)
|
|
720 newSelection= 0;
|
|
721 break;
|
|
722
|
|
723 case DWT.PAGE_DOWN :
|
|
724 newSelection += visibleRows;
|
|
725 if (newSelection >= fProposalTable.getItemCount())
|
|
726 newSelection= fProposalTable.getItemCount() - 1;
|
|
727 break;
|
|
728
|
|
729 case DWT.PAGE_UP :
|
|
730 newSelection -= visibleRows;
|
|
731 if (newSelection < 0)
|
|
732 newSelection= 0;
|
|
733 break;
|
|
734
|
|
735 case DWT.HOME :
|
|
736 newSelection= 0;
|
|
737 break;
|
|
738
|
|
739 case DWT.END :
|
|
740 newSelection= fProposalTable.getItemCount() - 1;
|
|
741 break;
|
|
742
|
|
743 default :
|
|
744 if (e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
|
|
745 hide();
|
|
746 return true;
|
|
747 }
|
|
748
|
|
749 selectProposal(newSelection, smartToggle);
|
|
750
|
|
751 e.doit= false;
|
|
752 return false;
|
|
753
|
|
754 }
|
|
755
|
|
756 // key !is 0
|
|
757 switch (key) {
|
|
758 case 0x1B: // Esc
|
|
759 e.doit= false;
|
|
760 hide();
|
|
761 break;
|
|
762
|
|
763 case '\n': // Ctrl-Enter on w2k
|
|
764 case '\r': // Enter
|
|
765 if ((e.stateMask & DWT.CTRL) is 0) {
|
|
766 e.doit= false;
|
|
767 selectProposalWithMask(e.stateMask);
|
|
768 }
|
|
769 break;
|
|
770
|
|
771 // in linked mode: hide popup
|
|
772 // plus: don't invalidate the event in order to give LinkedUI a chance to handle it
|
|
773 case '\t':
|
|
774 // hide();
|
|
775 break;
|
|
776
|
|
777 default:
|
|
778 ICompletionProposal p= getSelectedProposal();
|
|
779 if (p instanceof ICompletionProposalExtension) {
|
|
780 ICompletionProposalExtension t= (ICompletionProposalExtension) p;
|
|
781 char[] triggers= t.getTriggerCharacters();
|
|
782 if (contains(triggers, key)) {
|
|
783 hide();
|
|
784 if (key is ';') {
|
|
785 e.doit= true;
|
|
786 insertProposal(p, (char) 0, e.stateMask, fViewer.getSelectedRange().x);
|
|
787 } else {
|
|
788 e.doit= false;
|
|
789 insertProposal(p, key, e.stateMask, fViewer.getSelectedRange().x);
|
|
790 }
|
|
791 }
|
|
792 }
|
|
793 }
|
|
794
|
|
795 return true;
|
|
796 }
|
|
797
|
|
798 /**
|
|
799 * Selects the entry with the given index in the proposal selector and feeds
|
|
800 * the selection to the additional info controller.
|
|
801 *
|
|
802 * @param index the index in the list
|
|
803 * @param smartToggle <code>true</code> if the smart toogle key has been pressed
|
|
804 * @since 2.1
|
|
805 */
|
|
806 private void selectProposal(int index, bool smartToggle) {
|
|
807
|
|
808 ICompletionProposal oldProposal= getSelectedProposal();
|
|
809 if (oldProposal instanceof ICompletionProposalExtension2)
|
|
810 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
|
|
811
|
|
812 ICompletionProposal proposal= fFilteredProposals[index];
|
|
813 if (proposal instanceof ICompletionProposalExtension2)
|
|
814 ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
|
|
815
|
|
816 fLastProposal= proposal;
|
|
817
|
|
818 fProposalTable.setSelection(index);
|
|
819 fProposalTable.showSelection();
|
|
820 if (fAdditionalInfoController !is null)
|
|
821 fAdditionalInfoController.handleTableSelectionChanged();
|
|
822 }
|
|
823
|
|
824 /**
|
|
825 * Returns whether the given character is contained in the given array of
|
|
826 * characters.
|
|
827 *
|
|
828 * @param characters the list of characters
|
|
829 * @param c the character to look for in the list
|
|
830 * @return <code>true</code> if character belongs to the list
|
|
831 * @since 2.0
|
|
832 */
|
|
833 private bool contains(char[] characters, char c) {
|
|
834
|
|
835 if (characters is null)
|
|
836 return false;
|
|
837
|
|
838 for (int i= 0; i < characters.length; i++) {
|
|
839 if (c is characters[i])
|
|
840 return true;
|
|
841 }
|
|
842
|
|
843 return false;
|
|
844 }
|
|
845
|
|
846 /*
|
|
847 * @see IEventConsumer#processEvent(VerifyEvent)
|
|
848 */
|
|
849 public void processEvent(VerifyEvent e) {
|
|
850 }
|
|
851
|
|
852 /**
|
|
853 * Filters the displayed proposal based on the given cursor position and the
|
|
854 * offset of the original invocation of the content assistant.
|
|
855 */
|
|
856 private void filterProposals() {
|
|
857 ++ fInvocationCounter;
|
|
858 Control control= fViewer.getTextWidget();
|
|
859 control.getDisplay().asyncExec(new Runnable() {
|
|
860 long fCounter= fInvocationCounter;
|
|
861 public void run() {
|
|
862
|
|
863 if (fCounter !is fInvocationCounter) return;
|
|
864
|
|
865 int offset= fViewer.getSelectedRange().x;
|
|
866 ICompletionProposal[] proposals= null;
|
|
867 try {
|
|
868 if (offset > -1) {
|
|
869 DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents);
|
|
870 proposals= computeFilteredProposals(offset, event);
|
|
871 }
|
|
872 } catch (BadLocationException x) {
|
|
873 } finally {
|
|
874 fDocumentEvents.clear();
|
|
875 }
|
|
876 fFilterOffset= offset;
|
|
877
|
|
878 if (proposals !is null && proposals.length > 0)
|
|
879 setProposals(proposals);
|
|
880 else
|
|
881 hide();
|
|
882 }
|
|
883 });
|
|
884 }
|
|
885
|
|
886 /**
|
|
887 * Computes the subset of already computed propsals that are still valid for
|
|
888 * the given offset.
|
|
889 *
|
|
890 * @param offset the offset
|
|
891 * @param event the merged document event
|
|
892 * @return the set of filtered proposals
|
|
893 * @since 2.0
|
|
894 */
|
|
895 private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event) {
|
|
896
|
|
897 if (offset is fInvocationOffset && event is null)
|
|
898 return fComputedProposals;
|
|
899
|
|
900 if (offset < fInvocationOffset) {
|
|
901 return null;
|
|
902 }
|
|
903
|
|
904 ICompletionProposal[] proposals= fComputedProposals;
|
|
905 if (offset > fFilterOffset)
|
|
906 proposals= fFilteredProposals;
|
|
907
|
|
908 if (proposals is null)
|
|
909 return null;
|
|
910
|
|
911 IDocument document= fViewer.getDocument();
|
|
912 int length= proposals.length;
|
|
913 List filtered= new ArrayList(length);
|
|
914 for (int i= 0; i < length; i++) {
|
|
915
|
|
916 if (proposals[i] instanceof ICompletionProposalExtension2) {
|
|
917
|
|
918 ICompletionProposalExtension2 p= (ICompletionProposalExtension2) proposals[i];
|
|
919 if (p.validate(document, offset, event))
|
|
920 filtered.add(p);
|
|
921
|
|
922 } else if (proposals[i] instanceof ICompletionProposalExtension) {
|
|
923
|
|
924 ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i];
|
|
925 if (p.isValidFor(document, offset))
|
|
926 filtered.add(p);
|
|
927
|
|
928 } else {
|
|
929 // restore original behavior
|
|
930 fInvocationOffset= offset;
|
|
931 fComputedProposals= computeProposals(fInvocationOffset);
|
|
932 return fComputedProposals;
|
|
933 }
|
|
934 }
|
|
935
|
|
936 ICompletionProposal[] p= new ICompletionProposal[filtered.size()];
|
|
937 filtered.toArray(p);
|
|
938 return p;
|
|
939 }
|
|
940
|
|
941 /**
|
|
942 * Requests the proposal shell to take focus.
|
|
943 *
|
|
944 * @since 3.0
|
|
945 */
|
|
946 public void setFocus() {
|
|
947 if (Helper2.okToUse(fProposalShell))
|
|
948 fProposalShell.setFocus();
|
|
949 }
|
|
950 }
|