1 /* WrappedPlainView.java --
2 Copyright (C) 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.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.event.DocumentEvent;
49 import javax.swing.text.Position.Bias;
52 * @author Anthony Balkissoon abalkiss at redhat dot com
55 public class WrappedPlainView extends BoxView implements TabExpander
57 /** The color for selected text **/
60 /** The color for unselected text **/
61 Color unselectedColor;
63 /** The color for disabled components **/
67 * Stores the font metrics. This is package private to avoid synthetic
72 /** Whether or not to wrap on word boundaries **/
75 /** A ViewFactory that creates WrappedLines **/
76 ViewFactory viewFactory = new WrappedLineCreator();
78 /** The start of the selected text **/
81 /** The end of the selected text **/
84 /** The height of the line (used while painting) **/
88 * The base offset for tab calculations.
98 * The instance returned by {@link #getLineBuffer()}.
100 private transient Segment lineBuffer;
102 public WrappedPlainView (Element elem)
107 public WrappedPlainView (Element elem, boolean wordWrap)
109 super (elem, Y_AXIS);
110 this.wordWrap = wordWrap;
114 * Provides access to the Segment used for retrievals from the Document.
115 * @return the Segment.
117 protected final Segment getLineBuffer()
119 if (lineBuffer == null)
120 lineBuffer = new Segment();
125 * Returns the next tab stop position after a given reference position.
127 * This implementation ignores the <code>tabStop</code> argument.
129 * @param x the current x position in pixels
130 * @param tabStop the position within the text stream that the tab occured at
132 public float nextTabStop(float x, int tabStop)
137 int numTabs = ((int) x - tabBase) / tabSize;
138 next = tabBase + (numTabs + 1) * tabSize;
144 * Returns the tab size for the Document based on
145 * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is
148 * @return the tab size.
150 protected int getTabSize()
152 Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
155 return ((Integer)tabSize).intValue();
159 * Draws a line of text, suppressing white space at the end and expanding
160 * tabs. Calls drawSelectedText and drawUnselectedText.
161 * @param p0 starting document position to use
162 * @param p1 ending document position to use
163 * @param g graphics context
164 * @param x starting x position
165 * @param y starting y position
167 protected void drawLine(int p0, int p1, Graphics g, int x, int y)
171 // We have to draw both selected and unselected text. There are
173 // - entire range is unselected
174 // - entire range is selected
175 // - start of range is selected, end of range is unselected
176 // - start of range is unselected, end of range is selected
177 // - middle of range is selected, start and end of range is unselected
179 // entire range unselected:
180 if ((selectionStart == selectionEnd) ||
181 (p0 > selectionEnd || p1 < selectionStart))
182 drawUnselectedText(g, x, y, p0, p1);
184 // entire range selected
185 else if (p0 >= selectionStart && p1 <= selectionEnd)
186 drawSelectedText(g, x, y, p0, p1);
188 // start of range selected, end of range unselected
189 else if (p0 >= selectionStart)
191 x = drawSelectedText(g, x, y, p0, selectionEnd);
192 drawUnselectedText(g, x, y, selectionEnd, p1);
195 // start of range unselected, end of range selected
196 else if (selectionStart > p0 && selectionEnd > p1)
198 x = drawUnselectedText(g, x, y, p0, selectionStart);
199 drawSelectedText(g, x, y, selectionStart, p1);
202 // middle of range selected
203 else if (selectionStart > p0)
205 x = drawUnselectedText(g, x, y, p0, selectionStart);
206 x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
207 drawUnselectedText(g, x, y, selectionEnd, p1);
210 catch (BadLocationException ble)
217 * Renders the range of text as selected text. Just paints the text
218 * in the color specified by the host component. Assumes the highlighter
219 * will render the selected background.
220 * @param g the graphics context
221 * @param x the starting X coordinate
222 * @param y the starting Y coordinate
223 * @param p0 the starting model location
224 * @param p1 the ending model location
225 * @return the X coordinate of the end of the text
226 * @throws BadLocationException if the given range is invalid
228 protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
229 throws BadLocationException
231 g.setColor(selectedColor);
232 Segment segment = getLineBuffer();
233 getDocument().getText(p0, p1 - p0, segment);
234 return Utilities.drawTabbedText(segment, x, y, g, this, p0);
238 * Renders the range of text as normal unhighlighted text.
239 * @param g the graphics context
240 * @param x the starting X coordinate
241 * @param y the starting Y coordinate
242 * @param p0 the starting model location
243 * @param p1 the end model location
244 * @return the X location of the end off the range
245 * @throws BadLocationException if the range given is invalid
247 protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
248 throws BadLocationException
250 JTextComponent textComponent = (JTextComponent) getContainer();
251 if (textComponent.isEnabled())
252 g.setColor(unselectedColor);
254 g.setColor(disabledColor);
256 Segment segment = getLineBuffer();
257 getDocument().getText(p0, p1 - p0, segment);
258 return Utilities.drawTabbedText(segment, x, y, g, this, p0);
262 * Loads the children to initiate the view. Called by setParent.
263 * Creates a WrappedLine for each child Element.
265 protected void loadChildren (ViewFactory f)
267 Element root = getElement();
268 int numChildren = root.getElementCount();
269 if (numChildren == 0)
272 View[] children = new View[numChildren];
273 for (int i = 0; i < numChildren; i++)
274 children[i] = new WrappedLine(root.getElement(i));
275 replace(0, 0, children);
279 * Calculates the break position for the text between model positions
280 * p0 and p1. Will break on word boundaries or character boundaries
281 * depending on the break argument given in construction of this
282 * WrappedPlainView. Used by the nested WrappedLine class to determine
283 * when to start the next logical line.
284 * @param p0 the start model position
285 * @param p1 the end model position
286 * @return the model position at which to break the text
288 protected int calculateBreakPosition(int p0, int p1)
290 Segment s = new Segment();
293 getDocument().getText(p0, p1 - p0, s);
295 catch (BadLocationException ex)
297 assert false : "Couldn't load text";
299 int width = getWidth();
302 pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase,
303 tabBase + width, this, p0);
305 pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase,
306 tabBase + width, this, p0,
313 Container component = getContainer();
314 metrics = component.getFontMetrics(component.getFont());
315 tabSize = getTabSize()* metrics.charWidth('m');
319 * Determines the preferred span along the given axis. Implemented to
320 * cache the font metrics and then call the super classes method.
322 public float getPreferredSpan (int axis)
325 return super.getPreferredSpan(axis);
329 * Determines the minimum span along the given axis. Implemented to
330 * cache the font metrics and then call the super classes method.
332 public float getMinimumSpan (int axis)
335 return super.getMinimumSpan(axis);
339 * Determines the maximum span along the given axis. Implemented to
340 * cache the font metrics and then call the super classes method.
342 public float getMaximumSpan (int axis)
345 return super.getMaximumSpan(axis);
349 * Called when something was inserted. Overridden so that
350 * the view factory creates WrappedLine views.
352 public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f)
354 // Update children efficiently.
355 updateChildren(e, a);
358 Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
360 View v = getViewAtPosition(e.getOffset(), r);
362 v.insertUpdate(e, r, f);
366 * Called when something is removed. Overridden so that
367 * the view factory creates WrappedLine views.
369 public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f)
371 // Update children efficiently.
372 updateChildren(e, a);
375 Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a)
377 View v = getViewAtPosition(e.getOffset(), r);
379 v.removeUpdate(e, r, f);
383 * Called when the portion of the Document that this View is responsible
384 * for changes. Overridden so that the view factory creates
387 public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f)
389 // Update children efficiently.
390 updateChildren(e, a);
394 * Helper method. Updates the child views in response to
395 * insert/remove/change updates. This is here to be a little more efficient
396 * than the BoxView implementation.
398 * @param ev the document event
401 private void updateChildren(DocumentEvent ev, Shape a)
403 Element el = getElement();
404 DocumentEvent.ElementChange ec = ev.getChange(el);
407 Element[] removed = ec.getChildrenRemoved();
408 Element[] added = ec.getChildrenAdded();
409 View[] addedViews = new View[added.length];
410 for (int i = 0; i < added.length; i++)
411 addedViews[i] = new WrappedLine(added[i]);
412 replace(ec.getIndex(), removed.length, addedViews);
415 preferenceChanged(null, true, true);
416 getContainer().repaint();
422 class WrappedLineCreator implements ViewFactory
424 // Creates a new WrappedLine
425 public View create(Element elem)
427 return new WrappedLine(elem);
432 * Renders the <code>Element</code> that is associated with this
433 * <code>View</code>. Caches the metrics and then calls
434 * super.paint to paint all the child views.
436 * @param g the <code>Graphics</code> context to render to
437 * @param a the allocated region for the <code>Element</code>
439 public void paint(Graphics g, Shape a)
441 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
444 JTextComponent comp = (JTextComponent)getContainer();
445 // Ensure metrics are up-to-date.
448 selectionStart = comp.getSelectionStart();
449 selectionEnd = comp.getSelectionEnd();
451 selectedColor = comp.getSelectedTextColor();
452 unselectedColor = comp.getForeground();
453 disabledColor = comp.getDisabledTextColor();
454 selectedColor = comp.getSelectedTextColor();
455 lineHeight = metrics.getHeight();
456 g.setFont(comp.getFont());
462 * Sets the size of the View. Implemented to update the metrics
463 * and then call super method.
465 public void setSize (float width, float height)
468 if (width != getWidth())
469 preferenceChanged(null, true, true);
470 super.setSize(width, height);
473 class WrappedLine extends View
475 /** Used to cache the number of lines for this View **/
478 public WrappedLine(Element elem)
484 * Renders this (possibly wrapped) line using the given Graphics object
485 * and on the given rendering surface.
487 public void paint(Graphics g, Shape s)
489 Rectangle rect = s.getBounds();
491 int end = getEndOffset();
492 int currStart = getStartOffset();
496 // Determine layered highlights.
497 Container c = getContainer();
498 LayeredHighlighter lh = null;
499 JTextComponent tc = null;
500 if (c instanceof JTextComponent)
502 tc = (JTextComponent) c;
503 Highlighter h = tc.getHighlighter();
504 if (h instanceof LayeredHighlighter)
505 lh = (LayeredHighlighter) h;
508 while (currStart < end)
510 currEnd = calculateBreakPosition(currStart, end);
512 // Paint layered highlights, if any.
515 // Exclude trailing newline in last line.
517 lh.paintLayeredHighlights(g, currStart, currEnd - 1, s, tc,
520 lh.paintLayeredHighlights(g, currStart, currEnd, s, tc, this);
523 drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent());
525 rect.y += lineHeight;
526 if (currEnd == currStart)
535 if (count != numLines)
538 preferenceChanged(this, false, true);
544 * Calculates the number of logical lines that the Element
545 * needs to be displayed and updates the variable numLines
548 private int determineNumLines()
551 int end = getEndOffset();
552 for (int i = getStartOffset(); i < end;)
555 // careful: check that there's no off-by-one problem here
556 // depending on which position calculateBreakPosition returns
557 int breakPoint = calculateBreakPosition(i, end);
568 * Determines the preferred span for this view along the given axis.
570 * @param axis the axis (either X_AXIS or Y_AXIS)
572 * @return the preferred span along the given axis.
573 * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS
575 public float getPreferredSpan(int axis)
579 else if (axis == Y_AXIS)
583 return numLines * metrics.getHeight();
586 throw new IllegalArgumentException("Invalid axis for getPreferredSpan: "
591 * Provides a mapping from model space to view space.
593 * @param pos the position in the model
594 * @param a the region into which the view is rendered
595 * @param b the position bias (forward or backward)
597 * @return a box in view space that represents the given position
599 * @throws BadLocationException if the given model position is invalid
601 public Shape modelToView(int pos, Shape a, Bias b)
602 throws BadLocationException
604 Rectangle rect = a.getBounds();
606 // Throwing a BadLocationException is an observed behavior of the RI.
608 throw new BadLocationException("Unable to calculate view coordinates "
609 + "when allocation area is empty.", pos);
611 Segment s = getLineBuffer();
612 int lineHeight = metrics.getHeight();
614 // Return a rectangle with width 1 and height equal to the height
616 rect.height = lineHeight;
619 int currLineStart = getStartOffset();
620 int end = getEndOffset();
622 if (pos < currLineStart || pos >= end)
623 throw new BadLocationException("invalid offset", pos);
627 int currLineEnd = calculateBreakPosition(currLineStart, end);
628 // If pos is between currLineStart and currLineEnd then just find
629 // the width of the text from currLineStart to pos and add that
631 if (pos >= currLineStart && pos < currLineEnd)
635 getDocument().getText(currLineStart, pos - currLineStart, s);
637 catch (BadLocationException ble)
641 rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x,
642 WrappedPlainView.this,
646 // Increment rect.y so we're checking the next logical line
647 rect.y += lineHeight;
649 // Increment currLineStart to the model position of the start
650 // of the next logical line
651 if (currLineEnd == currLineStart)
654 currLineStart = currLineEnd;
660 * Provides a mapping from view space to model space.
662 * @param x the x coordinate in view space
663 * @param y the y coordinate in view space
664 * @param a the region into which the view is rendered
665 * @param b the position bias (forward or backward)
667 * @return the location in the model that best represents the
668 * given point in view space
670 public int viewToModel(float x, float y, Shape a, Bias[] b)
672 Segment s = getLineBuffer();
673 Rectangle rect = a.getBounds();
674 int currLineStart = getStartOffset();
676 // Although calling modelToView with the last possible offset will
677 // cause a BadLocationException in CompositeView it is allowed
678 // to return that offset in viewToModel.
679 int end = getEndOffset();
681 int lineHeight = metrics.getHeight();
683 return currLineStart;
685 if (y > rect.y + rect.height)
688 // Note: rect.x and rect.width do not represent the width of painted
689 // text but the area where text *may* be painted. This means the width
690 // is most of the time identical to the component's width.
692 while (currLineStart != end)
694 int currLineEnd = calculateBreakPosition(currLineStart, end);
696 // If we're at the right y-position that means we're on the right
697 // logical line and we should look for the character
698 if (y >= rect.y && y < rect.y + lineHeight)
702 getDocument().getText(currLineStart, currLineEnd - currLineStart, s);
704 catch (BadLocationException ble)
709 int offset = Utilities.getTabbedTextOffset(s, metrics, rect.x,
711 WrappedPlainView.this,
713 // If the calculated offset is the end of the line (in the
714 // document (= start of the next line) return the preceding
715 // offset instead. This makes sure that clicking right besides
716 // the last character in a line positions the cursor after the
717 // last character and not in the beginning of the next line.
718 return (offset == currLineEnd) ? offset - 1 : offset;
720 // Increment rect.y so we're checking the next logical line
721 rect.y += lineHeight;
723 // Increment currLineStart to the model position of the start
724 // of the next logical line.
725 currLineStart = currLineEnd;
733 * <p>This method is called from insertUpdate and removeUpdate.</p>
735 * <p>If the number of lines in the document has changed, just repaint
736 * the whole thing (note, could improve performance by not repainting
737 * anything above the changes). If the number of lines hasn't changed,
738 * just repaint the given Rectangle.</p>
740 * <p>Note that the <code>Rectangle</code> argument may be <code>null</code>
741 * when the allocation area is empty.</code>
743 * @param a the Rectangle to repaint if the number of lines hasn't changed
745 void updateDamage (Rectangle a)
747 int nLines = determineNumLines();
748 if (numLines != nLines)
751 preferenceChanged(this, false, true);
752 getContainer().repaint();
755 getContainer().repaint(a.x, a.y, a.width, a.height);
759 * This method is called when something is inserted into the Document
760 * that this View is displaying.
762 * @param changes the DocumentEvent for the changes.
763 * @param a the allocation of the View
764 * @param f the ViewFactory used to rebuild
766 public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f)
768 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
773 * This method is called when something is removed from the Document
774 * that this View is displaying.
776 * @param changes the DocumentEvent for the changes.
777 * @param a the allocation of the View
778 * @param f the ViewFactory used to rebuild
780 public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f)
782 // Note: This method is not called when characters from the
783 // end of the document are removed. The reason for this
784 // can be found in the implementation of View.forwardUpdate:
785 // The document event will denote offsets which do not exist
786 // any more, getViewIndex() will therefore return -1 and this
787 // makes View.forwardUpdate() skip this method call.
788 // However this seems to cause no trouble and as it reduces the
789 // number of method calls it can stay this way.
791 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();