Mercurial > projects > dwt2
comparison org.eclipse.jface/src/org/eclipse/jface/fieldassist/ContentProposalAdapter.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 | 6f068362a363 |
comparison
equal
deleted
inserted
replaced
11:43904fec5dca | 12:bc29606a740c |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2005, 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 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module org.eclipse.jface.fieldassist.ContentProposalAdapter; | |
14 | |
15 import org.eclipse.jface.fieldassist.IContentProposal; | |
16 import org.eclipse.jface.fieldassist.IContentProposalProvider; | |
17 import org.eclipse.jface.fieldassist.IControlContentAdapter; | |
18 import org.eclipse.jface.fieldassist.IControlContentAdapter2; | |
19 import org.eclipse.jface.fieldassist.IContentProposalListener; | |
20 import org.eclipse.jface.fieldassist.IContentProposalListener2; | |
21 | |
22 | |
23 import org.eclipse.swt.SWT; | |
24 import org.eclipse.swt.events.DisposeEvent; | |
25 import org.eclipse.swt.events.DisposeListener; | |
26 import org.eclipse.swt.events.FocusAdapter; | |
27 import org.eclipse.swt.events.FocusEvent; | |
28 import org.eclipse.swt.events.SelectionEvent; | |
29 import org.eclipse.swt.events.SelectionListener; | |
30 import org.eclipse.swt.graphics.Color; | |
31 import org.eclipse.swt.graphics.Image; | |
32 import org.eclipse.swt.graphics.Point; | |
33 import org.eclipse.swt.graphics.Rectangle; | |
34 import org.eclipse.swt.layout.GridData; | |
35 import org.eclipse.swt.widgets.Composite; | |
36 import org.eclipse.swt.widgets.Control; | |
37 import org.eclipse.swt.widgets.Event; | |
38 import org.eclipse.swt.widgets.Listener; | |
39 import org.eclipse.swt.widgets.ScrollBar; | |
40 import org.eclipse.swt.widgets.Shell; | |
41 import org.eclipse.swt.widgets.Table; | |
42 import org.eclipse.swt.widgets.TableItem; | |
43 import org.eclipse.swt.widgets.Text; | |
44 import org.eclipse.core.runtime.Assert; | |
45 import org.eclipse.core.runtime.ListenerList; | |
46 import org.eclipse.jface.bindings.keys.KeyStroke; | |
47 import org.eclipse.jface.dialogs.PopupDialog; | |
48 import org.eclipse.jface.preference.JFacePreferences; | |
49 import org.eclipse.jface.resource.JFaceResources; | |
50 import org.eclipse.jface.viewers.ILabelProvider; | |
51 | |
52 import java.lang.all; | |
53 import java.util.Set; | |
54 import java.lang.JThread; | |
55 static import tango.text.Text; | |
56 import tango.io.Stdout; | |
57 import tango.text.convert.Format; | |
58 alias tango.text.Text.Text!(char) StringBuffer; | |
59 /** | |
60 * ContentProposalAdapter can be used to attach content proposal behavior to a | |
61 * control. This behavior includes obtaining proposals, opening a popup dialog, | |
62 * managing the content of the control relative to the selections in the popup, | |
63 * and optionally opening up a secondary popup to further describe proposals. | |
64 * <p> | |
65 * A number of configurable options are provided to determine how the control | |
66 * content is altered when a proposal is chosen, how the content proposal popup | |
67 * is activated, and whether any filtering should be done on the proposals as | |
68 * the user types characters. | |
69 * <p> | |
70 * This class is not intended to be subclassed. | |
71 * | |
72 * @since 3.2 | |
73 */ | |
74 public class ContentProposalAdapter { | |
75 | |
76 /* | |
77 * The lightweight popup used to show content proposals for a text field. If | |
78 * additional information exists for a proposal, then selecting that | |
79 * proposal will result in the information being displayed in a secondary | |
80 * popup. | |
81 */ | |
82 class ContentProposalPopup : PopupDialog { | |
83 /* | |
84 * The listener we install on the popup and related controls to | |
85 * determine when to close the popup. Some events (move, resize, close, | |
86 * deactivate) trigger closure as soon as they are received, simply | |
87 * because one of the registered listeners received them. Other events | |
88 * depend on additional circumstances. | |
89 */ | |
90 private final class PopupCloserListener : Listener { | |
91 private bool scrollbarClicked = false; | |
92 | |
93 public void handleEvent(Event e) { | |
94 | |
95 // If focus is leaving an important widget or the field's | |
96 // shell is deactivating | |
97 if (e.type is SWT.FocusOut) { | |
98 scrollbarClicked = false; | |
99 /* | |
100 * Ignore this event if it's only happening because focus is | |
101 * moving between the popup shells, their controls, or a | |
102 * scrollbar. Do this in an async since the focus is not | |
103 * actually switched when this event is received. | |
104 */ | |
105 e.display.asyncExec(new class(e) Runnable { | |
106 Event e_; | |
107 this(Event e__){ e_=e__; } | |
108 public void run() { | |
109 if (isValid()) { | |
110 if (scrollbarClicked || hasFocus()) { | |
111 return; | |
112 } | |
113 // Workaround a problem on X and Mac, whereby at | |
114 // this point, the focus control is not known. | |
115 // This can happen, for example, when resizing | |
116 // the popup shell on the Mac. | |
117 // Check the active shell. | |
118 Shell activeShell = e_.display.getActiveShell(); | |
119 if (activeShell is getShell() | |
120 || (infoPopup !is null && infoPopup | |
121 .getShell() is activeShell)) { | |
122 return; | |
123 } | |
124 /* | |
125 * System.out.println(e); | |
126 * System.out.println(e.display.getFocusControl()); | |
127 * System.out.println(e.display.getActiveShell()); | |
128 */ | |
129 close(); | |
130 } | |
131 } | |
132 }); | |
133 return; | |
134 } | |
135 | |
136 // Scroll bar has been clicked. Remember this for focus event | |
137 // processing. | |
138 if (e.type is SWT.Selection) { | |
139 scrollbarClicked = true; | |
140 return; | |
141 } | |
142 // For all other events, merely getting them dictates closure. | |
143 close(); | |
144 } | |
145 | |
146 // Install the listeners for events that need to be monitored for | |
147 // popup closure. | |
148 void installListeners() { | |
149 // Listeners on this popup's table and scroll bar | |
150 proposalTable.addListener(SWT.FocusOut, this); | |
151 ScrollBar scrollbar = proposalTable.getVerticalBar(); | |
152 if (scrollbar !is null) { | |
153 scrollbar.addListener(SWT.Selection, this); | |
154 } | |
155 | |
156 // Listeners on this popup's shell | |
157 getShell().addListener(SWT.Deactivate, this); | |
158 getShell().addListener(SWT.Close, this); | |
159 | |
160 // Listeners on the target control | |
161 control.addListener(SWT.MouseDoubleClick, this); | |
162 control.addListener(SWT.MouseDown, this); | |
163 control.addListener(SWT.Dispose, this); | |
164 control.addListener(SWT.FocusOut, this); | |
165 // Listeners on the target control's shell | |
166 Shell controlShell = control.getShell(); | |
167 controlShell.addListener(SWT.Move, this); | |
168 controlShell.addListener(SWT.Resize, this); | |
169 | |
170 } | |
171 | |
172 // Remove installed listeners | |
173 void removeListeners() { | |
174 if (isValid()) { | |
175 proposalTable.removeListener(SWT.FocusOut, this); | |
176 ScrollBar scrollbar = proposalTable.getVerticalBar(); | |
177 if (scrollbar !is null) { | |
178 scrollbar.removeListener(SWT.Selection, this); | |
179 } | |
180 | |
181 getShell().removeListener(SWT.Deactivate, this); | |
182 getShell().removeListener(SWT.Close, this); | |
183 } | |
184 | |
185 if (control !is null && !control.isDisposed()) { | |
186 | |
187 control.removeListener(SWT.MouseDoubleClick, this); | |
188 control.removeListener(SWT.MouseDown, this); | |
189 control.removeListener(SWT.Dispose, this); | |
190 control.removeListener(SWT.FocusOut, this); | |
191 | |
192 Shell controlShell = control.getShell(); | |
193 controlShell.removeListener(SWT.Move, this); | |
194 controlShell.removeListener(SWT.Resize, this); | |
195 } | |
196 } | |
197 } | |
198 | |
199 /* | |
200 * The listener we will install on the target control. | |
201 */ | |
202 private final class TargetControlListener : Listener { | |
203 // Key events from the control | |
204 public void handleEvent(Event e) { | |
205 if (!isValid()) { | |
206 return; | |
207 } | |
208 | |
209 char key = e.character; | |
210 | |
211 // Traverse events are handled depending on whether the | |
212 // event has a character. | |
213 if (e.type is SWT.Traverse) { | |
214 // If the traverse event contains a legitimate character, | |
215 // then we must set doit false so that the widget will | |
216 // receive the key event. We return immediately so that | |
217 // the character is handled only in the key event. | |
218 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132101 | |
219 if (key !is 0) { | |
220 e.doit = false; | |
221 return; | |
222 } | |
223 // Traversal does not contain a character. Set doit true | |
224 // to indicate TRAVERSE_NONE will occur and that no key | |
225 // event will be triggered. We will check for navigation | |
226 // keys below. | |
227 e.detail = SWT.TRAVERSE_NONE; | |
228 e.doit = true; | |
229 } else { | |
230 // Default is to only propagate when configured that way. | |
231 // Some keys will always set doit to false anyway. | |
232 e.doit = propagateKeys; | |
233 } | |
234 | |
235 // No character. Check for navigation keys. | |
236 | |
237 if (key is 0) { | |
238 int newSelection = proposalTable.getSelectionIndex(); | |
239 int visibleRows = (proposalTable.getSize().y / proposalTable | |
240 .getItemHeight()) - 1; | |
241 switch (e.keyCode) { | |
242 case SWT.ARROW_UP: | |
243 newSelection -= 1; | |
244 if (newSelection < 0) { | |
245 newSelection = proposalTable.getItemCount() - 1; | |
246 } | |
247 // Not typical - usually we get this as a Traverse and | |
248 // therefore it never propagates. Added for consistency. | |
249 if (e.type is SWT.KeyDown) { | |
250 // don't propagate to control | |
251 e.doit = false; | |
252 } | |
253 | |
254 break; | |
255 | |
256 case SWT.ARROW_DOWN: | |
257 newSelection += 1; | |
258 if (newSelection > proposalTable.getItemCount() - 1) { | |
259 newSelection = 0; | |
260 } | |
261 // Not typical - usually we get this as a Traverse and | |
262 // therefore it never propagates. Added for consistency. | |
263 if (e.type is SWT.KeyDown) { | |
264 // don't propagate to control | |
265 e.doit = false; | |
266 } | |
267 | |
268 break; | |
269 | |
270 case SWT.PAGE_DOWN: | |
271 newSelection += visibleRows; | |
272 if (newSelection >= proposalTable.getItemCount()) { | |
273 newSelection = proposalTable.getItemCount() - 1; | |
274 } | |
275 if (e.type is SWT.KeyDown) { | |
276 // don't propagate to control | |
277 e.doit = false; | |
278 } | |
279 break; | |
280 | |
281 case SWT.PAGE_UP: | |
282 newSelection -= visibleRows; | |
283 if (newSelection < 0) { | |
284 newSelection = 0; | |
285 } | |
286 if (e.type is SWT.KeyDown) { | |
287 // don't propagate to control | |
288 e.doit = false; | |
289 } | |
290 break; | |
291 | |
292 case SWT.HOME: | |
293 newSelection = 0; | |
294 if (e.type is SWT.KeyDown) { | |
295 // don't propagate to control | |
296 e.doit = false; | |
297 } | |
298 break; | |
299 | |
300 case SWT.END: | |
301 newSelection = proposalTable.getItemCount() - 1; | |
302 if (e.type is SWT.KeyDown) { | |
303 // don't propagate to control | |
304 e.doit = false; | |
305 } | |
306 break; | |
307 | |
308 // If received as a Traverse, these should propagate | |
309 // to the control as keydown. If received as a keydown, | |
310 // proposals should be recomputed since the cursor | |
311 // position has changed. | |
312 case SWT.ARROW_LEFT: | |
313 case SWT.ARROW_RIGHT: | |
314 if (e.type is SWT.Traverse) { | |
315 e.doit = false; | |
316 } else { | |
317 e.doit = true; | |
318 String contents = getControlContentAdapter() | |
319 .getControlContents(getControl()); | |
320 // If there are no contents, changes in cursor | |
321 // position have no effect. Note also that we do | |
322 // not affect the filter text on ARROW_LEFT as | |
323 // we would with BS. | |
324 if (contents.length > 0) { | |
325 asyncRecomputeProposals(filterText); | |
326 } | |
327 } | |
328 break; | |
329 | |
330 // Any unknown keycodes will cause the popup to close. | |
331 // Modifier keys are explicitly checked and ignored because | |
332 // they are not complete yet (no character). | |
333 default: | |
334 if (e.keyCode !is SWT.CAPS_LOCK && e.keyCode !is SWT.MOD1 | |
335 && e.keyCode !is SWT.MOD2 | |
336 && e.keyCode !is SWT.MOD3 | |
337 && e.keyCode !is SWT.MOD4) { | |
338 close(); | |
339 } | |
340 return; | |
341 } | |
342 | |
343 // If any of these navigation events caused a new selection, | |
344 // then handle that now and return. | |
345 if (newSelection >= 0) { | |
346 selectProposal(newSelection); | |
347 } | |
348 return; | |
349 } | |
350 | |
351 // key !is 0 | |
352 // Check for special keys involved in cancelling, accepting, or | |
353 // filtering the proposals. | |
354 switch (key) { | |
355 case SWT.ESC: | |
356 e.doit = false; | |
357 close(); | |
358 break; | |
359 | |
360 case SWT.LF: | |
361 case SWT.CR: | |
362 e.doit = false; | |
363 Object p = cast(Object)getSelectedProposal(); | |
364 if (p !is null) { | |
365 acceptCurrentProposal(); | |
366 } else { | |
367 close(); | |
368 } | |
369 break; | |
370 | |
371 case SWT.TAB: | |
372 e.doit = false; | |
373 getShell().setFocus(); | |
374 return; | |
375 | |
376 case SWT.BS: | |
377 // Backspace should back out of any stored filter text | |
378 if (filterStyle !is FILTER_NONE) { | |
379 // We have no filter to back out of, so do nothing | |
380 if (filterText.length is 0) { | |
381 return; | |
382 } | |
383 // There is filter to back out of | |
384 filterText = filterText.substring(0, filterText | |
385 .length - 1); | |
386 asyncRecomputeProposals(filterText); | |
387 return; | |
388 } | |
389 // There is no filtering provided by us, but some | |
390 // clients provide their own filtering based on content. | |
391 // Recompute the proposals if the cursor position | |
392 // will change (is not at 0). | |
393 int pos = getControlContentAdapter().getCursorPosition( | |
394 getControl()); | |
395 // We rely on the fact that the contents and pos do not yet | |
396 // reflect the result of the BS. If the contents were | |
397 // already empty, then BS should not cause | |
398 // a recompute. | |
399 if (pos > 0) { | |
400 asyncRecomputeProposals(filterText); | |
401 } | |
402 break; | |
403 | |
404 default: | |
405 // If the key is a defined unicode character, and not one of | |
406 // the special cases processed above, update the filter text | |
407 // and filter the proposals. | |
408 if (CharacterIsDefined(key)) { | |
409 if (filterStyle is FILTER_CUMULATIVE) { | |
410 filterText = filterText ~ dcharToString(key); | |
411 } else if (filterStyle is FILTER_CHARACTER) { | |
412 filterText = dcharToString(key); | |
413 } | |
414 // Recompute proposals after processing this event. | |
415 asyncRecomputeProposals(filterText); | |
416 } | |
417 break; | |
418 } | |
419 } | |
420 } | |
421 | |
422 /* | |
423 * Internal class used to implement the secondary popup. | |
424 */ | |
425 private class InfoPopupDialog : PopupDialog { | |
426 | |
427 /* | |
428 * The text control that displays the text. | |
429 */ | |
430 private Text text; | |
431 | |
432 /* | |
433 * The String shown in the popup. | |
434 */ | |
435 private String contents = EMPTY; | |
436 | |
437 /* | |
438 * Construct an info-popup with the specified parent. | |
439 */ | |
440 this(Shell parent) { | |
441 super(parent, PopupDialog.HOVER_SHELLSTYLE, false, false, | |
442 false, false, null, null); | |
443 } | |
444 | |
445 /* | |
446 * Create a text control for showing the info about a proposal. | |
447 */ | |
448 protected override Control createDialogArea(Composite parent) { | |
449 text = new Text(parent, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | |
450 | SWT.NO_FOCUS); | |
451 | |
452 // Use the compact margins employed by PopupDialog. | |
453 GridData gd = new GridData(GridData.BEGINNING | |
454 | GridData.FILL_BOTH); | |
455 gd.horizontalIndent = PopupDialog.POPUP_HORIZONTALSPACING; | |
456 gd.verticalIndent = PopupDialog.POPUP_VERTICALSPACING; | |
457 text.setLayoutData(gd); | |
458 text.setText(contents); | |
459 | |
460 // since SWT.NO_FOCUS is only a hint... | |
461 text.addFocusListener(new class FocusAdapter { | |
462 public void focusGained(FocusEvent event) { | |
463 this.outer.close(); | |
464 } | |
465 }); | |
466 return text; | |
467 } | |
468 | |
469 /* | |
470 * Adjust the bounds so that we appear adjacent to our parent shell | |
471 */ | |
472 protected override void adjustBounds() { | |
473 Rectangle parentBounds = getParentShell().getBounds(); | |
474 Rectangle proposedBounds; | |
475 // Try placing the info popup to the right | |
476 Rectangle rightProposedBounds = new Rectangle(parentBounds.x | |
477 + parentBounds.width | |
478 + PopupDialog.POPUP_HORIZONTALSPACING, parentBounds.y | |
479 + PopupDialog.POPUP_VERTICALSPACING, | |
480 parentBounds.width, parentBounds.height); | |
481 rightProposedBounds = getConstrainedShellBounds(rightProposedBounds); | |
482 // If it won't fit on the right, try the left | |
483 if (rightProposedBounds.intersects(parentBounds)) { | |
484 Rectangle leftProposedBounds = new Rectangle(parentBounds.x | |
485 - parentBounds.width - POPUP_HORIZONTALSPACING - 1, | |
486 parentBounds.y, parentBounds.width, | |
487 parentBounds.height); | |
488 leftProposedBounds = getConstrainedShellBounds(leftProposedBounds); | |
489 // If it won't fit on the left, choose the proposed bounds | |
490 // that fits the best | |
491 if (leftProposedBounds.intersects(parentBounds)) { | |
492 if (rightProposedBounds.x - parentBounds.x >= parentBounds.x | |
493 - leftProposedBounds.x) { | |
494 rightProposedBounds.x = parentBounds.x | |
495 + parentBounds.width | |
496 + PopupDialog.POPUP_HORIZONTALSPACING; | |
497 proposedBounds = rightProposedBounds; | |
498 } else { | |
499 leftProposedBounds.width = parentBounds.x | |
500 - POPUP_HORIZONTALSPACING | |
501 - leftProposedBounds.x; | |
502 proposedBounds = leftProposedBounds; | |
503 } | |
504 } else { | |
505 // use the proposed bounds on the left | |
506 proposedBounds = leftProposedBounds; | |
507 } | |
508 } else { | |
509 // use the proposed bounds on the right | |
510 proposedBounds = rightProposedBounds; | |
511 } | |
512 getShell().setBounds(proposedBounds); | |
513 } | |
514 | |
515 /* | |
516 * (non-Javadoc) | |
517 * @see org.eclipse.jface.dialogs.PopupDialog#getForeground() | |
518 */ | |
519 protected Color getForeground() { | |
520 return control.getDisplay(). | |
521 getSystemColor(SWT.COLOR_INFO_FOREGROUND); | |
522 } | |
523 | |
524 /* | |
525 * (non-Javadoc) | |
526 * @see org.eclipse.jface.dialogs.PopupDialog#getBackground() | |
527 */ | |
528 protected Color getBackground() { | |
529 return control.getDisplay(). | |
530 getSystemColor(SWT.COLOR_INFO_BACKGROUND); | |
531 } | |
532 | |
533 /* | |
534 * Set the text contents of the popup. | |
535 */ | |
536 void setContents(String newContents) { | |
537 if (newContents is null) { | |
538 newContents = EMPTY; | |
539 } | |
540 this.contents = newContents; | |
541 if (text !is null && !text.isDisposed()) { | |
542 text.setText(contents); | |
543 } | |
544 } | |
545 | |
546 /* | |
547 * Return whether the popup has focus. | |
548 */ | |
549 bool hasFocus() { | |
550 if (text is null || text.isDisposed()) { | |
551 return false; | |
552 } | |
553 return text.getShell().isFocusControl() | |
554 || text.isFocusControl(); | |
555 } | |
556 } | |
557 | |
558 /* | |
559 * The listener installed on the target control. | |
560 */ | |
561 private Listener targetControlListener; | |
562 | |
563 /* | |
564 * The listener installed in order to close the popup. | |
565 */ | |
566 private PopupCloserListener popupCloser; | |
567 | |
568 /* | |
569 * The table used to show the list of proposals. | |
570 */ | |
571 private Table proposalTable; | |
572 | |
573 /* | |
574 * The proposals to be shown (cached to avoid repeated requests). | |
575 */ | |
576 private IContentProposal[] proposals; | |
577 | |
578 /* | |
579 * Secondary popup used to show detailed information about the selected | |
580 * proposal.. | |
581 */ | |
582 private InfoPopupDialog infoPopup; | |
583 | |
584 /* | |
585 * Flag indicating whether there is a pending secondary popup update. | |
586 */ | |
587 private bool pendingDescriptionUpdate = false; | |
588 | |
589 /* | |
590 * Filter text - tracked while popup is open, only if we are told to | |
591 * filter | |
592 */ | |
593 private String filterText = EMPTY; | |
594 | |
595 /** | |
596 * Constructs a new instance of this popup, specifying the control for | |
597 * which this popup is showing content, and how the proposals should be | |
598 * obtained and displayed. | |
599 * | |
600 * @param infoText | |
601 * Text to be shown in a lower info area, or | |
602 * <code>null</code> if there is no info area. | |
603 */ | |
604 this(String infoText, IContentProposal[] proposals) { | |
605 // IMPORTANT: Use of SWT.ON_TOP is critical here for ensuring | |
606 // that the target control retains focus on Mac and Linux. Without | |
607 // it, the focus will disappear, keystrokes will not go to the | |
608 // popup, and the popup closer will wrongly close the popup. | |
609 // On platforms where SWT.ON_TOP overrides SWT.RESIZE, we will live | |
610 // with this. | |
611 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=126138 | |
612 super(control.getShell(), SWT.RESIZE | SWT.ON_TOP, false, false, | |
613 false, false, null, infoText); | |
614 this.proposals = proposals; | |
615 } | |
616 | |
617 /* | |
618 * (non-Javadoc) | |
619 * @see org.eclipse.jface.dialogs.PopupDialog#getForeground() | |
620 */ | |
621 protected Color getForeground() { | |
622 return JFaceResources.getColorRegistry().get( | |
623 JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR); | |
624 } | |
625 | |
626 /* | |
627 * (non-Javadoc) | |
628 * @see org.eclipse.jface.dialogs.PopupDialog#getBackground() | |
629 */ | |
630 protected Color getBackground() { | |
631 return JFaceResources.getColorRegistry().get( | |
632 JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR); | |
633 } | |
634 | |
635 /* | |
636 * Creates the content area for the proposal popup. This creates a table | |
637 * and places it inside the composite. The table will contain a list of | |
638 * all the proposals. | |
639 * | |
640 * @param parent The parent composite to contain the dialog area; must | |
641 * not be <code>null</code>. | |
642 */ | |
643 protected override final Control createDialogArea(Composite parent) { | |
644 // Use virtual where appropriate (see flag definition). | |
645 if (USE_VIRTUAL) { | |
646 proposalTable = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL | |
647 | SWT.VIRTUAL); | |
648 | |
649 Listener listener = new class Listener { | |
650 public void handleEvent(Event event) { | |
651 handleSetData(event); | |
652 } | |
653 }; | |
654 proposalTable.addListener(SWT.SetData, listener); | |
655 } else { | |
656 proposalTable = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL); | |
657 } | |
658 | |
659 // set the proposals to force population of the table. | |
660 setProposals(filterProposals(proposals, filterText)); | |
661 | |
662 proposalTable.setHeaderVisible(false); | |
663 proposalTable.addSelectionListener(new class SelectionListener { | |
664 | |
665 public void widgetSelected(SelectionEvent e) { | |
666 // If a proposal has been selected, show it in the secondary | |
667 // popup. Otherwise close the popup. | |
668 if (e.item is null) { | |
669 if (infoPopup !is null) { | |
670 infoPopup.close(); | |
671 } | |
672 } else { | |
673 showProposalDescription(); | |
674 } | |
675 } | |
676 | |
677 // Default selection was made. Accept the current proposal. | |
678 public void widgetDefaultSelected(SelectionEvent e) { | |
679 acceptCurrentProposal(); | |
680 } | |
681 }); | |
682 return proposalTable; | |
683 } | |
684 | |
685 /* | |
686 * (non-Javadoc) | |
687 * | |
688 * @see org.eclipse.jface.dialogs.PopupDialog.adjustBounds() | |
689 */ | |
690 protected override void adjustBounds() { | |
691 // Get our control's location in display coordinates. | |
692 Point location = control.getDisplay().map(control.getParent(), | |
693 null, control.getLocation()); | |
694 int initialX = location.x + POPUP_OFFSET; | |
695 int initialY = location.y + control.getSize().y + POPUP_OFFSET; | |
696 // If we are inserting content, use the cursor position to | |
697 // position the control. | |
698 if (getProposalAcceptanceStyle() is PROPOSAL_INSERT) { | |
699 Rectangle insertionBounds = controlContentAdapter | |
700 .getInsertionBounds(control); | |
701 initialX = initialX + insertionBounds.x; | |
702 initialY = location.y + insertionBounds.y | |
703 + insertionBounds.height; | |
704 } | |
705 | |
706 // If there is no specified size, force it by setting | |
707 // up a layout on the table. | |
708 if (popupSize is null) { | |
709 GridData data = new GridData(GridData.FILL_BOTH); | |
710 data.heightHint = proposalTable.getItemHeight() | |
711 * POPUP_CHAR_HEIGHT; | |
712 data.widthHint = Math.max(control.getSize().x, | |
713 POPUP_MINIMUM_WIDTH); | |
714 proposalTable.setLayoutData(data); | |
715 getShell().pack(); | |
716 popupSize = getShell().getSize(); | |
717 } | |
718 getShell().setBounds(initialX, initialY, popupSize.x, popupSize.y); | |
719 | |
720 // Now set up a listener to monitor any changes in size. | |
721 getShell().addListener(SWT.Resize, new class Listener { | |
722 public void handleEvent(Event e) { | |
723 popupSize = getShell().getSize(); | |
724 if (infoPopup !is null) { | |
725 infoPopup.adjustBounds(); | |
726 } | |
727 } | |
728 }); | |
729 } | |
730 | |
731 /* | |
732 * Handle the set data event. Set the item data of the requested item to | |
733 * the corresponding proposal in the proposal cache. | |
734 */ | |
735 private void handleSetData(Event event) { | |
736 TableItem item = cast(TableItem) event.item; | |
737 int index = proposalTable.indexOf(item); | |
738 | |
739 if (0 <= index && index < proposals.length) { | |
740 IContentProposal current = proposals[index]; | |
741 item.setText(getString(current)); | |
742 item.setImage(getImage(current)); | |
743 item.setData(cast(Object)current); | |
744 } else { | |
745 // this should not happen, but does on win32 | |
746 } | |
747 } | |
748 | |
749 /* | |
750 * Caches the specified proposals and repopulates the table if it has | |
751 * been created. | |
752 */ | |
753 private void setProposals(IContentProposal[] newProposals) { | |
754 if (newProposals is null || newProposals.length is 0) { | |
755 newProposals = getEmptyProposalArray(); | |
756 } | |
757 this.proposals = newProposals; | |
758 | |
759 // If there is a table | |
760 if (isValid()) { | |
761 final int newSize = newProposals.length; | |
762 if (USE_VIRTUAL) { | |
763 // Set and clear the virtual table. Data will be | |
764 // provided in the SWT.SetData event handler. | |
765 proposalTable.setItemCount(newSize); | |
766 proposalTable.clearAll(); | |
767 } else { | |
768 // Populate the table manually | |
769 proposalTable.setRedraw(false); | |
770 proposalTable.setItemCount(newSize); | |
771 TableItem[] items = proposalTable.getItems(); | |
772 for (int i = 0; i < items.length; i++) { | |
773 TableItem item = items[i]; | |
774 IContentProposal proposal = newProposals[i]; | |
775 item.setText(getString(proposal)); | |
776 item.setImage(getImage(proposal)); | |
777 item.setData(cast(Object)proposal); | |
778 } | |
779 proposalTable.setRedraw(true); | |
780 } | |
781 // Default to the first selection if there is content. | |
782 if (newProposals.length > 0) { | |
783 selectProposal(0); | |
784 } else { | |
785 // No selection, close the secondary popup if it was open | |
786 if (infoPopup !is null) { | |
787 infoPopup.close(); | |
788 } | |
789 | |
790 } | |
791 } | |
792 } | |
793 | |
794 /* | |
795 * Get the string for the specified proposal. Always return a String of | |
796 * some kind. | |
797 */ | |
798 private String getString(IContentProposal proposal) { | |
799 if (proposal is null) { | |
800 return EMPTY; | |
801 } | |
802 if (labelProvider is null) { | |
803 return proposal.getLabel() is null ? proposal.getContent() | |
804 : proposal.getLabel(); | |
805 } | |
806 return labelProvider.getText(cast(Object)proposal); | |
807 } | |
808 | |
809 /* | |
810 * Get the image for the specified proposal. If there is no image | |
811 * available, return null. | |
812 */ | |
813 private Image getImage(IContentProposal proposal) { | |
814 if (proposal is null || labelProvider is null) { | |
815 return null; | |
816 } | |
817 return labelProvider.getImage(cast(Object)proposal); | |
818 } | |
819 | |
820 /* | |
821 * Return an empty array. Used so that something always shows in the | |
822 * proposal popup, even if no proposal provider was specified. | |
823 */ | |
824 private IContentProposal[] getEmptyProposalArray() { | |
825 return new IContentProposal[0]; | |
826 } | |
827 | |
828 /* | |
829 * Answer true if the popup is valid, which means the table has been | |
830 * created and not disposed. | |
831 */ | |
832 private bool isValid() { | |
833 return proposalTable !is null && !proposalTable.isDisposed(); | |
834 } | |
835 | |
836 /* | |
837 * Return whether the receiver has focus. Since 3.4, this includes a | |
838 * check for whether the info popup has focus. | |
839 */ | |
840 private bool hasFocus() { | |
841 if (!isValid()) { | |
842 return false; | |
843 } | |
844 if (getShell().isFocusControl() || proposalTable.isFocusControl()) { | |
845 return true; | |
846 } | |
847 if (infoPopup !is null && infoPopup.hasFocus()) { | |
848 return true; | |
849 } | |
850 return false; | |
851 } | |
852 | |
853 /* | |
854 * Return the current selected proposal. | |
855 */ | |
856 private IContentProposal getSelectedProposal() { | |
857 if (isValid()) { | |
858 int i = proposalTable.getSelectionIndex(); | |
859 if (proposals is null || i < 0 || i >= proposals.length) { | |
860 return null; | |
861 } | |
862 return proposals[i]; | |
863 } | |
864 return null; | |
865 } | |
866 | |
867 /* | |
868 * Select the proposal at the given index. | |
869 */ | |
870 private void selectProposal(int index) { | |
871 Assert | |
872 .isTrue(index >= 0, | |
873 "Proposal index should never be negative"); //$NON-NLS-1$ | |
874 if (!isValid() || proposals is null || index >= proposals.length) { | |
875 return; | |
876 } | |
877 proposalTable.setSelection(index); | |
878 proposalTable.showSelection(); | |
879 | |
880 showProposalDescription(); | |
881 } | |
882 | |
883 /** | |
884 * Opens this ContentProposalPopup. This method is extended in order to | |
885 * add the control listener when the popup is opened and to invoke the | |
886 * secondary popup if applicable. | |
887 * | |
888 * @return the return code | |
889 * | |
890 * @see org.eclipse.jface.window.Window#open() | |
891 */ | |
892 public override int open() { | |
893 int value = super.open(); | |
894 if (popupCloser is null) { | |
895 popupCloser = new PopupCloserListener(); | |
896 } | |
897 popupCloser.installListeners(); | |
898 IContentProposal p = getSelectedProposal(); | |
899 if (p !is null) { | |
900 showProposalDescription(); | |
901 } | |
902 return value; | |
903 } | |
904 | |
905 /** | |
906 * Closes this popup. This method is extended to remove the control | |
907 * listener. | |
908 * | |
909 * @return <code>true</code> if the window is (or was already) closed, | |
910 * and <code>false</code> if it is still open | |
911 */ | |
912 public override bool close() { | |
913 popupCloser.removeListeners(); | |
914 if (infoPopup !is null) { | |
915 infoPopup.close(); | |
916 } | |
917 bool ret = super.close(); | |
918 notifyPopupClosed(); | |
919 return ret; | |
920 } | |
921 | |
922 /* | |
923 * Show the currently selected proposal's description in a secondary | |
924 * popup. | |
925 */ | |
926 private void showProposalDescription() { | |
927 // If we do not already have a pending update, then | |
928 // create a thread now that will show the proposal description | |
929 if (!pendingDescriptionUpdate) { | |
930 // Create a thread that will sleep for the specified delay | |
931 // before creating the popup. We do not use Jobs since this | |
932 // code must be able to run independently of the Eclipse | |
933 // runtime. | |
934 auto r = new class() Runnable { | |
935 public void run() { | |
936 pendingDescriptionUpdate = true; | |
937 | |
938 try { | |
939 JThread.sleep( POPUP_DELAY ); | |
940 } | |
941 catch (InterruptedException e) { | |
942 } | |
943 | |
944 if (!isValid()) { | |
945 return; | |
946 } | |
947 getShell().getDisplay().syncExec(new class() Runnable { | |
948 public void run() { | |
949 // Query the current selection since we have | |
950 // been delayed | |
951 IContentProposal p = getSelectedProposal(); | |
952 if (p !is null) { | |
953 String description = p.getDescription(); | |
954 if (description !is null) { | |
955 if (infoPopup is null) { | |
956 infoPopup = new InfoPopupDialog( | |
957 getShell()); | |
958 infoPopup.open(); | |
959 infoPopup | |
960 .getShell() | |
961 .addDisposeListener( | |
962 new class DisposeListener { | |
963 public void widgetDisposed( | |
964 DisposeEvent event) { | |
965 infoPopup = null; | |
966 } | |
967 }); | |
968 } | |
969 infoPopup.setContents(p | |
970 .getDescription()); | |
971 } else if (infoPopup !is null) { | |
972 infoPopup.close(); | |
973 } | |
974 pendingDescriptionUpdate = false; | |
975 | |
976 } | |
977 } | |
978 }); | |
979 } | |
980 }; | |
981 JThread t = new JThread(r); | |
982 t.start(); | |
983 } | |
984 } | |
985 | |
986 /* | |
987 * Accept the current proposal. | |
988 */ | |
989 private void acceptCurrentProposal() { | |
990 // Close before accepting the proposal. This is important | |
991 // so that the cursor position can be properly restored at | |
992 // acceptance, which does not work without focus on some controls. | |
993 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108 | |
994 IContentProposal proposal = getSelectedProposal(); | |
995 close(); | |
996 proposalAccepted(proposal); | |
997 } | |
998 | |
999 /* | |
1000 * Request the proposals from the proposal provider, and recompute any | |
1001 * caches. Repopulate the popup if it is open. | |
1002 */ | |
1003 private void recomputeProposals(String filterText) { | |
1004 IContentProposal[] allProposals = getProposals(); | |
1005 // If the non-filtered proposal list is empty, we should | |
1006 // close the popup. | |
1007 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377 | |
1008 if (allProposals.length is 0) { | |
1009 proposals = allProposals; | |
1010 close(); | |
1011 } else { | |
1012 // Keep the popup open, but filter by any provided filter text | |
1013 setProposals(filterProposals(allProposals, filterText)); | |
1014 } | |
1015 } | |
1016 | |
1017 /* | |
1018 * In an async block, request the proposals. This is used when clients | |
1019 * are in the middle of processing an event that affects the widget | |
1020 * content. By using an async, we ensure that the widget content is up | |
1021 * to date with the event. | |
1022 */ | |
1023 private void asyncRecomputeProposals(String filterText) { | |
1024 if (isValid()) { | |
1025 control.getDisplay().asyncExec(new class(filterText) Runnable { | |
1026 String filterText_; | |
1027 this(String a){filterText_=a;} | |
1028 public void run() { | |
1029 recordCursorPosition(); | |
1030 recomputeProposals(filterText_); | |
1031 } | |
1032 }); | |
1033 } else { | |
1034 recomputeProposals(filterText); | |
1035 } | |
1036 } | |
1037 | |
1038 /* | |
1039 * Filter the provided list of content proposals according to the filter | |
1040 * text. | |
1041 */ | |
1042 private IContentProposal[] filterProposals( | |
1043 IContentProposal[] proposals, String filterString) { | |
1044 if (filterString.length is 0) { | |
1045 return proposals; | |
1046 } | |
1047 | |
1048 // Check each string for a match. Use the string displayed to the | |
1049 // user, not the proposal content. | |
1050 scope IContentProposal[] list = new IContentProposal[proposals.length]; | |
1051 int idx = 0; | |
1052 for (int i = 0; i < proposals.length; i++) { | |
1053 String string = getString(proposals[i]); | |
1054 if (string.length >= filterString.length | |
1055 && string.substring(0, filterString.length) | |
1056 .equalsIgnoreCase(filterString)) { | |
1057 list[idx++] = proposals[i]; | |
1058 } | |
1059 | |
1060 } | |
1061 return list[ 0 .. idx ].dup; | |
1062 } | |
1063 | |
1064 Listener getTargetControlListener() { | |
1065 if (targetControlListener is null) { | |
1066 targetControlListener = new TargetControlListener(); | |
1067 } | |
1068 return targetControlListener; | |
1069 } | |
1070 } | |
1071 | |
1072 /** | |
1073 * Flag that controls the printing of debug info. | |
1074 */ | |
1075 public static const bool DEBUG = false; | |
1076 | |
1077 /** | |
1078 * Indicates that a chosen proposal should be inserted into the field. | |
1079 */ | |
1080 public static const int PROPOSAL_INSERT = 1; | |
1081 | |
1082 /** | |
1083 * Indicates that a chosen proposal should replace the entire contents of | |
1084 * the field. | |
1085 */ | |
1086 public static const int PROPOSAL_REPLACE = 2; | |
1087 | |
1088 /** | |
1089 * Indicates that the contents of the control should not be modified when a | |
1090 * proposal is chosen. This is typically used when a client needs more | |
1091 * specialized behavior when a proposal is chosen. In this case, clients | |
1092 * typically register an IContentProposalListener so that they are notified | |
1093 * when a proposal is chosen. | |
1094 */ | |
1095 public static const int PROPOSAL_IGNORE = 3; | |
1096 | |
1097 /** | |
1098 * Indicates that there should be no filter applied as keys are typed in the | |
1099 * popup. | |
1100 */ | |
1101 public static const int FILTER_NONE = 1; | |
1102 | |
1103 /** | |
1104 * Indicates that a single character filter applies as keys are typed in the | |
1105 * popup. | |
1106 */ | |
1107 public static const int FILTER_CHARACTER = 2; | |
1108 | |
1109 /** | |
1110 * Indicates that a cumulative filter applies as keys are typed in the | |
1111 * popup. That is, each character typed will be added to the filter. | |
1112 * | |
1113 * @deprecated As of 3.4, filtering that is sensitive to changes in the | |
1114 * control content should be performed by the supplied | |
1115 * {@link IContentProposalProvider}, such as that performed by | |
1116 * {@link SimpleContentProposalProvider} | |
1117 */ | |
1118 public static const int FILTER_CUMULATIVE = 3; | |
1119 | |
1120 /* | |
1121 * Set to <code>true</code> to use a Table with SWT.VIRTUAL. This is a | |
1122 * workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c40 | |
1123 * The corresponding SWT bug is | |
1124 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321 | |
1125 */ | |
1126 private static const bool USE_VIRTUAL = !"motif".equals(SWT.getPlatform()); //$NON-NLS-1$ | |
1127 | |
1128 /* | |
1129 * The delay before showing a secondary popup. | |
1130 */ | |
1131 private static const int POPUP_DELAY = 750; | |
1132 | |
1133 /* | |
1134 * The character height hint for the popup. May be overridden by using | |
1135 * setInitialPopupSize. | |
1136 */ | |
1137 private static const int POPUP_CHAR_HEIGHT = 10; | |
1138 | |
1139 /* | |
1140 * The minimum pixel width for the popup. May be overridden by using | |
1141 * setInitialPopupSize. | |
1142 */ | |
1143 private static const int POPUP_MINIMUM_WIDTH = 300; | |
1144 | |
1145 /* | |
1146 * The pixel offset of the popup from the bottom corner of the control. | |
1147 */ | |
1148 private static const int POPUP_OFFSET = 3; | |
1149 | |
1150 /* | |
1151 * Empty string. | |
1152 */ | |
1153 private static const String EMPTY = ""; //$NON-NLS-1$ | |
1154 | |
1155 /* | |
1156 * The object that provides content proposals. | |
1157 */ | |
1158 private IContentProposalProvider proposalProvider; | |
1159 | |
1160 /* | |
1161 * A label provider used to display proposals in the popup, and to extract | |
1162 * Strings from non-String proposals. | |
1163 */ | |
1164 private ILabelProvider labelProvider; | |
1165 | |
1166 /* | |
1167 * The control for which content proposals are provided. | |
1168 */ | |
1169 private Control control; | |
1170 | |
1171 /* | |
1172 * The adapter used to extract the String contents from an arbitrary | |
1173 * control. | |
1174 */ | |
1175 private IControlContentAdapter controlContentAdapter; | |
1176 | |
1177 /* | |
1178 * The popup used to show proposals. | |
1179 */ | |
1180 private ContentProposalPopup popup; | |
1181 | |
1182 /* | |
1183 * The keystroke that signifies content proposals should be shown. | |
1184 */ | |
1185 private KeyStroke triggerKeyStroke; | |
1186 | |
1187 /* | |
1188 * The String containing characters that auto-activate the popup. | |
1189 */ | |
1190 private String autoActivateString; | |
1191 | |
1192 /* | |
1193 * Integer that indicates how an accepted proposal should affect the | |
1194 * control. One of PROPOSAL_IGNORE, PROPOSAL_INSERT, or PROPOSAL_REPLACE. | |
1195 * Default value is PROPOSAL_INSERT. | |
1196 */ | |
1197 private int proposalAcceptanceStyle = PROPOSAL_INSERT; | |
1198 | |
1199 /* | |
1200 * A bool that indicates whether key events received while the proposal | |
1201 * popup is open should also be propagated to the control. Default value is | |
1202 * true. | |
1203 */ | |
1204 private bool propagateKeys = true; | |
1205 | |
1206 /* | |
1207 * Integer that indicates the filtering style. One of FILTER_CHARACTER, | |
1208 * FILTER_CUMULATIVE, FILTER_NONE. | |
1209 */ | |
1210 private int filterStyle = FILTER_NONE; | |
1211 | |
1212 /* | |
1213 * The listener we install on the control. | |
1214 */ | |
1215 private Listener controlListener; | |
1216 | |
1217 /* | |
1218 * The list of IContentProposalListener listeners. | |
1219 */ | |
1220 private ListenerList proposalListeners; | |
1221 | |
1222 /* | |
1223 * The list of IContentProposalListener2 listeners. | |
1224 */ | |
1225 private ListenerList proposalListeners2; | |
1226 | |
1227 /* | |
1228 * Flag that indicates whether the adapter is enabled. In some cases, | |
1229 * adapters may be installed but depend upon outside state. | |
1230 */ | |
1231 private bool isEnabled_ = true; | |
1232 | |
1233 /* | |
1234 * The delay in milliseconds used when autoactivating the popup. | |
1235 */ | |
1236 private int autoActivationDelay = 0; | |
1237 | |
1238 /* | |
1239 * A bool indicating whether a keystroke has been received. Used to see | |
1240 * if an autoactivation delay was interrupted by a keystroke. | |
1241 */ | |
1242 private bool receivedKeyDown; | |
1243 | |
1244 /* | |
1245 * The desired size in pixels of the proposal popup. | |
1246 */ | |
1247 private Point popupSize; | |
1248 | |
1249 /* | |
1250 * The remembered position of the insertion position. Not all controls will | |
1251 * restore the insertion position if the proposal popup gets focus, so we | |
1252 * need to remember it. | |
1253 */ | |
1254 private int insertionPos = -1; | |
1255 | |
1256 /* | |
1257 * The remembered selection range. Not all controls will restore the | |
1258 * selection position if the proposal popup gets focus, so we need to | |
1259 * remember it. | |
1260 */ | |
1261 private Point selectionRange; | |
1262 | |
1263 /* | |
1264 * A flag that indicates that we are watching modify events | |
1265 */ | |
1266 private bool watchModify = false; | |
1267 | |
1268 /** | |
1269 * Construct a content proposal adapter that can assist the user with | |
1270 * choosing content for the field. | |
1271 * | |
1272 * @param control | |
1273 * the control for which the adapter is providing content assist. | |
1274 * May not be <code>null</code>. | |
1275 * @param controlContentAdapter | |
1276 * the <code>IControlContentAdapter</code> used to obtain and | |
1277 * update the control's contents as proposals are accepted. May | |
1278 * not be <code>null</code>. | |
1279 * @param proposalProvider | |
1280 * the <code>IContentProposalProvider</code> used to obtain | |
1281 * content proposals for this control, or <code>null</code> if | |
1282 * no content proposal is available. | |
1283 * @param keyStroke | |
1284 * the keystroke that will invoke the content proposal popup. If | |
1285 * this value is <code>null</code>, then proposals will be | |
1286 * activated automatically when any of the auto activation | |
1287 * characters are typed. | |
1288 * @param autoActivationCharacters | |
1289 * An array of characters that trigger auto-activation of content | |
1290 * proposal. If specified, these characters will trigger | |
1291 * auto-activation of the proposal popup, regardless of whether | |
1292 * an explicit invocation keyStroke was specified. If this | |
1293 * parameter is <code>null</code>, then only a specified | |
1294 * keyStroke will invoke content proposal. If this parameter is | |
1295 * <code>null</code> and the keyStroke parameter is | |
1296 * <code>null</code>, then all alphanumeric characters will | |
1297 * auto-activate content proposal. | |
1298 */ | |
1299 public this(Control control, | |
1300 IControlContentAdapter controlContentAdapter, | |
1301 IContentProposalProvider proposalProvider, KeyStroke keyStroke, | |
1302 char[] autoActivationCharacters) { | |
1303 //DWT_Init | |
1304 proposalListeners = new ListenerList(); | |
1305 proposalListeners2 = new ListenerList(); | |
1306 selectionRange = new Point(-1, -1); | |
1307 //super(); | |
1308 // We always assume the control and content adapter are valid. | |
1309 Assert.isNotNull(cast(Object)control); | |
1310 Assert.isNotNull(cast(Object)controlContentAdapter); | |
1311 this.control = control; | |
1312 this.controlContentAdapter = controlContentAdapter; | |
1313 | |
1314 // The rest of these may be null | |
1315 this.proposalProvider = proposalProvider; | |
1316 this.triggerKeyStroke = keyStroke; | |
1317 if (autoActivationCharacters.length !is 0 ) { | |
1318 this.autoActivateString = autoActivationCharacters; | |
1319 } | |
1320 addControlListener(control); | |
1321 } | |
1322 | |
1323 /** | |
1324 * Get the control on which the content proposal adapter is installed. | |
1325 * | |
1326 * @return the control on which the proposal adapter is installed. | |
1327 */ | |
1328 public Control getControl() { | |
1329 return control; | |
1330 } | |
1331 | |
1332 /** | |
1333 * Get the label provider that is used to show proposals. | |
1334 * | |
1335 * @return the {@link ILabelProvider} used to show proposals, or | |
1336 * <code>null</code> if one has not been installed. | |
1337 */ | |
1338 public ILabelProvider getLabelProvider() { | |
1339 return labelProvider; | |
1340 } | |
1341 | |
1342 /** | |
1343 * Return a bool indicating whether the receiver is enabled. | |
1344 * | |
1345 * @return <code>true</code> if the adapter is enabled, and | |
1346 * <code>false</code> if it is not. | |
1347 */ | |
1348 public bool isEnabled() { | |
1349 return isEnabled_; | |
1350 } | |
1351 | |
1352 /** | |
1353 * Set the label provider that is used to show proposals. The lifecycle of | |
1354 * the specified label provider is not managed by this adapter. Clients must | |
1355 * dispose the label provider when it is no longer needed. | |
1356 * | |
1357 * @param labelProvider | |
1358 * the (@link ILabelProvider} used to show proposals. | |
1359 */ | |
1360 public void setLabelProvider(ILabelProvider labelProvider) { | |
1361 this.labelProvider = labelProvider; | |
1362 } | |
1363 | |
1364 /** | |
1365 * Return the proposal provider that provides content proposals given the | |
1366 * current content of the field. A value of <code>null</code> indicates | |
1367 * that there are no content proposals available for the field. | |
1368 * | |
1369 * @return the {@link IContentProposalProvider} used to show proposals. May | |
1370 * be <code>null</code>. | |
1371 */ | |
1372 public IContentProposalProvider getContentProposalProvider() { | |
1373 return proposalProvider; | |
1374 } | |
1375 | |
1376 /** | |
1377 * Set the content proposal provider that is used to show proposals. | |
1378 * | |
1379 * @param proposalProvider | |
1380 * the {@link IContentProposalProvider} used to show proposals | |
1381 */ | |
1382 public void setContentProposalProvider( | |
1383 IContentProposalProvider proposalProvider) { | |
1384 this.proposalProvider = proposalProvider; | |
1385 } | |
1386 | |
1387 /** | |
1388 * Return the array of characters on which the popup is autoactivated. | |
1389 * | |
1390 * @return An array of characters that trigger auto-activation of content | |
1391 * proposal. If specified, these characters will trigger | |
1392 * auto-activation of the proposal popup, regardless of whether an | |
1393 * explicit invocation keyStroke was specified. If this parameter is | |
1394 * <code>null</code>, then only a specified keyStroke will invoke | |
1395 * content proposal. If this value is <code>null</code> and the | |
1396 * keyStroke value is <code>null</code>, then all alphanumeric | |
1397 * characters will auto-activate content proposal. | |
1398 */ | |
1399 public char[] getAutoActivationCharacters() { | |
1400 if (autoActivateString is null) { | |
1401 return null; | |
1402 } | |
1403 return autoActivateString/+.toCharArray()+/; | |
1404 } | |
1405 | |
1406 /** | |
1407 * Set the array of characters that will trigger autoactivation of the | |
1408 * popup. | |
1409 * | |
1410 * @param autoActivationCharacters | |
1411 * An array of characters that trigger auto-activation of content | |
1412 * proposal. If specified, these characters will trigger | |
1413 * auto-activation of the proposal popup, regardless of whether | |
1414 * an explicit invocation keyStroke was specified. If this | |
1415 * parameter is <code>null</code>, then only a specified | |
1416 * keyStroke will invoke content proposal. If this parameter is | |
1417 * <code>null</code> and the keyStroke value is | |
1418 * <code>null</code>, then all alphanumeric characters will | |
1419 * auto-activate content proposal. | |
1420 * | |
1421 */ | |
1422 public void setAutoActivationCharacters(char[] autoActivationCharacters) { | |
1423 if (autoActivationCharacters.length is 0) { | |
1424 this.autoActivateString = null; | |
1425 } else { | |
1426 this.autoActivateString = autoActivationCharacters; | |
1427 } | |
1428 } | |
1429 | |
1430 /** | |
1431 * Set the delay, in milliseconds, used before any autoactivation is | |
1432 * triggered. | |
1433 * | |
1434 * @return the time in milliseconds that will pass before a popup is | |
1435 * automatically opened | |
1436 */ | |
1437 public int getAutoActivationDelay() { | |
1438 return autoActivationDelay; | |
1439 | |
1440 } | |
1441 | |
1442 /** | |
1443 * Set the delay, in milliseconds, used before autoactivation is triggered. | |
1444 * | |
1445 * @param delay | |
1446 * the time in milliseconds that will pass before a popup is | |
1447 * automatically opened | |
1448 */ | |
1449 public void setAutoActivationDelay(int delay) { | |
1450 autoActivationDelay = delay; | |
1451 | |
1452 } | |
1453 | |
1454 /** | |
1455 * Get the integer style that indicates how an accepted proposal affects the | |
1456 * control's content. | |
1457 * | |
1458 * @return a constant indicating how an accepted proposal should affect the | |
1459 * control's content. Should be one of <code>PROPOSAL_INSERT</code>, | |
1460 * <code>PROPOSAL_REPLACE</code>, or <code>PROPOSAL_IGNORE</code>. | |
1461 * (Default is <code>PROPOSAL_INSERT</code>). | |
1462 */ | |
1463 public int getProposalAcceptanceStyle() { | |
1464 return proposalAcceptanceStyle; | |
1465 } | |
1466 | |
1467 /** | |
1468 * Set the integer style that indicates how an accepted proposal affects the | |
1469 * control's content. | |
1470 * | |
1471 * @param acceptance | |
1472 * a constant indicating how an accepted proposal should affect | |
1473 * the control's content. Should be one of | |
1474 * <code>PROPOSAL_INSERT</code>, <code>PROPOSAL_REPLACE</code>, | |
1475 * or <code>PROPOSAL_IGNORE</code> | |
1476 */ | |
1477 public void setProposalAcceptanceStyle(int acceptance) { | |
1478 proposalAcceptanceStyle = acceptance; | |
1479 } | |
1480 | |
1481 /** | |
1482 * Return the integer style that indicates how keystrokes affect the content | |
1483 * of the proposal popup while it is open. | |
1484 * | |
1485 * @return a constant indicating how keystrokes in the proposal popup affect | |
1486 * filtering of the proposals shown. <code>FILTER_NONE</code> | |
1487 * specifies that no filtering will occur in the content proposal | |
1488 * list as keys are typed. <code>FILTER_CHARACTER</code> specifies | |
1489 * the content of the popup will be filtered by the most recently | |
1490 * typed character. <code>FILTER_CUMULATIVE</code> is deprecated | |
1491 * and no longer recommended. It specifies that the content of the | |
1492 * popup will be filtered by a string containing all the characters | |
1493 * typed since the popup has been open. The default is | |
1494 * <code>FILTER_NONE</code>. | |
1495 */ | |
1496 public int getFilterStyle() { | |
1497 return filterStyle; | |
1498 } | |
1499 | |
1500 /** | |
1501 * Set the integer style that indicates how keystrokes affect the content of | |
1502 * the proposal popup while it is open. Popup-based filtering is useful for | |
1503 * narrowing and navigating the list of proposals provided once the popup is | |
1504 * open. Filtering of the proposals will occur even when the control content | |
1505 * is not affected by user typing. Note that automatic filtering is not used | |
1506 * to achieve content-sensitive filtering such as auto-completion. Filtering | |
1507 * that is sensitive to changes in the control content should be performed | |
1508 * by the supplied {@link IContentProposalProvider}. | |
1509 * | |
1510 * @param filterStyle | |
1511 * a constant indicating how keystrokes received in the proposal | |
1512 * popup affect filtering of the proposals shown. | |
1513 * <code>FILTER_NONE</code> specifies that no automatic | |
1514 * filtering of the content proposal list will occur as keys are | |
1515 * typed in the popup. <code>FILTER_CHARACTER</code> specifies | |
1516 * that the content of the popup will be filtered by the most | |
1517 * recently typed character. <code>FILTER_CUMULATIVE</code> is | |
1518 * deprecated and no longer recommended. It specifies that the | |
1519 * content of the popup will be filtered by a string containing | |
1520 * all the characters typed since the popup has been open. | |
1521 */ | |
1522 public void setFilterStyle(int filterStyle) { | |
1523 this.filterStyle = filterStyle; | |
1524 } | |
1525 | |
1526 /** | |
1527 * Return the size, in pixels, of the content proposal popup. | |
1528 * | |
1529 * @return a Point specifying the last width and height, in pixels, of the | |
1530 * content proposal popup. | |
1531 */ | |
1532 public Point getPopupSize() { | |
1533 return popupSize; | |
1534 } | |
1535 | |
1536 /** | |
1537 * Set the size, in pixels, of the content proposal popup. This size will be | |
1538 * used the next time the content proposal popup is opened. | |
1539 * | |
1540 * @param size | |
1541 * a Point specifying the desired width and height, in pixels, of | |
1542 * the content proposal popup. | |
1543 */ | |
1544 public void setPopupSize(Point size) { | |
1545 popupSize = size; | |
1546 } | |
1547 | |
1548 /** | |
1549 * Get the bool that indicates whether key events (including | |
1550 * auto-activation characters) received by the content proposal popup should | |
1551 * also be propagated to the adapted control when the proposal popup is | |
1552 * open. | |
1553 * | |
1554 * @return a bool that indicates whether key events (including | |
1555 * auto-activation characters) should be propagated to the adapted | |
1556 * control when the proposal popup is open. Default value is | |
1557 * <code>true</code>. | |
1558 */ | |
1559 public bool getPropagateKeys() { | |
1560 return propagateKeys; | |
1561 } | |
1562 | |
1563 /** | |
1564 * Set the bool that indicates whether key events (including | |
1565 * auto-activation characters) received by the content proposal popup should | |
1566 * also be propagated to the adapted control when the proposal popup is | |
1567 * open. | |
1568 * | |
1569 * @param propagateKeys | |
1570 * a bool that indicates whether key events (including | |
1571 * auto-activation characters) should be propagated to the | |
1572 * adapted control when the proposal popup is open. | |
1573 */ | |
1574 public void setPropagateKeys(bool propagateKeys) { | |
1575 this.propagateKeys = propagateKeys; | |
1576 } | |
1577 | |
1578 /** | |
1579 * Return the content adapter that can get or retrieve the text contents | |
1580 * from the adapter's control. This method is used when a client, such as a | |
1581 * content proposal listener, needs to update the control's contents | |
1582 * manually. | |
1583 * | |
1584 * @return the {@link IControlContentAdapter} which can update the control | |
1585 * text. | |
1586 */ | |
1587 public IControlContentAdapter getControlContentAdapter() { | |
1588 return controlContentAdapter; | |
1589 } | |
1590 | |
1591 /** | |
1592 * Set the bool flag that determines whether the adapter is enabled. | |
1593 * | |
1594 * @param enabled | |
1595 * <code>true</code> if the adapter is enabled and responding | |
1596 * to user input, <code>false</code> if it is ignoring user | |
1597 * input. | |
1598 * | |
1599 */ | |
1600 public void setEnabled(bool enabled) { | |
1601 // If we are disabling it while it's proposing content, close the | |
1602 // content proposal popup. | |
1603 if (isEnabled_ && !enabled) { | |
1604 if (popup !is null) { | |
1605 popup.close(); | |
1606 } | |
1607 } | |
1608 isEnabled_ = enabled; | |
1609 } | |
1610 | |
1611 /** | |
1612 * Add the specified listener to the list of content proposal listeners that | |
1613 * are notified when content proposals are chosen. | |
1614 * </p> | |
1615 * | |
1616 * @param listener | |
1617 * the IContentProposalListener to be added as a listener. Must | |
1618 * not be <code>null</code>. If an attempt is made to register | |
1619 * an instance which is already registered with this instance, | |
1620 * this method has no effect. | |
1621 * | |
1622 * @see org.eclipse.jface.fieldassist.IContentProposalListener | |
1623 */ | |
1624 public void addContentProposalListener(IContentProposalListener listener) { | |
1625 proposalListeners.add(cast(Object)listener); | |
1626 } | |
1627 | |
1628 /** | |
1629 * Removes the specified listener from the list of content proposal | |
1630 * listeners that are notified when content proposals are chosen. | |
1631 * </p> | |
1632 * | |
1633 * @param listener | |
1634 * the IContentProposalListener to be removed as a listener. Must | |
1635 * not be <code>null</code>. If the listener has not already | |
1636 * been registered, this method has no effect. | |
1637 * | |
1638 * @since 3.3 | |
1639 * @see org.eclipse.jface.fieldassist.IContentProposalListener | |
1640 */ | |
1641 public void removeContentProposalListener(IContentProposalListener listener) { | |
1642 proposalListeners.remove(cast(Object)listener); | |
1643 } | |
1644 | |
1645 /** | |
1646 * Add the specified listener to the list of content proposal listeners that | |
1647 * are notified when a content proposal popup is opened or closed. | |
1648 * </p> | |
1649 * | |
1650 * @param listener | |
1651 * the IContentProposalListener2 to be added as a listener. Must | |
1652 * not be <code>null</code>. If an attempt is made to register | |
1653 * an instance which is already registered with this instance, | |
1654 * this method has no effect. | |
1655 * | |
1656 * @since 3.3 | |
1657 * @see org.eclipse.jface.fieldassist.IContentProposalListener2 | |
1658 */ | |
1659 public void addContentProposalListener(IContentProposalListener2 listener) { | |
1660 proposalListeners2.add(cast(Object)listener); | |
1661 } | |
1662 | |
1663 /** | |
1664 * Remove the specified listener from the list of content proposal listeners | |
1665 * that are notified when a content proposal popup is opened or closed. | |
1666 * </p> | |
1667 * | |
1668 * @param listener | |
1669 * the IContentProposalListener2 to be removed as a listener. | |
1670 * Must not be <code>null</code>. If the listener has not | |
1671 * already been registered, this method has no effect. | |
1672 * | |
1673 * @since 3.3 | |
1674 * @see org.eclipse.jface.fieldassist.IContentProposalListener2 | |
1675 */ | |
1676 public void removeContentProposalListener(IContentProposalListener2 listener) { | |
1677 proposalListeners2.remove(cast(Object)listener); | |
1678 } | |
1679 | |
1680 /* | |
1681 * Add our listener to the control. Debug information to be left in until | |
1682 * this support is stable on all platforms. | |
1683 */ | |
1684 private void addControlListener(Control control) { | |
1685 if (DEBUG) { | |
1686 Stdout.formatln("ContentProposalListener#installControlListener()"); //$NON-NLS-1$ | |
1687 } | |
1688 | |
1689 if (controlListener !is null) { | |
1690 return; | |
1691 } | |
1692 controlListener = new class Listener { | |
1693 public void handleEvent(Event e) { | |
1694 if (!isEnabled_) { | |
1695 return; | |
1696 } | |
1697 | |
1698 switch (e.type) { | |
1699 case SWT.Traverse: | |
1700 case SWT.KeyDown: | |
1701 if (DEBUG) { | |
1702 StringBuffer sb; | |
1703 if (e.type is SWT.Traverse) { | |
1704 sb = new StringBuffer("Traverse"); //$NON-NLS-1$ | |
1705 } else { | |
1706 sb = new StringBuffer("KeyDown"); //$NON-NLS-1$ | |
1707 } | |
1708 sb.append(" received by adapter"); //$NON-NLS-1$ | |
1709 dump(sb.toString(), e); | |
1710 } | |
1711 // If the popup is open, it gets first shot at the | |
1712 // keystroke and should set the doit flags appropriately. | |
1713 if (popup !is null) { | |
1714 popup.getTargetControlListener().handleEvent(e); | |
1715 if (DEBUG) { | |
1716 StringBuffer sb; | |
1717 if (e.type is SWT.Traverse) { | |
1718 sb = new StringBuffer("Traverse"); //$NON-NLS-1$ | |
1719 } else { | |
1720 sb = new StringBuffer("KeyDown"); //$NON-NLS-1$ | |
1721 } | |
1722 sb.append(" after being handled by popup"); //$NON-NLS-1$ | |
1723 dump(sb.toString(), e); | |
1724 } | |
1725 | |
1726 return; | |
1727 } | |
1728 | |
1729 // We were only listening to traverse events for the popup | |
1730 if (e.type is SWT.Traverse) { | |
1731 return; | |
1732 } | |
1733 | |
1734 // The popup is not open. We are looking at keydown events | |
1735 // for a trigger to open the popup. | |
1736 if (triggerKeyStroke !is null) { | |
1737 // Either there are no modifiers for the trigger and we | |
1738 // check the character field... | |
1739 if ((triggerKeyStroke.getModifierKeys() is KeyStroke.NO_KEY && triggerKeyStroke | |
1740 .getNaturalKey() is e.character) | |
1741 || | |
1742 // ...or there are modifiers, in which case the | |
1743 // keycode and state must match | |
1744 (triggerKeyStroke.getNaturalKey() is e.keyCode && ((triggerKeyStroke | |
1745 .getModifierKeys() & e.stateMask) is triggerKeyStroke | |
1746 .getModifierKeys()))) { | |
1747 // We never propagate the keystroke for an explicit | |
1748 // keystroke invocation of the popup | |
1749 e.doit = false; | |
1750 openProposalPopup(false); | |
1751 return; | |
1752 } | |
1753 } | |
1754 /* | |
1755 * The triggering keystroke was not invoked. If a character | |
1756 * was typed, compare it to the autoactivation characters. | |
1757 */ | |
1758 if (e.character !is 0) { | |
1759 if (autoActivateString !is null) { | |
1760 if (autoActivateString.indexOf(e.character) >= 0) { | |
1761 autoActivate(); | |
1762 } else { | |
1763 // No autoactivation occurred, so record the key | |
1764 // down as a means to interrupt any | |
1765 // autoactivation | |
1766 // that is pending due to autoactivation delay. | |
1767 receivedKeyDown = true; | |
1768 } | |
1769 } else { | |
1770 // The autoactivate string is null. If the trigger | |
1771 // is also null, we want to act on any modification | |
1772 // to the content. Set a flag so we'll catch this | |
1773 // in the modify event. | |
1774 if (triggerKeyStroke is null) { | |
1775 watchModify = true; | |
1776 } | |
1777 } | |
1778 } | |
1779 break; | |
1780 | |
1781 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377 | |
1782 // Given that we will close the popup when there are no valid | |
1783 // proposals, we must reopen it when there are. This means | |
1784 // we should check modifications in those cases. | |
1785 // See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650 | |
1786 // The watchModify flag ensures that we don't autoactivate if | |
1787 // the content change was caused by something other than typing. | |
1788 case SWT.Modify: | |
1789 if (triggerKeyStroke is null && autoActivateString is null | |
1790 && watchModify) { | |
1791 if (DEBUG) { | |
1792 dump("Modify event triggers autoactivation", e); //$NON-NLS-1$ | |
1793 } | |
1794 watchModify = false; | |
1795 // We don't autoactivate if the net change is no | |
1796 // content. In other words, backspacing to empty | |
1797 // should never cause a popup to open. | |
1798 if (!isControlContentEmpty()) { | |
1799 autoActivate(); | |
1800 } | |
1801 } | |
1802 break; | |
1803 default: | |
1804 break; | |
1805 } | |
1806 } | |
1807 | |
1808 /** | |
1809 * Dump the given events to "standard" output. | |
1810 * | |
1811 * @param who | |
1812 * who is dumping the event | |
1813 * @param e | |
1814 * the event | |
1815 */ | |
1816 private void dump(String who, Event e) { | |
1817 StringBuffer sb = new StringBuffer( | |
1818 "--- [ContentProposalAdapter]\n"); //$NON-NLS-1$ | |
1819 sb.append(who); | |
1820 sb.append(Format(" - e: keyCode={}{}", e.keyCode, hex(e.keyCode))); //$NON-NLS-1$ | |
1821 sb.append(Format("; character={}{}", e.character, hex(e.character))); //$NON-NLS-1$ | |
1822 sb.append(Format("; stateMask={}{}", e.stateMask, hex(e.stateMask))); //$NON-NLS-1$ | |
1823 sb.append(Format("; doit={}", e.doit)); //$NON-NLS-1$ | |
1824 sb.append(Format("; detail={}", e.detail, hex(e.detail))); //$NON-NLS-1$ | |
1825 sb.append(Format("; widget={}", e.widget)); //$NON-NLS-1$ | |
1826 Stdout.formatln("{}",sb.toString); | |
1827 } | |
1828 | |
1829 private String hex(int i) { | |
1830 return Format("[0x{:X}]", i); //$NON-NLS-1$ | |
1831 } | |
1832 }; | |
1833 control.addListener(SWT.KeyDown, controlListener); | |
1834 control.addListener(SWT.Traverse, controlListener); | |
1835 control.addListener(SWT.Modify, controlListener); | |
1836 | |
1837 if (DEBUG) { | |
1838 Stdout.formatln("ContentProposalAdapter#installControlListener() - installed"); //$NON-NLS-1$ | |
1839 } | |
1840 } | |
1841 | |
1842 /** | |
1843 * Open the proposal popup and display the proposals provided by the | |
1844 * proposal provider. If there are no proposals to be shown, do not show the | |
1845 * popup. This method returns immediately. That is, it does not wait for the | |
1846 * popup to open or a proposal to be selected. | |
1847 * | |
1848 * @param autoActivated | |
1849 * a bool indicating whether the popup was autoactivated. If | |
1850 * false, a beep will sound when no proposals can be shown. | |
1851 */ | |
1852 private void openProposalPopup(bool autoActivated) { | |
1853 if (isValid()) { | |
1854 if (popup is null) { | |
1855 // Check whether there are any proposals to be shown. | |
1856 recordCursorPosition(); // must be done before getting proposals | |
1857 IContentProposal[] proposals = getProposals(); | |
1858 if (proposals.length > 0) { | |
1859 if (DEBUG) { | |
1860 Stdout.formatln("POPUP OPENED BY PRECEDING EVENT"); //$NON-NLS-1$ | |
1861 } | |
1862 recordCursorPosition(); | |
1863 popup = new ContentProposalPopup(null, proposals); | |
1864 popup.open(); | |
1865 popup.getShell().addDisposeListener(new class DisposeListener { | |
1866 public void widgetDisposed(DisposeEvent event) { | |
1867 popup = null; | |
1868 } | |
1869 }); | |
1870 notifyPopupOpened(); | |
1871 } else if (!autoActivated) { | |
1872 getControl().getDisplay().beep(); | |
1873 } | |
1874 } | |
1875 } | |
1876 } | |
1877 | |
1878 /** | |
1879 * Open the proposal popup and display the proposals provided by the | |
1880 * proposal provider. This method returns immediately. That is, it does not | |
1881 * wait for a proposal to be selected. This method is used by subclasses to | |
1882 * explicitly invoke the opening of the popup. If there are no proposals to | |
1883 * show, the popup will not open and a beep will be sounded. | |
1884 */ | |
1885 protected void openProposalPopup() { | |
1886 openProposalPopup(false); | |
1887 } | |
1888 | |
1889 /** | |
1890 * Close the proposal popup without accepting a proposal. This method | |
1891 * returns immediately, and has no effect if the proposal popup was not | |
1892 * open. This method is used by subclasses to explicitly close the popup | |
1893 * based on additional logic. | |
1894 * | |
1895 * @since 3.3 | |
1896 */ | |
1897 protected void closeProposalPopup() { | |
1898 if (popup !is null) { | |
1899 popup.close(); | |
1900 } | |
1901 } | |
1902 | |
1903 /* | |
1904 * A content proposal has been accepted. Update the control contents | |
1905 * accordingly and notify any listeners. | |
1906 * | |
1907 * @param proposal the accepted proposal | |
1908 */ | |
1909 private void proposalAccepted(IContentProposal proposal) { | |
1910 switch (proposalAcceptanceStyle) { | |
1911 case (PROPOSAL_REPLACE): | |
1912 setControlContent(proposal.getContent(), proposal | |
1913 .getCursorPosition()); | |
1914 break; | |
1915 case (PROPOSAL_INSERT): | |
1916 insertControlContent(proposal.getContent(), proposal | |
1917 .getCursorPosition()); | |
1918 break; | |
1919 default: | |
1920 // do nothing. Typically a listener is installed to handle this in | |
1921 // a custom way. | |
1922 break; | |
1923 } | |
1924 | |
1925 // In all cases, notify listeners of an accepted proposal. | |
1926 notifyProposalAccepted(proposal); | |
1927 } | |
1928 | |
1929 /* | |
1930 * Set the text content of the control to the specified text, setting the | |
1931 * cursorPosition at the desired location within the new contents. | |
1932 */ | |
1933 private void setControlContent(String text, int cursorPosition) { | |
1934 if (isValid()) { | |
1935 // should already be false, but just in case. | |
1936 watchModify = false; | |
1937 controlContentAdapter.setControlContents(control, text, | |
1938 cursorPosition); | |
1939 } | |
1940 } | |
1941 | |
1942 /* | |
1943 * Insert the specified text into the control content, setting the | |
1944 * cursorPosition at the desired location within the new contents. | |
1945 */ | |
1946 private void insertControlContent(String text, int cursorPosition) { | |
1947 if (isValid()) { | |
1948 // should already be false, but just in case. | |
1949 watchModify = false; | |
1950 // Not all controls preserve their selection index when they lose | |
1951 // focus, so we must set it explicitly here to what it was before | |
1952 // the popup opened. | |
1953 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108 | |
1954 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=139063 | |
1955 if ((null !is cast(IControlContentAdapter2)controlContentAdapter) | |
1956 && selectionRange.x !is -1) { | |
1957 (cast(IControlContentAdapter2) controlContentAdapter).setSelection( | |
1958 control, selectionRange); | |
1959 } else if (insertionPos !is -1) { | |
1960 controlContentAdapter.setCursorPosition(control, insertionPos); | |
1961 } | |
1962 controlContentAdapter.insertControlContents(control, text, | |
1963 cursorPosition); | |
1964 } | |
1965 } | |
1966 | |
1967 /* | |
1968 * Check that the control and content adapter are valid. | |
1969 */ | |
1970 private bool isValid() { | |
1971 return control !is null && !control.isDisposed() | |
1972 && controlContentAdapter !is null; | |
1973 } | |
1974 | |
1975 /* | |
1976 * Record the control's cursor position. | |
1977 */ | |
1978 private void recordCursorPosition() { | |
1979 if (isValid()) { | |
1980 IControlContentAdapter adapter = getControlContentAdapter(); | |
1981 insertionPos = adapter.getCursorPosition(control); | |
1982 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=139063 | |
1983 if (null !is cast(IControlContentAdapter2)adapter ) { | |
1984 selectionRange = (cast(IControlContentAdapter2) adapter) | |
1985 .getSelection(control); | |
1986 } | |
1987 | |
1988 } | |
1989 } | |
1990 | |
1991 /* | |
1992 * Get the proposals from the proposal provider. Gets all of the proposals | |
1993 * without doing any filtering. | |
1994 */ | |
1995 private IContentProposal[] getProposals() { | |
1996 if (proposalProvider is null || !isValid()) { | |
1997 return null; | |
1998 } | |
1999 if (DEBUG) { | |
2000 Stdout.formatln(">>> obtaining proposals from provider"); //$NON-NLS-1$ | |
2001 } | |
2002 int position = insertionPos; | |
2003 if (position is -1) { | |
2004 position = getControlContentAdapter().getCursorPosition( | |
2005 getControl()); | |
2006 } | |
2007 String contents = getControlContentAdapter().getControlContents( | |
2008 getControl()); | |
2009 IContentProposal[] proposals = proposalProvider.getProposals(contents, | |
2010 position); | |
2011 return proposals; | |
2012 } | |
2013 | |
2014 /** | |
2015 * Autoactivation has been triggered. Open the popup using any specified | |
2016 * delay. | |
2017 */ | |
2018 private void autoActivate() { | |
2019 if (autoActivationDelay > 0) { | |
2020 auto r = new class Runnable{ | |
2021 public void run(){ | |
2022 receivedKeyDown = false; | |
2023 try { | |
2024 JThread.sleep(autoActivationDelay); | |
2025 } catch (InterruptedException e) { | |
2026 } | |
2027 if (!isValid() || receivedKeyDown) { | |
2028 return; | |
2029 } | |
2030 getControl().getDisplay().syncExec(new class Runnable { | |
2031 public void run() { | |
2032 openProposalPopup(true); | |
2033 } | |
2034 }); | |
2035 } | |
2036 }; | |
2037 JThread t = new JThread(r); | |
2038 t.start(); | |
2039 } else { | |
2040 // Since we do not sleep, we must open the popup | |
2041 // in an async exec. This is necessary because | |
2042 // this method may be called in the middle of handling | |
2043 // some event that will cause the cursor position or | |
2044 // other important info to change as a result of this | |
2045 // event occurring. | |
2046 getControl().getDisplay().asyncExec(new class Runnable { | |
2047 public void run() { | |
2048 if (isValid()) { | |
2049 openProposalPopup(true); | |
2050 } | |
2051 } | |
2052 }); | |
2053 } | |
2054 } | |
2055 | |
2056 /* | |
2057 * A proposal has been accepted. Notify interested listeners. | |
2058 */ | |
2059 private void notifyProposalAccepted(IContentProposal proposal) { | |
2060 if (DEBUG) { | |
2061 Stdout.formatln("Notify listeners - proposal accepted."); //$NON-NLS-1$ | |
2062 } | |
2063 Object[] listenerArray = proposalListeners.getListeners(); | |
2064 for (int i = 0; i < listenerArray.length; i++) { | |
2065 (cast(IContentProposalListener) listenerArray[i]) | |
2066 .proposalAccepted(proposal); | |
2067 } | |
2068 } | |
2069 | |
2070 /* | |
2071 * The proposal popup has opened. Notify interested listeners. | |
2072 */ | |
2073 private void notifyPopupOpened() { | |
2074 if (DEBUG) { | |
2075 Stdout.formatln("Notify listeners - popup opened."); //$NON-NLS-1$ | |
2076 } | |
2077 Object[] listenerArray = proposalListeners2.getListeners(); | |
2078 for (int i = 0; i < listenerArray.length; i++) { | |
2079 (cast(IContentProposalListener2) listenerArray[i]) | |
2080 .proposalPopupOpened(this); | |
2081 } | |
2082 } | |
2083 | |
2084 /* | |
2085 * The proposal popup has closed. Notify interested listeners. | |
2086 */ | |
2087 private void notifyPopupClosed() { | |
2088 if (DEBUG) { | |
2089 Stdout.formatln("Notify listeners - popup closed."); //$NON-NLS-1$ | |
2090 } | |
2091 Object[] listenerArray = proposalListeners2.getListeners(); | |
2092 for (int i = 0; i < listenerArray.length; i++) { | |
2093 (cast(IContentProposalListener2) listenerArray[i]) | |
2094 .proposalPopupClosed(this); | |
2095 } | |
2096 } | |
2097 | |
2098 /** | |
2099 * Returns whether the content proposal popup has the focus. This includes | |
2100 * both the primary popup and any secondary info popup that may have focus. | |
2101 * | |
2102 * @return <code>true</code> if the proposal popup or its secondary info | |
2103 * popup has the focus | |
2104 * @since 3.4 | |
2105 */ | |
2106 public bool hasProposalPopupFocus() { | |
2107 return popup !is null && popup.hasFocus(); | |
2108 } | |
2109 | |
2110 /* | |
2111 * Return whether the control content is empty | |
2112 */ | |
2113 private bool isControlContentEmpty() { | |
2114 return getControlContentAdapter().getControlContents(getControl()) | |
2115 .length is 0; | |
2116 } | |
2117 } |