Mercurial > projects > dwt-addons
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/contentassist/ContentAssistant.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,2456 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Guy Gurfinkel, guy.g@zend.com - [content assist][api] provide better access to ContentAssistant - https://bugs.eclipse.org/bugs/show_bug.cgi?id=169954 + * 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.contentassist.ContentAssistant; + +import dwt.dwthelper.utils; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import dwt.DWT; +import dwt.DWTError; +import dwt.custom.VerifyKeyListener; +import dwt.events.ControlEvent; +import dwt.events.ControlListener; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.KeyAdapter; +import dwt.events.KeyEvent; +import dwt.events.KeyListener; +import dwt.events.MouseEvent; +import dwt.events.MouseListener; +import dwt.events.TraverseEvent; +import dwt.events.TraverseListener; +import dwt.events.VerifyEvent; +import dwt.graphics.Color; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.Monitor; +import dwt.widgets.Shell; +import dwt.widgets.Widget; +import dwtx.core.commands.IHandler; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.ListenerList; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.contentassist.IContentAssistSubjectControl; +import dwtx.jface.contentassist.ISubjectControlContentAssistProcessor; +import dwtx.jface.dialogs.IDialogSettings; +import dwtx.jface.preference.JFacePreferences; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentExtension3; +import dwtx.jface.text.IEventConsumer; +import dwtx.jface.text.IInformationControlCreator; +import dwtx.jface.text.ITextViewer; +import dwtx.jface.text.IViewportListener; +import dwtx.jface.text.IWidgetTokenKeeper; +import dwtx.jface.text.IWidgetTokenKeeperExtension; +import dwtx.jface.text.IWidgetTokenOwner; +import dwtx.jface.text.IWidgetTokenOwnerExtension; +import dwtx.jface.text.TextUtilities; +import dwtx.jface.util.Geometry; + + +/** + * The standard implementation of the <code>IContentAssistant</code> interface. Usually, clients + * instantiate this class and configure it before using it. + */ +public class ContentAssistant : IContentAssistant, IContentAssistantExtension, IContentAssistantExtension2, IContentAssistantExtension3, IContentAssistantExtension4, IWidgetTokenKeeper, IWidgetTokenKeeperExtension { + + + + /** + * Content assist command identifier for 'select next proposal'. + * + * @since 3.4 + */ + public static final String SELECT_NEXT_PROPOSAL_COMMAND_ID= "dwtx.ui.edit.text.contentAssist.selectNextProposal"; //$NON-NLS-1$ + /** + * Content assist command identifier for 'select previous proposal'. + * + * @since 3.4 + */ + public static final String SELECT_PREVIOUS_PROPOSAL_COMMAND_ID= "dwtx.ui.edit.text.contentAssist.selectPreviousProposal"; //$NON-NLS-1$ + + + /** + * A generic closer class used to monitor various interface events in order to determine whether + * content-assist should be terminated and all associated windows closed. + */ + class Closer : ControlListener, MouseListener, FocusListener, DisposeListener, IViewportListener { + + /** The shell that a <code>ControlListener</code> is registered with. */ + private Shell fShell; + /** + * The control that a <code>MouseListener</code>, a<code>FocusListener</code> and a + * <code>DisposeListener</code> are registered with. + */ + private Control fControl; + + /** + * Installs this closer on it's viewer's text widget. + */ + protected void install() { + Control control= fContentAssistSubjectControlAdapter.getControl(); + fControl= control; + if (Helper.okToUse(control)) { + + Shell shell= control.getShell(); + fShell= shell; + shell.addControlListener(this); + + control.addMouseListener(this); + control.addFocusListener(this); + + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of + * Internal Errors + */ + control.addDisposeListener(this); + } + if (fViewer !is null) + fViewer.addViewportListener(this); + } + + /** + * Uninstalls this closer from the viewer's text widget. + */ + protected void uninstall() { + Control shell= fShell; + fShell= null; + if (Helper.okToUse(shell)) + shell.removeControlListener(this); + + Control control= fControl; + fControl= null; + if (Helper.okToUse(control)) { + + control.removeMouseListener(this); + control.removeFocusListener(this); + + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of + * Internal Errors + */ + control.removeDisposeListener(this); + } + + if (fViewer !is null) + fViewer.removeViewportListener(this); + } + + /* + * @see ControlListener#controlResized(ControlEvent) + */ + public void controlResized(ControlEvent e) { + hide(); + } + + /* + * @see ControlListener#controlMoved(ControlEvent) + */ + public void controlMoved(ControlEvent e) { + hide(); + } + + /* + * @see MouseListener#mouseDown(MouseEvent) + */ + public void mouseDown(MouseEvent e) { + hide(); + } + + /* + * @see MouseListener#mouseUp(MouseEvent) + */ + public void mouseUp(MouseEvent e) { + } + + /* + * @see MouseListener#mouseDoubleClick(MouseEvent) + */ + public void mouseDoubleClick(MouseEvent e) { + hide(); + } + + /* + * @see FocusListener#focusGained(FocusEvent) + */ + public void focusGained(FocusEvent e) { + } + + /* + * @see FocusListener#focusLost(FocusEvent) + */ + public void focusLost(FocusEvent e) { + Control control= fControl; + if (Helper.okToUse(control)) { + Display d= control.getDisplay(); + if (d !is null) { + d.asyncExec(new Runnable() { + public void run() { + if (!fProposalPopup.hasFocus() && (fContextInfoPopup is null || !fContextInfoPopup.hasFocus())) + hide(); + } + }); + } + } + } + + /* + * @seeDisposeListener#widgetDisposed(DisposeEvent) + */ + public void widgetDisposed(DisposeEvent e) { + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal + * Errors + */ + hide(); + } + + /* + * @see IViewportListener#viewportChanged(int) + */ + public void viewportChanged(int topIndex) { + hide(); + } + } + + /** + * An implementation of <code>IContentAssistListener</code>, this class is used to monitor + * key events in support of automatic activation of the content assistant. If enabled, the + * implementation utilizes a thread to watch for input characters matching the activation + * characters specified by the content assist processor, and if detected, will wait the + * indicated delay interval before activating the content assistant. + * + * @since 3.4 protected, was added in 2.1 as private class + */ + protected class AutoAssistListener : KeyAdapter , KeyListener, Runnable, VerifyKeyListener { + + private Thread fThread; + private bool fIsReset= false; + private Object fMutex= new Object(); + private int fShowStyle; + + private final static int SHOW_PROPOSALS= 1; + private final static int SHOW_CONTEXT_INFO= 2; + + protected AutoAssistListener() { + } + + protected void start(int showStyle) { + fShowStyle= showStyle; + fThread= new Thread(this, JFaceTextMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$ + fThread.start(); + } + + public void run() { + try { + while (true) { + synchronized (fMutex) { + if (fAutoActivationDelay !is 0) + fMutex.wait(fAutoActivationDelay); + if (fIsReset) { + fIsReset= false; + continue; + } + } + showAssist(fShowStyle); + break; + } + } catch (InterruptedException e) { + } + fThread= null; + } + + protected void reset(int showStyle) { + synchronized (fMutex) { + fShowStyle= showStyle; + fIsReset= true; + fMutex.notifyAll(); + } + } + + protected void stop() { + Thread threadToStop= fThread; + if (threadToStop !is null && threadToStop.isAlive()) + threadToStop.interrupt(); + } + + private bool contains(char[] characters, char character) { + if (characters !is null) { + for (int i= 0; i < characters.length; i++) { + if (character is characters[i]) + return true; + } + } + return false; + } + + public void keyPressed(KeyEvent e) { + // Only act on typed characters and ignore modifier-only events + if (e.character is 0 && (e.keyCode & DWT.KEYCODE_BIT) is 0) + return; + + if (e.character !is 0 && (e.stateMask is DWT.ALT)) + return; + + // Only act on characters that are trigger candidates. This + // avoids computing the model selection on every keystroke + if (computeAllAutoActivationTriggers().indexOf(e.character) < 0) { + stop(); + return; + } + + int showStyle; + int pos= fContentAssistSubjectControlAdapter.getSelectedRange().x; + char[] activation; + + activation= fContentAssistSubjectControlAdapter.getCompletionProposalAutoActivationCharacters(ContentAssistant.this, pos); + + if (contains(activation, e.character) && !isProposalPopupActive()) + showStyle= SHOW_PROPOSALS; + else { + activation= fContentAssistSubjectControlAdapter.getContextInformationAutoActivationCharacters(ContentAssistant.this, pos); + if (contains(activation, e.character) && !isContextInfoPopupActive()) + showStyle= SHOW_CONTEXT_INFO; + else { + stop(); + return; + } + } + + if (fThread !is null && fThread.isAlive()) + reset(showStyle); + else + start(showStyle); + } + + /* + * @see dwt.custom.VerifyKeyListener#verifyKey(dwt.events.VerifyEvent) + */ + public void verifyKey(VerifyEvent event) { + keyPressed(event); + } + + protected void showAssist(final int showStyle) { + final Control control= fContentAssistSubjectControlAdapter.getControl(); + if (control is null) + return; + + final Display d= control.getDisplay(); + if (d is null) + return; + + try { + d.syncExec(new Runnable() { + public void run() { + if (isProposalPopupActive()) + return; + + if (control.isDisposed() || !control.isFocusControl()) + return; + + if (showStyle is SHOW_PROPOSALS) { + if (!prepareToShowCompletions(true)) + return; + fProposalPopup.showProposals(true); + fLastAutoActivation= System.currentTimeMillis(); + } else if (showStyle is SHOW_CONTEXT_INFO && fContextInfoPopup !is null) { + promoteKeyListener(); + fContextInfoPopup.showContextProposals(true); + } + } + }); + } catch (DWTError e) { + } + } + } + + /** + * The layout manager layouts the various windows associated with the content assistant based on + * the settings of the content assistant. + */ + class LayoutManager : Listener { + + // Presentation types. + /** The presentation type for the proposal selection popup. */ + public final static int LAYOUT_PROPOSAL_SELECTOR= 0; + /** The presentation type for the context selection popup. */ + public final static int LAYOUT_CONTEXT_SELECTOR= 1; + /** The presentation type for the context information hover . */ + public final static int LAYOUT_CONTEXT_INFO_POPUP= 2; + + int fContextType= LAYOUT_CONTEXT_SELECTOR; + Shell[] fShells= new Shell[3]; + Object[] fPopups= new Object[3]; + + protected void add(Object popup, Shell shell, int type, int offset) { + Assert.isNotNull(popup); + Assert.isTrue(shell !is null && !shell.isDisposed()); + checkType(type); + + if (fShells[type] !is shell) { + if (fShells[type] !is null) + fShells[type].removeListener(DWT.Dispose, this); + shell.addListener(DWT.Dispose, this); + fShells[type]= shell; + } + + fPopups[type]= popup; + if (type is LAYOUT_CONTEXT_SELECTOR || type is LAYOUT_CONTEXT_INFO_POPUP) + fContextType= type; + + layout(type, offset); + adjustListeners(type); + } + + protected void checkType(int type) { + Assert.isTrue(type is LAYOUT_PROPOSAL_SELECTOR || + type is LAYOUT_CONTEXT_SELECTOR || type is LAYOUT_CONTEXT_INFO_POPUP); + } + + public void handleEvent(Event event) { + Widget source= event.widget; + source.removeListener(DWT.Dispose, this); + + int type= getShellType(source); + checkType(type); + fShells[type]= null; + + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + if (fContextType is LAYOUT_CONTEXT_SELECTOR && + Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { + // Restore event notification to the tip popup. + addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); + } + break; + + case LAYOUT_CONTEXT_SELECTOR: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + if (fProposalPopupOrientation is PROPOSAL_STACKED) + layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); + // Restore event notification to the proposal popup. + addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); + } + fContextType= LAYOUT_CONTEXT_INFO_POPUP; + break; + + case LAYOUT_CONTEXT_INFO_POPUP: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + if (fContextInfoPopupOrientation is CONTEXT_INFO_BELOW) + layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); + } + fContextType= LAYOUT_CONTEXT_SELECTOR; + break; + } + } + + protected int getShellType(Widget shell) { + for (int i= 0; i < fShells.length; i++) { + if (fShells[i] is shell) + return i; + } + return -1; + } + + /** + * Layouts the popup defined by <code>type</code> at the given widget offset. + * + * @param type the kind of popup to layout + * @param offset the widget offset + */ + protected void layout(int type, int offset) { + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + layoutProposalSelector(offset); + break; + case LAYOUT_CONTEXT_SELECTOR: + layoutContextSelector(offset); + break; + case LAYOUT_CONTEXT_INFO_POPUP: + layoutContextInfoPopup(offset); + break; + } + } + + protected void layoutProposalSelector(int offset) { + if (fContextType is LAYOUT_CONTEXT_INFO_POPUP && + fContextInfoPopupOrientation is CONTEXT_INFO_BELOW && + Helper.okToUse(fShells[LAYOUT_CONTEXT_INFO_POPUP])) { + // Stack proposal selector beneath the tip box. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + shell.setLocation(getStackedLocation(shell, parent)); + } else if (fContextType !is LAYOUT_CONTEXT_SELECTOR || + !Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { + // There are no other presentations to be concerned with, + // so place the proposal selector beneath the cursor line. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + CompletionProposalPopup popup= (CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR]; + shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); + } else { + CompletionProposalPopup popup= ((CompletionProposalPopup) fPopups[LAYOUT_PROPOSAL_SELECTOR]); + switch (fProposalPopupOrientation) { + case PROPOSAL_REMOVE: { + // Remove the tip selector and place the + // proposal selector beneath the cursor line. + fShells[LAYOUT_CONTEXT_SELECTOR].dispose(); + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); + break; + } + case PROPOSAL_OVERLAY: { + // Overlay the tip selector with the proposal selector. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, popup)); + break; + } + case PROPOSAL_STACKED: { + // Stack the proposal selector beneath the tip selector. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + break; + } + } + } + } + + protected void layoutContextSelector(int offset) { + // Always place the context selector beneath the cursor line. + Shell shell= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setBounds(computeBoundsBelowAbove(shell, shell.getSize(), offset, null)); + + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + switch (fProposalPopupOrientation) { + case PROPOSAL_REMOVE: + // Remove the proposal selector. + fShells[LAYOUT_PROPOSAL_SELECTOR].dispose(); + break; + + case PROPOSAL_OVERLAY: + // The proposal selector has been overlaid by the tip selector. + break; + + case PROPOSAL_STACKED: { + // Stack the proposal selector beneath the tip selector. + shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + break; + } + } + } + } + + protected void layoutContextInfoPopup(int offset) { + switch (fContextInfoPopupOrientation) { + case CONTEXT_INFO_ABOVE: { + // Place the popup above the cursor line. + Shell shell= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + shell.setBounds(computeBoundsAboveBelow(shell, shell.getSize(), offset)); + break; + } + case CONTEXT_INFO_BELOW: { + // Place the popup beneath the cursor line. + Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + parent.setBounds(computeBoundsBelowAbove(parent, parent.getSize(), offset, null)); + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + // Stack the proposal selector beneath the context info popup. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + } + break; + } + } + } + + /** + * Moves <code>point</code> such that <code>rectangle</code> does not bleed outside of + * <code>bounds</code>. All coordinates must have the same reference. + * + * @param point the point to move if needed + * @param shellSize the size of the shell that may be moved + * @param bounds the bounds + * @since 3.3 + */ + protected void constrainLocation(Point point, Point shellSize, Rectangle bounds) { + if (point.x + shellSize.x > bounds.x + bounds.width) + point.x= bounds.x + bounds.width - shellSize.x; + + if (point.x < bounds.x) + point.x= bounds.x; + + if (point.y + shellSize.y > bounds.y + bounds.height) + point.y= bounds.y + bounds.height - shellSize.y; + + if (point.y < bounds.y) + point.y= bounds.y; + } + + protected Rectangle constrainHorizontally(Rectangle rect, Rectangle bounds) { + // clip width + if (rect.width > bounds.width) + rect.width= bounds.width; + + if (rect.x + rect.width > bounds.x + bounds.width) + rect.x= bounds.x + bounds.width - rect.width; + if (rect.x < bounds.x) + rect.x= bounds.x; + + return rect; + } + + /** + * Returns the display bounds for <code>shell</code> such that it appears right above + * <code>offset</code>, or below it if above is not suitable. The returned bounds lie + * within the monitor at the caret location and never overlap with the caret line. + * + * @param shell the shell to compute the placement for + * @param preferred the preferred size for <code>shell</code> + * @param offset the caret offset in the subject control + * @return the point right above <code>offset</code> in display coordinates + * @since 3.3 + */ + protected Rectangle computeBoundsAboveBelow(Shell shell, Point preferred, int offset) { + Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); + Display display= subjectControl.getDisplay(); + Rectangle caret= getCaretRectangle(offset); + Monitor monitor= getClosestMonitor(display, caret); + Rectangle bounds= monitor.getClientArea(); + Geometry.moveInside(caret, bounds); + + int spaceAbove= caret.y - bounds.y; + int caretLowerY= caret.y + caret.height; + int spaceBelow= bounds.y + bounds.height - caretLowerY; + Rectangle rect; + if (spaceAbove >= preferred.y) + rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y); + else if (spaceBelow >= preferred.y) + rect= new Rectangle(caret.x, caretLowerY, preferred.x, preferred.y); + // we can't fit in the preferred size - squeeze into larger area + else if (spaceBelow <= spaceAbove) + rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove); + else + rect= new Rectangle(caret.x, caretLowerY, preferred.x, spaceBelow); + + return constrainHorizontally(rect, bounds); + } + + /** + * Returns the display bounds for <code>shell</code> such that it appears right below + * <code>offset</code>, or above it if below is not suitable. The returned bounds lie + * within the monitor at the caret location and never overlap with the caret line. + * + * @param shell the shell to compute the placement for + * @param preferred the preferred size for <code>shell</code> + * @param offset the caret offset in the subject control + * @param popup a popup to inform if the location was switched to above, <code>null</code> to do nothing + * @return the point right below <code>offset</code> in display coordinates + * @since 3.3 + */ + protected Rectangle computeBoundsBelowAbove(Shell shell, Point preferred, int offset, CompletionProposalPopup popup) { + Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); + Display display= subjectControl.getDisplay(); + Rectangle caret= getCaretRectangle(offset); + Monitor monitor= getClosestMonitor(display, caret); + Rectangle bounds= monitor.getClientArea(); + Geometry.moveInside(caret, bounds); + + int threshold= popup is null ? Integer.MAX_VALUE : popup.getMinimalHeight(); + int spaceAbove= caret.y - bounds.y; + int spaceBelow= bounds.y + bounds.height - (caret.y + caret.height); + Rectangle rect; + bool switched= false; + if (spaceBelow >= preferred.y) + rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, preferred.y); + // squeeze in below if we have at least threshold space + else if (spaceBelow >= threshold) + rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow); + else if (spaceAbove >= preferred.y) { + rect= new Rectangle(caret.x, caret.y - preferred.y, preferred.x, preferred.y); + switched= true; + } else if (spaceBelow >= spaceAbove) { + // we can't fit in the preferred size - squeeze into larger area + rect= new Rectangle(caret.x, caret.y + caret.height, preferred.x, spaceBelow); + } else { + rect= new Rectangle(caret.x, bounds.y, preferred.x, spaceAbove); + switched= true; + } + + if (popup !is null) + popup.switchedPositionToAbove(switched); + + return constrainHorizontally(rect, bounds); + } + + private Rectangle getCaretRectangle(int offset) { + Point location= fContentAssistSubjectControlAdapter.getLocationAtOffset(offset); + Control subjectControl= fContentAssistSubjectControlAdapter.getControl(); + Point controlSize= subjectControl.getSize(); + constrainLocation(location, new Point(0, 0), new Rectangle(0, 0, controlSize.x, controlSize.y)); + location= subjectControl.toDisplay(location); + Rectangle subjectRectangle= new Rectangle(location.x, location.y, 1, fContentAssistSubjectControlAdapter.getLineHeight()); + return subjectRectangle; + } + + protected Point getStackedLocation(Shell shell, Shell parent) { + Point p= parent.getLocation(); + Point size= parent.getSize(); + p.x += size.x / 4; + p.y += size.y; + + p= parent.toDisplay(p); + + Point shellSize= shell.getSize(); + Monitor monitor= getClosestMonitor(parent.getDisplay(), new Rectangle(p.x, p.y, 0, 0)); + Rectangle displayBounds= monitor.getClientArea(); + constrainLocation(p, shellSize, displayBounds); + + return p; + } + + protected void adjustListeners(int type) { + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + if (fContextType is LAYOUT_CONTEXT_SELECTOR && + Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) + // Disable event notification to the tip selector. + removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); + break; + case LAYOUT_CONTEXT_SELECTOR: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) + // Disable event notification to the proposal selector. + removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); + break; + case LAYOUT_CONTEXT_INFO_POPUP: + break; + } + } + + /** + * Copied from dwtx.jface.window.Window. + * Returns the monitor whose client area contains the given point. If no + * monitor contains the point, returns the monitor that is closest to the + * point. If this is ever made public, it should be moved into a separate + * utility class. + * + * @param toSearch + * point to find (display coordinates) + * @param rectangle + * rectangle to find (display coordinates) + * @return the monitor closest to the given point + * @since 3.3 + */ + private Monitor getClosestMonitor(Display toSearch, Rectangle rectangle) { + int closest = Integer.MAX_VALUE; + + Point toFind= Geometry.centerPoint(rectangle); + Monitor[] monitors = toSearch.getMonitors(); + Monitor result = monitors[0]; + + for (int idx = 0; idx < monitors.length; idx++) { + Monitor current = monitors[idx]; + + Rectangle clientArea = current.getClientArea(); + + if (clientArea.contains(toFind)) { + return current; + } + + int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind); + if (distance < closest) { + closest = distance; + result = current; + } + } + + return result; + } + } + + /** + * Internal key listener and event consumer. + */ + class InternalListener : VerifyKeyListener, IEventConsumer { + + /** + * Verifies key events by notifying the registered listeners. Each listener is allowed to + * indicate that the event has been handled and should not be further processed. + * + * @param e the verify event + * @see VerifyKeyListener#verifyKey(dwt.events.VerifyEvent) + */ + public void verifyKey(VerifyEvent e) { + IContentAssistListener[] listeners= (IContentAssistListener[]) fListeners.clone(); + for (int i= 0; i < listeners.length; i++) { + if (listeners[i] !is null) { + if (!listeners[i].verifyKey(e) || !e.doit) + break; + } + } + if (fAutoAssistListener !is null) + fAutoAssistListener.keyPressed(e); + } + + /* + * @see IEventConsumer#processEvent + */ + public void processEvent(VerifyEvent event) { + + installKeyListener(); + + IContentAssistListener[] listeners= (IContentAssistListener[]) fListeners.clone(); + for (int i= 0; i < listeners.length; i++) { + if (listeners[i] !is null) { + listeners[i].processEvent(event); + if (!event.doit) + return; + } + } + } + } + + /** + * Dialog store constants. + * + * @since 3.0 + */ + public static final String STORE_SIZE_X= "size.x"; //$NON-NLS-1$ + public static final String STORE_SIZE_Y= "size.y"; //$NON-NLS-1$ + + // Content-Assist Listener types + final static int CONTEXT_SELECTOR= 0; + final static int PROPOSAL_SELECTOR= 1; + final static int CONTEXT_INFO_POPUP= 2; + + /** + * The popup priority: > linked position proposals and hover pop-ups. Default value: + * <code>20</code>; + * + * @since 3.0 + */ + public static final int WIDGET_PRIORITY= 20; + + private static final int DEFAULT_AUTO_ACTIVATION_DELAY= 500; + + private IInformationControlCreator fInformationControlCreator; + private int fAutoActivationDelay= DEFAULT_AUTO_ACTIVATION_DELAY; + private bool fIsAutoActivated= false; + private bool fIsAutoInserting= false; + private int fProposalPopupOrientation= PROPOSAL_OVERLAY; + private int fContextInfoPopupOrientation= CONTEXT_INFO_ABOVE; + private Map fProcessors; + + /** + * The partitioning. + * + * @since 3.0 + */ + private String fPartitioning; + + private Color fContextInfoPopupBackground; + private Color fContextInfoPopupForeground; + private Color fContextSelectorBackground; + private Color fContextSelectorForeground; + private Color fProposalSelectorBackground; + private Color fProposalSelectorForeground; + + private ITextViewer fViewer; + private String fLastErrorMessage; + + private Closer fCloser; + LayoutManager fLayoutManager; + private AutoAssistListener fAutoAssistListener; + private InternalListener fInternalListener; + private CompletionProposalPopup fProposalPopup; + private ContextInformationPopup fContextInfoPopup; + + /** + * Flag which tells whether a verify key listener is hooked. + * + * @since 3.0 + */ + private bool fVerifyKeyListenerHooked= false; + private IContentAssistListener[] fListeners= new IContentAssistListener[4]; + /** + * The content assist subject control. + * + * @since 3.0 + */ + private IContentAssistSubjectControl fContentAssistSubjectControl; + /** + * The content assist subject control's shell. + * + * @since 3.2 + */ + private Shell fContentAssistSubjectControlShell; + /** + * The content assist subject control's shell traverse listener. + * + * @since 3.2 + */ + private TraverseListener fCASCSTraverseListener; + /** + * The content assist subject control adapter. + * + * @since 3.0 + */ + private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter; + /** + * The dialog settings for the control's bounds. + * + * @since 3.0 + */ + private IDialogSettings fDialogSettings; + /** + * Prefix completion setting. + * + * @since 3.0 + */ + private bool fIsPrefixCompletionEnabled= false; + /** + * The list of completion listeners. + * + * @since 3.2 + */ + private ListenerList fCompletionListeners= new ListenerList(ListenerList.IDENTITY); + /** + * The message to display at the bottom of the proposal popup. + * + * @since 3.2 + */ + private String fMessage= ""; //$NON-NLS-1$ + /** + * The cycling mode property. + * + * @since 3.2 + */ + private bool fIsRepetitionMode= false; + /** + * The show empty property. + * + * @since 3.2 + */ + private bool fShowEmptyList= false; + /** + * The message line property. + * + * @since 3.2 + */ + private bool fIsStatusLineVisible; + /** + * The last system time when auto activation performed. + * + * @since 3.2 + */ + private long fLastAutoActivation= Long.MIN_VALUE; + /** + * The iteration key sequence to listen for, or <code>null</code>. + * + * @since 3.2 + */ + private KeySequence fRepeatedInvocationKeySequence; + + /** + * Maps handler to command identifiers. + * + * @since 3.4 + */ + private Map fHandlers; + + /** + * Tells whether colored labels support is enabled. + * + * @since 3.4 + */ + private bool fIsColoredLabelsSupportEnabled= false; + + + /** + * Creates a new content assistant. The content assistant is not automatically activated, + * overlays the completion proposals with context information list if necessary, and shows the + * context information above the location at which it was activated. If auto activation will be + * enabled, without further configuration steps, this content assistant is activated after a 500 + * milliseconds delay. It uses the default partitioning. + */ + public ContentAssistant() { + fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; + } + + /** + * Sets the document partitioning this content assistant is using. + * + * @param partitioning the document partitioning for this content assistant + * @since 3.0 + */ + public void setDocumentPartitioning(String partitioning) { + Assert.isNotNull(partitioning); + fPartitioning= partitioning; + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension#getDocumentPartitioning() + * @since 3.0 + */ + public String getDocumentPartitioning() { + return fPartitioning; + } + + /** + * Registers a given content assist processor for a particular content type. If there is already + * a processor registered for this type, the new processor is registered instead of the old one. + * + * @param processor the content assist processor to register, or <code>null</code> to remove + * an existing one + * @param contentType the content type under which to register + */ + public void setContentAssistProcessor(IContentAssistProcessor processor, String contentType) { + + Assert.isNotNull(contentType); + + if (fProcessors is null) + fProcessors= new HashMap(); + + if (processor is null) + fProcessors.remove(contentType); + else + fProcessors.put(contentType, processor); + } + + /* + * @see IContentAssistant#getContentAssistProcessor + */ + public IContentAssistProcessor getContentAssistProcessor(String contentType) { + if (fProcessors is null) + return null; + + return (IContentAssistProcessor) fProcessors.get(contentType); + } + + /** + * Computes the sorted set of all auto activation trigger characters. + * + * @return the sorted set of all auto activation trigger characters + * @since 3.1 + */ + private String computeAllAutoActivationTriggers() { + if (fProcessors is null) + return ""; //$NON-NLS-1$ + + StringBuffer buf= new StringBuffer(5); + Iterator iter= fProcessors.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry= (Entry) iter.next(); + IContentAssistProcessor processor= (IContentAssistProcessor) entry.getValue(); + char[] triggers= processor.getCompletionProposalAutoActivationCharacters(); + if (triggers !is null) + buf.append(triggers); + triggers= processor.getContextInformationAutoActivationCharacters(); + if (triggers !is null) + buf.append(triggers); + } + return buf.toString(); + } + + /** + * Enables the content assistant's auto activation mode. + * + * @param enabled indicates whether auto activation is enabled or not + */ + public void enableAutoActivation(bool enabled) { + fIsAutoActivated= enabled; + manageAutoActivation(fIsAutoActivated); + } + + /** + * Enables the content assistant's auto insertion mode. If enabled, the content assistant + * inserts a proposal automatically if it is the only proposal. In the case of ambiguities, the + * user must make the choice. + * + * @param enabled indicates whether auto insertion is enabled or not + * @since 2.0 + */ + public void enableAutoInsert(bool enabled) { + fIsAutoInserting= enabled; + } + + /** + * Returns whether this content assistant is in the auto insertion mode or not. + * + * @return <code>true</code> if in auto insertion mode + * @since 2.0 + */ + bool isAutoInserting() { + return fIsAutoInserting; + } + + /** + * Installs and uninstall the listeners needed for auto activation. + * + * @param start <code>true</code> if listeners must be installed, <code>false</code> if they + * must be removed + * @since 2.0 + */ + private void manageAutoActivation(bool start) { + if (start) { + + if ((fContentAssistSubjectControlAdapter !is null) && fAutoAssistListener is null) { + fAutoAssistListener= createAutoAssistListener(); + // For details see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212 + if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener()) + fContentAssistSubjectControlAdapter.appendVerifyKeyListener(fAutoAssistListener); + else + fContentAssistSubjectControlAdapter.addKeyListener(fAutoAssistListener); + } + + } else if (fAutoAssistListener !is null) { + // For details see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=49212 + if (fContentAssistSubjectControlAdapter.supportsVerifyKeyListener()) + fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fAutoAssistListener); + else + fContentAssistSubjectControlAdapter.removeKeyListener(fAutoAssistListener); + fAutoAssistListener= null; + } + } + + /** + * This method allows subclasses to provide their own {@link AutoAssistListener}. + * + * @return a new auto assist listener + * @since 3.4 + */ + protected AutoAssistListener createAutoAssistListener() { + return new AutoAssistListener(); + } + + /** + * Sets the delay after which the content assistant is automatically invoked if the cursor is + * behind an auto activation character. + * + * @param delay the auto activation delay + */ + public void setAutoActivationDelay(int delay) { + fAutoActivationDelay= delay; + } + + /** + * Gets the delay after which the content assistant is automatically invoked if the cursor is + * behind an auto activation character. + * + * @return the auto activation delay + * @since 3.4 + */ + public int getAutoActivationDelay() { + return fAutoActivationDelay; + } + + /** + * Sets the proposal pop-ups' orientation. The following values may be used: + * <ul> + * <li>PROPOSAL_OVERLAY<p> + * proposal popup windows should overlay each other + * </li> + * <li>PROPOSAL_REMOVE<p> + * any currently shown proposal popup should be closed + * </li> + * <li>PROPOSAL_STACKED<p> + * proposal popup windows should be vertical stacked, with no overlap, + * beneath the line containing the current cursor location + * </li> + * </ul> + * + * @param orientation the popup's orientation + */ + public void setProposalPopupOrientation(int orientation) { + fProposalPopupOrientation= orientation; + } + + /** + * Sets the context information popup's orientation. + * The following values may be used: + * <ul> + * <li>CONTEXT_ABOVE<p> + * context information popup should always appear above the line containing + * the current cursor location + * </li> + * <li>CONTEXT_BELOW<p> + * context information popup should always appear below the line containing + * the current cursor location + * </li> + * </ul> + * + * @param orientation the popup's orientation + */ + public void setContextInformationPopupOrientation(int orientation) { + fContextInfoPopupOrientation= orientation; + } + + /** + * Sets the context information popup's background color. + * + * @param background the background color + */ + public void setContextInformationPopupBackground(Color background) { + fContextInfoPopupBackground= background; + } + + /** + * Returns the background of the context information popup. + * + * @return the background of the context information popup + * @since 2.0 + */ + Color getContextInformationPopupBackground() { + return fContextInfoPopupBackground; + } + + /** + * Sets the context information popup's foreground color. + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setContextInformationPopupForeground(Color foreground) { + fContextInfoPopupForeground= foreground; + } + + /** + * Returns the foreground of the context information popup. + * + * + * @return the foreground of the context information popup + * @since 2.0 + */ + Color getContextInformationPopupForeground() { + return fContextInfoPopupForeground; + } + + /** + * Sets the proposal selector's background color. + * <p> + * <strong>Note:</strong> As of 3.4, you should only call this + * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_BACKGROUND_COLOR}. + * </p> + * + * @param background the background color + * @since 2.0 + */ + public void setProposalSelectorBackground(Color background) { + fProposalSelectorBackground= background; + } + + /** + * Returns the custom background color of the proposal selector. + * + * @return the background of the proposal selector or <code>null</code> if not set + * @since 2.0 + */ + Color getProposalSelectorBackground() { + return fProposalSelectorBackground; + } + + /** + * Sets the proposal's foreground color. + * <p> + * <strong>Note:</strong> As of 3.4, you should only call this + * method if you want to override the {@link JFacePreferences#CONTENT_ASSIST_FOREGROUND_COLOR}. + * </p> + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setProposalSelectorForeground(Color foreground) { + fProposalSelectorForeground= foreground; + } + + /** + * Returns the custom foreground color of the proposal selector. + * + * @return the foreground of the proposal selector or <code>null</code> if not set + * @since 2.0 + */ + Color getProposalSelectorForeground() { + return fProposalSelectorForeground; + } + + /** + * Sets the context selector's background color. + * + * @param background the background color + * @since 2.0 + */ + public void setContextSelectorBackground(Color background) { + fContextSelectorBackground= background; + } + + /** + * Returns the background of the context selector. + * + * @return the background of the context selector + * @since 2.0 + */ + Color getContextSelectorBackground() { + return fContextSelectorBackground; + } + + /** + * Sets the context selector's foreground color. + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setContextSelectorForeground(Color foreground) { + fContextSelectorForeground= foreground; + } + + /** + * Returns the foreground of the context selector. + * + * @return the foreground of the context selector + * @since 2.0 + */ + Color getContextSelectorForeground() { + return fContextSelectorForeground; + } + + /** + * Sets the information control creator for the additional information control. + * + * @param creator the information control creator for the additional information control + * @since 2.0 + */ + public void setInformationControlCreator(IInformationControlCreator creator) { + fInformationControlCreator= creator; + } + + /* + * @see IControlContentAssistant#install(IContentAssistSubjectControl) + * @since 3.0 + */ + protected void install(IContentAssistSubjectControl contentAssistSubjectControl) { + fContentAssistSubjectControl= contentAssistSubjectControl; + fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl); + install(); + } + + /* + * @see IContentAssist#install + * @since 3.0 + */ + public void install(ITextViewer textViewer) { + fViewer= textViewer; + fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer); + install(); + } + + protected void install() { + + fLayoutManager= new LayoutManager(); + fInternalListener= new InternalListener(); + + AdditionalInfoController controller= null; + if (fInformationControlCreator !is null) { + int delay= fAutoActivationDelay; + if (delay is 0) + delay= DEFAULT_AUTO_ACTIVATION_DELAY; + delay= Math.round(delay * 1.5f); + controller= new AdditionalInfoController(fInformationControlCreator, delay); + } + + fContextInfoPopup= fContentAssistSubjectControlAdapter.createContextInfoPopup(this); + fProposalPopup= fContentAssistSubjectControlAdapter.createCompletionProposalPopup(this, controller); + + registerHandler(SELECT_NEXT_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_NEXT)); + registerHandler(SELECT_PREVIOUS_PROPOSAL_COMMAND_ID, fProposalPopup.createProposalSelectionHandler(CompletionProposalPopup.ProposalSelectionHandler.SELECT_PREVIOUS)); + + if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) { + fContentAssistSubjectControlShell= fContentAssistSubjectControlAdapter.getControl().getShell(); + fCASCSTraverseListener= new TraverseListener() { + public void keyTraversed(TraverseEvent e) { + if (e.detail is DWT.TRAVERSE_ESCAPE && isProposalPopupActive()) + e.doit= false; + } + }; + fContentAssistSubjectControlShell.addTraverseListener(fCASCSTraverseListener); + } + + manageAutoActivation(fIsAutoActivated); + } + + /* + * @see IContentAssist#uninstall + */ + public void uninstall() { + hide(); + manageAutoActivation(false); + + if (fHandlers !is null) { + fHandlers.clear(); + fHandlers= null; + } + + if (fCloser !is null) { + fCloser.uninstall(); + fCloser= null; + } + + if (Helper.okToUse(fContentAssistSubjectControlShell)) + fContentAssistSubjectControlShell.removeTraverseListener(fCASCSTraverseListener); + fCASCSTraverseListener= null; + fContentAssistSubjectControlShell= null; + + fViewer= null; + fContentAssistSubjectControl= null; + fContentAssistSubjectControlAdapter= null; + } + + /** + * Adds the given shell of the specified type to the layout. Valid types are defined by + * <code>LayoutManager</code>. + * + * @param popup a content assist popup + * @param shell the shell of the content-assist popup + * @param type the type of popup + * @param visibleOffset the offset at which to layout the popup relative to the offset of the + * viewer's visible region + * @since 2.0 + */ + void addToLayout(Object popup, Shell shell, int type, int visibleOffset) { + fLayoutManager.add(popup, shell, type, visibleOffset); + } + + /** + * Layouts the registered popup of the given type relative to the given offset. The offset is + * relative to the offset of the viewer's visible region. Valid types are defined by + * <code>LayoutManager</code>. + * + * @param type the type of popup to layout + * @param visibleOffset the offset at which to layout relative to the offset of the viewer's + * visible region + * @since 2.0 + */ + void layout(int type, int visibleOffset) { + fLayoutManager.layout(type, visibleOffset); + } + + /** + * Returns the layout manager. + * + * @return the layout manager + * @since 3.3 + */ + LayoutManager getLayoutManager() { + return fLayoutManager; + } + + /** + * Notifies the controller that a popup has lost focus. + * + * @param e the focus event + */ + void popupFocusLost(FocusEvent e) { + fCloser.focusLost(e); + } + + /** + * Returns the offset of the selection relative to the offset of the visible region. + * + * @return the offset of the selection relative to the offset of the visible region + * @since 2.0 + */ + int getSelectionOffset() { + return fContentAssistSubjectControlAdapter.getWidgetSelectionRange().x; + } + + /** + * Returns whether the widget token could be acquired. The following are valid listener types: + * <ul> + * <li>AUTO_ASSIST</li> + * <li>CONTEXT_SELECTOR</li> + * <li>PROPOSAL_SELECTOR</li> + * <li>CONTEXT_INFO_POPUP</li> + * </ul> + * + * @param type the listener type for which to acquire + * @return <code>true</code> if the widget token could be acquired + * @since 2.0 + */ + private bool acquireWidgetToken(int type) { + switch (type) { + case CONTEXT_SELECTOR: + case PROPOSAL_SELECTOR: + if (fContentAssistSubjectControl instanceof IWidgetTokenOwnerExtension) { + IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fContentAssistSubjectControl; + return extension.requestWidgetToken(this, WIDGET_PRIORITY); + } else if (fContentAssistSubjectControl instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fContentAssistSubjectControl; + return owner.requestWidgetToken(this); + } else if (fViewer instanceof IWidgetTokenOwnerExtension) { + IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fViewer; + return extension.requestWidgetToken(this, WIDGET_PRIORITY); + } else if (fViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer; + return owner.requestWidgetToken(this); + } + } + return true; + } + + /** + * Registers a content assist listener. The following are valid listener types: + * <ul> + * <li>AUTO_ASSIST</li> + * <li>CONTEXT_SELECTOR</li> + * <li>PROPOSAL_SELECTOR</li> + * <li>CONTEXT_INFO_POPUP</li> + * </ul> + * Returns whether the listener could be added successfully. A listener can not be added if the + * widget token could not be acquired. + * + * @param listener the listener to register + * @param type the type of listener + * @return <code>true</code> if the listener could be added + */ + bool addContentAssistListener(IContentAssistListener listener, int type) { + + if (acquireWidgetToken(type)) { + + fListeners[type]= listener; + + if (fCloser is null && getNumberOfListeners() is 1) { + fCloser= new Closer(); + fCloser.install(); + fContentAssistSubjectControlAdapter.setEventConsumer(fInternalListener); + installKeyListener(); + } else + promoteKeyListener(); + return true; + } + + return false; + } + + /** + * Re-promotes the key listener to the first position, using prependVerifyKeyListener. This + * ensures no other instance is filtering away the keystrokes underneath, if we've been up for a + * while (e.g. when the context info is showing. + * + * @since 3.0 + */ + private void promoteKeyListener() { + uninstallVerifyKeyListener(); + installKeyListener(); + } + + /** + * Installs a key listener on the text viewer's widget. + */ + private void installKeyListener() { + if (!fVerifyKeyListenerHooked) { + if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) { + fVerifyKeyListenerHooked= fContentAssistSubjectControlAdapter.prependVerifyKeyListener(fInternalListener); + } + } + } + + /** + * Releases the previously acquired widget token if the token is no longer necessary. The + * following are valid listener types: + * <ul> + * <li>AUTO_ASSIST</li> + * <li>CONTEXT_SELECTOR</li> + * <li>PROPOSAL_SELECTOR</li> + * <li>CONTEXT_INFO_POPUP</li> + * </ul> + * + * @param type the listener type + * @since 2.0 + */ + private void releaseWidgetToken(int type) { + if (fListeners[CONTEXT_SELECTOR] is null && fListeners[PROPOSAL_SELECTOR] is null) { + IWidgetTokenOwner owner= null; + if (fContentAssistSubjectControl instanceof IWidgetTokenOwner) + owner= (IWidgetTokenOwner) fContentAssistSubjectControl; + else if (fViewer instanceof IWidgetTokenOwner) + owner= (IWidgetTokenOwner) fViewer; + if (owner !is null) + owner.releaseWidgetToken(this); + } + } + + /** + * Unregisters a content assist listener. + * + * @param listener the listener to unregister + * @param type the type of listener + * @see #addContentAssistListener(IContentAssistListener, int) + */ + void removeContentAssistListener(IContentAssistListener listener, int type) { + fListeners[type]= null; + + if (getNumberOfListeners() is 0) { + + if (fCloser !is null) { + fCloser.uninstall(); + fCloser= null; + } + + uninstallVerifyKeyListener(); + fContentAssistSubjectControlAdapter.setEventConsumer(null); + } + + releaseWidgetToken(type); + } + + /** + * Uninstall the key listener from the text viewer's widget. + * + * @since 3.0 + */ + private void uninstallVerifyKeyListener() { + if (fVerifyKeyListenerHooked) { + if (Helper.okToUse(fContentAssistSubjectControlAdapter.getControl())) + fContentAssistSubjectControlAdapter.removeVerifyKeyListener(fInternalListener); + fVerifyKeyListenerHooked= false; + } + } + + /** + * Returns the number of listeners. + * + * @return the number of listeners + * @since 2.0 + */ + private int getNumberOfListeners() { + int count= 0; + for (int i= 0; i <= CONTEXT_INFO_POPUP; i++) { + if (fListeners[i] !is null) + ++count; + } + return count; + } + + /* + * @see IContentAssist#showPossibleCompletions + */ + public String showPossibleCompletions() { + if (!prepareToShowCompletions(false)) + return null; + if (fIsPrefixCompletionEnabled) + return fProposalPopup.incrementalComplete(); + return fProposalPopup.showProposals(false); + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension#completePrefix() + * @since 3.0 + */ + public String completePrefix() { + if (!prepareToShowCompletions(false)) + return null; + return fProposalPopup.incrementalComplete(); + } + + /** + * Prepares to show content assist proposals. It returns false if auto activation has kicked in + * recently. + * + * @param isAutoActivated whether completion was triggered by auto activation + * @return <code>true</code> if the caller should continue and show the proposals, + * <code>false</code> otherwise. + * @since 3.2 + */ + private bool prepareToShowCompletions(bool isAutoActivated) { + long current= System.currentTimeMillis(); + int gracePeriod= Math.max(fAutoActivationDelay, 200); + if (current < fLastAutoActivation + gracePeriod) + return false; + + promoteKeyListener(); + fireSessionBeginEvent(isAutoActivated); + return true; + } + + /** + * Callback to signal this content assistant that the presentation of the possible completions + * has been stopped. + * + * @since 2.1 + */ + protected void possibleCompletionsClosed() { + fLastAutoActivation= Long.MIN_VALUE; + storeCompletionProposalPopupSize(); + } + + /* + * @see IContentAssist#showContextInformation + */ + public String showContextInformation() { + promoteKeyListener(); + if (fContextInfoPopup !is null) + return fContextInfoPopup.showContextProposals(false); + return null; + } + + /** + * Callback to signal this content assistant that the presentation of the context information + * has been stopped. + * + * @since 2.1 + */ + protected void contextInformationClosed() { + } + + /** + * Requests that the specified context information to be shown. + * + * @param contextInformation the context information to be shown + * @param offset the offset to which the context information refers to + * @since 2.0 + */ + void showContextInformation(IContextInformation contextInformation, int offset) { + if (fContextInfoPopup !is null) + fContextInfoPopup.showContextInformation(contextInformation, offset); + } + + /** + * Returns the current content assist error message. + * + * @return an error message or <code>null</code> if no error has occurred + */ + String getErrorMessage() { + return fLastErrorMessage; + } + + /** + * Returns the content assist processor for the content type of the specified document position. + * + * @param viewer the text viewer + * @param offset a offset within the document + * @return a content-assist processor or <code>null</code> if none exists + * @since 3.0 + */ + private IContentAssistProcessor getProcessor(ITextViewer viewer, int offset) { + try { + + IDocument document= viewer.getDocument(); + String type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true); + + return getContentAssistProcessor(type); + + } catch (BadLocationException x) { + } + + return null; + } + + /** + * Returns the content assist processor for the content type of the specified document position. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a offset within the document + * @return a content-assist processor or <code>null</code> if none exists + * @since 3.0 + */ + private IContentAssistProcessor getProcessor(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + try { + + IDocument document= contentAssistSubjectControl.getDocument(); + String type; + if (document !is null) + type= TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true); + else + type= IDocument.DEFAULT_CONTENT_TYPE; + + return getContentAssistProcessor(type); + + } catch (BadLocationException x) { + } + + return null; + } + + /** + * Returns an array of completion proposals computed based on the specified document position. + * The position is used to determine the appropriate content assist processor to invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return an array of completion proposals + * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) + * @since 3.0 + */ + ICompletionProposal[] computeCompletionProposals(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + fLastErrorMessage= null; + + ICompletionProposal[] result= null; + + IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); + if (p instanceof ISubjectControlContentAssistProcessor) { + result= ((ISubjectControlContentAssistProcessor) p).computeCompletionProposals(contentAssistSubjectControl, offset); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns an array of completion proposals computed based on the specified document position. + * The position is used to determine the appropriate content assist processor to invoke. + * + * @param viewer the viewer for which to compute the proposals + * @param offset a document offset + * @return an array of completion proposals or <code>null</code> if no proposals are possible + * @see IContentAssistProcessor#computeCompletionProposals(ITextViewer, int) + */ + ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + fLastErrorMessage= null; + + ICompletionProposal[] result= null; + + IContentAssistProcessor p= getProcessor(viewer, offset); + if (p !is null) { + result= p.computeCompletionProposals(viewer, offset); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns an array of context information objects computed based on the specified document + * position. The position is used to determine the appropriate content assist processor to + * invoke. + * + * @param viewer the viewer for which to compute the context information + * @param offset a document offset + * @return an array of context information objects + * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int) + */ + IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { + fLastErrorMessage= null; + + IContextInformation[] result= null; + + IContentAssistProcessor p= getProcessor(viewer, offset); + if (p !is null) { + result= p.computeContextInformation(viewer, offset); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns an array of context information objects computed based on the specified document + * position. The position is used to determine the appropriate content assist processor to + * invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return an array of context information objects + * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int) + * @since 3.0 + */ + IContextInformation[] computeContextInformation(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + fLastErrorMessage= null; + + IContextInformation[] result= null; + + IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); + if (p instanceof ISubjectControlContentAssistProcessor) { + result= ((ISubjectControlContentAssistProcessor) p).computeContextInformation(contentAssistSubjectControl, offset); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns the context information validator that should be used to determine when the currently + * displayed context information should be dismissed. The position is used to determine the + * appropriate content assist processor to invoke. + * + * @param viewer the text viewer + * @param offset a document offset + * @return an validator + * @see IContentAssistProcessor#getContextInformationValidator() + * @since 3.0 + */ + IContextInformationValidator getContextInformationValidator(ITextViewer viewer, int offset) { + IContentAssistProcessor p= getProcessor(viewer, offset); + return p !is null ? p.getContextInformationValidator() : null; + } + + /** + * Returns the context information validator that should be used to determine when the currently + * displayed context information should be dismissed. The position is used to determine the + * appropriate content assist processor to invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return an validator + * @see IContentAssistProcessor#getContextInformationValidator() + * @since 3.0 + */ + IContextInformationValidator getContextInformationValidator(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); + return p !is null ? p.getContextInformationValidator() : null; + } + + /** + * Returns the context information presenter that should be used to display context information. + * The position is used to determine the appropriate content assist processor to invoke. + * + * @param viewer the text viewer + * @param offset a document offset + * @return a presenter + * @since 2.0 + */ + IContextInformationPresenter getContextInformationPresenter(ITextViewer viewer, int offset) { + IContextInformationValidator validator= getContextInformationValidator(viewer, offset); + if (validator instanceof IContextInformationPresenter) + return (IContextInformationPresenter) validator; + return null; + } + + /** + * Returns the context information presenter that should be used to display context information. + * The position is used to determine the appropriate content assist processor to invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return a presenter + * @since 3.0 + */ + IContextInformationPresenter getContextInformationPresenter(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + IContextInformationValidator validator= getContextInformationValidator(contentAssistSubjectControl, offset); + if (validator instanceof IContextInformationPresenter) + return (IContextInformationPresenter) validator; + return null; + } + + /** + * Returns the characters which when typed by the user should automatically initiate proposing + * completions. The position is used to determine the appropriate content assist processor to + * invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return the auto activation characters + * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() + * @since 3.0 + */ + char[] getCompletionProposalAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); + return p !is null ? p.getCompletionProposalAutoActivationCharacters() : null; + } + + /** + * Returns the characters which when typed by the user should automatically initiate proposing + * completions. The position is used to determine the appropriate content assist processor to + * invoke. + * + * @param viewer the text viewer + * @param offset a document offset + * @return the auto activation characters + * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() + */ + char[] getCompletionProposalAutoActivationCharacters(ITextViewer viewer, int offset) { + IContentAssistProcessor p= getProcessor(viewer, offset); + return p !is null ? p.getCompletionProposalAutoActivationCharacters() : null; + } + + /** + * Returns the characters which when typed by the user should automatically initiate the + * presentation of context information. The position is used to determine the appropriate + * content assist processor to invoke. + * + * @param viewer the text viewer + * @param offset a document offset + * @return the auto activation characters + * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters() + * @since 3.0 + */ + char[] getContextInformationAutoActivationCharacters(ITextViewer viewer, int offset) { + IContentAssistProcessor p= getProcessor(viewer, offset); + return p !is null ? p.getContextInformationAutoActivationCharacters() : null; + } + + /** + * Returns the characters which when typed by the user should automatically initiate the + * presentation of context information. The position is used to determine the appropriate + * content assist processor to invoke. + * + * @param contentAssistSubjectControl the content assist subject control + * @param offset a document offset + * @return the auto activation characters + * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters() + * @since 3.0 + */ + char[] getContextInformationAutoActivationCharacters(IContentAssistSubjectControl contentAssistSubjectControl, int offset) { + IContentAssistProcessor p= getProcessor(contentAssistSubjectControl, offset); + return p !is null ? p.getContextInformationAutoActivationCharacters() : null; + } + + /* + * @see dwtx.jface.text.IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner) + * @since 2.0 + */ + public bool requestWidgetToken(IWidgetTokenOwner owner) { + return false; + } + + /* + * @see dwtx.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(dwtx.jface.text.IWidgetTokenOwner, + * int) + * @since 3.0 + */ + public bool requestWidgetToken(IWidgetTokenOwner owner, int priority) { + if (priority > WIDGET_PRIORITY) { + hide(); + return true; + } + return false; + } + + /* + * @see dwtx.jface.text.IWidgetTokenKeeperExtension#setFocus(dwtx.jface.text.IWidgetTokenOwner) + * @since 3.0 + */ + public bool setFocus(IWidgetTokenOwner owner) { + if (fProposalPopup !is null) { + fProposalPopup.setFocus(); + return fProposalPopup.hasFocus(); + } + return false; + } + + /** + * Hides any open pop-ups. + * + * @since 3.0 + */ + protected void hide() { + if (fProposalPopup !is null) + fProposalPopup.hide(); + + if (fContextInfoPopup !is null) + fContextInfoPopup.hide(); + } + + // ------ control's size handling dialog settings ------ + + /** + * Tells this information control manager to open the information control with the values + * contained in the given dialog settings and to store the control's last valid size in the + * given dialog settings. + * <p> + * Note: This API is only valid if the information control implements + * {@link dwtx.jface.text.IInformationControlExtension3}. Not following this restriction + * will later result in an {@link UnsupportedOperationException}. + * </p> + * <p> + * The constants used to store the values are: + * <ul> + * <li>{@link ContentAssistant#STORE_SIZE_X}</li> + * <li>{@link ContentAssistant#STORE_SIZE_Y}</li> + * </ul> + * </p> + * + * @param dialogSettings + * @since 3.0 + */ + public void setRestoreCompletionProposalSize(IDialogSettings dialogSettings) { + Assert.isTrue(dialogSettings !is null); + fDialogSettings= dialogSettings; + } + + /** + * Stores the content assist pop-up's size. + */ + protected void storeCompletionProposalPopupSize() { + if (fDialogSettings is null || fProposalPopup is null) + return; + + Point size= fProposalPopup.getSize(); + if (size is null) + return; + + fDialogSettings.put(STORE_SIZE_X, size.x); + fDialogSettings.put(STORE_SIZE_Y, size.y); + } + + /** + * Restores the content assist pop-up's size. + * + * @return the stored size + * @since 3.0 + */ + protected Point restoreCompletionProposalPopupSize() { + if (fDialogSettings is null) + return null; + + Point size= new Point(-1, -1); + + try { + size.x= fDialogSettings.getInt(STORE_SIZE_X); + size.y= fDialogSettings.getInt(STORE_SIZE_Y); + } catch (NumberFormatException ex) { + size.x= -1; + size.y= -1; + } + + // sanity check + if (size.x is -1 && size.y is -1) + return null; + + Rectangle maxBounds= null; + if (fContentAssistSubjectControl !is null && Helper.okToUse(fContentAssistSubjectControl.getControl())) + maxBounds= fContentAssistSubjectControl.getControl().getDisplay().getBounds(); + else { + // fallback + Display display= Display.getCurrent(); + if (display is null) + display= Display.getDefault(); + if (display !is null && !display.isDisposed()) + maxBounds= display.getBounds(); + } + + if (size.x > -1 && size.y > -1) { + if (maxBounds !is null) { + size.x= Math.min(size.x, maxBounds.width); + size.y= Math.min(size.y, maxBounds.height); + } + + // Enforce an absolute minimal size + size.x= Math.max(size.x, 30); + size.y= Math.max(size.y, 30); + } + + return size; + } + + /** + * Sets the prefix completion property. If enabled, content assist delegates completion to + * prefix completion. + * + * @param enabled <code>true</code> to enable prefix completion, <code>false</code> to + * disable + */ + public void enablePrefixCompletion(bool enabled) { + fIsPrefixCompletionEnabled= enabled; + } + + /** + * Returns the prefix completion state. + * + * @return <code>true</code> if prefix completion is enabled, <code>false</code> otherwise + * @since 3.2 + */ + bool isPrefixCompletionEnabled() { + return fIsPrefixCompletionEnabled; + } + + /** + * Returns whether the content assistant proposal popup has the focus. + * + * @return <code>true</code> if the proposal popup has the focus + * @since 3.0 + */ + public bool hasProposalPopupFocus() { + return fProposalPopup.hasFocus(); + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#addCompletionListener(dwtx.jface.text.contentassist.ICompletionListener) + * @since 3.2 + */ + public void addCompletionListener(ICompletionListener listener) { + Assert.isLegal(listener !is null); + fCompletionListeners.add(listener); + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#removeCompletionListener(dwtx.jface.text.contentassist.ICompletionListener) + * @since 3.2 + */ + public void removeCompletionListener(ICompletionListener listener) { + fCompletionListeners.remove(listener); + } + + /** + * Fires a session begin event to all registered {@link ICompletionListener}s. + * + * @param isAutoActivated <code>true</code> if this session was triggered by auto activation + * @since 3.2 + */ + void fireSessionBeginEvent(bool isAutoActivated) { + if (fContentAssistSubjectControlAdapter !is null && !isProposalPopupActive()) { + IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); + ContentAssistEvent event= new ContentAssistEvent(this, processor, isAutoActivated); + Object[] listeners= fCompletionListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ICompletionListener listener= (ICompletionListener)listeners[i]; + listener.assistSessionStarted(event); + } + } + } + + /** + * Fires a session restart event to all registered {@link ICompletionListener}s. + * + * @since 3.4 + */ + void fireSessionRestartEvent() { + if (fContentAssistSubjectControlAdapter !is null) { + IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); + ContentAssistEvent event= new ContentAssistEvent(this, processor); + Object[] listeners= fCompletionListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ICompletionListener listener= (ICompletionListener)listeners[i]; + if (listener instanceof ICompletionListenerExtension) + ((ICompletionListenerExtension)listener).assistSessionRestarted(event); + } + } + } + + /** + * Fires a session end event to all registered {@link ICompletionListener}s. + * + * @since 3.2 + */ + void fireSessionEndEvent() { + if (fContentAssistSubjectControlAdapter !is null) { + IContentAssistProcessor processor= getProcessor(fContentAssistSubjectControlAdapter, fContentAssistSubjectControlAdapter.getSelectedRange().x); + ContentAssistEvent event= new ContentAssistEvent(this, processor); + Object[] listeners= fCompletionListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ICompletionListener listener= (ICompletionListener)listeners[i]; + listener.assistSessionEnded(event); + } + } + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setRepeatedInvocationMode(bool) + * @since 3.2 + */ + public void setRepeatedInvocationMode(bool cycling) { + fIsRepetitionMode= cycling; + } + + /** + * Returns <code>true</code> if repeated invocation mode is enabled, <code>false</code> + * otherwise. + * + * @return <code>true</code> if repeated invocation mode is enabled, <code>false</code> + * otherwise + * @since 3.2 + */ + bool isRepeatedInvocationMode() { + return fIsRepetitionMode; + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setShowEmptyList(bool) + * @since 3.2 + */ + public void setShowEmptyList(bool showEmpty) { + fShowEmptyList= showEmpty; + } + + /** + * Returns <code>true</code> if empty lists should be displayed, <code>false</code> + * otherwise. + * + * @return <code>true</code> if empty lists should be displayed, <code>false</code> + * otherwise + * @since 3.2 + */ + bool isShowEmptyList() { + return fShowEmptyList; + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setStatusLineVisible(bool) + * @since 3.2 + */ + public void setStatusLineVisible(bool show) { + fIsStatusLineVisible= show; + if (fProposalPopup !is null) + fProposalPopup.setStatusLineVisible(show); + } + + /** + * Returns <code>true</code> if a message line should be displayed, <code>false</code> + * otherwise. + * + * @return <code>true</code> if a message line should be displayed, <code>false</code> + * otherwise + * @since 3.2 + */ + bool isStatusLineVisible() { + return fIsStatusLineVisible; + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setStatusMessage(java.lang.String) + * @since 3.2 + */ + public void setStatusMessage(String message) { + Assert.isLegal(message !is null); + fMessage= message; + if (fProposalPopup !is null) + fProposalPopup.setMessage(message); + } + + /** + * Returns the affordance caption for the cycling affordance at the bottom of the pop-up. + * + * @return the affordance caption for the cycling affordance at the bottom of the pop-up + * @since 3.2 + */ + String getStatusMessage() { + return fMessage; + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension2#setEmptyMessage(java.lang.String) + * @since 3.2 + */ + public void setEmptyMessage(String message) { + Assert.isLegal(message !is null); + if (fProposalPopup !is null) + fProposalPopup.setEmptyMessage(message); + } + + /** + * Fires a selection event, see {@link ICompletionListener}. + * + * @param proposal the selected proposal, possibly <code>null</code> + * @param smartToggle true if the smart toggle is on + * @since 3.2 + */ + void fireSelectionEvent(ICompletionProposal proposal, bool smartToggle) { + Object[] listeners= fCompletionListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ICompletionListener listener= (ICompletionListener)listeners[i]; + listener.selectionChanged(proposal, smartToggle); + } + } + + /* + * @see dwtx.jface.text.contentassist.IContentAssistantExtension3#setInvocationTrigger(dwtx.jface.bindings.keys.KeySequence) + * @since 3.2 + */ + public void setRepeatedInvocationTrigger(KeySequence sequence) { + fRepeatedInvocationKeySequence= sequence; + } + + /** + * Returns the repeated invocation key sequence. + * + * @return the repeated invocation key sequence or <code>null</code>, if none + * @since 3.2 + */ + KeySequence getRepeatedInvocationKeySequence() { + return fRepeatedInvocationKeySequence; + } + + /** + * Returns whether proposal popup is active. + * + * @return <code>true</code> if the proposal popup is active, <code>false</code> otherwise + * @since 3.4 + */ + protected bool isProposalPopupActive(){ + return fProposalPopup !is null && fProposalPopup.isActive(); + } + + /** + * Returns whether the context information popup is active. + * + * @return <code>true</code> if the context information popup is active, <code>false</code> otherwise + * @since 3.4 + */ + protected bool isContextInfoPopupActive(){ + return fContextInfoPopup !is null && fContextInfoPopup.isActive(); + } + + /** + * {@inheritDoc} + * + * @since 3.4 + */ + public final IHandler getHandler(String commandId) { + if (fHandlers is null) + throw new IllegalStateException(); + + IHandler handler= (IHandler)fHandlers.get(commandId); + if (handler !is null) + return handler; + + Assert.isLegal(false); + return null; + } + + /** + * Registers the given handler under the given command identifier. + * + * @param commandId the command identifier + * @param handler the handler + * @since 3.4 + */ + protected final void registerHandler(String commandId, IHandler handler) { + if (fHandlers is null) + fHandlers= new HashMap(2); + fHandlers.put(commandId, handler); + } + + /** + * Tells whether the support for colored labels is enabled. + * + * @return <code>true</code> if the support for colored labels is enabled, <code>false</code> otherwise + * @since 3.4 + */ + bool isColoredLabelsSupportEnabled() { + return fIsColoredLabelsSupportEnabled; + } + + /** + * Enables the support for colored labels in the proposal popup. + * <p>Completion proposals can implement {@link ICompletionProposalExtension6} + * to provide colored proposal labels.</p> + * + * @param isEnabled if <code>true</code> the support for colored labels is enabled in the proposal popup + * @since 3.4 + */ + public void enableColoredLabels(bool isEnabled) { + fIsColoredLabelsSupportEnabled= isEnabled; + } + +}