001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.console;
011:
012: import java.util.HashMap;
013:
014: import org.eclipse.core.runtime.jobs.ISchedulingRule;
015: import org.eclipse.jface.resource.ImageDescriptor;
016: import org.eclipse.jface.resource.JFaceResources;
017: import org.eclipse.jface.text.BadLocationException;
018: import org.eclipse.jface.text.BadPositionCategoryException;
019: import org.eclipse.jface.text.IDocument;
020: import org.eclipse.jface.text.IRegion;
021: import org.eclipse.jface.text.Position;
022: import org.eclipse.jface.text.Region;
023: import org.eclipse.swt.graphics.Color;
024: import org.eclipse.swt.graphics.Font;
025: import org.eclipse.ui.internal.console.ConsoleDocument;
026: import org.eclipse.ui.internal.console.ConsoleHyperlinkPosition;
027: import org.eclipse.ui.internal.console.ConsolePatternMatcher;
028: import org.eclipse.ui.part.IPageBookViewPage;
029:
030: /**
031: * An abstract text console that supports regular expression matching and
032: * hyperlinks.
033: * <p>
034: * Pattern match listeners can be registered with a console programmatically
035: * or via the <code>org.eclipse.ui.console.consolePatternMatchListeners</code>
036: * extension point.
037: * </p>
038: * <p>
039: * Clients may subclass this class. Subclasses must provide a document partitioner.
040: * </p>
041: * @since 3.1
042: */
043: public abstract class TextConsole extends AbstractConsole {
044:
045: /**
046: * The current width of the console. Used for fixed width consoles.
047: * A value of <=0 means does not have a fixed width.
048: */
049: private int fConsoleWidth;
050: /**
051: * The current tab width
052: */
053: private int fTabWidth;
054: /**
055: * The font used by this console
056: */
057: private Font fFont;
058:
059: /**
060: * The background color used by this console or <code>null</code> if default
061: */
062: private Color fBackground;
063:
064: /**
065: * The Console's regular expression pattern matcher
066: */
067: private ConsolePatternMatcher fPatternMatcher;
068:
069: /**
070: * The Console's document
071: */
072: private ConsoleDocument fDocument;
073:
074: /**
075: * indication that the console's partitioner is not expecting more input
076: */
077: private boolean fPartitionerFinished = false;
078:
079: /**
080: * Indication that the console's pattern matcher has finished.
081: * (all matches have been found and all listeners notified)
082: */
083: private boolean fMatcherFinished = false;
084:
085: /**
086: * indication that the console output complete property has been fired
087: */
088: private boolean fCompleteFired = false;
089:
090: /**
091: * Map of client defined attributes
092: */
093: private HashMap fAttributes = new HashMap();
094:
095: private IConsoleManager fConsoleManager = ConsolePlugin
096: .getDefault().getConsoleManager();
097:
098: /* (non-Javadoc)
099: * @see org.eclipse.ui.console.AbstractConsole#dispose()
100: */
101: protected void dispose() {
102: super .dispose();
103: fFont = null;
104: synchronized (fAttributes) {
105: fAttributes.clear();
106: }
107: }
108:
109: /**
110: * Constructs a console with the given name, image descriptor, and lifecycle
111: *
112: * @param name name to display for this console
113: * @param consoleType console type identifier or <code>null</code>
114: * @param imageDescriptor image to display for this console or <code>null</code>
115: * @param autoLifecycle whether lifecycle methods should be called automatically
116: * when this console is added/removed from the console manager
117: */
118: public TextConsole(String name, String consoleType,
119: ImageDescriptor imageDescriptor, boolean autoLifecycle) {
120: super (name, consoleType, imageDescriptor, autoLifecycle);
121: fDocument = new ConsoleDocument();
122: fDocument
123: .addPositionCategory(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
124: fPatternMatcher = new ConsolePatternMatcher(this );
125: fDocument.addDocumentListener(fPatternMatcher);
126: fTabWidth = IConsoleConstants.DEFAULT_TAB_SIZE;
127: }
128:
129: /* (non-Javadoc)
130: * @see org.eclipse.ui.console.IConsole#createPage(org.eclipse.ui.console.IConsoleView)
131: */
132: public IPageBookViewPage createPage(IConsoleView view) {
133: return new TextConsolePage(this , view);
134: }
135:
136: /**
137: * Returns this console's document.
138: * <p>
139: * Note that a console may or may not support direct manipulation of its document.
140: * For example, an I/O console document and its partitions are produced from the
141: * streams connected to it, and clients are not intended to modify the document's
142: * contents.
143: * </p>
144: *
145: * @return this console's document
146: */
147: public IDocument getDocument() {
148: return fDocument;
149: }
150:
151: /**
152: * Returns the current width of this console. A value of zero of less
153: * indicates this console has no fixed width.
154: *
155: * @return the current width of this console
156: */
157: public int getConsoleWidth() {
158: return fConsoleWidth;
159: }
160:
161: /**
162: * Sets the width of this console in characters. Any value greater than zero
163: * will cause this console to have a fixed width.
164: *
165: * @param width the width to make this console. Values of 0 or less imply
166: * the console does not have any fixed width.
167: */
168: public void setConsoleWidth(int width) {
169: if (fConsoleWidth != width) {
170: int old = fConsoleWidth;
171: fConsoleWidth = width;
172:
173: firePropertyChange(this , IConsoleConstants.P_CONSOLE_WIDTH,
174: new Integer(old), new Integer(fConsoleWidth));
175: }
176: }
177:
178: /**
179: * Sets the tab width used in this console.
180: *
181: * @param newTabWidth the tab width
182: */
183: public void setTabWidth(final int newTabWidth) {
184: if (fTabWidth != newTabWidth) {
185: final int oldTabWidth = fTabWidth;
186: fTabWidth = newTabWidth;
187: ConsolePlugin.getStandardDisplay().asyncExec(
188: new Runnable() {
189: public void run() {
190: firePropertyChange(TextConsole.this ,
191: IConsoleConstants.P_TAB_SIZE,
192: new Integer(oldTabWidth),
193: new Integer(fTabWidth));
194: }
195: });
196: }
197: }
198:
199: /**
200: * Returns the tab width used in this console.
201: *
202: * @return tab width used in this console
203: */
204: public int getTabWidth() {
205: return fTabWidth;
206: }
207:
208: /**
209: * Returns the font used by this console. Must be called in the UI thread.
210: *
211: * @return font used by this console
212: */
213: public Font getFont() {
214: if (fFont == null) {
215: fFont = getDefaultFont();
216: }
217: return fFont;
218: }
219:
220: /**
221: * Returns the default text font.
222: *
223: * @return the default text font
224: */
225: private Font getDefaultFont() {
226: return JFaceResources.getFont(JFaceResources.TEXT_FONT);
227: }
228:
229: /**
230: * Sets the font used by this console. Specify <code>null</code> to use
231: * the default text font.
232: *
233: * @param newFont font, or <code>null</code> to indicate the default font
234: */
235: public void setFont(Font newFont) {
236: // ensure font is initialized
237: getFont();
238: // translate null to default font
239: if (newFont == null) {
240: newFont = getDefaultFont();
241: }
242: // fire property change if required
243: if (!fFont.equals(newFont)) {
244: Font old = fFont;
245: fFont = newFont;
246: firePropertyChange(this , IConsoleConstants.P_FONT, old,
247: fFont);
248: }
249: }
250:
251: /**
252: * Sets the background color used by this console. Specify <code>null</code> to use
253: * the default background color.
254: *
255: * @param background background color or <code>null</code> for default
256: * @since 3.3
257: * @deprecated use setBackground(Color) instead
258: */
259: public void setBackgrond(Color background) {
260: setBackground(background);
261: }
262:
263: /**
264: * Sets the background color used by this console. Specify <code>null</code> to use
265: * the default background color.
266: *
267: * @param background background color or <code>null</code> for default
268: * @since 3.3
269: */
270: public void setBackground(Color background) {
271: if (fBackground == null) {
272: if (background == null) {
273: return;
274: }
275: } else if (fBackground.equals(background)) {
276: return;
277: }
278: Color old = fBackground;
279: fBackground = background;
280: firePropertyChange(this , IConsoleConstants.P_BACKGROUND_COLOR,
281: old, fBackground);
282: }
283:
284: /**
285: * Returns the background color to use for this console or <code>null</code> for the
286: * default background color.
287: *
288: * @return background color or <code>null</code> for default
289: * @since 3.3
290: */
291: public Color getBackground() {
292: return fBackground;
293: }
294:
295: /**
296: * Clears the console.
297: * <p>
298: * Since a console may or may not support direct manipulation
299: * of its document's contents, this method should be called to clear a text console's
300: * document. The default implementation sets this console's document content
301: * to the empty string directly. Subclasses should override as required.
302: * </p>
303: */
304: public void clearConsole() {
305: IDocument document = getDocument();
306: if (document != null) {
307: document.set(""); //$NON-NLS-1$
308: }
309: }
310:
311: /**
312: * Returns the console's document partitioner.
313: * @return The console's document partitioner
314: */
315: protected abstract IConsoleDocumentPartitioner getPartitioner();
316:
317: /**
318: * Returns all hyperlinks in this console.
319: *
320: * @return all hyperlinks in this console
321: */
322: public IHyperlink[] getHyperlinks() {
323: try {
324: Position[] positions = getDocument().getPositions(
325: ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
326: IHyperlink[] hyperlinks = new IHyperlink[positions.length];
327: for (int i = 0; i < positions.length; i++) {
328: ConsoleHyperlinkPosition position = (ConsoleHyperlinkPosition) positions[i];
329: hyperlinks[i] = position.getHyperLink();
330: }
331: return hyperlinks;
332: } catch (BadPositionCategoryException e) {
333: return new IHyperlink[0];
334: }
335: }
336:
337: /**
338: * Returns the hyperlink at the given offset of <code>null</code> if none.
339: *
340: * @param offset offset for which a hyperlink is requested
341: * @return the hyperlink at the given offset of <code>null</code> if none
342: */
343: public IHyperlink getHyperlink(int offset) {
344: try {
345: IDocument document = getDocument();
346: if (document != null) {
347: Position[] positions = document
348: .getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
349: Position position = findPosition(offset, positions);
350: if (position instanceof ConsoleHyperlinkPosition) {
351: return ((ConsoleHyperlinkPosition) position)
352: .getHyperLink();
353: }
354: }
355: } catch (BadPositionCategoryException e) {
356: }
357: return null;
358: }
359:
360: /**
361: * Binary search for the position at a given offset.
362: *
363: * @param offset the offset whose position should be found
364: * @return the position containing the offset, or <code>null</code>
365: */
366: private Position findPosition(int offset, Position[] positions) {
367:
368: if (positions.length == 0)
369: return null;
370:
371: int left = 0;
372: int right = positions.length - 1;
373: int mid = 0;
374: Position position = null;
375:
376: while (left < right) {
377:
378: mid = (left + right) / 2;
379:
380: position = positions[mid];
381: if (offset < position.getOffset()) {
382: if (left == mid)
383: right = left;
384: else
385: right = mid - 1;
386: } else if (offset > (position.getOffset()
387: + position.getLength() - 1)) {
388: if (right == mid)
389: left = right;
390: else
391: left = mid + 1;
392: } else {
393: left = right = mid;
394: }
395: }
396:
397: position = positions[left];
398: if (offset >= position.getOffset()
399: && (offset < (position.getOffset() + position
400: .getLength()))) {
401: return position;
402: }
403: return null;
404: }
405:
406: /**
407: * Adds the given pattern match listener to this console. The listener will
408: * be connected and receive match notifications. Has no effect if an identical
409: * listener has already been added.
410: *
411: * @param listener the listener to add
412: */
413: public void addPatternMatchListener(IPatternMatchListener listener) {
414: fPatternMatcher.addPatternMatchListener(listener);
415: }
416:
417: /**
418: * Removes the given pattern match listener from this console. The listener will be
419: * disconnected and will no longer receive match notifications. Has no effect
420: * if the listener was not previously added.
421: *
422: * @param listener the pattern match listener to remove
423: */
424: public void removePatternMatchListener(
425: IPatternMatchListener listener) {
426: fPatternMatcher.removePatternMatchListener(listener);
427: }
428:
429: /**
430: * Job scheduling rule that prevent the job from running if the console's PatternMatcher
431: * is active.
432: */
433: private class MatcherSchedulingRule implements ISchedulingRule {
434: public boolean contains(ISchedulingRule rule) {
435: return rule == this ;
436: }
437:
438: public boolean isConflicting(ISchedulingRule rule) {
439: if (contains(rule)) {
440: return true;
441: }
442: if (rule != this && rule instanceof MatcherSchedulingRule) {
443: return (((MatcherSchedulingRule) rule).getConsole() == TextConsole.this );
444: }
445: return false;
446: }
447:
448: public TextConsole getConsole() {
449: return TextConsole.this ;
450: }
451: }
452:
453: /**
454: * Returns a scheduling rule which can be used to prevent jobs from running
455: * while this console's pattern matcher is active.
456: * <p>
457: * Although this scheduling rule prevents jobs from running at the same time as
458: * pattern matching jobs for this console, it does not enforce any ordering of jobs.
459: * Since 3.2, pattern matching jobs belong to the job family identified by the console
460: * object that matching is occurring on. To ensure a job runs after all scheduled pattern
461: * matching is complete, clients must join on this console's job family.
462: * </p>
463: * @return a scheduling rule which can be used to prevent jobs from running
464: * while this console's pattern matcher is active
465: */
466: public ISchedulingRule getSchedulingRule() {
467: return new MatcherSchedulingRule();
468: }
469:
470: /**
471: * This console's partitioner should call this method when it is not expecting any new data
472: * to be appended to the document.
473: */
474: public void partitionerFinished() {
475: fPatternMatcher.forceFinalMatching();
476: fPartitionerFinished = true;
477: checkFinished();
478: }
479:
480: /**
481: * Called by this console's pattern matcher when matching is complete.
482: * <p>
483: * Clients should not call this method.
484: * <p>
485: */
486: public void matcherFinished() {
487: fMatcherFinished = true;
488: fDocument.removeDocumentListener(fPatternMatcher);
489: checkFinished();
490: }
491:
492: /**
493: * Fires the console output complete property change event.
494: */
495: private synchronized void checkFinished() {
496: if (!fCompleteFired && fPartitionerFinished && fMatcherFinished) {
497: fCompleteFired = true;
498: firePropertyChange(this ,
499: IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE, null,
500: null);
501: }
502: }
503:
504: /**
505: * Adds a hyperlink to this console.
506: *
507: * @param hyperlink the hyperlink to add
508: * @param offset the offset in the console document at which the hyperlink should be added
509: * @param length the length of the text which should be hyperlinked
510: * @throws BadLocationException if the specified location is not valid.
511: */
512: public void addHyperlink(IHyperlink hyperlink, int offset,
513: int length) throws BadLocationException {
514: IDocument document = getDocument();
515: ConsoleHyperlinkPosition hyperlinkPosition = new ConsoleHyperlinkPosition(
516: hyperlink, offset, length);
517: try {
518: document.addPosition(
519: ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY,
520: hyperlinkPosition);
521: fConsoleManager.refresh(this );
522: } catch (BadPositionCategoryException e) {
523: ConsolePlugin.log(e);
524: }
525: }
526:
527: /**
528: * Returns the region associated with the given hyperlink.
529: *
530: * @param link hyperlink
531: * @return the region associated with the hyperlink or null if the hyperlink is not found.
532: */
533: public IRegion getRegion(IHyperlink link) {
534: try {
535: IDocument doc = getDocument();
536: if (doc != null) {
537: Position[] positions = doc
538: .getPositions(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY);
539: for (int i = 0; i < positions.length; i++) {
540: ConsoleHyperlinkPosition position = (ConsoleHyperlinkPosition) positions[i];
541: if (position.getHyperLink().equals(link)) {
542: return new Region(position.getOffset(),
543: position.getLength());
544: }
545: }
546: }
547: } catch (BadPositionCategoryException e) {
548: }
549: return null;
550: }
551:
552: /**
553: * Returns the attribute associated with the specified key.
554: *
555: * @param key attribute key
556: * @return the attribute associated with the specified key
557: */
558: public Object getAttribute(String key) {
559: synchronized (fAttributes) {
560: return fAttributes.get(key);
561: }
562: }
563:
564: /**
565: * Sets an attribute value. Intended for client data.
566: *
567: * @param key attribute key
568: * @param value attribute value
569: */
570: public void setAttribute(String key, Object value) {
571: synchronized (fAttributes) {
572: fAttributes.put(key, value);
573: }
574: }
575: }
|