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