Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / text / WrappedPlainView.java
blobbaba343c5bf789f1bcf638ec0b067ac620af8585
1 /* WrappedPlainView.java --
2 Copyright (C) 2005 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.Container;
43 import java.awt.FontMetrics;
44 import java.awt.Graphics;
45 import java.awt.Rectangle;
46 import java.awt.Shape;
48 import javax.swing.SwingConstants;
49 import javax.swing.event.DocumentEvent;
50 import javax.swing.text.Position.Bias;
52 /**
53 * @author Anthony Balkissoon abalkiss at redhat dot com
56 public class WrappedPlainView extends BoxView implements TabExpander
58 /** The color for selected text **/
59 Color selectedColor;
61 /** The color for unselected text **/
62 Color unselectedColor;
64 /** The color for disabled components **/
65 Color disabledColor;
67 /** Stores the font metrics **/
68 protected FontMetrics metrics;
70 /** Whether or not to wrap on word boundaries **/
71 boolean wordWrap;
73 /** A ViewFactory that creates WrappedLines **/
74 ViewFactory viewFactory = new WrappedLineCreator();
76 /** The start of the selected text **/
77 int selectionStart;
79 /** The end of the selected text **/
80 int selectionEnd;
82 /**
83 * The instance returned by {@link #getLineBuffer()}.
85 private transient Segment lineBuffer;
87 public WrappedPlainView (Element elem)
89 this (elem, false);
92 public WrappedPlainView (Element elem, boolean wordWrap)
94 super (elem, Y_AXIS);
95 this.wordWrap = wordWrap;
98 /**
99 * Provides access to the Segment used for retrievals from the Document.
100 * @return the Segment.
102 protected final Segment getLineBuffer()
104 if (lineBuffer == null)
105 lineBuffer = new Segment();
106 return lineBuffer;
110 * Returns the next tab stop position after a given reference position.
112 * This implementation ignores the <code>tabStop</code> argument.
114 * @param x the current x position in pixels
115 * @param tabStop the position within the text stream that the tab occured at
117 public float nextTabStop(float x, int tabStop)
119 JTextComponent host = (JTextComponent)getContainer();
120 float tabSizePixels = getTabSize()
121 * host.getFontMetrics(host.getFont()).charWidth('m');
122 return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels;
126 * Returns the tab size for the Document based on
127 * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
128 * not defined
130 * @return the tab size.
132 protected int getTabSize()
134 Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
135 if (tabSize == null)
136 return 8;
137 return ((Integer)tabSize).intValue();
141 * Draws a line of text, suppressing white space at the end and expanding
142 * tabs. Calls drawSelectedText and drawUnselectedText.
143 * @param p0 starting document position to use
144 * @param p1 ending document position to use
145 * @param g graphics context
146 * @param x starting x position
147 * @param y starting y position
149 protected void drawLine(int p0, int p1, Graphics g, int x, int y)
153 // We have to draw both selected and unselected text. There are
154 // several cases:
155 // - entire range is unselected
156 // - entire range is selected
157 // - start of range is selected, end of range is unselected
158 // - start of range is unselected, end of range is selected
159 // - middle of range is selected, start and end of range is unselected
161 // entire range unselected:
162 if ((selectionStart == selectionEnd) ||
163 (p0 > selectionEnd || p1 < selectionStart))
164 drawUnselectedText(g, x, y, p0, p1);
166 // entire range selected
167 else if (p0 >= selectionStart && p1 <= selectionEnd)
168 drawSelectedText(g, x, y, p0, p1);
170 // start of range selected, end of range unselected
171 else if (p0 >= selectionStart)
173 x = drawSelectedText(g, x, y, p0, selectionEnd);
174 drawUnselectedText(g, x, y, selectionEnd, p1);
177 // start of range unselected, end of range selected
178 else if (selectionStart > p0 && selectionEnd > p1)
180 x = drawUnselectedText(g, x, y, p0, selectionStart);
181 drawSelectedText(g, x, y, selectionStart, p1);
184 // middle of range selected
185 else if (selectionStart > p0)
187 x = drawUnselectedText(g, x, y, p0, selectionStart);
188 x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
189 drawUnselectedText(g, x, y, selectionEnd, p1);
192 catch (BadLocationException ble)
194 // shouldn't happen
199 * Renders the range of text as selected text. Just paints the text
200 * in the color specified by the host component. Assumes the highlighter
201 * will render the selected background.
202 * @param g the graphics context
203 * @param x the starting X coordinate
204 * @param y the starting Y coordinate
205 * @param p0 the starting model location
206 * @param p1 the ending model location
207 * @return the X coordinate of the end of the text
208 * @throws BadLocationException if the given range is invalid
210 protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
211 throws BadLocationException
213 g.setColor(selectedColor);
214 Segment segment = getLineBuffer();
215 getDocument().getText(p0, p1 - p0, segment);
216 return Utilities.drawTabbedText(segment, x, y, g, this, p0);
220 * Renders the range of text as normal unhighlighted text.
221 * @param g the graphics context
222 * @param x the starting X coordinate
223 * @param y the starting Y coordinate
224 * @param p0 the starting model location
225 * @param p1 the end model location
226 * @return the X location of the end off the range
227 * @throws BadLocationException if the range given is invalid
229 protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
230 throws BadLocationException
232 JTextComponent textComponent = (JTextComponent) getContainer();
233 if (textComponent.isEnabled())
234 g.setColor(unselectedColor);
235 else
236 g.setColor(disabledColor);
238 Segment segment = getLineBuffer();
239 getDocument().getText(p0, p1 - p0, segment);
240 return Utilities.drawTabbedText(segment, x, y, g, this, p0);
244 * Loads the children to initiate the view. Called by setParent.
245 * Creates a WrappedLine for each child Element.
247 protected void loadChildren (ViewFactory f)
249 Element root = getElement();
250 int numChildren = root.getElementCount();
251 if (numChildren == 0)
252 return;
254 View[] children = new View[numChildren];
255 for (int i = 0; i < numChildren; i++)
256 children[i] = new WrappedLine(root.getElement(i));
257 replace(0, 0, children);
261 * Calculates the break position for the text between model positions
262 * p0 and p1. Will break on word boundaries or character boundaries
263 * depending on the break argument given in construction of this
264 * WrappedPlainView. Used by the nested WrappedLine class to determine
265 * when to start the next logical line.
266 * @param p0 the start model position
267 * @param p1 the end model position
268 * @return the model position at which to break the text
270 protected int calculateBreakPosition(int p0, int p1)
272 Container c = getContainer();
273 Rectangle alloc = c.isValid() ? c.getBounds()
274 : new Rectangle(c.getPreferredSize());
275 updateMetrics();
278 getDocument().getText(p0, p1 - p0, getLineBuffer());
280 catch (BadLocationException ble)
282 // this shouldn't happen
284 // FIXME: Should we account for the insets of the container?
285 if (wordWrap)
286 return p0
287 + Utilities.getBreakLocation(lineBuffer, metrics, alloc.x,
288 alloc.x + alloc.width, this, 0);
289 else
291 return p0
292 + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x,
293 alloc.x + alloc.width, this, 0);
297 void updateMetrics()
299 Container component = getContainer();
300 metrics = component.getFontMetrics(component.getFont());
304 * Determines the preferred span along the given axis. Implemented to
305 * cache the font metrics and then call the super classes method.
307 public float getPreferredSpan (int axis)
309 updateMetrics();
310 return super.getPreferredSpan(axis);
314 * Determines the minimum span along the given axis. Implemented to
315 * cache the font metrics and then call the super classes method.
317 public float getMinimumSpan (int axis)
319 updateMetrics();
320 return super.getMinimumSpan(axis);
324 * Determines the maximum span along the given axis. Implemented to
325 * cache the font metrics and then call the super classes method.
327 public float getMaximumSpan (int axis)
329 updateMetrics();
330 return super.getMaximumSpan(axis);
334 * Called when something was inserted. Overridden so that
335 * the view factory creates WrappedLine views.
337 public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
339 super.insertUpdate(e, a, viewFactory);
340 // FIXME: could improve performance by repainting only the necessary area
341 getContainer().repaint();
345 * Called when something is removed. Overridden so that
346 * the view factory creates WrappedLine views.
348 public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
350 super.removeUpdate(e, a, viewFactory);
351 // FIXME: could improve performance by repainting only the necessary area
352 getContainer().repaint();
356 * Called when the portion of the Document that this View is responsible
357 * for changes. Overridden so that the view factory creates
358 * WrappedLine views.
360 public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
362 super.changedUpdate(e, a, viewFactory);
363 // FIXME: could improve performance by repainting only the necessary area
364 getContainer().repaint();
367 class WrappedLineCreator implements ViewFactory
369 // Creates a new WrappedLine
370 public View create(Element elem)
372 return new WrappedLine(elem);
377 * Renders the <code>Element</code> that is associated with this
378 * <code>View</code>. Caches the metrics and then calls
379 * super.paint to paint all the child views.
381 * @param g the <code>Graphics</code> context to render to
382 * @param a the allocated region for the <code>Element</code>
384 public void paint(Graphics g, Shape a)
386 JTextComponent comp = (JTextComponent)getContainer();
387 selectionStart = comp.getSelectionStart();
388 selectionEnd = comp.getSelectionEnd();
389 updateMetrics();
390 super.paint(g, a);
394 * Sets the size of the View. Implemented to update the metrics
395 * and then call super method.
397 public void setSize (float width, float height)
399 updateMetrics();
400 if (width != getWidth())
401 preferenceChanged(null, true, true);
402 super.setSize(width, height);
405 class WrappedLine extends View
407 /** Used to cache the number of lines for this View **/
408 int numLines;
410 public WrappedLine(Element elem)
412 super(elem);
413 determineNumLines();
417 * Renders this (possibly wrapped) line using the given Graphics object
418 * and on the given rendering surface.
420 public void paint(Graphics g, Shape s)
422 // Ensure metrics are up-to-date.
423 updateMetrics();
424 JTextComponent textComponent = (JTextComponent) getContainer();
426 g.setFont(textComponent.getFont());
427 selectedColor = textComponent.getSelectedTextColor();
428 unselectedColor = textComponent.getForeground();
429 disabledColor = textComponent.getDisabledTextColor();
431 // FIXME: this is a hack, for some reason textComponent.getSelectedColor
432 // was returning black, which is not visible against a black background
433 selectedColor = Color.WHITE;
435 Rectangle rect = s.getBounds();
436 int lineHeight = metrics.getHeight();
438 int end = getEndOffset();
439 int currStart = getStartOffset();
440 int currEnd;
441 while (currStart < end)
443 currEnd = calculateBreakPosition(currStart, end);
444 drawLine(currStart, currEnd, g, rect.x, rect.y);
445 rect.y += lineHeight;
446 if (currEnd == currStart)
447 currStart ++;
448 else
449 currStart = currEnd;
454 * Determines the number of logical lines that the Element
455 * needs to be displayed
456 * @return the number of lines needed to display the Element
458 int determineNumLines()
460 numLines = 0;
461 int end = getEndOffset();
462 if (end == 0)
463 return 0;
465 int breakPoint;
466 for (int i = getStartOffset(); i < end;)
468 numLines ++;
469 // careful: check that there's no off-by-one problem here
470 // depending on which position calculateBreakPosition returns
471 breakPoint = calculateBreakPosition(i, end);
472 if (breakPoint == i)
473 i ++;
474 else
475 i = breakPoint;
477 return numLines;
481 * Determines the preferred span for this view along the given axis.
483 * @param axis the axis (either X_AXIS or Y_AXIS)
485 * @return the preferred span along the given axis.
486 * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
488 public float getPreferredSpan(int axis)
490 if (axis == X_AXIS)
491 return getWidth();
492 else if (axis == Y_AXIS)
493 return numLines * metrics.getHeight();
495 throw new IllegalArgumentException("Invalid axis for getPreferredSpan: "
496 + axis);
500 * Provides a mapping from model space to view space.
502 * @param pos the position in the model
503 * @param a the region into which the view is rendered
504 * @param b the position bias (forward or backward)
506 * @return a box in view space that represents the given position
507 * in model space
508 * @throws BadLocationException if the given model position is invalid
510 public Shape modelToView(int pos, Shape a, Bias b)
511 throws BadLocationException
513 Segment s = getLineBuffer();
514 int lineHeight = metrics.getHeight();
515 Rectangle rect = a.getBounds();
517 // Return a rectangle with width 1 and height equal to the height
518 // of the text
519 rect.height = lineHeight;
520 rect.width = 1;
522 int currLineStart = getStartOffset();
523 int end = getEndOffset();
525 if (pos < currLineStart || pos >= end)
526 throw new BadLocationException("invalid offset", pos);
528 while (true)
530 int currLineEnd = calculateBreakPosition(currLineStart, end);
531 // If pos is between currLineStart and currLineEnd then just find
532 // the width of the text from currLineStart to pos and add that
533 // to rect.x
534 if (pos >= currLineStart && pos < currLineEnd || pos == end - 1)
538 getDocument().getText(currLineStart, pos - currLineStart, s);
540 catch (BadLocationException ble)
542 // Shouldn't happen
544 rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x,
545 WrappedPlainView.this,
546 currLineStart);
547 return rect;
549 // Increment rect.y so we're checking the next logical line
550 rect.y += lineHeight;
552 // Increment currLineStart to the model position of the start
553 // of the next logical line
554 if (currLineEnd == currLineStart)
555 currLineStart = end;
556 else
557 currLineStart = currLineEnd;
563 * Provides a mapping from view space to model space.
565 * @param x the x coordinate in view space
566 * @param y the y coordinate in view space
567 * @param a the region into which the view is rendered
568 * @param b the position bias (forward or backward)
570 * @return the location in the model that best represents the
571 * given point in view space
573 public int viewToModel(float x, float y, Shape a, Bias[] b)
575 Segment s = getLineBuffer();
576 Rectangle rect = a.getBounds();
577 int currLineStart = getStartOffset();
578 int end = getEndOffset();
579 int lineHeight = metrics.getHeight();
580 if (y < rect.y)
581 return currLineStart;
582 if (y > rect.y + rect.height)
583 return end - 1;
585 while (true)
587 int currLineEnd = calculateBreakPosition(currLineStart, end);
588 // If we're at the right y-position that means we're on the right
589 // logical line and we should look for the character
590 if (y >= rect.y && y < rect.y + lineHeight)
592 // Check if the x position is to the left or right of the text
593 if (x < rect.x)
594 return currLineStart;
595 if (x > rect.x + rect.width)
596 return currLineEnd - 1;
600 getDocument().getText(currLineStart, end - currLineStart, s);
602 catch (BadLocationException ble)
604 // Shouldn't happen
606 int mark = Utilities.getTabbedTextOffset(s, metrics, rect.x,
607 (int) x,
608 WrappedPlainView.this,
609 currLineStart);
610 return currLineStart + mark;
612 // Increment rect.y so we're checking the next logical line
613 rect.y += lineHeight;
615 // Increment currLineStart to the model position of the start
616 // of the next logical line
617 if (currLineEnd == currLineStart)
618 currLineStart = end;
619 else
620 currLineStart = currLineEnd;
625 * This method is called from insertUpdate and removeUpdate.
626 * If the number of lines in the document has changed, just repaint
627 * the whole thing (note, could improve performance by not repainting
628 * anything above the changes). If the number of lines hasn't changed,
629 * just repaint the given Rectangle.
630 * @param a the Rectangle to repaint if the number of lines hasn't changed
632 void updateDamage (Rectangle a)
634 int newNumLines = determineNumLines();
635 if (numLines != newNumLines)
637 numLines = newNumLines;
638 getContainer().repaint();
640 else
641 getContainer().repaint(a.x, a.y, a.width, a.height);
645 * This method is called when something is inserted into the Document
646 * that this View is displaying.
648 * @param changes the DocumentEvent for the changes.
649 * @param a the allocation of the View
650 * @param f the ViewFactory used to rebuild
652 public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
654 updateDamage((Rectangle)a);
658 * This method is called when something is removed from the Document
659 * that this View is displaying.
661 * @param changes the DocumentEvent for the changes.
662 * @param a the allocation of the View
663 * @param f the ViewFactory used to rebuild
665 public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f)
667 updateDamage((Rectangle)a);