/******************************************************************************* * Copyright (c) 2004, 2012 Tasktop Technologies 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: * Tasktop Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.internal.tasks.ui.editors; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextPresentationListener; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewerExtension4; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; import org.eclipse.mylyn.tasks.ui.IHighlightingHyperlink; import org.eclipse.mylyn.tasks.ui.TaskHyperlink; import org.eclipse.swt.custom.StyleRange; /** * A manager that ensures that all task hyperlinks have the appropriate text presentation. Subclasses may specify logic * for filtering detected hyperlinks and text decoration. * * @author David Green */ public abstract class AbstractHyperlinkTextPresentationManager { /** * Regions with a lower offset and a shorter length are ordered before other regions. */ public static class RegionComparator implements Comparator<IRegion> { public int compare(IRegion o1, IRegion o2) { if (o1 == o2) { return 0; } if (o1.getOffset() < o2.getOffset()) { return -1; } else if (o1.getOffset() > o2.getOffset()) { return 1; } else if (o1.getLength() < o2.getLength()) { return -1; } else if (o1.getLength() > o2.getLength()) { return 1; } else { return 0; } } } private class Support implements ITextPresentationListener { public void applyTextPresentation(TextPresentation textPresentation) { StyleRange[] styleRanges = computeStyleRanges(textPresentation.getCoverage()); if (styleRanges != null && styleRanges.length > 0) { textPresentation.mergeStyleRanges(styleRanges); } } } private static final RegionComparator REGION_COMPARATOR = new RegionComparator(); private IHyperlinkDetector[] hyperlinkDetectors; private final ITextPresentationListener textPresentationListener = new Support(); private ITextViewer viewer; public AbstractHyperlinkTextPresentationManager() { } private void addRange(List<StyleRange> ranges, int start, int end) { // the style range is to be merged with other styles, so only set the bits that are needed StyleRange styleRange = new StyleRange(start, end - start, null, null); decorate(styleRange); ranges.add(styleRange); } protected StyleRange[] computeStyleRanges(IRegion extent) { if (viewer == null || hyperlinkDetectors == null || viewer.getDocument() == null || extent == null) { return null; } List<IRegion> regions = getRegions(extent); if (regions != null) { // style ranges may be adjacent but must not overlap, and they must be in order // of increasing offset. List<StyleRange> ranges = new ArrayList<StyleRange>(regions.size()); // sort them first to ensure increasing offset Collections.sort(regions, REGION_COMPARATOR); // now merge overlapping (and adjacent) ranges int start = -1; int end = -1; for (int x = 0; x < regions.size(); ++x) { IRegion region = regions.get(x); if (start == -1) { start = region.getOffset(); end = region.getOffset() + region.getLength(); } else { if (region.getOffset() >= end) { addRange(ranges, start, end); start = region.getOffset(); end = region.getOffset() + region.getLength(); } else { end = region.getOffset() + region.getLength(); } } } if (start != -1) { addRange(ranges, start, end); } return ranges.toArray(new StyleRange[ranges.size()]); } return null; } protected abstract void decorate(StyleRange styleRange); /** * The hyperlink detectors that are used to detect {@link TaskHyperlink task hyperlinks} */ public IHyperlinkDetector[] getHyperlinkDetector() { return hyperlinkDetectors; } protected List<IRegion> getRegions(IRegion extent) { List<IRegion> regions = null; for (IHyperlinkDetector hyperlinkDetector : hyperlinkDetectors) { IHyperlink[] hyperlinks = hyperlinkDetector.detectHyperlinks(viewer, extent, true); if (hyperlinks != null && hyperlinks.length > 0) { for (IHyperlink hyperlink : hyperlinks) { if (select(hyperlink)) { if (regions == null) { regions = new ArrayList<IRegion>(); } regions.add(hyperlink instanceof IHighlightingHyperlink ? ((IHighlightingHyperlink) hyperlink).getHighlightingRegion() : hyperlink.getHyperlinkRegion()); } } } } return regions; } public void install(ITextViewer viewer) { this.viewer = viewer; if (viewer instanceof ITextViewerExtension4) { ((ITextViewerExtension4) viewer).addTextPresentationListener(textPresentationListener); } } public boolean select(IHyperlink hyperlink) { return true; } /** * The hyperlink detector that are used to detect {@link TaskHyperlink task hyperlinks} */ public void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors) { this.hyperlinkDetectors = hyperlinkDetectors; } public void uninstall() { ((ITextViewerExtension4) viewer).removeTextPresentationListener(textPresentationListener); this.viewer = null; } }