0001: /*******************************************************************************
0002: * Copyright (c) 2006, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.jface.internal.text.revisions;
0011:
0012: import java.util.ArrayList;
0013: import java.util.Arrays;
0014: import java.util.Collections;
0015: import java.util.HashMap;
0016: import java.util.Iterator;
0017: import java.util.List;
0018: import java.util.ListIterator;
0019: import java.util.Map;
0020: import java.util.Map.Entry;
0021:
0022: import org.eclipse.swt.SWT;
0023: import org.eclipse.swt.custom.StyledText;
0024: import org.eclipse.swt.events.DisposeEvent;
0025: import org.eclipse.swt.events.DisposeListener;
0026: import org.eclipse.swt.events.MouseEvent;
0027: import org.eclipse.swt.events.MouseListener;
0028: import org.eclipse.swt.events.MouseMoveListener;
0029: import org.eclipse.swt.events.MouseTrackListener;
0030: import org.eclipse.swt.graphics.Color;
0031: import org.eclipse.swt.graphics.FontMetrics;
0032: import org.eclipse.swt.graphics.GC;
0033: import org.eclipse.swt.graphics.Point;
0034: import org.eclipse.swt.graphics.RGB;
0035: import org.eclipse.swt.graphics.Rectangle;
0036: import org.eclipse.swt.widgets.Canvas;
0037: import org.eclipse.swt.widgets.Control;
0038: import org.eclipse.swt.widgets.Display;
0039: import org.eclipse.swt.widgets.Event;
0040: import org.eclipse.swt.widgets.Listener;
0041: import org.eclipse.swt.widgets.Shell;
0042:
0043: import org.eclipse.core.runtime.Assert;
0044: import org.eclipse.core.runtime.ListenerList;
0045: import org.eclipse.core.runtime.Platform;
0046:
0047: import org.eclipse.jface.internal.text.html.BrowserInformationControl;
0048: import org.eclipse.jface.internal.text.html.HTMLPrinter;
0049: import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
0050:
0051: import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
0052: import org.eclipse.jface.text.BadLocationException;
0053: import org.eclipse.jface.text.DefaultInformationControl;
0054: import org.eclipse.jface.text.IDocument;
0055: import org.eclipse.jface.text.IInformationControl;
0056: import org.eclipse.jface.text.IInformationControlCreator;
0057: import org.eclipse.jface.text.IRegion;
0058: import org.eclipse.jface.text.ITextViewer;
0059: import org.eclipse.jface.text.ITextViewerExtension5;
0060: import org.eclipse.jface.text.JFaceTextUtil;
0061: import org.eclipse.jface.text.Position;
0062: import org.eclipse.jface.text.Region;
0063: import org.eclipse.jface.text.information.IInformationProviderExtension2;
0064: import org.eclipse.jface.text.revisions.IRevisionListener;
0065: import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension;
0066: import org.eclipse.jface.text.revisions.Revision;
0067: import org.eclipse.jface.text.revisions.RevisionEvent;
0068: import org.eclipse.jface.text.revisions.RevisionInformation;
0069: import org.eclipse.jface.text.revisions.RevisionRange;
0070: import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode;
0071: import org.eclipse.jface.text.source.Annotation;
0072: import org.eclipse.jface.text.source.CompositeRuler;
0073: import org.eclipse.jface.text.source.IAnnotationHover;
0074: import org.eclipse.jface.text.source.IAnnotationHoverExtension;
0075: import org.eclipse.jface.text.source.IAnnotationHoverExtension2;
0076: import org.eclipse.jface.text.source.IAnnotationModel;
0077: import org.eclipse.jface.text.source.IAnnotationModelExtension;
0078: import org.eclipse.jface.text.source.IAnnotationModelListener;
0079: import org.eclipse.jface.text.source.IChangeRulerColumn;
0080: import org.eclipse.jface.text.source.ILineDiffer;
0081: import org.eclipse.jface.text.source.ILineRange;
0082: import org.eclipse.jface.text.source.ISharedTextColors;
0083: import org.eclipse.jface.text.source.ISourceViewer;
0084: import org.eclipse.jface.text.source.IVerticalRulerColumn;
0085: import org.eclipse.jface.text.source.LineRange;
0086:
0087: /**
0088: * A strategy for painting the live annotate colors onto the vertical ruler column. It also manages
0089: * the revision hover.
0090: *
0091: * @since 3.2
0092: */
0093: public final class RevisionPainter {
0094: /** Tells whether this class is in debug mode. */
0095: private static boolean DEBUG = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text.source/debug/RevisionRulerColumn")); //$NON-NLS-1$//$NON-NLS-2$
0096:
0097: // RGBs provided by UI Designer
0098: private static final RGB BY_DATE_START_COLOR = new RGB(199, 134, 57);
0099: private static final RGB BY_DATE_END_COLOR = new RGB(241, 225, 206);
0100:
0101: /**
0102: * The annotations created to show a revision in the overview ruler.
0103: */
0104: private static final class RevisionAnnotation extends Annotation {
0105: public RevisionAnnotation(String text) {
0106: super (
0107: "org.eclipse.ui.workbench.texteditor.revisionAnnotation", false, text); //$NON-NLS-1$
0108: }
0109: }
0110:
0111: /**
0112: * The color tool manages revision colors and computes shaded colors based on the relative age
0113: * and author of a revision.
0114: */
0115: private final class ColorTool {
0116: /**
0117: * The average perceived intensity of a base color. 0 means black, 1 means white. A base
0118: * revision color perceived as light such as yellow will be darkened, while colors perceived
0119: * as dark such as blue will be lightened up.
0120: */
0121: private static final float AVERAGE_INTENSITY = 0.5f;
0122: /**
0123: * The maximum shading in [0, 1] - this is the shade that the most recent revision will
0124: * receive.
0125: */
0126: private static final float MAX_SHADING = 0.7f;
0127: /**
0128: * The minimum shading in [0, 1] - this is the shade that the oldest revision will receive.
0129: */
0130: private static final float MIN_SHADING = 0.2f;
0131: /**
0132: * The shade for the focus boxes.
0133: */
0134: private static final float FOCUS_COLOR_SHADING = 1f;
0135:
0136: /**
0137: * A list of {@link Long}, storing the age of each revision in a sorted list.
0138: */
0139: private List fRevisions;
0140: /**
0141: * The stored shaded colors.
0142: */
0143: private final Map fColors = new HashMap();
0144: /**
0145: * The stored focus colors.
0146: */
0147: private final Map fFocusColors = new HashMap();
0148:
0149: /**
0150: * Sets the revision information, which is needed to compute the relative age of a revision.
0151: *
0152: * @param info the new revision info, <code>null</code> for none.
0153: */
0154: public void setInfo(RevisionInformation info) {
0155: fRevisions = null;
0156: fColors.clear();
0157: fFocusColors.clear();
0158:
0159: if (info == null)
0160: return;
0161: List revisions = new ArrayList();
0162: for (Iterator it = info.getRevisions().iterator(); it
0163: .hasNext();) {
0164: Revision revision = (Revision) it.next();
0165: revisions.add(new Long(computeAge(revision)));
0166: }
0167: Collections.sort(revisions);
0168: fRevisions = revisions;
0169: }
0170:
0171: private RGB adaptColor(Revision revision, boolean focus) {
0172: RGB rgb;
0173: float scale;
0174: if (fRenderingMode == IRevisionRulerColumnExtension.AGE) {
0175: int index = computeAgeIndex(revision);
0176: if (index == -1 || fRevisions.size() == 0) {
0177: rgb = getBackground().getRGB();
0178: } else {
0179: // gradient from intense red for most recent to faint yellow for oldest
0180: RGB[] gradient = Colors.palette(
0181: BY_DATE_START_COLOR, BY_DATE_END_COLOR,
0182: fRevisions.size());
0183: rgb = gradient[gradient.length - index - 1];
0184: }
0185: scale = 0.99f;
0186: } else if (fRenderingMode == IRevisionRulerColumnExtension.AUTHOR) {
0187: rgb = revision.getColor();
0188: rgb = Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
0189: scale = 0.6f;
0190: } else if (fRenderingMode == IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE) {
0191: rgb = revision.getColor();
0192: rgb = Colors.adjustBrightness(rgb, AVERAGE_INTENSITY);
0193: int index = computeAgeIndex(revision);
0194: int size = fRevisions.size();
0195: // relative age: newest is 0, oldest is 1
0196: // if there is only one revision, use an intermediate value to avoid extreme coloring
0197: if (index == -1 || size < 2)
0198: scale = 0.5f;
0199: else
0200: scale = (float) index / (size - 1);
0201: } else {
0202: Assert.isTrue(false);
0203: return null; // dummy
0204: }
0205: rgb = getShadedColor(rgb, scale, focus);
0206: return rgb;
0207: }
0208:
0209: private int computeAgeIndex(Revision revision) {
0210: long age = computeAge(revision);
0211: int index = fRevisions.indexOf(new Long(age));
0212: return index;
0213: }
0214:
0215: private RGB getShadedColor(RGB color, float scale, boolean focus) {
0216: Assert.isLegal(scale >= 0.0);
0217: Assert.isLegal(scale <= 1.0);
0218: RGB background = getBackground().getRGB();
0219:
0220: // normalize to lie within [MIN_SHADING, MAX_SHADING]
0221: // use more intense colors if the ruler is narrow (i.e. not showing line numbers)
0222: boolean makeIntense = getWidth() <= 15;
0223: float intensityShift = makeIntense ? 0.3f : 0f;
0224: float max = MAX_SHADING + intensityShift;
0225: float min = MIN_SHADING + intensityShift;
0226: scale = (max - min) * scale + min;
0227:
0228: // focus coloring
0229: if (focus) {
0230: scale += FOCUS_COLOR_SHADING;
0231: if (scale > 1) {
0232: background = new RGB(255 - background.red,
0233: 255 - background.green,
0234: 255 - background.blue);
0235: scale = 2 - scale;
0236: }
0237: }
0238:
0239: return Colors.blend(background, color, scale);
0240: }
0241:
0242: private long computeAge(Revision revision) {
0243: return revision.getDate().getTime();
0244: }
0245:
0246: /**
0247: * Returns the color for a revision based on relative age and author.
0248: *
0249: * @param revision the revision
0250: * @param focus <code>true</code> to return the focus color
0251: * @return the color for a revision
0252: */
0253: public RGB getColor(Revision revision, boolean focus) {
0254: Map map = focus ? fFocusColors : fColors;
0255: RGB color = (RGB) map.get(revision);
0256: if (color != null)
0257: return color;
0258:
0259: color = adaptColor(revision, focus);
0260: map.put(revision, color);
0261: return color;
0262: }
0263: }
0264:
0265: /**
0266: * Handles all the mouse interaction in this line number ruler column.
0267: */
0268: private class MouseHandler implements MouseListener,
0269: MouseMoveListener, MouseTrackListener, Listener {
0270:
0271: private RevisionRange fMouseDownRegion;
0272:
0273: /*
0274: * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
0275: */
0276: public void mouseUp(MouseEvent e) {
0277: if (e.button == 1) {
0278: RevisionRange upRegion = fFocusRange;
0279: RevisionRange downRegion = fMouseDownRegion;
0280: fMouseDownRegion = null;
0281:
0282: if (upRegion == downRegion) {
0283: Revision revision = upRegion == null ? null
0284: : upRegion.getRevision();
0285: if (revision == fSelectedRevision)
0286: revision = null; // deselect already selected revision
0287: handleRevisionSelected(revision);
0288: }
0289: }
0290: }
0291:
0292: /*
0293: * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
0294: */
0295: public void mouseDown(MouseEvent e) {
0296: if (e.button == 3)
0297: updateFocusRevision(null); // kill any focus as the ctx menu is going to show
0298: if (e.button == 1) {
0299: fMouseDownRegion = fFocusRange;
0300: postRedraw();
0301: }
0302: }
0303:
0304: /*
0305: * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
0306: */
0307: public void mouseDoubleClick(MouseEvent e) {
0308: }
0309:
0310: /*
0311: * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
0312: */
0313: public void handleEvent(Event event) {
0314: Assert.isTrue(event.type == SWT.MouseWheel);
0315: handleMouseWheel(event);
0316: }
0317:
0318: /*
0319: * @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
0320: */
0321: public void mouseEnter(MouseEvent e) {
0322: updateFocusLine(toDocumentLineNumber(e.y));
0323: }
0324:
0325: /*
0326: * @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
0327: */
0328: public void mouseExit(MouseEvent e) {
0329: updateFocusLine(-1);
0330: }
0331:
0332: /*
0333: * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
0334: */
0335: public void mouseHover(MouseEvent e) {
0336: }
0337:
0338: /*
0339: * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
0340: */
0341: public void mouseMove(MouseEvent e) {
0342: updateFocusLine(toDocumentLineNumber(e.y));
0343: }
0344: }
0345:
0346: /**
0347: * Internal listener class that will update the ruler when the underlying model changes.
0348: */
0349: private class AnnotationListener implements
0350: IAnnotationModelListener {
0351: /*
0352: * @see org.eclipse.jface.text.source.IAnnotationModelListener#modelChanged(org.eclipse.jface.text.source.IAnnotationModel)
0353: */
0354: public void modelChanged(IAnnotationModel model) {
0355: clearRangeCache();
0356: postRedraw();
0357: }
0358:
0359: }
0360:
0361: /**
0362: * The information control creator.
0363: */
0364: private static final class HoverInformationControlCreator extends
0365: AbstractReusableInformationControlCreator {
0366: private boolean fIsFocusable;
0367:
0368: public HoverInformationControlCreator(boolean isFocusable) {
0369: fIsFocusable = isFocusable;
0370: }
0371:
0372: /*
0373: * @see org.eclipse.jface.internal.text.revisions.AbstractReusableInformationControlCreator#doCreateInformationControl(org.eclipse.swt.widgets.Shell)
0374: */
0375: protected IInformationControl doCreateInformationControl(
0376: Shell parent) {
0377: int style = fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL
0378: : SWT.NONE;
0379:
0380: if (BrowserInformationControl.isAvailable(parent)) {
0381: final int shellStyle = SWT.TOOL
0382: | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM);
0383: return new BrowserInformationControl(parent,
0384: shellStyle, style) {
0385: /*
0386: * @see org.eclipse.jface.internal.text.html.BrowserInformationControl#setInformation(java.lang.String)
0387: * @since 3.3
0388: */
0389: public void setInformation(String content) {
0390: content = addCSSToHTMLFragment(content);
0391: super .setInformation(content);
0392: }
0393:
0394: /**
0395: * Adds a HTML header and CSS info if <code>html</code> is only an HTML fragment (has no
0396: * <html> section).
0397: *
0398: * @param html the html / text produced by a revision
0399: * @return modified html
0400: */
0401: private String addCSSToHTMLFragment(String html) {
0402: int max = Math.min(100, html.length());
0403: if (html.substring(0, max).indexOf("<html>") != -1) //$NON-NLS-1$
0404: // there is already a header
0405: return html;
0406:
0407: StringBuffer info = new StringBuffer(512 + html
0408: .length());
0409: HTMLPrinter.insertPageProlog(info, 0,
0410: fgStyleSheet);
0411: info.append(html);
0412: HTMLPrinter.addPageEpilog(info);
0413: return info.toString();
0414: }
0415:
0416: };
0417: }
0418: return new DefaultInformationControl(parent, style,
0419: new HTMLTextPresenter());
0420: }
0421: }
0422:
0423: private static final String fgStyleSheet = "/* Font definitions */\n" + //$NON-NLS-1$
0424: "body, h1, h2, h3, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt {font-family: sans-serif; font-size: 9pt }\n"
0425: + //$NON-NLS-1$
0426: "pre { font-family: monospace; font-size: 9pt }\n"
0427: + //$NON-NLS-1$
0428: "\n"
0429: + //$NON-NLS-1$
0430: "/* Margins */\n"
0431: + //$NON-NLS-1$
0432: "body { overflow: auto; margin-top: 0; margin-bottom: 4; margin-left: 3; margin-right: 0 }\n"
0433: + //$NON-NLS-1$
0434: "h1 { margin-top: 5; margin-bottom: 1 } \n"
0435: + //$NON-NLS-1$
0436: "h2 { margin-top: 25; margin-bottom: 3 }\n"
0437: + //$NON-NLS-1$
0438: "h3 { margin-top: 20; margin-bottom: 3 }\n"
0439: + //$NON-NLS-1$
0440: "h4 { margin-top: 20; margin-bottom: 3 }\n"
0441: + //$NON-NLS-1$
0442: "h5 { margin-top: 0; margin-bottom: 0 }\n"
0443: + //$NON-NLS-1$
0444: "p { margin-top: 10px; margin-bottom: 10px }\n"
0445: + //$NON-NLS-1$
0446: "pre { margin-left: 6 }\n"
0447: + //$NON-NLS-1$
0448: "ul { margin-top: 0; margin-bottom: 10 }\n"
0449: + //$NON-NLS-1$
0450: "li { margin-top: 0; margin-bottom: 0 } \n"
0451: + //$NON-NLS-1$
0452: "li p { margin-top: 0; margin-bottom: 0 } \n"
0453: + //$NON-NLS-1$
0454: "ol { margin-top: 0; margin-bottom: 10 }\n"
0455: + //$NON-NLS-1$
0456: "dl { margin-top: 0; margin-bottom: 10 }\n"
0457: + //$NON-NLS-1$
0458: "dt { margin-top: 0; margin-bottom: 0; font-weight: bold }\n"
0459: + //$NON-NLS-1$
0460: "dd { margin-top: 0; margin-bottom: 0 }\n" + //$NON-NLS-1$
0461: "\n" + //$NON-NLS-1$
0462: "/* Styles and colors */\n" + //$NON-NLS-1$
0463: "a:link { color: #0000FF }\n" + //$NON-NLS-1$
0464: "a:hover { color: #000080 }\n" + //$NON-NLS-1$
0465: "a:visited { text-decoration: underline }\n" + //$NON-NLS-1$
0466: "h4 { font-style: italic }\n" + //$NON-NLS-1$
0467: "strong { font-weight: bold }\n" + //$NON-NLS-1$
0468: "em { font-style: italic }\n" + //$NON-NLS-1$
0469: "var { font-style: italic }\n" + //$NON-NLS-1$
0470: "th { font-weight: bold }\n" + //$NON-NLS-1$
0471: ""; //$NON-NLS-1$
0472:
0473: /**
0474: * The revision hover displays information about the currently selected revision.
0475: */
0476: private final class RevisionHover implements IAnnotationHover,
0477: IAnnotationHoverExtension, IAnnotationHoverExtension2,
0478: IInformationProviderExtension2 {
0479:
0480: /*
0481: * @see org.eclipse.jface.text.source.IAnnotationHover#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer,
0482: * int)
0483: */
0484: public String getHoverInfo(ISourceViewer sourceViewer,
0485: int lineNumber) {
0486: Object info = getHoverInfo(sourceViewer, getHoverLineRange(
0487: sourceViewer, lineNumber), 0);
0488: return info == null ? null : info.toString();
0489: }
0490:
0491: /*
0492: * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverControlCreator()
0493: */
0494: public IInformationControlCreator getHoverControlCreator() {
0495: RevisionInformation revisionInfo = fRevisionInfo;
0496: if (revisionInfo != null) {
0497: IInformationControlCreator creator = revisionInfo
0498: .getHoverControlCreator();
0499: if (creator != null)
0500: return creator;
0501: }
0502: return new HoverInformationControlCreator(false);
0503: }
0504:
0505: /*
0506: * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#canHandleMouseCursor()
0507: */
0508: public boolean canHandleMouseCursor() {
0509: return false;
0510: }
0511:
0512: /*
0513: * @see org.eclipse.jface.text.source.IAnnotationHoverExtension2#canHandleMouseWheel()
0514: */
0515: public boolean canHandleMouseWheel() {
0516: return true;
0517: }
0518:
0519: /*
0520: * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer,
0521: * org.eclipse.jface.text.source.ILineRange, int)
0522: */
0523: public Object getHoverInfo(ISourceViewer sourceViewer,
0524: ILineRange lineRange, int visibleNumberOfLines) {
0525: RevisionRange range = getRange(lineRange.getStartLine());
0526: Object info = range == null ? null : range.getRevision()
0527: .getHoverInfo();
0528: return info;
0529: }
0530:
0531: /*
0532: * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverLineRange(org.eclipse.jface.text.source.ISourceViewer,
0533: * int)
0534: */
0535: public ILineRange getHoverLineRange(ISourceViewer viewer,
0536: int lineNumber) {
0537: RevisionRange range = getRange(lineNumber);
0538: return range == null ? null : new LineRange(lineNumber, 1);
0539: }
0540:
0541: /*
0542: * @see org.eclipse.jface.text.information.IInformationProviderExtension2#getInformationPresenterControlCreator()
0543: */
0544: public IInformationControlCreator getInformationPresenterControlCreator() {
0545: RevisionInformation revisionInfo = fRevisionInfo;
0546: if (revisionInfo != null) {
0547: IInformationControlCreator creator = revisionInfo
0548: .getInformationPresenterControlCreator();
0549: if (creator != null)
0550: return creator;
0551: }
0552: return new HoverInformationControlCreator(true);
0553: }
0554: }
0555:
0556: /* Listeners and helpers. */
0557:
0558: /** The shared color provider. */
0559: private final ISharedTextColors fSharedColors;
0560: /** The color tool. */
0561: private final ColorTool fColorTool = new ColorTool();
0562: /** The mouse handler. */
0563: private final MouseHandler fMouseHandler = new MouseHandler();
0564: /** The hover. */
0565: private final RevisionHover fHover = new RevisionHover();
0566: /** The annotation listener. */
0567: private final AnnotationListener fAnnotationListener = new AnnotationListener();
0568: /** The selection provider. */
0569: private final RevisionSelectionProvider fRevisionSelectionProvider = new RevisionSelectionProvider(
0570: this );
0571: /**
0572: * The list of revision listeners.
0573: * @since 3.3.
0574: */
0575: private final ListenerList fRevisionListeners = new ListenerList();
0576:
0577: /* The context - column and viewer we are connected to. */
0578:
0579: /** The vertical ruler column that delegates painting to this painter. */
0580: private final IVerticalRulerColumn fColumn;
0581: /** The parent ruler. */
0582: private CompositeRuler fParentRuler;
0583: /** The column's control, typically a {@link Canvas}, possibly <code>null</code>. */
0584: private Control fControl;
0585: /** The text viewer that the column is attached to. */
0586: private ITextViewer fViewer;
0587: /** The viewer's text widget. */
0588: private StyledText fWidget;
0589:
0590: /* The models we operate on. */
0591:
0592: /** The revision model object. */
0593: private RevisionInformation fRevisionInfo;
0594: /** The line differ. */
0595: private ILineDiffer fLineDiffer = null;
0596: /** The annotation model. */
0597: private IAnnotationModel fAnnotationModel = null;
0598: /** The background color, possibly <code>null</code>. */
0599: private Color fBackground;
0600:
0601: /* Cache. */
0602:
0603: /** The cached list of ranges adapted to quick diff. */
0604: private List fRevisionRanges = null;
0605: /** The annotations created for the overview ruler temporary display. */
0606: private List fAnnotations = new ArrayList();
0607:
0608: /* State */
0609:
0610: /** The current focus line, -1 for none. */
0611: private int fFocusLine = -1;
0612: /** The current focus region, <code>null</code> if none. */
0613: private RevisionRange fFocusRange = null;
0614: /** The current focus revision, <code>null</code> if none. */
0615: private Revision fFocusRevision = null;
0616: /**
0617: * The currently selected revision, <code>null</code> if none. The difference between
0618: * {@link #fFocusRevision} and {@link #fSelectedRevision} may not be obvious: the focus revision
0619: * is the one focused by the mouse (by hovering over a block of the revision), while the
0620: * selected revision is sticky, i.e. is not removed when the mouse leaves the ruler.
0621: *
0622: * @since 3.3
0623: */
0624: private Revision fSelectedRevision = null;
0625: /** <code>true</code> if the mouse wheel handler is installed, <code>false</code> otherwise. */
0626: private boolean fWheelHandlerInstalled = false;
0627: /**
0628: * The revision rendering mode.
0629: */
0630: private RenderingMode fRenderingMode = IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE;
0631: /**
0632: * The required with in characters.
0633: * @since 3.3
0634: */
0635: private int fRequiredWidth = -1;
0636: /**
0637: * The width of the revision field in chars to compute {@link #fAuthorInset} from.
0638: * @since 3.3
0639: */
0640: private int fRevisionIdChars = 0;
0641: /**
0642: * <code>true</code> to show revision ids, <code>false</code> otherwise.
0643: * @since 3.3
0644: */
0645: private boolean fShowRevision = false;
0646: /**
0647: * <code>true</code> to show the author, <code>false</code> otherwise.
0648: * @since 3.3
0649: */
0650: private boolean fShowAuthor = false;
0651: /**
0652: * The author inset in pixels for when author *and* revision id are shown.
0653: * @since 3.3
0654: */
0655: private int fAuthorInset;
0656: /**
0657: * The remembered ruler width (as changing the ruler width triggers recomputation of the colors.
0658: * @since 3.3
0659: */
0660: private int fLastWidth = -1;
0661:
0662: /**
0663: * Creates a new revision painter for a vertical ruler column.
0664: *
0665: * @param column the column that will delegate{@link #paint(GC, ILineRange) painting} to the
0666: * newly created painter.
0667: * @param sharedColors a shared colors object to store shaded colors in
0668: */
0669: public RevisionPainter(IVerticalRulerColumn column,
0670: ISharedTextColors sharedColors) {
0671: Assert.isLegal(column != null);
0672: Assert.isLegal(sharedColors != null);
0673: fColumn = column;
0674: fSharedColors = sharedColors;
0675: }
0676:
0677: /**
0678: * Sets the revision information to be drawn and triggers a redraw.
0679: *
0680: * @param info the revision information to show, <code>null</code> to draw none
0681: */
0682: public void setRevisionInformation(RevisionInformation info) {
0683: if (fRevisionInfo != info) {
0684: fRequiredWidth = -1;
0685: fRevisionIdChars = 0;
0686: fRevisionInfo = info;
0687: clearRangeCache();
0688: updateFocusRange(null);
0689: handleRevisionSelected((Revision) null);
0690: fColorTool.setInfo(info);
0691: postRedraw();
0692: informListeners();
0693: }
0694: }
0695:
0696: /**
0697: * Changes the rendering mode and triggers redrawing if needed.
0698: *
0699: * @param renderingMode the rendering mode
0700: * @since 3.3
0701: */
0702: public void setRenderingMode(RenderingMode renderingMode) {
0703: Assert.isLegal(renderingMode != null);
0704: if (fRenderingMode != renderingMode) {
0705: fRenderingMode = renderingMode;
0706: fColorTool.setInfo(fRevisionInfo);
0707: postRedraw();
0708: }
0709: }
0710:
0711: /**
0712: * Sets the background color.
0713: *
0714: * @param background the background color, <code>null</code> for the platform's list
0715: * background
0716: */
0717: public void setBackground(Color background) {
0718: fBackground = background;
0719: }
0720:
0721: /**
0722: * Sets the parent ruler - the delegating column must call this method as soon as it creates its
0723: * control.
0724: *
0725: * @param parentRuler the parent ruler
0726: */
0727: public void setParentRuler(CompositeRuler parentRuler) {
0728: fParentRuler = parentRuler;
0729: }
0730:
0731: /**
0732: * Delegates the painting of the quick diff colors to this painter. The painter will draw the
0733: * color boxes onto the passed {@link GC} for all model (document) lines in
0734: * <code>visibleModelLines</code>.
0735: *
0736: * @param gc the {@link GC} to draw onto
0737: * @param visibleLines the lines (in document offsets) that are currently (perhaps only
0738: * partially) visible
0739: */
0740: public void paint(GC gc, ILineRange visibleLines) {
0741: connectIfNeeded();
0742: if (!isConnected())
0743: return;
0744:
0745: // compute the horizontal indent of the author for the case that we show revision
0746: // and author
0747: if (fShowAuthor && fShowRevision) {
0748: char[] string = new char[fRevisionIdChars + 1];
0749: Arrays.fill(string, '9');
0750: if (string.length > 1) {
0751: string[0] = '.';
0752: string[1] = ' ';
0753: }
0754: fAuthorInset = gc.stringExtent(new String(string)).x;
0755: }
0756:
0757: // recompute colors (show intense colors if ruler is narrow)
0758: int width = getWidth();
0759: if (width != fLastWidth) {
0760: fColorTool.setInfo(fRevisionInfo);
0761: fLastWidth = width;
0762: }
0763:
0764: // draw change regions
0765: List/* <RevisionRange> */ranges = getRanges(visibleLines);
0766: for (Iterator it = ranges.iterator(); it.hasNext();) {
0767: RevisionRange region = (RevisionRange) it.next();
0768: paintRange(region, gc);
0769: }
0770: }
0771:
0772: /**
0773: * Ensures that the column is fully instantiated, i.e. has a control, and that the viewer is
0774: * visible.
0775: */
0776: private void connectIfNeeded() {
0777: if (isConnected() || fParentRuler == null)
0778: return;
0779:
0780: fViewer = fParentRuler.getTextViewer();
0781: if (fViewer == null)
0782: return;
0783:
0784: fWidget = fViewer.getTextWidget();
0785: if (fWidget == null)
0786: return;
0787:
0788: fControl = fColumn.getControl();
0789: if (fControl == null)
0790: return;
0791:
0792: fControl.addMouseTrackListener(fMouseHandler);
0793: fControl.addMouseMoveListener(fMouseHandler);
0794: fControl.addMouseListener(fMouseHandler);
0795: fControl.addDisposeListener(new DisposeListener() {
0796: /*
0797: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
0798: */
0799: public void widgetDisposed(DisposeEvent e) {
0800: handleDispose();
0801: }
0802: });
0803:
0804: fRevisionSelectionProvider.install(fViewer);
0805: }
0806:
0807: /**
0808: * Returns <code>true</code> if the column is fully connected.
0809: *
0810: * @return <code>true</code> if the column is fully connected, false otherwise
0811: */
0812: private boolean isConnected() {
0813: return fControl != null;
0814: }
0815:
0816: /**
0817: * Sets the annotation model.
0818: *
0819: * @param model the annotation model, possibly <code>null</code>
0820: * @see IVerticalRulerColumn#setModel(IAnnotationModel)
0821: */
0822: public void setModel(IAnnotationModel model) {
0823: IAnnotationModel diffModel;
0824: if (model instanceof IAnnotationModelExtension)
0825: diffModel = ((IAnnotationModelExtension) model)
0826: .getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
0827: else
0828: diffModel = model;
0829:
0830: setDiffer(diffModel);
0831: setAnnotationModel(model);
0832: }
0833:
0834: /**
0835: * Sets the annotation model.
0836: *
0837: * @param model the annotation model.
0838: */
0839: private void setAnnotationModel(IAnnotationModel model) {
0840: if (fAnnotationModel != model)
0841: fAnnotationModel = model;
0842: }
0843:
0844: /**
0845: * Sets the line differ.
0846: *
0847: * @param differ the line differ or <code>null</code> if none
0848: */
0849: private void setDiffer(IAnnotationModel differ) {
0850: if (differ instanceof ILineDiffer || differ == null) {
0851: if (fLineDiffer != differ) {
0852: if (fLineDiffer != null)
0853: ((IAnnotationModel) fLineDiffer)
0854: .removeAnnotationModelListener(fAnnotationListener);
0855: fLineDiffer = (ILineDiffer) differ;
0856: if (fLineDiffer != null)
0857: ((IAnnotationModel) fLineDiffer)
0858: .addAnnotationModelListener(fAnnotationListener);
0859: }
0860: }
0861: }
0862:
0863: /**
0864: * Disposes of the painter's resources.
0865: */
0866: private void handleDispose() {
0867: updateFocusLine(-1);
0868:
0869: if (fLineDiffer != null) {
0870: ((IAnnotationModel) fLineDiffer)
0871: .removeAnnotationModelListener(fAnnotationListener);
0872: fLineDiffer = null;
0873: }
0874:
0875: fRevisionSelectionProvider.uninstall();
0876: }
0877:
0878: /**
0879: * Paints a single change region onto <code>gc</code>.
0880: *
0881: * @param range the range to paint
0882: * @param gc the {@link GC} to paint on
0883: */
0884: private void paintRange(RevisionRange range, GC gc) {
0885: ILineRange widgetRange = modelLinesToWidgetLines(range);
0886: if (widgetRange == null)
0887: return;
0888:
0889: Revision revision = range.getRevision();
0890: boolean drawArmedFocus = range == fMouseHandler.fMouseDownRegion;
0891: boolean drawSelection = !drawArmedFocus
0892: && revision == fSelectedRevision;
0893: boolean drawFocus = !drawSelection && !drawArmedFocus
0894: && revision == fFocusRevision;
0895: Rectangle box = computeBoxBounds(widgetRange);
0896:
0897: gc.setBackground(lookupColor(revision, false));
0898: if (drawArmedFocus) {
0899: Color foreground = gc.getForeground();
0900: Color focusColor = lookupColor(revision, true);
0901: gc.setForeground(focusColor);
0902: gc.fillRectangle(box);
0903: gc.drawRectangle(box.x, box.y, box.width - 1,
0904: box.height - 1); // highlight box
0905: gc.drawRectangle(box.x + 1, box.y + 1, box.width - 3,
0906: box.height - 3); // inner highlight box
0907: gc.setForeground(foreground);
0908: } else if (drawFocus || drawSelection) {
0909: Color foreground = gc.getForeground();
0910: Color focusColor = lookupColor(revision, true);
0911: gc.setForeground(focusColor);
0912: gc.fillRectangle(box);
0913: gc.drawRectangle(box.x, box.y, box.width - 1,
0914: box.height - 1); // highlight box
0915: gc.setForeground(foreground);
0916: } else {
0917: gc.fillRectangle(box);
0918: }
0919:
0920: if ((fShowAuthor || fShowRevision)) {
0921: int indentation = 1;
0922: int baselineBias = getBaselineBias(gc, widgetRange
0923: .getStartLine());
0924: if (fShowAuthor && fShowRevision) {
0925: gc.drawString(revision.getId(), indentation, box.y
0926: + baselineBias, true);
0927: gc.drawString(revision.getAuthor(), fAuthorInset, box.y
0928: + baselineBias, true);
0929: } else if (fShowAuthor) {
0930: gc.drawString(revision.getAuthor(), indentation, box.y
0931: + baselineBias, true);
0932: } else if (fShowRevision) {
0933: gc.drawString(revision.getId(), indentation, box.y
0934: + baselineBias, true);
0935: }
0936: }
0937: }
0938:
0939: /**
0940: * Returns the difference between the baseline of the widget and the
0941: * baseline as specified by the font for <code>gc</code>. When drawing
0942: * line numbers, the returned bias should be added to obtain text lined up
0943: * on the correct base line of the text widget.
0944: *
0945: * @param gc the <code>GC</code> to get the font metrics from
0946: * @param widgetLine the widget line
0947: * @return the baseline bias to use when drawing text that is lined up with
0948: * <code>fCachedTextWidget</code>
0949: * @since 3.3
0950: */
0951: private int getBaselineBias(GC gc, int widgetLine) {
0952: /*
0953: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951
0954: * widget line height may be more than the font height used for the
0955: * line numbers, since font styles (bold, italics...) can have larger
0956: * font metrics than the simple font used for the numbers.
0957: */
0958: int offset = fWidget.getOffsetAtLine(widgetLine);
0959: int widgetBaseline = fWidget.getBaseline(offset);
0960:
0961: FontMetrics fm = gc.getFontMetrics();
0962: int fontBaseline = fm.getAscent() + fm.getLeading();
0963: int baselineBias = widgetBaseline - fontBaseline;
0964: return Math.max(0, baselineBias);
0965: }
0966:
0967: /**
0968: * Looks up the color for a certain revision.
0969: *
0970: * @param revision the revision to get the color for
0971: * @param focus <code>true</code> if it is the focus revision
0972: * @return the color for the revision
0973: */
0974: private Color lookupColor(Revision revision, boolean focus) {
0975: return fSharedColors.getColor(fColorTool.getColor(revision,
0976: focus));
0977: }
0978:
0979: /**
0980: * Returns the revision range that contains the given line, or
0981: * <code>null</code> if there is none.
0982: *
0983: * @param line the line of interest
0984: * @return the corresponding <code>RevisionRange</code> or <code>null</code>
0985: */
0986: private RevisionRange getRange(int line) {
0987: List ranges = getRangeCache();
0988:
0989: if (ranges.isEmpty() || line == -1)
0990: return null;
0991:
0992: for (Iterator it = ranges.iterator(); it.hasNext();) {
0993: RevisionRange range = (RevisionRange) it.next();
0994: if (contains(range, line))
0995: return range;
0996: }
0997:
0998: // line may be right after the last region
0999: RevisionRange lastRegion = (RevisionRange) ranges.get(ranges
1000: .size() - 1);
1001: if (line == end(lastRegion))
1002: return lastRegion;
1003: return null;
1004: }
1005:
1006: /**
1007: * Returns the sublist of all <code>RevisionRange</code>s that intersect with the given lines.
1008: *
1009: * @param lines the model based lines of interest
1010: * @return elementType: RevisionRange
1011: */
1012: private List getRanges(ILineRange lines) {
1013: List ranges = getRangeCache();
1014:
1015: // return the interesting subset
1016: int end = end(lines);
1017: int first = -1, last = -1;
1018: for (int i = 0; i < ranges.size(); i++) {
1019: RevisionRange range = (RevisionRange) ranges.get(i);
1020: int rangeEnd = end(range);
1021: if (first == -1 && rangeEnd > lines.getStartLine())
1022: first = i;
1023: if (first != -1 && rangeEnd > end) {
1024: last = i;
1025: break;
1026: }
1027: }
1028: if (first == -1)
1029: return Collections.EMPTY_LIST;
1030: if (last == -1)
1031: last = ranges.size() - 1; // bottom index may be one too much
1032:
1033: return ranges.subList(first, last + 1);
1034: }
1035:
1036: /**
1037: * Gets all change ranges of the revisions in the revision model and adapts them to the current
1038: * quick diff information. The list is cached.
1039: *
1040: * @return the list of all change regions, with diff information applied
1041: */
1042: private List getRangeCache() {
1043: if (fRevisionRanges == null) {
1044: if (fRevisionInfo == null) {
1045: fRevisionRanges = Collections.EMPTY_LIST;
1046: } else {
1047: Hunk[] hunks = HunkComputer.computeHunks(fLineDiffer,
1048: fViewer.getDocument().getNumberOfLines());
1049: fRevisionInfo.applyDiff(hunks);
1050: fRevisionRanges = fRevisionInfo.getRanges();
1051: updateOverviewAnnotations();
1052: informListeners();
1053: }
1054: }
1055:
1056: return fRevisionRanges;
1057: }
1058:
1059: /**
1060: * Clears the range cache.
1061: *
1062: * @since 3.3
1063: */
1064: private void clearRangeCache() {
1065: fRevisionRanges = null;
1066: }
1067:
1068: /**
1069: * Returns <code>true</code> if <code>range</code> contains <code>line</code>. A line is
1070: * not contained in a range if it is the range's exclusive end line.
1071: *
1072: * @param range the range to check whether it contains <code>line</code>
1073: * @param line the line the line
1074: * @return <code>true</code> if <code>range</code> contains <code>line</code>,
1075: * <code>false</code> if not
1076: */
1077: private static boolean contains(ILineRange range, int line) {
1078: return range.getStartLine() <= line && end(range) > line;
1079: }
1080:
1081: /**
1082: * Computes the end index of a line range.
1083: *
1084: * @param range a line range
1085: * @return the last line (exclusive) of <code>range</code>
1086: */
1087: private static int end(ILineRange range) {
1088: return range.getStartLine() + range.getNumberOfLines();
1089: }
1090:
1091: /**
1092: * Returns the visible extent of a document line range in widget lines.
1093: *
1094: * @param range the document line range
1095: * @return the visible extent of <code>range</code> in widget lines
1096: */
1097: private ILineRange modelLinesToWidgetLines(ILineRange range) {
1098: int widgetStartLine = -1;
1099: int widgetEndLine = -1;
1100: if (fViewer instanceof ITextViewerExtension5) {
1101: ITextViewerExtension5 extension = (ITextViewerExtension5) fViewer;
1102: int modelEndLine = end(range);
1103: for (int modelLine = range.getStartLine(); modelLine < modelEndLine; modelLine++) {
1104: int widgetLine = extension
1105: .modelLine2WidgetLine(modelLine);
1106: if (widgetLine != -1) {
1107: if (widgetStartLine == -1)
1108: widgetStartLine = widgetLine;
1109: widgetEndLine = widgetLine;
1110: }
1111: }
1112: } else {
1113: IRegion region = fViewer.getVisibleRegion();
1114: IDocument document = fViewer.getDocument();
1115: try {
1116: int visibleStartLine = document.getLineOfOffset(region
1117: .getOffset());
1118: int visibleEndLine = document.getLineOfOffset(region
1119: .getOffset()
1120: + region.getLength());
1121: widgetStartLine = Math.max(0, range.getStartLine()
1122: - visibleStartLine);
1123: widgetEndLine = Math
1124: .min(visibleEndLine, end(range) - 1);
1125: } catch (BadLocationException x) {
1126: x.printStackTrace();
1127: // ignore and return null
1128: }
1129: }
1130: if (widgetStartLine == -1 || widgetEndLine == -1)
1131: return null;
1132: return new LineRange(widgetStartLine, widgetEndLine
1133: - widgetStartLine + 1);
1134: }
1135:
1136: /**
1137: * Returns the revision hover.
1138: *
1139: * @return the revision hover
1140: */
1141: public IAnnotationHover getHover() {
1142: return fHover;
1143: }
1144:
1145: /**
1146: * Computes and returns the bounds of the rectangle corresponding to a widget line range. The
1147: * rectangle is in pixel coordinates relative to the text widget's
1148: * {@link StyledText#getClientArea() client area} and has the width of the ruler.
1149: *
1150: * @param range the widget line range
1151: * @return the box bounds corresponding to <code>range</code>
1152: */
1153: private Rectangle computeBoxBounds(ILineRange range) {
1154: int y1 = fWidget.getLinePixel(range.getStartLine());
1155: int y2 = fWidget.getLinePixel(range.getStartLine()
1156: + range.getNumberOfLines());
1157:
1158: return new Rectangle(0, y1, getWidth(), y2 - y1 - 1);
1159: }
1160:
1161: /**
1162: * Shows (or hides) the overview annotations.
1163: */
1164: private void updateOverviewAnnotations() {
1165: if (fAnnotationModel == null)
1166: return;
1167:
1168: Revision revision = fFocusRevision != null ? fFocusRevision
1169: : fSelectedRevision;
1170:
1171: Map added = null;
1172: if (revision != null) {
1173: added = new HashMap();
1174: for (Iterator it = revision.getRegions().iterator(); it
1175: .hasNext();) {
1176: RevisionRange range = (RevisionRange) it.next();
1177: try {
1178: IRegion charRegion = toCharRegion(range);
1179: Position position = new Position(charRegion
1180: .getOffset(), charRegion.getLength());
1181: Annotation annotation = new RevisionAnnotation(
1182: revision.getId());
1183: added.put(annotation, position);
1184: } catch (BadLocationException x) {
1185: // ignore - document was changed, show no annotations
1186: }
1187: }
1188: }
1189:
1190: if (fAnnotationModel instanceof IAnnotationModelExtension) {
1191: IAnnotationModelExtension ext = (IAnnotationModelExtension) fAnnotationModel;
1192: ext.replaceAnnotations((Annotation[]) fAnnotations
1193: .toArray(new Annotation[fAnnotations.size()]),
1194: added);
1195: } else {
1196: for (Iterator it = fAnnotations.iterator(); it.hasNext();) {
1197: Annotation annotation = (Annotation) it.next();
1198: fAnnotationModel.removeAnnotation(annotation);
1199: }
1200: if (added != null) {
1201: for (Iterator it = added.entrySet().iterator(); it
1202: .hasNext();) {
1203: Entry entry = (Entry) it.next();
1204: fAnnotationModel.addAnnotation((Annotation) entry
1205: .getKey(), (Position) entry.getValue());
1206: }
1207: }
1208: }
1209: fAnnotations.clear();
1210: if (added != null)
1211: fAnnotations.addAll(added.keySet());
1212:
1213: }
1214:
1215: /**
1216: * Returns the character offset based region of a line range.
1217: *
1218: * @param lines the line range to convert
1219: * @return the character offset range corresponding to <code>range</code>
1220: * @throws BadLocationException if the line range is not within the document bounds
1221: */
1222: private IRegion toCharRegion(ILineRange lines)
1223: throws BadLocationException {
1224: IDocument document = fViewer.getDocument();
1225: int offset = document.getLineOffset(lines.getStartLine());
1226: int nextLine = end(lines);
1227: int endOffset;
1228: if (nextLine >= document.getNumberOfLines())
1229: endOffset = document.getLength();
1230: else
1231: endOffset = document.getLineOffset(nextLine);
1232: return new Region(offset, endOffset - offset);
1233: }
1234:
1235: /**
1236: * Handles the selection of a revision and informs listeners.
1237: *
1238: * @param revision the selected revision, <code>null</code> for none
1239: */
1240: void handleRevisionSelected(Revision revision) {
1241: fSelectedRevision = revision;
1242: fRevisionSelectionProvider.revisionSelected(revision);
1243: updateOverviewAnnotations();
1244: postRedraw();
1245: }
1246:
1247: /**
1248: * Handles the selection of a revision id and informs listeners
1249: *
1250: * @param id the selected revision id
1251: */
1252: void handleRevisionSelected(String id) {
1253: Assert.isLegal(id != null);
1254: if (fRevisionInfo == null)
1255: return;
1256:
1257: for (Iterator it = fRevisionInfo.getRevisions().iterator(); it
1258: .hasNext();) {
1259: Revision revision = (Revision) it.next();
1260: if (id.equals(revision.getId())) {
1261: handleRevisionSelected(revision);
1262: return;
1263: }
1264: }
1265:
1266: // clear selection if it does not exist
1267: handleRevisionSelected((Revision) null);
1268: }
1269:
1270: /**
1271: * Returns the selection provider.
1272: *
1273: * @return the selection provider
1274: */
1275: public RevisionSelectionProvider getRevisionSelectionProvider() {
1276: return fRevisionSelectionProvider;
1277: }
1278:
1279: /**
1280: * Updates the focus line with a new line.
1281: *
1282: * @param line the new focus line, -1 for no focus
1283: */
1284: private void updateFocusLine(int line) {
1285: if (fFocusLine != line)
1286: onFocusLineChanged(fFocusLine, line);
1287: }
1288:
1289: /**
1290: * Handles a changing focus line.
1291: *
1292: * @param previousLine the old focus line (-1 for no focus)
1293: * @param nextLine the new focus line (-1 for no focus)
1294: */
1295: private void onFocusLineChanged(int previousLine, int nextLine) {
1296: if (DEBUG)
1297: System.out
1298: .println("line: " + previousLine + " > " + nextLine); //$NON-NLS-1$ //$NON-NLS-2$
1299: fFocusLine = nextLine;
1300: RevisionRange region = getRange(nextLine);
1301: updateFocusRange(region);
1302: }
1303:
1304: /**
1305: * Updates the focus range.
1306: *
1307: * @param range the new focus range, <code>null</code> for no focus
1308: */
1309: private void updateFocusRange(RevisionRange range) {
1310: if (range != fFocusRange)
1311: onFocusRangeChanged(fFocusRange, range);
1312: }
1313:
1314: /**
1315: * Handles a changing focus range.
1316: *
1317: * @param previousRange the old focus range (<code>null</code> for no focus)
1318: * @param nextRange the new focus range (<code>null</code> for no focus)
1319: */
1320: private void onFocusRangeChanged(RevisionRange previousRange,
1321: RevisionRange nextRange) {
1322: if (DEBUG)
1323: System.out
1324: .println("range: " + previousRange + " > " + nextRange); //$NON-NLS-1$ //$NON-NLS-2$
1325: fFocusRange = nextRange;
1326: Revision revision = nextRange == null ? null : nextRange
1327: .getRevision();
1328: updateFocusRevision(revision);
1329: }
1330:
1331: private void updateFocusRevision(Revision revision) {
1332: if (fFocusRevision != revision)
1333: onFocusRevisionChanged(fFocusRevision, revision);
1334: }
1335:
1336: /**
1337: * Handles a changing focus revision.
1338: *
1339: * @param previousRevision the old focus revision (<code>null</code> for no focus)
1340: * @param nextRevision the new focus revision (<code>null</code> for no focus)
1341: */
1342: private void onFocusRevisionChanged(Revision previousRevision,
1343: Revision nextRevision) {
1344: if (DEBUG)
1345: System.out
1346: .println("revision: " + previousRevision + " > " + nextRevision); //$NON-NLS-1$ //$NON-NLS-2$
1347: fFocusRevision = nextRevision;
1348: uninstallWheelHandler();
1349: installWheelHandler();
1350: updateOverviewAnnotations();
1351: redraw(); // pick up new highlights
1352: }
1353:
1354: /**
1355: * Uninstalls the mouse wheel handler.
1356: */
1357: private void uninstallWheelHandler() {
1358: fControl.removeListener(SWT.MouseWheel, fMouseHandler);
1359: fWheelHandlerInstalled = false;
1360: }
1361:
1362: /**
1363: * Installs the mouse wheel handler.
1364: */
1365: private void installWheelHandler() {
1366: if (fFocusRevision != null && !fWheelHandlerInstalled) {
1367: fControl.addListener(SWT.MouseWheel, fMouseHandler);
1368: fWheelHandlerInstalled = true;
1369: }
1370: }
1371:
1372: /**
1373: * Handles a mouse wheel event.
1374: *
1375: * @param event the mouse wheel event
1376: */
1377: private void handleMouseWheel(Event event) {
1378: boolean up = event.count > 0;
1379: int documentHoverLine = fFocusLine;
1380:
1381: ILineRange nextWidgetRange = null;
1382: ILineRange last = null;
1383: List ranges = fFocusRevision.getRegions();
1384: if (up) {
1385: for (Iterator it = ranges.iterator(); it.hasNext();) {
1386: RevisionRange range = (RevisionRange) it.next();
1387: ILineRange widgetRange = modelLinesToWidgetLines(range);
1388: if (contains(range, documentHoverLine)) {
1389: nextWidgetRange = last;
1390: break;
1391: }
1392: if (widgetRange != null)
1393: last = widgetRange;
1394: }
1395: } else {
1396: for (ListIterator it = ranges.listIterator(ranges.size()); it
1397: .hasPrevious();) {
1398: RevisionRange range = (RevisionRange) it.previous();
1399: ILineRange widgetRange = modelLinesToWidgetLines(range);
1400: if (contains(range, documentHoverLine)) {
1401: nextWidgetRange = last;
1402: break;
1403: }
1404: if (widgetRange != null)
1405: last = widgetRange;
1406: }
1407: }
1408:
1409: if (nextWidgetRange == null)
1410: return;
1411:
1412: int widgetCurrentFocusLine = modelLinesToWidgetLines(
1413: new LineRange(documentHoverLine, 1)).getStartLine();
1414: int widgetNextFocusLine = nextWidgetRange.getStartLine();
1415: int newTopPixel = fWidget.getTopPixel()
1416: + JFaceTextUtil.computeLineHeight(fWidget,
1417: widgetCurrentFocusLine, widgetNextFocusLine,
1418: widgetNextFocusLine - widgetCurrentFocusLine);
1419: fWidget.setTopPixel(newTopPixel);
1420: if (newTopPixel < 0) {
1421: Point cursorLocation = fWidget.getDisplay()
1422: .getCursorLocation();
1423: cursorLocation.y += newTopPixel;
1424: fWidget.getDisplay().setCursorLocation(cursorLocation);
1425: } else {
1426: int topPixel = fWidget.getTopPixel();
1427: if (topPixel < newTopPixel) {
1428: Point cursorLocation = fWidget.getDisplay()
1429: .getCursorLocation();
1430: cursorLocation.y += newTopPixel - topPixel;
1431: fWidget.getDisplay().setCursorLocation(cursorLocation);
1432: }
1433: }
1434: updateFocusLine(toDocumentLineNumber(fWidget.toControl(fWidget
1435: .getDisplay().getCursorLocation()).y));
1436: immediateUpdate();
1437: }
1438:
1439: /**
1440: * Triggers a redraw in the display thread.
1441: */
1442: private final void postRedraw() {
1443: if (isConnected() && !fControl.isDisposed()) {
1444: Display d = fControl.getDisplay();
1445: if (d != null) {
1446: d.asyncExec(new Runnable() {
1447: public void run() {
1448: redraw();
1449: }
1450: });
1451: }
1452: }
1453: }
1454:
1455: /**
1456: * Translates a y coordinate in the pixel coordinates of the column's control to a document line
1457: * number.
1458: *
1459: * @param y the y coordinate
1460: * @return the corresponding document line, -1 for no line
1461: * @see CompositeRuler#toDocumentLineNumber(int)
1462: */
1463: private int toDocumentLineNumber(int y) {
1464: return fParentRuler.toDocumentLineNumber(y);
1465: }
1466:
1467: /**
1468: * Triggers redrawing of the column.
1469: */
1470: private void redraw() {
1471: fColumn.redraw();
1472: }
1473:
1474: /**
1475: * Triggers immediate redrawing of the entire column - use with care.
1476: */
1477: private void immediateUpdate() {
1478: fParentRuler.immediateUpdate();
1479: }
1480:
1481: /**
1482: * Returns the width of the column.
1483: *
1484: * @return the width of the column
1485: */
1486: private int getWidth() {
1487: return fColumn.getWidth();
1488: }
1489:
1490: /**
1491: * Returns the System background color for list widgets.
1492: *
1493: * @return the System background color for list widgets
1494: */
1495: private Color getBackground() {
1496: if (fBackground == null)
1497: return fWidget.getDisplay().getSystemColor(
1498: SWT.COLOR_LIST_BACKGROUND);
1499: return fBackground;
1500: }
1501:
1502: /**
1503: * Sets the hover later returned by {@link #getHover()}.
1504: *
1505: * @param hover the hover
1506: */
1507: public void setHover(IAnnotationHover hover) {
1508: // TODO ignore for now - must make revision hover settable from outside
1509: }
1510:
1511: /**
1512: * Returns <code>true</code> if the receiver can provide a hover for a certain document line.
1513: *
1514: * @param activeLine the document line of interest
1515: * @return <code>true</code> if the receiver can provide a hover
1516: */
1517: public boolean hasHover(int activeLine) {
1518: return fViewer instanceof ISourceViewer
1519: && fHover.getHoverLineRange((ISourceViewer) fViewer,
1520: activeLine) != null;
1521: }
1522:
1523: /**
1524: * Returns the revision at a certain document offset, or <code>null</code> for none.
1525: *
1526: * @param offset the document offset
1527: * @return the revision at offset, or <code>null</code> for none
1528: */
1529: Revision getRevision(int offset) {
1530: IDocument document = fViewer.getDocument();
1531: int line;
1532: try {
1533: line = document.getLineOfOffset(offset);
1534: } catch (BadLocationException x) {
1535: return null;
1536: }
1537: if (line != -1) {
1538: RevisionRange range = getRange(line);
1539: if (range != null)
1540: return range.getRevision();
1541: }
1542: return null;
1543: }
1544:
1545: /**
1546: * Returns <code>true</code> if a revision model has been set, <code>false</code> otherwise.
1547: *
1548: * @return <code>true</code> if a revision model has been set, <code>false</code> otherwise
1549: */
1550: public boolean hasInformation() {
1551: return fRevisionInfo != null;
1552: }
1553:
1554: /**
1555: * Returns the width in chars required to display information.
1556: *
1557: * @return the width in chars required to display information
1558: * @since 3.3
1559: */
1560: public int getRequiredWidth() {
1561: if (fRequiredWidth == -1) {
1562: if (hasInformation() && (fShowRevision || fShowAuthor)) {
1563: int revisionWidth = 0;
1564: int authorWidth = 0;
1565: for (Iterator it = fRevisionInfo.getRevisions()
1566: .iterator(); it.hasNext();) {
1567: Revision revision = (Revision) it.next();
1568: revisionWidth = Math.max(revisionWidth, revision
1569: .getId().length());
1570: authorWidth = Math.max(authorWidth, revision
1571: .getAuthor().length());
1572: }
1573: fRevisionIdChars = revisionWidth + 1;
1574: if (fShowAuthor && fShowRevision)
1575: fRequiredWidth = revisionWidth + authorWidth + 2;
1576: else if (fShowAuthor)
1577: fRequiredWidth = authorWidth + 1;
1578: else
1579: fRequiredWidth = revisionWidth + 1;
1580: } else {
1581: fRequiredWidth = 0;
1582: }
1583: }
1584: return fRequiredWidth;
1585: }
1586:
1587: /**
1588: * Enables showing the revision id.
1589: *
1590: * @param show <code>true</code> to show the revision, <code>false</code> to hide it
1591: */
1592: public void showRevisionId(boolean show) {
1593: if (fShowRevision != show) {
1594: fRequiredWidth = -1;
1595: fRevisionIdChars = 0;
1596: fShowRevision = show;
1597: postRedraw();
1598: }
1599: }
1600:
1601: /**
1602: * Enables showing the revision author.
1603: *
1604: * @param show <code>true</code> to show the author, <code>false</code> to hide it
1605: */
1606: public void showRevisionAuthor(boolean show) {
1607: if (fShowAuthor != show) {
1608: fRequiredWidth = -1;
1609: fRevisionIdChars = 0;
1610: fShowAuthor = show;
1611: postRedraw();
1612: }
1613: }
1614:
1615: /**
1616: * Adds a revision listener.
1617: *
1618: * @param listener the listener
1619: * @since 3.3
1620: */
1621: public void addRevisionListener(IRevisionListener listener) {
1622: fRevisionListeners.add(listener);
1623: }
1624:
1625: /**
1626: * Removes a revision listener.
1627: *
1628: * @param listener the listener
1629: * @since 3.3
1630: */
1631: public void removeRevisionListener(IRevisionListener listener) {
1632: fRevisionListeners.remove(listener);
1633: }
1634:
1635: /**
1636: * Informs the revision listeners about a change.
1637: *
1638: * @since 3.3
1639: */
1640: private void informListeners() {
1641: if (fRevisionInfo == null || fRevisionListeners.isEmpty())
1642: return;
1643:
1644: RevisionEvent event = new RevisionEvent(fRevisionInfo);
1645: Object[] listeners = fRevisionListeners.getListeners();
1646: for (int i = 0; i < listeners.length; i++) {
1647: IRevisionListener listener = (IRevisionListener) listeners[i];
1648: listener.revisionInformationChanged(event);
1649: }
1650: }
1651: }
|