blob: 1a84d45f6ff6e67b9c92b68d77b5ae5841339ccf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.caseditor.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.caseditor.CasEditorPlugin;
import org.apache.uima.caseditor.editor.action.DeleteFeatureStructureAction;
import org.apache.uima.caseditor.editor.annotation.DrawingStyle;
import org.apache.uima.caseditor.editor.annotation.EclipseAnnotationPeer;
import org.apache.uima.caseditor.editor.context.AnnotationEditingControlCreator;
import org.apache.uima.caseditor.editor.contextmenu.IModeMenuListener;
import org.apache.uima.caseditor.editor.contextmenu.IShowAnnotationsListener;
import org.apache.uima.caseditor.editor.contextmenu.ModeMenu;
import org.apache.uima.caseditor.editor.contextmenu.ShowAnnotationsMenu;
import org.apache.uima.caseditor.editor.outline.AnnotationOutline;
import org.apache.uima.caseditor.editor.util.AnnotationComparator;
import org.apache.uima.caseditor.editor.util.AnnotationSelection;
import org.apache.uima.caseditor.editor.util.FeatureStructureTransfer;
import org.apache.uima.caseditor.editor.util.Span;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.text.IPainter;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.information.InformationPresenter;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IAnnotationAccessExtension;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.IStatusField;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;
import org.eclipse.ui.texteditor.StatusTextEditor;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
* An editor to annotate text.
*
* TODO:
* add an action to increase left side of an annotation
* add an action to increase right side of an annotation
* add an action to decrease left side on an annotation
* add an action to decrease right side on an annotation
*/
public final class AnnotationEditor extends StatusTextEditor implements ICasEditor, ISelectionListener {
/**
* This action annotates the selected text with a defined tag.
*/
private class AnnotateAction extends Action {
private StyledText mTextWidget;
/**
* Initializes a new instance.
*
* @param textWidget
*/
AnnotateAction(StyledText textWidget) {
mTextWidget = textWidget;
}
/**
* Executes this action, adds an annotation of the marked span.
*/
@Override
public void run() {
if (isSomethingSelected()) {
Point selection = mTextWidget.getSelectionRange();
// get old annotations of current type for this area
// if there is something ... the delete them and add
Collection<AnnotationFS> oldAnnotations = getDocument().getAnnotation(
getAnnotationMode(), new Span(selection.x, selection.y));
if (!oldAnnotations.isEmpty()) {
getDocument().removeFeatureStructures(oldAnnotations);
}
int start = selection.x;
int end = start + selection.y;
AnnotationFS annotation = getDocument().getCAS().createAnnotation(getAnnotationMode(),
start, end);
getDocument().addFeatureStructure(annotation);
setAnnotationSelection(annotation);
}
}
ICasDocument getDocument() {
return AnnotationEditor.this.getDocument();
}
}
private class SmartAnnotateAction extends Action {
@Override
public void run() {
if (isSomethingSelected()) {
QuickTypeSelectionDialog typeDialog =
new QuickTypeSelectionDialog(Display.getCurrent().getActiveShell(),
AnnotationEditor.this);
typeDialog.open();
}
}
}
/**
* Shows the annotation editing context for the active annotation.
*/
private class ShowAnnotationContextEditAction extends Action {
private InformationPresenter mPresenter;
/**
* Initializes a new instance.
*/
ShowAnnotationContextEditAction() {
mPresenter = new InformationPresenter(new AnnotationEditingControlCreator());
mPresenter.setInformationProvider(new AnnotationInformationProvider(AnnotationEditor.this),
org.eclipse.jface.text.IDocument.DEFAULT_CONTENT_TYPE);
mPresenter.setDocumentPartitioning(org.eclipse.jface.text.IDocument.DEFAULT_CONTENT_TYPE);
mPresenter.install(getSourceViewer());
}
/**
* Executes this action, shows context information.
*/
@Override
public void run() {
mPresenter.showInformation();
// the information presenter closes ... if
// the subject control sends a keyDown event
// this action is triggered with
// a keyEvent send by the subject control
// inside this handler the presenter gets installed on
// the subject control during that the presenter registers
// a key listener on the subject control
// the presenter closes now because the keyEvent to trigger
// this action is also sent to the presenter
// to avoid this behavior this action is reposted
// to the END of the ui thread queue
// this does not happen if for the action is no key explicit
// configured with setActivationKey(...)
// TODO:
// This does not work for mac or linux
}
}
/**
* This <code>IDocumentChangeListener</code> is responsible to synchronize annotation in the
* document with the annotations in eclipse.
*/
private class DocumentListener extends AbstractAnnotationDocumentListener {
/**
* Adds a collection of annotations.
*
* @param annotations
*/
@Override
public void addedAnnotation(Collection<AnnotationFS> annotations) {
IAnnotationModelExtension annotationModel = (IAnnotationModelExtension) getDocumentProvider().getAnnotationModel(getEditorInput());
Map<Annotation, Position> addAnnotationMap = new HashMap<Annotation, Position>();
for (AnnotationFS annotation : annotations) {
addAnnotationMap.put(new EclipseAnnotationPeer(annotation), new Position(annotation.getBegin(),
annotation.getEnd() - annotation.getBegin()));
}
annotationModel.replaceAnnotations(null, addAnnotationMap);
}
/**
* Removes a collection of annotations.
*
* @param deletedAnnotations
*/
@Override
public void removedAnnotation(Collection<AnnotationFS> deletedAnnotations) {
if (getSite().getPage().getActivePart() == AnnotationEditor.this) {
mFeatureStructureSelectionProvider.clearSelection();
} else {
mFeatureStructureSelectionProvider.clearSelectionSilently();
}
highlight(0, 0); // TODO: only if removed annotation was selected
IAnnotationModelExtension annotationModel = (IAnnotationModelExtension) getDocumentProvider().getAnnotationModel(getEditorInput());
Annotation removeAnnotations[] = new Annotation[deletedAnnotations.size()];
int removeAnnotationsIndex = 0;
for (AnnotationFS annotation : deletedAnnotations) {
removeAnnotations[removeAnnotationsIndex++] = new EclipseAnnotationPeer(annotation);
}
annotationModel.replaceAnnotations(removeAnnotations, null);
}
/**
*
* @param annotations
*/
@Override
public void updatedAnnotation(Collection<AnnotationFS> annotations) {
removedAnnotation(annotations);
addedAnnotation(annotations);
List<ModelFeatureStructure> structures = new LinkedList<ModelFeatureStructure>();
for (AnnotationFS annotation : annotations) {
structures.add(new ModelFeatureStructure(getDocument(), annotation));
}
selectionChanged(getSite().getPage().getActivePart(), new StructuredSelection(structures));
}
public void changed() {
mFeatureStructureSelectionProvider.clearSelection();
syncAnnotations();
}
}
/**
* Sometimes the wrong annotation is selected ... ????
*/
private class FeatureStructureDragListener implements DragSourceListener {
private boolean mIsActive;
private AnnotationFS mCandidate;
FeatureStructureDragListener(final StyledText textWidget) {
textWidget.addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ALT) {
mIsActive = true;
textWidget.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND));
}
}
public void keyReleased(KeyEvent e) {
if (e.stateMask == SWT.ALT) {
mIsActive = false;
textWidget.setCursor(null);
}
}
});
textWidget.addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
if (mIsActive) {
// try to get the position inside the text
int offset;
// Workaround: It is possible check that is text under the cursor ?
try {
offset = textWidget.getOffsetAtLocation(new Point(e.x, e.y));
}
catch (IllegalArgumentException e2) {
return;
}
Map<Integer, AnnotationFS> view = getDocument().getView(getAnnotationMode());
mCandidate = view.get(offset);
if (mCandidate != null) {
textWidget.setSelectionRange(mCandidate.getBegin(), mCandidate.getEnd()
- mCandidate.getBegin());
}
}
}
});
}
public void dragStart(DragSourceEvent event) {
if (mIsActive) {
event.doit = mCandidate != null;
} else {
event.doit = false;
}
}
public void dragSetData(DragSourceEvent event) {
event.data = mCandidate;
}
public void dragFinished(DragSourceEvent event) {
}
}
private class AnnotationAccess implements IAnnotationAccess, IAnnotationAccessExtension {
public Object getType(Annotation annotation) {
return null;
}
public boolean isMultiLine(Annotation annotation) {
return false;
}
public boolean isTemporary(Annotation annotation) {
return false;
}
public int getLayer(Annotation annotation) {
if (annotation instanceof EclipseAnnotationPeer) {
EclipseAnnotationPeer eclipseAnnotation = (EclipseAnnotationPeer) annotation;
// ask document provider for this
// getAnnotation must be added to cas document provider
AnnotationStyle style = getDocumentProvider().getAnnotationStyle(getEditorInput(),
eclipseAnnotation.getAnnotationFS().getType());
return style.getLayer();
}
else {
return 0;
}
}
public Object[] getSupertypes(Object annotationType) {
return new Object[0];
}
public String getTypeLabel(Annotation annotation) {
return null;
}
public boolean isPaintable(Annotation annotation) {
assert false : "Should never be called";
return false;
}
/**
* This implementation imitates the behavior without the
* {@link IAnnotationAccessExtension}.
*/
public boolean isSubtype(Object annotationType, Object potentialSupertype) {
Type type = getDocument().getCAS().getTypeSystem().getType((String) annotationType);
return mShowAnnotationsMenu.getSelectedTypes().contains(type) ||
getAnnotationMode().equals(type);
}
public void paint(Annotation annotation, GC gc, Canvas canvas, Rectangle bounds) {
assert false : "Should never be called";
}
}
/**
* Listens for resource remove/delete event, if the input file for the
* editor is removed the editor will be closed.
*/
private static class CloseEditorListener implements IResourceChangeListener {
private AnnotationEditor editor;
public CloseEditorListener(AnnotationEditor editor) {
this.editor = editor;
}
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta = event.getDelta();
try {
IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) throws CoreException {
if (delta.getFlags() != IResourceDelta.MARKERS
&& delta.getResource().getType() == IResource.FILE) {
if (delta.getKind() == IResourceDelta.REMOVED) {
IResource resource = delta.getResource();
IEditorInput input = editor.getEditorInput();
if (input instanceof FileEditorInput) {
FileEditorInput fileInput = (FileEditorInput) input;
if (resource.equals(fileInput.getFile())) {
editor.close(false);
}
}
}
}
return true;
}
};
delta.accept(visitor);
} catch (CoreException e) {
CasEditorPlugin.log(e);
}
}
}
private Type mAnnotationMode;
/**
* The outline page belonging to this editor.
*/
private IContentOutlinePage mOutlinePage;
private IAnnotationEditorModifyListener mEditorListener;
/**
* TODO: Do we really need this position variable ?
*/
private int mCursorPosition;
private ICasDocument mDocument;
boolean mIsSomethingHighlighted = false;
private StyleRange mCurrentStyleRange;
private FeatureStructureSelectionProvider mFeatureStructureSelectionProvider;
private AnnotationPainter mPainter;
private ShowAnnotationsMenu mShowAnnotationsMenu;
private DocumentListener mAnnotationSynchronizer;
private CloseEditorListener closeEditorListener;
private Collection<Type> shownAnnotationTypes = new HashSet<Type>();
/**
* Creates an new AnnotationEditor object.
*/
public AnnotationEditor() {
CasDocumentProvider provider =
CasDocumentProviderFactory.instance().getDocumentProvider();
setDocumentProvider(provider);
}
@Override
public CasDocumentProvider getDocumentProvider() {
return (CasDocumentProvider) super.getDocumentProvider();
}
/**
* Retrieves annotation editor adapters.
*
* @param adapter
* @return an adapter or null
*/
@Override
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
if (IContentOutlinePage.class.equals(adapter) && getDocument() != null) {
if (mOutlinePage == null) {
mOutlinePage = new AnnotationOutline(this);
}
return mOutlinePage;
}
else if (CAS.class.equals(adapter) && getDocument() != null) {
return getDocument().getCAS();
}
else {
return super.getAdapter(adapter);
}
}
@Override
protected ISourceViewer createSourceViewer(Composite parent,
org.eclipse.jface.text.source.IVerticalRuler ruler, int styles) {
SourceViewer sourceViewer = new SourceViewer(parent, ruler, styles);
sourceViewer.setEditable(false);
mPainter = new AnnotationPainter(sourceViewer, new AnnotationAccess());
sourceViewer.addPainter(mPainter);
return sourceViewer;
}
/**
* Configures the editor.
*
* @param parent
*/
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
/*
* this is a workaround for the quickdiff assertion if nothing was changed, how to do this
* better ? is this the right way ?
*/
showChangeInformation(false);
getSourceViewer().getTextWidget().addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
int newCaretOffset = getSourceViewer().getTextWidget().getCaretOffset();
if (newCaretOffset != mCursorPosition) {
mCursorPosition = newCaretOffset;
cursorPositionChanged();
}
}
public void keyReleased(KeyEvent e) {
// not implemented
}
});
getSourceViewer().getTextWidget().addMouseListener(new MouseListener() {
public void mouseDown(MouseEvent e) {
int newCaretOffset = getSourceViewer().getTextWidget().getCaretOffset();
if (newCaretOffset != mCursorPosition) {
mCursorPosition = newCaretOffset;
cursorPositionChanged();
}
}
public void mouseDoubleClick(MouseEvent e) {
// not needed
}
public void mouseUp(MouseEvent e) {
// not needed
}
});
DragSource dragSource = new DragSource(getSourceViewer().getTextWidget(), DND.DROP_COPY);
Transfer[] types = new Transfer[] { FeatureStructureTransfer.getInstance() };
dragSource.setTransfer(types);
dragSource.addDragListener(new FeatureStructureDragListener(getSourceViewer().getTextWidget()));
getSite().getPage().addSelectionListener(this);
getSourceViewer().getTextWidget().setEditable(false);
getSourceViewer().setEditable(false);
getSite().setSelectionProvider(mFeatureStructureSelectionProvider);
if (getDocument() != null) {
mShowAnnotationsMenu = new ShowAnnotationsMenu(
getDocumentProvider().getEditorAnnotationStatus(getEditorInput()),
getDocument().getCAS().getTypeSystem(), shownAnnotationTypes);
mShowAnnotationsMenu.addListener(new IShowAnnotationsListener() {
public void selectionChanged(Collection<Type> selection) {
// if changes selection is either larger, or smaller
// if larger an annotation type was added
// if smaller one was removed
if (shownAnnotationTypes.size() < selection.size()) {
List<Type> clonedCollection = new ArrayList<Type>(selection);
clonedCollection.removeAll(shownAnnotationTypes);
Type addedAnnotationType = clonedCollection.get(0);
showAnnotationType(addedAnnotationType, true);
getDocumentProvider().addShownType(getEditorInput(), addedAnnotationType);
}
else if (selection.size() < shownAnnotationTypes.size()) {
List<Type> clonedCollection = new ArrayList<Type>(shownAnnotationTypes);
clonedCollection.removeAll(selection);
Type removedAnnotationType = clonedCollection.get(0);
showAnnotationType(removedAnnotationType, false);
getDocumentProvider().removeShownType(getEditorInput(), removedAnnotationType);
}
// Repaint after annotations are changed
mPainter.paint(IPainter.CONFIGURATION);
EditorAnnotationStatus status =
getDocumentProvider().getEditorAnnotationStatus(getEditorInput());
getDocumentProvider().setEditorAnnotationStatus(getEditorInput(),
new EditorAnnotationStatus(status.getMode(), selection));
if (mEditorListener != null)
mEditorListener.showAnnotationsChanged(selection);
}
});
EditorAnnotationStatus status = getDocumentProvider()
.getEditorAnnotationStatus(getEditorInput());
setAnnotationMode(getDocument().getType(status.getMode()));
}
}
// TODO: still not called always, e.g. on mouse selection
private void cursorPositionChanged() {
mFeatureStructureSelectionProvider.setSelection(getDocument(), getSelectedAnnotations());
}
/**
* Checks if the current instance is editable.
*
* @return false
*/
@Override
public boolean isEditable() {
return false;
}
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
super.doSetInput(input);
mDocument = (ICasDocument) getDocumentProvider().getDocument(input);
if (mDocument != null) {
closeEditorListener = new CloseEditorListener(this);
ResourcesPlugin.getWorkspace().addResourceChangeListener(closeEditorListener,
IResourceChangeEvent.POST_CHANGE);
syncAnnotations();
mAnnotationSynchronizer = new DocumentListener();
getDocument().addChangeListener(mAnnotationSynchronizer);
Collection<String> shownTypes = getDocumentProvider().getShownTypes(input);
for (String shownType : shownTypes) {
// Types can be deleted from the type system but still be marked
// as shown in the .dotCorpus file, in that case the type
// name cannot be mapped to a type and should be ignored.
Type type = getDocument().getType(shownType);
if (type != null)
shownAnnotationTypes.add(type);
}
}
}
@Override
protected void editorContextMenuAboutToShow(IMenuManager menu) {
super.editorContextMenuAboutToShow(menu);
TypeSystem typeSytem = getDocument().getCAS().getTypeSystem();
// mode menu
MenuManager modeMenuManager = new MenuManager("Mode");
menu.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, modeMenuManager);
ModeMenu modeMenu = new ModeMenu(typeSytem);
modeMenu.addListener(new IModeMenuListener(){
public void modeChanged(Type newMode) {
IAction actionToExecute = new ChangeModeAction(newMode, newMode.getShortName(),
AnnotationEditor.this);
actionToExecute.run();
}});
modeMenuManager.add(modeMenu);
// annotation menu
MenuManager showAnnotationMenu = new MenuManager("Show Annotations");
menu.appendToGroup(IWorkbenchActionConstants.MB_ADDITIONS, showAnnotationMenu);
showAnnotationMenu.add(mShowAnnotationsMenu);
}
/**
* Updates the status line.
*/
private void updateStatusLineModeItem() {
// TODO: refactore this
IStatusField statusField = getStatusField(AnnotationEditorActionContributor.ID);
// can be null directly after doSetInput()
if (statusField != null) {
statusField.setText(getAnnotationMode().getShortName());
}
}
/**
* Returns the current <code>AnnotationDocument</code> of this editor.
*
* @return current <code>AnnotationDocument</code>
*/
public ICasDocument getDocument() {
return mDocument;
}
/**
* Returns the current annotation type.
*
* @return - current annotation type
*/
public Type getAnnotationMode() {
return mAnnotationMode;
}
/**
* Sets the new annotation type.
*
* @param type
*/
protected void setAnnotationMode(Type type) {
// TODO: check if this type is a subtype of Annotation
mAnnotationMode = type;
highlight(0, 0);
setProjectEditorStatus();
updateStatusLineModeItem();
syncAnnotationTypes();
fireAnnotationTypeChanged(getAnnotationMode());
mShowAnnotationsMenu.setEditorAnnotationMode(type);
}
public Collection<Type> getShownAnnotationTypes() {
return mShowAnnotationsMenu.getSelectedTypes();
}
/**
* @param type
*/
private void fireAnnotationTypeChanged(Type type) {
if (mEditorListener != null) {
mEditorListener.annotationModeChanged(type);
}
}
/**
* Set the shown annotation status of a type.
*
* @param type
* @param isVisible if true the type is shown, if false the type
* it not shown
*/
private void showAnnotationType(Type type, boolean isVisible) {
if (isVisible) {
AnnotationStyle style = getDocumentProvider().getAnnotationStyle(getEditorInput(), type);
mPainter.addDrawingStrategy(type.getName(),
DrawingStyle.valueOf(style.getStyle().name()).getStrategy());
mPainter.addAnnotationType(type.getName(), type.getName());
java.awt.Color color = style.getColor();
mPainter.setAnnotationTypeColor(type.getName(), new Color(null, color.getRed(),
color.getGreen(), color.getBlue()));
shownAnnotationTypes.add(type);
}
else {
mPainter.removeAnnotationType(type.getName());
shownAnnotationTypes.remove(type);
}
}
/**
* Synchronizes all annotations which the eclipse annotation painter.
*/
public void syncAnnotationTypes() {
mPainter.removeAllAnnotationTypes();
for (Type displayType : mShowAnnotationsMenu.getSelectedTypes()) {
showAnnotationType(displayType, true);
}
if (!mShowAnnotationsMenu.getSelectedTypes().contains(getAnnotationMode())) {
showAnnotationType(getAnnotationMode(), true);
}
mPainter.paint(IPainter.CONFIGURATION);
}
private void syncAnnotations() {
// Remove all annotation from the model
IAnnotationModel annotationModel = getDocumentProvider().getAnnotationModel(getEditorInput());
((IAnnotationModelExtension) annotationModel).removeAllAnnotations();
// Add all annotation to the model
// copy annotations into annotation model
final Iterator<AnnotationFS> mAnnotations = mDocument.getCAS().getAnnotationIndex()
.iterator();
while (mAnnotations.hasNext()) {
AnnotationFS annotationFS = mAnnotations.next();
annotationModel.addAnnotation(new EclipseAnnotationPeer(annotationFS), new Position(
annotationFS.getBegin(), annotationFS.getEnd() - annotationFS.getBegin()));
}
}
/**
* @param listener
*/
public void addAnnotationListener(IAnnotationEditorModifyListener listener) {
mEditorListener = listener;
}
/**
* Returns the selection.
*
* @return - the selection
*/
public Point getSelection() {
return getSourceViewer().getTextWidget().getSelection();
}
/**
* Highlights the given range in the editor.
*
* @param start
* @param length
*/
private void highlight(int start, int length) {
ISourceViewer sourceViewer = getSourceViewer();
assert sourceViewer != null;
StyledText text = sourceViewer.getTextWidget();
if (mCurrentStyleRange != null) {
// reset current style range
StyleRange resetedStyleRange = new StyleRange(mCurrentStyleRange.start,
mCurrentStyleRange.length, null, null);
text.setStyleRange(resetedStyleRange);
}
if (length != 0) {
mCurrentStyleRange = new StyleRange(start, length, text.getSelectionForeground(), text
.getSelectionBackground());
text.setStyleRange(mCurrentStyleRange);
}
}
/**
* Retrieves the currently selected annotation.
*
* TODO: make this private ??? clients can use selections for this ...
*
* @return the selected annotation or null if none
*/
public List<AnnotationFS> getSelectedAnnotations() {
List<AnnotationFS> selection = new ArrayList<AnnotationFS>();
if (isSomethingSelected()) {
Point selectedText = getSourceViewer().getTextWidget().getSelectionRange();
Span selecectedSpan = new Span(selectedText.x, selectedText.y);
Collection<AnnotationFS> selectedAnnotations = getDocument().getAnnotation(
getAnnotationMode(), selecectedSpan);
for (AnnotationFS annotation : selectedAnnotations) {
selection.add(annotation);
}
Collections.sort(selection, new AnnotationComparator());
} else {
Map<Integer, AnnotationFS> view = getDocument().getView(getAnnotationMode());
AnnotationFS annotation = view.get(mCursorPosition);
if (annotation == null) {
annotation = view.get(mCursorPosition - 1);
}
if (annotation != null) {
selection.add(annotation);
}
}
return selection;
}
/**
* Text is not editable, cause of the nature of the annotation editor. This does not mean, that
* the annotations are not editable.
*
* @return false
*/
@Override
public boolean isEditorInputModifiable() {
return false;
}
/**
* Notifies the current instance about selection changes in the workbench.
*
* @param part
* @param selection
*/
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
if (selection instanceof StructuredSelection) {
AnnotationSelection annotations = new AnnotationSelection((StructuredSelection) selection);
// only process these selection if the annotations belong
// to the current editor instance
if (getSite().getPage().getActiveEditor() == this && !annotations.isEmpty()) {
highlight(annotations.getFirst().getBegin(), annotations.getLast().getEnd()
- annotations.getFirst().getBegin());
// move caret to new position when selected outside of the editor
if (AnnotationEditor.this != part) {
// Note: The caret cannot be placed between line delimiters
// See bug UIMA-1470
int newCaretOffset = annotations.getLast().getEnd();
String text = getSourceViewer().getTextWidget().getText();
if (newCaretOffset > 0 && newCaretOffset < text.length()) {
char beforeCaret = text.charAt(newCaretOffset -1);
char afterCaret = text.charAt(newCaretOffset);
final int cr = 0x0D;
final int lf = 0x0A;
if (beforeCaret == cr && afterCaret == lf) {
// In case the caret offset is in the middle
// of a multiple-char line delimiter place caret
// before
newCaretOffset = newCaretOffset -1;
}
}
// check bounds, if out of text do nothing
getSourceViewer().getTextWidget().setCaretOffset(newCaretOffset);
}
}
}
}
private boolean isSomethingSelected() {
// TODO: sometimes we get a NPE here ... mh
// getSourceViewer() returns null here ... but why ?
return getSourceViewer().getTextWidget().getSelectionCount() != 0;
}
private void setProjectEditorStatus() {
// TODO: do not replace if equal ... check this
EditorAnnotationStatus status = new EditorAnnotationStatus(getAnnotationMode().getName(),
mShowAnnotationsMenu.getSelectedTypes());
getDocumentProvider().setEditorAnnotationStatus(getEditorInput(), status);
}
/**
* Creates custom annotation actions:
*
* Annotate Action
* Smart Annotate Action
* Delete Annotations Action
* Find Annotate Action
*/
@Override
protected void createActions() {
super.createActions();
mFeatureStructureSelectionProvider = new FeatureStructureSelectionProvider();
getSite().setSelectionProvider(mFeatureStructureSelectionProvider);
// create annotate action
AnnotateAction annotateAction = new AnnotateAction(getSourceViewer().getTextWidget());
final String annotateActionID = "Enter";
annotateAction.setActionDefinitionId(annotateActionID);
setAction(annotateActionID, annotateAction);
setActionActivationCode(annotateActionID, '\r', SWT.CR,
SWT.NONE);
SmartAnnotateAction smartAnnotateAction = new SmartAnnotateAction();
smartAnnotateAction.setActionDefinitionId(ITextEditorActionDefinitionIds.SMART_ENTER);
setAction(ITextEditorActionDefinitionIds.SMART_ENTER, smartAnnotateAction);
setActionActivationCode(ITextEditorActionDefinitionIds.SMART_ENTER, '\r', SWT.CR,
SWT.SHIFT);
// create delete action
DeleteFeatureStructureAction deleteAnnotationAction = new DeleteFeatureStructureAction(
getDocument());
getSite().getSelectionProvider().addSelectionChangedListener(deleteAnnotationAction);
deleteAnnotationAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.DELETE);
setAction(IWorkbenchActionDefinitionIds.DELETE, deleteAnnotationAction);
setActionActivationCode(IWorkbenchActionDefinitionIds.DELETE, (char) 0, SWT.CR, SWT.NONE);
// create show annotation context editing action
ShowAnnotationContextEditAction annotationContextEditAction =
new ShowAnnotationContextEditAction();
annotationContextEditAction.setActionDefinitionId(ITextEditorActionDefinitionIds.QUICK_ASSIST);
setAction(ITextEditorActionDefinitionIds.QUICK_ASSIST, annotationContextEditAction);
// create find annotate action
FindAnnotateAction findAnnotateAction = new FindAnnotateAction(this, getSourceViewer().getFindReplaceTarget());
findAnnotateAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.FIND_REPLACE);
setAction(ITextEditorActionConstants.FIND, findAnnotateAction);
}
@Override
public void dispose() {
// remove selection listener
getSite().getWorkbenchWindow().getSelectionService().removeSelectionListener(this);
getSite().getPage().removeSelectionListener(this);
ICasDocument document = getDocument();
if (document != null) {
document.removeChangeListener(mAnnotationSynchronizer);
}
if (closeEditorListener != null) {
ResourcesPlugin.getWorkspace()
.removeResourceChangeListener(closeEditorListener);
}
super.dispose();
}
void setAnnotationSelection(AnnotationFS annotation) {
mFeatureStructureSelectionProvider.setSelection(getDocument(), annotation);
}
/**
* Creates a list of all {@link AnnotationEditor} which are currently opened.
*/
public static AnnotationEditor[] getAnnotationEditors() {
ArrayList<AnnotationEditor> dirtyParts = new ArrayList<AnnotationEditor>();
IWorkbenchWindow windows[] = PlatformUI.getWorkbench().getWorkbenchWindows();
for (IWorkbenchWindow element : windows) {
IWorkbenchPage pages[] = element.getPages();
for (IWorkbenchPage page : pages) {
IEditorReference[] references = page.getEditorReferences();
for (IEditorReference reference : references) {
IEditorPart part = reference.getEditor(false);
if (part instanceof AnnotationEditor) {
AnnotationEditor editor = (AnnotationEditor) part;
dirtyParts.add(editor);
}
}
}
}
return dirtyParts.toArray(new AnnotationEditor[dirtyParts.size()]);
}
}