2 Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.text;
41 import java.awt.Color;
42 import java.awt.Component;
44 import java.awt.FontMetrics;
45 import java.awt.Graphics;
46 import java.awt.Rectangle;
47 import java.awt.Shape;
49 import javax.swing.SwingUtilities;
50 import javax.swing.event.DocumentEvent;
51 import javax.swing.event.DocumentEvent.ElementChange;
53 public class PlainView extends View implements TabExpander
56 Color unselectedColor;
59 * The color that is used to draw disabled text fields.
64 * While painting this is the textcomponent's current start index
70 * While painting this is the textcomponent's current end index
77 /** The length of the longest line in the Document **/
78 float maxLineLength = -1;
80 /** The longest line in the Document **/
81 Element longestLine = null;
83 protected FontMetrics metrics;
86 * The instance returned by {@link #getLineBuffer()}.
88 private transient Segment lineBuffer;
91 * The base offset for tab calculations.
100 public PlainView(Element elem)
108 protected void updateMetrics()
110 Component component = getContainer();
111 Font font = component.getFont();
113 if (this.font != font)
116 metrics = component.getFontMetrics(font);
117 tabSize = getTabSize() * metrics.charWidth('m');
124 protected Rectangle lineToRect(Shape a, int line)
126 // Ensure metrics are up-to-date.
129 Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
130 int fontHeight = metrics.getHeight();
131 return new Rectangle(rect.x, rect.y + (line * fontHeight),
132 rect.width, fontHeight);
135 public Shape modelToView(int position, Shape a, Position.Bias b)
136 throws BadLocationException
138 // Ensure metrics are up-to-date.
141 Document document = getDocument();
143 // Get rectangle of the line containing position.
144 int lineIndex = getElement().getElementIndex(position);
145 Rectangle rect = lineToRect(a, lineIndex);
148 // Get the rectangle for position.
149 Element line = getElement().getElement(lineIndex);
150 int lineStart = line.getStartOffset();
151 Segment segment = getLineBuffer();
152 document.getText(lineStart, position - lineStart, segment);
153 int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
156 // Calc the real rectangle.
159 rect.height = metrics.getHeight();
165 * Draws a line of text. The X and Y coordinates specify the start of
166 * the <em>baseline</em> of the line.
168 * @param lineIndex the index of the line
169 * @param g the graphics to use for drawing the text
170 * @param x the X coordinate of the baseline
171 * @param y the Y coordinate of the baseline
173 protected void drawLine(int lineIndex, Graphics g, int x, int y)
177 Element line = getElement().getElement(lineIndex);
178 int startOffset = line.getStartOffset();
179 int endOffset = line.getEndOffset() - 1;
181 if (selectionStart <= startOffset)
182 // Selection starts before the line ...
183 if (selectionEnd <= startOffset)
185 // end ends before the line: Draw completely unselected text.
186 drawUnselectedText(g, x, y, startOffset, endOffset);
188 else if (selectionEnd <= endOffset)
190 // and ends within the line: First part is selected,
192 x = drawSelectedText(g, x, y, startOffset, selectionEnd);
193 drawUnselectedText(g, x, y, selectionEnd, endOffset);
196 // and ends behind the line: Draw completely selected text.
197 drawSelectedText(g, x, y, startOffset, endOffset);
198 else if (selectionStart < endOffset)
199 // Selection starts within the line ..
200 if (selectionEnd < endOffset)
202 // and ends within it: First part unselected, second part
203 // selected, third part unselected.
204 x = drawUnselectedText(g, x, y, startOffset, selectionStart);
205 x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
206 drawUnselectedText(g, x, y, selectionEnd, endOffset);
210 // and ends behind the line: First part unselected, second
212 x = drawUnselectedText(g, x, y, startOffset, selectionStart);
213 drawSelectedText(g, x, y, selectionStart, endOffset);
216 // Selection is behind this line: Draw completely unselected text.
217 drawUnselectedText(g, x, y, startOffset, endOffset);
219 catch (BadLocationException e)
221 AssertionError ae = new AssertionError("Unexpected bad location");
227 protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
228 throws BadLocationException
230 g.setColor(selectedColor);
231 Segment segment = getLineBuffer();
232 getDocument().getText(p0, p1 - p0, segment);
233 return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
237 * Draws a chunk of unselected text.
239 * @param g the graphics to use for drawing the text
240 * @param x the X coordinate of the baseline
241 * @param y the Y coordinate of the baseline
242 * @param p0 the start position in the text model
243 * @param p1 the end position in the text model
245 * @return the X location of the end of the range
247 * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
250 protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
251 throws BadLocationException
253 JTextComponent textComponent = (JTextComponent) getContainer();
254 if (textComponent.isEnabled())
255 g.setColor(unselectedColor);
257 g.setColor(disabledColor);
259 Segment segment = getLineBuffer();
260 getDocument().getText(p0, p1 - p0, segment);
261 return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
264 public void paint(Graphics g, Shape s)
266 // Ensure metrics are up-to-date.
269 JTextComponent textComponent = (JTextComponent) getContainer();
271 selectedColor = textComponent.getSelectedTextColor();
272 unselectedColor = textComponent.getForeground();
273 disabledColor = textComponent.getDisabledTextColor();
274 selectionStart = textComponent.getSelectionStart();
275 selectionEnd = textComponent.getSelectionEnd();
277 Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
280 // FIXME: Text may be scrolled.
281 Document document = textComponent.getDocument();
282 Element root = getElement();
283 int height = metrics.getHeight();
285 // For layered highlighters we need to paint the layered highlights
286 // before painting any text.
287 LayeredHighlighter hl = null;
288 Highlighter h = textComponent.getHighlighter();
289 if (h instanceof LayeredHighlighter)
290 hl = (LayeredHighlighter) h;
292 int count = root.getElementCount();
294 // Determine first and last line inside the clip.
295 Rectangle clip = g.getClipBounds();
296 SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
298 int line0 = (clip.y - rect.y) / height;
299 line0 = Math.max(0, Math.min(line0, count - 1));
300 int line1 = (clip.y + clip.height - rect.y) / height;
301 line1 = Math.max(0, Math.min(line1, count - 1));
302 int y = rect.y + metrics.getAscent() + height * line0;
303 for (int i = line0; i <= line1; i++)
307 Element lineEl = root.getElement(i);
308 // Exclude the trailing newline from beeing highlighted.
310 hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
311 lineEl.getEndOffset(), s, textComponent,
314 hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
315 lineEl.getEndOffset() - 1, s,
316 textComponent, this);
318 drawLine(i, g, rect.x, y);
324 * Returns the tab size of a tab. Checks the Document's
325 * properties for PlainDocument.tabSizeAttribute and returns it if it is
326 * defined, otherwise returns 8.
328 * @return the tab size.
330 protected int getTabSize()
332 Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
335 return ((Integer)tabSize).intValue();
339 * Returns the next tab stop position after a given reference position.
341 * This implementation ignores the <code>tabStop</code> argument.
343 * @param x the current x position in pixels
344 * @param tabStop the position within the text stream that the tab occured at
346 public float nextTabStop(float x, int tabStop)
351 int numTabs = (((int) x) - tabBase) / tabSize;
352 next = tabBase + (numTabs + 1) * tabSize;
358 * Returns the length of the longest line, used for getting the span
359 * @return the length of the longest line
361 float determineMaxLineLength()
363 // if the longest line is cached, return the cached value
364 if (maxLineLength != -1)
365 return maxLineLength;
367 // otherwise we have to go through all the lines and find it
368 Element el = getElement();
369 Segment seg = getLineBuffer();
371 for (int i = 0; i < el.getElementCount(); i++)
373 Element child = el.getElement(i);
374 int start = child.getStartOffset();
375 int end = child.getEndOffset() - 1;
378 el.getDocument().getText(start, end - start, seg);
380 catch (BadLocationException ex)
382 AssertionError ae = new AssertionError("Unexpected bad location");
387 if (seg == null || seg.array == null || seg.count == 0)
390 int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
397 maxLineLength = span;
398 return maxLineLength;
401 public float getPreferredSpan(int axis)
403 if (axis != X_AXIS && axis != Y_AXIS)
404 throw new IllegalArgumentException();
406 // make sure we have the metrics
409 Element el = getElement();
415 span = determineMaxLineLength();
419 span = metrics.getHeight() * el.getElementCount();
427 * Maps coordinates from the <code>View</code>'s space into a position
428 * in the document model.
430 * @param x the x coordinate in the view space
431 * @param y the y coordinate in the view space
432 * @param a the allocation of this <code>View</code>
433 * @param b the bias to use
435 * @return the position in the document that corresponds to the screen
436 * coordinates <code>x, y</code>
438 public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
440 Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
445 // Above our area vertically. Return start offset.
446 pos = getStartOffset();
447 else if ((int) y > rec.y + rec.height)
448 // Below our area vertically. Return end offset.
449 pos = getEndOffset() - 1;
452 // Inside the allocation vertically. Determine line and X offset.
453 Document doc = getDocument();
454 Element root = doc.getDefaultRootElement();
455 int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
456 if (line >= root.getElementCount())
457 pos = getEndOffset() - 1;
460 Element lineEl = root.getElement(line);
462 // To the left of the allocation area.
463 pos = lineEl.getStartOffset();
464 else if (x > rec.x + rec.width)
465 // To the right of the allocation area.
466 pos = lineEl.getEndOffset() - 1;
471 int p0 = lineEl.getStartOffset();
472 int p1 = lineEl.getEndOffset();
473 Segment s = new Segment();
474 doc.getText(p0, p1 - p0, s);
476 pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
480 catch (BadLocationException ex)
482 // Should not happen.
489 // Bias is always forward.
490 b[0] = Position.Bias.Forward;
495 * Since insertUpdate and removeUpdate each deal with children
496 * Elements being both added and removed, they both have to perform
497 * the same checks. So they both simply call this method.
498 * @param changes the DocumentEvent for the changes to the Document.
499 * @param a the allocation of the View.
500 * @param f the ViewFactory to use for rebuilding.
502 protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
504 // This happens during initialization.
508 preferenceChanged(null, true, true);
512 Element element = getElement();
514 // Find longest line if it hasn't been initialized yet.
515 if (longestLine == null)
516 findLongestLine(0, element.getElementCount() - 1);
518 ElementChange change = changes.getChange(element);
519 if (changes.getType() == DocumentEvent.EventType.INSERT)
521 // Handles character/line insertion.
523 // Determine if lines have been added. In this case we repaint
525 boolean linesAdded = true;
529 // Determine the start line.
532 start = change.getIndex();
534 start = element.getElementIndex(changes.getOffset());
536 // Determine the length of the updated region.
539 length = change.getChildrenAdded().length - 1;
541 // Update the longest line and length.
542 int oldMaxLength = (int) maxLineLength;
543 if (longestLine.getEndOffset() < changes.getOffset()
544 || longestLine.getStartOffset() > changes.getOffset()
545 + changes.getLength())
547 findLongestLine(start, start + length);
551 findLongestLine(0, element.getElementCount() - 1);
554 // Trigger a preference change so that the layout gets updated
556 preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
558 // Damage the updated line range.
561 endLine = element.getElementCount() - 1;
562 damageLineRange(start, endLine, a, getContainer());
567 // Handles character/lines removals.
569 // Update the longest line and length and trigger preference changed.
570 int oldMaxLength = (int) maxLineLength;
573 // Line(s) have been removed.
574 findLongestLine(0, element.getElementCount() - 1);
575 preferenceChanged(null, maxLineLength != oldMaxLength, true);
579 // No line has been removed.
580 int lineNo = getElement().getElementIndex(changes.getOffset());
581 Element line = getElement().getElement(lineNo);
582 if (longestLine == line)
584 findLongestLine(0, element.getElementCount() - 1);
585 preferenceChanged(null, maxLineLength != oldMaxLength, false);
587 damageLineRange(lineNo, lineNo, a, getContainer());
593 * This method is called when something is inserted into the Document
594 * that this View is displaying.
596 * @param changes the DocumentEvent for the changes.
597 * @param a the allocation of the View
598 * @param f the ViewFactory used to rebuild
600 public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
602 updateDamage(changes, a, f);
606 * This method is called when something is removed from the Document
607 * that this View is displaying.
609 * @param changes the DocumentEvent for the changes.
610 * @param a the allocation of the View
611 * @param f the ViewFactory used to rebuild
613 public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
615 updateDamage(changes, a, f);
619 * This method is called when attributes were changed in the
620 * Document in a location that this view is responsible for.
622 public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
624 updateDamage(changes, a, f);
628 * Repaint the given line range. This is called from insertUpdate,
629 * changedUpdate, and removeUpdate when no new lines were added
630 * and no lines were removed, to repaint the line that was
633 * @param line0 the start of the range
634 * @param line1 the end of the range
635 * @param a the rendering region of the host
636 * @param host the Component that uses this View (used to call repaint
641 protected void damageLineRange (int line0, int line1, Shape a, Component host)
646 Rectangle rec0 = lineToRect(a, line0);
647 Rectangle rec1 = lineToRect(a, line1);
649 if (rec0 == null || rec1 == null)
650 // something went wrong, repaint the entire host to be safe
654 Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
657 host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
663 * Provides a {@link Segment} object, that can be used to fetch text from
666 * @returna {@link Segment} object, that can be used to fetch text from
669 protected final Segment getLineBuffer()
671 if (lineBuffer == null)
672 lineBuffer = new Segment();
677 * Finds and updates the longest line in the view inside an interval of
680 * @param start the start of the search interval
681 * @param end the end of the search interval
683 private void findLongestLine(int start, int end)
685 for (int i = start; i <= end; i++)
687 int w = getLineLength(i);
688 if (w > maxLineLength)
691 longestLine = getElement().getElement(i);
697 * Determines the length of the specified line.
699 * @param line the number of the line
701 * @return the length of the line in pixels
703 private int getLineLength(int line)
705 Element lineEl = getElement().getElement(line);
706 Segment buffer = getLineBuffer();
709 Document doc = getDocument();
710 doc.getText(lineEl.getStartOffset(),
711 lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
714 catch (BadLocationException ex)
716 AssertionError err = new AssertionError("Unexpected bad location");
721 return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
722 lineEl.getStartOffset());