/** * */ package de.ovgu.cide.editor.inlineprojection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentInformationMappingExtension; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ISlaveDocumentManager; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.projection.ProjectionDocument; import org.eclipse.jface.text.projection.ProjectionDocumentEvent; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationModelEvent; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.eclipse.jface.text.source.IAnnotationModelListener; import org.eclipse.jface.text.source.IAnnotationModelListenerExtension; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.projection.IProjectionListener; import org.eclipse.jface.text.source.projection.IProjectionPosition; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.swt.SWTError; import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; @SuppressWarnings(value={"unchecked"}) public class InlineProjectionSourceViewer extends ProjectionViewer implements ITextViewerExtension5 { /** * Internal listener to changes of the annotation model. */ private class AnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension { /* * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel) */ public void modelChanged(IAnnotationModel model) { processModelChanged(model, null); } /* * @see org.eclipse.jface.text.source.IAnnotationModelListenerExtension#modelChanged(org.eclipse.jface.text.source.AnnotationModelEvent) */ public void modelChanged(AnnotationModelEvent event) { processModelChanged(event.getAnnotationModel(), event); } private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) { if (model == fInlineProjectionAnnotationModel) { if (fProjectionSummary != null) fProjectionSummary .updateSummaries(new NullProgressMonitor()); processInlineCatchupRequest(event); } else if (model == getAnnotationModel() && fProjectionSummary != null) fProjectionSummary.updateSummaries(new NullProgressMonitor()); } } /** * Executes the 'replaceVisibleDocument' operation when called the first * time. Self-destructs afterwards. */ private class ReplaceVisibleDocumentExecutor implements IDocumentListener { private IDocument fSlaveDocument; private IDocument fExecutionTrigger; /** * Creates a new executor in order to free the given slave document. * * @param slaveDocument * the slave document to free */ public ReplaceVisibleDocumentExecutor(IDocument slaveDocument) { fSlaveDocument = slaveDocument; } /** * Installs this executor on the given trigger document. * * @param executionTrigger * the trigger document */ public void install(IDocument executionTrigger) { if (executionTrigger != null && fSlaveDocument != null) { fExecutionTrigger = executionTrigger; fExecutionTrigger.addDocumentListener(this); } } /* * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) */ public void documentAboutToBeChanged(DocumentEvent event) { } /* * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent) */ public void documentChanged(DocumentEvent event) { fExecutionTrigger.removeDocumentListener(this); executeReplaceVisibleDocument(fSlaveDocument); } } /** * A command representing a change of the projection document. This can be * either adding a master document range, removing a master document change, * or invalidating the viewer text presentation. */ private static class ProjectionCommand { final static int ADD = 0; final static int REMOVE = 1; final static int INVALIDATE_PRESENTATION = 2; ProjectionDocument fProjection; int fType; int fOffset; int fLength; ProjectionCommand(ProjectionDocument projection, int type, int offset, int length) { fProjection = projection; fType = type; fOffset = offset; fLength = length; } ProjectionCommand(int offset, int length) { fType = INVALIDATE_PRESENTATION; fOffset = offset; fLength = length; } int computeExpectedCosts() { switch (fType) { case ADD: { try { IRegion[] gaps = fProjection .computeUnprojectedMasterRegions(fOffset, fLength); return gaps == null ? 0 : gaps.length; } catch (BadLocationException x) { } break; } case REMOVE: { try { IRegion[] fragments = fProjection .computeProjectedMasterRegions(fOffset, fLength); return fragments == null ? 0 : fragments.length; } catch (BadLocationException x) { } break; } } return 0; } } /** * The queue of projection command objects. */ private static class ProjectionCommandQueue { final static int REDRAW_COSTS = 15; final static int INVALIDATION_COSTS = 10; List fList = new ArrayList(15); int fExpectedExecutionCosts = -1; void add(ProjectionCommand command) { fList.add(command); } Iterator iterator() { return fList.iterator(); } void clear() { fList.clear(); fExpectedExecutionCosts = -1; } boolean passedRedrawCostsThreshold() { if (fExpectedExecutionCosts == -1) computeExpectedExecutionCosts(); return fExpectedExecutionCosts > REDRAW_COSTS; } boolean passedInvalidationCostsThreshold() { if (fExpectedExecutionCosts == -1) computeExpectedExecutionCosts(); return fExpectedExecutionCosts > INVALIDATION_COSTS; } private void computeExpectedExecutionCosts() { int max_costs = Math.max(REDRAW_COSTS, INVALIDATION_COSTS); fExpectedExecutionCosts = fList.size(); if (fExpectedExecutionCosts <= max_costs) { ProjectionCommand command; Iterator e = fList.iterator(); while (e.hasNext()) { command = (ProjectionCommand) e.next(); fExpectedExecutionCosts += command.computeExpectedCosts(); if (fExpectedExecutionCosts > max_costs) break; } } } } /** The projection annotation model used by this viewer. */ private InlineProjectionAnnotationModel fInlineProjectionAnnotationModel; /** The annotation model listener */ private IAnnotationModelListener fAnnotationModelListener = new AnnotationModelListener(); /** The projection summary. */ private InlineProjectionSummary fProjectionSummary; /** Indication that an annotation world change has not yet been processed. */ private boolean fPendingAnnotationWorldChange = false; /** * Indication whether projection changes in the visible document should be * considered. */ private boolean fHandleInlineProjectionChanges = true; /** Internal lock for protecting the list of pending requests */ private Object fLock = new Object(); /** The list of pending requests */ private List fPendingRequests = new ArrayList(); /** The replace-visible-document execution trigger */ private IDocument fReplaceVisibleDocumentExecutionTrigger; /** * <code>true</code> if projection was on the last time we switched to * segmented mode. */ private boolean fWasInlineProjectionEnabled; /** * The queue of projection commands used to assess the costs of projection * changes. */ private ProjectionCommandQueue fCommandQueue; /** * The amount of lines deleted by the last document event issued by the * visible document event. * * @since 3.1 */ private int fDeletedLines; /** * Creates a new projection source viewer. * * @param parent * the SWT parent control * @param ruler * the vertical ruler * @param overviewRuler * the overview ruler * @param showsAnnotationOverview * <code>true</code> if the overview ruler should be shown * @param styles * the SWT style bits * @param fPreferenceStore */ public InlineProjectionSourceViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) { super(parent, ruler, overviewRuler, showsAnnotationOverview, styles); } /** * Sets the projection summary for this viewer. * * @param projectionSummary * the projection summary. */ public void setProjectionSummary(InlineProjectionSummary projectionSummary) { fProjectionSummary = projectionSummary; } /** * Adds the projection annotation model to the given annotation model. * * @param model * the model to which the projection annotation model is added */ private void addProjectionAnnotationModel(IAnnotationModel model) { if (model instanceof IAnnotationModelExtension) { IAnnotationModelExtension extension = (IAnnotationModelExtension) model; extension.addAnnotationModel( InlineProjectionSupport.INLINEPROJECTION, fInlineProjectionAnnotationModel); model.addAnnotationModelListener(fAnnotationModelListener); } } /** * Removes the projection annotation model from the given annotation model. * * @param model * the mode from which the projection annotation model is removed * @return the removed projection annotation model or <code>null</code> if * there was none */ private IAnnotationModel removeProjectionAnnotationModel( IAnnotationModel model) { if (model instanceof IAnnotationModelExtension) { model.removeAnnotationModelListener(fAnnotationModelListener); IAnnotationModelExtension extension = (IAnnotationModelExtension) model; return extension .removeAnnotationModel(InlineProjectionSupport.INLINEPROJECTION); } return null; } /* * @see org.eclipse.jface.text.source.SourceViewer#setDocument(org.eclipse.jface.text.IDocument, * org.eclipse.jface.text.source.IAnnotationModel, int, int) */ public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) { super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength); boolean wasProjectionEnabled = false; synchronized (fLock) { fPendingRequests.clear(); } if (fInlineProjectionAnnotationModel != null) { wasProjectionEnabled = removeProjectionAnnotationModel(getVisualAnnotationModel()) != null; fInlineProjectionAnnotationModel = null; } super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength); if (wasProjectionEnabled && document != null) enableInlineProjection(); } /* * @see org.eclipse.jface.text.source.SourceViewer#createVisualAnnotationModel(org.eclipse.jface.text.source.IAnnotationModel) */ protected IAnnotationModel createVisualAnnotationModel( IAnnotationModel annotationModel) { super.createVisualAnnotationModel(annotationModel); IAnnotationModel model = super .createVisualAnnotationModel(annotationModel); fInlineProjectionAnnotationModel = new InlineProjectionAnnotationModel(); return model; } /** * Returns the projection annotation model. * * @return the projection annotation model */ public InlineProjectionAnnotationModel getInlineProjectionAnnotationModel() { IAnnotationModel model = getVisualAnnotationModel(); if (model instanceof IAnnotationModelExtension) { IAnnotationModelExtension extension = (IAnnotationModelExtension) model; return (InlineProjectionAnnotationModel) extension .getAnnotationModel(InlineProjectionSupport.INLINEPROJECTION); } return null; } /** * Returns whether this viewer is in projection mode. * * @return <code>true</code> if this viewer is in projection mode, * <code>false</code> otherwise */ public final boolean isInlineProjectionMode() { return getInlineProjectionAnnotationModel() != null; } /** * Disables the projection mode. */ public final void disableInlineProjection() { if (isInlineProjectionMode()) { removeProjectionAnnotationModel(getVisualAnnotationModel()); fInlineProjectionAnnotationModel.removeAllAnnotations(); fFindReplaceDocumentAdapter = null; fireInlineProjectionDisabled(); } } /** * Enables the projection mode. */ public final void enableInlineProjection() { if (!isInlineProjectionMode()) { addProjectionAnnotationModel(getVisualAnnotationModel()); fFindReplaceDocumentAdapter = null; fireInlineProjectionEnabled(); } } private void expandAll() { int offset = 0; IDocument doc = getDocument(); int length = doc == null ? 0 : doc.getLength(); if (isInlineProjectionMode()) { fInlineProjectionAnnotationModel.expandAll(offset, length); } } private void expand() { if (isInlineProjectionMode()) { Position found = null; Annotation bestMatch = null; Point selection = getSelectedRange(); for (Iterator e = fInlineProjectionAnnotationModel .getAnnotationIterator(); e.hasNext();) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) e .next(); if (annotation.isCollapsed()) { Position position = fInlineProjectionAnnotationModel .getPosition(annotation); // take the first most fine grained match if (position != null && touches(selection, position)) if (found == null || position.includes(found.offset) && position.includes(found.offset + found.length)) { found = position; bestMatch = annotation; } } } if (bestMatch != null) { fInlineProjectionAnnotationModel.expand(bestMatch); revealRange(selection.x, selection.y); } } } private boolean touches(Point selection, Position position) { return position.overlapsWith(selection.x, selection.y) || selection.y == 0 && position.offset + position.length == selection.x + selection.y; } private void collapse() { if (isInlineProjectionMode()) { Position found = null; Annotation bestMatch = null; Point selection = getSelectedRange(); for (Iterator e = fInlineProjectionAnnotationModel .getAnnotationIterator(); e.hasNext();) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) e .next(); if (!annotation.isCollapsed()) { Position position = fInlineProjectionAnnotationModel .getPosition(annotation); // take the first most fine grained match if (position != null && touches(selection, position)) if (found == null || found.includes(position.offset) && found.includes(position.offset + position.length)) { found = position; bestMatch = annotation; } } } if (bestMatch != null) { fInlineProjectionAnnotationModel.collapse(bestMatch); revealRange(selection.x, selection.y); } } } /* * @since 3.2 */ private void collapseAll() { int offset = 0; IDocument doc = getDocument(); int length = doc == null ? 0 : doc.getLength(); if (isInlineProjectionMode()) { fInlineProjectionAnnotationModel.collapseAll(offset, length); } } /** * Adds the given master range to the given projection document. While the * modification is processed, the viewer no longer handles projection * changes, as it is causing them. * * @param projection * the projection document * @param offset * the offset in the master document * @param length * the length in the master document * @throws BadLocationException * in case the specified range is invalid * * @see ProjectionDocument#addMasterDocumentRange(int, int) */ private void addMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException { if (fCommandQueue != null) { fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.ADD, offset, length)); } else { try { fHandleInlineProjectionChanges = false; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258 // make sure the document range is strictly line based // int end = offset + length; // offset = toLineStart(projection.getMasterDocument(), offset, // false); // length = toLineStart(projection.getMasterDocument(), end, // true) // - offset; projection.addMasterDocumentRange(offset + 1, length - 1); } finally { fHandleInlineProjectionChanges = true; } } } /** * Removes the given master range from the given projection document. While * the modification is processed, the viewer no longer handles projection * changes, as it is causing them. * * @param projection * the projection document * @param offset * the offset in the master document * @param length * the length in the master document * @throws BadLocationException * in case the specified range is invalid * * @see ProjectionDocument#removeMasterDocumentRange(int, int) */ private void removeMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException { if (fCommandQueue != null) { fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.REMOVE, offset, length)); } else { try { fHandleInlineProjectionChanges = false; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258 // make sure the document range is strictly line based // int end = offset + length; // offset = toLineStart(projection.getMasterDocument(), offset, // false); // length = toLineStart(projection.getMasterDocument(), end, // true) // - offset; projection.removeMasterDocumentRange(offset + 1, length - 1); } finally { fHandleInlineProjectionChanges = true; } } } /** * Returns the first line offset <= <code>offset</code>. If * <code>testLastLine</code> is <code>true</code> and the offset is on * last line then <code>offset</code> is returned. * * @param document * the document * @param offset * the master document offset * @param testLastLine * <code>true</code> if the test for the last line should be * performed * @return the closest line offset >= <code>offset</code> * @throws BadLocationException * if the offset is invalid * @since 3.2 */ // private int toLineStart(IDocument document, int offset, boolean // testLastLine) // throws BadLocationException { // if (document == null) // return offset; // // if (testLastLine // && offset >= document.getLineInformationOfOffset( // document.getLength() - 1).getOffset()) // return offset; // // return document.getLineInformationOfOffset(offset).getOffset(); // } /* * @see org.eclipse.jface.text.TextViewer#setVisibleRegion(int, int) */ public void setVisibleRegion(int start, int length) { if (!isSegmented()) fWasInlineProjectionEnabled = isInlineProjectionMode(); disableProjection(); super.setVisibleRegion(start, length); } /* * @see org.eclipse.jface.text.TextViewer#setVisibleDocument(org.eclipse.jface.text.IDocument) */ protected void setVisibleDocument(IDocument document) { if (!isInlineProjectionMode()) { super.setVisibleDocument(document); return; } // In projection mode we don't want to throw away the find/replace // document adapter FindReplaceDocumentAdapter adapter = fFindReplaceDocumentAdapter; super.setVisibleDocument(document); fFindReplaceDocumentAdapter = adapter; } /* * @see org.eclipse.jface.text.TextViewer#resetVisibleRegion() */ public void resetVisibleRegion() { super.resetVisibleRegion(); if (fWasInlineProjectionEnabled) enableInlineProjection(); } /* * @see org.eclipse.jface.text.ITextViewer#getVisibleRegion() */ public IRegion getVisibleRegion() { disableProjection(); IRegion visibleRegion = getModelCoverage(); if (visibleRegion == null) visibleRegion = new Region(0, 0); return visibleRegion; } /* * @see org.eclipse.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int) */ public boolean overlapsWithVisibleRegion(int offset, int length) { disableProjection(); IRegion coverage = getModelCoverage(); if (coverage == null) return false; boolean appending = (offset == coverage.getOffset() + coverage.getLength()) && length == 0; return appending || TextUtilities.overlaps(coverage, new Region(offset, length)); } /** * Replace the visible document with the given document. Maintains the * scroll offset and the selection. * * @param slave * the visible document */ private void replaceVisibleDocument(IDocument slave) { if (fReplaceVisibleDocumentExecutionTrigger != null) { ReplaceVisibleDocumentExecutor executor = new ReplaceVisibleDocumentExecutor( slave); executor.install(fReplaceVisibleDocumentExecutionTrigger); } else executeReplaceVisibleDocument(slave); } private void executeReplaceVisibleDocument(IDocument visibleDocument) { StyledText textWidget = getTextWidget(); try { if (textWidget != null && !textWidget.isDisposed()) textWidget.setRedraw(false); int topIndex = getTopIndex(); Point selection = getSelectedRange(); setVisibleDocument(visibleDocument); Point currentSelection = getSelectedRange(); if (currentSelection.x != selection.x || currentSelection.y != selection.y) setSelectedRange(selection.x, selection.y); setTopIndex(topIndex); } finally { if (textWidget != null && !textWidget.isDisposed()) textWidget.setRedraw(true); } } /** * Hides the given range by collapsing it. If requested, a redraw request is * issued. * * @param offset * the offset of the range to hide * @param length * the length of the range to hide * @param fireRedraw * <code>true</code> if a redraw request should be issued, * <code>false</code> otherwise * @throws BadLocationException * in case the range is invalid */ private void collapse(int offset, int length, boolean fireRedraw) throws BadLocationException { ProjectionDocument projection = null; IDocument visibleDocument = getVisibleDocument(); if (visibleDocument instanceof ProjectionDocument) projection = (ProjectionDocument) visibleDocument; else { IDocument master = getDocument(); IDocument slave = createSlaveDocument(getDocument()); if (slave instanceof ProjectionDocument) { projection = (ProjectionDocument) slave; addMasterDocumentRange(projection, 0, master.getLength()); replaceVisibleDocument(projection); } } if (projection != null) removeMasterDocumentRange(projection, offset, length); if (projection != null && fireRedraw) { // repaint line above to get the folding box IDocument document = getDocument(); int line = document.getLineOfOffset(offset); if (line > 0) { IRegion info = document.getLineInformation(line - 1); internalInvalidateTextPresentation(info.getOffset(), info .getLength()); } } } /** * Makes the given range visible again while not changing the folding state * of any contained ranges. If requested, a redraw request is issued. * * @param offset * the offset of the range to be expanded * @param length * the length of the range to be expanded * @param fireRedraw * <code>true</code> if a redraw request should be issued, * <code>false</code> otherwise * @throws BadLocationException * in case the range is invalid */ private void expand(int offset, int length, boolean fireRedraw) throws BadLocationException { IDocument slave = getVisibleDocument(); if (slave instanceof ProjectionDocument) { ProjectionDocument projection = (ProjectionDocument) slave; // expand addMasterDocumentRange(projection, offset, length); // collapse contained regions InlineProjectionAnnotation[] collapsed = computeCollapsedNestedAnnotations( offset, length); if (collapsed != null) { for (int i = 0; i < collapsed.length; i++) { IRegion[] regions = computeCollapsedRegions(fInlineProjectionAnnotationModel .getPosition(collapsed[i])); if (regions != null) for (int j = 0; j < regions.length; j++) removeMasterDocumentRange(projection, regions[j] .getOffset(), regions[j].getLength()); } } // redraw if requested if (fireRedraw) internalInvalidateTextPresentation(offset, length); } } /** * Processes the request for catch up with the annotation model in the UI * thread. If the current thread is not the UI thread or there are pending * catch up requests, a new request is posted. * * @param event * the annotation model event */ protected final void processInlineCatchupRequest(AnnotationModelEvent event) { if (Display.getCurrent() != null) { boolean run = false; synchronized (fLock) { run = fPendingRequests.isEmpty(); } if (run) { try { catchupWithProjectionAnnotationModel(event); } catch (BadLocationException x) { throw new IllegalArgumentException(); } } else postInlineCatchupRequest(event); } else { postInlineCatchupRequest(event); } } /** * Posts the request for catch up with the annotation model into the UI * thread. * * @param event * the annotation model event */ protected final void postInlineCatchupRequest( final AnnotationModelEvent event) { synchronized (fLock) { fPendingRequests.add(event); if (fPendingRequests.size() == 1) { StyledText widget = getTextWidget(); if (widget != null) { Display display = widget.getDisplay(); if (display != null) { display.asyncExec(new Runnable() { public void run() { try { while (true) { AnnotationModelEvent ame = null; synchronized (fLock) { if (fPendingRequests.size() == 0) return; ame = (AnnotationModelEvent) fPendingRequests .remove(0); } catchupWithProjectionAnnotationModel(ame); } } catch (BadLocationException x) { try { catchupWithProjectionAnnotationModel(null); } catch (BadLocationException x1) { throw new IllegalArgumentException(); } finally { synchronized (fLock) { fPendingRequests.clear(); } } } } }); } } } } } /** * Tests whether the visible document's master document is identical to this * viewer's document. * * @return <code>true</code> if the visible document's master is identical * to this viewer's document * @since 3.1 */ private boolean isVisibleMasterDocumentSameAsDocument() { IDocument visibleDocument = getVisibleDocument(); return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument) visibleDocument).getMasterDocument() == getDocument(); } /** * Adapts the slave visual document of this viewer to the changes described * in the annotation model event. When the event is <code>null</code>, * this is identical to a world change event. * * @param event * the annotation model event or <code>null</code> * @exception BadLocationException * in case the annotation model event is no longer in * synchronization with the document */ private void catchupWithProjectionAnnotationModel(AnnotationModelEvent event) throws BadLocationException { if (event == null || !isVisibleMasterDocumentSameAsDocument()) { fPendingAnnotationWorldChange = false; reinitializeInlineProjection(); } else if (event.isWorldChange()) { if (event.isValid()) { fPendingAnnotationWorldChange = false; reinitializeInlineProjection(); } else fPendingAnnotationWorldChange = true; } else if (fPendingAnnotationWorldChange) { if (event.isValid()) { fPendingAnnotationWorldChange = false; reinitializeInlineProjection(); } } else { Annotation[] addedAnnotations = event.getAddedAnnotations(); Annotation[] changedAnnotation = event.getChangedAnnotations(); Annotation[] removedAnnotations = event.getRemovedAnnotations(); fCommandQueue = new ProjectionCommandQueue(); boolean isRedrawing = redraws(); int topIndex = isRedrawing ? getTopIndex() : -1; processDeletions(event, removedAnnotations, true); List coverage = new ArrayList(); processChanges(addedAnnotations, true, coverage); processChanges(changedAnnotation, true, coverage); ProjectionCommandQueue commandQueue = fCommandQueue; fCommandQueue = null; if (commandQueue.passedRedrawCostsThreshold()) { setRedraw(false); try { executeProjectionCommands(commandQueue, false); } catch (IllegalArgumentException x) { reinitializeInlineProjection(); } finally { setRedraw(true, topIndex); } } else { StyledText textWidget = getTextWidget(); try { if (isRedrawing && textWidget != null && !textWidget.isDisposed()) textWidget.setRedraw(false); boolean fireRedraw = !commandQueue .passedInvalidationCostsThreshold(); executeProjectionCommands(commandQueue, fireRedraw); if (!fireRedraw) invalidateTextPresentation(); } catch (IllegalArgumentException x) { reinitializeInlineProjection(); } finally { if (isRedrawing && textWidget != null && !textWidget.isDisposed()) textWidget.setRedraw(true); } } } } private void executeProjectionCommands(ProjectionCommandQueue commandQueue, boolean fireRedraw) throws BadLocationException { ProjectionCommand command; Iterator e = commandQueue.iterator(); while (e.hasNext()) { command = (ProjectionCommand) e.next(); switch (command.fType) { case ProjectionCommand.ADD: addMasterDocumentRange(command.fProjection, command.fOffset, command.fLength); break; case ProjectionCommand.REMOVE: removeMasterDocumentRange(command.fProjection, command.fOffset, command.fLength); break; case ProjectionCommand.INVALIDATE_PRESENTATION: if (fireRedraw) invalidateTextPresentation(command.fOffset, command.fLength); break; } } commandQueue.clear(); } private boolean covers(int offset, int length, Position position) { if (!(position.offset == offset && position.length == length) && !position.isDeleted()) return offset <= position.getOffset() && position.getOffset() + position.getLength() <= offset + length; return false; } private InlineProjectionAnnotation[] computeCollapsedNestedAnnotations( int offset, int length) { List annotations = new ArrayList(5); Iterator e = fInlineProjectionAnnotationModel.getAnnotationIterator(); while (e.hasNext()) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) e .next(); if (annotation.isCollapsed()) { Position position = fInlineProjectionAnnotationModel .getPosition(annotation); if (position == null) { // annotation might already be deleted, we will be informed // later on about this deletion continue; } if (covers(offset, length, position)) annotations.add(annotation); } } if (annotations.size() > 0) { InlineProjectionAnnotation[] result = new InlineProjectionAnnotation[annotations .size()]; annotations.toArray(result); return result; } return null; } private void internalInvalidateTextPresentation(int offset, int length) { if (fCommandQueue != null) { fCommandQueue.add(new ProjectionCommand(offset, length)); } else { invalidateTextPresentation(offset, length); } } /* * We pass the removed annotation into this method for performance reasons * only. Otherwise, they could be fetch from the event. */ private void processDeletions(AnnotationModelEvent event, Annotation[] removedAnnotations, boolean fireRedraw) throws BadLocationException { for (int i = 0; i < removedAnnotations.length; i++) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) removedAnnotations[i]; if (annotation.isCollapsed()) { Position expanded = event .getPositionOfRemovedAnnotation(annotation); expand(expanded.getOffset(), expanded.getLength(), fireRedraw); } } } /** * Computes the region that must be collapsed when the given position is the * position of an expanded projection annotation. * * @param position * the position * @return the range that must be collapsed */ public IRegion computeCollapsedRegion(Position position) { try { IDocument document = getDocument(); if (document == null) return null; int line = document.getLineOfOffset(position.getOffset()); int offset = document.getLineOffset(line + 1); int length = position.getLength() - (offset - position.getOffset()); if (length > 0) return new Region(offset, length); } catch (BadLocationException x) { } return null; } /** * Computes the regions that must be collapsed when the given position is * the position of an expanded projection annotation. * * @param position * the position * @return the ranges that must be collapsed, or <code>null</code> if * there are none * @since 3.1 */ IRegion[] computeCollapsedRegions(Position position) { try { IDocument document = getDocument(); if (document == null) return null; if (position instanceof IProjectionPosition) { IProjectionPosition projPosition = (IProjectionPosition) position; return projPosition.computeProjectionRegions(document); } return new IRegion[] { new Region(position.getOffset(), position .getLength()) }; // int line = document.getLineOfOffset(position.getOffset()); // int offset = document.getLineOffset(line + 1); // // int length = position.getLength() - (offset - // position.getOffset()); // if (length > 0) // return new IRegion[] { new Region(offset, length) }; // // return null; } catch (BadLocationException x) { return null; } } /** * Computes the collapsed region anchor for the given position. Assuming * that the position is the position of an expanded projection annotation, * the anchor is the region that is still visible after the projection * annotation has been collapsed. * * @param position * the position * @return the collapsed region anchor */ public Position computeCollapsedRegionAnchor(Position position) { try { IDocument document = getDocument(); if (document == null) return null; int captionOffset = position.getOffset(); if (position instanceof IProjectionPosition) captionOffset += ((IProjectionPosition) position) .computeCaptionOffset(document); IRegion lineInfo = document .getLineInformationOfOffset(captionOffset); return new Position(lineInfo.getOffset() + lineInfo.getLength(), 0); } catch (BadLocationException x) { } return null; } private void processChanges(Annotation[] annotations, boolean fireRedraw, List coverage) throws BadLocationException { for (int i = 0; i < annotations.length; i++) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) annotations[i]; Position position = fInlineProjectionAnnotationModel .getPosition(annotation); if (position == null) continue; if (!covers(coverage, position)) { if (annotation.isCollapsed()) { coverage.add(position); IRegion[] regions = computeCollapsedRegions(position); if (regions != null) for (int j = 0; j < regions.length; j++) collapse(regions[j].getOffset(), regions[j] .getLength(), fireRedraw); } else { expand(position.getOffset(), position.getLength(), fireRedraw); } } } } private boolean covers(List coverage, Position position) { Iterator e = coverage.iterator(); while (e.hasNext()) { Position p = (Position) e.next(); if (p.getOffset() <= position.getOffset() && position.getOffset() + position.getLength() <= p .getOffset() + p.getLength()) return true; } return false; } /** * Forces this viewer to throw away any old state and to initialize its * content from its projection annotation model. * * @throws BadLocationException * in case something goes wrong during initialization */ public final void reinitializeInlineProjection() throws BadLocationException { ProjectionDocument projection = null; ISlaveDocumentManager manager = getSlaveDocumentManager(); if (manager != null) { IDocument master = getDocument(); if (master != null) { IDocument slave = manager.createSlaveDocument(master); if (slave instanceof ProjectionDocument) { projection = (ProjectionDocument) slave; addMasterDocumentRange(projection, 0, master.getLength()); } } } if (projection != null) { Iterator e = fInlineProjectionAnnotationModel .getAnnotationIterator(); while (e.hasNext()) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) e .next(); if (annotation.isCollapsed()) { Position position = fInlineProjectionAnnotationModel .getPosition(annotation); if (position != null) { IRegion[] regions = computeCollapsedRegions(position); if (regions != null) for (int i = 0; i < regions.length; i++) removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i] .getLength()); } } } } replaceVisibleDocument(projection); } // /* // * @see // org.eclipse.jface.text.TextViewer#handleVerifyEvent(org.eclipse.swt.events.VerifyEvent) // */ // protected void handleVerifyEvent(VerifyEvent e) { // IRegion modelRange = event2ModelRange(e); // if (exposeModelRange(modelRange)) // e.doit = false; // else // super.handleVerifyEvent(e); // } // /** // * Adds the give column as last column to this viewer's vertical ruler. // * // * @param column // * the column to be added // */ // public void addVerticalRulerColumn(IVerticalRulerColumn column) { // IVerticalRuler ruler = getVerticalRuler(); // if (ruler instanceof CompositeRuler) { // CompositeRuler compositeRuler = (CompositeRuler) ruler; // compositeRuler.addDecorator(99, column); // } // } // // /** // * Removes the give column from this viewer's vertical ruler. // * // * @param column // * the column to be removed // */ // public void removeVerticalRulerColumn(IVerticalRulerColumn column) { // IVerticalRuler ruler = getVerticalRuler(); // if (ruler instanceof CompositeRuler) { // CompositeRuler compositeRuler = (CompositeRuler) ruler; // compositeRuler.removeDecorator(column); // } // } // // /* // * @see // org.eclipse.jface.text.ITextViewerExtension5#exposeModelRange(org.eclipse.jface.text.IRegion) // */ // public boolean exposeModelRange(IRegion modelRange) { // if (isInlineProjectionMode()) // return fInlineProjectionAnnotationModel.expandAll(modelRange.getOffset(), // modelRange.getLength()); // // if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange // .getLength())) { // resetVisibleRegion(); // return true; // } // // return false; // } // // /* // * @see org.eclipse.jface.text.source.SourceViewer#setRangeIndication(int, // * int, boolean) // */ // public void setRangeIndication(int offset, int length, boolean // moveCursor) { // // if (getRangeIndication() != null) { // List expand = new ArrayList(2); // if (moveCursor && fInlineProjectionAnnotationModel != null) { // // // expand the immediate effected collapsed regions // Iterator iterator = fInlineProjectionAnnotationModel // .getAnnotationIterator(); // while (iterator.hasNext()) { // InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) // iterator // .next(); // if (annotation.isCollapsed() // && willAutoExpand(fInlineProjectionAnnotationModel // .getPosition(annotation), offset, length)) // expand.add(annotation); // } // // if (!expand.isEmpty()) { // Iterator e = expand.iterator(); // while (e.hasNext()) // fInlineProjectionAnnotationModel // .expand((Annotation) e.next()); // } // } // } // // super.setRangeIndication(offset, length, moveCursor); // } // private boolean willAutoExpand(Position position, int offset, int length) // { // if (position == null || position.isDeleted()) // return false; // // right or left boundary // if (position.getOffset() == offset // || position.getOffset() + position.getLength() == offset // + length) // return true; // // completely embedded in given position // if (position.getOffset() < offset // && offset + length < position.getOffset() // + position.getLength()) // return true; // return false; // } /* * @see org.eclipse.jface.text.source.SourceViewer#handleDispose() * @since 3.0 */ protected void handleDispose() { fWasInlineProjectionEnabled = false; super.handleDispose(); } /* * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) */ protected void handleVisibleDocumentChanged(DocumentEvent event) { if (fHandleInlineProjectionChanges && event instanceof ProjectionDocumentEvent && isInlineProjectionMode()) { ProjectionDocumentEvent e = (ProjectionDocumentEvent) event; DocumentEvent master = e.getMasterEvent(); if (master != null) fReplaceVisibleDocumentExecutionTrigger = master.getDocument(); try { int replaceLength = e.getText() == null ? 0 : e.getText() .length(); if (ProjectionDocumentEvent.PROJECTION_CHANGE == e .getChangeType()) { if (e.getLength() == 0 && replaceLength != 0) fInlineProjectionAnnotationModel.expandAll(e .getMasterOffset(), e.getMasterLength()); } else if (master != null && (replaceLength > 0 || fDeletedLines > 1)) { try { int numberOfLines = e.getDocument().getNumberOfLines( e.getOffset(), replaceLength); if (numberOfLines > 1 || fDeletedLines > 1) fInlineProjectionAnnotationModel.expandAll(master .getOffset(), master.getLength()); } catch (BadLocationException x) { } } } finally { fReplaceVisibleDocumentExecutionTrigger = null; } } } /* * @see org.eclipse.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) * @since 3.1 */ protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) { if (fHandleInlineProjectionChanges && event instanceof ProjectionDocumentEvent && isInlineProjectionMode()) { int deletedLines; try { deletedLines = event.getDocument().getNumberOfLines( event.getOffset(), event.getLength()); } catch (BadLocationException e1) { deletedLines = 0; } fDeletedLines = deletedLines; } } /* * @see org.eclipse.jface.text.ITextViewerExtension5#getCoveredModelRanges(org.eclipse.jface.text.IRegion) */ public IRegion[] getCoveredModelRanges(IRegion modelRange) { if (fInformationMapping == null) return new IRegion[] { new Region(modelRange.getOffset(), modelRange.getLength()) }; if (fInformationMapping instanceof IDocumentInformationMappingExtension) { IDocumentInformationMappingExtension extension = (IDocumentInformationMappingExtension) fInformationMapping; try { return extension.getExactCoverage(modelRange); } catch (BadLocationException x) { } } return null; } /* * @see org.eclipse.jface.text.ITextOperationTarget#doOperation(int) */ public void doOperation(int operation) { switch (operation) { case TOGGLE: if (canDoOperation(TOGGLE)) { if (!isInlineProjectionMode()) { enableInlineProjection(); } else { expandAll(); disableInlineProjection(); } return; } } if (!isInlineProjectionMode()) { super.doOperation(operation); return; } StyledText textWidget = getTextWidget(); if (textWidget == null) return; Point selection = null; switch (operation) { case CUT: if (redraws()) { selection = getSelectedRange(); if (selection.y == 0) copyMarkedRegion(true); else copyToClipboard(selection.x, selection.y, true, textWidget); selection = textWidget.getSelectionRange(); fireSelectionChanged(selection.x, selection.y); } break; case COPY: if (redraws()) { selection = getSelectedRange(); if (selection.y == 0) copyMarkedRegion(false); else copyToClipboard(selection.x, selection.y, false, textWidget); } break; case DELETE: if (redraws()) { try { selection = getSelectedRange(); Point widgetSelection = textWidget.getSelectionRange(); if (selection.y == 0 || selection.y == widgetSelection.y) getTextWidget().invokeAction(ST.DELETE_NEXT); else deleteTextRange(selection.x, selection.y, textWidget); selection = textWidget.getSelectionRange(); fireSelectionChanged(selection.x, selection.y); } catch (BadLocationException x) { // ignore } } break; case EXPAND_ALL: if (redraws()) expandAll(); break; case EXPAND: if (redraws()) { expand(); } break; case COLLAPSE_ALL: if (redraws()) collapseAll(); break; case COLLAPSE: if (redraws()) { collapse(); } break; default: super.doOperation(operation); } } /* * @see org.eclipse.jface.text.source.SourceViewer#canDoOperation(int) */ public boolean canDoOperation(int operation) { switch (operation) { case COLLAPSE: case COLLAPSE_ALL: case EXPAND: case EXPAND_ALL: return isInlineProjectionMode(); case TOGGLE: return isInlineProjectionMode() || !isSegmented(); } return super.canDoOperation(operation); } private boolean isSegmented() { IDocument document = getDocument(); int length = document == null ? 0 : document.getLength(); IRegion visible = getModelCoverage(); boolean isSegmented = visible != null && !visible.equals(new Region(0, length)); return isSegmented; } private IRegion getMarkedRegion() { if (getTextWidget() == null) return null; if (fMarkPosition == null || fMarkPosition.isDeleted()) return null; int start = fMarkPosition.getOffset(); int end = getSelectedRange().x; return start > end ? new Region(end, start - end) : new Region(start, end - start); } /* * @see org.eclipse.jface.text.TextViewer#copyMarkedRegion(boolean) */ protected void copyMarkedRegion(boolean delete) { IRegion markedRegion = getMarkedRegion(); if (markedRegion != null) copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget()); } private void copyToClipboard(int offset, int length, boolean delete, StyledText textWidget) { String copyText = null; try { IDocument document = getDocument(); copyText = document.get(offset, length); } catch (BadLocationException ex) { // XXX: should log here, but JFace Text has no Log // As a fallback solution let the widget handle this textWidget.copy(); } if (copyText != null && copyText.equals(textWidget.getSelectionText())) { /* * XXX: Reduce pain of * https://bugs.eclipse.org/bugs/show_bug.cgi?id=64498 by letting * the widget handle the copy operation in this special case. */ textWidget.copy(); } else if (copyText != null) { Clipboard clipboard = new Clipboard(textWidget.getDisplay()); try { Transfer[] dataTypes = new Transfer[] { TextTransfer .getInstance() }; Object[] data = new Object[] { copyText }; try { clipboard.setContents(data, dataTypes); } catch (SWTError e) { if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD) throw e; /* * TODO see * https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459 we * should either log and/or inform the user silently fail * for now. */ return; } } finally { clipboard.dispose(); } } if (delete) { try { deleteTextRange(offset, length, textWidget); } catch (BadLocationException x) { // XXX: should log here, but JFace Text has no Log } } } private void deleteTextRange(int offset, int length, StyledText textWidget) throws BadLocationException { getDocument().replace(offset, length, null); int widgetCaret = modelOffset2WidgetOffset(offset); if (widgetCaret > -1) textWidget.setSelection(widgetCaret); } // @Override // public IRegion modelRange2WidgetRange(IRegion widgetRange) { // // TODO Auto-generated method stub // IRegion result = super.modelRange2WidgetRange(widgetRange); // System.out.println(widgetRange+" -> "+result); // return result; // } /** * Adapts the behavior of the super class to respect line based folding. * * @param widgetSelection * the widget selection * @return the model selection while respecting line based folding */ protected Point widgetSelection2ModelSelection(Point widgetSelection) { if (!isInlineProjectionMode()) return super.widgetSelection2ModelSelection(widgetSelection); /* * There is one requirement that governs preservation of logical * positions: * * 1) a selection with widget_length == 0 should never expand to have * model_length > 0. * * There are a number of ambiguities to resolve with projection regions. * A projected region P has a widget-length of zero. Its widget offset * may interact with the selection S in various ways: * * A) P.widget_offset lies at the caret, S.widget_length is zero. * Requirement 1 applies. S is *behind* P (done so by * widgetRange2ModelRange). * * B) P.widget_offset lies inside the widget selection. This case is * easy: P is included in S, which is automatically done so by * widgetRange2ModelRange. * * C) P.widget_offset lies at S.widget_end: This is arguable - our * policy is to include P if it belongs to a projection annotation that * overlaps with the widget selection. * * D) P.widget_offset lies at S.widget_offset: Arguable - our policy is * to include P if it belongs to a projection annotation that overlaps * with the widget selection */ IRegion modelSelection = widgetRange2ModelRange(new Region( widgetSelection.x, widgetSelection.y)); if (modelSelection == null) return null; int modelOffset = modelSelection.getOffset(); int modelEndOffset = modelOffset + modelSelection.getLength(); /* Case A: never expand a zero-length selection. S is *behind* P. */ if (widgetSelection.y == 0) return new Point(modelEndOffset, 0); int widgetSelectionExclusiveEnd = widgetSelection.x + widgetSelection.y; Position[] annotationPositions = computeOverlappingAnnotationPositions(modelSelection); for (int i = 0; i < annotationPositions.length; i++) { IRegion[] regions = computeCollapsedRegions(annotationPositions[i]); if (regions == null) continue; for (int j = 0; j < regions.length; j++) { IRegion modelRange = regions[j]; IRegion widgetRange = modelRange2ClosestWidgetRange(modelRange); // only take collapsed ranges, i.e. widget length is 0 if (widgetRange != null && widgetRange.getLength() == 0) { int widgetOffset = widgetRange.getOffset(); // D) region is collapsed at S.widget_offset if (widgetOffset == widgetSelection.x) modelOffset = Math.min(modelOffset, modelRange .getOffset()); // C) region is collapsed at S.widget_end else if (widgetOffset == widgetSelectionExclusiveEnd) modelEndOffset = Math.max(modelEndOffset, modelRange .getOffset() + modelRange.getLength()); } } } return new Point(modelOffset, modelEndOffset - modelOffset); } /** * Returns the positions of all annotations that intersect with * <code>modelSelection</code> and that are at least partly visible. * * @param modelSelection * a model range * @return the positions of all annotations that intersect with * <code>modelSelection</code> * @since 3.1 */ private Position[] computeOverlappingAnnotationPositions( IRegion modelSelection) { List positions = new ArrayList(); for (Iterator e = fInlineProjectionAnnotationModel .getAnnotationIterator(); e.hasNext();) { InlineProjectionAnnotation annotation = (InlineProjectionAnnotation) e .next(); Position position = fInlineProjectionAnnotationModel .getPosition(annotation); if (position != null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) != null) positions.add(position); } return (Position[]) positions.toArray(new Position[positions.size()]); } /* * @see org.eclipse.jface.text.TextViewer#getFindReplaceDocumentAdapter() */ protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() { if (fFindReplaceDocumentAdapter == null) { IDocument document = isInlineProjectionMode() ? getDocument() : getVisibleDocument(); fFindReplaceDocumentAdapter = new FindReplaceDocumentAdapter( document); } return fFindReplaceDocumentAdapter; } /* * @see org.eclipse.jface.text.TextViewer#findAndSelect(int, * java.lang.String, boolean, boolean, boolean, boolean) */ protected int findAndSelect(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { if (!isInlineProjectionMode()) return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); StyledText textWidget = getTextWidget(); if (textWidget == null) return -1; try { IRegion matchRegion = getFindReplaceDocumentAdapter().find( startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); if (matchRegion != null) { exposeModelRange(matchRegion); revealRange(matchRegion.getOffset(), matchRegion.getLength()); setSelectedRange(matchRegion.getOffset(), matchRegion .getLength()); return matchRegion.getOffset(); } } catch (BadLocationException x) { } return -1; } /* * @see org.eclipse.jface.text.TextViewer#findAndSelectInRange(int, * java.lang.String, boolean, boolean, boolean, int, int, boolean) */ protected int findAndSelectInRange(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, int rangeOffset, int rangeLength, boolean regExSearch) { if (!isInlineProjectionMode()) return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch); StyledText textWidget = getTextWidget(); if (textWidget == null) return -1; try { int modelOffset = startPosition; if (forwardSearch && (startPosition == -1 || startPosition < rangeOffset)) { modelOffset = rangeOffset; } else if (!forwardSearch && (startPosition == -1 || startPosition > rangeOffset + rangeLength)) { modelOffset = rangeOffset + rangeLength; } IRegion matchRegion = getFindReplaceDocumentAdapter().find( modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); if (matchRegion != null) { int offset = matchRegion.getOffset(); int length = matchRegion.getLength(); if (rangeOffset <= offset && offset + length <= rangeOffset + rangeLength) { exposeModelRange(matchRegion); revealRange(offset, length); setSelectedRange(offset, length); return offset; } } } catch (BadLocationException x) { } return -1; } // /** // * disable classic projection // */ // public ProjectionAnnotationModel getProjectionAnnotationModel() { // return null; // } private List fInlineProjectionListeners; /** * Notifies all registered projection listeners that projection mode has * been enabled. */ protected void fireInlineProjectionEnabled() { if (fInlineProjectionListeners != null) { Iterator e = new ArrayList(fInlineProjectionListeners).iterator(); while (e.hasNext()) { IInlineProjectionListener l = (IInlineProjectionListener) e .next(); l.inlineProjectionEnabled(); } } } /** * Notifies all registered projection listeners that projection mode has * been disabled. */ protected void fireInlineProjectionDisabled() { if (fInlineProjectionListeners != null) { Iterator e = new ArrayList(fInlineProjectionListeners).iterator(); while (e.hasNext()) { IInlineProjectionListener l = (IInlineProjectionListener) e .next(); l.inlineProjectionDisabled(); } } } /** * Adds a projection annotation listener to this viewer. The listener may * not be <code>null</code>. If the listener is already registered, this * method does not have any effect. * * @param listener * the listener to add */ public void addProjectionListener(IProjectionListener listener) { super.addProjectionListener(listener); if (listener instanceof IInlineProjectionListener) { if (fInlineProjectionListeners == null) fInlineProjectionListeners = new ArrayList(); if (!fInlineProjectionListeners.contains(listener)) fInlineProjectionListeners.add(listener); } } /** * Removes the given listener from this viewer. The listener may not be * <code>null</code>. If the listener is not registered with this viewer, * this method is without effect. * * @param listener * the listener to remove */ public void removeProjectionListener(IProjectionListener listener) { super.removeProjectionListener(listener); if (listener instanceof IInlineProjectionListener) { if (fInlineProjectionListeners != null) { fInlineProjectionListeners.remove(listener); if (fInlineProjectionListeners.size() == 0) fInlineProjectionListeners = null; } } } }