Mercurial > projects > dwt-addons
view dwtx/jface/text/link/LinkedModeUI.d @ 153:f70d9508c95c
Fix java Collection imports
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 25 Aug 2008 00:27:31 +0200 |
parents | 5cf141e43417 |
children | 1a5b8f8129df |
line wrap: on
line source
/******************************************************************************* * 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 * Port to the D programming language: * Frank Benoit <benoit@tionex.de> *******************************************************************************/ module dwtx.jface.text.link.LinkedModeUI; import dwtx.jface.text.link.LinkedModeModel; // packageimport import dwtx.jface.text.link.LinkedPosition; // packageimport import dwtx.jface.text.link.ILinkedModeListener; // packageimport import dwtx.jface.text.link.TabStopIterator; // packageimport import dwtx.jface.text.link.InclusivePositionUpdater; // packageimport import dwtx.jface.text.link.LinkedPositionGroup; // packageimport import dwtx.jface.text.link.LinkedModeManager; // packageimport import dwtx.jface.text.link.LinkedPositionAnnotations; // packageimport import dwtx.jface.text.link.ProposalPosition; // packageimport import dwt.dwthelper.utils; import dwtx.dwtxhelper.Collection; import dwt.DWT; import dwt.custom.StyledText; import dwt.custom.VerifyKeyListener; import dwt.events.ShellEvent; import dwt.events.ShellListener; import dwt.events.VerifyEvent; import dwt.graphics.Point; import dwt.widgets.Display; import dwt.widgets.Shell; import dwtx.core.runtime.Assert; import dwtx.jface.internal.text.link.contentassist.ContentAssistant2; import dwtx.jface.internal.text.link.contentassist.IProposalListener; import dwtx.jface.text.BadLocationException; import dwtx.jface.text.BadPartitioningException; import dwtx.jface.text.BadPositionCategoryException; import dwtx.jface.text.DefaultPositionUpdater; import dwtx.jface.text.DocumentCommand; import dwtx.jface.text.DocumentEvent; import dwtx.jface.text.IAutoEditStrategy; import dwtx.jface.text.IDocument; import dwtx.jface.text.IDocumentExtension3; import dwtx.jface.text.IDocumentListener; import dwtx.jface.text.IEditingSupport; import dwtx.jface.text.IEditingSupportRegistry; import dwtx.jface.text.IPositionUpdater; import dwtx.jface.text.IRegion; import dwtx.jface.text.IRewriteTarget; import dwtx.jface.text.ITextInputListener; import dwtx.jface.text.ITextOperationTarget; import dwtx.jface.text.ITextSelection; import dwtx.jface.text.ITextViewer; import dwtx.jface.text.ITextViewerExtension; import dwtx.jface.text.ITextViewerExtension2; import dwtx.jface.text.ITextViewerExtension5; import dwtx.jface.text.Position; import dwtx.jface.text.Region; import dwtx.jface.text.contentassist.ICompletionProposal; import dwtx.jface.text.contentassist.ICompletionProposalExtension6; import dwtx.jface.text.source.IAnnotationModel; import dwtx.jface.text.source.IAnnotationModelExtension; import dwtx.jface.text.source.ISourceViewer; import dwtx.jface.viewers.IPostSelectionProvider; import dwtx.jface.viewers.ISelection; import dwtx.jface.viewers.ISelectionChangedListener; import dwtx.jface.viewers.SelectionChangedEvent; /** * The UI for linked mode. Detects events that influence behavior of the linked mode * UI and acts upon them. * <p> * <code>LinkedModeUI</code> relies on all added * <code>LinkedModeUITarget</code>s to provide implementations of * <code>ITextViewer</code> that implement <code>ITextViewerExtension</code>, * and the documents being edited to implement <code>IDocumentExtension3</code>. * </p> * <p> * Clients may instantiate and extend this class. * </p> * * @since 3.0 */ public class LinkedModeUI { /* cycle constants */ /** * Constant indicating that this UI should never cycle from the last * position to the first and vice versa. */ public static const Object CYCLE_NEVER= new Object(); /** * Constant indicating that this UI should always cycle from the last * position to the first and vice versa. */ public static const Object CYCLE_ALWAYS= new Object(); /** * Constant indicating that this UI should cycle from the last position to * the first and vice versa if its model is not nested. */ public static const Object CYCLE_WHEN_NO_PARENT= new Object(); /** * Listener that gets notified when the linked mode UI switches its focus position. * <p> * Clients may implement this interface. * </p> */ public interface ILinkedModeUIFocusListener { /** * Called when the UI for the linked mode leaves a linked position. * * @param position the position being left * @param target the target where <code>position</code> resides in */ void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target); /** * Called when the UI for the linked mode gives focus to a linked position. * * @param position the position being entered * @param target the target where <code>position</code> resides in */ void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target); } /** * Null object implementation of focus listener. */ private static final class EmtpyFocusListener : ILinkedModeUIFocusListener { public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) { // ignore } public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) { // ignore } } /** * A link target consists of a viewer and gets notified if the linked mode UI on * it is being shown. * <p> * Clients may extend this class. * </p> * @since 3.0 */ public static abstract class LinkedModeUITarget : ILinkedModeUIFocusListener { /** * Returns the viewer represented by this target, never <code>null</code>. * * @return the viewer associated with this target. */ public abstract ITextViewer getViewer(); /** * The viewer's text widget is initialized when the UI first connects * to the viewer and never changed thereafter. This is to keep the * reference of the widget that we have registered our listeners with, * as the viewer, when it gets disposed, does not remember it, resulting * in a situation where we cannot uninstall the listeners and a memory leak. */ StyledText fWidget; /** The cached shell - same reason as fWidget. */ Shell fShell; /** The registered listener, or <code>null</code>. */ KeyListener fKeyListener; /** The cached custom annotation model. */ LinkedPositionAnnotations fAnnotationModel; } private static final class EmptyTarget : LinkedModeUITarget { private ITextViewer fTextViewer; /** * @param viewer the viewer */ public this(ITextViewer viewer) { Assert.isNotNull(viewer); fTextViewer= viewer; } /* * @see dwtx.jdt.internal.ui.text.link2.LinkedModeUI.ILinkedUITarget#getViewer() */ public ITextViewer getViewer() { return fTextViewer; } /** * {@inheritDoc} */ public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) { } /** * {@inheritDoc} */ public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) { } } /** * Listens for state changes in the model. */ private final class ExitListener : ILinkedModeListener { public void left(LinkedModeModel model, int flags) { leave(ILinkedModeListener.EXIT_ALL | flags); } public void suspend(LinkedModeModel model) { disconnect(); redraw(); } public void resume(LinkedModeModel model, int flags) { if ((flags & ILinkedModeListener.EXIT_ALL) !is 0) { leave(flags); } else { connect(); if ((flags & ILinkedModeListener.SELECT) !is 0) select(); ensureAnnotationModelInstalled(); redraw(); } } } /** * Exit flags returned if a custom exit policy wants to exit linked mode. * <p> * Clients may instantiate this class. * </p> */ public static class ExitFlags { /** The flags to return in the <code>leave</code> method. */ public int flags; /** The doit flag of the checked <code>VerifyKeyEvent</code>. */ public bool doit; /** * Creates a new instance. * * @param flags the exit flags * @param doit the doit flag for the verify event */ public this(int flags, bool doit) { this.flags= flags; this.doit= doit; } } /** * An exit policy can be registered by a caller to get custom exit * behavior. * <p> * Clients may implement this interface. * </p> */ public interface IExitPolicy { /** * Checks whether the linked mode should be left after receiving the * given <code>VerifyEvent</code> and selection. Note that the event * carries widget coordinates as opposed to <code>offset</code> and * <code>length</code> which are document coordinates. * * @param model the linked mode model * @param event the verify event * @param offset the offset of the current selection * @param length the length of the current selection * @return valid exit flags or <code>null</code> if no special action * should be taken */ ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length); } /** * A NullObject implementation of <code>IExitPolicy</code>. */ private static class NullExitPolicy : IExitPolicy { /* * @see dwtx.jdt.internal.ui.text.link2.LinkedModeUI.IExitPolicy#doExit(dwt.events.VerifyEvent, int, int) */ public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) { return null; } } /** * Listens for shell events and acts upon them. */ private class Closer : ShellListener, ITextInputListener { public void shellActivated(ShellEvent e) { } public void shellClosed(ShellEvent e) { leave(ILinkedModeListener.EXIT_ALL); } public void shellDeactivated(ShellEvent e) { // TODO re-enable after debugging // if (true) return; // from LinkedPositionUI: // don't deactivate on focus lost, since the proposal popups may take focus // plus: it doesn't hurt if you can check with another window without losing linked mode // since there is no intrusive popup sticking out. // need to check first what happens on reentering based on an open action // Seems to be no problem // Better: // Check with content assistant and only leave if its not the proposal shell that took the // focus away. StyledText text; final ITextViewer viewer; Display display; if (fCurrentTarget is null || (text= fCurrentTarget.fWidget) is null || text.isDisposed() || (display= text.getDisplay()) is null || display.isDisposed() || (viewer= fCurrentTarget.getViewer()) is null) { leave(ILinkedModeListener.EXIT_ALL); } else { // Post in UI thread since the assistant popup will only get the focus after we lose it. display.asyncExec(new class() Runnable { public void run() { if (fIsActive && cast(IEditingSupportRegistry)viewer ) { IEditingSupport[] helpers= (cast(IEditingSupportRegistry) viewer).getRegisteredSupports(); for (int i= 0; i < helpers.length; i++) { if (helpers[i].ownsFocusShell()) return; } } // else leave(ILinkedModeListener.EXIT_ALL); } }); } } public void shellDeiconified(ShellEvent e) { } public void shellIconified(ShellEvent e) { leave(ILinkedModeListener.EXIT_ALL); } /* * @see dwtx.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) */ public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { leave(ILinkedModeListener.EXIT_ALL); } /* * @see dwtx.jface.text.ITextInputListener#inputDocumentChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) */ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { } } /** * @since 3.1 */ private class DocumentListener : IDocumentListener { /* * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent) */ public void documentAboutToBeChanged(DocumentEvent event) { // default behavior: any document change outside a linked position // causes us to exit int end= event.getOffset() + event.getLength(); for (int offset= event.getOffset(); offset <= end; offset++) { if (!fModel.anyPositionContains(offset)) { ITextViewer viewer= fCurrentTarget.getViewer(); if (fFramePosition !is null && cast(IEditingSupportRegistry)viewer ) { IEditingSupport[] helpers= (cast(IEditingSupportRegistry) viewer).getRegisteredSupports(); for (int i= 0; i < helpers.length; i++) { if (helpers[i].isOriginator(null, new Region(fFramePosition.getOffset(), fFramePosition.getLength()))) return; } } leave(ILinkedModeListener.EXTERNAL_MODIFICATION); return; } } } /* * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent) */ public void documentChanged(DocumentEvent event) { } } /** * Listens for key events, checks the exit policy for custom exit * strategies but defaults to handling Tab, Enter, and Escape. */ private class KeyListener : VerifyKeyListener { private bool fIsEnabled= true; public void verifyKey(VerifyEvent event) { if (!event.doit || !fIsEnabled) return; Point selection= fCurrentTarget.getViewer().getSelectedRange(); int offset= selection.x; int length= selection.y; // if the custom exit policy returns anything, use that ExitFlags exitFlags= fExitPolicy.doExit(fModel, event, offset, length); if (exitFlags !is null) { leave(exitFlags.flags); event.doit= exitFlags.doit; return; } // standard behavior: // (Shift+)Tab: jumps from position to position, depending on cycle mode // Enter: accepts all entries and leaves all (possibly stacked) environments, the last sets the caret // Esc: accepts all entries and leaves all (possibly stacked) environments, the caret stays // ? what do we do to leave one level of a cycling model that is stacked? // -> This is only the case if the level was set up with forced cycling cast(CYCLE_ALWAYS), in which case // the caller is sure that one does not need by-level exiting. switch (event.character) { // [SHIFT-]TAB = hop between edit boxes case 0x09: if (!(fExitPosition !is null && fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) { // outside any edit box -> leave (all? TODO should only leave the affected, level and forward to the next upper) leave(ILinkedModeListener.EXIT_ALL); break; } if (event.stateMask is DWT.SHIFT) previous(); else next(); event.doit= false; break; // ENTER case 0x0A: // Ctrl+Enter on WinXP case 0x0D: // if ((fExitPosition !is null && fExitPosition.includes(offset)) || !fModel.anyPositionContains(offset)) { if (!fModel.anyPositionContains(offset)) { // if ((fExitPosition is null || !fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) { // outside any edit box or on exit position -> leave (all? TODO should only leave the affected, level and forward to the next upper) leave(ILinkedModeListener.EXIT_ALL); break; } // normal case: exit entire stack and put caret to final position leave(ILinkedModeListener.EXIT_ALL | ILinkedModeListener.UPDATE_CARET); event.doit= false; break; // ESC case 0x1B: // exit entire stack and leave caret leave(ILinkedModeListener.EXIT_ALL); event.doit= false; break; default: if (event.character !is 0) { if (!controlUndoBehavior(offset, length)) { leave(ILinkedModeListener.EXIT_ALL); break; } } } } private bool controlUndoBehavior(int offset, int length) { LinkedPosition position= fModel.findPosition(new LinkedPosition(fCurrentTarget.getViewer().getDocument(), offset, length, LinkedPositionGroup.NO_STOP)); if (position !is null) { // if the last position is not the same and there is an open change: close it. if (!position.equals(fPreviousPosition)) endCompoundChange(); beginCompoundChange(); } fPreviousPosition= position; return fPreviousPosition !is null; } /** * @param enabled the new enabled state */ public void setEnabled(bool enabled) { fIsEnabled= enabled; } } /** * Installed as post selection listener on the watched viewer. Updates the * linked position after cursor movement, even to positions not in the * iteration list. */ private class MySelectionListener : ISelectionChangedListener { /* * @see dwtx.jface.viewers.ISelectionChangedListener#selectionChanged(dwtx.jface.viewers.SelectionChangedEvent) */ public void selectionChanged(SelectionChangedEvent event) { ISelection selection= event.getSelection(); if ( cast(ITextSelection)selection ) { ITextSelection textsel= cast(ITextSelection) selection; if ( cast(ITextViewer)event.getSelectionProvider() ) { IDocument doc= (cast(ITextViewer) event.getSelectionProvider()).getDocument(); if (doc !is null) { int offset= textsel.getOffset(); int length= textsel.getLength(); if (offset >= 0 && length >= 0) { LinkedPosition find= new LinkedPosition(doc, offset, length, LinkedPositionGroup.NO_STOP); LinkedPosition pos= fModel.findPosition(find); if (pos is null && fExitPosition !is null && fExitPosition.includes(find)) pos= fExitPosition; if (pos !is null) switchPosition(pos, false, false); } } } } } } private class ProposalListener : IProposalListener { /* * @see dwtx.jface.internal.text.link.contentassist.IProposalListener#proposalChosen(dwtx.jface.text.contentassist.ICompletionProposal) */ public void proposalChosen(ICompletionProposal proposal) { next(); } } /** The current viewer. */ private LinkedModeUITarget fCurrentTarget; /** * The manager of the linked positions we provide a UI for. * @since 3.1 */ private LinkedModeModel fModel; /** The set of viewers we manage. */ private LinkedModeUITarget[] fTargets; /** The iterator over the tab stop positions. */ private TabStopIterator fIterator; /* Our team of event listeners */ /** The shell listener. */ private Closer fCloser= new Closer(); /** The linked mode listener. */ private ILinkedModeListener fLinkedListener= new ExitListener(); /** The selection listener. */ private MySelectionListener fSelectionListener= new MySelectionListener(); /** The content assist listener. */ private ProposalListener fProposalListener= new ProposalListener(); /** * The document listener. * @since 3.1 */ private IDocumentListener fDocumentListener= new DocumentListener(); /** The last caret position, used by fCaretListener. */ private const Position fCaretPosition= new Position(0, 0); /** The exit policy to control custom exit behavior */ private IExitPolicy fExitPolicy= new NullExitPolicy(); /** The current frame position shown in the UI, or <code>null</code>. */ private LinkedPosition fFramePosition; /** The last visited position, used for undo / redo. */ private LinkedPosition fPreviousPosition; /** The content assistant used to show proposals. */ private ContentAssistant2 fAssistant; /** The exit position. */ private LinkedPosition fExitPosition; /** State indicator to prevent multiple invocation of leave. */ private bool fIsActive= false; /** The position updater for the exit position. */ private IPositionUpdater fPositionUpdater= new DefaultPositionUpdater(getCategory()); /** Whether to show context info. */ private bool fDoContextInfo= false; /** Whether we have begun a compound change, but not yet closed. */ private bool fHasOpenCompoundChange= false; /** The position listener. */ private ILinkedModeUIFocusListener fPositionListener= new EmtpyFocusListener(); private IAutoEditStrategy fAutoEditVetoer= new class() IAutoEditStrategy { /* * @see dwtx.jface.text.IAutoEditStrategy#customizeDocumentCommand(dwtx.jface.text.IDocument, dwtx.jface.text.DocumentCommand) */ public void customizeDocumentCommand(IDocument document, DocumentCommand command) { // invalidate the change to ensure that the change is performed on the document only. if (fModel.anyPositionContains(command.offset)) { command.doit= false; command.caretOffset= command.offset + command.length; } } }; /** Whether this UI is in simple highlighting mode or not. */ private bool fSimple; /** * Tells whether colored labels support is enabled. * @since 3.4 */ private bool fIsColoredLabelsSupportEnabled= false; /** * Creates a new UI on the given model and the set of viewers. The model * must provide a tab stop sequence with a non-empty list of tab stops. * * @param model the linked mode model * @param targets the non-empty list of targets upon which the linked mode * UI should act */ public this(LinkedModeModel model, LinkedModeUITarget[] targets) { constructor(model, targets); } /** * Convenience constructor for just one viewer. * * @param model the linked mode model * @param viewer the viewer upon which the linked mode UI should act */ public this(LinkedModeModel model, ITextViewer viewer) { constructor(model, [new EmptyTarget(viewer)]); } /** * Convenience constructor for multiple viewers. * * @param model the linked mode model * @param viewers the non-empty list of viewers upon which the linked mode * UI should act */ public this(LinkedModeModel model, ITextViewer[] viewers) { LinkedModeUITarget[] array= new LinkedModeUITarget[viewers.length]; for (int i= 0; i < array.length; i++) { array[i]= new EmptyTarget(viewers[i]); } constructor(model, array); } /** * Convenience constructor for one target. * * @param model the linked mode model * @param target the target upon which the linked mode UI should act */ public this(LinkedModeModel model, LinkedModeUITarget target) { constructor(model, [target]); } /** * This does the actual constructor work. * * @param model the linked mode model * @param targets the non-empty array of targets upon which the linked mode UI * should act */ private void constructor(LinkedModeModel model, LinkedModeUITarget[] targets) { Assert.isNotNull(model); Assert.isNotNull(targets); Assert.isTrue(targets.length > 0); Assert.isTrue(model.getTabStopSequence().size() > 0); fModel= model; fTargets= targets; fCurrentTarget= targets[0]; fIterator= new TabStopIterator(fModel.getTabStopSequence()); fIterator.setCycling(!fModel.isNested()); fModel.addLinkingListener(fLinkedListener); fAssistant= new ContentAssistant2(); fAssistant.addProposalListener(fProposalListener); // TODO find a way to set up content assistant. // fAssistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING); fAssistant.enableColoredLabels(fIsColoredLabelsSupportEnabled); fCaretPosition.delete_(); } /** * Starts this UI on the first position. */ public void enter() { fIsActive= true; connect(); next(); } /** * Sets an <code>IExitPolicy</code> to customize the exit behavior of * this linked mode UI. * * @param policy the exit policy to use. */ public void setExitPolicy(IExitPolicy policy) { fExitPolicy= policy; } /** * Sets the exit position to move the caret to when linked mode mode is * exited. * * @param target the target where the exit position is located * @param offset the offset of the exit position * @param length the length of the exit position (in case there should be a * selection) * @param sequence set to the tab stop position of the exit position, or * <code>LinkedPositionGroup.NO_STOP</code> if there should be no * tab stop. * @throws BadLocationException if the position is not valid in the viewer's * document */ public void setExitPosition(LinkedModeUITarget target, int offset, int length, int sequence) { // remove any existing exit position if (fExitPosition !is null) { fExitPosition.getDocument().removePosition(fExitPosition); fIterator.removePosition(fExitPosition); fExitPosition= null; } IDocument doc= target.getViewer().getDocument(); if (doc is null) return; fExitPosition= new LinkedPosition(doc, offset, length, sequence); doc.addPosition(fExitPosition); // gets removed in leave() if (sequence !is LinkedPositionGroup.NO_STOP) fIterator.addPosition(fExitPosition); } /** * Sets the exit position to move the caret to when linked mode is exited. * * @param viewer the viewer where the exit position is located * @param offset the offset of the exit position * @param length the length of the exit position (in case there should be a * selection) * @param sequence set to the tab stop position of the exit position, or * <code>LinkedPositionGroup.NO_STOP</code> if there should be no tab stop. * @throws BadLocationException if the position is not valid in the * viewer's document */ public void setExitPosition(ITextViewer viewer, int offset, int length, int sequence) { setExitPosition(new EmptyTarget(viewer), offset, length, sequence); } /** * Sets the cycling mode to either of <code>CYCLING_ALWAYS</code>, * <code>CYCLING_NEVER</code>, or <code>CYCLING_WHEN_NO_PARENT</code>, * which is the default. * * @param mode the new cycling mode. */ public void setCyclingMode(Object mode) { if (mode !is CYCLE_ALWAYS && mode !is CYCLE_NEVER && mode !is CYCLE_WHEN_NO_PARENT) throw new IllegalArgumentException(); if (mode is CYCLE_ALWAYS || mode is CYCLE_WHEN_NO_PARENT && !fModel.isNested()) fIterator.setCycling(true); else fIterator.setCycling(false); } void next() { if (fIterator.hasNext(fFramePosition)) { switchPosition(fIterator.next(fFramePosition), true, true); return; } leave(ILinkedModeListener.UPDATE_CARET); } void previous() { if (fIterator.hasPrevious(fFramePosition)) { switchPosition(fIterator.previous(fFramePosition), true, true); } else // dont't update caret, but rather select the current frame leave(ILinkedModeListener.SELECT); } private void triggerContextInfo() { ITextOperationTarget target= fCurrentTarget.getViewer().getTextOperationTarget(); if (target !is null) { if (target.canDoOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION)) target.doOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); } } /** Trigger content assist on choice positions */ private void triggerContentAssist() { if ( cast(ProposalPosition)fFramePosition ) { ProposalPosition pp= cast(ProposalPosition) fFramePosition; ICompletionProposal[] choices= pp.getChoices(); if (choices !is null && choices.length > 0) { fAssistant.setCompletions(choices); fAssistant.showPossibleCompletions(); return; } } fAssistant.setCompletions(new ICompletionProposal[0]); fAssistant.hidePossibleCompletions(); } private void switchPosition(LinkedPosition pos, bool select, bool showProposals) { Assert.isNotNull(pos); if (pos.equals(fFramePosition)) return; if (fFramePosition !is null && fCurrentTarget !is null) fPositionListener.linkingFocusLost(fFramePosition, fCurrentTarget); // undo endCompoundChange(); redraw(); // redraw current position being left - usually not needed IDocument oldDoc= fFramePosition is null ? null : fFramePosition.getDocument(); IDocument newDoc= pos.getDocument(); switchViewer(oldDoc, newDoc, pos); fFramePosition= pos; if (select) select(); if (fFramePosition is fExitPosition && !fIterator.isCycling()) leave(ILinkedModeListener.NONE); else { redraw(); // redraw new position ensureAnnotationModelInstalled(); } if (showProposals) triggerContentAssist(); if (fFramePosition !is fExitPosition && fDoContextInfo) triggerContextInfo(); if (fFramePosition !is null && fCurrentTarget !is null) fPositionListener.linkingFocusGained(fFramePosition, fCurrentTarget); } private void ensureAnnotationModelInstalled() { LinkedPositionAnnotations lpa= fCurrentTarget.fAnnotationModel; if (lpa !is null) { ITextViewer viewer= fCurrentTarget.getViewer(); if ( cast(ISourceViewer)viewer ) { ISourceViewer sv= cast(ISourceViewer) viewer; IAnnotationModel model= sv.getAnnotationModel(); if ( cast(IAnnotationModelExtension)model ) { IAnnotationModelExtension ext= cast(IAnnotationModelExtension) model; IAnnotationModel ourModel= ext.getAnnotationModel(getUniqueKey()); if (ourModel is null) { ext.addAnnotationModel(getUniqueKey(), lpa); } } } } } private void uninstallAnnotationModel(LinkedModeUITarget target) { ITextViewer viewer= target.getViewer(); if ( cast(ISourceViewer)viewer ) { ISourceViewer sv= cast(ISourceViewer) viewer; IAnnotationModel model= sv.getAnnotationModel(); if ( cast(IAnnotationModelExtension)model ) { IAnnotationModelExtension ext= cast(IAnnotationModelExtension) model; ext.removeAnnotationModel(getUniqueKey()); } } } private void switchViewer(IDocument oldDoc, IDocument newDoc, LinkedPosition pos) { if (oldDoc !is newDoc) { // redraw current document with new position before switching viewer if (fCurrentTarget.fAnnotationModel !is null) fCurrentTarget.fAnnotationModel.switchToPosition(fModel, pos); LinkedModeUITarget target= null; for (int i= 0; i < fTargets.length; i++) { if (fTargets[i].getViewer().getDocument() is newDoc) { target= fTargets[i]; break; } } if (target !is fCurrentTarget) { disconnect(); fCurrentTarget= target; target.linkingFocusLost(fFramePosition, target); connect(); ensureAnnotationModelInstalled(); if (fCurrentTarget !is null) fCurrentTarget.linkingFocusGained(pos, fCurrentTarget); } } } private void select() { ITextViewer viewer= fCurrentTarget.getViewer(); if ( cast(ITextViewerExtension5)viewer ) { ITextViewerExtension5 extension5= cast(ITextViewerExtension5) viewer; extension5.exposeModelRange(new Region(fFramePosition.offset, fFramePosition.length)); } else if (!viewer.overlapsWithVisibleRegion(fFramePosition.offset, fFramePosition.length)) { viewer.resetVisibleRegion(); } viewer.revealRange(fFramePosition.offset, fFramePosition.length); viewer.setSelectedRange(fFramePosition.offset, fFramePosition.length); } private void redraw() { if (fCurrentTarget.fAnnotationModel !is null) fCurrentTarget.fAnnotationModel.switchToPosition(fModel, fFramePosition); } private void connect() { Assert.isNotNull(fCurrentTarget); ITextViewer viewer= fCurrentTarget.getViewer(); Assert.isNotNull(viewer); fCurrentTarget.fWidget= viewer.getTextWidget(); if (fCurrentTarget.fWidget is null) leave(ILinkedModeListener.EXIT_ALL); if (fCurrentTarget.fKeyListener is null) { fCurrentTarget.fKeyListener= new KeyListener(); (cast(ITextViewerExtension) viewer).prependVerifyKeyListener(fCurrentTarget.fKeyListener); } else fCurrentTarget.fKeyListener.setEnabled(true); registerAutoEditVetoer(viewer); (cast(IPostSelectionProvider) viewer).addPostSelectionChangedListener(fSelectionListener); createAnnotationModel(); showSelection(); fCurrentTarget.fShell= fCurrentTarget.fWidget.getShell(); if (fCurrentTarget.fShell is null) leave(ILinkedModeListener.EXIT_ALL); fCurrentTarget.fShell.addShellListener(fCloser); fAssistant.install(viewer); viewer.addTextInputListener(fCloser); viewer.getDocument().addDocumentListener(fDocumentListener); } /** * Reveals the selection on the current target's widget, if it is valid. */ private void showSelection() { final StyledText widget= fCurrentTarget.fWidget; if (widget is null || widget.isDisposed()) return; // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132263 widget.getDisplay().asyncExec(new class() Runnable { public void run() { if (!widget.isDisposed()) try { widget.showSelection(); } catch (IllegalArgumentException e) { /* * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=66914 * if the StyledText is in setRedraw(false) mode, its * selection may not be up2date and calling showSelection * will throw an IAE. * We don't have means to find out whether the selection is valid * or whether the widget is redrawing or not therefore we try * and ignore an IAE. */ } } }); } /** * Registers our auto edit vetoer with the viewer. * * @param viewer the viewer we want to veto ui-triggered changes within * linked positions */ private void registerAutoEditVetoer(ITextViewer viewer) { try { String[] contentTypes= getContentTypes(viewer.getDocument()); if ( cast(ITextViewerExtension2)viewer ) { ITextViewerExtension2 vExtension= (cast(ITextViewerExtension2) viewer); for (int i= 0; i < contentTypes.length; i++) { vExtension.prependAutoEditStrategy(fAutoEditVetoer, contentTypes[i]); } } else { Assert.isTrue(false); } } catch (BadPartitioningException e) { leave(ILinkedModeListener.EXIT_ALL); } } private void unregisterAutoEditVetoer(ITextViewer viewer) { try { String[] contentTypes= getContentTypes(viewer.getDocument()); if ( cast(ITextViewerExtension2)viewer ) { ITextViewerExtension2 vExtension= (cast(ITextViewerExtension2) viewer); for (int i= 0; i < contentTypes.length; i++) { vExtension.removeAutoEditStrategy(fAutoEditVetoer, contentTypes[i]); } } else { Assert.isTrue(false); } } catch (BadPartitioningException e) { leave(ILinkedModeListener.EXIT_ALL); } } /** * Returns all possible content types of <code>document</code>. * * @param document the document * @return all possible content types of <code>document</code> * @throws BadPartitioningException * @since 3.1 */ private String[] getContentTypes(IDocument document) { if ( cast(IDocumentExtension3)document ) { IDocumentExtension3 ext= cast(IDocumentExtension3) document; String[] partitionings= ext.getPartitionings(); Set contentTypes= new HashSet(20); for (int i= 0; i < partitionings.length; i++) { contentTypes.addAll(Arrays.asList(ext.getLegalContentTypes(partitionings[i]))); } contentTypes.add(IDocument.DEFAULT_CONTENT_TYPE); return (String[]) contentTypes.toArray(new String[contentTypes.size()]); } return document.getLegalContentTypes(); } private void createAnnotationModel() { if (fCurrentTarget.fAnnotationModel is null) { LinkedPositionAnnotations lpa= new LinkedPositionAnnotations(); if (fSimple) { lpa.markExitTarget(true); lpa.markFocus(false); lpa.markSlaves(false); lpa.markTargets(false); } lpa.setTargets(fIterator.getPositions()); lpa.setExitTarget(fExitPosition); lpa.connect(fCurrentTarget.getViewer().getDocument()); fCurrentTarget.fAnnotationModel= lpa; } } private String getUniqueKey() { return "linked.annotationmodelkey."+toString(); //$NON-NLS-1$ } private void disconnect() { Assert.isNotNull(fCurrentTarget); ITextViewer viewer= fCurrentTarget.getViewer(); Assert.isNotNull(viewer); viewer.getDocument().removeDocumentListener(fDocumentListener); fAssistant.uninstall(); fAssistant.removeProposalListener(fProposalListener); fCurrentTarget.fWidget= null; Shell shell= fCurrentTarget.fShell; fCurrentTarget.fShell= null; if (shell !is null && !shell.isDisposed()) shell.removeShellListener(fCloser); // this one is asymmetric: we don't install the model in // connect, but leave it to its callers to ensure they // have the model installed if they need it uninstallAnnotationModel(fCurrentTarget); unregisterAutoEditVetoer(viewer); // don't remove the verify key listener to let it keep its position // in the listener queue if (fCurrentTarget.fKeyListener !is null) fCurrentTarget.fKeyListener.setEnabled(false); (cast(IPostSelectionProvider) viewer).removePostSelectionChangedListener(fSelectionListener); redraw(); } void leave(final int flags) { if (!fIsActive) return; fIsActive= false; endCompoundChange(); Display display= null; if (fCurrentTarget.fWidget !is null && !fCurrentTarget.fWidget.isDisposed()) display= fCurrentTarget.fWidget.getDisplay(); if (fCurrentTarget.fAnnotationModel !is null) fCurrentTarget.fAnnotationModel.removeAllAnnotations(); disconnect(); for (int i= 0; i < fTargets.length; i++) { LinkedModeUITarget target= fTargets[i]; ITextViewer viewer= target.getViewer(); if (target.fKeyListener !is null) { (cast(ITextViewerExtension) viewer).removeVerifyKeyListener(target.fKeyListener); target.fKeyListener= null; } viewer.removeTextInputListener(fCloser); } for (int i= 0; i < fTargets.length; i++) { if (fTargets[i].fAnnotationModel !is null) { fTargets[i].fAnnotationModel.removeAllAnnotations(); fTargets[i].fAnnotationModel.disconnect(fTargets[i].getViewer().getDocument()); fTargets[i].fAnnotationModel= null; } uninstallAnnotationModel(fTargets[i]); } if ((flags & ILinkedModeListener.UPDATE_CARET) !is 0 && fExitPosition !is null && fFramePosition !is fExitPosition && !fExitPosition.isDeleted()) switchPosition(fExitPosition, true, false); final List docs= new ArrayList(); for (int i= 0; i < fTargets.length; i++) { IDocument doc= fTargets[i].getViewer().getDocument(); if (doc !is null) docs.add(doc); } fModel.stopForwarding(flags); Runnable runnable= new class() Runnable { public void run() { if (fExitPosition !is null) fExitPosition.getDocument().removePosition(fExitPosition); for (Iterator iter = docs.iterator(); iter.hasNext(); ) { IDocument doc= cast(IDocument) iter.next(); doc.removePositionUpdater(fPositionUpdater); bool uninstallCat= false; String[] cats= doc.getPositionCategories(); for (int j= 0; j < cats.length; j++) { if (getCategory().equals(cats[j])) { uninstallCat= true; break; } } if (uninstallCat) try { doc.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // ignore } } fModel.exit(flags); } }; // remove positions (both exit positions AND linked positions in the // model) asynchronously to make sure that the annotation painter // gets correct document offsets. if (display !is null) display.asyncExec(runnable); else runnable.run(); } private void endCompoundChange() { if (fHasOpenCompoundChange) { ITextViewerExtension extension= cast(ITextViewerExtension) fCurrentTarget.getViewer(); IRewriteTarget target= extension.getRewriteTarget(); target.endCompoundChange(); fHasOpenCompoundChange= false; } } private void beginCompoundChange() { if (!fHasOpenCompoundChange) { ITextViewerExtension extension= cast(ITextViewerExtension) fCurrentTarget.getViewer(); IRewriteTarget target= extension.getRewriteTarget(); target.beginCompoundChange(); fHasOpenCompoundChange= true; } } /** * Returns the currently selected region or <code>null</code>. * * @return the currently selected region or <code>null</code> */ public IRegion getSelectedRegion() { if (fFramePosition !is null) return new Region(fFramePosition.getOffset(), fFramePosition.getLength()); if (fExitPosition !is null) return new Region(fExitPosition.getOffset(), fExitPosition.getLength()); return null; } private String getCategory() { return toString(); } /** * Sets the context info property. If set to <code>true</code>, context * info will be invoked on the current target's viewer whenever a position * is switched. * * @param doContextInfo <code>true</code> if context information should be * displayed */ public void setDoContextInfo(bool doContextInfo) { fDoContextInfo= doContextInfo; } /** * Sets the focus callback which will get informed when the focus of the * linked mode UI changes. * <p> * If there is a listener installed already, it will be replaced. * </p> * * @param listener the new listener, never <code>null</code>. */ protected void setPositionListener(ILinkedModeUIFocusListener listener) { Assert.isNotNull(listener); fPositionListener= listener; } /** * Sets the "simple" mode of the receiver. A linked mode UI in simple mode * merely draws the exit position, but not the target, focus, and slave * positions. Default is <code>false</code>. This method must be called * before it is entered. * * @param simple <code>true</code> if the UI should be in simple mode. */ public void setSimpleMode(bool simple) { fSimple= simple; } /** * 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; } }