blob: 7f60b8773652ea474c6d94f7e95925df06654f7a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Atlassian 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:
* Atlassian - initial API and implementation
******************************************************************************/
package org.eclipse.mylyn.internal.reviews.ui.compare;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import org.eclipse.compare.internal.MergeSourceViewer;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.AnnotationBarHoverManager;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.LineRange;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.reviews.ui.ReviewsUiPlugin;
import org.eclipse.mylyn.internal.reviews.ui.actions.AddLineCommentToFileAction;
import org.eclipse.mylyn.internal.reviews.ui.annotations.CommentAnnotation;
import org.eclipse.mylyn.internal.reviews.ui.annotations.CommentAnnotationHover;
import org.eclipse.mylyn.internal.reviews.ui.annotations.CommentInformationControlCreator;
import org.eclipse.mylyn.internal.reviews.ui.annotations.IReviewCompareSourceViewer;
import org.eclipse.mylyn.internal.reviews.ui.annotations.ReviewAnnotationModel;
import org.eclipse.mylyn.internal.reviews.ui.editors.ruler.CommentAnnotationRulerColumn;
import org.eclipse.mylyn.reviews.core.model.ILineLocation;
import org.eclipse.mylyn.reviews.core.model.ILocation;
import org.eclipse.mylyn.reviews.core.model.ITopic;
import org.eclipse.swt.custom.LineBackgroundEvent;
import org.eclipse.swt.custom.LineBackgroundListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.internal.texteditor.AnnotationColumn;
import org.eclipse.ui.internal.texteditor.PropertyEventDispatcher;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.AnnotationPreferenceLookup;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
/**
* @author Thomas Ehrnhoefer
* @author Steffen Pingel
*/
class ReviewCompareInputListener implements ITextInputListener, IReviewCompareSourceViewer {
/**
* Ensures that the line background is fully colored in the compare editor.
*/
private final class ColoringLineBackgroundListener implements LineBackgroundListener {
private Color colorCommented;
private PropertyEventDispatcher fDispatcher;
private final StyledText styledText;
private ColoringLineBackgroundListener(StyledText styledText) {
this.styledText = styledText;
initialize();
}
public void lineGetBackground(LineBackgroundEvent event) {
int lineNr = styledText.getLineAtOffset(event.lineOffset) + 1;
Iterator<CommentAnnotation> it = annotationModel.getAnnotationIterator();
while (it.hasNext()) {
CommentAnnotation annotation = it.next();
int startLine;
int endLine;
ITopic comment = annotation.getTopic();
startLine = ((ILineLocation) comment.getLocation()).getTotalMin();
endLine = ((ILineLocation) comment.getLocation()).getTotalMax();
if (lineNr >= startLine && lineNr <= endLine) {
AnnotationPreference pref = new AnnotationPreferenceLookup().getAnnotationPreference(annotation);
if (pref.getHighlightPreferenceValue()) {
event.lineBackground = colorCommented;
}
}
}
}
/**
* Galileo hack to deal with slaveDocuments (when clicking on java structure elements). The styledText will not
* contain the whole text anymore, so our line numbering is off
*
* @param event
* @return
*/
@SuppressWarnings("unused")
private int getDocumentOffset(LineBackgroundEvent event) {
/*
* there is no access to DefaultDocumentAdapter and thus the (master or slave) document.. so we have to assume
* that on first call this event actually has the full text. this text, and the text of the current styled text
* will be used to calculate the offset
*/
if (event.widget instanceof StyledText) {
String currentText = ((StyledText) event.widget).getText();
if (initialText == null) {
initialText = currentText;
// since it is initial call, offset should be 0 anyway
return 0;
}
// if text is unchanged, offset it 0
if (currentText.equals(initialText)) {
return 0;
}
// current text is different, check if it is contained in initialText
if (initialText.contains(currentText)) {
// calculate the offset
int charoffset = initialText.indexOf(currentText);
int lineOffset = 0;
String delimiter = ((StyledText) event.widget).getLineDelimiter();
for (String line : initialText.split(delimiter)) {
if (charoffset > 0) {
charoffset -= (line.length() + delimiter.length());
lineOffset++;
} else {
break;
}
}
return lineOffset;
} else {
// log error since we assume the initial text contains all slaveTexts.
StatusHandler.log(new Status(IStatus.ERROR, ReviewsUiPlugin.PLUGIN_ID,
"Could not find text offset for annotation highlighting"
+ " - current text not contained in initial text."));
}
}
return 0;
}
private void initialize() {
final IPreferenceStore store = EditorsUI.getPreferenceStore();
if (store == null) {
return;
}
AnnotationPreferenceLookup lookup = EditorsUI.getAnnotationPreferenceLookup();
final AnnotationPreference commentedPref = lookup.getAnnotationPreference(CommentAnnotation.COMMENT_ANNOTATION_ID);
updateCommentedColor(commentedPref, store);
fDispatcher = new PropertyEventDispatcher(store);
if (commentedPref != null) {
fDispatcher.addPropertyChangeListener(commentedPref.getColorPreferenceKey(),
new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
updateCommentedColor(commentedPref, store);
}
});
}
}
private void updateCommentedColor(AnnotationPreference pref, IPreferenceStore store) {
if (pref != null) {
RGB rgb = CommentAnnotationRulerColumn.getColorFromAnnotationPreference(store, pref);
colorCommented = EditorsUI.getSharedTextColors().getColor(rgb);
}
}
}
private AddLineCommentToFileAction addLineCommentAction;
private final ReviewAnnotationModel annotationModel;
private String initialText;
private final MergeSourceViewer mergeSourceViewer;
// private AddGeneralCommentToFileAction addGeneralCommentAction;
private final SourceViewer sourceViewer;
ReviewCompareInputListener(MergeSourceViewer mergeSourceViewer, ReviewAnnotationModel annotationModel) {
this.sourceViewer = CompareUtil.getSourceViewer(mergeSourceViewer);
this.mergeSourceViewer = mergeSourceViewer;
this.annotationModel = annotationModel;
}
public void focusOnLines(ILocation range) {
if (range instanceof ILineLocation) {
ILineLocation lineLocation = (ILineLocation) range;
// editors count lines from 0, Crucible counts from 1
final int startLine = lineLocation.getTotalMin() - 1;
final int endLine = lineLocation.getTotalMax() - 1;
if (sourceViewer != null) {
IDocument document = sourceViewer.getDocument();
if (document != null) {
try {
int offset = document.getLineOffset(startLine);
int length = document.getLineOffset(endLine) - offset;
StyledText widget = sourceViewer.getTextWidget();
try {
widget.setRedraw(false);
//sourceViewer.revealRange(offset, length);
//sourceViewer.setSelectedRange(offset, 0);
sourceViewer.setSelection(new TextSelection(offset, length), true);
} finally {
widget.setRedraw(true);
}
} catch (BadLocationException e) {
StatusHandler.log(new Status(IStatus.ERROR, ReviewsUiPlugin.PLUGIN_ID, e.getMessage(), e));
}
}
}
}
}
public void forceCustomAnnotationHover() throws NoSuchFieldException, IllegalAccessException {
Class<SourceViewer> sourceViewerClazz = SourceViewer.class;
sourceViewer.setAnnotationHover(new CommentAnnotationHover(null));
// hack for Eclipse 3.5
try {
Field hoverControlCreator = TextViewer.class.getDeclaredField("fHoverControlCreator");
hoverControlCreator.setAccessible(true);
hoverControlCreator.set(sourceViewer, new CommentInformationControlCreator());
} catch (Throwable t) {
// ignore as it may not exist in other versions
}
// hack for Eclipse 3.5
try {
Method ensureMethod = sourceViewerClazz.getDeclaredMethod("ensureAnnotationHoverManagerInstalled");
ensureMethod.setAccessible(true);
ensureMethod.invoke(sourceViewer);
} catch (Throwable t) {
// ignore as it may not exist in other versions
}
Field hoverManager = SourceViewer.class.getDeclaredField("fVerticalRulerHoveringController");
hoverManager.setAccessible(true);
AnnotationBarHoverManager manager = (AnnotationBarHoverManager) hoverManager.get(sourceViewer);
if (manager != null) {
Field annotationHover = AnnotationBarHoverManager.class.getDeclaredField("fAnnotationHover");
annotationHover.setAccessible(true);
IAnnotationHover hover = (IAnnotationHover) annotationHover.get(manager);
annotationHover.set(manager, new CommentAnnotationHover(hover));
}
sourceViewer.showAnnotations(true);
sourceViewer.showAnnotationsOverview(true);
}
public ReviewAnnotationModel getAnnotationModel() {
return annotationModel;
}
public LineRange getSelection() {
if (sourceViewer != null) {
TextSelection selection = (TextSelection) sourceViewer.getSelection();
return new LineRange(selection.getStartLine() + 1, selection.getEndLine() - selection.getStartLine());
}
return null;
}
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
// ignore
}
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
if (oldInput != null) {
annotationModel.disconnect(oldInput);
}
if (newInput != null && sourceViewer != null) {
IAnnotationModel originalAnnotationModel = sourceViewer.getAnnotationModel();
if (originalAnnotationModel instanceof IAnnotationModelExtension) {
IAnnotationModelExtension annotationModelExtension = (IAnnotationModelExtension) originalAnnotationModel;
annotationModelExtension.addAnnotationModel(ReviewsUiPlugin.PLUGIN_ID, originalAnnotationModel);
} else {
try {
Class<SourceViewer> sourceViewerClazz = SourceViewer.class;
Field declaredField2 = sourceViewerClazz.getDeclaredField("fVisualAnnotationModel");
declaredField2.setAccessible(true);
Method declaredMethod = sourceViewerClazz.getDeclaredMethod("createVisualAnnotationModel",
IAnnotationModel.class);
declaredMethod.setAccessible(true);
originalAnnotationModel = (IAnnotationModel) declaredMethod.invoke(sourceViewer, annotationModel);
declaredField2.set(sourceViewer, originalAnnotationModel);
originalAnnotationModel.connect(newInput);
sourceViewer.showAnnotations(true);
createVerticalRuler(newInput, sourceViewerClazz);
createOverviewRuler(newInput, sourceViewerClazz);
createHighlighting(sourceViewerClazz);
} catch (Throwable t) {
StatusHandler.log(new Status(IStatus.ERROR, ReviewsUiPlugin.PLUGIN_ID,
"Error attaching annotation model", t));
}
}
}
}
public boolean isListenerFor(MergeSourceViewer viewer, ReviewAnnotationModel annotationModel) {
return this.mergeSourceViewer == viewer && this.annotationModel == annotationModel;
}
public void registerContextMenu() {
addLineCommentAction = new AddLineCommentToFileAction(this);
// addLineCommentAction.setImageDescriptor(CrucibleImages.ADD_COMMENT);
// addGeneralCommentAction = new AddGeneralCommentToFileAction(crucibleAnnotationModel.getCrucibleFile());
if (sourceViewer != null) {
sourceViewer.addSelectionChangedListener(addLineCommentAction);
// sourceViewer.addSelectionChangedListener(addGeneralCommentAction);
}
mergeSourceViewer.addTextAction(addLineCommentAction);
// mergeSourceViewer.addTextAction(addGeneralCommentAction);
}
private void createHighlighting(Class<SourceViewer> sourceViewerClazz) throws IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
// TODO this could use some performance tweaks
final StyledText styledText = sourceViewer.getTextWidget();
styledText.addLineBackgroundListener(new ColoringLineBackgroundListener(styledText));
}
/*
* overview ruler problem: displayed in both viewers. the diff editor ruler is actually custom drawn (see
* TextMergeViewer.fBirdsEyeCanvas) the ruler that gets created in this method is longer than the editor, meaning its
* not an overview (not next to the scrollbar)
*/
@SuppressWarnings("unused")
private void createOverviewRuler(IDocument newInput, Class<SourceViewer> sourceViewerClazz)
throws SecurityException, NoSuchMethodException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
sourceViewer.setOverviewRulerAnnotationHover(new CommentAnnotationHover(null));
OverviewRuler ruler = new OverviewRuler(new DefaultMarkerAnnotationAccess(), 15, EditorsPlugin.getDefault()
.getSharedTextColors());
Field compositeField = sourceViewerClazz.getDeclaredField("fComposite");
compositeField.setAccessible(true);
ruler.createControl((Composite) compositeField.get(sourceViewer), sourceViewer);
ruler.setModel(annotationModel);
//ruler.setModel(compareAnnotationModel.leftAnnotationModel);
// XXX should go through SourceViewerDecorationSupport instead
// ruler.addAnnotationType("org.eclipse.mylyn.reviews.ui.comment.Annotation");
// ruler.setAnnotationTypeLayer("org.eclipse.mylyn.reviews.ui.comment.Annotation", 1);
// ruler.update();
IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess();
final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(sourceViewer, ruler,
annotationAccess, EditorsUI.getSharedTextColors());
Iterator<?> e = new MarkerAnnotationPreferences().getAnnotationPreferences().iterator();
while (e.hasNext()) {
support.setAnnotationPreference((AnnotationPreference) e.next());
}
support.install(EditorsUI.getPreferenceStore());
sourceViewer.getControl().addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
support.dispose();
}
});
Field overViewRulerField = sourceViewerClazz.getDeclaredField("fOverviewRuler");
overViewRulerField.setAccessible(true);
if (overViewRulerField.get(sourceViewer) == null) {
overViewRulerField.set(sourceViewer, ruler);
}
Method declareMethod = sourceViewerClazz.getDeclaredMethod("ensureOverviewHoverManagerInstalled");
declareMethod.setAccessible(true);
declareMethod.invoke(sourceViewer);
// overviewRuler is null
Field hoverManager = sourceViewerClazz.getDeclaredField("fOverviewRulerHoveringController");
hoverManager.setAccessible(true);
AnnotationBarHoverManager manager = (AnnotationBarHoverManager) hoverManager.get(sourceViewer);
if (manager != null) {
Field annotationHover = AnnotationBarHoverManager.class.getDeclaredField("fAnnotationHover");
annotationHover.setAccessible(true);
IAnnotationHover hover = (IAnnotationHover) annotationHover.get(manager);
annotationHover.set(manager, new CommentAnnotationHover(null));
}
sourceViewer.showAnnotations(true);
sourceViewer.showAnnotationsOverview(true);
declareMethod = sourceViewerClazz.getDeclaredMethod("showAnnotationsOverview", new Class[] { Boolean.TYPE });
declareMethod.setAccessible(true);
}
private void createVerticalRuler(IDocument newInput, Class<SourceViewer> sourceViewerClazz)
throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException, NoSuchFieldException {
forceCustomAnnotationHover();
Method declaredMethod2 = sourceViewerClazz.getDeclaredMethod("getVerticalRuler");
declaredMethod2.setAccessible(true);
CompositeRuler ruler = (CompositeRuler) declaredMethod2.invoke(sourceViewer);
boolean hasDecorator = false;
Iterator<?> iter = (ruler).getDecoratorIterator();
while (iter.hasNext()) {
Object obj = iter.next();
if (obj instanceof AnnotationColumn) {
hasDecorator = true;
}
}
if (!hasDecorator) {
AnnotationColumn annotationColumn = new AnnotationColumn();
annotationColumn.createControl(ruler, ruler.getControl().getParent());
ruler.addDecorator(0, annotationColumn);
}
}
}