/******************************************************************************* * Copyright (c) 2000, 2007 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.console; import java.util.HashMap; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.ui.internal.console.ConsoleDocument; import org.eclipse.ui.internal.console.ConsoleHyperlinkPosition; import org.eclipse.ui.internal.console.ConsolePatternMatcher; import org.eclipse.ui.part.IPageBookViewPage; /** * An abstract text console that supports regular expression matching and * hyperlinks. *
* Pattern match listeners can be registered with a console programmatically
* or via the org.eclipse.ui.console.consolePatternMatchListeners
* extension point.
*
* Clients may subclass this class. Subclasses must provide a document partitioner. *
* @since 3.1 */ public abstract class TextConsole extends AbstractConsole { /** * The current width of the console. Used for fixed width consoles. * A value of <=0 means does not have a fixed width. */ private int fConsoleWidth; /** * The current tab width */ private int fTabWidth; /** * The font used by this console */ private Font fFont; /** * The background color used by this console ornull
if default
*/
private Color fBackground;
/**
* The Console's regular expression pattern matcher
*/
private ConsolePatternMatcher fPatternMatcher;
/**
* The Console's document
*/
private ConsoleDocument fDocument;
/**
* indication that the console's partitioner is not expecting more input
*/
private boolean fPartitionerFinished = false;
/**
* Indication that the console's pattern matcher has finished.
* (all matches have been found and all listeners notified)
*/
private boolean fMatcherFinished = false;
/**
* indication that the console output complete property has been fired
*/
private boolean fCompleteFired = false;
/**
* Map of client defined attributes
*/
private HashMap fAttributes = new HashMap();
private IConsoleManager fConsoleManager = ConsolePlugin.getDefault().getConsoleManager();
/* (non-Javadoc)
* @see org.eclipse.ui.console.AbstractConsole#dispose()
*/
protected void dispose() {
super.dispose();
fFont = null;
synchronized(fAttributes) {
fAttributes.clear();
}
}
/**
* Constructs a console with the given name, image descriptor, and lifecycle
*
* @param name name to display for this console
* @param consoleType console type identifier or null
* @param imageDescriptor image to display for this console or null
* @param autoLifecycle whether lifecycle methods should be called automatically
* when this console is added/removed from the console manager
*/
public TextConsole(String name, String consoleType, ImageDescriptor imageDescriptor, boolean autoLifecycle) {
super(name, consoleType, imageDescriptor, autoLifecycle);
fDocument = new ConsoleDocument();
fDocument.addPositionCategory(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
fPatternMatcher = new ConsolePatternMatcher(this);
fDocument.addDocumentListener(fPatternMatcher);
fTabWidth = IConsoleConstants.DEFAULT_TAB_SIZE;
}
/* (non-Javadoc)
* @see org.eclipse.ui.console.IConsole#createPage(org.eclipse.ui.console.IConsoleView)
*/
public IPageBookViewPage createPage(IConsoleView view) {
return new TextConsolePage(this, view);
}
/**
* Returns this console's document.
* * Note that a console may or may not support direct manipulation of its document. * For example, an I/O console document and its partitions are produced from the * streams connected to it, and clients are not intended to modify the document's * contents. *
* * @return this console's document */ public IDocument getDocument() { return fDocument; } /** * Returns the current width of this console. A value of zero of less * indicates this console has no fixed width. * * @return the current width of this console */ public int getConsoleWidth() { return fConsoleWidth; } /** * Sets the width of this console in characters. Any value greater than zero * will cause this console to have a fixed width. * * @param width the width to make this console. Values of 0 or less imply * the console does not have any fixed width. */ public void setConsoleWidth(int width) { if (fConsoleWidth != width) { int old = fConsoleWidth; fConsoleWidth = width; firePropertyChange(this, IConsoleConstants.P_CONSOLE_WIDTH, new Integer(old), new Integer(fConsoleWidth)); } } /** * Sets the tab width used in this console. * * @param newTabWidth the tab width */ public void setTabWidth(final int newTabWidth) { if (fTabWidth != newTabWidth) { final int oldTabWidth = fTabWidth; fTabWidth = newTabWidth; ConsolePlugin.getStandardDisplay().asyncExec(new Runnable() { public void run() { firePropertyChange(TextConsole.this, IConsoleConstants.P_TAB_SIZE, new Integer(oldTabWidth), new Integer(fTabWidth)); } }); } } /** * Returns the tab width used in this console. * * @return tab width used in this console */ public int getTabWidth() { return fTabWidth; } /** * Returns the font used by this console. Must be called in the UI thread. * * @return font used by this console */ public Font getFont() { if (fFont == null) { fFont = getDefaultFont(); } return fFont; } /** * Returns the default text font. * * @return the default text font */ private Font getDefaultFont() { return JFaceResources.getFont(JFaceResources.TEXT_FONT); } /** * Sets the font used by this console. Specifynull
to use
* the default text font.
*
* @param newFont font, or null
to indicate the default font
*/
public void setFont(Font newFont) {
// ensure font is initialized
getFont();
// translate null to default font
if (newFont == null) {
newFont = getDefaultFont();
}
// fire property change if required
if (!fFont.equals(newFont)) {
Font old = fFont;
fFont = newFont;
firePropertyChange(this, IConsoleConstants.P_FONT, old, fFont);
}
}
/**
* Sets the background color used by this console. Specify null
to use
* the default background color.
*
* @param background background color or null
for default
* @since 3.3
* @deprecated use setBackground(Color) instead
*/
public void setBackgrond(Color background) {
setBackground(background);
}
/**
* Sets the background color used by this console. Specify null
to use
* the default background color.
*
* @param background background color or null
for default
* @since 3.3
*/
public void setBackground(Color background) {
if (fBackground == null) {
if (background == null) {
return;
}
} else if (fBackground.equals(background)){
return;
}
Color old = fBackground;
fBackground = background;
firePropertyChange(this, IConsoleConstants.P_BACKGROUND_COLOR, old, fBackground);
}
/**
* Returns the background color to use for this console or null
for the
* default background color.
*
* @return background color or null
for default
* @since 3.3
*/
public Color getBackground() {
return fBackground;
}
/**
* Clears the console.
* * Since a console may or may not support direct manipulation * of its document's contents, this method should be called to clear a text console's * document. The default implementation sets this console's document content * to the empty string directly. Subclasses should override as required. *
*/ public void clearConsole() { IDocument document = getDocument(); if (document != null) { document.set(""); //$NON-NLS-1$ } } /** * Returns the console's document partitioner. * @return The console's document partitioner */ protected abstract IConsoleDocumentPartitioner getPartitioner(); /** * Returns all hyperlinks in this console. * * @return all hyperlinks in this console */ public IHyperlink[] getHyperlinks() { try { Position[] positions = getDocument().getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY); IHyperlink[] hyperlinks = new IHyperlink[positions.length]; for (int i = 0; i < positions.length; i++) { ConsoleHyperlinkPosition position = (ConsoleHyperlinkPosition) positions[i]; hyperlinks[i] = position.getHyperLink(); } return hyperlinks; } catch (BadPositionCategoryException e) { return new IHyperlink[0]; } } /** * Returns the hyperlink at the given offset ofnull
if none.
*
* @param offset offset for which a hyperlink is requested
* @return the hyperlink at the given offset of null
if none
*/
public IHyperlink getHyperlink(int offset) {
try {
IDocument document = getDocument();
if (document != null) {
Position[] positions = document.getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
Position position = findPosition(offset, positions);
if (position instanceof ConsoleHyperlinkPosition) {
return ((ConsoleHyperlinkPosition) position).getHyperLink();
}
}
} catch (BadPositionCategoryException e) {
}
return null;
}
/**
* Binary search for the position at a given offset.
*
* @param offset the offset whose position should be found
* @return the position containing the offset, or null
*/
private Position findPosition(int offset, Position[] positions) {
if (positions.length == 0)
return null;
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 (offset < 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;
}
}
position= positions[left];
if (offset >= position.getOffset() && (offset < (position.getOffset() + position.getLength()))) {
return position;
}
return null;
}
/**
* Adds the given pattern match listener to this console. The listener will
* be connected and receive match notifications. Has no effect if an identical
* listener has already been added.
*
* @param listener the listener to add
*/
public void addPatternMatchListener(IPatternMatchListener listener) {
fPatternMatcher.addPatternMatchListener(listener);
}
/**
* Removes the given pattern match listener from this console. The listener will be
* disconnected and will no longer receive match notifications. Has no effect
* if the listener was not previously added.
*
* @param listener the pattern match listener to remove
*/
public void removePatternMatchListener(IPatternMatchListener listener) {
fPatternMatcher.removePatternMatchListener(listener);
}
/**
* Job scheduling rule that prevent the job from running if the console's PatternMatcher
* is active.
*/
private class MatcherSchedulingRule implements ISchedulingRule {
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
public boolean isConflicting(ISchedulingRule rule) {
if (contains(rule)) {
return true;
}
if (rule != this && rule instanceof MatcherSchedulingRule) {
return (((MatcherSchedulingRule)rule).getConsole() == TextConsole.this);
}
return false;
}
public TextConsole getConsole() {
return TextConsole.this;
}
}
/**
* Returns a scheduling rule which can be used to prevent jobs from running
* while this console's pattern matcher is active.
* * Although this scheduling rule prevents jobs from running at the same time as * pattern matching jobs for this console, it does not enforce any ordering of jobs. * Since 3.2, pattern matching jobs belong to the job family identified by the console * object that matching is occurring on. To ensure a job runs after all scheduled pattern * matching is complete, clients must join on this console's job family. *
* @return a scheduling rule which can be used to prevent jobs from running * while this console's pattern matcher is active */ public ISchedulingRule getSchedulingRule() { return new MatcherSchedulingRule(); } /** * This console's partitioner should call this method when it is not expecting any new data * to be appended to the document. */ public void partitionerFinished() { fPatternMatcher.forceFinalMatching(); fPartitionerFinished = true; checkFinished(); } /** * Called by this console's pattern matcher when matching is complete. ** Clients should not call this method. *
*/ public void matcherFinished() { fMatcherFinished = true; fDocument.removeDocumentListener(fPatternMatcher); checkFinished(); } /** * Fires the console output complete property change event. */ private synchronized void checkFinished() { if (!fCompleteFired && fPartitionerFinished && fMatcherFinished ) { fCompleteFired = true; firePropertyChange(this, IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE, null, null); } } /** * Adds a hyperlink to this console. * * @param hyperlink the hyperlink to add * @param offset the offset in the console document at which the hyperlink should be added * @param length the length of the text which should be hyperlinked * @throws BadLocationException if the specified location is not valid. */ public void addHyperlink(IHyperlink hyperlink, int offset, int length) throws BadLocationException { IDocument document = getDocument(); ConsoleHyperlinkPosition hyperlinkPosition = new ConsoleHyperlinkPosition(hyperlink, offset, length); try { document.addPosition(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY, hyperlinkPosition); fConsoleManager.refresh(this); } catch (BadPositionCategoryException e) { ConsolePlugin.log(e); } } /** * Returns the region associated with the given hyperlink. * * @param link hyperlink * @return the region associated with the hyperlink or null if the hyperlink is not found. */ public IRegion getRegion(IHyperlink link) { try { IDocument doc = getDocument(); if (doc != null) { Position[] positions = doc.getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY); for (int i = 0; i < positions.length; i++) { ConsoleHyperlinkPosition position = (ConsoleHyperlinkPosition)positions[i]; if (position.getHyperLink().equals(link)) { return new Region(position.getOffset(), position.getLength()); } } } } catch (BadPositionCategoryException e) { } return null; } /** * Returns the attribute associated with the specified key. * * @param key attribute key * @return the attribute associated with the specified key */ public Object getAttribute(String key) { synchronized (fAttributes) { return fAttributes.get(key); } } /** * Sets an attribute value. Intended for client data. * * @param key attribute key * @param value attribute value */ public void setAttribute(String key, Object value) { synchronized(fAttributes) { fAttributes.put(key, value); } } }