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 }