Merge from mainline.
[official-gcc.git] / libjava / classpath / javax / swing / text / PlainView.java
blob18818c0bad34bab32c2163fa7023a0174629f6e4
1 /* PlainView.java --
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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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;
43 import java.awt.Font;
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
55 Color selectedColor;
56 Color unselectedColor;
58 /**
59 * The color that is used to draw disabled text fields.
61 Color disabledColor;
63 /**
64 * While painting this is the textcomponent's current start index
65 * of the selection.
67 int selectionStart;
69 /**
70 * While painting this is the textcomponent's current end index
71 * of the selection.
73 int selectionEnd;
75 Font font;
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;
85 /**
86 * The instance returned by {@link #getLineBuffer()}.
88 private transient Segment lineBuffer;
90 public PlainView(Element elem)
92 super(elem);
95 /**
96 * @since 1.4
98 protected void updateMetrics()
100 Component component = getContainer();
101 Font font = component.getFont();
103 if (this.font != font)
105 this.font = font;
106 metrics = component.getFontMetrics(font);
111 * @since 1.4
113 protected Rectangle lineToRect(Shape a, int line)
115 // Ensure metrics are up-to-date.
116 updateMetrics();
118 Rectangle rect = a.getBounds();
119 int fontHeight = metrics.getHeight();
120 return new Rectangle(rect.x, rect.y + (line * fontHeight),
121 rect.width, fontHeight);
124 public Shape modelToView(int position, Shape a, Position.Bias b)
125 throws BadLocationException
127 // Ensure metrics are up-to-date.
128 updateMetrics();
130 Document document = getDocument();
132 // Get rectangle of the line containing position.
133 int lineIndex = getElement().getElementIndex(position);
134 Rectangle rect = lineToRect(a, lineIndex);
136 // Get the rectangle for position.
137 Element line = getElement().getElement(lineIndex);
138 int lineStart = line.getStartOffset();
139 Segment segment = getLineBuffer();
140 document.getText(lineStart, position - lineStart, segment);
141 int xoffset = Utilities.getTabbedTextWidth(segment, metrics, rect.x,
142 this, lineStart);
144 // Calc the real rectangle.
145 rect.x += xoffset;
146 rect.width = 1;
147 rect.height = metrics.getHeight();
149 return rect;
153 * Draws a line of text. The X and Y coordinates specify the start of
154 * the <em>baseline</em> of the line.
156 * @param lineIndex the index of the line
157 * @param g the graphics to use for drawing the text
158 * @param x the X coordinate of the baseline
159 * @param y the Y coordinate of the baseline
161 protected void drawLine(int lineIndex, Graphics g, int x, int y)
165 Element line = getElement().getElement(lineIndex);
166 int startOffset = line.getStartOffset();
167 int endOffset = line.getEndOffset() - 1;
169 if (selectionStart <= startOffset)
170 // Selection starts before the line ...
171 if (selectionEnd <= startOffset)
173 // end ends before the line: Draw completely unselected text.
174 drawUnselectedText(g, x, y, startOffset, endOffset);
176 else if (selectionEnd <= endOffset)
178 // and ends within the line: First part is selected,
179 // second is not.
180 x = drawSelectedText(g, x, y, startOffset, selectionEnd);
181 drawUnselectedText(g, x, y, selectionEnd, endOffset);
183 else
184 // and ends behind the line: Draw completely selected text.
185 drawSelectedText(g, x, y, startOffset, endOffset);
186 else if (selectionStart < endOffset)
187 // Selection starts within the line ..
188 if (selectionEnd < endOffset)
190 // and ends within it: First part unselected, second part
191 // selected, third part unselected.
192 x = drawUnselectedText(g, x, y, startOffset, selectionStart);
193 x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
194 drawUnselectedText(g, x, y, selectionEnd, endOffset);
196 else
198 // and ends behind the line: First part unselected, second
199 // part selected.
200 x = drawUnselectedText(g, x, y, startOffset, selectionStart);
201 drawSelectedText(g, x, y, selectionStart, endOffset);
203 else
204 // Selection is behind this line: Draw completely unselected text.
205 drawUnselectedText(g, x, y, startOffset, endOffset);
207 catch (BadLocationException e)
209 AssertionError ae = new AssertionError("Unexpected bad location");
210 ae.initCause(e);
211 throw ae;
215 protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
216 throws BadLocationException
218 g.setColor(selectedColor);
219 Segment segment = getLineBuffer();
220 getDocument().getText(p0, p1 - p0, segment);
221 return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
225 * Draws a chunk of unselected text.
227 * @param g the graphics to use for drawing the text
228 * @param x the X coordinate of the baseline
229 * @param y the Y coordinate of the baseline
230 * @param p0 the start position in the text model
231 * @param p1 the end position in the text model
233 * @return the X location of the end of the range
235 * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
236 * invalid
238 protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
239 throws BadLocationException
241 JTextComponent textComponent = (JTextComponent) getContainer();
242 if (textComponent.isEnabled())
243 g.setColor(unselectedColor);
244 else
245 g.setColor(disabledColor);
247 Segment segment = getLineBuffer();
248 getDocument().getText(p0, p1 - p0, segment);
249 return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
252 public void paint(Graphics g, Shape s)
254 // Ensure metrics are up-to-date.
255 updateMetrics();
257 JTextComponent textComponent = (JTextComponent) getContainer();
259 selectedColor = textComponent.getSelectedTextColor();
260 unselectedColor = textComponent.getForeground();
261 disabledColor = textComponent.getDisabledTextColor();
262 selectionStart = textComponent.getSelectionStart();
263 selectionEnd = textComponent.getSelectionEnd();
265 Rectangle rect = s.getBounds();
267 // FIXME: Text may be scrolled.
268 Document document = textComponent.getDocument();
269 Element root = document.getDefaultRootElement();
270 int y = rect.y + metrics.getAscent();
271 int height = metrics.getHeight();
273 int count = root.getElementCount();
274 for (int i = 0; i < count; i++)
276 drawLine(i, g, rect.x, y);
277 y += height;
282 * Returns the tab size of a tab. Checks the Document's
283 * properties for PlainDocument.tabSizeAttribute and returns it if it is
284 * defined, otherwise returns 8.
286 * @return the tab size.
288 protected int getTabSize()
290 Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
291 if (tabSize == null)
292 return 8;
293 return ((Integer)tabSize).intValue();
297 * Returns the next tab stop position after a given reference position.
299 * This implementation ignores the <code>tabStop</code> argument.
301 * @param x the current x position in pixels
302 * @param tabStop the position within the text stream that the tab occured at
304 public float nextTabStop(float x, int tabStop)
306 float tabSizePixels = getTabSize() * metrics.charWidth('m');
307 return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
311 * Returns the length of the longest line, used for getting the span
312 * @return the length of the longest line
314 float determineMaxLineLength()
316 // if the longest line is cached, return the cached value
317 if (maxLineLength != -1)
318 return maxLineLength;
320 // otherwise we have to go through all the lines and find it
321 Element el = getElement();
322 Segment seg = getLineBuffer();
323 float span = 0;
324 for (int i = 0; i < el.getElementCount(); i++)
326 Element child = el.getElement(i);
327 int start = child.getStartOffset();
328 int end = child.getEndOffset() - 1;
331 el.getDocument().getText(start, end - start, seg);
333 catch (BadLocationException ex)
335 AssertionError ae = new AssertionError("Unexpected bad location");
336 ae.initCause(ex);
337 throw ae;
340 if (seg == null || seg.array == null || seg.count == 0)
341 continue;
343 int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
344 if (width > span)
346 longestLine = child;
347 span = width;
350 maxLineLength = span;
351 return maxLineLength;
354 public float getPreferredSpan(int axis)
356 if (axis != X_AXIS && axis != Y_AXIS)
357 throw new IllegalArgumentException();
359 // make sure we have the metrics
360 updateMetrics();
362 Element el = getElement();
363 float span;
365 switch (axis)
367 case X_AXIS:
368 span = determineMaxLineLength();
369 break;
370 case Y_AXIS:
371 default:
372 span = metrics.getHeight() * el.getElementCount();
373 break;
376 return span;
380 * Maps coordinates from the <code>View</code>'s space into a position
381 * in the document model.
383 * @param x the x coordinate in the view space
384 * @param y the y coordinate in the view space
385 * @param a the allocation of this <code>View</code>
386 * @param b the bias to use
388 * @return the position in the document that corresponds to the screen
389 * coordinates <code>x, y</code>
391 public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
393 Rectangle rec = a.getBounds();
394 Document doc = getDocument();
395 Element root = doc.getDefaultRootElement();
397 // PlainView doesn't support line-wrapping so we can find out which
398 // Element was clicked on just by the y-position.
399 // Since the coordinates may be outside of the coordinate space
400 // of the allocation area (e.g. user dragged mouse outside
401 // the component) we have to limit the values.
402 // This has the nice effect that the user can drag the
403 // mouse above or below the component and it will still
404 // react to the x values (e.g. when selecting).
405 int lineClicked
406 = Math.min(Math.max((int) (y - rec.y) / metrics.getHeight(), 0),
407 root.getElementCount() - 1);
409 Element line = root.getElement(lineClicked);
411 Segment s = getLineBuffer();
412 int start = line.getStartOffset();
413 // We don't want the \n at the end of the line.
414 int end = line.getEndOffset() - 1;
417 doc.getText(start, end - start, s);
419 catch (BadLocationException ble)
421 AssertionError ae = new AssertionError("Unexpected bad location");
422 ae.initCause(ble);
423 throw ae;
426 int pos = Utilities.getTabbedTextOffset(s, metrics, rec.x, (int)x, this, start);
427 return Math.max (0, pos);
431 * Since insertUpdate and removeUpdate each deal with children
432 * Elements being both added and removed, they both have to perform
433 * the same checks. So they both simply call this method.
434 * @param changes the DocumentEvent for the changes to the Document.
435 * @param a the allocation of the View.
436 * @param f the ViewFactory to use for rebuilding.
438 protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
440 // Return early and do no updates if the allocation area is null
441 // (like the RI).
442 if (a == null)
443 return;
445 float oldMaxLineLength = maxLineLength;
446 Rectangle alloc = a.getBounds();
447 Element el = getElement();
448 ElementChange ec = changes.getChange(el);
450 // If ec is null then no lines were added or removed, just
451 // repaint the changed line
452 if (ec == null)
454 int line = el.getElementIndex(changes.getOffset());
456 // If characters have been removed from the current longest line
457 // we have to find out which one is the longest now otherwise
458 // the preferred x-axis span will not shrink.
459 if (changes.getType() == DocumentEvent.EventType.REMOVE
460 && el.getElement(line) == longestLine)
462 maxLineLength = -1;
463 if (determineMaxLineLength() != alloc.width)
464 preferenceChanged(this, true, false);
467 damageLineRange(line, line, a, getContainer());
468 return;
471 Element[] removed = ec.getChildrenRemoved();
472 Element[] newElements = ec.getChildrenAdded();
474 // If no Elements were added or removed, we just want to repaint
475 // the area containing the line that was modified
476 if (removed == null && newElements == null)
478 int line = getElement().getElementIndex(changes.getOffset());
480 damageLineRange(line, line, a, getContainer());
481 return;
484 // Check to see if we removed the longest line, if so we have to
485 // search through all lines and find the longest one again.
486 if (removed != null)
488 for (int i = 0; i < removed.length; i++)
489 if (removed[i].equals(longestLine))
491 // reset maxLineLength and search through all lines for longest one
492 maxLineLength = -1;
493 if (determineMaxLineLength() != alloc.width)
494 preferenceChanged(this, true, removed.length != newElements.length);
496 ((JTextComponent)getContainer()).repaint();
498 return;
502 // If we've reached here, that means we haven't removed the longest line
503 if (newElements == null)
505 // No lines were added, just repaint the container and exit
506 ((JTextComponent)getContainer()).repaint();
508 return;
511 // Make sure we have the metrics
512 updateMetrics();
514 // If we've reached here, that means we haven't removed the longest line
515 // and we have added at least one line, so we have to check if added lines
516 // are longer than the previous longest line
517 Segment seg = getLineBuffer();
518 float longestNewLength = 0;
519 Element longestNewLine = null;
521 // Loop through the added lines to check their length
522 for (int i = 0; i < newElements.length; i++)
524 Element child = newElements[i];
525 int start = child.getStartOffset();
526 int end = child.getEndOffset() - 1;
529 el.getDocument().getText(start, end - start, seg);
531 catch (BadLocationException ex)
533 AssertionError ae = new AssertionError("Unexpected bad location");
534 ae.initCause(ex);
535 throw ae;
538 if (seg == null || seg.array == null || seg.count == 0)
539 continue;
541 int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
542 if (width > longestNewLength)
544 longestNewLine = child;
545 longestNewLength = width;
549 // Check if the longest of the new lines is longer than our previous
550 // longest line, and if so update our values
551 if (longestNewLength > maxLineLength)
553 maxLineLength = longestNewLength;
554 longestLine = longestNewLine;
557 // Report any changes to the preferred sizes of the view
558 // which may cause the underlying component to be revalidated.
559 boolean widthChanged = oldMaxLineLength != maxLineLength;
560 boolean heightChanged = removed.length != newElements.length;
561 if (widthChanged || heightChanged)
562 preferenceChanged(this, widthChanged, heightChanged);
564 // Repaint the container
565 ((JTextComponent)getContainer()).repaint();
569 * This method is called when something is inserted into the Document
570 * that this View is displaying.
572 * @param changes the DocumentEvent for the changes.
573 * @param a the allocation of the View
574 * @param f the ViewFactory used to rebuild
576 public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
578 updateDamage(changes, a, f);
582 * This method is called when something is removed from the Document
583 * that this View is displaying.
585 * @param changes the DocumentEvent for the changes.
586 * @param a the allocation of the View
587 * @param f the ViewFactory used to rebuild
589 public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
591 updateDamage(changes, a, f);
595 * This method is called when attributes were changed in the
596 * Document in a location that this view is responsible for.
598 public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
600 updateDamage(changes, a, f);
604 * Repaint the given line range. This is called from insertUpdate,
605 * changedUpdate, and removeUpdate when no new lines were added
606 * and no lines were removed, to repaint the line that was
607 * modified.
609 * @param line0 the start of the range
610 * @param line1 the end of the range
611 * @param a the rendering region of the host
612 * @param host the Component that uses this View (used to call repaint
613 * on that Component)
615 * @since 1.4
617 protected void damageLineRange (int line0, int line1, Shape a, Component host)
619 if (a == null)
620 return;
622 Rectangle rec0 = lineToRect(a, line0);
623 Rectangle rec1 = lineToRect(a, line1);
625 if (rec0 == null || rec1 == null)
626 // something went wrong, repaint the entire host to be safe
627 host.repaint();
628 else
630 Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
631 rec0.width,
632 rec0.height, rec1);
633 host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
634 repaintRec.height);
639 * Provides a {@link Segment} object, that can be used to fetch text from
640 * the document.
642 * @returna {@link Segment} object, that can be used to fetch text from
643 * the document
645 protected final Segment getLineBuffer()
647 if (lineBuffer == null)
648 lineBuffer = new Segment();
649 return lineBuffer;