Mercurial > projects > dwt-addons
annotate dwtx/jface/internal/text/link/contentassist/CompletionProposalPopup2.d @ 133:7d818bd32d63
Fix ctors to this with gvim regexp
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 24 Aug 2008 01:29:22 +0200 |
parents | c4fb132a086c |
children | 51e6e63f930e |
rev | line source |
---|---|
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 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
165 public this(ContentAssistant2 contentAssistant, ITextViewer viewer, AdditionalInfoController2 infoController) { |
129 | 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 } |