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