Mercurial > projects > dwt-addons
comparison dwtx/jface/text/contentassist/CompletionProposalPopup.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
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.text.contentassist.CompletionProposalPopup; | |
15 | |
16 import dwt.dwthelper.utils; | |
17 | |
18 import java.util.ArrayList; | |
19 import java.util.List; | |
20 | |
21 import dwt.DWT; | |
22 import dwt.custom.BusyIndicator; | |
23 import dwt.custom.StyleRange; | |
24 import dwt.events.ControlEvent; | |
25 import dwt.events.ControlListener; | |
26 import dwt.events.DisposeEvent; | |
27 import dwt.events.DisposeListener; | |
28 import dwt.events.FocusEvent; | |
29 import dwt.events.FocusListener; | |
30 import dwt.events.KeyAdapter; | |
31 import dwt.events.KeyEvent; | |
32 import dwt.events.KeyListener; | |
33 import dwt.events.MouseAdapter; | |
34 import dwt.events.MouseEvent; | |
35 import dwt.events.SelectionEvent; | |
36 import dwt.events.SelectionListener; | |
37 import dwt.events.TraverseEvent; | |
38 import dwt.events.TraverseListener; | |
39 import dwt.events.VerifyEvent; | |
40 import dwt.graphics.Color; | |
41 import dwt.graphics.Font; | |
42 import dwt.graphics.FontData; | |
43 import dwt.graphics.Image; | |
44 import dwt.graphics.Point; | |
45 import dwt.graphics.Rectangle; | |
46 import dwt.layout.GridData; | |
47 import dwt.layout.GridLayout; | |
48 import dwt.widgets.Control; | |
49 import dwt.widgets.Display; | |
50 import dwt.widgets.Event; | |
51 import dwt.widgets.Label; | |
52 import dwt.widgets.Listener; | |
53 import dwt.widgets.Shell; | |
54 import dwt.widgets.Table; | |
55 import dwt.widgets.TableItem; | |
56 import dwtx.core.commands.AbstractHandler; | |
57 import dwtx.core.commands.ExecutionEvent; | |
58 import dwtx.core.commands.ExecutionException; | |
59 import dwtx.core.commands.IHandler; | |
60 import dwtx.core.runtime.Assert; | |
61 import dwtx.jface.bindings.keys.KeySequence; | |
62 import dwtx.jface.bindings.keys.SWTKeySupport; | |
63 import dwtx.jface.contentassist.IContentAssistSubjectControl; | |
64 import dwtx.jface.internal.text.InformationControlReplacer; | |
65 import dwtx.jface.internal.text.TableOwnerDrawSupport; | |
66 import dwtx.jface.preference.JFacePreferences; | |
67 import dwtx.jface.resource.JFaceResources; | |
68 import dwtx.jface.text.AbstractInformationControlManager; | |
69 import dwtx.jface.text.BadLocationException; | |
70 import dwtx.jface.text.DocumentEvent; | |
71 import dwtx.jface.text.IDocument; | |
72 import dwtx.jface.text.IDocumentListener; | |
73 import dwtx.jface.text.IEditingSupport; | |
74 import dwtx.jface.text.IEditingSupportRegistry; | |
75 import dwtx.jface.text.IInformationControl; | |
76 import dwtx.jface.text.IRegion; | |
77 import dwtx.jface.text.IRewriteTarget; | |
78 import dwtx.jface.text.ITextViewer; | |
79 import dwtx.jface.text.ITextViewerExtension; | |
80 import dwtx.jface.text.TextUtilities; | |
81 import dwtx.jface.text.AbstractInformationControlManager.Anchor; | |
82 import dwtx.jface.util.Geometry; | |
83 import dwtx.jface.viewers.StyledString; | |
84 | |
85 | |
86 /** | |
87 * This class is used to present proposals to the user. If additional | |
88 * information exists for a proposal, then selecting that proposal | |
89 * will result in the information being displayed in a secondary | |
90 * window. | |
91 * | |
92 * @see dwtx.jface.text.contentassist.ICompletionProposal | |
93 * @see dwtx.jface.text.contentassist.AdditionalInfoController | |
94 */ | |
95 class CompletionProposalPopup : IContentAssistListener { | |
96 /** | |
97 * Set to <code>true</code> to use a Table with DWT.VIRTUAL. | |
98 * XXX: This is a workaround for: https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321 | |
99 * More details see also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c36 | |
100 * @since 3.1 | |
101 */ | |
102 private static final bool USE_VIRTUAL= !"motif".equals(DWT.getPlatform()); //$NON-NLS-1$ | |
103 | |
104 | |
105 /** | |
106 * Completion proposal selection handler. | |
107 * | |
108 * @since 3.4 | |
109 */ | |
110 final class ProposalSelectionHandler : AbstractHandler { | |
111 | |
112 /** | |
113 * Selection operation codes. | |
114 */ | |
115 static final int SELECT_NEXT= 1; | |
116 static final int SELECT_PREVIOUS= 2; | |
117 | |
118 | |
119 private int fOperationCode; | |
120 | |
121 /** | |
122 * Creates a new selection handler. | |
123 * | |
124 * @param operationCode the operation code | |
125 * @since 3.4 | |
126 */ | |
127 public ProposalSelectionHandler(int operationCode) { | |
128 Assert.isLegal(operationCode is SELECT_NEXT || operationCode is SELECT_PREVIOUS); | |
129 fOperationCode= operationCode; | |
130 } | |
131 | |
132 /* | |
133 * @see dwtx.core.commands.AbstractHandler#execute(dwtx.core.commands.ExecutionEvent) | |
134 * @since 3.4 | |
135 */ | |
136 public Object execute(ExecutionEvent event) throws ExecutionException { | |
137 int itemCount= fProposalTable.getItemCount(); | |
138 int selectionIndex= fProposalTable.getSelectionIndex(); | |
139 switch (fOperationCode) { | |
140 case SELECT_NEXT: | |
141 selectionIndex+= 1; | |
142 if (selectionIndex > itemCount - 1) | |
143 selectionIndex= 0; | |
144 break; | |
145 case SELECT_PREVIOUS: | |
146 selectionIndex-= 1; | |
147 if (selectionIndex < 0) | |
148 selectionIndex= itemCount - 1; | |
149 break; | |
150 } | |
151 selectProposal(selectionIndex, false); | |
152 return null; | |
153 } | |
154 | |
155 } | |
156 | |
157 | |
158 /** | |
159 * The empty proposal displayed if there is nothing else to show. | |
160 * | |
161 * @since 3.2 | |
162 */ | |
163 private static final class EmptyProposal : ICompletionProposal, ICompletionProposalExtension { | |
164 | |
165 String fDisplayString; | |
166 int fOffset; | |
167 /* | |
168 * @see ICompletionProposal#apply(IDocument) | |
169 */ | |
170 public void apply(IDocument document) { | |
171 } | |
172 | |
173 /* | |
174 * @see ICompletionProposal#getSelection(IDocument) | |
175 */ | |
176 public Point getSelection(IDocument document) { | |
177 return new Point(fOffset, 0); | |
178 } | |
179 | |
180 /* | |
181 * @see ICompletionProposal#getContextInformation() | |
182 */ | |
183 public IContextInformation getContextInformation() { | |
184 return null; | |
185 } | |
186 | |
187 /* | |
188 * @see ICompletionProposal#getImage() | |
189 */ | |
190 public Image getImage() { | |
191 return null; | |
192 } | |
193 | |
194 /* | |
195 * @see ICompletionProposal#getDisplayString() | |
196 */ | |
197 public String getDisplayString() { | |
198 return fDisplayString; | |
199 } | |
200 | |
201 /* | |
202 * @see ICompletionProposal#getAdditionalProposalInfo() | |
203 */ | |
204 public String getAdditionalProposalInfo() { | |
205 return null; | |
206 } | |
207 | |
208 /* | |
209 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#apply(dwtx.jface.text.IDocument, char, int) | |
210 */ | |
211 public void apply(IDocument document, char trigger, int offset) { | |
212 } | |
213 | |
214 /* | |
215 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#isValidFor(dwtx.jface.text.IDocument, int) | |
216 */ | |
217 public bool isValidFor(IDocument document, int offset) { | |
218 return false; | |
219 } | |
220 | |
221 /* | |
222 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters() | |
223 */ | |
224 public char[] getTriggerCharacters() { | |
225 return null; | |
226 } | |
227 | |
228 /* | |
229 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition() | |
230 */ | |
231 public int getContextInformationPosition() { | |
232 return -1; | |
233 } | |
234 } | |
235 | |
236 private final class ProposalSelectionListener : KeyListener { | |
237 public void keyPressed(KeyEvent e) { | |
238 if (!Helper.okToUse(fProposalShell)) | |
239 return; | |
240 | |
241 if (e.character is 0 && e.keyCode is DWT.CTRL) { | |
242 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754 | |
243 int index= fProposalTable.getSelectionIndex(); | |
244 if (index >= 0) | |
245 selectProposal(index, true); | |
246 } | |
247 } | |
248 | |
249 public void keyReleased(KeyEvent e) { | |
250 if (!Helper.okToUse(fProposalShell)) | |
251 return; | |
252 | |
253 if (e.character is 0 && e.keyCode is DWT.CTRL) { | |
254 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754 | |
255 int index= fProposalTable.getSelectionIndex(); | |
256 if (index >= 0) | |
257 selectProposal(index, false); | |
258 } | |
259 } | |
260 } | |
261 | |
262 private final class CommandKeyListener : KeyAdapter { | |
263 private KeySequence fCommandSequence; | |
264 | |
265 private CommandKeyListener(KeySequence keySequence) { | |
266 fCommandSequence= keySequence; | |
267 } | |
268 | |
269 public void keyPressed(KeyEvent e) { | |
270 if (!Helper.okToUse(fProposalShell)) | |
271 return; | |
272 | |
273 int accelerator= SWTKeySupport.convertEventToUnmodifiedAccelerator(e); | |
274 KeySequence sequence= KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator)); | |
275 if (sequence.equals(fCommandSequence)) | |
276 if (fContentAssistant.isPrefixCompletionEnabled()) | |
277 incrementalComplete(); | |
278 else | |
279 showProposals(false); | |
280 | |
281 } | |
282 } | |
283 | |
284 | |
285 /** The associated text viewer. */ | |
286 private ITextViewer fViewer; | |
287 /** The associated content assistant. */ | |
288 private ContentAssistant fContentAssistant; | |
289 /** The used additional info controller. */ | |
290 private AdditionalInfoController fAdditionalInfoController; | |
291 /** The closing strategy for this completion proposal popup. */ | |
292 private PopupCloser fPopupCloser= new PopupCloser(); | |
293 /** The popup shell. */ | |
294 private Shell fProposalShell; | |
295 /** The proposal table. */ | |
296 private Table fProposalTable; | |
297 /** Indicates whether a completion proposal is being inserted. */ | |
298 private bool fInserting= false; | |
299 /** The key listener to control navigation. */ | |
300 private ProposalSelectionListener fKeyListener; | |
301 /** List of document events used for filtering proposals. */ | |
302 private List fDocumentEvents= new ArrayList(); | |
303 /** Listener filling the document event queue. */ | |
304 private IDocumentListener fDocumentListener; | |
305 /** The filter list of proposals. */ | |
306 private ICompletionProposal[] fFilteredProposals; | |
307 /** The computed list of proposals. */ | |
308 private ICompletionProposal[] fComputedProposals; | |
309 /** The offset for which the proposals have been computed. */ | |
310 private int fInvocationOffset; | |
311 /** The offset for which the computed proposals have been filtered. */ | |
312 private int fFilterOffset; | |
313 /** | |
314 * The most recently selected proposal. | |
315 * @since 3.0 | |
316 */ | |
317 private ICompletionProposal fLastProposal; | |
318 /** | |
319 * The content assist subject control. | |
320 * This replaces <code>fViewer</code> | |
321 * | |
322 * @since 3.0 | |
323 */ | |
324 private IContentAssistSubjectControl fContentAssistSubjectControl; | |
325 /** | |
326 * The content assist subject control adapter. | |
327 * This replaces <code>fViewer</code> | |
328 * | |
329 * @since 3.0 | |
330 */ | |
331 private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter; | |
332 /** | |
333 * Remembers the size for this completion proposal popup. | |
334 * @since 3.0 | |
335 */ | |
336 private Point fSize; | |
337 /** | |
338 * Editor helper that communicates that the completion proposal popup may | |
339 * have focus while the 'logical focus' is still with the editor. | |
340 * @since 3.1 | |
341 */ | |
342 private IEditingSupport fFocusHelper; | |
343 /** | |
344 * Set to true by {@link #computeFilteredProposals(int, DocumentEvent)} if | |
345 * the returned proposals are a subset of {@link #fFilteredProposals}, | |
346 * <code>false</code> if not. | |
347 * @since 3.1 | |
348 */ | |
349 private bool fIsFilteredSubset; | |
350 /** | |
351 * The filter runnable. | |
352 * | |
353 * @since 3.1.1 | |
354 */ | |
355 private final Runnable fFilterRunnable= new Runnable() { | |
356 public void run() { | |
357 if (!fIsFilterPending) | |
358 return; | |
359 | |
360 fIsFilterPending= false; | |
361 | |
362 if (!Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) | |
363 return; | |
364 | |
365 int offset= fContentAssistSubjectControlAdapter.getSelectedRange().x; | |
366 ICompletionProposal[] proposals= null; | |
367 try { | |
368 if (offset > -1) { | |
369 DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents); | |
370 proposals= computeFilteredProposals(offset, event); | |
371 } | |
372 } catch (BadLocationException x) { | |
373 } finally { | |
374 fDocumentEvents.clear(); | |
375 } | |
376 fFilterOffset= offset; | |
377 | |
378 if (proposals !is null && proposals.length > 0) | |
379 setProposals(proposals, fIsFilteredSubset); | |
380 else | |
381 hide(); | |
382 } | |
383 }; | |
384 /** | |
385 * <code>true</code> if <code>fFilterRunnable</code> has been | |
386 * posted, <code>false</code> if not. | |
387 * | |
388 * @since 3.1.1 | |
389 */ | |
390 private bool fIsFilterPending= false; | |
391 /** | |
392 * The info message at the bottom of the popup, or <code>null</code> for no popup (if | |
393 * ContentAssistant does not provide one). | |
394 * | |
395 * @since 3.2 | |
396 */ | |
397 private Label fMessageText; | |
398 /** | |
399 * The font used for <code>fMessageText</code> or null; dispose when done. | |
400 * | |
401 * @since 3.2 | |
402 */ | |
403 private Font fMessageTextFont; | |
404 /** | |
405 * The most recent completion offset (used to determine repeated invocation) | |
406 * | |
407 * @since 3.2 | |
408 */ | |
409 private int fLastCompletionOffset; | |
410 /** | |
411 * The (reusable) empty proposal. | |
412 * | |
413 * @since 3.2 | |
414 */ | |
415 private final EmptyProposal fEmptyProposal= new EmptyProposal(); | |
416 /** | |
417 * The text for the empty proposal, or <code>null</code> to use the default text. | |
418 * | |
419 * @since 3.2 | |
420 */ | |
421 private String fEmptyMessage= null; | |
422 /** | |
423 * Tells whether colored labels support is enabled. | |
424 * Only valid while the popup is active. | |
425 * | |
426 * @since 3.4 | |
427 */ | |
428 private bool fIsColoredLabelsSupportEnabled= false; | |
429 | |
430 | |
431 /** | |
432 * Creates a new completion proposal popup for the given elements. | |
433 * | |
434 * @param contentAssistant the content assistant feeding this popup | |
435 * @param viewer the viewer on top of which this popup appears | |
436 * @param infoController the information control collaborating with this popup | |
437 * @since 2.0 | |
438 */ | |
439 public CompletionProposalPopup(ContentAssistant contentAssistant, ITextViewer viewer, AdditionalInfoController infoController) { | |
440 fContentAssistant= contentAssistant; | |
441 fViewer= viewer; | |
442 fAdditionalInfoController= infoController; | |
443 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer); | |
444 } | |
445 | |
446 /** | |
447 * Creates a new completion proposal popup for the given elements. | |
448 * | |
449 * @param contentAssistant the content assistant feeding this popup | |
450 * @param contentAssistSubjectControl the content assist subject control on top of which this popup appears | |
451 * @param infoController the information control collaborating with this popup | |
452 * @since 3.0 | |
453 */ | |
454 public CompletionProposalPopup(ContentAssistant contentAssistant, IContentAssistSubjectControl contentAssistSubjectControl, AdditionalInfoController infoController) { | |
455 fContentAssistant= contentAssistant; | |
456 fContentAssistSubjectControl= contentAssistSubjectControl; | |
457 fAdditionalInfoController= infoController; | |
458 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl); | |
459 } | |
460 | |
461 /** | |
462 * Computes and presents completion proposals. The flag indicates whether this call has | |
463 * be made out of an auto activation context. | |
464 * | |
465 * @param autoActivated <code>true</code> if auto activation context | |
466 * @return an error message or <code>null</code> in case of no error | |
467 */ | |
468 public String showProposals(final bool autoActivated) { | |
469 | |
470 if (fKeyListener is null) | |
471 fKeyListener= new ProposalSelectionListener(); | |
472 | |
473 final Control control= fContentAssistSubjectControlAdapter.getControl(); | |
474 | |
475 if (!Helper.okToUse(fProposalShell) && control !is null && !control.isDisposed()) { | |
476 // add the listener before computing the proposals so we don't move the caret | |
477 // when the user types fast. | |
478 fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener); | |
479 | |
480 BusyIndicator.showWhile(control.getDisplay(), new Runnable() { | |
481 public void run() { | |
482 | |
483 fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x; | |
484 fFilterOffset= fInvocationOffset; | |
485 fLastCompletionOffset= fFilterOffset; | |
486 fComputedProposals= computeProposals(fInvocationOffset); | |
487 | |
488 int count= (fComputedProposals is null ? 0 : fComputedProposals.length); | |
489 if (count is 0 && hideWhenNoProposals(autoActivated)) | |
490 return; | |
491 | |
492 if (count is 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) { | |
493 insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset); | |
494 hide(); | |
495 } else { | |
496 createProposalSelector(); | |
497 setProposals(fComputedProposals, false); | |
498 displayProposals(); | |
499 } | |
500 } | |
501 }); | |
502 } else { | |
503 fLastCompletionOffset= fFilterOffset; | |
504 handleRepeatedInvocation(); | |
505 } | |
506 | |
507 return getErrorMessage(); | |
508 } | |
509 | |
510 /** | |
511 * Hides the popup and returns <code>true</code> if the popup is configured | |
512 * to never display an empty list. Returns <code>false</code> otherwise. | |
513 * | |
514 * @param autoActivated whether the invocation was auto-activated | |
515 * @return <code>false</code> if an empty list should be displayed, <code>true</code> otherwise | |
516 * @since 3.2 | |
517 */ | |
518 private bool hideWhenNoProposals(bool autoActivated) { | |
519 if (autoActivated || !fContentAssistant.isShowEmptyList()) { | |
520 if (!autoActivated) { | |
521 Control control= fContentAssistSubjectControlAdapter.getControl(); | |
522 if (control !is null && !control.isDisposed()) | |
523 control.getDisplay().beep(); | |
524 } | |
525 hide(); | |
526 return true; | |
527 } | |
528 return false; | |
529 } | |
530 | |
531 /** | |
532 * If content assist is set up to handle cycling, then the proposals are recomputed. Otherwise, | |
533 * nothing happens. | |
534 * | |
535 * @since 3.2 | |
536 */ | |
537 private void handleRepeatedInvocation() { | |
538 if (fContentAssistant.isRepeatedInvocationMode()) { | |
539 fComputedProposals= computeProposals(fFilterOffset); | |
540 setProposals(fComputedProposals, false); | |
541 } | |
542 } | |
543 | |
544 /** | |
545 * Returns the completion proposal available at the given offset of the | |
546 * viewer's document. Delegates the work to the content assistant. | |
547 * | |
548 * @param offset the offset | |
549 * @return the completion proposals available at this offset | |
550 */ | |
551 private ICompletionProposal[] computeProposals(int offset) { | |
552 if (fContentAssistSubjectControl !is null) | |
553 return fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset); | |
554 return fContentAssistant.computeCompletionProposals(fViewer, offset); | |
555 } | |
556 | |
557 /** | |
558 * Returns the error message. | |
559 * | |
560 * @return the error message | |
561 */ | |
562 private String getErrorMessage() { | |
563 return fContentAssistant.getErrorMessage(); | |
564 } | |
565 | |
566 /** | |
567 * Creates the proposal selector. | |
568 */ | |
569 private void createProposalSelector() { | |
570 if (Helper.okToUse(fProposalShell)) | |
571 return; | |
572 | |
573 Control control= fContentAssistSubjectControlAdapter.getControl(); | |
574 fProposalShell= new Shell(control.getShell(), DWT.ON_TOP | DWT.RESIZE ); | |
575 fProposalShell.setFont(JFaceResources.getDefaultFont()); | |
576 if (USE_VIRTUAL) { | |
577 fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL | DWT.VIRTUAL); | |
578 | |
579 Listener listener= new Listener() { | |
580 public void handleEvent(Event event) { | |
581 handleSetData(event); | |
582 } | |
583 }; | |
584 fProposalTable.addListener(DWT.SetData, listener); | |
585 } else { | |
586 fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL); | |
587 } | |
588 | |
589 fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled(); | |
590 if (fIsColoredLabelsSupportEnabled) | |
591 TableOwnerDrawSupport.install(fProposalTable); | |
592 | |
593 fProposalTable.setLocation(0, 0); | |
594 if (fAdditionalInfoController !is null) | |
595 fAdditionalInfoController.setSizeConstraints(50, 10, true, true); | |
596 | |
597 GridLayout layout= new GridLayout(); | |
598 layout.marginWidth= 0; | |
599 layout.marginHeight= 0; | |
600 layout.verticalSpacing= 1; | |
601 fProposalShell.setLayout(layout); | |
602 | |
603 if (fContentAssistant.isStatusLineVisible()) { | |
604 createMessageText(); | |
605 } | |
606 | |
607 GridData data= new GridData(GridData.FILL_BOTH); | |
608 | |
609 Point size= fContentAssistant.restoreCompletionProposalPopupSize(); | |
610 if (size !is null) { | |
611 fProposalTable.setLayoutData(data); | |
612 fProposalShell.setSize(size); | |
613 } else { | |
614 int height= fProposalTable.getItemHeight() * 10; | |
615 // use golden ratio as default aspect ratio | |
616 final double aspectRatio= (1 + Math.sqrt(5)) / 2; | |
617 int width= (int) (height * aspectRatio); | |
618 Rectangle trim= fProposalTable.computeTrim(0, 0, width, height); | |
619 data.heightHint= trim.height; | |
620 data.widthHint= trim.width; | |
621 fProposalTable.setLayoutData(data); | |
622 fProposalShell.pack(); | |
623 } | |
624 fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset()); | |
625 | |
626 fProposalShell.addControlListener(new ControlListener() { | |
627 | |
628 public void controlMoved(ControlEvent e) {} | |
629 | |
630 public void controlResized(ControlEvent e) { | |
631 if (fAdditionalInfoController !is null) { | |
632 // reset the cached resize constraints | |
633 fAdditionalInfoController.setSizeConstraints(50, 10, true, false); | |
634 } | |
635 | |
636 fSize= fProposalShell.getSize(); | |
637 } | |
638 }); | |
639 | |
640 fProposalShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_GRAY)); | |
641 | |
642 Color c= getBackgroundColor(control); | |
643 fProposalTable.setBackground(c); | |
644 | |
645 c= getForegroundColor(control); | |
646 fProposalTable.setForeground(c); | |
647 | |
648 fProposalTable.addSelectionListener(new SelectionListener() { | |
649 | |
650 public void widgetSelected(SelectionEvent e) {} | |
651 | |
652 public void widgetDefaultSelected(SelectionEvent e) { | |
653 insertSelectedProposalWithMask(e.stateMask); | |
654 } | |
655 }); | |
656 | |
657 fPopupCloser.install(fContentAssistant, fProposalTable, fAdditionalInfoController); | |
658 | |
659 fProposalShell.addDisposeListener(new DisposeListener() { | |
660 public void widgetDisposed(DisposeEvent e) { | |
661 unregister(); // but don't dispose the shell, since we're being called from its disposal event! | |
662 } | |
663 }); | |
664 | |
665 fProposalTable.setHeaderVisible(false); | |
666 | |
667 addCommandSupport(fProposalTable); | |
668 } | |
669 | |
670 /** | |
671 * Returns the minimal required height for the proposal, may return 0 if the popup has not been | |
672 * created yet. | |
673 * | |
674 * @return the minimal height | |
675 * @since 3.3 | |
676 */ | |
677 int getMinimalHeight() { | |
678 int height= 0; | |
679 if (Helper.okToUse(fProposalTable)) { | |
680 int items= fProposalTable.getItemHeight() * 10; | |
681 Rectangle trim= fProposalTable.computeTrim(0, 0, DWT.DEFAULT, items); | |
682 height= trim.height; | |
683 } | |
684 if (Helper.okToUse(fMessageText)) | |
685 height+= fMessageText.getSize().y + 1; | |
686 return height; | |
687 } | |
688 | |
689 /** | |
690 * Adds command support to the given control. | |
691 * | |
692 * @param control the control to watch for focus | |
693 * @since 3.2 | |
694 */ | |
695 private void addCommandSupport(final Control control) { | |
696 final KeySequence commandSequence= fContentAssistant.getRepeatedInvocationKeySequence(); | |
697 if (commandSequence !is null && !commandSequence.isEmpty() && fContentAssistant.isRepeatedInvocationMode()) { | |
698 control.addFocusListener(new FocusListener() { | |
699 private CommandKeyListener fCommandKeyListener; | |
700 public void focusGained(FocusEvent e) { | |
701 if (Helper.okToUse(control)) { | |
702 if (fCommandKeyListener is null) { | |
703 fCommandKeyListener= new CommandKeyListener(commandSequence); | |
704 fProposalTable.addKeyListener(fCommandKeyListener); | |
705 } | |
706 } | |
707 } | |
708 public void focusLost(FocusEvent e) { | |
709 if (fCommandKeyListener !is null) { | |
710 control.removeKeyListener(fCommandKeyListener); | |
711 fCommandKeyListener= null; | |
712 } | |
713 } | |
714 }); | |
715 } | |
716 control.addFocusListener(new FocusListener() { | |
717 private TraverseListener fTraverseListener; | |
718 public void focusGained(FocusEvent e) { | |
719 if (Helper.okToUse(control)) { | |
720 if (fTraverseListener is null) { | |
721 fTraverseListener= new TraverseListener() { | |
722 public void keyTraversed(TraverseEvent event) { | |
723 if (event.detail is DWT.TRAVERSE_TAB_NEXT) { | |
724 IInformationControl iControl= fAdditionalInfoController.getCurrentInformationControl2(); | |
725 if (fAdditionalInfoController.getInternalAccessor().canReplace(iControl)) { | |
726 fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true); | |
727 event.doit= false; | |
728 } | |
729 } | |
730 } | |
731 }; | |
732 fProposalTable.addTraverseListener(fTraverseListener); | |
733 } | |
734 } | |
735 } | |
736 public void focusLost(FocusEvent e) { | |
737 if (fTraverseListener !is null) { | |
738 control.removeTraverseListener(fTraverseListener); | |
739 fTraverseListener= null; | |
740 } | |
741 } | |
742 }); | |
743 } | |
744 | |
745 /** | |
746 * Returns the background color to use. | |
747 * | |
748 * @param control the control to get the display from | |
749 * @return the background color | |
750 * @since 3.2 | |
751 */ | |
752 private Color getBackgroundColor(Control control) { | |
753 Color c= fContentAssistant.getProposalSelectorBackground(); | |
754 if (c is null) | |
755 c= JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR); | |
756 return c; | |
757 } | |
758 | |
759 /** | |
760 * Returns the foreground color to use. | |
761 * | |
762 * @param control the control to get the display from | |
763 * @return the foreground color | |
764 * @since 3.2 | |
765 */ | |
766 private Color getForegroundColor(Control control) { | |
767 Color c= fContentAssistant.getProposalSelectorForeground(); | |
768 if (c is null) | |
769 c= JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR); | |
770 return c; | |
771 } | |
772 | |
773 /** | |
774 * Creates the caption line under the proposal table. | |
775 * | |
776 * @since 3.2 | |
777 */ | |
778 private void createMessageText() { | |
779 if (fMessageText is null) { | |
780 fMessageText= new Label(fProposalShell, DWT.RIGHT); | |
781 GridData textData= new GridData(DWT.FILL, DWT.BOTTOM, true, false); | |
782 fMessageText.setLayoutData(textData); | |
783 fMessageText.setText(fContentAssistant.getStatusMessage() + " "); //$NON-NLS-1$ | |
784 if (fMessageTextFont is null) { | |
785 Font font= fMessageText.getFont(); | |
786 Display display= fProposalShell.getDisplay(); | |
787 FontData[] fontDatas= font.getFontData(); | |
788 for (int i= 0; i < fontDatas.length; i++) | |
789 fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10); | |
790 fMessageTextFont= new Font(display, fontDatas); | |
791 } | |
792 fMessageText.setFont(fMessageTextFont); | |
793 fMessageText.setBackground(getBackgroundColor(fProposalShell)); | |
794 fMessageText.setForeground(getForegroundColor(fProposalShell)); | |
795 | |
796 if (fContentAssistant.isRepeatedInvocationMode()) { | |
797 fMessageText.setCursor(fProposalShell.getDisplay().getSystemCursor(DWT.CURSOR_HAND)); | |
798 fMessageText.addMouseListener(new MouseAdapter() { | |
799 public void mouseUp(MouseEvent e) { | |
800 fLastCompletionOffset= fFilterOffset; | |
801 fProposalTable.setFocus(); | |
802 handleRepeatedInvocation(); | |
803 } | |
804 | |
805 public void mouseDown(MouseEvent e) { | |
806 } | |
807 }); | |
808 } | |
809 } | |
810 } | |
811 | |
812 /* | |
813 * @since 3.1 | |
814 */ | |
815 private void handleSetData(Event event) { | |
816 TableItem item= (TableItem) event.item; | |
817 int index= fProposalTable.indexOf(item); | |
818 | |
819 if (0 <= index && index < fFilteredProposals.length) { | |
820 ICompletionProposal current= fFilteredProposals[index]; | |
821 | |
822 String displayString; | |
823 StyleRange[] styleRanges= null; | |
824 if (fIsColoredLabelsSupportEnabled && current instanceof ICompletionProposalExtension6) { | |
825 StyledString styledString= ((ICompletionProposalExtension6)current).getStyledDisplayString(); | |
826 displayString= styledString.getString(); | |
827 styleRanges= styledString.getStyleRanges(); | |
828 } else | |
829 displayString= current.getDisplayString(); | |
830 | |
831 item.setText(displayString); | |
832 if (fIsColoredLabelsSupportEnabled) | |
833 TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges); | |
834 | |
835 item.setImage(current.getImage()); | |
836 item.setData(current); | |
837 } else { | |
838 // this should not happen, but does on win32 | |
839 } | |
840 } | |
841 | |
842 /** | |
843 * Returns the proposal selected in the proposal selector. | |
844 * | |
845 * @return the selected proposal | |
846 * @since 2.0 | |
847 */ | |
848 private ICompletionProposal getSelectedProposal() { | |
849 /* Make sure that there is no filter runnable pending. | |
850 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=31427 | |
851 */ | |
852 if (fIsFilterPending) | |
853 fFilterRunnable.run(); | |
854 | |
855 // filter runnable may have hidden the proposals | |
856 if (!Helper.okToUse(fProposalTable)) | |
857 return null; | |
858 | |
859 int i= fProposalTable.getSelectionIndex(); | |
860 if (fFilteredProposals is null || i < 0 || i >= fFilteredProposals.length) | |
861 return null; | |
862 return fFilteredProposals[i]; | |
863 } | |
864 | |
865 /** | |
866 * Takes the selected proposal and applies it. | |
867 * | |
868 * @param stateMask the state mask | |
869 * @since 3.2 | |
870 */ | |
871 private void insertSelectedProposalWithMask(int stateMask) { | |
872 ICompletionProposal p= getSelectedProposal(); | |
873 hide(); | |
874 if (p !is null) | |
875 insertProposal(p, (char) 0, stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x); | |
876 } | |
877 | |
878 /** | |
879 * Applies the given proposal at the given offset. The given character is the | |
880 * one that triggered the insertion of this proposal. | |
881 * | |
882 * @param p the completion proposal | |
883 * @param trigger the trigger character | |
884 * @param stateMask the state mask | |
885 * @param offset the offset | |
886 * @since 2.1 | |
887 */ | |
888 private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) { | |
889 | |
890 fInserting= true; | |
891 IRewriteTarget target= null; | |
892 IEditingSupport helper= new IEditingSupport() { | |
893 | |
894 public bool isOriginator(DocumentEvent event, IRegion focus) { | |
895 return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset; | |
896 } | |
897 | |
898 public bool ownsFocusShell() { | |
899 return false; | |
900 } | |
901 | |
902 }; | |
903 | |
904 try { | |
905 | |
906 IDocument document= fContentAssistSubjectControlAdapter.getDocument(); | |
907 | |
908 if (fViewer instanceof ITextViewerExtension) { | |
909 ITextViewerExtension extension= (ITextViewerExtension) fViewer; | |
910 target= extension.getRewriteTarget(); | |
911 } | |
912 | |
913 if (target !is null) | |
914 target.beginCompoundChange(); | |
915 | |
916 if (fViewer instanceof IEditingSupportRegistry) { | |
917 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; | |
918 registry.register(helper); | |
919 } | |
920 | |
921 | |
922 if (p instanceof ICompletionProposalExtension2 && fViewer !is null) { | |
923 ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p; | |
924 e.apply(fViewer, trigger, stateMask, offset); | |
925 } else if (p instanceof ICompletionProposalExtension) { | |
926 ICompletionProposalExtension e= (ICompletionProposalExtension) p; | |
927 e.apply(document, trigger, offset); | |
928 } else { | |
929 p.apply(document); | |
930 } | |
931 | |
932 Point selection= p.getSelection(document); | |
933 if (selection !is null) { | |
934 fContentAssistSubjectControlAdapter.setSelectedRange(selection.x, selection.y); | |
935 fContentAssistSubjectControlAdapter.revealRange(selection.x, selection.y); | |
936 } | |
937 | |
938 IContextInformation info= p.getContextInformation(); | |
939 if (info !is null) { | |
940 | |
941 int contextInformationOffset; | |
942 if (p instanceof ICompletionProposalExtension) { | |
943 ICompletionProposalExtension e= (ICompletionProposalExtension) p; | |
944 contextInformationOffset= e.getContextInformationPosition(); | |
945 } else { | |
946 if (selection is null) | |
947 selection= fContentAssistSubjectControlAdapter.getSelectedRange(); | |
948 contextInformationOffset= selection.x + selection.y; | |
949 } | |
950 | |
951 fContentAssistant.showContextInformation(info, contextInformationOffset); | |
952 } else | |
953 fContentAssistant.showContextInformation(null, -1); | |
954 | |
955 | |
956 } finally { | |
957 if (target !is null) | |
958 target.endCompoundChange(); | |
959 | |
960 if (fViewer instanceof IEditingSupportRegistry) { | |
961 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; | |
962 registry.unregister(helper); | |
963 } | |
964 fInserting= false; | |
965 } | |
966 } | |
967 | |
968 /** | |
969 * Returns whether this popup has the focus. | |
970 * | |
971 * @return <code>true</code> if the popup has the focus | |
972 */ | |
973 public bool hasFocus() { | |
974 if (Helper.okToUse(fProposalShell)) { | |
975 if ((fProposalShell.isFocusControl() || fProposalTable.isFocusControl())) | |
976 return true; | |
977 /* | |
978 * We have to delegate this query to the additional info controller | |
979 * as well, since the content assistant is the widget token owner | |
980 * and its closer does not know that the additional info control can | |
981 * now also take focus. | |
982 */ | |
983 if (fAdditionalInfoController !is null) { | |
984 IInformationControl informationControl= fAdditionalInfoController.getCurrentInformationControl2(); | |
985 if (informationControl !is null && informationControl.isFocusControl()) | |
986 return true; | |
987 InformationControlReplacer replacer= fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer(); | |
988 if (replacer !is null) { | |
989 informationControl= replacer.getCurrentInformationControl2(); | |
990 if (informationControl !is null && informationControl.isFocusControl()) | |
991 return true; | |
992 } | |
993 } | |
994 } | |
995 | |
996 return false; | |
997 } | |
998 | |
999 /** | |
1000 * Hides this popup. | |
1001 */ | |
1002 public void hide() { | |
1003 | |
1004 unregister(); | |
1005 | |
1006 if (fViewer instanceof IEditingSupportRegistry) { | |
1007 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; | |
1008 registry.unregister(fFocusHelper); | |
1009 } | |
1010 | |
1011 if (Helper.okToUse(fProposalShell)) { | |
1012 | |
1013 fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR); | |
1014 | |
1015 fPopupCloser.uninstall(); | |
1016 fProposalShell.setVisible(false); | |
1017 fProposalShell.dispose(); | |
1018 fProposalShell= null; | |
1019 } | |
1020 | |
1021 if (fMessageTextFont !is null) { | |
1022 fMessageTextFont.dispose(); | |
1023 fMessageTextFont= null; | |
1024 } | |
1025 | |
1026 if (fMessageText !is null) { | |
1027 fMessageText= null; | |
1028 } | |
1029 | |
1030 fEmptyMessage= null; | |
1031 | |
1032 fLastCompletionOffset= -1; | |
1033 | |
1034 fContentAssistant.fireSessionEndEvent(); | |
1035 } | |
1036 | |
1037 /** | |
1038 * Unregister this completion proposal popup. | |
1039 * | |
1040 * @since 3.0 | |
1041 */ | |
1042 private void unregister() { | |
1043 if (fDocumentListener !is null) { | |
1044 IDocument document= fContentAssistSubjectControlAdapter.getDocument(); | |
1045 if (document !is null) | |
1046 document.removeDocumentListener(fDocumentListener); | |
1047 fDocumentListener= null; | |
1048 } | |
1049 fDocumentEvents.clear(); | |
1050 | |
1051 if (fKeyListener !is null && fContentAssistSubjectControlAdapter.getControl() !is null && !fContentAssistSubjectControlAdapter.getControl().isDisposed()) { | |
1052 fContentAssistSubjectControlAdapter.removeKeyListener(fKeyListener); | |
1053 fKeyListener= null; | |
1054 } | |
1055 | |
1056 if (fLastProposal !is null) { | |
1057 if (fLastProposal instanceof ICompletionProposalExtension2 && fViewer !is null) { | |
1058 ICompletionProposalExtension2 extension= (ICompletionProposalExtension2) fLastProposal; | |
1059 extension.unselected(fViewer); | |
1060 } | |
1061 fLastProposal= null; | |
1062 } | |
1063 | |
1064 fFilteredProposals= null; | |
1065 fComputedProposals= null; | |
1066 | |
1067 fContentAssistant.possibleCompletionsClosed(); | |
1068 } | |
1069 | |
1070 /** | |
1071 *Returns whether this popup is active. It is active if the proposal selector is visible. | |
1072 * | |
1073 * @return <code>true</code> if this popup is active | |
1074 */ | |
1075 public bool isActive() { | |
1076 return fProposalShell !is null && !fProposalShell.isDisposed(); | |
1077 } | |
1078 | |
1079 /** | |
1080 * Initializes the proposal selector with these given proposals. | |
1081 * @param proposals the proposals | |
1082 * @param isFilteredSubset if <code>true</code>, the proposal table is | |
1083 * not cleared, but the proposals that are not in the passed array | |
1084 * are removed from the displayed set | |
1085 */ | |
1086 private void setProposals(ICompletionProposal[] proposals, bool isFilteredSubset) { | |
1087 ICompletionProposal[] oldProposals= fFilteredProposals; | |
1088 ICompletionProposal oldProposal= getSelectedProposal(); // may trigger filtering and a reentrant call to setProposals() | |
1089 if (oldProposals !is fFilteredProposals) // reentrant call was first - abort | |
1090 return; | |
1091 | |
1092 if (Helper.okToUse(fProposalTable)) { | |
1093 if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null) | |
1094 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer); | |
1095 | |
1096 if (proposals is null || proposals.length is 0) { | |
1097 fEmptyProposal.fOffset= fFilterOffset; | |
1098 fEmptyProposal.fDisplayString= fEmptyMessage !is null ? fEmptyMessage : JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"); //$NON-NLS-1$ | |
1099 proposals= new ICompletionProposal[] { fEmptyProposal }; | |
1100 } | |
1101 | |
1102 fFilteredProposals= proposals; | |
1103 final int newLen= proposals.length; | |
1104 if (USE_VIRTUAL) { | |
1105 fProposalTable.setItemCount(newLen); | |
1106 fProposalTable.clearAll(); | |
1107 } else { | |
1108 fProposalTable.setRedraw(false); | |
1109 fProposalTable.setItemCount(newLen); | |
1110 TableItem[] items= fProposalTable.getItems(); | |
1111 for (int i= 0; i < items.length; i++) { | |
1112 TableItem item= items[i]; | |
1113 ICompletionProposal proposal= proposals[i]; | |
1114 item.setText(proposal.getDisplayString()); | |
1115 item.setImage(proposal.getImage()); | |
1116 item.setData(proposal); | |
1117 } | |
1118 fProposalTable.setRedraw(true); | |
1119 } | |
1120 | |
1121 Point currentLocation= fProposalShell.getLocation(); | |
1122 Point newLocation= getLocation(); | |
1123 if ((newLocation.x < currentLocation.x && newLocation.y is currentLocation.y) || newLocation.y < currentLocation.y) | |
1124 fProposalShell.setLocation(newLocation); | |
1125 | |
1126 selectProposal(0, false); | |
1127 } | |
1128 } | |
1129 | |
1130 /** | |
1131 * Returns the graphical location at which this popup should be made visible. | |
1132 * | |
1133 * @return the location of this popup | |
1134 */ | |
1135 private Point getLocation() { | |
1136 int caret= fContentAssistSubjectControlAdapter.getCaretOffset(); | |
1137 Rectangle location= fContentAssistant.getLayoutManager().computeBoundsBelowAbove(fProposalShell, fSize is null ? fProposalShell.getSize() : fSize, caret, this); | |
1138 return Geometry.getLocation(location); | |
1139 } | |
1140 | |
1141 /** | |
1142 * Returns the size of this completion proposal popup. | |
1143 * | |
1144 * @return a Point containing the size | |
1145 * @since 3.0 | |
1146 */ | |
1147 Point getSize() { | |
1148 return fSize; | |
1149 } | |
1150 | |
1151 /** | |
1152 * Displays this popup and install the additional info controller, so that additional info | |
1153 * is displayed when a proposal is selected and additional info is available. | |
1154 */ | |
1155 private void displayProposals() { | |
1156 | |
1157 if (!Helper.okToUse(fProposalShell) || !Helper.okToUse(fProposalTable)) | |
1158 return; | |
1159 | |
1160 if (fContentAssistant.addContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR)) { | |
1161 | |
1162 ensureDocumentListenerInstalled(); | |
1163 | |
1164 if (fFocusHelper is null) { | |
1165 fFocusHelper= new IEditingSupport() { | |
1166 | |
1167 public bool isOriginator(DocumentEvent event, IRegion focus) { | |
1168 return false; // this helper just covers the focus change to the proposal shell, no remote editions | |
1169 } | |
1170 | |
1171 public bool ownsFocusShell() { | |
1172 return true; | |
1173 } | |
1174 | |
1175 }; | |
1176 } | |
1177 if (fViewer instanceof IEditingSupportRegistry) { | |
1178 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; | |
1179 registry.register(fFocusHelper); | |
1180 } | |
1181 | |
1182 | |
1183 /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646 | |
1184 * on GTK, setVisible and such may run the event loop | |
1185 * (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511) | |
1186 * Since the user may have already canceled the popup or selected | |
1187 * an entry (ESC or RETURN), we have to double check whether | |
1188 * the table is still okToUse. See comments below | |
1189 */ | |
1190 fProposalShell.setVisible(true); // may run event loop on GTK | |
1191 // transfer focus since no verify key listener can be attached | |
1192 if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && Helper.okToUse(fProposalShell)) | |
1193 fProposalShell.setFocus(); // may run event loop on GTK ?? | |
1194 | |
1195 if (fAdditionalInfoController !is null && Helper.okToUse(fProposalTable)) { | |
1196 fAdditionalInfoController.install(fProposalTable); | |
1197 fAdditionalInfoController.handleTableSelectionChanged(); | |
1198 } | |
1199 } else | |
1200 hide(); | |
1201 } | |
1202 | |
1203 /** | |
1204 * Installs the document listener if not already done. | |
1205 * | |
1206 * @since 3.2 | |
1207 */ | |
1208 private void ensureDocumentListenerInstalled() { | |
1209 if (fDocumentListener is null) { | |
1210 fDocumentListener= new IDocumentListener() { | |
1211 public void documentAboutToBeChanged(DocumentEvent event) { | |
1212 if (!fInserting) | |
1213 fDocumentEvents.add(event); | |
1214 } | |
1215 | |
1216 public void documentChanged(DocumentEvent event) { | |
1217 if (!fInserting) | |
1218 filterProposals(); | |
1219 } | |
1220 }; | |
1221 IDocument document= fContentAssistSubjectControlAdapter.getDocument(); | |
1222 if (document !is null) | |
1223 document.addDocumentListener(fDocumentListener); | |
1224 } | |
1225 } | |
1226 | |
1227 /* | |
1228 * @see IContentAssistListener#verifyKey(VerifyEvent) | |
1229 */ | |
1230 public bool verifyKey(VerifyEvent e) { | |
1231 if (!Helper.okToUse(fProposalShell)) | |
1232 return true; | |
1233 | |
1234 char key= e.character; | |
1235 if (key is 0) { | |
1236 int newSelection= fProposalTable.getSelectionIndex(); | |
1237 int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1; | |
1238 int itemCount= fProposalTable.getItemCount(); | |
1239 bool smartToggle= false; | |
1240 switch (e.keyCode) { | |
1241 | |
1242 case DWT.ARROW_LEFT : | |
1243 case DWT.ARROW_RIGHT : | |
1244 filterProposals(); | |
1245 return true; | |
1246 | |
1247 case DWT.ARROW_UP : | |
1248 newSelection -= 1; | |
1249 if (newSelection < 0) | |
1250 newSelection= itemCount - 1; | |
1251 break; | |
1252 | |
1253 case DWT.ARROW_DOWN : | |
1254 newSelection += 1; | |
1255 if (newSelection > itemCount - 1) | |
1256 newSelection= 0; | |
1257 break; | |
1258 | |
1259 case DWT.PAGE_DOWN : | |
1260 newSelection += visibleRows; | |
1261 if (newSelection >= itemCount) | |
1262 newSelection= itemCount - 1; | |
1263 break; | |
1264 | |
1265 case DWT.PAGE_UP : | |
1266 newSelection -= visibleRows; | |
1267 if (newSelection < 0) | |
1268 newSelection= 0; | |
1269 break; | |
1270 | |
1271 case DWT.HOME : | |
1272 newSelection= 0; | |
1273 break; | |
1274 | |
1275 case DWT.END : | |
1276 newSelection= itemCount - 1; | |
1277 break; | |
1278 | |
1279 default : | |
1280 if (e.keyCode !is DWT.CAPS_LOCK && e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4) | |
1281 hide(); | |
1282 return true; | |
1283 } | |
1284 | |
1285 selectProposal(newSelection, smartToggle); | |
1286 | |
1287 e.doit= false; | |
1288 return false; | |
1289 | |
1290 } | |
1291 | |
1292 // key !is 0 | |
1293 switch (key) { | |
1294 case 0x1B: // Esc | |
1295 e.doit= false; | |
1296 hide(); | |
1297 break; | |
1298 | |
1299 case '\n': // Ctrl-Enter on w2k | |
1300 case '\r': // Enter | |
1301 e.doit= false; | |
1302 insertSelectedProposalWithMask(e.stateMask); | |
1303 break; | |
1304 | |
1305 case '\t': | |
1306 e.doit= false; | |
1307 fProposalShell.setFocus(); | |
1308 return false; | |
1309 | |
1310 default: | |
1311 ICompletionProposal p= getSelectedProposal(); | |
1312 if (p instanceof ICompletionProposalExtension) { | |
1313 ICompletionProposalExtension t= (ICompletionProposalExtension) p; | |
1314 char[] triggers= t.getTriggerCharacters(); | |
1315 if (contains(triggers, key)) { | |
1316 e.doit= false; | |
1317 hide(); | |
1318 insertProposal(p, key, e.stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x); | |
1319 } | |
1320 } | |
1321 } | |
1322 | |
1323 return true; | |
1324 } | |
1325 | |
1326 /** | |
1327 * Selects the entry with the given index in the proposal selector and feeds | |
1328 * the selection to the additional info controller. | |
1329 * | |
1330 * @param index the index in the list | |
1331 * @param smartToggle <code>true</code> if the smart toggle key has been pressed | |
1332 * @since 2.1 | |
1333 */ | |
1334 private void selectProposal(int index, bool smartToggle) { | |
1335 | |
1336 ICompletionProposal oldProposal= getSelectedProposal(); | |
1337 if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null) | |
1338 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer); | |
1339 | |
1340 if (fFilteredProposals is null) { | |
1341 fireSelectionEvent(null, smartToggle); | |
1342 return; | |
1343 } | |
1344 | |
1345 ICompletionProposal proposal= fFilteredProposals[index]; | |
1346 if (proposal instanceof ICompletionProposalExtension2 && fViewer !is null) | |
1347 ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle); | |
1348 | |
1349 fireSelectionEvent(proposal, smartToggle); | |
1350 | |
1351 fLastProposal= proposal; | |
1352 | |
1353 fProposalTable.setSelection(index); | |
1354 fProposalTable.showSelection(); | |
1355 if (fAdditionalInfoController !is null) | |
1356 fAdditionalInfoController.handleTableSelectionChanged(); | |
1357 } | |
1358 | |
1359 /** | |
1360 * Fires a selection event, see {@link ICompletionListener}. | |
1361 * | |
1362 * @param proposal the selected proposal, possibly <code>null</code> | |
1363 * @param smartToggle true if the smart toggle is on | |
1364 * @since 3.2 | |
1365 */ | |
1366 private void fireSelectionEvent(ICompletionProposal proposal, bool smartToggle) { | |
1367 fContentAssistant.fireSelectionEvent(proposal, smartToggle); | |
1368 } | |
1369 | |
1370 /** | |
1371 * Returns whether the given character is contained in the given array of | |
1372 * characters. | |
1373 * | |
1374 * @param characters the list of characters | |
1375 * @param c the character to look for in the list | |
1376 * @return <code>true</code> if character belongs to the list | |
1377 * @since 2.0 | |
1378 */ | |
1379 private bool contains(char[] characters, char c) { | |
1380 | |
1381 if (characters is null) | |
1382 return false; | |
1383 | |
1384 for (int i= 0; i < characters.length; i++) { | |
1385 if (c is characters[i]) | |
1386 return true; | |
1387 } | |
1388 | |
1389 return false; | |
1390 } | |
1391 | |
1392 /* | |
1393 * @see IEventConsumer#processEvent(VerifyEvent) | |
1394 */ | |
1395 public void processEvent(VerifyEvent e) { | |
1396 } | |
1397 | |
1398 /** | |
1399 * Filters the displayed proposal based on the given cursor position and the | |
1400 * offset of the original invocation of the content assistant. | |
1401 */ | |
1402 private void filterProposals() { | |
1403 if (!fIsFilterPending) { | |
1404 fIsFilterPending= true; | |
1405 Control control= fContentAssistSubjectControlAdapter.getControl(); | |
1406 control.getDisplay().asyncExec(fFilterRunnable); | |
1407 } | |
1408 } | |
1409 | |
1410 /** | |
1411 * Computes the subset of already computed proposals that are still valid for | |
1412 * the given offset. | |
1413 * | |
1414 * @param offset the offset | |
1415 * @param event the merged document event | |
1416 * @return the set of filtered proposals | |
1417 * @since 3.0 | |
1418 */ | |
1419 private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event) { | |
1420 | |
1421 if (offset is fInvocationOffset && event is null) { | |
1422 fIsFilteredSubset= false; | |
1423 return fComputedProposals; | |
1424 } | |
1425 | |
1426 if (offset < fInvocationOffset) { | |
1427 fIsFilteredSubset= false; | |
1428 fInvocationOffset= offset; | |
1429 fContentAssistant.fireSessionRestartEvent(); | |
1430 fComputedProposals= computeProposals(fInvocationOffset); | |
1431 return fComputedProposals; | |
1432 } | |
1433 | |
1434 ICompletionProposal[] proposals; | |
1435 if (offset < fFilterOffset) { | |
1436 proposals= fComputedProposals; | |
1437 fIsFilteredSubset= false; | |
1438 } else { | |
1439 proposals= fFilteredProposals; | |
1440 fIsFilteredSubset= true; | |
1441 } | |
1442 | |
1443 if (proposals is null) { | |
1444 fIsFilteredSubset= false; | |
1445 return null; | |
1446 } | |
1447 | |
1448 IDocument document= fContentAssistSubjectControlAdapter.getDocument(); | |
1449 int length= proposals.length; | |
1450 List filtered= new ArrayList(length); | |
1451 for (int i= 0; i < length; i++) { | |
1452 | |
1453 if (proposals[i] instanceof ICompletionProposalExtension2) { | |
1454 | |
1455 ICompletionProposalExtension2 p= (ICompletionProposalExtension2) proposals[i]; | |
1456 if (p.validate(document, offset, event)) | |
1457 filtered.add(p); | |
1458 | |
1459 } else if (proposals[i] instanceof ICompletionProposalExtension) { | |
1460 | |
1461 ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i]; | |
1462 if (p.isValidFor(document, offset)) | |
1463 filtered.add(p); | |
1464 | |
1465 } else { | |
1466 // restore original behavior | |
1467 fIsFilteredSubset= false; | |
1468 fInvocationOffset= offset; | |
1469 fContentAssistant.fireSessionRestartEvent(); | |
1470 fComputedProposals= computeProposals(fInvocationOffset); | |
1471 return fComputedProposals; | |
1472 } | |
1473 } | |
1474 | |
1475 return (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]); | |
1476 } | |
1477 | |
1478 /** | |
1479 * Requests the proposal shell to take focus. | |
1480 * | |
1481 * @since 3.0 | |
1482 */ | |
1483 public void setFocus() { | |
1484 if (Helper.okToUse(fProposalShell)) { | |
1485 fProposalShell.setFocus(); | |
1486 } | |
1487 } | |
1488 | |
1489 /** | |
1490 * Returns <code>true</code> if <code>proposal</code> should be auto-inserted, | |
1491 * <code>false</code> otherwise. | |
1492 * | |
1493 * @param proposal the single proposal that might be automatically inserted | |
1494 * @return <code>true</code> if <code>proposal</code> can be inserted automatically, | |
1495 * <code>false</code> otherwise | |
1496 * @since 3.1 | |
1497 */ | |
1498 private bool canAutoInsert(ICompletionProposal proposal) { | |
1499 if (fContentAssistant.isAutoInserting()) { | |
1500 if (proposal instanceof ICompletionProposalExtension4) { | |
1501 ICompletionProposalExtension4 ext= (ICompletionProposalExtension4) proposal; | |
1502 return ext.isAutoInsertable(); | |
1503 } | |
1504 return true; // default behavior before ICompletionProposalExtension4 was introduced | |
1505 } | |
1506 return false; | |
1507 } | |
1508 | |
1509 /** | |
1510 * Completes the common prefix of all proposals directly in the code. If no | |
1511 * common prefix can be found, the proposal popup is shown. | |
1512 * | |
1513 * @return an error message if completion failed. | |
1514 * @since 3.0 | |
1515 */ | |
1516 public String incrementalComplete() { | |
1517 if (Helper.okToUse(fProposalShell) && fFilteredProposals !is null) { | |
1518 if (fLastCompletionOffset is fFilterOffset) { | |
1519 handleRepeatedInvocation(); | |
1520 } else { | |
1521 fLastCompletionOffset= fFilterOffset; | |
1522 completeCommonPrefix(); | |
1523 } | |
1524 } else { | |
1525 final Control control= fContentAssistSubjectControlAdapter.getControl(); | |
1526 | |
1527 if (fKeyListener is null) | |
1528 fKeyListener= new ProposalSelectionListener(); | |
1529 | |
1530 if (!Helper.okToUse(fProposalShell) && !control.isDisposed()) | |
1531 fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener); | |
1532 | |
1533 BusyIndicator.showWhile(control.getDisplay(), new Runnable() { | |
1534 public void run() { | |
1535 | |
1536 fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x; | |
1537 fFilterOffset= fInvocationOffset; | |
1538 fLastCompletionOffset= fFilterOffset; | |
1539 fFilteredProposals= computeProposals(fInvocationOffset); | |
1540 | |
1541 int count= (fFilteredProposals is null ? 0 : fFilteredProposals.length); | |
1542 if (count is 0 && hideWhenNoProposals(false)) | |
1543 return; | |
1544 | |
1545 if (count is 1 && canAutoInsert(fFilteredProposals[0])) { | |
1546 insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset); | |
1547 hide(); | |
1548 } else { | |
1549 ensureDocumentListenerInstalled(); | |
1550 if (count > 0 && completeCommonPrefix()) | |
1551 hide(); | |
1552 else { | |
1553 fComputedProposals= fFilteredProposals; | |
1554 createProposalSelector(); | |
1555 setProposals(fComputedProposals, false); | |
1556 displayProposals(); | |
1557 } | |
1558 } | |
1559 } | |
1560 }); | |
1561 } | |
1562 return getErrorMessage(); | |
1563 } | |
1564 | |
1565 /** | |
1566 * Acts upon <code>fFilteredProposals</code>: if there is just one valid | |
1567 * proposal, it is inserted, otherwise, the common prefix of all proposals | |
1568 * is inserted into the document. If there is no common prefix, nothing | |
1569 * happens and <code>false</code> is returned. | |
1570 * | |
1571 * @return <code>true</code> if a single proposal was inserted and the | |
1572 * selector can be closed, <code>false</code> otherwise | |
1573 * @since 3.0 | |
1574 */ | |
1575 private bool completeCommonPrefix() { | |
1576 | |
1577 // 0: insert single proposals | |
1578 if (fFilteredProposals.length is 1) { | |
1579 if (canAutoInsert(fFilteredProposals[0])) { | |
1580 insertProposal(fFilteredProposals[0], (char) 0, 0, fFilterOffset); | |
1581 hide(); | |
1582 return true; | |
1583 } | |
1584 return false; | |
1585 } | |
1586 | |
1587 // 1: extract pre- and postfix from all remaining proposals | |
1588 IDocument document= fContentAssistSubjectControlAdapter.getDocument(); | |
1589 | |
1590 // contains the common postfix in the case that there are any proposals matching our LHS | |
1591 StringBuffer rightCasePostfix= null; | |
1592 List rightCase= new ArrayList(); | |
1593 | |
1594 bool isWrongCaseMatch= false; | |
1595 | |
1596 // the prefix of all case insensitive matches. This differs from the document | |
1597 // contents and will be replaced. | |
1598 CharSequence wrongCasePrefix= null; | |
1599 int wrongCasePrefixStart= 0; | |
1600 // contains the common postfix of all case-insensitive matches | |
1601 StringBuffer wrongCasePostfix= null; | |
1602 List wrongCase= new ArrayList(); | |
1603 | |
1604 for (int i= 0; i < fFilteredProposals.length; i++) { | |
1605 ICompletionProposal proposal= fFilteredProposals[i]; | |
1606 | |
1607 if (!(proposal instanceof ICompletionProposalExtension3)) | |
1608 return false; | |
1609 | |
1610 int start= ((ICompletionProposalExtension3)proposal).getPrefixCompletionStart(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset); | |
1611 CharSequence insertion= ((ICompletionProposalExtension3)proposal).getPrefixCompletionText(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset); | |
1612 if (insertion is null) | |
1613 insertion= proposal.getDisplayString(); | |
1614 try { | |
1615 int prefixLength= fFilterOffset - start; | |
1616 int relativeCompletionOffset= Math.min(insertion.length(), prefixLength); | |
1617 String prefix= document.get(start, prefixLength); | |
1618 if (!isWrongCaseMatch && insertion.toString().startsWith(prefix)) { | |
1619 isWrongCaseMatch= false; | |
1620 rightCase.add(proposal); | |
1621 CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length()); | |
1622 if (rightCasePostfix is null) | |
1623 rightCasePostfix= new StringBuffer(newPostfix.toString()); | |
1624 else | |
1625 truncatePostfix(rightCasePostfix, newPostfix); | |
1626 } else if (i is 0 || isWrongCaseMatch) { | |
1627 CharSequence newPrefix= insertion.subSequence(0, relativeCompletionOffset); | |
1628 if (isPrefixCompatible(wrongCasePrefix, wrongCasePrefixStart, newPrefix, start, document)) { | |
1629 isWrongCaseMatch= true; | |
1630 wrongCasePrefix= newPrefix; | |
1631 wrongCasePrefixStart= start; | |
1632 CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length()); | |
1633 if (wrongCasePostfix is null) | |
1634 wrongCasePostfix= new StringBuffer(newPostfix.toString()); | |
1635 else | |
1636 truncatePostfix(wrongCasePostfix, newPostfix); | |
1637 wrongCase.add(proposal); | |
1638 } else { | |
1639 return false; | |
1640 } | |
1641 } else | |
1642 return false; | |
1643 } catch (BadLocationException e2) { | |
1644 // bail out silently | |
1645 return false; | |
1646 } | |
1647 | |
1648 if (rightCasePostfix !is null && rightCasePostfix.length() is 0 && rightCase.size() > 1) | |
1649 return false; | |
1650 } | |
1651 | |
1652 // 2: replace single proposals | |
1653 | |
1654 if (rightCase.size() is 1) { | |
1655 ICompletionProposal proposal= (ICompletionProposal) rightCase.get(0); | |
1656 if (canAutoInsert(proposal) && rightCasePostfix.length() > 0) { | |
1657 insertProposal(proposal, (char) 0, 0, fInvocationOffset); | |
1658 hide(); | |
1659 return true; | |
1660 } | |
1661 return false; | |
1662 } else if (isWrongCaseMatch && wrongCase.size() is 1) { | |
1663 ICompletionProposal proposal= (ICompletionProposal) wrongCase.get(0); | |
1664 if (canAutoInsert(proposal)) { | |
1665 insertProposal(proposal, (char) 0, 0, fInvocationOffset); | |
1666 hide(); | |
1667 return true; | |
1668 } | |
1669 return false; | |
1670 } | |
1671 | |
1672 // 3: replace post- / prefixes | |
1673 | |
1674 CharSequence prefix; | |
1675 if (isWrongCaseMatch) | |
1676 prefix= wrongCasePrefix; | |
1677 else | |
1678 prefix= ""; //$NON-NLS-1$ | |
1679 | |
1680 CharSequence postfix; | |
1681 if (isWrongCaseMatch) | |
1682 postfix= wrongCasePostfix; | |
1683 else | |
1684 postfix= rightCasePostfix; | |
1685 | |
1686 if (prefix is null || postfix is null) | |
1687 return false; | |
1688 | |
1689 try { | |
1690 // 4: check if parts of the postfix are already in the document | |
1691 int to= Math.min(document.getLength(), fFilterOffset + postfix.length()); | |
1692 StringBuffer inDocument= new StringBuffer(document.get(fFilterOffset, to - fFilterOffset)); | |
1693 truncatePostfix(inDocument, postfix); | |
1694 | |
1695 // 5: replace and reveal | |
1696 document.replace(fFilterOffset - prefix.length(), prefix.length() + inDocument.length(), prefix.toString() + postfix.toString()); | |
1697 | |
1698 fContentAssistSubjectControlAdapter.setSelectedRange(fFilterOffset + postfix.length(), 0); | |
1699 fContentAssistSubjectControlAdapter.revealRange(fFilterOffset + postfix.length(), 0); | |
1700 fFilterOffset+= postfix.length(); | |
1701 fLastCompletionOffset= fFilterOffset; | |
1702 | |
1703 return false; | |
1704 } catch (BadLocationException e) { | |
1705 // ignore and return false | |
1706 return false; | |
1707 } | |
1708 } | |
1709 | |
1710 /* | |
1711 * @since 3.1 | |
1712 */ | |
1713 private bool isPrefixCompatible(CharSequence oneSequence, int oneOffset, CharSequence twoSequence, int twoOffset, IDocument document) throws BadLocationException { | |
1714 if (oneSequence is null || twoSequence is null) | |
1715 return true; | |
1716 | |
1717 int min= Math.min(oneOffset, twoOffset); | |
1718 int oneEnd= oneOffset + oneSequence.length(); | |
1719 int twoEnd= twoOffset + twoSequence.length(); | |
1720 | |
1721 String one= document.get(oneOffset, min - oneOffset) + oneSequence + document.get(oneEnd, Math.min(fFilterOffset, fFilterOffset - oneEnd)); | |
1722 String two= document.get(twoOffset, min - twoOffset) + twoSequence + document.get(twoEnd, Math.min(fFilterOffset, fFilterOffset - twoEnd)); | |
1723 | |
1724 return one.equals(two); | |
1725 } | |
1726 | |
1727 /** | |
1728 * Truncates <code>buffer</code> to the common prefix of <code>buffer</code> | |
1729 * and <code>sequence</code>. | |
1730 * | |
1731 * @param buffer the common postfix to truncate | |
1732 * @param sequence the characters to truncate with | |
1733 */ | |
1734 private void truncatePostfix(StringBuffer buffer, CharSequence sequence) { | |
1735 // find common prefix | |
1736 int min= Math.min(buffer.length(), sequence.length()); | |
1737 for (int c= 0; c < min; c++) { | |
1738 if (sequence.charAt(c) !is buffer.charAt(c)) { | |
1739 buffer.delete(c, buffer.length()); | |
1740 return; | |
1741 } | |
1742 } | |
1743 | |
1744 // all equal up to minimum | |
1745 buffer.delete(min, buffer.length()); | |
1746 } | |
1747 | |
1748 /** | |
1749 * Sets the message for the repetition affordance text at the bottom of the proposal. Only has | |
1750 * an effect if {@link ContentAssistant#isRepeatedInvocationMode()} returns <code>true</code>. | |
1751 * | |
1752 * @param message the new caption | |
1753 * @since 3.2 | |
1754 */ | |
1755 void setMessage(String message) { | |
1756 Assert.isNotNull(message); | |
1757 if (isActive() && fMessageText !is null) | |
1758 fMessageText.setText(message + " "); //$NON-NLS-1$ | |
1759 } | |
1760 | |
1761 /** | |
1762 * Sets the text to be displayed if no proposals are available. Only has an effect if | |
1763 * {@link ContentAssistant#isShowEmptyList()} returns <code>true</code>. | |
1764 * | |
1765 * @param message the empty message | |
1766 * @since 3.2 | |
1767 */ | |
1768 void setEmptyMessage(String message) { | |
1769 Assert.isNotNull(message); | |
1770 fEmptyMessage= message; | |
1771 } | |
1772 | |
1773 /** | |
1774 * Enables or disables showing of the caption line. See also {@link #setMessage(String)}. | |
1775 * | |
1776 * @param show | |
1777 * @since 3.2 | |
1778 */ | |
1779 public void setStatusLineVisible(bool show) { | |
1780 if (!isActive() || show is (fMessageText !is null)) | |
1781 return; // nothing to do | |
1782 | |
1783 if (show) { | |
1784 createMessageText(); | |
1785 } else { | |
1786 fMessageText.dispose(); | |
1787 fMessageText= null; | |
1788 } | |
1789 fProposalShell.layout(); | |
1790 } | |
1791 | |
1792 /** | |
1793 * Informs the popup that it is being placed above the caret line instead of below. | |
1794 * | |
1795 * @param above <code>true</code> if the location of the popup is above the caret line, <code>false</code> if it is below | |
1796 * @since 3.3 | |
1797 */ | |
1798 void switchedPositionToAbove(bool above) { | |
1799 if (fAdditionalInfoController !is null) { | |
1800 fAdditionalInfoController.setFallbackAnchors(new Anchor[] { | |
1801 AbstractInformationControlManager.ANCHOR_RIGHT, | |
1802 AbstractInformationControlManager.ANCHOR_LEFT, | |
1803 above ? AbstractInformationControlManager.ANCHOR_TOP : AbstractInformationControlManager.ANCHOR_BOTTOM | |
1804 }); | |
1805 } | |
1806 } | |
1807 | |
1808 /** | |
1809 * Returns a new proposal selection handler. | |
1810 * | |
1811 * @param operationCode the operation code | |
1812 * @return the handler | |
1813 * @since 3.4 | |
1814 */ | |
1815 IHandler createProposalSelectionHandler(int operationCode) { | |
1816 return new ProposalSelectionHandler(operationCode); | |
1817 } | |
1818 | |
1819 } |