| /* WrappedPlainView.java -- | |
| Copyright (C) 2005, 2006 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.text; | |
| import java.awt.Color; | |
| import java.awt.Container; | |
| import java.awt.FontMetrics; | |
| import java.awt.Graphics; | |
| import java.awt.Rectangle; | |
| import java.awt.Shape; | |
| import javax.swing.event.DocumentEvent; | |
| import javax.swing.text.Position.Bias; | |
| /** | |
| * @author Anthony Balkissoon abalkiss at redhat dot com | |
| * | |
| */ | |
| public class WrappedPlainView extends BoxView implements TabExpander | |
| { | |
| /** The color for selected text **/ | |
| Color selectedColor; | |
| /** The color for unselected text **/ | |
| Color unselectedColor; | |
| /** The color for disabled components **/ | |
| Color disabledColor; | |
| /** | |
| * Stores the font metrics. This is package private to avoid synthetic | |
| * accessor method. | |
| */ | |
| FontMetrics metrics; | |
| /** Whether or not to wrap on word boundaries **/ | |
| boolean wordWrap; | |
| /** A ViewFactory that creates WrappedLines **/ | |
| ViewFactory viewFactory = new WrappedLineCreator(); | |
| /** The start of the selected text **/ | |
| int selectionStart; | |
| /** The end of the selected text **/ | |
| int selectionEnd; | |
| /** The height of the line (used while painting) **/ | |
| int lineHeight; | |
| /** | |
| * The base offset for tab calculations. | |
| */ | |
| private int tabBase; | |
| /** | |
| * The tab size. | |
| */ | |
| private int tabSize; | |
| /** | |
| * The instance returned by {@link #getLineBuffer()}. | |
| */ | |
| private transient Segment lineBuffer; | |
| public WrappedPlainView (Element elem) | |
| { | |
| this (elem, false); | |
| } | |
| public WrappedPlainView (Element elem, boolean wordWrap) | |
| { | |
| super (elem, Y_AXIS); | |
| this.wordWrap = wordWrap; | |
| } | |
| /** | |
| * Provides access to the Segment used for retrievals from the Document. | |
| * @return the Segment. | |
| */ | |
| protected final Segment getLineBuffer() | |
| { | |
| if (lineBuffer == null) | |
| lineBuffer = new Segment(); | |
| return lineBuffer; | |
| } | |
| /** | |
| * Returns the next tab stop position after a given reference position. | |
| * | |
| * This implementation ignores the <code>tabStop</code> argument. | |
| * | |
| * @param x the current x position in pixels | |
| * @param tabStop the position within the text stream that the tab occured at | |
| */ | |
| public float nextTabStop(float x, int tabStop) | |
| { | |
| int next = (int) x; | |
| if (tabSize != 0) | |
| { | |
| int numTabs = ((int) x - tabBase) / tabSize; | |
| next = tabBase + (numTabs + 1) * tabSize; | |
| } | |
| return next; | |
| } | |
| /** | |
| * Returns the tab size for the Document based on | |
| * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is | |
| * not defined | |
| * | |
| * @return the tab size. | |
| */ | |
| protected int getTabSize() | |
| { | |
| Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute); | |
| if (tabSize == null) | |
| return 8; | |
| return ((Integer)tabSize).intValue(); | |
| } | |
| /** | |
| * Draws a line of text, suppressing white space at the end and expanding | |
| * tabs. Calls drawSelectedText and drawUnselectedText. | |
| * @param p0 starting document position to use | |
| * @param p1 ending document position to use | |
| * @param g graphics context | |
| * @param x starting x position | |
| * @param y starting y position | |
| */ | |
| protected void drawLine(int p0, int p1, Graphics g, int x, int y) | |
| { | |
| try | |
| { | |
| // We have to draw both selected and unselected text. There are | |
| // several cases: | |
| // - entire range is unselected | |
| // - entire range is selected | |
| // - start of range is selected, end of range is unselected | |
| // - start of range is unselected, end of range is selected | |
| // - middle of range is selected, start and end of range is unselected | |
| // entire range unselected: | |
| if ((selectionStart == selectionEnd) || | |
| (p0 > selectionEnd || p1 < selectionStart)) | |
| drawUnselectedText(g, x, y, p0, p1); | |
| // entire range selected | |
| else if (p0 >= selectionStart && p1 <= selectionEnd) | |
| drawSelectedText(g, x, y, p0, p1); | |
| // start of range selected, end of range unselected | |
| else if (p0 >= selectionStart) | |
| { | |
| x = drawSelectedText(g, x, y, p0, selectionEnd); | |
| drawUnselectedText(g, x, y, selectionEnd, p1); | |
| } | |
| // start of range unselected, end of range selected | |
| else if (selectionStart > p0 && selectionEnd > p1) | |
| { | |
| x = drawUnselectedText(g, x, y, p0, selectionStart); | |
| drawSelectedText(g, x, y, selectionStart, p1); | |
| } | |
| // middle of range selected | |
| else if (selectionStart > p0) | |
| { | |
| x = drawUnselectedText(g, x, y, p0, selectionStart); | |
| x = drawSelectedText(g, x, y, selectionStart, selectionEnd); | |
| drawUnselectedText(g, x, y, selectionEnd, p1); | |
| } | |
| } | |
| catch (BadLocationException ble) | |
| { | |
| // shouldn't happen | |
| } | |
| } | |
| /** | |
| * Renders the range of text as selected text. Just paints the text | |
| * in the color specified by the host component. Assumes the highlighter | |
| * will render the selected background. | |
| * @param g the graphics context | |
| * @param x the starting X coordinate | |
| * @param y the starting Y coordinate | |
| * @param p0 the starting model location | |
| * @param p1 the ending model location | |
| * @return the X coordinate of the end of the text | |
| * @throws BadLocationException if the given range is invalid | |
| */ | |
| protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) | |
| throws BadLocationException | |
| { | |
| g.setColor(selectedColor); | |
| Segment segment = getLineBuffer(); | |
| getDocument().getText(p0, p1 - p0, segment); | |
| return Utilities.drawTabbedText(segment, x, y, g, this, p0); | |
| } | |
| /** | |
| * Renders the range of text as normal unhighlighted text. | |
| * @param g the graphics context | |
| * @param x the starting X coordinate | |
| * @param y the starting Y coordinate | |
| * @param p0 the starting model location | |
| * @param p1 the end model location | |
| * @return the X location of the end off the range | |
| * @throws BadLocationException if the range given is invalid | |
| */ | |
| protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) | |
| throws BadLocationException | |
| { | |
| JTextComponent textComponent = (JTextComponent) getContainer(); | |
| if (textComponent.isEnabled()) | |
| g.setColor(unselectedColor); | |
| else | |
| g.setColor(disabledColor); | |
| Segment segment = getLineBuffer(); | |
| getDocument().getText(p0, p1 - p0, segment); | |
| return Utilities.drawTabbedText(segment, x, y, g, this, p0); | |
| } | |
| /** | |
| * Loads the children to initiate the view. Called by setParent. | |
| * Creates a WrappedLine for each child Element. | |
| */ | |
| protected void loadChildren (ViewFactory f) | |
| { | |
| Element root = getElement(); | |
| int numChildren = root.getElementCount(); | |
| if (numChildren == 0) | |
| return; | |
| View[] children = new View[numChildren]; | |
| for (int i = 0; i < numChildren; i++) | |
| children[i] = new WrappedLine(root.getElement(i)); | |
| replace(0, 0, children); | |
| } | |
| /** | |
| * Calculates the break position for the text between model positions | |
| * p0 and p1. Will break on word boundaries or character boundaries | |
| * depending on the break argument given in construction of this | |
| * WrappedPlainView. Used by the nested WrappedLine class to determine | |
| * when to start the next logical line. | |
| * @param p0 the start model position | |
| * @param p1 the end model position | |
| * @return the model position at which to break the text | |
| */ | |
| protected int calculateBreakPosition(int p0, int p1) | |
| { | |
| Segment s = new Segment(); | |
| try | |
| { | |
| getDocument().getText(p0, p1 - p0, s); | |
| } | |
| catch (BadLocationException ex) | |
| { | |
| assert false : "Couldn't load text"; | |
| } | |
| int width = getWidth(); | |
| int pos; | |
| if (wordWrap) | |
| pos = p0 + Utilities.getBreakLocation(s, metrics, tabBase, | |
| tabBase + width, this, p0); | |
| else | |
| pos = p0 + Utilities.getTabbedTextOffset(s, metrics, tabBase, | |
| tabBase + width, this, p0, | |
| false); | |
| return pos; | |
| } | |
| void updateMetrics() | |
| { | |
| Container component = getContainer(); | |
| metrics = component.getFontMetrics(component.getFont()); | |
| tabSize = getTabSize()* metrics.charWidth('m'); | |
| } | |
| /** | |
| * Determines the preferred span along the given axis. Implemented to | |
| * cache the font metrics and then call the super classes method. | |
| */ | |
| public float getPreferredSpan (int axis) | |
| { | |
| updateMetrics(); | |
| return super.getPreferredSpan(axis); | |
| } | |
| /** | |
| * Determines the minimum span along the given axis. Implemented to | |
| * cache the font metrics and then call the super classes method. | |
| */ | |
| public float getMinimumSpan (int axis) | |
| { | |
| updateMetrics(); | |
| return super.getMinimumSpan(axis); | |
| } | |
| /** | |
| * Determines the maximum span along the given axis. Implemented to | |
| * cache the font metrics and then call the super classes method. | |
| */ | |
| public float getMaximumSpan (int axis) | |
| { | |
| updateMetrics(); | |
| return super.getMaximumSpan(axis); | |
| } | |
| /** | |
| * Called when something was inserted. Overridden so that | |
| * the view factory creates WrappedLine views. | |
| */ | |
| public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f) | |
| { | |
| // Update children efficiently. | |
| updateChildren(e, a); | |
| // Notify children. | |
| Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) | |
| : null; | |
| View v = getViewAtPosition(e.getOffset(), r); | |
| if (v != null) | |
| v.insertUpdate(e, r, f); | |
| } | |
| /** | |
| * Called when something is removed. Overridden so that | |
| * the view factory creates WrappedLine views. | |
| */ | |
| public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f) | |
| { | |
| // Update children efficiently. | |
| updateChildren(e, a); | |
| // Notify children. | |
| Rectangle r = a != null && isAllocationValid() ? getInsideAllocation(a) | |
| : null; | |
| View v = getViewAtPosition(e.getOffset(), r); | |
| if (v != null) | |
| v.removeUpdate(e, r, f); | |
| } | |
| /** | |
| * Called when the portion of the Document that this View is responsible | |
| * for changes. Overridden so that the view factory creates | |
| * WrappedLine views. | |
| */ | |
| public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f) | |
| { | |
| // Update children efficiently. | |
| updateChildren(e, a); | |
| } | |
| /** | |
| * Helper method. Updates the child views in response to | |
| * insert/remove/change updates. This is here to be a little more efficient | |
| * than the BoxView implementation. | |
| * | |
| * @param ev the document event | |
| * @param a the shape | |
| */ | |
| private void updateChildren(DocumentEvent ev, Shape a) | |
| { | |
| Element el = getElement(); | |
| DocumentEvent.ElementChange ec = ev.getChange(el); | |
| if (ec != null) | |
| { | |
| Element[] removed = ec.getChildrenRemoved(); | |
| Element[] added = ec.getChildrenAdded(); | |
| View[] addedViews = new View[added.length]; | |
| for (int i = 0; i < added.length; i++) | |
| addedViews[i] = new WrappedLine(added[i]); | |
| replace(ec.getIndex(), removed.length, addedViews); | |
| if (a != null) | |
| { | |
| preferenceChanged(null, true, true); | |
| getContainer().repaint(); | |
| } | |
| } | |
| updateMetrics(); | |
| } | |
| class WrappedLineCreator implements ViewFactory | |
| { | |
| // Creates a new WrappedLine | |
| public View create(Element elem) | |
| { | |
| return new WrappedLine(elem); | |
| } | |
| } | |
| /** | |
| * Renders the <code>Element</code> that is associated with this | |
| * <code>View</code>. Caches the metrics and then calls | |
| * super.paint to paint all the child views. | |
| * | |
| * @param g the <code>Graphics</code> context to render to | |
| * @param a the allocated region for the <code>Element</code> | |
| */ | |
| public void paint(Graphics g, Shape a) | |
| { | |
| Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); | |
| tabBase = r.x; | |
| JTextComponent comp = (JTextComponent)getContainer(); | |
| // Ensure metrics are up-to-date. | |
| updateMetrics(); | |
| selectionStart = comp.getSelectionStart(); | |
| selectionEnd = comp.getSelectionEnd(); | |
| selectedColor = comp.getSelectedTextColor(); | |
| unselectedColor = comp.getForeground(); | |
| disabledColor = comp.getDisabledTextColor(); | |
| selectedColor = comp.getSelectedTextColor(); | |
| lineHeight = metrics.getHeight(); | |
| g.setFont(comp.getFont()); | |
| super.paint(g, a); | |
| } | |
| /** | |
| * Sets the size of the View. Implemented to update the metrics | |
| * and then call super method. | |
| */ | |
| public void setSize (float width, float height) | |
| { | |
| updateMetrics(); | |
| if (width != getWidth()) | |
| preferenceChanged(null, true, true); | |
| super.setSize(width, height); | |
| } | |
| class WrappedLine extends View | |
| { | |
| /** Used to cache the number of lines for this View **/ | |
| int numLines = 1; | |
| public WrappedLine(Element elem) | |
| { | |
| super(elem); | |
| } | |
| /** | |
| * Renders this (possibly wrapped) line using the given Graphics object | |
| * and on the given rendering surface. | |
| */ | |
| public void paint(Graphics g, Shape s) | |
| { | |
| Rectangle rect = s.getBounds(); | |
| int end = getEndOffset(); | |
| int currStart = getStartOffset(); | |
| int currEnd; | |
| int count = 0; | |
| // Determine layered highlights. | |
| Container c = getContainer(); | |
| LayeredHighlighter lh = null; | |
| JTextComponent tc = null; | |
| if (c instanceof JTextComponent) | |
| { | |
| tc = (JTextComponent) c; | |
| Highlighter h = tc.getHighlighter(); | |
| if (h instanceof LayeredHighlighter) | |
| lh = (LayeredHighlighter) h; | |
| } | |
| while (currStart < end) | |
| { | |
| currEnd = calculateBreakPosition(currStart, end); | |
| // Paint layered highlights, if any. | |
| if (lh != null) | |
| { | |
| // Exclude trailing newline in last line. | |
| if (currEnd == end) | |
| lh.paintLayeredHighlights(g, currStart, currEnd - 1, s, tc, | |
| this); | |
| else | |
| lh.paintLayeredHighlights(g, currStart, currEnd, s, tc, this); | |
| } | |
| drawLine(currStart, currEnd, g, rect.x, rect.y + metrics.getAscent()); | |
| rect.y += lineHeight; | |
| if (currEnd == currStart) | |
| currStart ++; | |
| else | |
| currStart = currEnd; | |
| count++; | |
| } | |
| if (count != numLines) | |
| { | |
| numLines = count; | |
| preferenceChanged(this, false, true); | |
| } | |
| } | |
| /** | |
| * Calculates the number of logical lines that the Element | |
| * needs to be displayed and updates the variable numLines | |
| * accordingly. | |
| */ | |
| private int determineNumLines() | |
| { | |
| int nLines = 0; | |
| int end = getEndOffset(); | |
| for (int i = getStartOffset(); i < end;) | |
| { | |
| nLines++; | |
| // careful: check that there's no off-by-one problem here | |
| // depending on which position calculateBreakPosition returns | |
| int breakPoint = calculateBreakPosition(i, end); | |
| if (breakPoint == i) | |
| i = breakPoint + 1; | |
| else | |
| i = breakPoint; | |
| } | |
| return nLines; | |
| } | |
| /** | |
| * Determines the preferred span for this view along the given axis. | |
| * | |
| * @param axis the axis (either X_AXIS or Y_AXIS) | |
| * | |
| * @return the preferred span along the given axis. | |
| * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS | |
| */ | |
| public float getPreferredSpan(int axis) | |
| { | |
| if (axis == X_AXIS) | |
| return getWidth(); | |
| else if (axis == Y_AXIS) | |
| { | |
| if (metrics == null) | |
| updateMetrics(); | |
| return numLines * metrics.getHeight(); | |
| } | |
| throw new IllegalArgumentException("Invalid axis for getPreferredSpan: " | |
| + axis); | |
| } | |
| /** | |
| * Provides a mapping from model space to view space. | |
| * | |
| * @param pos the position in the model | |
| * @param a the region into which the view is rendered | |
| * @param b the position bias (forward or backward) | |
| * | |
| * @return a box in view space that represents the given position | |
| * in model space | |
| * @throws BadLocationException if the given model position is invalid | |
| */ | |
| public Shape modelToView(int pos, Shape a, Bias b) | |
| throws BadLocationException | |
| { | |
| Rectangle rect = a.getBounds(); | |
| // Throwing a BadLocationException is an observed behavior of the RI. | |
| if (rect.isEmpty()) | |
| throw new BadLocationException("Unable to calculate view coordinates " | |
| + "when allocation area is empty.", pos); | |
| Segment s = getLineBuffer(); | |
| int lineHeight = metrics.getHeight(); | |
| // Return a rectangle with width 1 and height equal to the height | |
| // of the text | |
| rect.height = lineHeight; | |
| rect.width = 1; | |
| int currLineStart = getStartOffset(); | |
| int end = getEndOffset(); | |
| if (pos < currLineStart || pos >= end) | |
| throw new BadLocationException("invalid offset", pos); | |
| while (true) | |
| { | |
| int currLineEnd = calculateBreakPosition(currLineStart, end); | |
| // If pos is between currLineStart and currLineEnd then just find | |
| // the width of the text from currLineStart to pos and add that | |
| // to rect.x | |
| if (pos >= currLineStart && pos < currLineEnd) | |
| { | |
| try | |
| { | |
| getDocument().getText(currLineStart, pos - currLineStart, s); | |
| } | |
| catch (BadLocationException ble) | |
| { | |
| // Shouldn't happen | |
| } | |
| rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x, | |
| WrappedPlainView.this, | |
| currLineStart); | |
| return rect; | |
| } | |
| // Increment rect.y so we're checking the next logical line | |
| rect.y += lineHeight; | |
| // Increment currLineStart to the model position of the start | |
| // of the next logical line | |
| if (currLineEnd == currLineStart) | |
| currLineStart = end; | |
| else | |
| currLineStart = currLineEnd; | |
| } | |
| } | |
| /** | |
| * Provides a mapping from view space to model space. | |
| * | |
| * @param x the x coordinate in view space | |
| * @param y the y coordinate in view space | |
| * @param a the region into which the view is rendered | |
| * @param b the position bias (forward or backward) | |
| * | |
| * @return the location in the model that best represents the | |
| * given point in view space | |
| */ | |
| public int viewToModel(float x, float y, Shape a, Bias[] b) | |
| { | |
| Segment s = getLineBuffer(); | |
| Rectangle rect = a.getBounds(); | |
| int currLineStart = getStartOffset(); | |
| // Although calling modelToView with the last possible offset will | |
| // cause a BadLocationException in CompositeView it is allowed | |
| // to return that offset in viewToModel. | |
| int end = getEndOffset(); | |
| int lineHeight = metrics.getHeight(); | |
| if (y < rect.y) | |
| return currLineStart; | |
| if (y > rect.y + rect.height) | |
| return end - 1; | |
| // Note: rect.x and rect.width do not represent the width of painted | |
| // text but the area where text *may* be painted. This means the width | |
| // is most of the time identical to the component's width. | |
| while (currLineStart != end) | |
| { | |
| int currLineEnd = calculateBreakPosition(currLineStart, end); | |
| // If we're at the right y-position that means we're on the right | |
| // logical line and we should look for the character | |
| if (y >= rect.y && y < rect.y + lineHeight) | |
| { | |
| try | |
| { | |
| getDocument().getText(currLineStart, currLineEnd - currLineStart, s); | |
| } | |
| catch (BadLocationException ble) | |
| { | |
| // Shouldn't happen | |
| } | |
| int offset = Utilities.getTabbedTextOffset(s, metrics, rect.x, | |
| (int) x, | |
| WrappedPlainView.this, | |
| currLineStart); | |
| // If the calculated offset is the end of the line (in the | |
| // document (= start of the next line) return the preceding | |
| // offset instead. This makes sure that clicking right besides | |
| // the last character in a line positions the cursor after the | |
| // last character and not in the beginning of the next line. | |
| return (offset == currLineEnd) ? offset - 1 : offset; | |
| } | |
| // Increment rect.y so we're checking the next logical line | |
| rect.y += lineHeight; | |
| // Increment currLineStart to the model position of the start | |
| // of the next logical line. | |
| currLineStart = currLineEnd; | |
| } | |
| return end; | |
| } | |
| /** | |
| * <p>This method is called from insertUpdate and removeUpdate.</p> | |
| * | |
| * <p>If the number of lines in the document has changed, just repaint | |
| * the whole thing (note, could improve performance by not repainting | |
| * anything above the changes). If the number of lines hasn't changed, | |
| * just repaint the given Rectangle.</p> | |
| * | |
| * <p>Note that the <code>Rectangle</code> argument may be <code>null</code> | |
| * when the allocation area is empty.</code> | |
| * | |
| * @param a the Rectangle to repaint if the number of lines hasn't changed | |
| */ | |
| void updateDamage (Rectangle a) | |
| { | |
| int nLines = determineNumLines(); | |
| if (numLines != nLines) | |
| { | |
| numLines = nLines; | |
| preferenceChanged(this, false, true); | |
| getContainer().repaint(); | |
| } | |
| else if (a != null) | |
| getContainer().repaint(a.x, a.y, a.width, a.height); | |
| } | |
| /** | |
| * This method is called when something is inserted into the Document | |
| * that this View is displaying. | |
| * | |
| * @param changes the DocumentEvent for the changes. | |
| * @param a the allocation of the View | |
| * @param f the ViewFactory used to rebuild | |
| */ | |
| public void insertUpdate (DocumentEvent changes, Shape a, ViewFactory f) | |
| { | |
| Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); | |
| updateDamage(r); | |
| } | |
| /** | |
| * This method is called when something is removed from the Document | |
| * that this View is displaying. | |
| * | |
| * @param changes the DocumentEvent for the changes. | |
| * @param a the allocation of the View | |
| * @param f the ViewFactory used to rebuild | |
| */ | |
| public void removeUpdate (DocumentEvent changes, Shape a, ViewFactory f) | |
| { | |
| // Note: This method is not called when characters from the | |
| // end of the document are removed. The reason for this | |
| // can be found in the implementation of View.forwardUpdate: | |
| // The document event will denote offsets which do not exist | |
| // any more, getViewIndex() will therefore return -1 and this | |
| // makes View.forwardUpdate() skip this method call. | |
| // However this seems to cause no trouble and as it reduces the | |
| // number of method calls it can stay this way. | |
| Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); | |
| updateDamage(r); | |
| } | |
| } | |
| } |