blob: 9957682d058f840a776ebfc1dd6cfb0a15e2fc0f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Ralf Heydenreich - Bug 559694
*******************************************************************************/
package org.eclipse.e4.ui.internal.dialogs.about;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.program.Program;
/**
* Manages links in styled text.
*/
public class AboutText {
private String aboutProperty;
private StyledText styledText;
private Cursor handCursor;
private Cursor busyCursor;
private boolean mouseDown = false;
private boolean dragEvent = false;
private AboutItem item;
public AboutText(final Supplier<AboutItem> item) {
this.setItem(item.get());
}
public AboutText(StyledText text, final Supplier<AboutItem> item) {
this.styledText = text;
this.setItem(item.get());
createCursors();
addListeners();
}
public AboutText(String aboutProperty) {
this.aboutProperty = aboutProperty;
}
/**
* Scan the contents of the about text
*
* @return
*/
private void createAboutItem() {
if (aboutProperty == null || aboutProperty.isEmpty()) {
return;
}
HyperlinkExtractor hyperlinkExtractor = new HyperlinkExtractor(aboutProperty);
setItem(new AboutItem(aboutProperty, hyperlinkExtractor.getLinkRanges(), hyperlinkExtractor.getLinks()));
}
private void createCursors() {
handCursor = new Cursor(styledText.getDisplay(), SWT.CURSOR_HAND);
busyCursor = new Cursor(styledText.getDisplay(), SWT.CURSOR_WAIT);
styledText.addDisposeListener((DisposeEvent e) -> {
handCursor.dispose();
handCursor = null;
busyCursor.dispose();
busyCursor = null;
});
}
/**
* Adds listeners to the given styled text
*/
protected void addListeners() {
styledText.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
if (e.button != 1) {
return;
}
mouseDown = true;
}
@Override
public void mouseUp(MouseEvent e) {
mouseDown = false;
int offset = styledText.getCaretOffset();
if (dragEvent) {
// don't activate a link during a drag/mouse up operation
dragEvent = false;
if (item != null && item.isLinkAt(offset)) {
styledText.setCursor(handCursor);
}
} else if (item != null && item.isLinkAt(offset)) {
styledText.setCursor(busyCursor);
item.getLinkAt(offset).ifPresent(l -> Program.launch(l));
StyleRange selectionRange = getCurrentRange();
styledText.setSelectionRange(selectionRange.start, selectionRange.length);
styledText.setCursor(null);
}
}
});
styledText.addMouseMoveListener((MouseEvent e) -> {
// Do not change cursor on drag events
if (mouseDown) {
if (!dragEvent) {
StyledText text = (StyledText) e.widget;
text.setCursor(null);
}
dragEvent = true;
return;
}
StyledText text = (StyledText) e.widget;
int offset = -1;
try {
offset = text.getOffsetAtPoint(new Point(e.x, e.y));
} catch (IllegalArgumentException ex) {
// leave value as -1
}
if (offset == -1) {
text.setCursor(null);
} else if (item != null && item.isLinkAt(offset)) {
text.setCursor(handCursor);
} else {
text.setCursor(null);
}
});
styledText.addTraverseListener(new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent e) {
switch (e.detail) {
case SWT.TRAVERSE_ESCAPE:
e.doit = true;
break;
case SWT.TRAVERSE_TAB_NEXT:
// Previously traverse out in the backward direction?
Point nextSelection = styledText.getSelection();
int charCount = styledText.getCharCount();
if ((nextSelection.x == charCount) && (nextSelection.y == charCount)) {
styledText.setSelection(0);
}
StyleRange nextRange = findNextRange();
if (nextRange == null) {
// Next time in start at beginning, also used by
// TRAVERSE_TAB_PREVIOUS to indicate we traversed out
// in the forward direction
styledText.setSelection(0);
} else {
styledText.setSelectionRange(nextRange.start, nextRange.length);
e.detail = SWT.TRAVERSE_NONE;
}
e.doit = true;
break;
case SWT.TRAVERSE_TAB_PREVIOUS:
// Previously traverse out in the forward direction?
Point previousSelection = styledText.getSelection();
if ((previousSelection.x == 0) && (previousSelection.y == 0)) {
styledText.setSelection(styledText.getCharCount());
}
StyleRange previousRange = findPreviousRange();
if (previousRange == null) {
// Next time in start at the end, also used by
// TRAVERSE_TAB_NEXT to indicate we traversed out
// in the backward direction
styledText.setSelection(styledText.getCharCount());
} else {
styledText.setSelectionRange(previousRange.start, previousRange.length);
e.detail = SWT.TRAVERSE_NONE;
}
e.doit = true;
break;
default:
break;
}
}
});
// Listen for Tab and Space to allow keyboard navigation
styledText.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent event) {
StyledText text = (StyledText) event.widget;
if (event.character == ' ' || event.character == SWT.CR) {
if (item != null) {
// Be sure we are in the selection
int offset = text.getSelection().x + 1;
if (item.isLinkAt(offset)) {
text.setCursor(busyCursor);
item.getLinkAt(offset).ifPresent(l -> Program.launch(l));
StyleRange selectionRange = getCurrentRange();
text.setSelectionRange(selectionRange.start, selectionRange.length);
text.setCursor(null);
}
}
return;
}
}
});
}
/**
* Gets the about item.
*
* @return the about item
*/
public Optional<AboutItem> getAboutItem() {
if (item == null) {
createAboutItem();
}
return Optional.ofNullable(item);
}
/**
* Sets the about item.
*
* @param item about item
*/
private void setItem(AboutItem item) {
this.item = item;
if (item != null && styledText != null) {
styledText.setText(item.getText());
setLinkRanges(item.getLinkRanges());
}
}
/**
* Find the range of the current selection.
*/
private StyleRange getCurrentRange() {
StyleRange[] ranges = styledText.getStyleRanges();
int currentSelectionEnd = styledText.getSelection().y;
int currentSelectionStart = styledText.getSelection().x;
return Arrays.stream(ranges).filter(
range -> (currentSelectionStart >= range.start && currentSelectionEnd <= range.start + range.length))
.findFirst().orElse(null);
}
/**
* Find the next range after the current selection.
*/
private StyleRange findNextRange() {
StyleRange[] ranges = styledText.getStyleRanges();
int currentSelectionEnd = styledText.getSelection().y;
return Arrays.stream(ranges).filter(range -> (range.start >= currentSelectionEnd)).findFirst().orElse(null);
}
/**
* Find the previous range before the current selection.
*/
private StyleRange findPreviousRange() {
StyleRange[] ranges = styledText.getStyleRanges();
int currentSelectionStart = styledText.getSelection().x;
for (int i = ranges.length - 1; i > -1; i--) {
if ((ranges[i].start + ranges[i].length - 1) < currentSelectionStart) {
return ranges[i];
}
}
return null;
}
/**
* Sets the styled text's link (blue) ranges
*/
private void setLinkRanges(final List<HyperlinkRange> linkRanges) {
Color fg = JFaceColors.getHyperlinkText(styledText.getShell().getDisplay());
if (fg == null) {
// TODO use CSS styling!
// fall back (update JFaceRegistry)
JFaceResources.getColorRegistry().put(JFacePreferences.HYPERLINK_COLOR, new RGB(0, 0, 128));
fg = JFaceColors.getHyperlinkText(styledText.getShell().getDisplay());
}
for (HyperlinkRange linkRange : linkRanges) {
StyleRange range = new StyleRange(linkRange.offset(), linkRange.length(), fg, null);
styledText.setStyleRange(range);
}
}
}