001: // Copyright (c) 2000, 2005 BlueJ Group, Deakin University
002: //
003: // This software is made available under the terms of the "MIT License"
004: // A copy of this license is included with this source distribution
005: // in "license.txt" and is also available at:
006: // http://www.opensource.org/licenses/mit-license.html
007: // Any queries should be directed to Michael Kolling mik@bluej.org
008:
009: package bluej.editor.moe;
010:
011: /**
012: * MoeSyntaxView.java - adapted from
013: * SyntaxView.java - jEdit's own Swing view implementation
014: * to add Syntax highlighting to the BlueJ programming environment.
015: */
016:
017: import javax.swing.text.*;
018:
019: import java.awt.*;
020: import org.syntax.jedit.tokenmarker.*;
021: import org.syntax.jedit.*;
022:
023: /**
024: * A Swing view implementation that colorizes lines of a
025: * SyntaxDocument using a TokenMarker.
026: *
027: * This class should not be used directly; a SyntaxEditorKit
028: * should be used instead.
029: *
030: * @author Slava Pestov
031: * @author Bruce Quig
032: * @author Michael Kolling
033: *
034: * @version $Id: BlueJSyntaxView.java 5366 2007-11-01 05:27:26Z davmac $
035: */
036:
037: public abstract class BlueJSyntaxView extends PlainView {
038: /** width of tag area for setting breakpoints */
039: public static final short TAG_WIDTH = 14;
040: protected static final int BREAKPOINT_OFFSET = TAG_WIDTH + 2;
041:
042: // private members
043: private Segment line;
044:
045: private Font defaultFont;
046: // protected FontMetrics metrics; is inherited from PlainView
047: private Font lineNumberFont;
048: private Font smallLineNumberFont;
049: FontMetrics lineNumberMetrics;
050: private boolean initialised = false;
051:
052: /**
053: * Creates a new BlueJSyntaxView.
054: * @param elem The element
055: */
056: public BlueJSyntaxView(Element elem) {
057: super (elem);
058: line = new Segment();
059: }
060:
061: /**
062: * Paints the specified line.
063: *
064: * This method performs the following:
065: *
066: * - Gets the token marker and color table from the current document,
067: * typecast to a SyntaxDocument.
068: * - Tokenizes the required line by calling the
069: * markTokens() method of the token marker.
070: * - Paints each token, obtaining the color by looking up the
071: * the Token.id value in the color table.
072: *
073: * If either the document doesn't implement
074: * SyntaxDocument, or if the returned token marker is
075: * null, the line will be painted with no colorization.
076: *
077: * Currently, we assume that the whole document uses the same font.
078: * To support font changes, some of the code from "initilise" needs
079: * to be here to be done repeatedly for each line.
080: *
081: * @param lineIndex The line number
082: * @param g The graphics context
083: * @param x The x co-ordinate where the line should be painted
084: * @param y The y co-ordinate where the line should be painted
085: */
086: protected void drawLine(int lineIndex, Graphics g, int x, int y) {
087: if (!initialised)
088: initialise(g);
089:
090: SyntaxDocument document = (SyntaxDocument) getDocument();
091: TokenMarker tokenMarker = document.getTokenMarker();
092:
093: Color def = MoeSyntaxDocument.getDefaultColor();
094:
095: try {
096: Element lineElement = getElement().getElement(lineIndex);
097: int start = lineElement.getStartOffset();
098: int end = lineElement.getEndOffset();
099:
100: document.getText(start, end - (start + 1), line);
101: g.setColor(def);
102:
103: paintTaggedLine(line, lineIndex, g, x, y, document,
104: tokenMarker, def, lineElement);
105: } catch (BadLocationException bl) {
106: // shouldn't happen
107: bl.printStackTrace();
108: }
109: }
110:
111: /**
112: * Draw a line for this view, including the tag mark.
113: */
114: public abstract void paintTaggedLine(Segment line, int lineIndex,
115: Graphics g, int x, int y, SyntaxDocument document,
116: TokenMarker tokenMarker, Color def, Element lineElement);
117:
118: /**
119: * Draw the line number in front of the line
120: */
121: protected void drawLineNumber(Graphics g, int lineNumber, int x,
122: int y) {
123: g.setColor(Color.darkGray);
124:
125: String number = Integer.toString(lineNumber);
126: int stringWidth = lineNumberMetrics.stringWidth(number);
127: int xoffset = BREAKPOINT_OFFSET - stringWidth - 4;
128:
129: if (xoffset < -2) // if it doesn't fit, shift one pixel over.
130: xoffset++;
131:
132: if (xoffset < -2) { // if it still doesn't fit...
133: g.setFont(smallLineNumberFont);
134: g.drawString(number, x - 3, y);
135: } else {
136: g.setFont(lineNumberFont);
137: g.drawString(number, x + xoffset, y);
138: }
139: g.setFont(defaultFont);
140: }
141:
142: /**
143: * paints a line with syntax highlighting,
144: * redefined from DefaultSyntaxDocument.
145: *
146: */
147: protected void paintSyntaxLine(Segment line, int lineIndex, int x,
148: int y, Graphics g, SyntaxDocument document,
149: TokenMarker tokenMarker, Color def) {
150: Color[] colors = document.getColors();
151: Token tokens = tokenMarker.markTokens(line, lineIndex);
152: int offset = 0;
153: for (;;) {
154: byte id = tokens.id;
155: if (id == Token.END)
156: break;
157:
158: int length = tokens.length;
159: Color color;
160: if (id == Token.NULL)
161: color = def;
162: else {
163: // check we are within the array bounds
164: // safeguard for updated syntax package
165: if (id < colors.length)
166: color = colors[id];
167: else
168: color = def;
169: }
170: g.setColor(color == null ? def : color);
171:
172: line.count = length;
173: x = Utilities.drawTabbedText(line, x, y, g, this , offset);
174: line.offset += length;
175: offset += length;
176:
177: tokens = tokens.next;
178: }
179: }
180:
181: /**
182: * Check whether a given line is tagged with a given tag.
183: * @param line The line to check
184: * @param tag The name of the tag
185: * @return True, if the tag is set
186: */
187: protected final boolean hasTag(Element line, String tag) {
188: return Boolean.TRUE.equals(line.getAttributes().getAttribute(
189: tag));
190: }
191:
192: /**
193: * Initialise some fields after we get a graphics context for the first time
194: */
195: private void initialise(Graphics g) {
196: defaultFont = g.getFont();
197: lineNumberFont = defaultFont.deriveFont(9.0f);
198: smallLineNumberFont = defaultFont.deriveFont(7.0f);
199: Component c = getContainer();
200: lineNumberMetrics = c.getFontMetrics(lineNumberFont);
201: initialised = true;
202: }
203:
204: /**
205: * Return default foreground colour
206: */
207: protected Color getDefaultColor() {
208: return getContainer().getForeground();
209: }
210:
211: /**
212: * Provides a mapping from the document model coordinate space
213: * to the coordinate space of the view mapped to it. This is a
214: * redefined method from PlainView that adds an offset for the
215: * view to allow for a breakpoint area in the associated editor.
216: *
217: * @param pos the position to convert >= 0
218: * @param a the allocated region to render into
219: * @return the bounding box of the given position
220: * @exception BadLocationException if the given position does not
221: * represent a valid location in the associated document
222: * @see View#modelToView
223: */
224: public Shape modelToView(int pos, Shape a, Position.Bias b)
225: throws BadLocationException {
226: // line coordinates
227: Document doc = getDocument();
228: Element map = getElement();
229: int lineIndex = map.getElementIndex(pos);
230: Rectangle lineArea = lineToRect(a, lineIndex);
231:
232: // determine span from the start of the line
233: int tabBase = lineArea.x + TAG_WIDTH + 2;
234:
235: Element line = map.getElement(lineIndex);
236: int p0 = line.getStartOffset();
237: Segment buffer = getLineBuffer();
238: doc.getText(p0, pos - p0, buffer);
239: int xOffs = Utilities.getTabbedTextWidth(buffer, metrics,
240: tabBase, this , p0);
241:
242: // fill in the results and return, include breakpoint area offset
243: lineArea.x += xOffs + (TAG_WIDTH + 2);
244: lineArea.width = 1;
245: lineArea.height = metrics.getHeight();
246: return lineArea;
247: }
248:
249: /**
250: * Provides a mapping from the view coordinate space to the logical
251: * coordinate space of the model.
252: *
253: * @param fx the X coordinate >= 0
254: * @param fy the Y coordinate >= 0
255: * @param a the allocated region to render into
256: * @return the location within the model that best represents the
257: * given point in the view >= 0
258: * @see View#viewToModel
259: */
260: public int viewToModel(float fx, float fy, Shape a,
261: Position.Bias[] bias) {
262: bias[0] = Position.Bias.Forward;
263:
264: Rectangle alloc = a.getBounds();
265: Document doc = getDocument();
266: int x = (int) fx;
267: int y = (int) fy;
268: if (y < alloc.y) {
269: // above the area covered by this icon, so the the position
270: // is assumed to be the start of the coverage for this view.
271: return getStartOffset();
272: } else if (y > alloc.y + alloc.height) {
273: // below the area covered by this icon, so the the position
274: // is assumed to be the end of the coverage for this view.
275: return getEndOffset() - 1;
276: } else {
277: // positioned within the coverage of this view vertically,
278: // so we figure out which line the point corresponds to.
279: // if the line is greater than the number of lines contained, then
280: // simply use the last line as it represents the last possible place
281: // we can position to.
282: Element map = doc.getDefaultRootElement();
283: int lineIndex = Math.abs((y - alloc.y)
284: / metrics.getHeight());
285: if (lineIndex >= map.getElementCount()) {
286: return getEndOffset() - 1;
287: }
288: Element line = map.getElement(lineIndex);
289: if (x < alloc.x) {
290: // point is to the left of the line
291: return line.getStartOffset();
292: } else if (x > alloc.x + alloc.width) {
293: // point is to the right of the line
294: return line.getEndOffset() - 1;
295: } else {
296: // Determine the offset into the text
297: try {
298: Segment buffer = getLineBuffer();
299: int p0 = line.getStartOffset();
300: int p1 = line.getEndOffset() - 1;
301: doc.getText(p0, p1 - p0, buffer);
302: // add Moe breakpoint offset area width
303: int tabBase = alloc.x + TAG_WIDTH + 2;
304: int offs = p0
305: + Utilities.getTabbedTextOffset(buffer,
306: metrics, tabBase, x, this , p0);
307: return offs;
308: } catch (BadLocationException e) {
309: // should not happen
310: return -1;
311: }
312: }
313: }
314: }
315:
316: // --- TabExpander interface methods -----------------------------------
317:
318: /**
319: * Returns the next tab stop position after a given reference position.
320: * This implementation does not support things like centering so it
321: * ignores the tabOffset argument.
322: *
323: * @param x the current position >= 0
324: * @param tabOffset the position within the text stream
325: * that the tab occurred at >= 0.
326: * @return the tab stop, measured in points >= 0
327: */
328: public float nextTabStop(float x, int tabOffset) {
329: // calculate tabsize using fontwidth and tab spaces
330: int tabSize = getTabSize() * metrics.charWidth('m');
331: if (tabSize == 0) {
332: return x;
333: }
334: int tabStopNumber = (int) ((x - BREAKPOINT_OFFSET) / tabSize) + 1;
335: return (tabStopNumber * tabSize) + BREAKPOINT_OFFSET + 2;
336: }
337:
338: /**
339: * redefined from PlainView private method to allow for redefinition of
340: * modelToView method
341: */
342: public Rectangle lineToRect(Shape a, int line) {
343: Rectangle r = null;
344: if (metrics != null) {
345: Rectangle alloc = a.getBounds();
346: r = new Rectangle(alloc.x, alloc.y
347: + (line * metrics.getHeight()), alloc.width,
348: metrics.getHeight());
349: }
350: return r;
351: }
352: }
|