blob: 0a5dabad38a453c127c963b16dd9b2c174adadc8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors: IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.ui.console;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentAdapter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.LineBackgroundEvent;
import org.eclipse.swt.custom.LineBackgroundListener;
import org.eclipse.swt.custom.LineStyleEvent;
import org.eclipse.swt.custom.LineStyleListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.internal.console.ConsoleDocumentAdapter;
import org.eclipse.ui.internal.console.ConsoleHyperlinkPosition;
/**
* Default viewer used to display a <code>TextConsole</code>.
* <p>
* Clients may subclass this class.
* </p>
* @since 3.1
*/
public class TextConsoleViewer extends TextViewer implements LineStyleListener, LineBackgroundListener, MouseTrackListener, MouseMoveListener, MouseListener, PaintListener {
/**
* Adapts document to the text widget.
*/
private ConsoleDocumentAdapter documentAdapter;
private IHyperlink hyperlink;
private Cursor handCursor;
private Cursor textCursor;
private int consoleWidth = -1;
private TextConsole console;
private IPropertyChangeListener propertyChangeListener;
/**
* Constructs a new viewer in the given parent for the specified console.
*
* @param parent containing widget
* @param console text console
*/
public TextConsoleViewer(Composite parent, TextConsole console) {
super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
this.console = console;
setDocument(console.getDocument());
StyledText styledText = getTextWidget();
styledText.setDoubleClickEnabled(true);
styledText.addLineStyleListener(this);
styledText.addLineBackgroundListener(this);
styledText.setEditable(true);
setFont(console.getFont());
styledText.addMouseTrackListener(this);
styledText.addPaintListener(this);
ColorRegistry colorRegistry = JFaceResources.getColorRegistry();
propertyChangeListener = new HyperlinkColorChangeListener();
colorRegistry.addListener(propertyChangeListener);
}
/**
* Sets the tab width used by this viewer.
*
* @param tabWidth the tab width used by this viewer
*/
public void setTabWidth(int tabWidth) {
StyledText styledText = getTextWidget();
styledText.setTabs(tabWidth);
styledText.redraw();
}
/**
* Sets the font used by this viewer.
*
* @param font the font used by this viewer
*/
public void setFont(Font font) {
StyledText styledText = getTextWidget();
styledText.setFont(font);
styledText.redraw();
}
/**
* Positions the cursor at the end of the document.
*/
protected void revealEndOfDocument() {
StyledText text = getTextWidget();
if (text != null) {
int charCount = text.getCharCount();
text.setCaretOffset(charCount);
text.showSelection();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.custom.LineStyleListener#lineGetStyle(org.eclipse.swt.custom.LineStyleEvent)
*/
public void lineGetStyle(LineStyleEvent event) {
IDocument document = getDocument();
if (document != null && document.getLength() > 0) {
ArrayList ranges = new ArrayList();
int offset = event.lineOffset;
int length = event.lineText.length();
StyleRange[] partitionerStyles = ((IConsoleDocumentPartitioner) document.getDocumentPartitioner()).getStyleRanges(event.lineOffset, event.lineText.length());
if (partitionerStyles != null) {
for (int i = 0; i < partitionerStyles.length; i++) {
ranges.add(partitionerStyles[i]);
}
}
try {
Display display = ConsolePlugin.getStandardDisplay();
Color hyperlinkText = JFaceColors.getHyperlinkText(display);
Position[] positions = getDocument().getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
Position[] overlap = findPosition(offset, length, positions);
if (overlap != null) {
for (int i = 0; i < overlap.length; i++) {
weave(ranges, new StyleRange(overlap[i].offset, overlap[i].length, hyperlinkText, null));
}
}
if (hyperlink != null) {
IRegion region = console.getRegion(hyperlink);
Color color = JFaceColors.getActiveHyperlinkText(Display.getCurrent());
StyleRange linkRange = new StyleRange(region.getOffset(), region.getLength(), color, null);
override(ranges, linkRange);
}
} catch (BadPositionCategoryException e) {
}
if (ranges.size() > 0) {
event.styles = (StyleRange[]) ranges.toArray(new StyleRange[ranges.size()]);
}
}
}
/**
* Weaves the given style range into the given list of style ranges. The given
* range may overlap ranges in the list of ranges, and must be split into
* non-overlapping ranges and inserted into the list to maintain order.
*
* @param ranges
* @param styleRange
*/
private void weave(List ranges, StyleRange newRange) {
if (ranges.isEmpty()) {
ranges.add(newRange);
return;
}
int start = newRange.start;
int end = start + newRange.length;
for (int i = 0; i < ranges.size(); i++) {
StyleRange existingRange = (StyleRange) ranges.get(i);
int rEnd = existingRange.start + existingRange.length;
if (start < existingRange.start) {
if (end >= existingRange.start) {
ranges.add(i, new StyleRange(start, existingRange.start - start, newRange.foreground, newRange.background));
if (end > rEnd) {
start = rEnd + 1;
} else {
return;
}
} else {
}
} else if (start < rEnd) {
if (end > rEnd) {
start = rEnd + 1;
} else {
return;
}
}
}
if (start < end) {
ranges.add(new StyleRange(start, end - start, newRange.foreground, newRange.background));
}
}
private void override(List ranges, StyleRange newRange) {
if (ranges.isEmpty()) {
ranges.add(newRange);
return;
}
int start = newRange.start;
int end = start + newRange.length;
for (int i = 0; i < ranges.size(); i++) {
StyleRange existingRange = (StyleRange) ranges.get(i);
int rEnd = existingRange.start + existingRange.length;
if (end <= existingRange.start || start >= rEnd) {
continue;
}
if (start >= existingRange.start && end <=rEnd) {
existingRange.length = start - existingRange.start - 1;
ranges.add(++i, newRange);
if (end != rEnd) {
ranges.add(++i, new StyleRange(end, rEnd-end-1, existingRange.foreground, existingRange.background));
}
return;
} else if (start >= existingRange.start && start < rEnd) {
existingRange.length = start-existingRange.start;
ranges.add(++i, newRange);
} else if (end >= rEnd) {
ranges.remove(i);
} else {
ranges.add(++i, new StyleRange(end+1, rEnd-end+1, existingRange.foreground, existingRange.background));
}
}
}
/**
* Binary search for the positions overlapping the given range
*
* @param offset the offset of the range
* @param length the length of the range
* @param positions the positions to search
* @return the positions overlapping the given range, or <code>null</code>
*/
private Position[] findPosition(int offset, int length, Position[] positions) {
if (positions.length == 0)
return null;
int rangeEnd = offset + length;
int left= 0;
int right= positions.length - 1;
int mid= 0;
Position position= null;
while (left < right) {
mid= (left + right) / 2;
position= positions[mid];
if (rangeEnd < position.getOffset()) {
if (left == mid)
right= left;
else
right= mid -1;
} else if (offset > (position.getOffset() + position.getLength() - 1)) {
if (right == mid)
left= right;
else
left= mid +1;
} else {
left= right= mid;
}
}
List list = new ArrayList();
int index = left - 1;
if (index >= 0) {
position= positions[index];
while (index >= 0 && (position.getOffset() + position.getLength()) > offset) {
index--;
if (index > 0) {
position= positions[index];
}
}
}
index++;
position= positions[index];
while (index < positions.length && (position.getOffset() < rangeEnd)) {
list.add(position);
index++;
if (index < positions.length) {
position= positions[index];
}
}
if (list.isEmpty()) {
return null;
}
return (Position[])list.toArray(new Position[list.size()]);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.custom.LineBackgroundListener#lineGetBackground(org.eclipse.swt.custom.LineBackgroundEvent)
*/
public void lineGetBackground(LineBackgroundEvent event) {
event.lineBackground = null;
}
/**
* @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
*/
public void paintControl(PaintEvent e) {
if (hyperlink != null) {
IDocument doc = getDocument();
StyledText text = getTextWidget();
if (doc == null || text == null) {
return;
}
IConsoleDocumentPartitioner partitioner = (IConsoleDocumentPartitioner) doc.getDocumentPartitioner();
if (partitioner == null) {
return;
}
IRegion linkRegion = console.getRegion(hyperlink);
if (linkRegion != null) {
int start = linkRegion.getOffset();
int end = start + linkRegion.getLength();
Color fontColor = JFaceColors.getActiveHyperlinkText(Display.getCurrent());
Color color = e.gc.getForeground();
e.gc.setForeground(fontColor);
FontMetrics metrics = e.gc.getFontMetrics();
int height = metrics.getHeight();
int width = metrics.getAverageCharWidth();
int startLine = text.getLineAtOffset(start);
int endLine = text.getLineAtOffset(end);
int baseLineBias = text.getBaseline() - (metrics.getAscent() + metrics.getLeading());
for (int i = startLine; i <= endLine; i++) {
int styleStart = i == startLine ? start : text.getOffsetAtLine(i);
int styleEnd = i == endLine ? end : text.getOffsetAtLine(i + 1);
Point p1 = text.getLocationAtOffset(styleStart);
Point p2 = text.getLocationAtOffset(styleEnd - 1);
e.gc.drawLine(p1.x, p1.y + height + baseLineBias, p2.x + width, p2.y + height + baseLineBias);
}
e.gc.setForeground(color);
}
}
}
/**
* Returns the hand cursor.
*
* @return the hand cursor
*/
protected Cursor getHandCursor() {
if (handCursor == null) {
handCursor = new Cursor(ConsolePlugin.getStandardDisplay(), SWT.CURSOR_HAND);
}
return handCursor;
}
/**
* Returns the text cursor.
*
* @return the text cursor
*/
protected Cursor getTextCursor() {
if (textCursor == null) {
textCursor = new Cursor(ConsolePlugin.getStandardDisplay(), SWT.CURSOR_IBEAM);
}
return textCursor;
}
/**
* Notification a hyperlink has been entered.
*
* @param link the link that was entered
*/
protected void linkEntered(IHyperlink link) {
Control control = getTextWidget();
control.setRedraw(false);
if (hyperlink != null) {
linkExited(hyperlink);
}
hyperlink = link;
hyperlink.linkEntered();
control.setCursor(getHandCursor());
control.setRedraw(true);
control.redraw();
control.addMouseListener(this);
}
/**
* Notification a link was exited.
*
* @param link the link that was exited
*/
protected void linkExited(IHyperlink link) {
link.linkExited();
hyperlink = null;
Control control = getTextWidget();
control.setCursor(getTextCursor());
control.redraw();
control.removeMouseListener(this);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseTrackListener#mouseEnter(org.eclipse.swt.events.MouseEvent)
*/
public void mouseEnter(MouseEvent e) {
getTextWidget().addMouseMoveListener(this);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseTrackListener#mouseExit(org.eclipse.swt.events.MouseEvent)
*/
public void mouseExit(MouseEvent e) {
getTextWidget().removeMouseMoveListener(this);
if (hyperlink != null) {
linkExited(hyperlink);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt.events.MouseEvent)
*/
public void mouseHover(MouseEvent e) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
*/
public void mouseMove(MouseEvent e) {
int offset = -1;
try {
Point p = new Point(e.x, e.y);
offset = getTextWidget().getOffsetAtLocation(p);
} catch (IllegalArgumentException ex) {
// out of the document range
}
updateLinks(offset);
}
/**
* The cursor has just be moved to the given offset, the mouse has hovered
* over the given offset. Update link rendering.
*
* @param offset
*/
protected void updateLinks(int offset) {
if (offset >= 0) {
IHyperlink link = getHyperlink(offset);
if (link != null) {
if (link.equals(hyperlink)) {
return;
}
linkEntered(link);
return;
}
}
if (hyperlink != null) {
linkExited(hyperlink);
}
}
/**
* Returns the currently active hyperlink or <code>null</code> if none.
*
* @return the currently active hyperlink or <code>null</code> if none
*/
public IHyperlink getHyperlink() {
return hyperlink;
}
/**
* Returns the hyperlink at the specified offset, or <code>null</code> if none.
*
* @param offset offset at which a hyperlink has been requested
* @return hyperlink at the specified offset, or <code>null</code> if none
*/
public IHyperlink getHyperlink(int offset) {
if (offset >= 0 && console != null) {
return console.getHyperlink(offset);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDoubleClick(MouseEvent e) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
*/
public void mouseDown(MouseEvent e) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
*/
public void mouseUp(MouseEvent e) {
if (hyperlink != null) {
String selection = getTextWidget().getSelectionText();
if (selection.length() <= 0) {
if (e.button == 1) {
hyperlink.linkActivated();
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.TextViewer#createDocumentAdapter()
*/
protected IDocumentAdapter createDocumentAdapter() {
if (documentAdapter == null) {
documentAdapter = new ConsoleDocumentAdapter(consoleWidth = -1);
}
return documentAdapter;
}
/**
* Sets the console to have a fixed character width. Use -1 to indicate that a fixed
* width should not be used.
*
* @param consoleWidth fixed characater width of the console, or -1
*/
public void setConsoleWidth(int width) {
consoleWidth = width;
if (documentAdapter != null) {
documentAdapter.setWidth(consoleWidth);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.TextViewer#handleDispose()
*/
protected void handleDispose() {
super.handleDispose();
StyledText styledText = getTextWidget();
styledText.removeLineStyleListener(this);
styledText.removeLineBackgroundListener(this);
styledText.removeMouseTrackListener(this);
styledText.removePaintListener(this);
handCursor = null;
textCursor = null;
hyperlink = null;
console = null;
ColorRegistry colorRegistry = JFaceResources.getColorRegistry();
colorRegistry.removeListener(propertyChangeListener);
}
class HyperlinkColorChangeListener implements IPropertyChangeListener {
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(JFacePreferences.ACTIVE_HYPERLINK_COLOR) || event.getProperty().equals(JFacePreferences.HYPERLINK_COLOR)) {
getTextWidget().redraw();
}
}
}
}