1 | /*
|
---|
2 | * 02/11/2009
|
---|
3 | *
|
---|
4 | * LineNumberList.java - Renders line numbers in an RTextScrollPane.
|
---|
5 | *
|
---|
6 | * This library is distributed under a modified BSD license. See the included
|
---|
7 | * RSyntaxTextArea.License.txt file for details.
|
---|
8 | */
|
---|
9 | package org.fife.ui.rtextarea;
|
---|
10 |
|
---|
11 | import java.awt.Color;
|
---|
12 | import java.awt.Dimension;
|
---|
13 | import java.awt.Font;
|
---|
14 | import java.awt.FontMetrics;
|
---|
15 | import java.awt.Graphics;
|
---|
16 | import java.awt.Graphics2D;
|
---|
17 | import java.awt.Insets;
|
---|
18 | import java.awt.Point;
|
---|
19 | import java.awt.Rectangle;
|
---|
20 | import java.awt.event.MouseEvent;
|
---|
21 | import java.beans.PropertyChangeEvent;
|
---|
22 | import java.beans.PropertyChangeListener;
|
---|
23 | import java.util.Map;
|
---|
24 | import javax.swing.event.CaretEvent;
|
---|
25 | import javax.swing.event.CaretListener;
|
---|
26 | import javax.swing.event.DocumentEvent;
|
---|
27 | import javax.swing.event.MouseInputListener;
|
---|
28 | import javax.swing.text.BadLocationException;
|
---|
29 | import javax.swing.text.Document;
|
---|
30 | import javax.swing.text.Element;
|
---|
31 | import javax.swing.text.View;
|
---|
32 |
|
---|
33 | import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
---|
34 | import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
|
---|
35 | import org.fife.ui.rsyntaxtextarea.folding.Fold;
|
---|
36 | import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
---|
37 |
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * Renders line numbers in the gutter.
|
---|
41 | *
|
---|
42 | * @author Robert Futrell
|
---|
43 | * @version 1.0
|
---|
44 | */
|
---|
45 | public class LineNumberList extends AbstractGutterComponent
|
---|
46 | implements MouseInputListener {
|
---|
47 |
|
---|
48 | private int currentLine; // The last line the caret was on.
|
---|
49 | private int lastY = -1; // Used to check if caret changes lines when line wrap is enabled.
|
---|
50 |
|
---|
51 | private int cellHeight; // Height of a line number "cell" when word wrap is off.
|
---|
52 | private int cellWidth; // The width used for all line number cells.
|
---|
53 | private int ascent; // The ascent to use when painting line numbers.
|
---|
54 |
|
---|
55 | private Map aaHints;
|
---|
56 |
|
---|
57 | private int mouseDragStartOffset;
|
---|
58 |
|
---|
59 | /**
|
---|
60 | * Listens for events from the current text area.
|
---|
61 | */
|
---|
62 | private Listener l;
|
---|
63 |
|
---|
64 | /**
|
---|
65 | * Used in {@link #paintComponent(Graphics)} to prevent reallocation on
|
---|
66 | * each paint.
|
---|
67 | */
|
---|
68 | private Insets textAreaInsets;
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * Used in {@link #paintComponent(Graphics)} to prevent reallocation on
|
---|
72 | * each paint.
|
---|
73 | */
|
---|
74 | private Rectangle visibleRect;
|
---|
75 |
|
---|
76 | /**
|
---|
77 | * The index at which line numbering should start. The default value is
|
---|
78 | * <code>1</code>, but applications can change this if, for example, they
|
---|
79 | * are displaying a subset of lines in a file.
|
---|
80 | */
|
---|
81 | private int lineNumberingStartIndex;
|
---|
82 |
|
---|
83 |
|
---|
84 | /**
|
---|
85 | * Constructs a new <code>LineNumberList</code> using default values for
|
---|
86 | * line number color (gray) and highlighting the current line.
|
---|
87 | *
|
---|
88 | * @param textArea The text component for which line numbers will be
|
---|
89 | * displayed.
|
---|
90 | */
|
---|
91 | public LineNumberList(RTextArea textArea) {
|
---|
92 | this(textArea, null);
|
---|
93 | }
|
---|
94 |
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * Constructs a new <code>LineNumberList</code>.
|
---|
98 | *
|
---|
99 | * @param textArea The text component for which line numbers will be
|
---|
100 | * displayed.
|
---|
101 | * @param numberColor The color to use for the line numbers. If this is
|
---|
102 | * <code>null</code>, gray will be used.
|
---|
103 | */
|
---|
104 | public LineNumberList(RTextArea textArea, Color numberColor) {
|
---|
105 |
|
---|
106 | super(textArea);
|
---|
107 |
|
---|
108 | if (numberColor!=null) {
|
---|
109 | setForeground(numberColor);
|
---|
110 | }
|
---|
111 | else {
|
---|
112 | setForeground(Color.GRAY);
|
---|
113 | }
|
---|
114 |
|
---|
115 | // Initialize currentLine; otherwise, the current line won't start
|
---|
116 | // off as highlighted.
|
---|
117 | currentLine = 0;
|
---|
118 | setLineNumberingStartIndex(1);
|
---|
119 |
|
---|
120 | visibleRect = new Rectangle(); // Must be initialized
|
---|
121 |
|
---|
122 | addMouseListener(this);
|
---|
123 | addMouseMotionListener(this);
|
---|
124 |
|
---|
125 | aaHints = RSyntaxUtilities.getDesktopAntiAliasHints();
|
---|
126 |
|
---|
127 | }
|
---|
128 |
|
---|
129 |
|
---|
130 | /**
|
---|
131 | * Overridden to set width of this component correctly when we are first
|
---|
132 | * displayed (as keying off of the RTextArea gives us (0,0) when it isn't
|
---|
133 | * yet displayed.
|
---|
134 | */
|
---|
135 | public void addNotify() {
|
---|
136 | super.addNotify();
|
---|
137 | if (textArea!=null) {
|
---|
138 | l.install(textArea); // Won't double-install
|
---|
139 | }
|
---|
140 | updateCellWidths();
|
---|
141 | updateCellHeights();
|
---|
142 | }
|
---|
143 |
|
---|
144 |
|
---|
145 | /**
|
---|
146 | * Returns the starting line's line number. The default value is
|
---|
147 | * <code>1</code>.
|
---|
148 | *
|
---|
149 | * @return The index
|
---|
150 | * @see #setLineNumberingStartIndex(int)
|
---|
151 | */
|
---|
152 | public int getLineNumberingStartIndex() {
|
---|
153 | return lineNumberingStartIndex;
|
---|
154 | }
|
---|
155 |
|
---|
156 |
|
---|
157 | /**
|
---|
158 | * {@inheritDoc}
|
---|
159 | */
|
---|
160 | public Dimension getPreferredSize() {
|
---|
161 | int h = textArea!=null ? textArea.getHeight() : 100; // Arbitrary
|
---|
162 | return new Dimension(cellWidth, h);
|
---|
163 | }
|
---|
164 |
|
---|
165 |
|
---|
166 | /**
|
---|
167 | * Returns the width of the empty border on this component's right-hand
|
---|
168 | * side (or left-hand side, if the orientation is RTL).
|
---|
169 | *
|
---|
170 | * @return The border width.
|
---|
171 | */
|
---|
172 | private int getRhsBorderWidth() {
|
---|
173 | int w = 4;
|
---|
174 | if (textArea instanceof RSyntaxTextArea) {
|
---|
175 | if (((RSyntaxTextArea)textArea).isCodeFoldingEnabled()) {
|
---|
176 | w = 0;
|
---|
177 | }
|
---|
178 | }
|
---|
179 | return w;
|
---|
180 | }
|
---|
181 |
|
---|
182 |
|
---|
183 | /**
|
---|
184 | * {@inheritDoc}
|
---|
185 | */
|
---|
186 | void handleDocumentEvent(DocumentEvent e) {
|
---|
187 | int newLineCount = textArea!=null ? textArea.getLineCount() : 0;
|
---|
188 | if (newLineCount!=currentLineCount) {
|
---|
189 | // Adjust the amount of space the line numbers take up,
|
---|
190 | // if necessary.
|
---|
191 | if (newLineCount/10 != currentLineCount/10) {
|
---|
192 | updateCellWidths();
|
---|
193 | }
|
---|
194 | currentLineCount = newLineCount;
|
---|
195 | repaint();
|
---|
196 | }
|
---|
197 | }
|
---|
198 |
|
---|
199 |
|
---|
200 | /**
|
---|
201 | * {@inheritDoc}
|
---|
202 | */
|
---|
203 | void lineHeightsChanged() {
|
---|
204 | updateCellHeights();
|
---|
205 | }
|
---|
206 |
|
---|
207 |
|
---|
208 | public void mouseClicked(MouseEvent e) {
|
---|
209 | }
|
---|
210 |
|
---|
211 |
|
---|
212 | public void mouseDragged(MouseEvent e) {
|
---|
213 | if (mouseDragStartOffset>-1) {
|
---|
214 | int pos = textArea.viewToModel(new Point(0, e.getY()));
|
---|
215 | if (pos>=0) { // Not -1
|
---|
216 | textArea.setCaretPosition(mouseDragStartOffset);
|
---|
217 | textArea.moveCaretPosition(pos);
|
---|
218 | }
|
---|
219 | }
|
---|
220 | }
|
---|
221 |
|
---|
222 |
|
---|
223 | public void mouseEntered(MouseEvent e) {
|
---|
224 | }
|
---|
225 |
|
---|
226 |
|
---|
227 | public void mouseExited(MouseEvent e) {
|
---|
228 | }
|
---|
229 |
|
---|
230 |
|
---|
231 | public void mouseMoved(MouseEvent e) {
|
---|
232 | }
|
---|
233 |
|
---|
234 |
|
---|
235 | public void mousePressed(MouseEvent e) {
|
---|
236 | if (textArea==null) {
|
---|
237 | return;
|
---|
238 | }
|
---|
239 | if (e.getButton()==MouseEvent.BUTTON1) {
|
---|
240 | int pos = textArea.viewToModel(new Point(0, e.getY()));
|
---|
241 | if (pos>=0) { // Not -1
|
---|
242 | textArea.setCaretPosition(pos);
|
---|
243 | }
|
---|
244 | mouseDragStartOffset = pos;
|
---|
245 | }
|
---|
246 | else {
|
---|
247 | mouseDragStartOffset = -1;
|
---|
248 | }
|
---|
249 | }
|
---|
250 |
|
---|
251 |
|
---|
252 | public void mouseReleased(MouseEvent e) {
|
---|
253 | }
|
---|
254 |
|
---|
255 |
|
---|
256 | /**
|
---|
257 | * Paints this component.
|
---|
258 | *
|
---|
259 | * @param g The graphics context.
|
---|
260 | */
|
---|
261 | protected void paintComponent(Graphics g) {
|
---|
262 |
|
---|
263 | if (textArea==null) {
|
---|
264 | return;
|
---|
265 | }
|
---|
266 |
|
---|
267 | visibleRect = g.getClipBounds(visibleRect);
|
---|
268 | if (visibleRect==null) { // ???
|
---|
269 | visibleRect = getVisibleRect();
|
---|
270 | }
|
---|
271 | //System.out.println("LineNumberList repainting: " + visibleRect);
|
---|
272 | if (visibleRect==null) {
|
---|
273 | return;
|
---|
274 | }
|
---|
275 |
|
---|
276 | Color bg = getBackground();
|
---|
277 | if (getGutter()!=null) { // Should always be true
|
---|
278 | bg = getGutter().getBackground();
|
---|
279 | }
|
---|
280 | g.setColor(bg);
|
---|
281 | g.fillRect(0,visibleRect.y, cellWidth,visibleRect.height);
|
---|
282 | g.setFont(getFont());
|
---|
283 | if (aaHints!=null) {
|
---|
284 | ((Graphics2D)g).addRenderingHints(aaHints);
|
---|
285 | }
|
---|
286 |
|
---|
287 | if (textArea.getLineWrap()) {
|
---|
288 | paintWrappedLineNumbers(g, visibleRect);
|
---|
289 | return;
|
---|
290 | }
|
---|
291 |
|
---|
292 | // Get where to start painting (top of the row), and where to paint
|
---|
293 | // the line number (drawString expects y==baseline).
|
---|
294 | // We need to be "scrolled up" just enough for the missing part of
|
---|
295 | // the first line.
|
---|
296 | int topLine = visibleRect.y/cellHeight;
|
---|
297 | int actualTopY = topLine*cellHeight;
|
---|
298 | textAreaInsets = textArea.getInsets(textAreaInsets);
|
---|
299 | actualTopY += textAreaInsets.top;
|
---|
300 | int y = actualTopY + ascent;
|
---|
301 |
|
---|
302 | // Get the actual first line to paint, taking into account folding.
|
---|
303 | FoldManager fm = null;
|
---|
304 | if (textArea instanceof RSyntaxTextArea) {
|
---|
305 | fm = ((RSyntaxTextArea)textArea).getFoldManager();
|
---|
306 | topLine += fm.getHiddenLineCountAbove(topLine, true);
|
---|
307 | }
|
---|
308 | final int RHS_BORDER_WIDTH = getRhsBorderWidth();
|
---|
309 |
|
---|
310 | /*
|
---|
311 | // Highlight the current line's line number, if desired.
|
---|
312 | if (textArea.getHighlightCurrentLine() && currentLine>=topLine &&
|
---|
313 | currentLine<=bottomLine) {
|
---|
314 | g.setColor(textArea.getCurrentLineHighlightColor());
|
---|
315 | g.fillRect(0,actualTopY+(currentLine-topLine)*cellHeight,
|
---|
316 | cellWidth,cellHeight);
|
---|
317 | }
|
---|
318 | */
|
---|
319 |
|
---|
320 | // Paint line numbers
|
---|
321 | g.setColor(getForeground());
|
---|
322 | boolean ltr = getComponentOrientation().isLeftToRight();
|
---|
323 | if (ltr) {
|
---|
324 | FontMetrics metrics = g.getFontMetrics();
|
---|
325 | int rhs = getWidth() - RHS_BORDER_WIDTH;
|
---|
326 | int line = topLine + 1;
|
---|
327 | while (y<visibleRect.y+visibleRect.height+ascent && line<=textArea.getLineCount()) {
|
---|
328 | String number = Integer.toString(line + getLineNumberingStartIndex() - 1);
|
---|
329 | int width = metrics.stringWidth(number);
|
---|
330 | g.drawString(number, rhs-width,y);
|
---|
331 | y += cellHeight;
|
---|
332 | Fold fold = fm.getFoldForLine(line-1);
|
---|
333 | // Skip to next line to paint, taking extra care for lines with
|
---|
334 | // block ends and begins together, e.g. "} else {"
|
---|
335 | while (fold!=null && fold.isCollapsed()) {
|
---|
336 | int hiddenLineCount = fold.getLineCount();
|
---|
337 | if (hiddenLineCount==0) {
|
---|
338 | // Fold parser identified a 0-line fold region... This
|
---|
339 | // is really a bug, but we'll handle it gracefully.
|
---|
340 | break;
|
---|
341 | }
|
---|
342 | line += hiddenLineCount;
|
---|
343 | fold = fm.getFoldForLine(line-1);
|
---|
344 | }
|
---|
345 | line++;
|
---|
346 | }
|
---|
347 | }
|
---|
348 | else { // rtl
|
---|
349 | int line = topLine + 1;
|
---|
350 | while (y<visibleRect.y+visibleRect.height && line<textArea.getLineCount()) {
|
---|
351 | String number = Integer.toString(line + getLineNumberingStartIndex() - 1);
|
---|
352 | g.drawString(number, RHS_BORDER_WIDTH, y);
|
---|
353 | y += cellHeight;
|
---|
354 | Fold fold = fm.getFoldForLine(line-1);
|
---|
355 | // Skip to next line to paint, taking extra care for lines with
|
---|
356 | // block ends and begins together, e.g. "} else {"
|
---|
357 | while (fold!=null && fold.isCollapsed()) {
|
---|
358 | line += fold.getLineCount();
|
---|
359 | fold = fm.getFoldForLine(line);
|
---|
360 | }
|
---|
361 | line++;
|
---|
362 | }
|
---|
363 | }
|
---|
364 |
|
---|
365 | }
|
---|
366 |
|
---|
367 |
|
---|
368 | /**
|
---|
369 | * Paints line numbers for text areas with line wrap enabled.
|
---|
370 | *
|
---|
371 | * @param g The graphics context.
|
---|
372 | * @param visibleRect The visible rectangle of these line numbers.
|
---|
373 | */
|
---|
374 | private void paintWrappedLineNumbers(Graphics g, Rectangle visibleRect) {
|
---|
375 |
|
---|
376 | // The variables we use are as follows:
|
---|
377 | // - visibleRect is the "visible" area of the text area; e.g.
|
---|
378 | // [0,100, 300,100+(lineCount*cellHeight)-1].
|
---|
379 | // actualTop.y is the topmost-pixel in the first logical line we
|
---|
380 | // paint. Note that we may well not paint this part of the logical
|
---|
381 | // line, as it may be broken into many physical lines, with the first
|
---|
382 | // few physical lines scrolled past. Note also that this is NOT the
|
---|
383 | // visible rect of this line number list; this line number list has
|
---|
384 | // visible rect == [0,0, insets.left-1,visibleRect.height-1].
|
---|
385 | // - offset (<=0) is the y-coordinate at which we begin painting when
|
---|
386 | // we begin painting with the first logical line. This can be
|
---|
387 | // negative, signifying that we've scrolled past the actual topmost
|
---|
388 | // part of this line.
|
---|
389 |
|
---|
390 | // The algorithm is as follows:
|
---|
391 | // - Get the starting y-coordinate at which to paint. This may be
|
---|
392 | // above the first visible y-coordinate as we're in line-wrapping
|
---|
393 | // mode, but we always paint entire logical lines.
|
---|
394 | // - Paint that line's line number and highlight, if appropriate.
|
---|
395 | // Increment y to be just below the are we just painted (i.e., the
|
---|
396 | // beginning of the next logical line's view area).
|
---|
397 | // - Get the ending visual position for that line. We can now loop
|
---|
398 | // back, paint this line, and continue until our y-coordinate is
|
---|
399 | // past the last visible y-value.
|
---|
400 |
|
---|
401 | // We avoid using modelToView/viewToModel where possible, as these
|
---|
402 | // methods trigger a parsing of the line into syntax tokens, which is
|
---|
403 | // costly. It's cheaper to just grab the child views' bounds.
|
---|
404 |
|
---|
405 | // Some variables we'll be using.
|
---|
406 | int width = getWidth();
|
---|
407 |
|
---|
408 | RTextAreaUI ui = (RTextAreaUI)textArea.getUI();
|
---|
409 | View v = ui.getRootView(textArea).getView(0);
|
---|
410 | //boolean currentLineHighlighted = textArea.getHighlightCurrentLine();
|
---|
411 | Document doc = textArea.getDocument();
|
---|
412 | Element root = doc.getDefaultRootElement();
|
---|
413 | int lineCount = root.getElementCount();
|
---|
414 | int topPosition = textArea.viewToModel(
|
---|
415 | new Point(visibleRect.x,visibleRect.y));
|
---|
416 | int topLine = root.getElementIndex(topPosition);
|
---|
417 | FoldManager fm = ((RSyntaxTextArea)textArea).getFoldManager();
|
---|
418 |
|
---|
419 | // Compute the y at which to begin painting text, taking into account
|
---|
420 | // that 1 logical line => at least 1 physical line, so it may be that
|
---|
421 | // y<0. The computed y-value is the y-value of the top of the first
|
---|
422 | // (possibly) partially-visible view.
|
---|
423 | Rectangle visibleEditorRect = ui.getVisibleEditorRect();
|
---|
424 | Rectangle r = LineNumberList.getChildViewBounds(v, topLine,
|
---|
425 | visibleEditorRect);
|
---|
426 | int y = r.y;
|
---|
427 | final int RHS_BORDER_WIDTH = getRhsBorderWidth();
|
---|
428 | int rhs;
|
---|
429 | boolean ltr = getComponentOrientation().isLeftToRight();
|
---|
430 | if (ltr) {
|
---|
431 | rhs = width - RHS_BORDER_WIDTH;
|
---|
432 | }
|
---|
433 | else { // rtl
|
---|
434 | rhs = RHS_BORDER_WIDTH;
|
---|
435 | }
|
---|
436 | int visibleBottom = visibleRect.y + visibleRect.height;
|
---|
437 | FontMetrics metrics = g.getFontMetrics();
|
---|
438 |
|
---|
439 | // Keep painting lines until our y-coordinate is past the visible
|
---|
440 | // end of the text area.
|
---|
441 | g.setColor(getForeground());
|
---|
442 |
|
---|
443 | while (y < visibleBottom) {
|
---|
444 |
|
---|
445 | r = LineNumberList.getChildViewBounds(v, topLine, visibleEditorRect);
|
---|
446 |
|
---|
447 | /*
|
---|
448 | // Highlight the current line's line number, if desired.
|
---|
449 | if (currentLineHighlighted && topLine==currentLine) {
|
---|
450 | g.setColor(textArea.getCurrentLineHighlightColor());
|
---|
451 | g.fillRect(0,y, width,(r.y+r.height)-y);
|
---|
452 | g.setColor(getForeground());
|
---|
453 | }
|
---|
454 | */
|
---|
455 |
|
---|
456 | // Paint the line number.
|
---|
457 | int index = (topLine+1) + getLineNumberingStartIndex() - 1;
|
---|
458 | String number = Integer.toString(index);
|
---|
459 | if (ltr) {
|
---|
460 | int strWidth = metrics.stringWidth(number);
|
---|
461 | g.drawString(number, rhs-strWidth,y+ascent);
|
---|
462 | }
|
---|
463 | else {
|
---|
464 | int x = RHS_BORDER_WIDTH;
|
---|
465 | g.drawString(number, x, y+ascent);
|
---|
466 | }
|
---|
467 |
|
---|
468 | // The next possible y-coordinate is just after the last line
|
---|
469 | // painted.
|
---|
470 | y += r.height;
|
---|
471 |
|
---|
472 | // Update topLine (we're actually using it for our "current line"
|
---|
473 | // variable now).
|
---|
474 | Fold fold = fm.getFoldForLine(topLine);
|
---|
475 | if (fold!=null && fold.isCollapsed()) {
|
---|
476 | topLine += fold.getCollapsedLineCount();
|
---|
477 | }
|
---|
478 | topLine++;
|
---|
479 | if (topLine>=lineCount)
|
---|
480 | break;
|
---|
481 |
|
---|
482 | }
|
---|
483 |
|
---|
484 | }
|
---|
485 |
|
---|
486 |
|
---|
487 | /**
|
---|
488 | * Called when this component is removed from the view hierarchy.
|
---|
489 | */
|
---|
490 | public void removeNotify() {
|
---|
491 | super.removeNotify();
|
---|
492 | if (textArea!=null) {
|
---|
493 | l.uninstall(textArea);
|
---|
494 | }
|
---|
495 | }
|
---|
496 |
|
---|
497 |
|
---|
498 | /**
|
---|
499 | * Repaints a single line in this list.
|
---|
500 | *
|
---|
501 | * @param line The line to repaint.
|
---|
502 | */
|
---|
503 | private void repaintLine(int line) {
|
---|
504 | int y = textArea.getInsets().top;
|
---|
505 | y += line*cellHeight;
|
---|
506 | repaint(0,y, cellWidth,cellHeight);
|
---|
507 | }
|
---|
508 |
|
---|
509 |
|
---|
510 | /**
|
---|
511 | * Overridden to ensure line number cell sizes are updated with the
|
---|
512 | * font size change.
|
---|
513 | *
|
---|
514 | * @param font The new font to use for line numbers.
|
---|
515 | */
|
---|
516 | public void setFont(Font font) {
|
---|
517 | super.setFont(font);
|
---|
518 | updateCellWidths();
|
---|
519 | updateCellHeights();
|
---|
520 | }
|
---|
521 |
|
---|
522 |
|
---|
523 | /**
|
---|
524 | * Sets the starting line's line number. The default value is
|
---|
525 | * <code>1</code>. Applications can call this method to change this value
|
---|
526 | * if they are displaying a subset of lines in a file, for example.
|
---|
527 | *
|
---|
528 | * @param index The new index.
|
---|
529 | * @see #getLineNumberingStartIndex()
|
---|
530 | */
|
---|
531 | public void setLineNumberingStartIndex(int index) {
|
---|
532 | lineNumberingStartIndex = index;
|
---|
533 | }
|
---|
534 |
|
---|
535 |
|
---|
536 | /**
|
---|
537 | * Sets the text area being displayed.
|
---|
538 | *
|
---|
539 | * @param textArea The text area.
|
---|
540 | */
|
---|
541 | public void setTextArea(RTextArea textArea) {
|
---|
542 |
|
---|
543 | if (l==null) {
|
---|
544 | l = new Listener();
|
---|
545 | }
|
---|
546 |
|
---|
547 | if (this.textArea!=null) {
|
---|
548 | l.uninstall(textArea);
|
---|
549 | }
|
---|
550 |
|
---|
551 | super.setTextArea(textArea);
|
---|
552 |
|
---|
553 | if (textArea!=null) {
|
---|
554 | l.install(textArea); // Won't double-install
|
---|
555 | updateCellHeights();
|
---|
556 | updateCellWidths();
|
---|
557 | }
|
---|
558 |
|
---|
559 | }
|
---|
560 |
|
---|
561 |
|
---|
562 | /**
|
---|
563 | * Changes the height of the cells in the JList so that they are as tall as
|
---|
564 | * font. This function should be called whenever the user changes the Font
|
---|
565 | * of <code>textArea</code>.
|
---|
566 | */
|
---|
567 | private void updateCellHeights() {
|
---|
568 | if (textArea!=null) {
|
---|
569 | cellHeight = textArea.getLineHeight();
|
---|
570 | ascent = textArea.getMaxAscent();
|
---|
571 | }
|
---|
572 | else {
|
---|
573 | cellHeight = 20; // Arbitrary number.
|
---|
574 | ascent = 5; // Also arbitrary
|
---|
575 | }
|
---|
576 | repaint();
|
---|
577 | }
|
---|
578 |
|
---|
579 |
|
---|
580 | /**
|
---|
581 | * Changes the width of the cells in the JList so you can see every digit
|
---|
582 | * of each.
|
---|
583 | */
|
---|
584 | void updateCellWidths() {
|
---|
585 |
|
---|
586 | int oldCellWidth = cellWidth;
|
---|
587 | cellWidth = getRhsBorderWidth();
|
---|
588 |
|
---|
589 | // Adjust the amount of space the line numbers take up, if necessary.
|
---|
590 | if (textArea!=null) {
|
---|
591 | Font font = getFont();
|
---|
592 | if (font!=null) {
|
---|
593 | FontMetrics fontMetrics = getFontMetrics(font);
|
---|
594 | int count = 0;
|
---|
595 | int lineCount = textArea.getLineCount();
|
---|
596 | do {
|
---|
597 | lineCount = lineCount/10;
|
---|
598 | count++;
|
---|
599 | } while (lineCount >= 10);
|
---|
600 | cellWidth += fontMetrics.charWidth('9')*(count+1) + 3;
|
---|
601 | }
|
---|
602 | }
|
---|
603 |
|
---|
604 | if (cellWidth!=oldCellWidth) { // Always true
|
---|
605 | revalidate();
|
---|
606 | }
|
---|
607 |
|
---|
608 | }
|
---|
609 |
|
---|
610 |
|
---|
611 | /**
|
---|
612 | * Listens for events in the text area we're interested in.
|
---|
613 | */
|
---|
614 | private class Listener implements CaretListener, PropertyChangeListener {
|
---|
615 |
|
---|
616 | private boolean installed;
|
---|
617 |
|
---|
618 | public void caretUpdate(CaretEvent e) {
|
---|
619 |
|
---|
620 | int dot = textArea.getCaretPosition();
|
---|
621 |
|
---|
622 | // We separate the line wrap/no line wrap cases because word wrap
|
---|
623 | // can make a single line from the model (document) be on multiple
|
---|
624 | // lines on the screen (in the view); thus, we have to enhance the
|
---|
625 | // logic for that case a bit - we check the actual y-coordinate of
|
---|
626 | // the caret when line wrap is enabled. For the no-line-wrap case,
|
---|
627 | // getting the line number of the caret suffices. This increases
|
---|
628 | // efficiency in the no-line-wrap case.
|
---|
629 |
|
---|
630 | if (textArea.getLineWrap()==false) {
|
---|
631 | int line = textArea.getDocument().getDefaultRootElement().
|
---|
632 | getElementIndex(dot);
|
---|
633 | if (currentLine!=line) {
|
---|
634 | repaintLine(line);
|
---|
635 | repaintLine(currentLine);
|
---|
636 | currentLine = line;
|
---|
637 | }
|
---|
638 | }
|
---|
639 | else { // lineWrap enabled; must check actual y position of caret
|
---|
640 | try {
|
---|
641 | int y = textArea.yForLineContaining(dot);
|
---|
642 | if (y!=lastY) {
|
---|
643 | lastY = y;
|
---|
644 | currentLine = textArea.getDocument().
|
---|
645 | getDefaultRootElement().getElementIndex(dot);
|
---|
646 | repaint(); // *Could* be optimized...
|
---|
647 | }
|
---|
648 | } catch (BadLocationException ble) {
|
---|
649 | ble.printStackTrace();
|
---|
650 | }
|
---|
651 | }
|
---|
652 |
|
---|
653 | }
|
---|
654 |
|
---|
655 | public void install(RTextArea textArea) {
|
---|
656 | if (!installed) {
|
---|
657 | //System.out.println("Installing");
|
---|
658 | textArea.addCaretListener(this);
|
---|
659 | textArea.addPropertyChangeListener(this);
|
---|
660 | caretUpdate(null); // Force current line highlight repaint
|
---|
661 | installed = true;
|
---|
662 | }
|
---|
663 | }
|
---|
664 |
|
---|
665 | public void propertyChange(PropertyChangeEvent e) {
|
---|
666 |
|
---|
667 | String name = e.getPropertyName();
|
---|
668 |
|
---|
669 | // If they change the current line highlight in any way...
|
---|
670 | if (RTextArea.HIGHLIGHT_CURRENT_LINE_PROPERTY.equals(name) ||
|
---|
671 | RTextArea.CURRENT_LINE_HIGHLIGHT_COLOR_PROPERTY.equals(name)) {
|
---|
672 | repaintLine(currentLine);
|
---|
673 | }
|
---|
674 |
|
---|
675 | }
|
---|
676 |
|
---|
677 | public void uninstall(RTextArea textArea) {
|
---|
678 | if (installed) {
|
---|
679 | //System.out.println("Uninstalling");
|
---|
680 | textArea.removeCaretListener(this);
|
---|
681 | textArea.removePropertyChangeListener(this);
|
---|
682 | installed = false;
|
---|
683 | }
|
---|
684 | }
|
---|
685 |
|
---|
686 | }
|
---|
687 |
|
---|
688 |
|
---|
689 | } |
---|