Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java')
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java425
1 files changed, 425 insertions, 0 deletions
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java
new file mode 100644
index 0000000000..6b3d746276
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/HyperlinkSourceViewer.java
@@ -0,0 +1,425 @@
+/*******************************************************************************
+ * Copyright (C) 2015 Thomas Wolf <thomas.wolf@paranor.ch>.
+ *
+ * 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
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.dialogs;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2;
+import org.eclipse.jface.text.source.IOverviewRuler;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.SourceViewer;
+import org.eclipse.jface.text.source.SourceViewerConfiguration;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.ui.texteditor.HyperlinkDetectorDescriptor;
+
+/**
+ * A {@link SourceViewer} that automatically reacts to changes in the
+ * hyperlinking preferences.
+ */
+public class HyperlinkSourceViewer extends SourceViewer {
+ // The default SourceViewer doesn't do this and instead AbstractTextEditor
+ // has code that does all that. For our uses,it is much more convenient if
+ // the viewer itself handles this.
+
+ private Configuration configuration;
+
+ private Set<String> preferenceKeysForEnablement;
+
+ private Set<String> preferenceKeysForActivation;
+
+ private IPropertyChangeListener hyperlinkChangeListener;
+
+ /**
+ * Constructs a new source viewer. The vertical ruler is initially visible.
+ * The viewer has not yet been initialized with a source viewer
+ * configuration.
+ *
+ * @param parent
+ * the parent of the viewer's control
+ * @param ruler
+ * the vertical ruler used by this source viewer
+ * @param styles
+ * the SWT style bits for the viewer's control,
+ */
+ public HyperlinkSourceViewer(Composite parent, IVerticalRuler ruler,
+ int styles) {
+ super(parent, ruler, styles);
+ }
+
+ /**
+ * Constructs a new source viewer. The vertical ruler is initially visible.
+ * The overview ruler visibility is controlled by the value of
+ * <code>showAnnotationsOverview</code>. The viewer has not yet been
+ * initialized with a source viewer configuration.
+ *
+ * @param parent
+ * the parent of the viewer's control
+ * @param verticalRuler
+ * the vertical ruler used by this source viewer
+ * @param overviewRuler
+ * the overview ruler
+ * @param showAnnotationsOverview
+ * {@code true} if the overview ruler should be visible,
+ * {@code false} otherwise
+ * @param styles
+ * the SWT style bits for the viewer's control,
+ */
+ public HyperlinkSourceViewer(Composite parent, IVerticalRuler verticalRuler,
+ IOverviewRuler overviewRuler, boolean showAnnotationsOverview,
+ int styles) {
+ super(parent, verticalRuler, overviewRuler, showAnnotationsOverview,
+ styles);
+ }
+
+ @Override
+ public void configure(SourceViewerConfiguration config) {
+ super.configure(config);
+ if (config instanceof Configuration) {
+ configuration = (Configuration) config;
+ configurePreferenceKeys();
+ // Install a listener
+ hyperlinkChangeListener = new IPropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ String property = event.getProperty();
+ if (preferenceKeysForEnablement.contains(property)) {
+ resetHyperlinkDetectors();
+ final Control control = getControl();
+ if (control != null && !control.isDisposed()) {
+ control.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (!control.isDisposed()) {
+ refresh();
+ }
+ }
+ });
+ }
+ } else if (preferenceKeysForActivation.contains(property)) {
+ resetHyperlinkDetectors();
+ }
+ }
+ };
+ EditorsUI.getPreferenceStore()
+ .addPropertyChangeListener(hyperlinkChangeListener);
+ } else {
+ configuration = null;
+ hyperlinkChangeListener = null;
+ }
+ }
+
+ private void configurePreferenceKeys() {
+ preferenceKeysForEnablement = new HashSet<>();
+ preferenceKeysForActivation = new HashSet<>();
+ // Global settings (master switch)
+ preferenceKeysForEnablement
+ .add(AbstractTextEditor.PREFERENCE_HYPERLINKS_ENABLED);
+ preferenceKeysForActivation
+ .add(AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER);
+ // All applicable individual hyperlink detectors settings.
+ Map targets = configuration.getHyperlinkDetectorTargets(this);
+ for (HyperlinkDetectorDescriptor desc : EditorsUI
+ .getHyperlinkDetectorRegistry()
+ .getHyperlinkDetectorDescriptors()) {
+ if (targets.keySet().contains(desc.getTargetId())) {
+ preferenceKeysForEnablement.add(desc.getId());
+ preferenceKeysForActivation.add(desc.getId()
+ + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX);
+ }
+ }
+ }
+
+ private void resetHyperlinkDetectors() {
+ IHyperlinkDetector[] detectors = configuration
+ .getHyperlinkDetectors(this);
+ int stateMask = configuration.getHyperlinkStateMask(this);
+ setHyperlinkDetectors(detectors, stateMask);
+ }
+
+ @Override
+ public void unconfigure() {
+ super.unconfigure();
+ if (hyperlinkChangeListener != null) {
+ EditorsUI.getPreferenceStore()
+ .removePropertyChangeListener(hyperlinkChangeListener);
+ }
+ preferenceKeysForEnablement = null;
+ preferenceKeysForActivation = null;
+ }
+
+ /**
+ * A {@link TextSourceViewerConfiguration} for use in conjunction with
+ * {@link HyperlinkSourceViewer}. Includes support for opening hyperlinks on
+ * the configured modifier key, or also when no key is pressed (i.e., on
+ * {@link SWT#NONE}) if
+ * {@link TextSourceViewerConfiguration#getHyperlinkStateMask(ISourceViewer)
+ * getHyperlinkStateMask(ISourceViewer)} returns {@link SWT#NONE}
+ */
+ public static class Configuration extends TextSourceViewerConfiguration {
+
+ /**
+ * Creates a new configuration.
+ */
+ public Configuration() {
+ super();
+ }
+
+ /**
+ * Creates a new configuration and initializes it with the given
+ * preference store.
+ *
+ * @param preferenceStore
+ * the preference store used to initialize this configuration
+ */
+ public Configuration(IPreferenceStore preferenceStore) {
+ super(preferenceStore);
+ }
+
+ /**
+ * Returns the hyperlink detectors which are to be used to detect
+ * hyperlinks in the given source viewer. This implementation uses
+ * {@link #internalGetHyperlinkDetectors(ISourceViewer)} to get the
+ * hyperlink detectors.
+ * <p>
+ * Sets up the hyperlink detetctors such that they are active on both
+ * {@link SWT#NONE} and on the configured modifier key combination if
+ * the viewer is configured to open hyperlinks on direct click, i.e., if
+ * {@link TextSourceViewerConfiguration#getHyperlinkStateMask(ISourceViewer)
+ * getHyperlinkStateMask(ISourceViewer)} returns {@link SWT#NONE}.
+ * </p>
+ *
+ * @param sourceViewer
+ * the {@link ISourceViewer} to be configured by this
+ * configuration
+ * @return an array with {@link IHyperlinkDetector}s or {@code null} if
+ * no hyperlink support should be installed
+ */
+ @Override
+ public final IHyperlinkDetector[] getHyperlinkDetectors(
+ ISourceViewer sourceViewer) {
+ IHyperlinkDetector[] detectors = internalGetHyperlinkDetectors(
+ sourceViewer);
+ if (detectors != null && detectors.length > 0
+ && getHyperlinkStateMask(sourceViewer) == SWT.NONE) {
+ // Duplicate them all with a detector that is active on SWT.NONE
+ int defaultMask = getConfiguredDefaultMask();
+ IHyperlinkDetector[] newDetectors = new IHyperlinkDetector[detectors.length
+ * 2];
+ int j = 0;
+ for (IHyperlinkDetector original : detectors) {
+ if (original instanceof IHyperlinkDetectorExtension2) {
+ int mask = ((IHyperlinkDetectorExtension2) original)
+ .getStateMask();
+ if (mask == -1) {
+ newDetectors[j++] = new FixedMaskHyperlinkDetector(
+ original, defaultMask);
+ if (defaultMask != SWT.NONE) {
+ newDetectors[j++] = new NoMaskHyperlinkDetector(
+ original);
+ }
+ } else {
+ newDetectors[j++] = original;
+ if (mask != SWT.NONE) {
+ newDetectors[j++] = new NoMaskHyperlinkDetector(
+ original);
+ }
+ }
+ } else {
+ newDetectors[j++] = original;
+ }
+ }
+ IHyperlinkDetector[] result = new IHyperlinkDetector[j];
+ System.arraycopy(newDetectors, 0, result, 0, j);
+ return result;
+ }
+ return detectors;
+ }
+
+ /**
+ * Collects the {@link IHyperlinkDetector}s for the given
+ * {@link ISourceViewer}.
+ *
+ * @param sourceViewer
+ * to get the detectors for
+ * @return the hyperlink detectors, or {@code null} if none shall be
+ * installed
+ * @see TextSourceViewerConfiguration#getHyperlinkDetectors(ISourceViewer)
+ */
+ protected @Nullable IHyperlinkDetector[] internalGetHyperlinkDetectors(
+ ISourceViewer sourceViewer) {
+ return super.getHyperlinkDetectors(sourceViewer);
+ }
+
+ @Override
+ protected Map getHyperlinkDetectorTargets(ISourceViewer sourceViewer) {
+ // Just so that we have visibility on this in the enclosing class.
+ return super.getHyperlinkDetectorTargets(sourceViewer);
+ }
+
+ }
+
+ private static abstract class InternalHyperlinkDetector
+ implements IHyperlinkDetector, IHyperlinkDetectorExtension,
+ IHyperlinkDetectorExtension2 {
+
+ protected IHyperlinkDetector delegate;
+
+ protected InternalHyperlinkDetector(IHyperlinkDetector delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public final IHyperlink[] detectHyperlinks(ITextViewer textViewer,
+ IRegion region, boolean canShowMultipleHyperlinks) {
+ return delegate.detectHyperlinks(textViewer, region,
+ canShowMultipleHyperlinks);
+ }
+
+ @Override
+ public final void dispose() {
+ if (delegate instanceof IHyperlinkDetectorExtension) {
+ ((IHyperlinkDetectorExtension) delegate).dispose();
+ }
+ }
+ }
+
+ private static class FixedMaskHyperlinkDetector
+ extends InternalHyperlinkDetector {
+
+ private final int mask;
+
+ protected FixedMaskHyperlinkDetector(IHyperlinkDetector delegate,
+ int mask) {
+ super(delegate);
+ this.mask = mask;
+ }
+
+ @Override
+ public int getStateMask() {
+ return mask;
+ }
+ }
+
+ /**
+ * An {@link IHyperlinkDetector} that activates when no modifier key is
+ * pressed. It's protected so that the {@link HyperlinkTokenScanner} can see
+ * it and can avoid calling those for syntax coloring since they are
+ * duplicates of some other hyperlink detector anyway.
+ */
+ protected static class NoMaskHyperlinkDetector
+ extends FixedMaskHyperlinkDetector {
+
+ private NoMaskHyperlinkDetector(IHyperlinkDetector delegate) {
+ // Private to allow instantiation only within this compilation unit
+ super(delegate, SWT.NONE);
+ }
+
+ }
+
+ private static int getConfiguredDefaultMask() {
+ int mask = computeStateMask(EditorsUI.getPreferenceStore().getString(
+ AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER));
+ if (mask == -1) {
+ // Fallback
+ mask = EditorsUI.getPreferenceStore().getInt(
+ AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER_MASK);
+ }
+ return mask;
+ }
+
+ // The preference for
+ // AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER_MASK is not
+ // recomputed when the user changes preferences. Therefore, we have to
+ // use the AbstractTextEditor.PREFERENCE_HYPERLINK_KEY_MODIFIER and
+ // translate that to a state mask explicitly. Code below copied from
+ // org.eclipse.ui.internal.editors.text.HyperlinkDetectorsConfigurationBlock.
+
+ /**
+ * Maps the localized modifier name to a code in the same manner as
+ * {@link org.eclipse.jface.action.Action#findModifier
+ * Action.findModifier()}.
+ *
+ * @param modifierName
+ * the modifier name
+ * @return the SWT modifier bit, or {@code 0} if no match was found
+ */
+ private static final int findLocalizedModifier(String modifierName) {
+ if (modifierName == null) {
+ return 0;
+ }
+
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.CTRL))) {
+ return SWT.CTRL;
+ }
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.SHIFT))) {
+ return SWT.SHIFT;
+ }
+ if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT))) {
+ return SWT.ALT;
+ }
+ if (modifierName
+ .equalsIgnoreCase(Action.findModifierString(SWT.COMMAND))) {
+ return SWT.COMMAND;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Computes the state mask for the given modifier string.
+ *
+ * @param modifiers
+ * the string with the modifiers, separated by '+', '-', ';', ','
+ * or '.'
+ * @return the state mask or {@code -1} if the input is invalid
+ */
+ private static final int computeStateMask(String modifiers) {
+ if (modifiers == null) {
+ return -1;
+ }
+
+ if (modifiers.length() == 0) {
+ return SWT.NONE;
+ }
+
+ int stateMask = 0;
+ StringTokenizer modifierTokenizer = new StringTokenizer(modifiers,
+ ",;.:+-* "); //$NON-NLS-1$
+ while (modifierTokenizer.hasMoreTokens()) {
+ int modifier = findLocalizedModifier(modifierTokenizer.nextToken());
+ if (modifier == 0 || (stateMask & modifier) == modifier) {
+ return -1;
+ }
+ stateMask = stateMask | modifier;
+ }
+ return stateMask;
+ }
+
+}

Back to the top