1 | /*
|
---|
2 | * 08/06/2004
|
---|
3 | *
|
---|
4 | * WrappedSyntaxView.java - Test implementation of WrappedSyntaxView that
|
---|
5 | * is also aware of RSyntaxTextArea's different fonts per token type.
|
---|
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.*;
|
---|
13 | import javax.swing.text.*;
|
---|
14 | import javax.swing.text.Position.Bias;
|
---|
15 | import javax.swing.event.*;
|
---|
16 |
|
---|
17 | import org.fife.ui.rsyntaxtextarea.folding.Fold;
|
---|
18 | import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
---|
19 | import org.fife.ui.rtextarea.Gutter;
|
---|
20 |
|
---|
21 |
|
---|
22 | /**
|
---|
23 | * The view used by {@link RSyntaxTextArea} when word wrap is enabled.
|
---|
24 | *
|
---|
25 | * @author Robert Futrell
|
---|
26 | * @version 0.2
|
---|
27 | */
|
---|
28 | public class WrappedSyntaxView extends BoxView implements TabExpander,
|
---|
29 | RSTAView {
|
---|
30 |
|
---|
31 | boolean widthChanging;
|
---|
32 | int tabBase;
|
---|
33 | int tabSize;
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * This is reused to keep from allocating/deallocating.
|
---|
37 | */
|
---|
38 | private Segment s, drawSeg;
|
---|
39 |
|
---|
40 | /**
|
---|
41 | * Another variable initialized once to keep from allocating/deallocating.
|
---|
42 | */
|
---|
43 | private Rectangle tempRect;
|
---|
44 |
|
---|
45 | /**
|
---|
46 | * Cached for each paint() call so each drawView() call has access to it.
|
---|
47 | */
|
---|
48 | private RSyntaxTextArea host;
|
---|
49 | private FontMetrics metrics;
|
---|
50 |
|
---|
51 | // /**
|
---|
52 | // * The end-of-line marker.
|
---|
53 | // */
|
---|
54 | // private static final char[] eolMarker = { '.' };
|
---|
55 |
|
---|
56 | /**
|
---|
57 | * The width of this view cannot be below this amount, as if the width
|
---|
58 | * is ever 0 (really a bug), we'll go into an infinite loop.
|
---|
59 | */
|
---|
60 | private static final int MIN_WIDTH = 20;
|
---|
61 |
|
---|
62 |
|
---|
63 | /**
|
---|
64 | * Creates a new WrappedSyntaxView. Lines will be wrapped
|
---|
65 | * on character boundaries.
|
---|
66 | *
|
---|
67 | * @param elem the element underlying the view
|
---|
68 | */
|
---|
69 | public WrappedSyntaxView(Element elem) {
|
---|
70 | super(elem, Y_AXIS);
|
---|
71 | s = new Segment();
|
---|
72 | drawSeg = new Segment();
|
---|
73 | tempRect = new Rectangle();
|
---|
74 | }
|
---|
75 |
|
---|
76 |
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * This is called by the nested wrapped line
|
---|
80 | * views to determine the break location. This can
|
---|
81 | * be reimplemented to alter the breaking behavior.
|
---|
82 | * It will either break at word or character boundaries
|
---|
83 | * depending upon the break argument given at
|
---|
84 | * construction.
|
---|
85 | */
|
---|
86 | protected int calculateBreakPosition(int p0, Token tokenList, float x0) {
|
---|
87 | //System.err.println("------ beginning calculateBreakPosition() --------");
|
---|
88 | int p = p0;
|
---|
89 | RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
|
---|
90 | float currentWidth = getWidth();
|
---|
91 | if (currentWidth==Integer.MAX_VALUE)
|
---|
92 | currentWidth = getPreferredSpan(X_AXIS);
|
---|
93 | // Make sure width>0; this is a huge hack to fix a bug where
|
---|
94 | // loading text into an RTextArea before it is visible if word wrap
|
---|
95 | // is enabled causes an infinite loop in calculateBreakPosition()
|
---|
96 | // because of the 0-width! We cannot simply check in setSize()
|
---|
97 | // because the width is set to 0 somewhere else too somehow...
|
---|
98 | currentWidth = Math.max(currentWidth, MIN_WIDTH);
|
---|
99 | Token t = tokenList;
|
---|
100 | while (t!=null && t.isPaintable()) {
|
---|
101 | // FIXME: Replace the code below with the commented-out line below. This will
|
---|
102 | // allow long tokens to be broken at embedded spaces (such as MLC's). But it
|
---|
103 | // currently throws BadLocationExceptions sometimes...
|
---|
104 | float tokenWidth = t.getWidth(textArea, this, x0);
|
---|
105 | if (tokenWidth>currentWidth) {
|
---|
106 | // If the current token alone is too long for this line,
|
---|
107 | // break at a character boundary.
|
---|
108 | if (p==p0) {
|
---|
109 | return t.getOffsetBeforeX(textArea, this, 0, currentWidth);
|
---|
110 | }
|
---|
111 | // Return the first non-whitespace char (i.e., don't start
|
---|
112 | // off the continuation of a wrapped line with whitespace).
|
---|
113 | return t.isWhitespace() ? p+t.textCount : p;
|
---|
114 | //return getBreakLocation(t, fm, x0, currentWidth, this);
|
---|
115 | }
|
---|
116 | currentWidth -= tokenWidth;
|
---|
117 | x0 += tokenWidth;
|
---|
118 | p += t.textCount;
|
---|
119 | //System.err.println("*** *** *** token fit entirely (width==" + tokenWidth + "), adding " + t.textCount + " to p, now p==" + p);
|
---|
120 | t = t.getNextToken();
|
---|
121 | }
|
---|
122 | //System.err.println("... ... whole line fits; returning p==" + p);
|
---|
123 | //System.err.println("------ ending calculateBreakPosition() --------");
|
---|
124 |
|
---|
125 | // return p;
|
---|
126 | return p + 1;
|
---|
127 | }
|
---|
128 |
|
---|
129 | //private int getBreakLocation(Token t, FontMetrics fm, int x0, int x,
|
---|
130 | // TabExpander e) {
|
---|
131 | // Segment s = new Segment();
|
---|
132 | // s.array = t.text;
|
---|
133 | // s.offset = t.textOffset;
|
---|
134 | // s.count = t.textCount;
|
---|
135 | // return t.offset + Utilities.getBreakLocation(s, fm, x0, x, e, t.offset);
|
---|
136 | //}
|
---|
137 |
|
---|
138 | /**
|
---|
139 | * Gives notification from the document that attributes were changed
|
---|
140 | * in a location that this view is responsible for.
|
---|
141 | *
|
---|
142 | * @param e the change information from the associated document
|
---|
143 | * @param a the current allocation of the view
|
---|
144 | * @param f the factory to use to rebuild if the view has children
|
---|
145 | * @see View#changedUpdate
|
---|
146 | */
|
---|
147 | public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
|
---|
148 | updateChildren(e, a);
|
---|
149 | }
|
---|
150 |
|
---|
151 |
|
---|
152 | /**
|
---|
153 | * Sets the allocation rectangle for a given line's view, but sets the
|
---|
154 | * y value to the passed-in value. This should be used instead of
|
---|
155 | * {@link #childAllocation(int, Rectangle)} since it allows you to account
|
---|
156 | * for hidden lines in collapsed fold regions.
|
---|
157 | *
|
---|
158 | * @param line
|
---|
159 | * @param y
|
---|
160 | * @param alloc
|
---|
161 | */
|
---|
162 | private void childAllocation2(int line, int y, Rectangle alloc) {
|
---|
163 | alloc.x += getOffset(X_AXIS, line);
|
---|
164 | alloc.y += y;
|
---|
165 | alloc.width = getSpan(X_AXIS, line);
|
---|
166 | alloc.height = getSpan(Y_AXIS, line);
|
---|
167 | }
|
---|
168 |
|
---|
169 |
|
---|
170 | /**
|
---|
171 | * Draws a single view (i.e., a line of text for a wrapped view),
|
---|
172 | * wrapping the text onto multiple lines if necessary.
|
---|
173 | *
|
---|
174 | * @param g The graphics context in which to paint.
|
---|
175 | * @param r The rectangle in which to paint.
|
---|
176 | * @param view The <code>View</code> to paint.
|
---|
177 | * @param fontHeight The height of the font being used.
|
---|
178 | * @param y The y-coordinate at which to begin painting.
|
---|
179 | */
|
---|
180 | protected void drawView(Graphics2D g, Rectangle r, View view,
|
---|
181 | int fontHeight, int y) {
|
---|
182 |
|
---|
183 | float x = r.x;
|
---|
184 |
|
---|
185 | LayeredHighlighter h = (LayeredHighlighter)host.getHighlighter();
|
---|
186 |
|
---|
187 | RSyntaxDocument document = (RSyntaxDocument)getDocument();
|
---|
188 | Element map = getElement();
|
---|
189 |
|
---|
190 | int p0 = view.getStartOffset();
|
---|
191 | int lineNumber = map.getElementIndex(p0);
|
---|
192 | int p1 = view.getEndOffset();// - 1;
|
---|
193 |
|
---|
194 | setSegment(p0,p1-1, document, drawSeg);
|
---|
195 | //System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")");
|
---|
196 | int start = p0 - drawSeg.offset;
|
---|
197 | Token token = document.getTokenListForLine(lineNumber);
|
---|
198 |
|
---|
199 | // If this line is an empty line, then the token list is simply a
|
---|
200 | // null token. In this case, the line highlight will be skipped in
|
---|
201 | // the loop below, so unfortunately we must manually do it here.
|
---|
202 | if (token!=null && token.type==Token.NULL) {
|
---|
203 | h.paintLayeredHighlights(g, p0,p1, r, host, this);
|
---|
204 | return;
|
---|
205 | }
|
---|
206 |
|
---|
207 | // Loop through all tokens in this view and paint them!
|
---|
208 | while (token!=null && token.isPaintable()) {
|
---|
209 |
|
---|
210 | int p = calculateBreakPosition(p0, token, x);
|
---|
211 | x = r.x;
|
---|
212 |
|
---|
213 | h.paintLayeredHighlights(g, p0,p, r, host, this);
|
---|
214 |
|
---|
215 | while (token!=null && token.isPaintable() && token.offset+token.textCount-1<p) {//<=p) {
|
---|
216 | x = token.paint(g, x,y, host, this);
|
---|
217 | token = token.getNextToken();
|
---|
218 | }
|
---|
219 |
|
---|
220 | if (token!=null && token.isPaintable() && token.offset<p) {
|
---|
221 | int tokenOffset = token.offset;
|
---|
222 | Token temp = new DefaultToken(drawSeg, tokenOffset-start,
|
---|
223 | p-1-start, tokenOffset,
|
---|
224 | token.type);
|
---|
225 | temp.paint(g, x,y, host, this);
|
---|
226 | temp = null;
|
---|
227 | token.makeStartAt(p);
|
---|
228 | }
|
---|
229 |
|
---|
230 | p0 = (p==p0) ? p1 : p;
|
---|
231 | y += fontHeight;
|
---|
232 |
|
---|
233 | } // End of while (token!=null && token.isPaintable()).
|
---|
234 |
|
---|
235 | // NOTE: We should re-use code from Token (paintBackground()) here,
|
---|
236 | // but don't because I'm just too lazy.
|
---|
237 | if (host.getEOLMarkersVisible()) {
|
---|
238 | g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
|
---|
239 | g.setFont(host.getFontForTokenType(Token.WHITESPACE));
|
---|
240 | g.drawString("\u00B6", x, y-fontHeight);
|
---|
241 | }
|
---|
242 |
|
---|
243 | }
|
---|
244 |
|
---|
245 |
|
---|
246 | /**
|
---|
247 | * Fetches the allocation for the given child view.<p>
|
---|
248 | * Overridden to account for code folding.
|
---|
249 | *
|
---|
250 | * @param index The index of the child, >= 0 && < getViewCount().
|
---|
251 | * @param a The allocation to this view
|
---|
252 | * @return The allocation to the child; or <code>null</code> if
|
---|
253 | * <code>a</code> is <code>null</code>; or <code>null</code> if the
|
---|
254 | * layout is invalid
|
---|
255 | */
|
---|
256 | public Shape getChildAllocation(int index, Shape a) {
|
---|
257 | if (a != null) {
|
---|
258 | Shape ca = getChildAllocationImpl(index, a);
|
---|
259 | if ((ca != null) && (!isAllocationValid())) {
|
---|
260 | // The child allocation may not have been set yet.
|
---|
261 | Rectangle r = (ca instanceof Rectangle) ? (Rectangle) ca : ca
|
---|
262 | .getBounds();
|
---|
263 | if ((r.width == 0) && (r.height == 0)) {
|
---|
264 | return null;
|
---|
265 | }
|
---|
266 | }
|
---|
267 | return ca;
|
---|
268 | }
|
---|
269 | return null;
|
---|
270 | }
|
---|
271 |
|
---|
272 | /**
|
---|
273 | * Fetches the allocation for the given child view to render into.<p>
|
---|
274 | * Overridden to account for lines hidden by collapsed folded regions.
|
---|
275 | *
|
---|
276 | * @param line The index of the child, >= 0 && < getViewCount()
|
---|
277 | * @param a The allocation to this view
|
---|
278 | * @return The allocation to the child
|
---|
279 | */
|
---|
280 | public Shape getChildAllocationImpl(int line, Shape a) {
|
---|
281 |
|
---|
282 | Rectangle alloc = getInsideAllocation(a);
|
---|
283 | host = (RSyntaxTextArea)getContainer();
|
---|
284 | FoldManager fm = host.getFoldManager();
|
---|
285 | int y = alloc.y;
|
---|
286 |
|
---|
287 | // TODO: Make cached getOffset() calls for Y_AXIS valid even for
|
---|
288 | // folding, to speed this up!
|
---|
289 | for (int i=0; i<line; i++) {
|
---|
290 | y += getSpan(Y_AXIS, i);
|
---|
291 | Fold fold = fm.getFoldForLine(i);
|
---|
292 | if (fold!=null && fold.isCollapsed()) {
|
---|
293 | i += fold.getCollapsedLineCount();
|
---|
294 | }
|
---|
295 | }
|
---|
296 |
|
---|
297 | childAllocation2(line, y, alloc);
|
---|
298 | return alloc;
|
---|
299 |
|
---|
300 | }
|
---|
301 |
|
---|
302 |
|
---|
303 | /**
|
---|
304 | * Determines the maximum span for this view along an
|
---|
305 | * axis. This is implemented to provide the superclass
|
---|
306 | * behavior after first making sure that the current font
|
---|
307 | * metrics are cached (for the nested lines which use
|
---|
308 | * the metrics to determine the height of the potentially
|
---|
309 | * wrapped lines).
|
---|
310 | *
|
---|
311 | * @param axis may be either View.X_AXIS or View.Y_AXIS
|
---|
312 | * @return the span the view would like to be rendered into.
|
---|
313 | * Typically the view is told to render into the span
|
---|
314 | * that is returned, although there is no guarantee.
|
---|
315 | * The parent may choose to resize or break the view.
|
---|
316 | * @see View#getMaximumSpan
|
---|
317 | */
|
---|
318 | public float getMaximumSpan(int axis) {
|
---|
319 | updateMetrics();
|
---|
320 | float span = super.getPreferredSpan(axis);
|
---|
321 | if (axis==View.X_AXIS) { // EOL marker
|
---|
322 | span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
|
---|
323 | }
|
---|
324 | return span;
|
---|
325 | }
|
---|
326 |
|
---|
327 |
|
---|
328 | /**
|
---|
329 | * Determines the minimum span for this view along an
|
---|
330 | * axis. This is implemented to provide the superclass
|
---|
331 | * behavior after first making sure that the current font
|
---|
332 | * metrics are cached (for the nested lines which use
|
---|
333 | * the metrics to determine the height of the potentially
|
---|
334 | * wrapped lines).
|
---|
335 | *
|
---|
336 | * @param axis may be either View.X_AXIS or View.Y_AXIS
|
---|
337 | * @return the span the view would like to be rendered into.
|
---|
338 | * Typically the view is told to render into the span
|
---|
339 | * that is returned, although there is no guarantee.
|
---|
340 | * The parent may choose to resize or break the view.
|
---|
341 | * @see View#getMinimumSpan
|
---|
342 | */
|
---|
343 | public float getMinimumSpan(int axis) {
|
---|
344 | updateMetrics();
|
---|
345 | float span = super.getPreferredSpan(axis);
|
---|
346 | if (axis==View.X_AXIS) { // EOL marker
|
---|
347 | span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
|
---|
348 | }
|
---|
349 | return span;
|
---|
350 | }
|
---|
351 |
|
---|
352 |
|
---|
353 | /**
|
---|
354 | * Determines the preferred span for this view along an
|
---|
355 | * axis. This is implemented to provide the superclass
|
---|
356 | * behavior after first making sure that the current font
|
---|
357 | * metrics are cached (for the nested lines which use
|
---|
358 | * the metrics to determine the height of the potentially
|
---|
359 | * wrapped lines).
|
---|
360 | *
|
---|
361 | * @param axis may be either View.X_AXIS or View.Y_AXIS
|
---|
362 | * @return the span the view would like to be rendered into.
|
---|
363 | * Typically the view is told to render into the span
|
---|
364 | * that is returned, although there is no guarantee.
|
---|
365 | * The parent may choose to resize or break the view.
|
---|
366 | * @see View#getPreferredSpan
|
---|
367 | */
|
---|
368 | public float getPreferredSpan(int axis) {
|
---|
369 | updateMetrics();
|
---|
370 | float span = 0;
|
---|
371 | if (axis==View.X_AXIS) { // Add EOL marker
|
---|
372 | span = super.getPreferredSpan(axis);
|
---|
373 | span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
|
---|
374 | }
|
---|
375 | else {
|
---|
376 | span = super.getPreferredSpan(axis);
|
---|
377 | host = (RSyntaxTextArea)getContainer();
|
---|
378 | if (host.isCodeFoldingEnabled()) {
|
---|
379 | // TODO: Cache y-offsets again to speed this up
|
---|
380 | //System.out.println("y-axis baby");
|
---|
381 | int lineCount = host.getLineCount();
|
---|
382 | FoldManager fm = host.getFoldManager();
|
---|
383 | for (int i=0; i<lineCount; i++) {
|
---|
384 | if (fm.isLineHidden(i)) {
|
---|
385 | span -= getSpan(View.Y_AXIS, i);
|
---|
386 | }
|
---|
387 | }
|
---|
388 | }
|
---|
389 | }
|
---|
390 | return span;
|
---|
391 | }
|
---|
392 |
|
---|
393 |
|
---|
394 | /**
|
---|
395 | * Returns the tab size set for the document, defaulting to 5.
|
---|
396 | *
|
---|
397 | * @return the tab size
|
---|
398 | */
|
---|
399 | protected int getTabSize() {
|
---|
400 | Integer i = (Integer) getDocument().
|
---|
401 | getProperty(PlainDocument.tabSizeAttribute);
|
---|
402 | int size = (i != null) ? i.intValue() : 5;
|
---|
403 | return size;
|
---|
404 | }
|
---|
405 |
|
---|
406 |
|
---|
407 | /**
|
---|
408 | * Overridden to allow for folded regions.
|
---|
409 | */
|
---|
410 | protected View getViewAtPoint(int x, int y, Rectangle alloc) {
|
---|
411 |
|
---|
412 | int lineCount = getViewCount();
|
---|
413 | int curY = alloc.y + getOffset(Y_AXIS, 0); // Always at least 1 line
|
---|
414 | host = (RSyntaxTextArea)getContainer();
|
---|
415 | FoldManager fm = host.getFoldManager();
|
---|
416 |
|
---|
417 | for (int line=1; line<lineCount; line++) {
|
---|
418 | int span = getSpan(Y_AXIS, line-1);
|
---|
419 | if (y<curY+span) {
|
---|
420 | childAllocation2(line-1, curY, alloc);
|
---|
421 | return getView(line-1);
|
---|
422 | }
|
---|
423 | curY += span;
|
---|
424 | Fold fold = fm.getFoldForLine(line-1);
|
---|
425 | if (fold!=null && fold.isCollapsed()) {
|
---|
426 | line += fold.getCollapsedLineCount();
|
---|
427 | }
|
---|
428 | }
|
---|
429 |
|
---|
430 | // Not found - return last line's view.
|
---|
431 | childAllocation2(lineCount - 1, curY, alloc);
|
---|
432 | return getView(lineCount - 1);
|
---|
433 |
|
---|
434 | }
|
---|
435 |
|
---|
436 |
|
---|
437 | /**
|
---|
438 | * Gives notification that something was inserted into the
|
---|
439 | * document in a location that this view is responsible for.
|
---|
440 | * This is implemented to simply update the children.
|
---|
441 | *
|
---|
442 | * @param changes The change information from the associated document.
|
---|
443 | * @param a the current allocation of the view
|
---|
444 | * @param f the factory to use to rebuild if the view has children
|
---|
445 | * @see View#insertUpdate
|
---|
446 | */
|
---|
447 | public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
448 | updateChildren(changes, a);
|
---|
449 | Rectangle alloc = ((a != null) && isAllocationValid()) ?
|
---|
450 | getInsideAllocation(a) : null;
|
---|
451 | int pos = changes.getOffset();
|
---|
452 | View v = getViewAtPosition(pos, alloc);
|
---|
453 | if (v != null)
|
---|
454 | v.insertUpdate(changes, alloc, f);
|
---|
455 | }
|
---|
456 |
|
---|
457 |
|
---|
458 | /**
|
---|
459 | * Loads all of the children to initialize the view.
|
---|
460 | * This is called by the <code>setParent</code> method.
|
---|
461 | * Subclasses can re-implement this to initialize their
|
---|
462 | * child views in a different manner. The default
|
---|
463 | * implementation creates a child view for each
|
---|
464 | * child element.
|
---|
465 | *
|
---|
466 | * @param f the view factory
|
---|
467 | */
|
---|
468 | protected void loadChildren(ViewFactory f) {
|
---|
469 | Element e = getElement();
|
---|
470 | int n = e.getElementCount();
|
---|
471 | if (n > 0) {
|
---|
472 | View[] added = new View[n];
|
---|
473 | for (int i = 0; i < n; i++)
|
---|
474 | added[i] = new WrappedLine(e.getElement(i));
|
---|
475 | replace(0, 0, added);
|
---|
476 | }
|
---|
477 | }
|
---|
478 |
|
---|
479 |
|
---|
480 | public Shape modelToView(int pos, Shape a, Position.Bias b)
|
---|
481 | throws BadLocationException {
|
---|
482 |
|
---|
483 | if (! isAllocationValid()) {
|
---|
484 | Rectangle alloc = a.getBounds();
|
---|
485 | setSize(alloc.width, alloc.height);
|
---|
486 | }
|
---|
487 |
|
---|
488 | boolean isBackward = (b == Position.Bias.Backward);
|
---|
489 | int testPos = (isBackward) ? Math.max(0, pos - 1) : pos;
|
---|
490 | if(isBackward && testPos < getStartOffset()) {
|
---|
491 | return null;
|
---|
492 | }
|
---|
493 |
|
---|
494 | int vIndex = getViewIndexAtPosition(testPos);
|
---|
495 | if ((vIndex != -1) && (vIndex < getViewCount())) {
|
---|
496 | View v = getView(vIndex);
|
---|
497 | if(v != null && testPos >= v.getStartOffset() &&
|
---|
498 | testPos < v.getEndOffset()) {
|
---|
499 | Shape childShape = getChildAllocation(vIndex, a);
|
---|
500 | if (childShape == null) {
|
---|
501 | // We are likely invalid, fail.
|
---|
502 | return null;
|
---|
503 | }
|
---|
504 | Shape retShape = v.modelToView(pos, childShape, b);
|
---|
505 | if(retShape == null && v.getEndOffset() == pos) {
|
---|
506 | if(++vIndex < getViewCount()) {
|
---|
507 | v = getView(vIndex);
|
---|
508 | retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b);
|
---|
509 | }
|
---|
510 | }
|
---|
511 | return retShape;
|
---|
512 | }
|
---|
513 | }
|
---|
514 |
|
---|
515 | throw new BadLocationException("Position not represented by view", pos);
|
---|
516 |
|
---|
517 | }
|
---|
518 |
|
---|
519 |
|
---|
520 | /**
|
---|
521 | * Provides a mapping, for a given region, from the document model
|
---|
522 | * coordinate space to the view coordinate space. The specified region is
|
---|
523 | * created as a union of the first and last character positions.<p>
|
---|
524 | *
|
---|
525 | * This is implemented to subtract the width of the second character, as
|
---|
526 | * this view's <code>modelToView</code> actually returns the width of the
|
---|
527 | * character instead of "1" or "0" like the View implementations in
|
---|
528 | * <code>javax.swing.text</code>. Thus, if we don't override this method,
|
---|
529 | * the <code>View</code> implementation will return one character's width
|
---|
530 | * too much for its consumers (implementations of
|
---|
531 | * <code>javax.swing.text.Highlighter</code>).
|
---|
532 | *
|
---|
533 | * @param p0 the position of the first character (>=0)
|
---|
534 | * @param b0 The bias of the first character position, toward the previous
|
---|
535 | * character or the next character represented by the offset, in
|
---|
536 | * case the position is a boundary of two views; <code>b0</code>
|
---|
537 | * will have one of these values:
|
---|
538 | * <ul>
|
---|
539 | * <li> <code>Position.Bias.Forward</code>
|
---|
540 | * <li> <code>Position.Bias.Backward</code>
|
---|
541 | * </ul>
|
---|
542 | * @param p1 the position of the last character (>=0)
|
---|
543 | * @param b1 the bias for the second character position, defined
|
---|
544 | * one of the legal values shown above
|
---|
545 | * @param a the area of the view, which encompasses the requested region
|
---|
546 | * @return the bounding box which is a union of the region specified
|
---|
547 | * by the first and last character positions
|
---|
548 | * @exception BadLocationException if the given position does
|
---|
549 | * not represent a valid location in the associated document
|
---|
550 | * @exception IllegalArgumentException if <code>b0</code> or
|
---|
551 | * <code>b1</code> are not one of the
|
---|
552 | * legal <code>Position.Bias</code> values listed above
|
---|
553 | * @see View#viewToModel
|
---|
554 | */
|
---|
555 | public Shape modelToView(int p0, Position.Bias b0,
|
---|
556 | int p1, Position.Bias b1,
|
---|
557 | Shape a) throws BadLocationException {
|
---|
558 |
|
---|
559 | Shape s0 = modelToView(p0, a, b0);
|
---|
560 | Shape s1;
|
---|
561 | if (p1 ==getEndOffset()) {
|
---|
562 | try {
|
---|
563 | s1 = modelToView(p1, a, b1);
|
---|
564 | } catch (BadLocationException ble) {
|
---|
565 | s1 = null;
|
---|
566 | }
|
---|
567 | if (s1 == null) {
|
---|
568 | // Assume extends left to right.
|
---|
569 | Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
|
---|
570 | a.getBounds();
|
---|
571 | s1 = new Rectangle(alloc.x + alloc.width - 1, alloc.y,
|
---|
572 | 1, alloc.height);
|
---|
573 | }
|
---|
574 | }
|
---|
575 | else {
|
---|
576 | s1 = modelToView(p1, a, b1);
|
---|
577 | }
|
---|
578 | Rectangle r0 = s0.getBounds();
|
---|
579 | Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle) s1 :
|
---|
580 | s1.getBounds();
|
---|
581 | if (r0.y != r1.y) {
|
---|
582 | // If it spans lines, force it to be the width of the view.
|
---|
583 | Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
|
---|
584 | a.getBounds();
|
---|
585 | r0.x = alloc.x;
|
---|
586 | r0.width = alloc.width;
|
---|
587 | }
|
---|
588 |
|
---|
589 | r0.add(r1);
|
---|
590 | // The next line is the only difference between this method and
|
---|
591 | // View's implementation. We're subtracting the width of the second
|
---|
592 | // character. This is because this method is used by Highlighter
|
---|
593 | // implementations to get the area to "highlight", and if we don't do
|
---|
594 | // this, one character too many is highlighted thanks to our
|
---|
595 | // modelToView() implementation returning the actual width of the
|
---|
596 | // character requested!
|
---|
597 | if (p1>p0) r0.width -= r1.width;
|
---|
598 | return r0;
|
---|
599 |
|
---|
600 | }
|
---|
601 |
|
---|
602 |
|
---|
603 | /**
|
---|
604 | * Returns the next tab stop position after a given reference position.
|
---|
605 | * This implementation does not support things like centering so it
|
---|
606 | * ignores the tabOffset argument.
|
---|
607 | *
|
---|
608 | * @param x the current position >= 0
|
---|
609 | * @param tabOffset the position within the text stream
|
---|
610 | * that the tab occurred at >= 0.
|
---|
611 | * @return the tab stop, measured in points >= 0
|
---|
612 | */
|
---|
613 | public float nextTabStop(float x, int tabOffset) {
|
---|
614 | if (tabSize == 0)
|
---|
615 | return x;
|
---|
616 | int ntabs = ((int) x - tabBase) / tabSize;
|
---|
617 | return tabBase + ((ntabs + 1) * tabSize);
|
---|
618 | }
|
---|
619 |
|
---|
620 |
|
---|
621 | /**
|
---|
622 | * Paints the word-wrapped text.
|
---|
623 | *
|
---|
624 | * @param g The graphics context in which to paint.
|
---|
625 | * @param a The shape (usually a rectangle) in which to paint.
|
---|
626 | */
|
---|
627 | public void paint(Graphics g, Shape a) {
|
---|
628 |
|
---|
629 | Rectangle alloc = (a instanceof Rectangle) ?
|
---|
630 | (Rectangle)a : a.getBounds();
|
---|
631 | tabBase = alloc.x;
|
---|
632 |
|
---|
633 | Graphics2D g2d = (Graphics2D)g;
|
---|
634 | host = (RSyntaxTextArea)getContainer();
|
---|
635 | int ascent = host.getMaxAscent();
|
---|
636 | int fontHeight = host.getLineHeight();
|
---|
637 | FoldManager fm = host.getFoldManager();
|
---|
638 |
|
---|
639 | int n = getViewCount(); // Number of lines.
|
---|
640 | int x = alloc.x + getLeftInset();
|
---|
641 | tempRect.y = alloc.y + getTopInset();
|
---|
642 | Rectangle clip = g.getClipBounds();
|
---|
643 | for (int i = 0; i < n; i++) {
|
---|
644 | tempRect.x = x + getOffset(X_AXIS, i);
|
---|
645 | //tempRect.y = y + getOffset(Y_AXIS, i);
|
---|
646 | tempRect.width = getSpan(X_AXIS, i);
|
---|
647 | tempRect.height = getSpan(Y_AXIS, i);
|
---|
648 | //System.err.println("For line " + i + ": tempRect==" + tempRect);
|
---|
649 | if (tempRect.intersects(clip)) {
|
---|
650 | View view = getView(i);
|
---|
651 | drawView(g2d, alloc, view, fontHeight, tempRect.y+ascent);
|
---|
652 | }
|
---|
653 | tempRect.y += tempRect.height;
|
---|
654 | Fold possibleFold = fm.getFoldForLine(i);
|
---|
655 | if (possibleFold!=null && possibleFold.isCollapsed()) {
|
---|
656 | i += possibleFold.getCollapsedLineCount();
|
---|
657 | // Visible indicator of collapsed lines
|
---|
658 | Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
|
---|
659 | if (c!=null) {
|
---|
660 | g.setColor(c);
|
---|
661 | g.drawLine(x,tempRect.y-1, alloc.width,tempRect.y-1);
|
---|
662 | }
|
---|
663 | }
|
---|
664 | }
|
---|
665 |
|
---|
666 | }
|
---|
667 |
|
---|
668 |
|
---|
669 | /**
|
---|
670 | * Gives notification that something was removed from the
|
---|
671 | * document in a location that this view is responsible for.
|
---|
672 | * This is implemented to simply update the children.
|
---|
673 | *
|
---|
674 | * @param changes The change information from the associated document.
|
---|
675 | * @param a the current allocation of the view
|
---|
676 | * @param f the factory to use to rebuild if the view has children
|
---|
677 | * @see View#removeUpdate
|
---|
678 | */
|
---|
679 | public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
---|
680 |
|
---|
681 | updateChildren(changes, a);
|
---|
682 |
|
---|
683 | Rectangle alloc = ((a != null) && isAllocationValid()) ?
|
---|
684 | getInsideAllocation(a) : null;
|
---|
685 | int pos = changes.getOffset();
|
---|
686 | View v = getViewAtPosition(pos, alloc);
|
---|
687 | if (v != null)
|
---|
688 | v.removeUpdate(changes, alloc, f);
|
---|
689 |
|
---|
690 | }
|
---|
691 |
|
---|
692 |
|
---|
693 | /**
|
---|
694 | * Makes a <code>Segment</code> point to the text in our
|
---|
695 | * document between the given positions. Note that the positions MUST be
|
---|
696 | * valid positions in the document.
|
---|
697 | *
|
---|
698 | * @param p0 The first position in the document.
|
---|
699 | * @param p1 The second position in the document.
|
---|
700 | * @param document The document from which you want to get the text.
|
---|
701 | * @param seg The segment in which to load the text.
|
---|
702 | */
|
---|
703 | private void setSegment(int p0, int p1, Document document,
|
---|
704 | Segment seg) {
|
---|
705 | try {
|
---|
706 | //System.err.println("... in setSharedSegment, p0/p1==" + p0 + "/" + p1);
|
---|
707 | document.getText(p0, p1-p0, seg);
|
---|
708 | //System.err.println("... in setSharedSegment: s=='" + s + "'; line/numLines==" + line + "/" + numLines);
|
---|
709 | } catch (BadLocationException ble) { // Never happens
|
---|
710 | ble.printStackTrace();
|
---|
711 | }
|
---|
712 | }
|
---|
713 |
|
---|
714 |
|
---|
715 | /**
|
---|
716 | * Sets the size of the view. This should cause layout of the view along
|
---|
717 | * the given axis, if it has any layout duties.
|
---|
718 | *
|
---|
719 | * @param width the width >= 0
|
---|
720 | * @param height the height >= 0
|
---|
721 | */
|
---|
722 | public void setSize(float width, float height) {
|
---|
723 | updateMetrics();
|
---|
724 | if ((int) width != getWidth()) {
|
---|
725 | // invalidate the view itself since the childrens
|
---|
726 | // desired widths will be based upon this views width.
|
---|
727 | preferenceChanged(null, true, true);
|
---|
728 | widthChanging = true;
|
---|
729 | }
|
---|
730 | super.setSize(width, height);
|
---|
731 | widthChanging = false;
|
---|
732 | }
|
---|
733 |
|
---|
734 |
|
---|
735 | /**
|
---|
736 | * Update the child views in response to a
|
---|
737 | * document event.
|
---|
738 | */
|
---|
739 | void updateChildren(DocumentEvent e, Shape a) {
|
---|
740 |
|
---|
741 | Element elem = getElement();
|
---|
742 | DocumentEvent.ElementChange ec = e.getChange(elem);
|
---|
743 |
|
---|
744 | // This occurs when syntax highlighting only changes on lines
|
---|
745 | // (i.e. beginning a multiline comment).
|
---|
746 | if (e.getType()==DocumentEvent.EventType.CHANGE) {
|
---|
747 | //System.err.println("Updating the damage due to a CHANGE event...");
|
---|
748 | // FIXME: Make me repaint more intelligently.
|
---|
749 | getContainer().repaint();
|
---|
750 | //damageLineRange(startLine,endLine, a, host);
|
---|
751 | }
|
---|
752 |
|
---|
753 | else if (ec != null) {
|
---|
754 |
|
---|
755 | // the structure of this element changed.
|
---|
756 | Element[] removedElems = ec.getChildrenRemoved();
|
---|
757 | Element[] addedElems = ec.getChildrenAdded();
|
---|
758 | View[] added = new View[addedElems.length];
|
---|
759 |
|
---|
760 | for (int i = 0; i < addedElems.length; i++)
|
---|
761 | added[i] = new WrappedLine(addedElems[i]);
|
---|
762 | //System.err.println("Replacing " + removedElems.length +
|
---|
763 | // " children with " + addedElems.length);
|
---|
764 | replace(ec.getIndex(), removedElems.length, added);
|
---|
765 |
|
---|
766 | // should damge a little more intelligently.
|
---|
767 | if (a != null) {
|
---|
768 | preferenceChanged(null, true, true);
|
---|
769 | getContainer().repaint();
|
---|
770 | }
|
---|
771 |
|
---|
772 | }
|
---|
773 |
|
---|
774 | // update font metrics which may be used by the child views
|
---|
775 | updateMetrics();
|
---|
776 |
|
---|
777 | }
|
---|
778 |
|
---|
779 |
|
---|
780 | final void updateMetrics() {
|
---|
781 | Component host = getContainer();
|
---|
782 | Font f = host.getFont();
|
---|
783 | metrics = host.getFontMetrics(f); // Metrics for the default font.
|
---|
784 | tabSize = getTabSize() * metrics.charWidth('m');
|
---|
785 | }
|
---|
786 |
|
---|
787 |
|
---|
788 | public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
|
---|
789 |
|
---|
790 | int offs = -1;
|
---|
791 |
|
---|
792 | if (! isAllocationValid()) {
|
---|
793 | Rectangle alloc = a.getBounds();
|
---|
794 | setSize(alloc.width, alloc.height);
|
---|
795 | }
|
---|
796 |
|
---|
797 | // Get the child view for the line at (x,y), and ask it for the
|
---|
798 | // specific offset.
|
---|
799 | Rectangle alloc = getInsideAllocation(a);
|
---|
800 | View v = getViewAtPoint((int) x, (int) y, alloc);
|
---|
801 | if (v != null) {
|
---|
802 | offs = v.viewToModel(x, y, alloc, bias);
|
---|
803 | }
|
---|
804 |
|
---|
805 | // Code folding may have hidden the last line. If so, return the last
|
---|
806 | // visible offset instead of the last offset.
|
---|
807 | if (host.isCodeFoldingEnabled() && v==getView(getViewCount()-1) &&
|
---|
808 | offs==v.getEndOffset()-1) {
|
---|
809 | offs = host.getLastVisibleOffset();
|
---|
810 | }
|
---|
811 |
|
---|
812 | return offs;
|
---|
813 |
|
---|
814 | }
|
---|
815 |
|
---|
816 |
|
---|
817 | /**
|
---|
818 | * {@inheritDoc}
|
---|
819 | */
|
---|
820 | public int yForLine(Rectangle alloc, int line) throws BadLocationException {
|
---|
821 | return yForLineContaining(alloc,
|
---|
822 | getElement().getElement(line).getStartOffset());
|
---|
823 | //return alloc.y + getOffset(Y_AXIS, line);
|
---|
824 | }
|
---|
825 |
|
---|
826 |
|
---|
827 | /**
|
---|
828 | * {@inheritDoc}
|
---|
829 | */
|
---|
830 | public int yForLineContaining(Rectangle alloc, int offs)
|
---|
831 | throws BadLocationException {
|
---|
832 | if (isAllocationValid()) {
|
---|
833 | // TODO: make cached Y_AXIS offsets valid even with folding enabled
|
---|
834 | // to speed this back up!
|
---|
835 | Rectangle r = (Rectangle)modelToView(offs, alloc, Bias.Forward);
|
---|
836 | if (r!=null) {
|
---|
837 | if (host.isCodeFoldingEnabled()) {
|
---|
838 | int line = host.getLineOfOffset(offs);
|
---|
839 | FoldManager fm = host.getFoldManager();
|
---|
840 | if (fm.isLineHidden(line)) {
|
---|
841 | return -1;
|
---|
842 | }
|
---|
843 | }
|
---|
844 | return r.y;
|
---|
845 | }
|
---|
846 | }
|
---|
847 | return -1;
|
---|
848 | }
|
---|
849 |
|
---|
850 |
|
---|
851 | /**
|
---|
852 | * Simple view of a line that wraps if it doesn't
|
---|
853 | * fit withing the horizontal space allocated.
|
---|
854 | * This class tries to be lightweight by carrying little
|
---|
855 | * state of it's own and sharing the state of the outer class
|
---|
856 | * with it's siblings.
|
---|
857 | */
|
---|
858 | class WrappedLine extends View {
|
---|
859 |
|
---|
860 | int nlines;
|
---|
861 |
|
---|
862 | WrappedLine(Element elem) {
|
---|
863 | super(elem);
|
---|
864 | }
|
---|
865 |
|
---|
866 | /**
|
---|
867 | * Calculate the number of lines that will be rendered
|
---|
868 | * by logical line when it is wrapped.
|
---|
869 | */
|
---|
870 | final int calculateLineCount() {
|
---|
871 |
|
---|
872 | int nlines = 0;
|
---|
873 | int startOffset = getStartOffset();
|
---|
874 | int p1 = getEndOffset();
|
---|
875 |
|
---|
876 | // Get the token list for this line so we don't have to keep
|
---|
877 | // recomputing it if this logical line spans multiple physical
|
---|
878 | // lines.
|
---|
879 | RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
|
---|
880 | RSyntaxDocument doc = (RSyntaxDocument)getDocument();
|
---|
881 | Element map = doc.getDefaultRootElement();
|
---|
882 | int line = map.getElementIndex(startOffset);
|
---|
883 | Token tokenList = doc.getTokenListForLine(line);
|
---|
884 | float x0 = 0;// FIXME: should be alloc.x!! alloc.x;//0;
|
---|
885 |
|
---|
886 |
|
---|
887 | //System.err.println(">>> calculateLineCount: " + startOffset + "-" + p1);
|
---|
888 | for (int p0=startOffset; p0<p1; ) {
|
---|
889 | //System.err.println("... ... " + p0 + ", " + p1);
|
---|
890 | nlines += 1;
|
---|
891 | x0 = RSyntaxUtilities.makeTokenListStartAt(tokenList, p0,
|
---|
892 | WrappedSyntaxView.this, textArea, x0);
|
---|
893 | int p = calculateBreakPosition(p0, tokenList, x0);
|
---|
894 |
|
---|
895 | //System.err.println("... ... ... break position p==" + p);
|
---|
896 | p0 = (p == p0) ? ++p : p; // this is the fix of #4410243
|
---|
897 | // we check on situation when
|
---|
898 | // width is too small and
|
---|
899 | // break position is calculated
|
---|
900 | // incorrectly.
|
---|
901 | //System.err.println("... ... ... new p0==" + p0);
|
---|
902 | }
|
---|
903 | /*
|
---|
904 | int numLines = 0;
|
---|
905 | try {
|
---|
906 | numLines = textArea.getLineCount();
|
---|
907 | } catch (BadLocationException ble) {
|
---|
908 | ble.printStackTrace();
|
---|
909 | }
|
---|
910 | System.err.println(">>> >>> calculated number of lines for this view (line " + line + "/" + numLines + ": " + nlines);
|
---|
911 | */
|
---|
912 | return nlines;
|
---|
913 | }
|
---|
914 |
|
---|
915 | /**
|
---|
916 | * Determines the preferred span for this view along an
|
---|
917 | * axis.
|
---|
918 | *
|
---|
919 | * @param axis may be either X_AXIS or Y_AXIS
|
---|
920 | * @return the span the view would like to be rendered into.
|
---|
921 | * Typically the view is told to render into the span
|
---|
922 | * that is returned, although there is no guarantee.
|
---|
923 | * The parent may choose to resize or break the view.
|
---|
924 | * @see View#getPreferredSpan
|
---|
925 | */
|
---|
926 | public float getPreferredSpan(int axis) {
|
---|
927 | switch (axis) {
|
---|
928 | case View.X_AXIS:
|
---|
929 | float width = getWidth();
|
---|
930 | if (width == Integer.MAX_VALUE) {
|
---|
931 | // We have been initially set to MAX_VALUE, but we don't
|
---|
932 | // want this as our preferred.
|
---|
933 | return 100f;
|
---|
934 | }
|
---|
935 | return width;
|
---|
936 | case View.Y_AXIS:
|
---|
937 | if (nlines == 0 || widthChanging)
|
---|
938 | nlines = calculateLineCount();
|
---|
939 | int h = nlines * ((RSyntaxTextArea)getContainer()).getLineHeight();
|
---|
940 | return h;
|
---|
941 | default:
|
---|
942 | throw new IllegalArgumentException("Invalid axis: " + axis);
|
---|
943 | }
|
---|
944 | }
|
---|
945 |
|
---|
946 | /**
|
---|
947 | * Renders using the given rendering surface and area on that
|
---|
948 | * surface. The view may need to do layout and create child
|
---|
949 | * views to enable itself to render into the given allocation.
|
---|
950 | *
|
---|
951 | * @param g the rendering surface to use
|
---|
952 | * @param a the allocated region to render into
|
---|
953 | * @see View#paint
|
---|
954 | */
|
---|
955 | public void paint(Graphics g, Shape a) {
|
---|
956 | // This is done by drawView() above.
|
---|
957 | }
|
---|
958 |
|
---|
959 | /**
|
---|
960 | * Provides a mapping from the document model coordinate space
|
---|
961 | * to the coordinate space of the view mapped to it.
|
---|
962 | *
|
---|
963 | * @param pos the position to convert
|
---|
964 | * @param a the allocated region to render into
|
---|
965 | * @return the bounding box of the given position is returned
|
---|
966 | * @exception BadLocationException if the given position does not
|
---|
967 | * represent a valid location in the associated document.
|
---|
968 | */
|
---|
969 | public Shape modelToView(int pos, Shape a, Position.Bias b)
|
---|
970 | throws BadLocationException {
|
---|
971 |
|
---|
972 | //System.err.println("--- begin modelToView ---");
|
---|
973 | Rectangle alloc = a.getBounds();
|
---|
974 | RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
|
---|
975 | alloc.height = textArea.getLineHeight();//metrics.getHeight();
|
---|
976 | alloc.width = 1;
|
---|
977 | int p0 = getStartOffset();
|
---|
978 | int p1 = getEndOffset();
|
---|
979 | int testP = (b == Position.Bias.Forward) ? pos :
|
---|
980 | Math.max(p0, pos - 1);
|
---|
981 |
|
---|
982 | // Get the token list for this line so we don't have to keep
|
---|
983 | // recomputing it if this logical line spans multiple physical
|
---|
984 | // lines.
|
---|
985 | RSyntaxDocument doc = (RSyntaxDocument)getDocument();
|
---|
986 | Element map = doc.getDefaultRootElement();
|
---|
987 | int line = map.getElementIndex(p0);
|
---|
988 | Token tokenList = doc.getTokenListForLine(line);
|
---|
989 | float x0 = alloc.x;//0;
|
---|
990 |
|
---|
991 | while (p0 < p1) {
|
---|
992 | x0 = RSyntaxUtilities.makeTokenListStartAt(tokenList, p0,
|
---|
993 | WrappedSyntaxView.this, textArea, x0);
|
---|
994 | int p = calculateBreakPosition(p0, tokenList, x0);
|
---|
995 | if ((pos >= p0) && (testP<p)) {//pos < p)) {
|
---|
996 | // it's in this line
|
---|
997 | alloc = RSyntaxUtilities.getLineWidthUpTo(
|
---|
998 | textArea, s, p0, pos,
|
---|
999 | WrappedSyntaxView.this,
|
---|
1000 | alloc, alloc.x);
|
---|
1001 | //System.err.println("--- end modelToView ---");
|
---|
1002 | return alloc;
|
---|
1003 | }
|
---|
1004 | //if (p == p1 && pos == p1) {
|
---|
1005 | if (p==p1-1 && pos==p1-1) {
|
---|
1006 | // Wants end.
|
---|
1007 | if (pos > p0) {
|
---|
1008 | alloc = RSyntaxUtilities.getLineWidthUpTo(
|
---|
1009 | textArea, s, p0, pos,
|
---|
1010 | WrappedSyntaxView.this,
|
---|
1011 | alloc, alloc.x);
|
---|
1012 | }
|
---|
1013 | //System.err.println("--- end modelToView ---");
|
---|
1014 | return alloc;
|
---|
1015 | }
|
---|
1016 |
|
---|
1017 | p0 = (p == p0) ? p1 : p;
|
---|
1018 | //System.err.println("... ... Incrementing y");
|
---|
1019 | alloc.y += alloc.height;
|
---|
1020 |
|
---|
1021 | }
|
---|
1022 |
|
---|
1023 | throw new BadLocationException(null, pos);
|
---|
1024 |
|
---|
1025 | }
|
---|
1026 |
|
---|
1027 | /**
|
---|
1028 | * Provides a mapping from the view coordinate space to the logical
|
---|
1029 | * coordinate space of the model.
|
---|
1030 | *
|
---|
1031 | * @param fx the X coordinate
|
---|
1032 | * @param fy the Y coordinate
|
---|
1033 | * @param a the allocated region to render into
|
---|
1034 | * @return the location within the model that best represents the
|
---|
1035 | * given point in the view
|
---|
1036 | * @see View#viewToModel
|
---|
1037 | */
|
---|
1038 | public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
|
---|
1039 |
|
---|
1040 | // PENDING(prinz) implement bias properly
|
---|
1041 | bias[0] = Position.Bias.Forward;
|
---|
1042 |
|
---|
1043 | Rectangle alloc = (Rectangle) a;
|
---|
1044 | RSyntaxDocument doc = (RSyntaxDocument)getDocument();
|
---|
1045 | int x = (int) fx;
|
---|
1046 | int y = (int) fy;
|
---|
1047 | if (y < alloc.y) {
|
---|
1048 | // above the area covered by this icon, so the the position
|
---|
1049 | // is assumed to be the start of the coverage for this view.
|
---|
1050 | return getStartOffset();
|
---|
1051 | }
|
---|
1052 | else if (y > alloc.y + alloc.height) {
|
---|
1053 | // below the area covered by this icon, so the the position
|
---|
1054 | // is assumed to be the end of the coverage for this view.
|
---|
1055 | return getEndOffset() - 1;
|
---|
1056 | }
|
---|
1057 | else {
|
---|
1058 |
|
---|
1059 | // positioned within the coverage of this view vertically,
|
---|
1060 | // so we figure out which line the point corresponds to.
|
---|
1061 | // if the line is greater than the number of lines
|
---|
1062 | // contained, then simply use the last line as it represents
|
---|
1063 | // the last possible place we can position to.
|
---|
1064 |
|
---|
1065 | RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
|
---|
1066 | alloc.height = textArea.getLineHeight();
|
---|
1067 | int p1 = getEndOffset();
|
---|
1068 |
|
---|
1069 | // Get the token list for this line so we don't have to keep
|
---|
1070 | // recomputing it if this logical line spans multiple
|
---|
1071 | // physical lines.
|
---|
1072 | Element map = doc.getDefaultRootElement();
|
---|
1073 | int p0 = getStartOffset();
|
---|
1074 | int line = map.getElementIndex(p0);
|
---|
1075 | Token tlist = doc.getTokenListForLine(line);
|
---|
1076 |
|
---|
1077 | // Look at each physical line-chunk of this logical line.
|
---|
1078 | while (p0<p1) {
|
---|
1079 |
|
---|
1080 | // We can always use alloc.x since we always break
|
---|
1081 | // lines so they start at the beginning of a physical
|
---|
1082 | // line.
|
---|
1083 | RSyntaxUtilities.makeTokenListStartAt(tlist, p0,
|
---|
1084 | WrappedSyntaxView.this, textArea, alloc.x);
|
---|
1085 | int p = calculateBreakPosition(p0, tlist, alloc.x);
|
---|
1086 |
|
---|
1087 | // If desired view position is in this physical chunk.
|
---|
1088 | if ((y>=alloc.y) && (y<(alloc.y+alloc.height))) {
|
---|
1089 |
|
---|
1090 | // Point is to the left of the line
|
---|
1091 | if (x < alloc.x) {
|
---|
1092 | return p0;
|
---|
1093 | }
|
---|
1094 |
|
---|
1095 | // Point is to the right of the line
|
---|
1096 | else if (x > alloc.x + alloc.width) {
|
---|
1097 | return p - 1;
|
---|
1098 | }
|
---|
1099 |
|
---|
1100 | // Point is in this physical line!
|
---|
1101 | else {
|
---|
1102 |
|
---|
1103 | // Start at alloc.x since this chunk starts
|
---|
1104 | // at the beginning of a physical line.
|
---|
1105 | int n = tlist.getListOffset(textArea,
|
---|
1106 | WrappedSyntaxView.this,
|
---|
1107 | alloc.x, x);
|
---|
1108 |
|
---|
1109 | // NOTE: We needed to add the max() with
|
---|
1110 | // p0 as getTokenListForLine returns -1
|
---|
1111 | // for empty lines (just a null token).
|
---|
1112 | // How did this work before?
|
---|
1113 | // FIXME: Have null tokens have their
|
---|
1114 | // offset but a -1 length.
|
---|
1115 | return Math.max(Math.min(n, p1-1), p0);
|
---|
1116 |
|
---|
1117 | } // End of else.
|
---|
1118 |
|
---|
1119 | } // End of if ((y>=alloc.y) && ...
|
---|
1120 |
|
---|
1121 | p0 = (p == p0) ? p1 : p;
|
---|
1122 | alloc.y += alloc.height;
|
---|
1123 |
|
---|
1124 | } // End of while (p0<p1).
|
---|
1125 |
|
---|
1126 | return getEndOffset() - 1;
|
---|
1127 |
|
---|
1128 | } // End of else.
|
---|
1129 |
|
---|
1130 | }
|
---|
1131 |
|
---|
1132 | private void handleDocumentEvent(DocumentEvent e, Shape a,
|
---|
1133 | ViewFactory f) {
|
---|
1134 | int n = calculateLineCount();
|
---|
1135 | if (this.nlines != n) {
|
---|
1136 | this.nlines = n;
|
---|
1137 | WrappedSyntaxView.this.preferenceChanged(this, false, true);
|
---|
1138 | // have to repaint any views after the receiver.
|
---|
1139 | RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
|
---|
1140 | textArea.repaint();
|
---|
1141 | // Must also revalidate container so gutter components, such
|
---|
1142 | // as line numbers, get updated for this line's new height
|
---|
1143 | Gutter gutter = RSyntaxUtilities.getGutter(textArea);
|
---|
1144 | if (gutter!=null) {
|
---|
1145 | gutter.revalidate();
|
---|
1146 | gutter.repaint();
|
---|
1147 | }
|
---|
1148 | }
|
---|
1149 | else if (a != null) {
|
---|
1150 | Component c = getContainer();
|
---|
1151 | Rectangle alloc = (Rectangle) a;
|
---|
1152 | c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
|
---|
1153 | }
|
---|
1154 | }
|
---|
1155 |
|
---|
1156 | public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
|
---|
1157 | handleDocumentEvent(e, a, f);
|
---|
1158 | }
|
---|
1159 |
|
---|
1160 | public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
|
---|
1161 | handleDocumentEvent(e, a, f);
|
---|
1162 | }
|
---|
1163 |
|
---|
1164 | }
|
---|
1165 |
|
---|
1166 |
|
---|
1167 | } |
---|