1 | /*
|
---|
2 | * 10/28/2004
|
---|
3 | *
|
---|
4 | * VisibleWhitespaceToken.java - Token that paints special symbols for its
|
---|
5 | * whitespace characters (space and tab).
|
---|
6 | *
|
---|
7 | * This library is distributed under a modified BSD license. See the included
|
---|
8 | * RSyntaxTextArea.License.txt file for details.
|
---|
9 | */
|
---|
10 | package org.fife.ui.rsyntaxtextarea;
|
---|
11 |
|
---|
12 | import java.awt.Color;
|
---|
13 | import java.awt.FontMetrics;
|
---|
14 | import java.awt.Graphics2D;
|
---|
15 | import java.awt.geom.Rectangle2D;
|
---|
16 | import javax.swing.text.Segment;
|
---|
17 | import javax.swing.text.TabExpander;
|
---|
18 |
|
---|
19 |
|
---|
20 | /**
|
---|
21 | * This token class paints spaces and tabs with special symbols so the user
|
---|
22 | * can see the whitespace in his document. Rendering hints are honored.<p>
|
---|
23 | *
|
---|
24 | * The current implementation paints as follows:
|
---|
25 | * <ul>
|
---|
26 | * <li>The first tab or space, if any, is found in the token.</li>
|
---|
27 | * <li>If a tab was found, all characters up to it are painted as a
|
---|
28 | * group.</li>
|
---|
29 | * <li>If a space was found, all characters up to and including it are
|
---|
30 | * painted (it is painted with a special symbol to denote it as
|
---|
31 | * a space).</li>
|
---|
32 | * <li>If neither a tab nor a whitespace was found, all characters in the
|
---|
33 | * token are painted.</li>
|
---|
34 | * <li>Repeat until all characters are painted.</li>
|
---|
35 | * </ul>
|
---|
36 | * This means that rendering hints are applied to all groups of characters
|
---|
37 | * within a token, excluding whitespace and tabs.<p>
|
---|
38 | *
|
---|
39 | * A problem with this implementation is that FontMetrics.charsWidth() is still
|
---|
40 | * used to calculate the width of a group of chars painted. Thus, the group of
|
---|
41 | * characters will be painted with the rendering hints specified, but the
|
---|
42 | * following tab (or group of characters if the current group was the end of a
|
---|
43 | * token) will not necessarily be painted at the proper x-coordinate (as
|
---|
44 | * FontMetrics.charsWidth() returns an <code>int</code> and not a
|
---|
45 | * <code>float</code>). The way around this would be to calculate the token's
|
---|
46 | * width in such a way that a float is returned (Font.getStringBounds()?).
|
---|
47 | *
|
---|
48 | * @author Robert Futrell
|
---|
49 | * @version 0.5
|
---|
50 | * @see Token
|
---|
51 | * @see DefaultToken
|
---|
52 | */
|
---|
53 | public class VisibleWhitespaceToken extends DefaultToken {
|
---|
54 |
|
---|
55 | private Rectangle2D.Float dotRect;
|
---|
56 |
|
---|
57 |
|
---|
58 | /**
|
---|
59 | * Creates a "null token." The token itself is not null; rather, it
|
---|
60 | * signifies that it is the last token in a linked list of tokens and
|
---|
61 | * that it is not part of a "multi-line token."
|
---|
62 | */
|
---|
63 | public VisibleWhitespaceToken() {
|
---|
64 | super();
|
---|
65 | dotRect = new Rectangle2D.Float(0,0, 1,1);
|
---|
66 | }
|
---|
67 |
|
---|
68 |
|
---|
69 | /**
|
---|
70 | * Constructor.
|
---|
71 | *
|
---|
72 | * @param line The segment from which to get the token.
|
---|
73 | * @param beg The first character's position in <code>line</code>.
|
---|
74 | * @param end The last character's position in <code>line</code>.
|
---|
75 | * @param startOffset The offset into the document at which this
|
---|
76 | * token begins.
|
---|
77 | * @param type A token type listed as "generic" above.
|
---|
78 | */
|
---|
79 | public VisibleWhitespaceToken(final Segment line, final int beg,
|
---|
80 | final int end, final int startOffset, final int type) {
|
---|
81 | this(line.array, beg,end, startOffset, type);
|
---|
82 | }
|
---|
83 |
|
---|
84 |
|
---|
85 | /**
|
---|
86 | * Constructor.
|
---|
87 | *
|
---|
88 | * @param line The segment from which to get the token.
|
---|
89 | * @param beg The first character's position in <code>line</code>.
|
---|
90 | * @param end The last character's position in <code>line</code>.
|
---|
91 | * @param startOffset The offset into the document at which this
|
---|
92 | * token begins.
|
---|
93 | * @param type A token type listed as "generic" above.
|
---|
94 | */
|
---|
95 | public VisibleWhitespaceToken(final char[] line, final int beg,
|
---|
96 | final int end, final int startOffset, final int type) {
|
---|
97 | super(line, beg,end, startOffset, type);
|
---|
98 | }
|
---|
99 |
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * Paints this token, using special symbols for whitespace characters.
|
---|
103 | *
|
---|
104 | * @param g The graphics context in which to paint.
|
---|
105 | * @param x The x-coordinate at which to paint.
|
---|
106 | * @param y The y-coordinate at which to paint.
|
---|
107 | * @param host The text area this token is in.
|
---|
108 | * @param e How to expand tabs.
|
---|
109 | * @param clipStart The left boundary of the clip rectangle in which we're
|
---|
110 | * painting. This optimizes painting by allowing us to not paint
|
---|
111 | * not paint when this token is "to the left" of the clip rectangle.
|
---|
112 | * @return The x-coordinate representing the end of the painted text.
|
---|
113 | */
|
---|
114 | public final float paint(Graphics2D g, float x, float y,
|
---|
115 | RSyntaxTextArea host, TabExpander e,
|
---|
116 | float clipStart) {
|
---|
117 |
|
---|
118 | int origX = (int)x;
|
---|
119 | int end = textOffset + textCount;
|
---|
120 | float nextX = x;
|
---|
121 | int flushLen = 0;
|
---|
122 | int flushIndex = textOffset;
|
---|
123 | Color fg = host.getForegroundForToken(this);
|
---|
124 | Color bg = host.getBackgroundForTokenType(type);
|
---|
125 | g.setFont(host.getFontForTokenType(type));
|
---|
126 | FontMetrics fm = host.getFontMetricsForTokenType(type);
|
---|
127 |
|
---|
128 | int ascent = fm.getAscent();
|
---|
129 | int height = fm.getHeight();
|
---|
130 |
|
---|
131 | for (int i=textOffset; i<end; i++) {
|
---|
132 |
|
---|
133 | switch (text[i]) {
|
---|
134 |
|
---|
135 | case '\t':
|
---|
136 |
|
---|
137 | // Fill in background.
|
---|
138 | nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
---|
139 | float nextNextX = e.nextTabStop(nextX, 0);
|
---|
140 | if (bg!=null) {
|
---|
141 | paintBackground(x,y, nextNextX-x,height, g,
|
---|
142 | ascent, host, bg);
|
---|
143 | }
|
---|
144 | g.setColor(fg);
|
---|
145 |
|
---|
146 | // Paint chars cached before the tab.
|
---|
147 | if (flushLen > 0) {
|
---|
148 | g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
---|
149 | flushLen = 0;
|
---|
150 | }
|
---|
151 | flushIndex = i + 1;
|
---|
152 |
|
---|
153 | // Draw an arrow representing the tab.
|
---|
154 | int halfHeight = height / 2;
|
---|
155 | int quarterHeight = halfHeight / 2;
|
---|
156 | int ymid = (int)y - ascent + halfHeight;
|
---|
157 | g.drawLine((int)nextX,ymid, (int)nextNextX,ymid);
|
---|
158 | g.drawLine((int)nextNextX,ymid, (int)nextNextX-4,ymid-quarterHeight);
|
---|
159 | g.drawLine((int)nextNextX,ymid, (int)nextNextX-4,ymid+quarterHeight);
|
---|
160 |
|
---|
161 | x = nextNextX;
|
---|
162 | break;
|
---|
163 |
|
---|
164 | case ' ':
|
---|
165 |
|
---|
166 | // NOTE: There is a little bit of a "fudge factor"
|
---|
167 | // here when "smooth text" is enabled, as "width"
|
---|
168 | // below may well not be the width given to the space
|
---|
169 | // by fm.charsWidth() (it depends on how it places the
|
---|
170 | // space with respect to the preceding character).
|
---|
171 | // But, we assume the approximation is close enough for
|
---|
172 | // our drawing a dot for the space.
|
---|
173 |
|
---|
174 | // "flushLen+1" ensures text is aligned correctly (or,
|
---|
175 | // aligned the same as in getWidth()).
|
---|
176 | nextX = x+fm.charsWidth(text, flushIndex,flushLen+1);
|
---|
177 | int width = fm.charWidth(' ');
|
---|
178 |
|
---|
179 | // Paint background.
|
---|
180 | if (bg!=null) {
|
---|
181 | paintBackground(x,y, nextX-x,height, g,
|
---|
182 | ascent, host, bg);
|
---|
183 | }
|
---|
184 | g.setColor(fg);
|
---|
185 |
|
---|
186 | // Paint chars before space.
|
---|
187 | if (flushLen>0) {
|
---|
188 | g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
---|
189 | flushLen = 0;
|
---|
190 | }
|
---|
191 |
|
---|
192 | // Paint a dot representing the space.
|
---|
193 | dotRect.x = nextX - width/2.0f; // "2.0f" for FindBugs
|
---|
194 | dotRect.y = y - ascent + height/2.0f; // Ditto
|
---|
195 | g.fill(dotRect);
|
---|
196 | flushIndex = i + 1;
|
---|
197 | x = nextX;
|
---|
198 | break;
|
---|
199 |
|
---|
200 |
|
---|
201 | case '\f':
|
---|
202 | // ???
|
---|
203 | // fall-through for now.
|
---|
204 |
|
---|
205 | default:
|
---|
206 | flushLen += 1;
|
---|
207 | break;
|
---|
208 |
|
---|
209 | }
|
---|
210 | }
|
---|
211 |
|
---|
212 | nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
---|
213 |
|
---|
214 | if (flushLen>0 && nextX>=clipStart) {
|
---|
215 | if (bg!=null) {
|
---|
216 | paintBackground(x,y, nextX-x,height, g,
|
---|
217 | ascent, host, bg);
|
---|
218 | }
|
---|
219 | g.setColor(fg);
|
---|
220 | g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
---|
221 | }
|
---|
222 |
|
---|
223 | if (host.getUnderlineForToken(this)) {
|
---|
224 | g.setColor(fg);
|
---|
225 | int y2 = (int)(y+1);
|
---|
226 | g.drawLine(origX,y2, (int)nextX,y2);
|
---|
227 | }
|
---|
228 |
|
---|
229 | // Don't check if it's whitespace - some TokenMakers may return types
|
---|
230 | // other than Token.WHITESPACE for spaces (such as Token.IDENTIFIER).
|
---|
231 | // This also allows us to paint tab lines for MLC's.
|
---|
232 | if (host.getPaintTabLines() && origX==host.getMargin().left) {// && isWhitespace()) {
|
---|
233 | paintTabLines(origX, (int)y, (int)nextX, g, e, host);
|
---|
234 | }
|
---|
235 |
|
---|
236 | return nextX;
|
---|
237 |
|
---|
238 | }
|
---|
239 |
|
---|
240 |
|
---|
241 | } |
---|