comparison dwtx/jface/text/contentassist/ContentAssistant.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 * Guy Gurfinkel, guy.g@zend.com - [content assist][api] provide better access to ContentAssistant - https://bugs.eclipse.org/bugs/show_bug.cgi?id=169954
11 * Anton Leherbauer (Wind River Systems) - [content assist][api] ContentAssistEvent should contain information about auto activation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=193728
12 * Port to the D programming language:
13 * Frank Benoit <benoit@tionex.de>
14 *******************************************************************************/
15 module dwtx.jface.text.contentassist.ContentAssistant;
16
17 import dwt.dwthelper.utils;
18
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 import dwt.DWT;
25 import dwt.DWTError;
26 import dwt.custom.VerifyKeyListener;
27 import dwt.events.ControlEvent;
28 import dwt.events.ControlListener;
29 import dwt.events.DisposeEvent;
30 import dwt.events.DisposeListener;
31 import dwt.events.FocusEvent;
32 import dwt.events.FocusListener;
33 import dwt.events.KeyAdapter;
34 import dwt.events.KeyEvent;
35 import dwt.events.KeyListener;
36 import dwt.events.MouseEvent;
37 import dwt.events.MouseListener;
38 import dwt.events.TraverseEvent;
39 import dwt.events.TraverseListener;
40 import dwt.events.VerifyEvent;
41 import dwt.graphics.Color;
42 import dwt.graphics.Point;
43 import dwt.graphics.Rectangle;
44 import dwt.widgets.Control;
45 import dwt.widgets.Display;
46 import dwt.widgets.Event;
47 import dwt.widgets.Listener;
48 import dwt.widgets.Monitor;
49 import dwt.widgets.Shell;
50 import dwt.widgets.Widget;
51 import dwtx.core.commands.IHandler;
52 import dwtx.core.runtime.Assert;
53 import dwtx.core.runtime.ListenerList;
54 import dwtx.jface.bindings.keys.KeySequence;
55 import dwtx.jface.contentassist.IContentAssistSubjectControl;
56 import dwtx.jface.contentassist.ISubjectControlContentAssistProcessor;
57 import dwtx.jface.dialogs.IDialogSettings;
58 import dwtx.jface.preference.JFacePreferences;
59 import dwtx.jface.text.BadLocationException;
60 import dwtx.jface.text.IDocument;
61 import dwtx.jface.text.IDocumentExtension3;
62 import dwtx.jface.text.IEventConsumer;
63 import dwtx.jface.text.IInformationControlCreator;
64 import dwtx.jface.text.ITextViewer;
65 import dwtx.jface.text.IViewportListener;
66 import dwtx.jface.text.IWidgetTokenKeeper;
67 import dwtx.jface.text.IWidgetTokenKeeperExtension;
68 import dwtx.jface.text.IWidgetTokenOwner;
69 import dwtx.jface.text.IWidgetTokenOwnerExtension;
70 import dwtx.jface.text.TextUtilities;
71 import dwtx.jface.util.Geometry;
72
73
74 /**
75 * The standard implementation of the <code>IContentAssistant</code> interface. Usually, clients
76 * instantiate this class and configure it before using it.
77 */
78 public class ContentAssistant : IContentAssistant, IContentAssistantExtension, IContentAssistantExtension2, IContentAssistantExtension3, IContentAssistantExtension4, IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
79
80
81
82 /**
83 * Content assist command identifier for 'select next proposal'.
84 *
85 * @since 3.4
86 */
87 public static final String SELECT_NEXT_PROPOSAL_COMMAND_ID= "dwtx.ui.edit.text.contentAssist.selectNextProposal"; //$NON-NLS-1$
88 /**
89 * Content assist command identifier for 'select previous proposal'.
90 *
91 * @since 3.4
92 */
93 public static final String SELECT_PREVIOUS_PROPOSAL_COMMAND_ID= "dwtx.ui.edit.text.contentAssist.selectPreviousProposal"; //$NON-NLS-1$
94
95
96 /**
97 * A generic closer class used to monitor various interface events in order to determine whether
98 * content-assist should be terminated and all associated windows closed.
99 */
100 class Closer : ControlListener, MouseListener, FocusListener, DisposeListener, IViewportListener {
101
102 /** The shell that a <code>ControlListener</code> is registered with. */
103 private Shell fShell;
104 /**
105 * The control that a <code>MouseListener</code>, a<code>FocusListener</code> and a
106 * <code>DisposeListener</code> are registered with.
107 */
108 private Control fControl;
109
110 /**
111 * Installs this closer on it's viewer's text widget.
112 */
113 protected void install() {
114 Control control= fContentAssistSubjectControlAdapter.getControl();
115 fControl= control;
116 if (Helper.okToUse(control)) {
117
118 Shell shell= control.getShell();
119 fShell= shell;
120 shell.addControlListener(this);
121
122 control.addMouseListener(this);
123 control.addFocusListener(this);
124
125 /*
126 * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of
127 * Internal Errors
128 */
129 control.addDisposeListener(this);
130 }
131 if (fViewer !is null)
132 fViewer.addViewportListener(this);
133 }
134
135 /**
136 * Uninstalls this closer from the viewer's text widget.
137 */
138 protected void uninstall() {
139 Control shell= fShell;
140 fShell= null;
141 if (Helper.okToUse(shell))
142 shell.removeControlListener(this);
143
144 Control control= fControl;
145 fControl= null;
146 if (Helper.okToUse(control)) {
147
148 control.removeMouseListener(this);
149 control.removeFocusListener(this);
150
151 /*
152 * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of
153 * Internal Errors
154 */
155 control.removeDisposeListener(this);
156 }
157
158 if (fViewer !is null)
159 fViewer.removeViewportListener(this);
160 }
161
162 /*
163 * @see ControlListener#controlResized(ControlEvent)
164 */
165 public void controlResized(ControlEvent e) {
166 hide();
167 }
168
169 /*
170 * @see ControlListener#controlMoved(ControlEvent)
171 */
172 public void controlMoved(ControlEvent e) {
173 hide();
174 }
175
176 /*
177 * @see MouseListener#mouseDown(MouseEvent)
178 */
179 public void mouseDown(MouseEvent e) {
180 hide();
181 }
182
183 /*
184 * @see MouseListener#mouseUp(MouseEvent)
185 */
186 public void mouseUp(MouseEvent e) {
187 }
188
189 /*
190 * @see MouseListener#mouseDoubleClick(MouseEvent)
191 */
192 public void mouseDoubleClick(MouseEvent e) {
193 hide();
194 }
195
196 /*
197 * @see FocusListener#focusGained(FocusEvent)
198 */
199 public void focusGained(FocusEvent e) {
200 }
201
202 /*
203 * @see FocusListener#focusLost(FocusEvent)
204 */
205 public void focusLost(FocusEvent e) {
206 Control control= fControl;
207 if (Helper.okToUse(control)) {
208 Display d= control.getDisplay();
209 if (d !is null) {
210 d.asyncExec(new Runnable() {
211 public void run() {
212 if (!fProposalPopup.hasFocus() && (fContextInfoPopup is null || !fContextInfoPopup.hasFocus()))
213 hide();
214 }
215 });
216 }
217 }
218 }
219
220 /*
221 * @seeDisposeListener#widgetDisposed(DisposeEvent)
222 */
223 public void widgetDisposed(DisposeEvent e) {
224 /*
225 * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal
226 * Errors
227 */
228 hide();
229 }
230
231 /*
232 * @see IViewportListener#viewportChanged(int)
233 */
234 public void viewportChanged(int topIndex) {
235 hide();
236 }
237 }
238
239 /**
240 * An implementation of <code>IContentAssistListener</code>, this class is used to monitor
241 * key events in support of automatic activation of the content assistant. If enabled, the
242 * implementation utilizes a thread to watch for input characters matching the activation
243 * characters specified by the content assist processor, and if detected, will wait the
244 * indicated delay interval before activating the content assistant.
245 *
246 * @since 3.4 protected, was added in 2.1 as private class
247 */
248 protected class AutoAssistListener : KeyAdapter , KeyListener, Runnable, VerifyKeyListener {
249
250 private Thread fThread;
251 private bool fIsReset= false;
252 private Object fMutex= new Object();
253 private int fShowStyle;
254
255 private final static int SHOW_PROPOSALS= 1;
256 private final static int SHOW_CONTEXT_INFO= 2;
257
258 protected AutoAssistListener() {
259 }
260
261 protected void start(int showStyle) {
262 fShowStyle= showStyle;
263 fThread= new Thread(this, JFaceTextMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$
264 fThread.start();
265 }
266
267 public void run() {
268 try {
269 while (true) {
270 synchronized (fMutex) {
271 if (fAutoActivationDelay !is 0)
272 fMutex.wait(fAutoActivationDelay);
273 if (fIsReset) {
274 fIsReset= false;
275 continue;
276 }
277 }
278 showAssist(fShowStyle);
279 break;
280 }
281 } catch (InterruptedException e) {
282 }
283 fThread= null;
284 }
285
286 protected void reset(int showStyle) {
287 synchronized (fMutex) {
288 fShowStyle= showStyle;
289 fIsReset= true;
290 fMutex.notifyAll();
291 }
292 }
293
294 protected void stop() {
295 Thread threadToStop= fThread;
296 if (threadToStop !is null && threadToStop.isAlive())
297 threadToStop.interrupt();
298 }
299
300 private bool contains(char[] characters, char character) {
301 if (characters !is null) {
302 for (int i= 0; i < characters.length; i++) {
303 if (character is characters[i])
304 return true;
305 }
306 }
307 return false;
308 }
309
310 public void keyPressed(KeyEvent e) {
311 // Only act on typed characters and ignore modifier-only events
312 if (e.character is 0 && (e.keyCode & DWT.KEYCODE_BIT) is 0)
313 return;
314
315 if (e.character !is 0 && (e.stateMask is DWT.ALT))
316 return;
317
318 // Only act on characters that are trigger candidates. This
319 // avoids computing the model selection on every keystroke
320 if (computeAllAutoActivationTriggers().indexOf(e.character) < 0) {
321 stop();
322 return;
323 }
324
325 int showStyle;
326 int pos= fContentAssistSubjectControlAdapter.getSelectedRange().x;
327 char[] activation;
328
329 activation= fContentAssistSubjectControlAdapter.getCompletionProposalAutoActivationCharacters(ContentAssistant.this, pos);
330
331 if (contains(activation, e.character) && !isProposalPopupActive())
332 showStyle= SHOW_PROPOSALS;
333 else {
334 activation= fContentAssistSubjectControlAdapter.getContextInformationAutoActivationCharacters(ContentAssistant.this, pos);
335 if (contains(activation, e.character) && !isContextInfoPopupActive())
336 showStyle= SHOW_CONTEXT_INFO;
337 else {
338 stop();
339 return;
340 }
341 }
342
343 if (fThread !is null && fThread.isAlive())
344 reset(showStyle);
345 else
346 start(showStyle);
347 }
348
349 /*
350 * @see dwt.custom.VerifyKeyListener#verifyKey(dwt.events.VerifyEvent)
351 */
352 public void verifyKey(VerifyEvent event) {
353 keyPressed(event);
354 }
355
356 protected void showAssist(final int showStyle) {
357 final Control control= fContentAssistSubjectControlAdapter.getControl();
358 if (control is null)
359 return;
360
361 final Display d= control.getDisplay();
362 if (d is null)
363 return;
364
365 try {
366 d.syncExec(new Runnable() {
367 public void run() {
368 if (isProposalPopupActive())
369 return;
370
371 if (control.isDisposed() || !control.isFocusControl())
372 return;
373
374 if (showStyle is SHOW_PROPOSALS) {
375 if (!prepareToShowCompletions(true))
376 return;
377 fProposalPopup.showProposals(true);
378 fLastAutoActivation= System.currentTimeMillis();
379 } else if (showStyle is SHOW_CONTEXT_INFO && fContextInfoPopup !is null) {
380 promoteKeyListener();
381 fContextInfoPopup.showContextProposals(true);
382 }
383 }
384 });
385 } catch (DWTError e) {
386 }
387 }
388 }
389
390 /**
391 * The layout manager layouts the various windows associated with the content assistant based on
392 * the settings of the content assistant.
393 */
394 class LayoutManager : Listener {
395
396 // Presentation types.
397 /** The presentation type for the proposal selection popup. */
398 public final static int LAYOUT_PROPOSAL_SELECTOR= 0;
399 /** The presentation type for the context selection popup. */
400 public final static int LAYOUT_CONTEXT_SELECTOR= 1;
401 /** The presentation type for the context information hover . */
402 public final static int LAYOUT_CONTEXT_INFO_POPUP= 2;
403
404 int fContextType= LAYOUT_CONTEXT_SELECTOR;
405 Shell[] fShells= new Shell[3];
406 Object[] fPopups= new Object[3];
407
408 protected void add(Object popup, Shell shell, int type, int offset) {
409 Assert.isNotNull(popup);
410 Assert.isTrue(shell !is null && !shell.isDisposed());
411 checkType(type);
412
413 if (fShells[type] !is shell) {
414 if (fShells[type] !is null)
415 fShells[type].removeListener(DWT.Dispose, this);
416 shell.addListener(DWT.Dispose, this);
417 fShells[type]= shell;
418 }
419
420 fPopups[type]= popup;
421 if (type is LAYOUT_CONTEXT_SELECTOR || type is LAYOUT_CONTEXT_INFO_POPUP)
422 fContextType= type;
423
424 layout(type, offset);
425 adjustListeners(type);
426 }
427
428 protected void checkType(int type) {
429 Assert.isTrue(type is LAYOUT_PROPOSAL_SELECTOR ||
430 type is LAYOUT_CONTEXT_SELECTOR || type is LAYOUT_CONTEXT_INFO_POPUP);
431 }
432
433 public void handleEvent(Event event) {
434 Widget source= event.widget;
435 source.removeListener(DWT.Dispose, this);
436
437 int type= getShellType(source);
438 checkType(type);
439 fShells[type]= null;
440
441 switch (type) {
442 case LAYOUT_PROPOSAL_SELECTOR:
443 if (fContextType is LAYOUT_CONTEXT_SELECTOR &&
444 Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) {
445 // Restore event notification to the tip popup.
446 addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR);
447 }
448 break;
449
450 case LAYOUT_CONTEXT_SELECTOR:
451 if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
452 if (fProposalPopupOrientation is PROPOSAL_STACKED)
453 layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset());
454 // Restore event notification to the proposal popup.
455 addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR);
456 }
457 fContextType= LAYOUT_CONTEXT_INFO_POPUP;
458 break;
459
460 case LAYOUT_CONTEXT_INFO_POPUP:
461 if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
462 if (fContextInfoPopupOrientation is CONTEXT_INFO_BELOW)
463 layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset());
464 }
465 fContextType= LAYOUT_CONTEXT_SELECTOR;
466 break;
467 }
468 }
469
470 protected int getShellType(Widget shell) {
471 for (int i= 0; i < fShells.length; i++) {
472 if (fShells[i] is shell)
473 return i;
474 }
475 return -1;
476 }
477
478 /**
479 * Layouts the popup defined by <code>type</code> at the given widget offset.
480 *
481 * @param type the kind of popup to layout
482 * @param offset the widget offset
483 */
484 protected void layout(int type, int offset) {
485 switch (type) {
486 case LAYOUT_PROPOSAL_SELECTOR:
487 layoutProposalSelector(offset);
488 break;
489 case LAYOUT_CONTEXT_SELECTOR:
490 layoutContextSelector(offset);
491 break;
492 case LAYOUT_CONTEXT_INFO_POPUP:
493 layoutContextInfoPopup(offset);
494 break;
495 }
496 }
497
498 protected void layoutProposalSelector(int offset) {
499 if (fContextType is LAYOUT_CONTEXT_INFO_POPUP &&
500 fContextInfoPopupOrientation is CONTEXT_INFO_BELOW &&
501 Helper.okToUse(fShells[LAYOUT_CONTEXT_INFO_POPUP])) {
502 // Stack proposal selector beneath the tip box.
503 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
504 Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP];
505 shell.setLocation(getStackedLocation(shell, parent));
506 } else if (fContextType !is LAYOUT_CONTEXT_SELECTOR ||
507 !Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) {
508 // There are no other presentations to be concerned with,
509 // so place the proposal selector beneath the cursor line.
510 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
511 CompletionProposalPopup popup= (CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR];
512 shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup));
513 } else {
514 CompletionProposalPopup popup= ((CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR]);
515 switch (fProposalPopupOrientation) {
516 case PROPOSAL_REMOVE: {
517 // Remove the tip selector and place the
518 // proposal selector beneath the cursor line.
519 fShells[LAYOUT_CONTEXT_SELECTOR].dispose();
520 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
521 shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup));
522 break;
523 }
524 case PROPOSAL_OVERLAY: {
525 // Overlay the tip selector with the proposal selector.
526 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
527 shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup));
528 break;
529 }
530 case PROPOSAL_STACKED: {
531 // Stack the proposal selector beneath the tip selector.
532 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
533 Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR];
534 shell.setLocation(getStackedLocation(shell, parent));
535 break;
536 }
537 }
538 }
539 }
540
541 protected void layoutContextSelector(int offset) {
542 // Always place the context selector beneath the cursor line.
543 Shell shell= fShells[LAYOUT_CONTEXT_SELECTOR];
544 shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, null));
545
546 if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
547 switch (fProposalPopupOrientation) {
548 case PROPOSAL_REMOVE:
549 // Remove the proposal selector.
550 fShells[LAYOUT_PROPOSAL_SELECTOR].dispose();
551 break;
552
553 case PROPOSAL_OVERLAY:
554 // The proposal selector has been overlaid by the tip selector.
555 break;
556
557 case PROPOSAL_STACKED: {
558 // Stack the proposal selector beneath the tip selector.
559 shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
560 Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR];
561 shell.setLocation(getStackedLocation(shell, parent));
562 break;
563 }
564 }
565 }
566 }
567
568 protected void layoutContextInfoPopup(int offset) {
569 switch (fContextInfoPopupOrientation) {
570 case CONTEXT_INFO_ABOVE: {
571 // Place the popup above the cursor line.
572 Shell shell= fShells[LAYOUT_CONTEXT_INFO_POPUP];
573 shell.setBounds(computeBoundsAboveBelow(shell, shell.getSize(), offset));
574 break;
575 }
576 case CONTEXT_INFO_BELOW: {
577 // Place the popup beneath the cursor line.
578 Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP];
579 parent.setBounds(computeBoundsBelowAbove(parent, parent.getSize(), offset, null));
580 if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
581 // Stack the proposal selector beneath the context info popup.
582 Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR];
583 shell.setLocation(getStackedLocation(shell, parent));
584 }
585 break;
586 }
587 }
588 }
589
590 /**
591 * Moves <code>point</code> such that <code>rectangle</code> does not bleed outside of
592 * <code>bounds</code>. All coordinates must have the same reference.
593 *
594 * @param point the point to move if needed
595 * @param shellSize the size of the shell that may be moved
596 * @param bounds the bounds
597 * @since 3.3
598 */
599 protected void constrainLocation(Point point, Point shellSize, Rectangle bounds) {
600 if (point.x + shellSize.x > bounds.x + bounds.width)
601 point.x= bounds.x + bounds.width - shellSize.x;
602
603 if (point.x < bounds.x)
604 point.x= bounds.x;
605
606 if (point.y + shellSize.y > bounds.y + bounds.height)
607 point.y= bounds.y + bounds.height - shellSize.y;
608
609 if (point.y < bounds.y)
610 point.y= bounds.y;
611 }
612
613 protected Rectangle constrainHorizontally(Rectangle rect, Rectangle bounds) {
614 // clip width
615 if (rect.width > bounds.width)
616 rect.width= bounds.width;
617
618 if (rect.x + rect.width > bounds.x + bounds.width)
619 rect.x= bounds.x + bounds.width - rect.width;
620 if (rect.x < bounds.x)
621 rect.x= bounds.x;
622
623 return rect;
624 }
625
626 /**
627 * Returns the display bounds for <code>shell</code> such that it appears right above
628 * <code>offset</code>, or below it if above is not suitable. The returned bounds lie
629 * within the monitor at the caret location and never overlap with the caret line.
630 *
631 * @param shell the shell to compute the placement for
632 * @param preferred the preferred size for <code>shell</code>
633 * @param offset the caret offset in the subject control
634 * @return the point right above <code>offset</code> in display coordinates
635 * @since 3.3
636 */
637 protected Rectangle computeBoundsAboveBelow(Shell shell, Point preferred, int offset) {
638 Control subjectControl= fContentAssistSubjectControlAdapter.getControl();
639 Display display= subjectControl.getDisplay();
640 Rectangle caret= getCaretRectangle(offset);
641 Monitor monitor= getClosestMonitor(display, caret);
642 Rectangle bounds= monitor.getClientArea();
643 Geometry.moveInside(caret, bounds);
644
645 int spaceAbove= caret.y - bounds.y;
646 int caretLowerY= caret.y + caret.height;
647 int spaceBelow= bounds.y + bounds.height - caretLowerY;
648 Rectangle rect;
649 if (spaceAbove >= preferred.y)
650 rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y);
651 else if (spaceBelow >= preferred.y)
652 rect= new Rectangle(caret.x, caretLowerY, preferred.x, preferred.y);
653 // we can't fit in the preferred size - squeeze into larger area
654 else if (spaceBelow <= spaceAbove)
655 rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove);
656 else
657 rect= new Rectangle(caret.x, caretLowerY, preferred.x, spaceBelow);
658
659 return constrainHorizontally(rect, bounds);
660 }
661
662 /**
663 * Returns the display bounds for <code>shell</code> such that it appears right below
664 * <code>offset</code>, or above it if below is not suitable. The returned bounds lie
665 * within the monitor at the caret location and never overlap with the caret line.
666 *
667 * @param shell the shell to compute the placement for
668 * @param preferred the preferred size for <code>shell</code>
669 * @param offset the caret offset in the subject control
670 * @param popup a popup to inform if the location was switched to above, <code>null</code> to do nothing
671 * @return the point right below <code>offset</code> in display coordinates
672 * @since 3.3
673 */
674 protected Rectangle computeBoundsBelowAbove(Shell shell, Point preferred, int offset, CompletionProposalPopup popup) {
675 Control subjectControl= fContentAssistSubjectControlAdapter.getControl();
676 Display display= subjectControl.getDisplay();
677 Rectangle caret= getCaretRectangle(offset);
678 Monitor monitor= getClosestMonitor(display, caret);
679 Rectangle bounds= monitor.getClientArea();
680 Geometry.moveInside(caret, bounds);
681
682 int threshold= popup is null ? Integer.MAX_VALUE : popup.getMinimalHeight();
683 int spaceAbove= caret.y - bounds.y;
684 int spaceBelow= bounds.y + bounds.height - (caret.y + caret.height);
685 Rectangle rect;
686 bool switched= false;
687 if (spaceBelow >= preferred.y)
688 rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, preferred.y);
689 // squeeze in below if we have at least threshold space
690 else if (spaceBelow >= threshold)
691 rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow);
692 else if (spaceAbove >= preferred.y) {
693 rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y);
694 switched= true;
695 } else if (spaceBelow >= spaceAbove) {
696 // we can't fit in the preferred size - squeeze into larger area
697 rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow);
698 } else {
699 rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove);
700 switched= true;
701 }
702
703 if (popup !is null)
704 popup.switchedPositionToAbove(switched);
705
706 return constrainHorizontally(rect, bounds);
707 }
708
709 private Rectangle getCaretRectangle(int offset) {
710 Point location= fContentAssistSubjectControlAdapter.getLocationAtOffset(offset);
711 Control subjectControl= fContentAssistSubjectControlAdapter.getControl();
712 Point controlSize= subjectControl.getSize();
713 constrainLocation(location, new Point(0, 0), new Rectangle(0, 0, controlSize.x, controlSize.y));
714 location= subjectControl.toDisplay(location);
715 Rectangle subjectRectangle= new Rectangle(location.x, location.y, 1, fContentAssistSubjectControlAdapter.getLineHeight());
716 return subjectRectangle;
717 }
718
719 protected Point getStackedLocation(Shell shell, Shell parent) {
720 Point p= parent.getLocation();
721 Point size= parent.getSize();
722 p.x += size.x / 4;
723 p.y += size.y;
724
725 p= parent.toDisplay(p);
726
727 Point shellSize= shell.getSize();
728 Monitor monitor= getClosestMonitor(parent.getDisplay(), new Rectangle(p.x, p.y, 0, 0));
729 Rectangle displayBounds= monitor.getClientArea();
730 constrainLocation(p, shellSize, displayBounds);
731
732 return p;
733 }
734
735 protected void adjustListeners(int type) {
736 switch (type) {
737 case LAYOUT_PROPOSAL_SELECTOR:
738 if (fContextType is LAYOUT_CONTEXT_SELECTOR &&
739 Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR]))
740 // Disable event notification to the tip selector.
741 removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR);
742 break;
743 case LAYOUT_CONTEXT_SELECTOR:
744 if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR]))
745 // Disable event notification to the proposal selector.
746 removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR);
747 break;
748 case LAYOUT_CONTEXT_INFO_POPUP:
749 break;
750 }
751 }
752
753 /**
754 * Copied from dwtx.jface.window.Window.
755 * Returns the monitor whose client area contains the given point. If no
756 * monitor contains the point, returns the monitor that is closest to the
757 * point. If this is ever made public, it should be moved into a separate
758 * utility class.
759 *
760 * @param toSearch
761 * point to find (display coordinates)
762 * @param rectangle
763 * rectangle to find (display coordinates)
764 * @return the monitor closest to the given point
765 * @since 3.3
766 */
767 private Monitor getClosestMonitor(Display toSearch, Rectangle rectangle) {
768 int closest = Integer.MAX_VALUE;
769
770 Point toFind= Geometry.centerPoint(rectangle);
771 Monitor[] monitors = toSearch.getMonitors();
772 Monitor result = monitors[0];
773
774 for (int idx = 0; idx < monitors.length; idx++) {
775 Monitor current = monitors[idx];
776
777 Rectangle clientArea = current.getClientArea();
778
779 if (clientArea.contains(toFind)) {
780 return current;
781 }
782
783 int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind);
784 if (distance < closest) {
785 closest = distance;
786 result = current;
787 }
788 }
789
790 return result;
791 }
792 }
793
794 /**
795 * Internal key listener and event consumer.
796 */
797 class InternalListener : VerifyKeyListener, IEventConsumer {
798
799 /**
800 * Verifies key events by notifying the registered listeners. Each listener is allowed to
801 * indicate that the event has been handled and should not be further processed.
802 *
803 * @param e the verify event
804 * @see VerifyKeyListener#verifyKey(dwt.events.VerifyEvent)
805 */
806 public void verifyKey(VerifyEvent e) {
807 IContentAssistListener[] listeners= (IContentAssistListener[]) fListeners.clone();
808 for (int i= 0; i < listeners.length; i++) {
809 if (listeners[i] !is null) {
810 if (!listeners[i].verifyKey(e) || !e.doit)
811 break;
812 }
813 }
814 if (fAutoAssistListener !is null)
815 fAutoAssistListener.keyPressed(e);
816 }
817
818 /*
819 * @see IEventConsumer#processEvent
820 */
821 public void processEvent(VerifyEvent event) {
822
823 installKeyListener();
824
825 IContentAssistListener[] listeners= (IContentAssistListener[]) fListeners.clone();
826 for (int i= 0; i < listeners.length; i++) {
827 if (listeners[i] !is null) {
828 listeners[i].processEvent(event);
829 if (!event.doit)
830 return;
831 }
832 }
833 }
834 }
835
836 /**
837 * Dialog store constants.
838 *
839 * @since 3.0
840 */
841 public static final String STORE_SIZE_X= "size.x"; //$NON-NLS-1$
842 public static final String STORE_SIZE_Y= "size.y"; //$NON-NLS-1$
843
844 // Content-Assist Listener types
845 final static int CONTEXT_SELECTOR= 0;
846 final static int PROPOSAL_SELECTOR= 1;
847 final static int CONTEXT_INFO_POPUP= 2;
848
849 /**
850 * The popup priority: &gt; linked position proposals and hover pop-ups. Default value:
851 * <code>20</code>;
852 *
853 * @since 3.0
854 */
855 public static final int WIDGET_PRIORITY= 20;
856
857 private static final int DEFAULT_AUTO_ACTIVATION_DELAY= 500;
858
859 private IInformationControlCreator fInformationControlCreator;
860 private int fAutoActivationDelay= DEFAULT_AUTO_ACTIVATION_DELAY;
861 private bool fIsAutoActivated= false;
862 private bool fIsAutoInserting= false;
863 private int fProposalPopupOrientation= PROPOSAL_OVERLAY;
864 private int fContextInfoPopupOrientation= CONTEXT_INFO_ABOVE;
865 private Map fProcessors;
866
867 /**
868 * The partitioning.
869 *
870 * @since 3.0
871 */
872 private String fPartitioning;
873
874 private Color fContextInfoPopupBackground;
875 private Color fContextInfoPopupForeground;
876 private Color fContextSelectorBackground;
877 private Color fContextSelectorForeground;
878 private Color fProposalSelectorBackground;
879 private Color fProposalSelectorForeground;
880
881 private ITextViewer fViewer;
882 private String fLastErrorMessage;
883
884 private Closer fCloser;
885 LayoutManager fLayoutManager;
886 private AutoAssistListener fAutoAssistListener;
887 private InternalListener fInternalListener;
888 private CompletionProposalPopup fProposalPopup;
889 private ContextInformationPopup fContextInfoPopup;
890
891 /**
892 * Flag which tells whether a verify key listener is hooked.
893 *
894 * @since 3.0
895 */
896 private bool fVerifyKeyListenerHooked= false;
897 private IContentAssistListener[] fListeners= new IContentAssistListener[4];
898 /**
899 * The content assist subject control.
900 *
901 * @since 3.0
902 */
903 private IContentAssistSubjectControl fContentAssistSubjectControl;
904 /**
905 * The content assist subject control's shell.
906 *
907 * @since 3.2
908 */
909 private Shell fContentAssistSubjectControlShell;
910 /**
911 * The content assist subject control's shell traverse listener.
912 *
913 * @since 3.2
914 */
915 private TraverseListener fCASCSTraverseListener;
916 /**
917 * The content assist subject control adapter.
918 *
919 * @since 3.0
920 */
921 private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter;
922 /**
923 * The dialog settings for the control's bounds.
924 *
925 * @since 3.0
926 */
927 private IDialogSettings fDialogSettings;
928 /**
929 * Prefix completion setting.
930 *
931 * @since 3.0
932 */
933 private bool fIsPrefixCompletionEnabled= false;
934 /**
935 * The list of completion listeners.
936 *
937 * @since 3.2
938 */
939 private ListenerList fCompletionListeners= new ListenerList(ListenerList.IDENTITY);
940 /**
941 * The message to display at the bottom of the proposal popup.
942 *
943 * @since 3.2
944 */
945 private String fMessage= ""; //$NON-NLS-1$
946 /**
947 * The cycling mode property.
948 *
949 * @since 3.2
950 */
951 private bool fIsRepetitionMode= false;
952 /**
953 * The show empty property.
954 *
955 * @since 3.2
956 */
957 private bool fShowEmptyList= false;
958 /**
959 * The message line property.
960 *
961 * @since 3.2
962 */
963 private bool fIsStatusLineVisible;
964 /**
965 * The last system time when auto activation performed.
966 *
967 * @since 3.2
968 */
969 private long fLastAutoActivation= Long.MIN_VALUE;
970 /**
971 * The iteration key sequence to listen for, or <code>null</code>.
972 *
973 * @since 3.2
974 */
975 private KeySequence fRepeatedInvocationKeySequence;
976
977 /**
978 * Maps handler to command identifiers.
979 *
980 * @since 3.4
981 */
982 private Map fHandlers;
983
984 /**
985 * Tells whether colored labels support is enabled.
986 *
987 * @since 3.4
988 */
989 private bool fIsColoredLabelsSupportEnabled= false;
990
991
992 /**
993 * Creates a new content assistant. The content assistant is not automatically activated,
994 * overlays the completion proposals with context information list if necessary, and shows the
995 * context information above the location at which it was activated. If auto activation will be
996 * enabled, without further configuration steps, this content assistant is activated after a 500
997 * milliseconds delay. It uses the default partitioning.
998 */
999 public ContentAssistant() {
1000 fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING;
1001 }
1002
1003 /**
1004 * Sets the document partitioning this content assistant is using.
1005 *
1006 * @param partitioning the document partitioning for this content assistant
1007 * @since 3.0
1008 */
1009 public void setDocumentPartitioning(String partitioning) {
1010 Assert.isNotNull(partitioning);
1011 fPartitioning= partitioning;
1012 }
1013
1014 /*
1015 * @see dwtx.jface.text.contentassist.IContentAssistantExtension#getDocumentPartitioning()
1016 * @since 3.0
1017 */
1018 public String getDocumentPartitioning() {
1019 return fPartitioning;
1020 }
1021
1022 /**
1023 * Registers a given content assist processor for a particular content type. If there is already
1024 * a processor registered for this type, the new processor is registered instead of the old one.
1025 *
1026 * @param processor the content assist processor to register, or <code>null</code> to remove
1027 * an existing one
1028 * @param contentType the content type under which to register
1029 */
1030 public void setContentAssistProcessor(IContentAssistProcessor processor, String contentType) {
1031
1032 Assert.isNotNull(contentType);
1033
1034 if (fProcessors is null)
1035 fProcessors= new HashMap();
1036
1037 if (processor is null)
1038 fProcessors.remove(contentType);
1039 else
1040 fProcessors.put(contentType, processor);
1041 }
1042
1043 /*
1044 * @see IContentAssistant#getContentAssistProcessor
1045 */
1046 public IContentAssistProcessor getContentAssistProcessor(String contentType) {
1047 if (fProcessors is null)
1048 return null;
1049
1050 return (IContentAssistProcessor) fProcessors.get(contentType);
1051 }
1052
1053 /**
1054 * Computes the sorted set of all auto activation trigger characters.
1055 *
1056 * @return the sorted set of all auto activation trigger characters
1057 * @since 3.1
1058 */
1059 private String computeAllAutoActivationTriggers() {
1060 if (fProcessors is null)
1061 return ""; //$NON-NLS-1$
1062
1063 StringBuffer buf= new StringBuffer(5);
1064 Iterator iter= fProcessors.entrySet().iterator();
1065 while (iter.hasNext()) {
1066 Entry entry= (Entry) iter.next();
1067 IContentAssistProcessor processor= (IContentAssistProcessor) entry.getValue();
1068 char[] triggers= processor.getCompletionProposalAutoActivationCharacters();
1069 if (triggers !is null)
1070 buf.append(triggers);
1071 triggers= processor.getContextInformationAutoActivationCharacters();
1072 if (triggers !is null)
1073 buf.append(triggers);
1074 }
1075 return buf.toString();
1076 }
1077
1078 /**
1079 * Enables the content assistant's auto activation mode.
1080 *
1081 * @param enabled indicates whether auto activation is enabled or not
1082 */
1083 public void enableAutoActivation(bool enabled) {
1084 fIsAutoActivated= enabled;
1085 manageAutoActivation(fIsAutoActivated);
1086 }
1087
1088 /**
1089 * Enables the content assistant's auto insertion mode. If enabled, the content assistant
1090 * inserts a proposal automatically if it is the only proposal. In the case of ambiguities, the
1091 * user must make the choice.
1092 *
1093 * @param enabled indicates whether auto insertion is enabled or not
1094 * @since 2.0
1095 */
1096 public void enableAutoInsert(bool enabled) {
1097 fIsAutoInserting= enabled;
1098 }
1099
1100 /**
1101 * Returns whether this content assistant is in the auto insertion mode or not.
1102 *
1103 * @return <code>true</code> if in auto insertion mode
1104 * @since 2.0
1105 */
1106 bool isAutoInserting() {
1107 return fIsAutoInserting;
1108 }
1109
1110 /**
1111 * Installs and uninstall the listeners needed for auto activation.
1112 *
1113 * @param start <code>true</code> if listeners must be installed, <code>false</code> if they
1114 * must be removed
1115 * @since 2.0
1116 */
1117 private void manageAutoActivation(bool start) {
1118 if (start) {
1119
1120 if ((fContentAssistSubjectControlAdapter !is null) && fAutoAssistListener is null) {
1121 fAutoAssistListener= createAutoAssistListener();
1122 // For details see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212
1123 if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener())
1124 fContentAssistSubjectControlAdapter.appendVerifyKeyListener(fAutoAssistListener);
1125 else
1126 fContentAssistSubjectControlAdapter.addKeyListener(fAutoAssistListener);
1127 }
1128
1129 } else if (fAutoAssistListener !is null) {
1130 // For details see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212
1131 if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener())
1132 fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fAutoAssistListener);
1133 else
1134 fContentAssistSubjectControlAdapter.removeKeyListener(fAutoAssistListener);
1135 fAutoAssistListener= null;
1136 }
1137 }
1138
1139 /**
1140 * This method allows subclasses to provide their own {@link AutoAssistListener}.
1141 *
1142 * @return a new auto assist listener
1143 * @since 3.4
1144 */
1145 protected AutoAssistListener createAutoAssistListener() {
1146 return new AutoAssistListener();
1147 }
1148
1149 /**
1150 * Sets the delay after which the content assistant is automatically invoked if the cursor is
1151 * behind an auto activation character.
1152 *
1153 * @param delay the auto activation delay
1154 */
1155 public void setAutoActivationDelay(int delay) {
1156 fAutoActivationDelay= delay;
1157 }
1158
1159 /**
1160 * Gets the delay after which the content assistant is automatically invoked if the cursor is
1161 * behind an auto activation character.
1162 *
1163 * @return the auto activation delay
1164 * @since 3.4
1165 */
1166 public int getAutoActivationDelay() {
1167 return fAutoActivationDelay;
1168 }
1169
1170 /**
1171 * Sets the proposal pop-ups' orientation. The following values may be used:
1172 * <ul>
1173 * <li>PROPOSAL_OVERLAY<p>
1174 * proposal popup windows should overlay each other
1175 * </li>
1176 * <li>PROPOSAL_REMOVE<p>
1177 * any currently shown proposal popup should be closed
1178 * </li>
1179 * <li>PROPOSAL_STACKED<p>
1180 * proposal popup windows should be vertical stacked, with no overlap,
1181 * beneath the line containing the current cursor location
1182 * </li>
1183 * </ul>
1184 *
1185 * @param orientation the popup's orientation
1186 */
1187 public void setProposalPopupOrientation(int orientation) {
1188 fProposalPopupOrientation= orientation;
1189 }
1190
1191 /**
1192 * Sets the context information popup's orientation.
1193 * The following values may be used:
1194 * <ul>
1195 * <li>CONTEXT_ABOVE<p>
1196 * context information popup should always appear above the line containing
1197 * the current cursor location
1198 * </li>
1199 * <li>CONTEXT_BELOW<p>
1200 * context information popup should always appear below the line containing
1201 * the current cursor location
1202 * </li>
1203 * </ul>
1204 *
1205 * @param orientation the popup's orientation
1206 */
1207 public void setContextInformationPopupOrientation(int orientation) {
1208 fContextInfoPopupOrientation= orientation;
1209 }
1210
1211 /**
1212 * Sets the context information popup's background color.
1213 *
1214 * @param background the background color
1215 */
1216 public void setContextInformationPopupBackground(Color background) {
1217 fContextInfoPopupBackground= background;
1218 }
1219
1220 /**
1221 * Returns the background of the context information popup.
1222 *
1223 * @return the background of the context information popup
1224 * @since 2.0
1225 */
1226 Color getContextInformationPopupBackground() {
1227 return fContextInfoPopupBackground;
1228 }
1229
1230 /**
1231 * Sets the context information popup's foreground color.
1232 *
1233 * @param foreground the foreground color
1234 * @since 2.0
1235 */
1236 public void setContextInformationPopupForeground(Color foreground) {
1237 fContextInfoPopupForeground= foreground;
1238 }
1239
1240 /**
1241 * Returns the foreground of the context information popup.
1242 *
1243 *
1244 * @return the foreground of the context information popup
1245 * @since 2.0
1246 */
1247 Color getContextInformationPopupForeground() {
1248 return fContextInfoPopupForeground;
1249 }
1250
1251 /**
1252 * Sets the proposal selector's background color.
1253 * <p>
1254 * <strong>Note:</strong> As of 3.4, you should only call this
1255 * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_BACKGROUND_COLOR}.
1256 * </p>
1257 *
1258 * @param background the background color
1259 * @since 2.0
1260 */
1261 public void setProposalSelectorBackground(Color background) {
1262 fProposalSelectorBackground= background;
1263 }
1264
1265 /**
1266 * Returns the custom background color of the proposal selector.
1267 *
1268 * @return the background of the proposal selector or <code>null</code> if not set
1269 * @since 2.0
1270 */
1271 Color getProposalSelectorBackground() {
1272 return fProposalSelectorBackground;
1273 }
1274
1275 /**
1276 * Sets the proposal's foreground color.
1277 * <p>
1278 * <strong>Note:</strong> As of 3.4, you should only call this
1279 * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_FOREGROUND_COLOR}.
1280 * </p>
1281 *
1282 * @param foreground the foreground color
1283 * @since 2.0
1284 */
1285 public void setProposalSelectorForeground(Color foreground) {
1286 fProposalSelectorForeground= foreground;
1287 }
1288
1289 /**
1290 * Returns the custom foreground color of the proposal selector.
1291 *
1292 * @return the foreground of the proposal selector or <code>null</code> if not set
1293 * @since 2.0
1294 */
1295 Color getProposalSelectorForeground() {
1296 return fProposalSelectorForeground;
1297 }
1298
1299 /**
1300 * Sets the context selector's background color.
1301 *
1302 * @param background the background color
1303 * @since 2.0
1304 */
1305 public void setContextSelectorBackground(Color background) {
1306 fContextSelectorBackground= background;
1307 }
1308
1309 /**
1310 * Returns the background of the context selector.
1311 *
1312 * @return the background of the context selector
1313 * @since 2.0
1314 */
1315 Color getContextSelectorBackground() {
1316 return fContextSelectorBackground;
1317 }
1318
1319 /**
1320 * Sets the context selector's foreground color.
1321 *
1322 * @param foreground the foreground color
1323 * @since 2.0
1324 */
1325 public void setContextSelectorForeground(Color foreground) {
1326 fContextSelectorForeground= foreground;
1327 }
1328
1329 /**
1330 * Returns the foreground of the context selector.
1331 *
1332 * @return the foreground of the context selector
1333 * @since 2.0
1334 */
1335 Color getContextSelectorForeground() {
1336 return fContextSelectorForeground;
1337 }
1338
1339 /**
1340 * Sets the information control creator for the additional information control.
1341 *
1342 * @param creator the information control creator for the additional information control
1343 * @since 2.0
1344 */
1345 public void setInformationControlCreator(IInformationControlCreator creator) {
1346 fInformationControlCreator= creator;
1347 }
1348
1349 /*
1350 * @see IControlContentAssistant#install(IContentAssistSubjectControl)
1351 * @since 3.0
1352 */
1353 protected void install(IContentAssistSubjectControl contentAssistSubjectControl) {
1354 fContentAssistSubjectControl= contentAssistSubjectControl;
1355 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl);
1356 install();
1357 }
1358
1359 /*
1360 * @see IContentAssist#install
1361 * @since 3.0
1362 */
1363 public void install(ITextViewer textViewer) {
1364 fViewer= textViewer;
1365 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer);
1366 install();
1367 }
1368
1369 protected void install() {
1370
1371 fLayoutManager= new LayoutManager();
1372 fInternalListener= new InternalListener();
1373
1374 AdditionalInfoController controller= null;
1375 if (fInformationControlCreator !is null) {
1376 int delay= fAutoActivationDelay;
1377 if (delay is 0)
1378 delay= DEFAULT_AUTO_ACTIVATION_DELAY;
1379 delay= Math.round(delay * 1.5f);
1380 controller= new AdditionalInfoController(fInformationControlCreator, delay);
1381 }
1382
1383 fContextInfoPopup= fContentAssistSubjectControlAdapter.createContextInfoPopup(this);
1384 fProposalPopup= fContentAssistSubjectControlAdapter.createCompletionProposalPopup(this, controller);
1385
1386 registerHandler(SELECT_NEXT_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_NEXT));
1387 registerHandler(SELECT_PREVIOUS_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_PREVIOUS));
1388
1389 if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) {
1390 fContentAssistSubjectControlShell= fContentAssistSubjectControlAdapter.getControl().getShell();
1391 fCASCSTraverseListener= new TraverseListener() {
1392 public void keyTraversed(TraverseEvent e) {
1393 if (e.detail is DWT.TRAVERSE_ESCAPE && isProposalPopupActive())
1394 e.doit= false;
1395 }
1396 };
1397 fContentAssistSubjectControlShell.addTraverseListener(fCASCSTraverseListener);
1398 }
1399
1400 manageAutoActivation(fIsAutoActivated);
1401 }
1402
1403 /*
1404 * @see IContentAssist#uninstall
1405 */
1406 public void uninstall() {
1407 hide();
1408 manageAutoActivation(false);
1409
1410 if (fHandlers !is null) {
1411 fHandlers.clear();
1412 fHandlers= null;
1413 }
1414
1415 if (fCloser !is null) {
1416 fCloser.uninstall();
1417 fCloser= null;
1418 }
1419
1420 if (Helper.okToUse(fContentAssistSubjectControlShell))
1421 fContentAssistSubjectControlShell.removeTraverseListener(fCASCSTraverseListener);
1422 fCASCSTraverseListener= null;
1423 fContentAssistSubjectControlShell= null;
1424
1425 fViewer= null;
1426 fContentAssistSubjectControl= null;
1427 fContentAssistSubjectControlAdapter= null;
1428 }
1429
1430 /**
1431 * Adds the given shell of the specified type to the layout. Valid types are defined by
1432 * <code>LayoutManager</code>.
1433 *
1434 * @param popup a content assist popup
1435 * @param shell the shell of the content-assist popup
1436 * @param type the type of popup
1437 * @param visibleOffset the offset at which to layout the popup relative to the offset of the
1438 * viewer's visible region
1439 * @since 2.0
1440 */
1441 void addToLayout(Object popup, Shell shell, int type, int visibleOffset) {
1442 fLayoutManager.add(popup, shell, type, visibleOffset);
1443 }
1444
1445 /**
1446 * Layouts the registered popup of the given type relative to the given offset. The offset is
1447 * relative to the offset of the viewer's visible region. Valid types are defined by
1448 * <code>LayoutManager</code>.
1449 *
1450 * @param type the type of popup to layout
1451 * @param visibleOffset the offset at which to layout relative to the offset of the viewer's
1452 * visible region
1453 * @since 2.0
1454 */
1455 void layout(int type, int visibleOffset) {
1456 fLayoutManager.layout(type, visibleOffset);
1457 }
1458
1459 /**
1460 * Returns the layout manager.
1461 *
1462 * @return the layout manager
1463 * @since 3.3
1464 */
1465 LayoutManager getLayoutManager() {
1466 return fLayoutManager;
1467 }
1468
1469 /**
1470 * Notifies the controller that a popup has lost focus.
1471 *
1472 * @param e the focus event
1473 */
1474 void popupFocusLost(FocusEvent e) {
1475 fCloser.focusLost(e);
1476 }
1477
1478 /**
1479 * Returns the offset of the selection relative to the offset of the visible region.
1480 *
1481 * @return the offset of the selection relative to the offset of the visible region
1482 * @since 2.0
1483 */
1484 int getSelectionOffset() {
1485 return fContentAssistSubjectControlAdapter.getWidgetSelectionRange().x;
1486 }
1487
1488 /**
1489 * Returns whether the widget token could be acquired. The following are valid listener types:
1490 * <ul>
1491 * <li>AUTO_ASSIST</li>
1492 * <li>CONTEXT_SELECTOR</li>
1493 * <li>PROPOSAL_SELECTOR</li>
1494 * <li>CONTEXT_INFO_POPUP</li>
1495 * </ul>
1496 *
1497 * @param type the listener type for which to acquire
1498 * @return <code>true</code> if the widget token could be acquired
1499 * @since 2.0
1500 */
1501 private bool acquireWidgetToken(int type) {
1502 switch (type) {
1503 case CONTEXT_SELECTOR:
1504 case PROPOSAL_SELECTOR:
1505 if (fContentAssistSubjectControl instanceof IWidgetTokenOwnerExtension) {
1506 IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fContentAssistSubjectControl;
1507 return extension.requestWidgetToken(this, WIDGET_PRIORITY);
1508 } else if (fContentAssistSubjectControl instanceof IWidgetTokenOwner) {
1509 IWidgetTokenOwner owner= (IWidgetTokenOwner) fContentAssistSubjectControl;
1510 return owner.requestWidgetToken(this);
1511 } else if (fViewer instanceof IWidgetTokenOwnerExtension) {
1512 IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fViewer;
1513 return extension.requestWidgetToken(this, WIDGET_PRIORITY);
1514 } else if (fViewer instanceof IWidgetTokenOwner) {
1515 IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer;
1516 return owner.requestWidgetToken(this);
1517 }
1518 }
1519 return true;
1520 }
1521
1522 /**
1523 * Registers a content assist listener. The following are valid listener types:
1524 * <ul>
1525 * <li>AUTO_ASSIST</li>
1526 * <li>CONTEXT_SELECTOR</li>
1527 * <li>PROPOSAL_SELECTOR</li>
1528 * <li>CONTEXT_INFO_POPUP</li>
1529 * </ul>
1530 * Returns whether the listener could be added successfully. A listener can not be added if the
1531 * widget token could not be acquired.
1532 *
1533 * @param listener the listener to register
1534 * @param type the type of listener
1535 * @return <code>true</code> if the listener could be added
1536 */
1537 bool addContentAssistListener(IContentAssistListener listener, int type) {
1538
1539 if (acquireWidgetToken(type)) {
1540
1541 fListeners[type]= listener;
1542
1543 if (fCloser is null && getNumberOfListeners() is 1) {
1544 fCloser= new Closer();
1545 fCloser.install();
1546 fContentAssistSubjectControlAdapter.setEventConsumer(fInternalListener);
1547 installKeyListener();
1548 } else
1549 promoteKeyListener();
1550 return true;
1551 }
1552
1553 return false;
1554 }
1555
1556 /**
1557 * Re-promotes the key listener to the first position, using prependVerifyKeyListener. This
1558 * ensures no other instance is filtering away the keystrokes underneath, if we've been up for a
1559 * while (e.g. when the context info is showing.
1560 *
1561 * @since 3.0
1562 */
1563 private void promoteKeyListener() {
1564 uninstallVerifyKeyListener();
1565 installKeyListener();
1566 }
1567
1568 /**
1569 * Installs a key listener on the text viewer's widget.
1570 */
1571 private void installKeyListener() {
1572 if (!fVerifyKeyListenerHooked) {
1573 if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) {
1574 fVerifyKeyListenerHooked= fContentAssistSubjectControlAdapter.prependVerifyKeyListener(fInternalListener);
1575 }
1576 }
1577 }
1578
1579 /**
1580 * Releases the previously acquired widget token if the token is no longer necessary. The
1581 * following are valid listener types:
1582 * <ul>
1583 * <li>AUTO_ASSIST</li>
1584 * <li>CONTEXT_SELECTOR</li>
1585 * <li>PROPOSAL_SELECTOR</li>
1586 * <li>CONTEXT_INFO_POPUP</li>
1587 * </ul>
1588 *
1589 * @param type the listener type
1590 * @since 2.0
1591 */
1592 private void releaseWidgetToken(int type) {
1593 if (fListeners[CONTEXT_SELECTOR] is null && fListeners[PROPOSAL_SELECTOR] is null) {
1594 IWidgetTokenOwner owner= null;
1595 if (fContentAssistSubjectControl instanceof IWidgetTokenOwner)
1596 owner= (IWidgetTokenOwner) fContentAssistSubjectControl;
1597 else if (fViewer instanceof IWidgetTokenOwner)
1598 owner= (IWidgetTokenOwner) fViewer;
1599 if (owner !is null)
1600 owner.releaseWidgetToken(this);
1601 }
1602 }
1603
1604 /**
1605 * Unregisters a content assist listener.
1606 *
1607 * @param listener the listener to unregister
1608 * @param type the type of listener
1609 * @see #addContentAssistListener(IContentAssistListener, int)
1610 */
1611 void removeContentAssistListener(IContentAssistListener listener, int type) {
1612 fListeners[type]= null;
1613
1614 if (getNumberOfListeners() is 0) {
1615
1616 if (fCloser !is null) {
1617 fCloser.uninstall();
1618 fCloser= null;
1619 }
1620
1621 uninstallVerifyKeyListener();
1622 fContentAssistSubjectControlAdapter.setEventConsumer(null);
1623 }
1624
1625 releaseWidgetToken(type);
1626 }
1627
1628 /**
1629 * Uninstall the key listener from the text viewer's widget.
1630 *
1631 * @since 3.0
1632 */
1633 private void uninstallVerifyKeyListener() {
1634 if (fVerifyKeyListenerHooked) {
1635 if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl()))
1636 fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fInternalListener);
1637 fVerifyKeyListenerHooked= false;
1638 }
1639 }
1640
1641 /**
1642 * Returns the number of listeners.
1643 *
1644 * @return the number of listeners
1645 * @since 2.0
1646 */
1647 private int getNumberOfListeners() {
1648 int count= 0;
1649 for (int i= 0; i <= CONTEXT_INFO_POPUP; i++) {
1650 if (fListeners[i] !is null)
1651 ++count;
1652 }
1653 return count;
1654 }
1655
1656 /*
1657 * @see IContentAssist#showPossibleCompletions
1658 */
1659 public String showPossibleCompletions() {
1660 if (!prepareToShowCompletions(false))
1661 return null;
1662 if (fIsPrefixCompletionEnabled)
1663 return fProposalPopup.incrementalComplete();
1664 return fProposalPopup.showProposals(false);
1665 }
1666
1667 /*
1668 * @see dwtx.jface.text.contentassist.IContentAssistantExtension#completePrefix()
1669 * @since 3.0
1670 */
1671 public String completePrefix() {
1672 if (!prepareToShowCompletions(false))
1673 return null;
1674 return fProposalPopup.incrementalComplete();
1675 }
1676
1677 /**
1678 * Prepares to show content assist proposals. It returns false if auto activation has kicked in
1679 * recently.
1680 *
1681 * @param isAutoActivated whether completion was triggered by auto activation
1682 * @return <code>true</code> if the caller should continue and show the proposals,
1683 * <code>false</code> otherwise.
1684 * @since 3.2
1685 */
1686 private bool prepareToShowCompletions(bool isAutoActivated) {
1687 long current= System.currentTimeMillis();
1688 int gracePeriod= Math.max(fAutoActivationDelay, 200);
1689 if (current < fLastAutoActivation + gracePeriod)
1690 return false;
1691
1692 promoteKeyListener();
1693 fireSessionBeginEvent(isAutoActivated);
1694 return true;
1695 }
1696
1697 /**
1698 * Callback to signal this content assistant that the presentation of the possible completions
1699 * has been stopped.
1700 *
1701 * @since 2.1
1702 */
1703 protected void possibleCompletionsClosed() {
1704 fLastAutoActivation= Long.MIN_VALUE;
1705 storeCompletionProposalPopupSize();
1706 }
1707
1708 /*
1709 * @see IContentAssist#showContextInformation
1710 */
1711 public String showContextInformation() {
1712 promoteKeyListener();
1713 if (fContextInfoPopup !is null)
1714 return fContextInfoPopup.showContextProposals(false);
1715 return null;
1716 }
1717
1718 /**
1719 * Callback to signal this content assistant that the presentation of the context information
1720 * has been stopped.
1721 *
1722 * @since 2.1
1723 */
1724 protected void contextInformationClosed() {
1725 }
1726
1727 /**
1728 * Requests that the specified context information to be shown.
1729 *
1730 * @param contextInformation the context information to be shown
1731 * @param offset the offset to which the context information refers to
1732 * @since 2.0
1733 */
1734 void showContextInformation(IContextInformation contextInformation, int offset) {
1735 if (fContextInfoPopup !is null)
1736 fContextInfoPopup.showContextInformation(contextInformation, offset);
1737 }
1738
1739 /**
1740 * Returns the current content assist error message.
1741 *
1742 * @return an error message or <code>null</code> if no error has occurred
1743 */
1744 String getErrorMessage() {
1745 return fLastErrorMessage;
1746 }
1747
1748 /**
1749 * Returns the content assist processor for the content type of the specified document position.
1750 *
1751 * @param viewer the text viewer
1752 * @param offset a offset within the document
1753 * @return a content-assist processor or <code>null</code> if none exists
1754 * @since 3.0
1755 */
1756 private IContentAssistProcessor getProcessor(ITextViewer viewer, int offset) {
1757 try {
1758
1759 IDocument document= viewer.getDocument();
1760 String type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true);
1761
1762 return getContentAssistProcessor(type);
1763
1764 } catch (BadLocationException x) {
1765 }
1766
1767 return null;
1768 }
1769
1770 /**
1771 * Returns the content assist processor for the content type of the specified document position.
1772 *
1773 * @param contentAssistSubjectControl the content assist subject control
1774 * @param offset a offset within the document
1775 * @return a content-assist processor or <code>null</code> if none exists
1776 * @since 3.0
1777 */
1778 private IContentAssistProcessor getProcessor(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1779 try {
1780
1781 IDocument document= contentAssistSubjectControl.getDocument();
1782 String type;
1783 if (document !is null)
1784 type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true);
1785 else
1786 type= IDocument.DEFAULT_CONTENT_TYPE;
1787
1788 return getContentAssistProcessor(type);
1789
1790 } catch (BadLocationException x) {
1791 }
1792
1793 return null;
1794 }
1795
1796 /**
1797 * Returns an array of completion proposals computed based on the specified document position.
1798 * The position is used to determine the appropriate content assist processor to invoke.
1799 *
1800 * @param contentAssistSubjectControl the content assist subject control
1801 * @param offset a document offset
1802 * @return an array of completion proposals
1803 * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int)
1804 * @since 3.0
1805 */
1806 ICompletionProposal[] computeCompletionProposals(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1807 fLastErrorMessage= null;
1808
1809 ICompletionProposal[] result= null;
1810
1811 IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
1812 if (p instanceof ISubjectControlContentAssistProcessor) {
1813 result= ((ISubjectControlContentAssistProcessor) p).computeCompletionProposals(contentAssistSubjectControl, offset);
1814 fLastErrorMessage= p.getErrorMessage();
1815 }
1816
1817 return result;
1818 }
1819
1820 /**
1821 * Returns an array of completion proposals computed based on the specified document position.
1822 * The position is used to determine the appropriate content assist processor to invoke.
1823 *
1824 * @param viewer the viewer for which to compute the proposals
1825 * @param offset a document offset
1826 * @return an array of completion proposals or <code>null</code> if no proposals are possible
1827 * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int)
1828 */
1829 ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
1830 fLastErrorMessage= null;
1831
1832 ICompletionProposal[] result= null;
1833
1834 IContentAssistProcessor p= getProcessor(viewer, offset);
1835 if (p !is null) {
1836 result= p.computeCompletionProposals(viewer, offset);
1837 fLastErrorMessage= p.getErrorMessage();
1838 }
1839
1840 return result;
1841 }
1842
1843 /**
1844 * Returns an array of context information objects computed based on the specified document
1845 * position. The position is used to determine the appropriate content assist processor to
1846 * invoke.
1847 *
1848 * @param viewer the viewer for which to compute the context information
1849 * @param offset a document offset
1850 * @return an array of context information objects
1851 * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int)
1852 */
1853 IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
1854 fLastErrorMessage= null;
1855
1856 IContextInformation[] result= null;
1857
1858 IContentAssistProcessor p= getProcessor(viewer, offset);
1859 if (p !is null) {
1860 result= p.computeContextInformation(viewer, offset);
1861 fLastErrorMessage= p.getErrorMessage();
1862 }
1863
1864 return result;
1865 }
1866
1867 /**
1868 * Returns an array of context information objects computed based on the specified document
1869 * position. The position is used to determine the appropriate content assist processor to
1870 * invoke.
1871 *
1872 * @param contentAssistSubjectControl the content assist subject control
1873 * @param offset a document offset
1874 * @return an array of context information objects
1875 * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int)
1876 * @since 3.0
1877 */
1878 IContextInformation[] computeContextInformation(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1879 fLastErrorMessage= null;
1880
1881 IContextInformation[] result= null;
1882
1883 IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
1884 if (p instanceof ISubjectControlContentAssistProcessor) {
1885 result= ((ISubjectControlContentAssistProcessor) p).computeContextInformation(contentAssistSubjectControl, offset);
1886 fLastErrorMessage= p.getErrorMessage();
1887 }
1888
1889 return result;
1890 }
1891
1892 /**
1893 * Returns the context information validator that should be used to determine when the currently
1894 * displayed context information should be dismissed. The position is used to determine the
1895 * appropriate content assist processor to invoke.
1896 *
1897 * @param viewer the text viewer
1898 * @param offset a document offset
1899 * @return an validator
1900 * @see IContentAssistProcessor#getContextInformationValidator()
1901 * @since 3.0
1902 */
1903 IContextInformationValidator getContextInformationValidator(ITextViewer viewer, int offset) {
1904 IContentAssistProcessor p= getProcessor(viewer, offset);
1905 return p !is null ? p.getContextInformationValidator() : null;
1906 }
1907
1908 /**
1909 * Returns the context information validator that should be used to determine when the currently
1910 * displayed context information should be dismissed. The position is used to determine the
1911 * appropriate content assist processor to invoke.
1912 *
1913 * @param contentAssistSubjectControl the content assist subject control
1914 * @param offset a document offset
1915 * @return an validator
1916 * @see IContentAssistProcessor#getContextInformationValidator()
1917 * @since 3.0
1918 */
1919 IContextInformationValidator getContextInformationValidator(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1920 IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
1921 return p !is null ? p.getContextInformationValidator() : null;
1922 }
1923
1924 /**
1925 * Returns the context information presenter that should be used to display context information.
1926 * The position is used to determine the appropriate content assist processor to invoke.
1927 *
1928 * @param viewer the text viewer
1929 * @param offset a document offset
1930 * @return a presenter
1931 * @since 2.0
1932 */
1933 IContextInformationPresenter getContextInformationPresenter(ITextViewer viewer, int offset) {
1934 IContextInformationValidator validator= getContextInformationValidator(viewer, offset);
1935 if (validator instanceof IContextInformationPresenter)
1936 return (IContextInformationPresenter) validator;
1937 return null;
1938 }
1939
1940 /**
1941 * Returns the context information presenter that should be used to display context information.
1942 * The position is used to determine the appropriate content assist processor to invoke.
1943 *
1944 * @param contentAssistSubjectControl the content assist subject control
1945 * @param offset a document offset
1946 * @return a presenter
1947 * @since 3.0
1948 */
1949 IContextInformationPresenter getContextInformationPresenter(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1950 IContextInformationValidator validator= getContextInformationValidator(contentAssistSubjectControl, offset);
1951 if (validator instanceof IContextInformationPresenter)
1952 return (IContextInformationPresenter) validator;
1953 return null;
1954 }
1955
1956 /**
1957 * Returns the characters which when typed by the user should automatically initiate proposing
1958 * completions. The position is used to determine the appropriate content assist processor to
1959 * invoke.
1960 *
1961 * @param contentAssistSubjectControl the content assist subject control
1962 * @param offset a document offset
1963 * @return the auto activation characters
1964 * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
1965 * @since 3.0
1966 */
1967 char[] getCompletionProposalAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
1968 IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
1969 return p !is null ? p.getCompletionProposalAutoActivationCharacters() : null;
1970 }
1971
1972 /**
1973 * Returns the characters which when typed by the user should automatically initiate proposing
1974 * completions. The position is used to determine the appropriate content assist processor to
1975 * invoke.
1976 *
1977 * @param viewer the text viewer
1978 * @param offset a document offset
1979 * @return the auto activation characters
1980 * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
1981 */
1982 char[] getCompletionProposalAutoActivationCharacters(ITextViewer viewer, int offset) {
1983 IContentAssistProcessor p= getProcessor(viewer, offset);
1984 return p !is null ? p.getCompletionProposalAutoActivationCharacters() : null;
1985 }
1986
1987 /**
1988 * Returns the characters which when typed by the user should automatically initiate the
1989 * presentation of context information. The position is used to determine the appropriate
1990 * content assist processor to invoke.
1991 *
1992 * @param viewer the text viewer
1993 * @param offset a document offset
1994 * @return the auto activation characters
1995 * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters()
1996 * @since 3.0
1997 */
1998 char[] getContextInformationAutoActivationCharacters(ITextViewer viewer, int offset) {
1999 IContentAssistProcessor p= getProcessor(viewer, offset);
2000 return p !is null ? p.getContextInformationAutoActivationCharacters() : null;
2001 }
2002
2003 /**
2004 * Returns the characters which when typed by the user should automatically initiate the
2005 * presentation of context information. The position is used to determine the appropriate
2006 * content assist processor to invoke.
2007 *
2008 * @param contentAssistSubjectControl the content assist subject control
2009 * @param offset a document offset
2010 * @return the auto activation characters
2011 * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters()
2012 * @since 3.0
2013 */
2014 char[] getContextInformationAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) {
2015 IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset);
2016 return p !is null ? p.getContextInformationAutoActivationCharacters() : null;
2017 }
2018
2019 /*
2020 * @see dwtx.jface.text.IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner)
2021 * @since 2.0
2022 */
2023 public bool requestWidgetToken(IWidgetTokenOwner owner) {
2024 return false;
2025 }
2026
2027 /*
2028 * @see dwtx.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(dwtx.jface.text.IWidgetTokenOwner,
2029 * int)
2030 * @since 3.0
2031 */
2032 public bool requestWidgetToken(IWidgetTokenOwner owner, int priority) {
2033 if (priority > WIDGET_PRIORITY) {
2034 hide();
2035 return true;
2036 }
2037 return false;
2038 }
2039
2040 /*
2041 * @see dwtx.jface.text.IWidgetTokenKeeperExtension#setFocus(dwtx.jface.text.IWidgetTokenOwner)
2042 * @since 3.0
2043 */
2044 public bool setFocus(IWidgetTokenOwner owner) {
2045 if (fProposalPopup !is null) {
2046 fProposalPopup.setFocus();
2047 return fProposalPopup.hasFocus();
2048 }
2049 return false;
2050 }
2051
2052 /**
2053 * Hides any open pop-ups.
2054 *
2055 * @since 3.0
2056 */
2057 protected void hide() {
2058 if (fProposalPopup !is null)
2059 fProposalPopup.hide();
2060
2061 if (fContextInfoPopup !is null)
2062 fContextInfoPopup.hide();
2063 }
2064
2065 // ------ control's size handling dialog settings ------
2066
2067 /**
2068 * Tells this information control manager to open the information control with the values
2069 * contained in the given dialog settings and to store the control's last valid size in the
2070 * given dialog settings.
2071 * <p>
2072 * Note: This API is only valid if the information control implements
2073 * {@link dwtx.jface.text.IInformationControlExtension3}. Not following this restriction
2074 * will later result in an {@link UnsupportedOperationException}.
2075 * </p>
2076 * <p>
2077 * The constants used to store the values are:
2078 * <ul>
2079 * <li>{@link ContentAssistant#STORE_SIZE_X}</li>
2080 * <li>{@link ContentAssistant#STORE_SIZE_Y}</li>
2081 * </ul>
2082 * </p>
2083 *
2084 * @param dialogSettings
2085 * @since 3.0
2086 */
2087 public void setRestoreCompletionProposalSize(IDialogSettings dialogSettings) {
2088 Assert.isTrue(dialogSettings !is null);
2089 fDialogSettings= dialogSettings;
2090 }
2091
2092 /**
2093 * Stores the content assist pop-up's size.
2094 */
2095 protected void storeCompletionProposalPopupSize() {
2096 if (fDialogSettings is null || fProposalPopup is null)
2097 return;
2098
2099 Point size= fProposalPopup.getSize();
2100 if (size is null)
2101 return;
2102
2103 fDialogSettings.put(STORE_SIZE_X, size.x);
2104 fDialogSettings.put(STORE_SIZE_Y, size.y);
2105 }
2106
2107 /**
2108 * Restores the content assist pop-up's size.
2109 *
2110 * @return the stored size
2111 * @since 3.0
2112 */
2113 protected Point restoreCompletionProposalPopupSize() {
2114 if (fDialogSettings is null)
2115 return null;
2116
2117 Point size= new Point(-1, -1);
2118
2119 try {
2120 size.x= fDialogSettings.getInt(STORE_SIZE_X);
2121 size.y= fDialogSettings.getInt(STORE_SIZE_Y);
2122 } catch (NumberFormatException ex) {
2123 size.x= -1;
2124 size.y= -1;
2125 }
2126
2127 // sanity check
2128 if (size.x is -1 && size.y is -1)
2129 return null;
2130
2131 Rectangle maxBounds= null;
2132 if (fContentAssistSubjectControl !is null && Helper.okToUse(fContentAssistSubjectControl.getControl()))
2133 maxBounds= fContentAssistSubjectControl.getControl().getDisplay().getBounds();
2134 else {
2135 // fallback
2136 Display display= Display.getCurrent();
2137 if (display is null)
2138 display= Display.getDefault();
2139 if (display !is null && !display.isDisposed())
2140 maxBounds= display.getBounds();
2141 }
2142
2143 if (size.x > -1 && size.y > -1) {
2144 if (maxBounds !is null) {
2145 size.x= Math.min(size.x, maxBounds.width);
2146 size.y= Math.min(size.y, maxBounds.height);
2147 }
2148
2149 // Enforce an absolute minimal size
2150 size.x= Math.max(size.x, 30);
2151 size.y= Math.max(size.y, 30);
2152 }
2153
2154 return size;
2155 }
2156
2157 /**
2158 * Sets the prefix completion property. If enabled, content assist delegates completion to
2159 * prefix completion.
2160 *
2161 * @param enabled <code>true</code> to enable prefix completion, <code>false</code> to
2162 * disable
2163 */
2164 public void enablePrefixCompletion(bool enabled) {
2165 fIsPrefixCompletionEnabled= enabled;
2166 }
2167
2168 /**
2169 * Returns the prefix completion state.
2170 *
2171 * @return <code>true</code> if prefix completion is enabled, <code>false</code> otherwise
2172 * @since 3.2
2173 */
2174 bool isPrefixCompletionEnabled() {
2175 return fIsPrefixCompletionEnabled;
2176 }
2177
2178 /**
2179 * Returns whether the content assistant proposal popup has the focus.
2180 *
2181 * @return <code>true</code> if the proposal popup has the focus
2182 * @since 3.0
2183 */
2184 public bool hasProposalPopupFocus() {
2185 return fProposalPopup.hasFocus();
2186 }
2187
2188 /*
2189 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#addCompletionListener(dwtx.jface.text.contentassist.ICompletionListener)
2190 * @since 3.2
2191 */
2192 public void addCompletionListener(ICompletionListener listener) {
2193 Assert.isLegal(listener !is null);
2194 fCompletionListeners.add(listener);
2195 }
2196
2197 /*
2198 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#removeCompletionListener(dwtx.jface.text.contentassist.ICompletionListener)
2199 * @since 3.2
2200 */
2201 public void removeCompletionListener(ICompletionListener listener) {
2202 fCompletionListeners.remove(listener);
2203 }
2204
2205 /**
2206 * Fires a session begin event to all registered {@link ICompletionListener}s.
2207 *
2208 * @param isAutoActivated <code>true</code> if this session was triggered by auto activation
2209 * @since 3.2
2210 */
2211 void fireSessionBeginEvent(bool isAutoActivated) {
2212 if (fContentAssistSubjectControlAdapter !is null && !isProposalPopupActive()) {
2213 IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
2214 ContentAssistEvent event= new ContentAssistEvent(this, processor, isAutoActivated);
2215 Object[] listeners= fCompletionListeners.getListeners();
2216 for (int i= 0; i < listeners.length; i++) {
2217 ICompletionListener listener= (ICompletionListener)listeners[i];
2218 listener.assistSessionStarted(event);
2219 }
2220 }
2221 }
2222
2223 /**
2224 * Fires a session restart event to all registered {@link ICompletionListener}s.
2225 *
2226 * @since 3.4
2227 */
2228 void fireSessionRestartEvent() {
2229 if (fContentAssistSubjectControlAdapter !is null) {
2230 IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
2231 ContentAssistEvent event= new ContentAssistEvent(this, processor);
2232 Object[] listeners= fCompletionListeners.getListeners();
2233 for (int i= 0; i < listeners.length; i++) {
2234 ICompletionListener listener= (ICompletionListener)listeners[i];
2235 if (listener instanceof ICompletionListenerExtension)
2236 ((ICompletionListenerExtension)listener).assistSessionRestarted(event);
2237 }
2238 }
2239 }
2240
2241 /**
2242 * Fires a session end event to all registered {@link ICompletionListener}s.
2243 *
2244 * @since 3.2
2245 */
2246 void fireSessionEndEvent() {
2247 if (fContentAssistSubjectControlAdapter !is null) {
2248 IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x);
2249 ContentAssistEvent event= new ContentAssistEvent(this, processor);
2250 Object[] listeners= fCompletionListeners.getListeners();
2251 for (int i= 0; i < listeners.length; i++) {
2252 ICompletionListener listener= (ICompletionListener)listeners[i];
2253 listener.assistSessionEnded(event);
2254 }
2255 }
2256 }
2257
2258 /*
2259 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setRepeatedInvocationMode(bool)
2260 * @since 3.2
2261 */
2262 public void setRepeatedInvocationMode(bool cycling) {
2263 fIsRepetitionMode= cycling;
2264 }
2265
2266 /**
2267 * Returns <code>true</code> if repeated invocation mode is enabled, <code>false</code>
2268 * otherwise.
2269 *
2270 * @return <code>true</code> if repeated invocation mode is enabled, <code>false</code>
2271 * otherwise
2272 * @since 3.2
2273 */
2274 bool isRepeatedInvocationMode() {
2275 return fIsRepetitionMode;
2276 }
2277
2278 /*
2279 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setShowEmptyList(bool)
2280 * @since 3.2
2281 */
2282 public void setShowEmptyList(bool showEmpty) {
2283 fShowEmptyList= showEmpty;
2284 }
2285
2286 /**
2287 * Returns <code>true</code> if empty lists should be displayed, <code>false</code>
2288 * otherwise.
2289 *
2290 * @return <code>true</code> if empty lists should be displayed, <code>false</code>
2291 * otherwise
2292 * @since 3.2
2293 */
2294 bool isShowEmptyList() {
2295 return fShowEmptyList;
2296 }
2297
2298 /*
2299 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setStatusLineVisible(bool)
2300 * @since 3.2
2301 */
2302 public void setStatusLineVisible(bool show) {
2303 fIsStatusLineVisible= show;
2304 if (fProposalPopup !is null)
2305 fProposalPopup.setStatusLineVisible(show);
2306 }
2307
2308 /**
2309 * Returns <code>true</code> if a message line should be displayed, <code>false</code>
2310 * otherwise.
2311 *
2312 * @return <code>true</code> if a message line should be displayed, <code>false</code>
2313 * otherwise
2314 * @since 3.2
2315 */
2316 bool isStatusLineVisible() {
2317 return fIsStatusLineVisible;
2318 }
2319
2320 /*
2321 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setStatusMessage(java.lang.String)
2322 * @since 3.2
2323 */
2324 public void setStatusMessage(String message) {
2325 Assert.isLegal(message !is null);
2326 fMessage= message;
2327 if (fProposalPopup !is null)
2328 fProposalPopup.setMessage(message);
2329 }
2330
2331 /**
2332 * Returns the affordance caption for the cycling affordance at the bottom of the pop-up.
2333 *
2334 * @return the affordance caption for the cycling affordance at the bottom of the pop-up
2335 * @since 3.2
2336 */
2337 String getStatusMessage() {
2338 return fMessage;
2339 }
2340
2341 /*
2342 * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setEmptyMessage(java.lang.String)
2343 * @since 3.2
2344 */
2345 public void setEmptyMessage(String message) {
2346 Assert.isLegal(message !is null);
2347 if (fProposalPopup !is null)
2348 fProposalPopup.setEmptyMessage(message);
2349 }
2350
2351 /**
2352 * Fires a selection event, see {@link ICompletionListener}.
2353 *
2354 * @param proposal the selected proposal, possibly <code>null</code>
2355 * @param smartToggle true if the smart toggle is on
2356 * @since 3.2
2357 */
2358 void fireSelectionEvent(ICompletionProposal proposal, bool smartToggle) {
2359 Object[] listeners= fCompletionListeners.getListeners();
2360 for (int i= 0; i < listeners.length; i++) {
2361 ICompletionListener listener= (ICompletionListener)listeners[i];
2362 listener.selectionChanged(proposal, smartToggle);
2363 }
2364 }
2365
2366 /*
2367 * @see dwtx.jface.text.contentassist.IContentAssistantExtension3#setInvocationTrigger(dwtx.jface.bindings.keys.KeySequence)
2368 * @since 3.2
2369 */
2370 public void setRepeatedInvocationTrigger(KeySequence sequence) {
2371 fRepeatedInvocationKeySequence= sequence;
2372 }
2373
2374 /**
2375 * Returns the repeated invocation key sequence.
2376 *
2377 * @return the repeated invocation key sequence or <code>null</code>, if none
2378 * @since 3.2
2379 */
2380 KeySequence getRepeatedInvocationKeySequence() {
2381 return fRepeatedInvocationKeySequence;
2382 }
2383
2384 /**
2385 * Returns whether proposal popup is active.
2386 *
2387 * @return <code>true</code> if the proposal popup is active, <code>false</code> otherwise
2388 * @since 3.4
2389 */
2390 protected bool isProposalPopupActive(){
2391 return fProposalPopup !is null && fProposalPopup.isActive();
2392 }
2393
2394 /**
2395 * Returns whether the context information popup is active.
2396 *
2397 * @return <code>true</code> if the context information popup is active, <code>false</code> otherwise
2398 * @since 3.4
2399 */
2400 protected bool isContextInfoPopupActive(){
2401 return fContextInfoPopup !is null && fContextInfoPopup.isActive();
2402 }
2403
2404 /**
2405 * {@inheritDoc}
2406 *
2407 * @since 3.4
2408 */
2409 public final IHandler getHandler(String commandId) {
2410 if (fHandlers is null)
2411 throw new IllegalStateException();
2412
2413 IHandler handler= (IHandler)fHandlers.get(commandId);
2414 if (handler !is null)
2415 return handler;
2416
2417 Assert.isLegal(false);
2418 return null;
2419 }
2420
2421 /**
2422 * Registers the given handler under the given command identifier.
2423 *
2424 * @param commandId the command identifier
2425 * @param handler the handler
2426 * @since 3.4
2427 */
2428 protected final void registerHandler(String commandId, IHandler handler) {
2429 if (fHandlers is null)
2430 fHandlers= new HashMap(2);
2431 fHandlers.put(commandId, handler);
2432 }
2433
2434 /**
2435 * Tells whether the support for colored labels is enabled.
2436 *
2437 * @return <code>true</code> if the support for colored labels is enabled, <code>false</code> otherwise
2438 * @since 3.4
2439 */
2440 bool isColoredLabelsSupportEnabled() {
2441 return fIsColoredLabelsSupportEnabled;
2442 }
2443
2444 /**
2445 * Enables the support for colored labels in the proposal popup.
2446 * <p>Completion proposals can implement {@link ICompletionProposalExtension6}
2447 * to provide colored proposal labels.</p>
2448 *
2449 * @param isEnabled if <code>true</code> the support for colored labels is enabled in the proposal popup
2450 * @since 3.4
2451 */
2452 public void enableColoredLabels(bool isEnabled) {
2453 fIsColoredLabelsSupportEnabled= isEnabled;
2454 }
2455
2456 }