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