KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output2 > WrappedTextView


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.core.output2;
20
21 import java.util.HashMap JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import javax.swing.*;
25 import javax.swing.text.*;
26 import java.awt.*;
27 import org.openide.util.Exceptions;
28
29 /**
30  * A custom Swing text View which supports line wrapping. The default Swing
31  * line wrapping code is not appropriate for our purposes - particularly, it
32  * will iterate the entire buffer multiple times to determine break positions.
33  * Since it would defeat the purpose of using a memory mapped file to have to
34  * pull the entire thing into memory every time it's painted or its size should
35  * be calculated, we have this class instead.
36  * <p>
37  * All position/line calculations this view does are based on the integer array
38  * of line offsets kept by the writer's Lines object.
39  *
40  * @author Tim Boudreau
41  */

42 public class WrappedTextView extends View {
43     /**
44      * The component we will paint
45      */

46     private JTextComponent comp;
47     /**
48      * Precalculated number of characters per line
49      */

50     private int charsPerLine = -1;
51     /**
52      * Precalculated font descent, used to adjust the bounding rectangle of
53      * characters as returned by modelToView.
54      */

55     private int fontDescent = -1;
56     /**
57      * A scratch Segment object to avoid allocation while painting lines
58      */

59     private static final Segment SEGMENT = new Segment();
60     /**
61      * Precalculated width (in pixels) we are to paint into, the end being the wrap point
62      */

63     private int width = -1;
64     /**
65      * Flag indicating we need to recalculate metrics before painting
66      */

67     private boolean changed = true;
68     /**
69      * Precalculated width of a single character (assumes fixed width font).
70      */

71     private int charWidth = -1;
72     /**
73      * Precalculated height of a single character (assumes fixed width font).
74      */

75     private int charHeight = -1;
76     /**
77      * A scratchpad int array
78      */

79     static final int[] ln = new int[3];
80     /**
81      * Flag indicating that the antialiasing flag is set on the Graphics object.
82      * We do a somewhat prettier arrow if it is.
83      */

84     private boolean aa = false;
85     /** set antialiasing hints when it's requested. */
86     private static final boolean antialias = Boolean.getBoolean ("swing.aatext") || //NOI18N
87
"Aqua".equals (UIManager.getLookAndFeel().getID()); // NOI18N
88

89     //Self explanatory...
90
static Color selectedFg;
91     static Color unselectedFg;
92     static Color selectedLinkFg;
93     static Color unselectedLinkFg;
94     static Color selectedImportantLinkFg;
95     static Color unselectedImportantLinkFg;
96     static Color selectedErr;
97     static Color unselectedErr;
98     static final Color arrowColor = new Color (80, 162, 80);
99
100     private static Map JavaDoc hintsMap = null;
101     
102     static final Map JavaDoc getHints() {
103         if (hintsMap == null) {
104             //Thanks to Phil Race for making this possible
105
hintsMap = (Map JavaDoc)(Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints")); //NOI18N
106
if (hintsMap == null) {
107                 hintsMap = new HashMap JavaDoc();
108                 if (antialias) {
109                     hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
110                     hintsMap.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
111                 }
112             }
113         }
114         return hintsMap;
115     }
116    
117     static {
118         selectedFg = UIManager.getColor ("nb.output.foreground.selected"); //NOI18N
119
if (selectedFg == null) {
120             selectedFg = UIManager.getColor("textText") == null ? Color.BLACK : //NOI18N
121
UIManager.getColor("textText"); //NOI18N
122
}
123         
124         unselectedFg = UIManager.getColor ("nb.output.foreground"); //NOI18N
125
if (unselectedFg == null) {
126             unselectedFg = selectedFg;
127         }
128
129         selectedLinkFg = UIManager.getColor("nb.output.link.foreground.selected"); //NOI18N
130
if (selectedLinkFg == null) selectedLinkFg = Color.BLUE.darker();
131         
132         unselectedLinkFg = UIManager.getColor("nb.output.link.foreground"); //NOI18N
133
if (unselectedLinkFg == null) {
134             unselectedLinkFg = selectedLinkFg;
135         }
136         
137         selectedImportantLinkFg = UIManager.getColor("nb.output.link.foreground.important.selected"); //NOI18N
138
if (selectedImportantLinkFg == null) {
139             selectedImportantLinkFg = selectedLinkFg.brighter();
140         }
141         
142         unselectedImportantLinkFg = UIManager.getColor("nb.output.link.foreground.important"); //NOI18N
143
if (unselectedImportantLinkFg == null) {
144             unselectedImportantLinkFg = selectedImportantLinkFg;
145         }
146
147         selectedErr = UIManager.getColor ("nb.output.err.foreground.selected"); //NOI18N
148
if (selectedErr == null) {
149             selectedErr = new Color (164, 0, 0);
150         }
151         unselectedErr = UIManager.getColor ("nb.output.err.foreground"); //NOI18N
152
if (unselectedErr == null) {
153             unselectedErr = selectedErr;
154         }
155     }
156
157
158     public WrappedTextView(Element elem, JTextComponent comp) {
159         super(elem);
160         this.comp = comp;
161     }
162
163
164     public float getPreferredSpan(int axis) {
165         OutputDocument doc = odoc();
166         float result = 0;
167         if (doc != null) {
168             switch (axis) {
169                 case X_AXIS :
170                     result = getCharsPerLine();
171                     break;
172                 case Y_AXIS :
173                     updateInfo(null);
174                     result = doc.getLines().getLogicalLineCountIfWrappedAt(getCharsPerLine()) * charHeight() + fontDescent();
175                     break;
176                 default :
177                     throw new IllegalArgumentException JavaDoc (Integer.toString(axis));
178             }
179         }
180         return result;
181     }
182
183     public float getMinimumSpan(int axis) {
184         return getPreferredSpan(axis);
185     }
186
187     public float getMaximumSpan(int axis) {
188         return getPreferredSpan(axis);
189     }
190
191     /**
192      * Get the last calculated character width, returning a reasonable
193      * default if none has been calculated.
194      *
195      * @return The character width
196      */

197     private int charWidth() {
198         if (charWidth == -1) {
199             return 12;
200         }
201         return charWidth;
202     }
203
204     /**
205      * Get the last calculated character height, returning a reasonable
206      * default if none has been calculated.
207      *
208      * @return The character height
209      */

210     private int charHeight() {
211         if (charHeight == -1) {
212             return 7;
213         }
214         return charHeight;
215     }
216
217     /**
218      * Get the component's document as an instance of OutputDocument, if it
219      * is one, returning null if it is not (briefly it will not be after the
220      * editor kit has been installed - this is unavoidable).
221      *
222      * @return An instance of OutputDocument or null.
223      */

224     private OutputDocument odoc() {
225         Document doc = comp.getDocument();
226         if (doc instanceof OutputDocument) {
227             return (OutputDocument) doc;
228         }
229         return null;
230     }
231
232     /**
233      * Set a flag indicating that on the next paint, widths, character widths,
234      * etc. need to be recalculated.
235      *
236      */

237     public void setChanged() {
238         changed = true;
239         updateInfo (null);
240         preferenceChanged(this, true, true);
241     }
242
243     /**
244      * Get the number of characters per line that can be displayed before wrapping
245      * given the component's current font and size.
246      *
247      * @return Characters per line, or 80 if not yet calculated
248      */

249     private int getCharsPerLine() {
250         if (charsPerLine == -1) {
251             return 80;
252         }
253         return getWidth() / charWidth();
254     }
255
256     /**
257      * Get the last known width of the component we're painting into
258      *
259      * @return The width we should paint into
260      */

261     private int getWidth() {
262         if (comp.getParent() instanceof JViewport) {
263             JViewport jv = (JViewport) comp.getParent();
264             width = jv.getExtentSize().width - (aa ? 18 : 17);
265         } else {
266             width = comp.getWidth() - (aa ? 18 : 17);
267         }
268         return width;
269     }
270
271     /**
272      * Get the font descent for the last known font, or a reasonable default if unknown.
273      * This is added to the y position of character rectangles in modelToView() so
274      * painting the selection includes the complete character and does not interfere
275      * with the line above.
276      *
277      * @return The font descent.
278      */

279     private int fontDescent() {
280         if (fontDescent == -1) {
281             return 4;
282         }
283         return fontDescent;
284     }
285
286     /**
287      * Update metrics we use when painting, such as the visible area of the component,
288      * the charcter width/height, etc.
289      * @param g
290      */

291     public void updateInfo(Graphics g) {
292         if (charWidth == -1 || changed) {
293             if (g != null) {
294                 aa = ((Graphics2D) g).getRenderingHint(RenderingHints.KEY_ANTIALIASING) ==
295                     RenderingHints.VALUE_ANTIALIAS_ON;
296
297                 FontMetrics fm = g.getFontMetrics(comp.getFont());
298                 charWidth = fm.charWidth('m'); //NOI18N
299
charHeight = fm.getHeight();
300                 fontDescent = fm.getMaxDescent();
301                 charsPerLine = width / charWidth;
302             }
303             if (comp.getParent() instanceof JViewport) {
304                 JViewport jv = (JViewport) comp.getParent();
305                 width = jv.getExtentSize().width - (aa ? 18 : 17);
306             } else {
307                 width = comp.getWidth() - (aa ? 18 : 17);
308             }
309         }
310     }
311
312     /**
313      * Get the left hand margin required for printing line wrap decorations.
314      *
315      * @return A margin in pixels
316      */

317     private static int margin() {
318         return 9;
319     }
320
321     public void paint(Graphics g, Shape allocation) {
322         
323         ((Graphics2D)g).addRenderingHints(getHints());
324         
325         updateInfo(g);
326         
327         comp.getHighlighter().paint(g);
328         
329         Rectangle vis = comp.getVisibleRect();
330         
331         OutputDocument d = odoc();
332         if (d != null) {
333             Rectangle clip = g.getClipBounds();
334             clip.y = Math.max (0, clip.y - charHeight());
335             clip.height += charHeight() * 2;
336
337             int lineCount = d.getElementCount();
338             if (lineCount == 0) {
339                 return;
340             }
341
342             int charsPerLine = getCharsPerLine();
343             int physicalLine = clip.y / charHeight;
344             ln[0] = physicalLine;
345             d.getLines().toLogicalLineIndex(ln, charsPerLine);
346
347             int firstline = ln[0];
348             int count = (lineCount - firstline);
349             g.setColor (comp.getForeground());
350             Segment seg = SwingUtilities.isEventDispatchThread() ? SEGMENT : new Segment();
351
352             int selStart = comp.getSelectionStart();
353             int selEnd = comp.getSelectionEnd();
354             int y = (clip.y - (clip.y % charHeight()) + charHeight());
355             
356             try {
357                 for (int i=0; i < count; i++) {
358                     int lineStart = d.getLineStart(i + firstline);
359                     int lineEnd = d.getLineEnd (i + firstline);
360                     int length = lineEnd - lineStart;
361
362                     g.setColor(getColorForLocation(lineStart, d, true)); //XXX should not always be 'true'
363

364                     //Get the text to print into the segment's array
365
d.getText(lineStart, length, seg);
366
367                     //Get the number of logical lines this physical line contains
368
int logicalLines = seg.count <= charsPerLine ? 1 : 1 + (length / charsPerLine);
369
370                     int currLogicalLine = 0;
371
372                     if (i == 0 && logicalLines > 0) {
373                         while (ln[1] > currLogicalLine) {
374                             //Fast forward through logical lines above the first one we want
375
//to paint, but do redraw the arrow so we don't erase it
376
currLogicalLine++;
377                             drawArrow (g, y - ((logicalLines - currLogicalLine) * charHeight()), currLogicalLine == ln[1]);
378                         }
379                     }
380                     //Iterate all the logicalLines lines
381
for (; currLogicalLine < logicalLines; currLogicalLine++) {
382                         int charpos = currLogicalLine * charsPerLine;
383                         int lenToDraw = Math.min(charsPerLine, length - charpos);
384                         if (lenToDraw <= 0) {
385                             break;
386                         }
387                         drawLogicalLine(seg, currLogicalLine, logicalLines, g, y, lineStart, charpos, selStart, lenToDraw, selEnd);
388                         if (g.getColor() == unselectedLinkFg || g.getColor() == unselectedImportantLinkFg) {
389                             underline(g, seg, charpos, lenToDraw, currLogicalLine, y);
390                         }
391                         y += charHeight();
392                     }
393                     if (y > clip.y + clip.height || i + firstline == lineCount -1) {
394                         break;
395                     }
396                 }
397             } catch (BadLocationException e) {
398                 Exceptions.printStackTrace(e);
399             }
400         }
401     }
402
403     /**
404      * Draw one logical (wrapped) line
405      *
406      * @param seg A Segment object containing the text
407      * @param currLogicalLine Index of the current logical line in the physical line
408      * @param logicalLines Number of logical lines there are
409      * @param g The graphics context
410      * @param y The baseline in the graphics context
411      * @param lineStart The character position at which the line starts
412      * @param charpos The current character position within the segment
413      * @param selStart The character index at which the selected range, if any, starts
414      * @param lenToDraw The number of characters we'll draw before we're outside the clip rectangle
415      * @param selEnd The end of the selected range of text, if any
416      */

417     private void drawLogicalLine(Segment seg, int currLogicalLine, int logicalLines, Graphics g, int y, int lineStart, int charpos, int selStart, int lenToDraw, int selEnd) {
418         if (currLogicalLine != logicalLines-1) {
419             drawArrow (g, y, currLogicalLine == logicalLines-2);
420         }
421         int realPos = lineStart + charpos;
422
423         if (realPos >= selStart && realPos + lenToDraw <= selEnd) {
424             Color c = g.getColor();
425             g.setColor (comp.getSelectionColor());
426             g.fillRect (margin(), y+fontDescent()-charHeight(), lenToDraw * charWidth(), charHeight());
427             g.setColor (c);
428         } else if (realPos <= selStart && realPos + lenToDraw >= selStart) {
429             int selx = margin() + (charWidth() * (selStart - realPos));
430             int selLen = selEnd > realPos + lenToDraw ? ((lenToDraw + realPos) - selStart) * charWidth() :
431                     (selEnd - selStart) * charWidth();
432             Color c = g.getColor();
433             g.setColor (comp.getSelectionColor());
434             g.fillRect (selx, y + fontDescent() - charHeight(), selLen, charHeight());
435             g.setColor (c);
436         } else if (realPos > selStart && realPos + lenToDraw >= selEnd) {
437             //we're drawing the tail of a selection
438
int selLen = (selEnd - realPos) * charWidth();
439             Color c = g.getColor();
440             g.setColor (comp.getSelectionColor());
441             g.fillRect (margin(), y + fontDescent() - charHeight(), selLen, charHeight());
442             g.setColor (c);
443         }
444         g.drawChars(seg.array, charpos, lenToDraw, margin(), y);
445     }
446
447
448     private void underline(Graphics g, Segment seg, int charpos, int lenToDraw, int currLogicalLine, int y) {
449         int underlineStart = margin();
450         int underlineEnd = underlineStart + g.getFontMetrics().charsWidth(seg.array, charpos, lenToDraw);
451         if (currLogicalLine == 0) {
452             //#47263 - start hyperlink underline at first
453
//non-whitespace character
454
for (int k=1; k < lenToDraw; k++) {
455                 if (Character.isWhitespace(seg.array[charpos + k])) {
456                     underlineStart += charWidth();
457                     underlineEnd -= charWidth();
458                 } else {
459                     break;
460                 }
461             }
462         } else {
463             underlineStart = margin();
464         }
465         g.drawLine (underlineStart, y+1, underlineEnd, y+1);
466     }
467
468     /**
469      * Draw the decorations used with wrapped lines.
470      *
471      * @param g A graphics to paint into
472      * @param y The y coordinate of the line as a font baseline position
473      */

474     private void drawArrow (Graphics g, int y, boolean drawHead) {
475         int fontHeight = charHeight();
476         Color c = g.getColor();
477
478         g.setColor (arrowColor());
479
480         int w = getWidth() + 15;
481         y+=2;
482
483
484         int rpos = aa ? 8 : 4;
485         if (aa) {
486             g.drawArc(w - rpos, y - (fontHeight / 2), rpos + 1, fontHeight, 265, 185);
487         } else {
488             g.drawLine (w-rpos, y - (fontHeight / 2), w, y - (fontHeight / 2));
489             g.drawLine (w, y - (fontHeight / 2)+1, w, y + (fontHeight / 2) - 1);
490             g.drawLine (w-rpos, y + (fontHeight / 2), w, y + (fontHeight / 2));
491         }
492         if (aa) {
493             w++;
494         }
495         if (drawHead) {
496             rpos = aa ? 7 : 8;
497             int[] xpoints = new int[] {
498                 w - rpos,
499                 w - rpos + 5,
500                 w - rpos + 5,
501             };
502             int[] ypoints = new int[] {
503                 y + (fontHeight / 2),
504                 y + (fontHeight / 2) - 5,
505                 y + (fontHeight / 2) + 5,
506             };
507             g.fillPolygon(xpoints, ypoints, 3);
508         }
509
510         g.setColor (arrowColor());
511         g.drawLine (1, y - (fontHeight / 2), 5, y - (fontHeight / 2));
512         g.drawLine (1, y - (fontHeight / 2), 1, y + (fontHeight / 2));
513         g.drawLine (1, y + (fontHeight / 2), 5, y + (fontHeight / 2));
514
515         g.setColor (c);
516
517     }
518
519     /**
520      * Get the color used for the line wrap arrow
521      *
522      * @return The arrow color
523      */

524     private static Color arrowColor() {
525         return arrowColor;
526     }
527
528     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
529         Rectangle result = new Rectangle();
530         result.setBounds (0, 0, charWidth(), charHeight());
531         OutputDocument od = odoc();
532         if (od != null) {
533             int line = od.getElementIndex(pos);
534             int start = od.getLineStart(line);
535
536             int column = pos - start;
537
538             int charsPerLine = getCharsPerLine();
539
540             int row = od.getLines().getLogicalLineCountAbove(line, charsPerLine);
541             if (column > charsPerLine) {
542                 row += (column / charsPerLine);
543                 column %= charsPerLine;
544             }
545             result.y = (row * charHeight()) + fontDescent();
546             result.x = margin() + (column * charWidth());
547 // System.err.println(pos + "@" + result.x + "," + result.y + " line " + line + " start " + start + " row " + row + " col " + column);
548
}
549         
550         return result;
551     }
552
553     public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
554         OutputDocument od = odoc();
555         if (od != null) {
556             int ix = (int) x - margin();
557             int iy = (int) y - fontDescent();
558
559             int charsPerLine = getCharsPerLine();
560
561             int physicalLine = (iy / charHeight);
562
563             ln[0] = physicalLine;
564             od.getLines().toLogicalLineIndex(ln, charsPerLine);
565             int logicalLine = ln[0];
566             int wraps = ln[2] - 1;
567
568             int totalLines = od.getElementCount();
569             if (totalLines == 0) {
570                 return 0;
571             }
572             if (logicalLine >= totalLines) {
573                 return od.getLength() - 1;
574             }
575
576             int lineStart = od.getLineStart(logicalLine);
577             int lineLength = od.getLines().length(logicalLine);
578
579             int column = (ix / charWidth());
580             if (column > lineLength-1) {
581                 column = lineLength-1;
582             }
583
584             return wraps > 0 ?
585                 Math.min(od.getLineEnd(logicalLine) - 1, lineStart + (ln[1] * charsPerLine) + column)
586                 : lineStart + column;
587 /* System.err.println ("ViewToModel " + ix + "," + iy + " = " + result + " physical ln " + physicalLine +
588                     " logical ln " + logicalLine + " on wrap line " + ln[1] + " of " + wraps + " charsPerLine " +
589                     charsPerLine + " column " + column + " line length " + lineLength);
590 // System.err.println ("v2m: [" + ix + "," + iy + "] = " + result);
591 */

592         } else {
593             return 0;
594         }
595     }
596
597     /**
598      * Get a color for a given position in the document - this will either be plain,
599      * hyperlink or standard error colors.
600      *
601      * @param start A position in the document
602      * @param d The document, presumably an instance of OutputDocument (though briefly it is not
603      * during an editor kit change)
604      * @param selected Whether or not it is selected
605      * @return The foreground color to paint with
606      */

607     private static Color getColorForLocation (int start, Document d, boolean selected) {
608         OutputDocument od = (OutputDocument) d;
609         int line = od.getElementIndex (start);
610         boolean hyperlink = od.getLines().isHyperlink(line);
611         boolean important = hyperlink ? od.getLines().isImportantHyperlink(line) : false;
612         boolean isErr = od.getLines().isErr(line);
613         return hyperlink ? (important ? (selected ? selectedImportantLinkFg : unselectedImportantLinkFg) :
614                                         (selected ? selectedLinkFg : unselectedLinkFg)) :
615                            (selected ? (isErr ? selectedErr : selectedFg) :
616                                        (isErr ? unselectedErr : unselectedFg));
617     }
618 }
619
Popular Tags