1. /*
  2. * @(#)WrappedPlainView.java 1.22 00/02/02
  3. *
  4. * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text;
  11. import java.util.Vector;
  12. import java.util.Properties;
  13. import java.awt.*;
  14. import javax.swing.event.*;
  15. /**
  16. * View of plain text (text with only one font and color)
  17. * that does line-wrapping. This view expects that its
  18. * associated element has child elements that represent
  19. * the lines it should be wrapping. It is implemented
  20. * as a vertical box that contains logical line views.
  21. * The logical line views are nested classes that render
  22. * the logical line as multiple physical line if the logical
  23. * line is too wide to fit within the allocation. The
  24. * line views draw upon the outer class for its state
  25. * to reduce their memory requirements.
  26. * <p>
  27. * The line views do all of their rendering through the
  28. * <code>drawLine</code> method which in turn does all of
  29. * its rendering through the <code>drawSelectedText</code>
  30. * and <code>drawUnselectedText</code> methods. This
  31. * enables subclasses to easily specialize the rendering
  32. * without concern for the layout aspects.
  33. *
  34. * @author Timothy Prinzing
  35. * @version 1.22 02/02/00
  36. * @see View
  37. */
  38. public class WrappedPlainView extends BoxView implements TabExpander {
  39. /**
  40. * Creates a new WrappedPlainView. Lines will be wrapped
  41. * on character boundaries.
  42. *
  43. * @param elem the element underlying the view
  44. */
  45. public WrappedPlainView(Element elem) {
  46. this(elem, false);
  47. }
  48. /**
  49. * Creates a new WrappedPlainView. Lines can be wrapped on
  50. * either character or word boundaries depending upon the
  51. * setting of the wordWrap parameter.
  52. *
  53. * @param elem the element underlying the view
  54. * @param wordWrap should lines be wrapped on word boundaries?
  55. */
  56. public WrappedPlainView(Element elem, boolean wordWrap) {
  57. super(elem, Y_AXIS);
  58. lineBuffer = new Segment();
  59. this.wordWrap = wordWrap;
  60. }
  61. /**
  62. * Returns the tab size set for the document, defaulting to 8.
  63. *
  64. * @return the tab size
  65. */
  66. protected int getTabSize() {
  67. Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
  68. int size = (i != null) ? i.intValue() : 8;
  69. return size;
  70. }
  71. /**
  72. * Renders a line of text, suppressing whitespace at the end
  73. * and expanding any tabs. This is implemented to make calls
  74. * to the methods <code>drawUnselectedText</code> and
  75. * <code>drawSelectedText</code> so that the way selected and
  76. * unselected text are rendered can be customized.
  77. *
  78. * @param p0 the starting document location to use >= 0
  79. * @param p1 the ending document location to use >= p1
  80. * @param g the graphics context
  81. * @param x the starting X position >= 0
  82. * @param y the starting Y position >= 0
  83. * @see #drawUnselectedText
  84. * @see #drawSelectedText
  85. */
  86. protected void drawLine(int p0, int p1, Graphics g, int x, int y) {
  87. Element lineMap = getElement();
  88. Element line = lineMap.getElement(lineMap.getElementIndex(p0));
  89. Element elem;
  90. try {
  91. if (line.isLeaf()) {
  92. drawText(line, p0, p1, g, x, y);
  93. } else {
  94. // this line contains the composed text.
  95. int idx = line.getElementIndex(p0);
  96. int lastIdx = line.getElementIndex(p1);
  97. for(; idx <= lastIdx; idx++) {
  98. elem = line.getElement(idx);
  99. int start = Math.max(elem.getStartOffset(), p0);
  100. int end = Math.min(elem.getEndOffset(), p1);
  101. x = drawText(elem, start, end, g, x, y);
  102. }
  103. }
  104. } catch (BadLocationException e) {
  105. throw new StateInvariantError("Can't render: " + p0 + "," + p1);
  106. }
  107. }
  108. private int drawText(Element elem, int p0, int p1, Graphics g, int x, int y) throws BadLocationException {
  109. p1 = Math.min(getDocument().getLength(), p1);
  110. AttributeSet attr = elem.getAttributes();
  111. if (Utilities.isComposedTextAttributeDefined(attr)) {
  112. g.setColor(unselected);
  113. x = Utilities.drawComposedText(attr, g, x, y,
  114. p0-elem.getStartOffset(),
  115. p1-elem.getStartOffset());
  116. } else {
  117. if (sel0 == sel1) {
  118. // no selection
  119. x = drawUnselectedText(g, x, y, p0, p1);
  120. } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
  121. x = drawSelectedText(g, x, y, p0, p1);
  122. } else if (sel0 >= p0 && sel0 <= p1) {
  123. if (sel1 >= p0 && sel1 <= p1) {
  124. x = drawUnselectedText(g, x, y, p0, sel0);
  125. x = drawSelectedText(g, x, y, sel0, sel1);
  126. x = drawUnselectedText(g, x, y, sel1, p1);
  127. } else {
  128. x = drawUnselectedText(g, x, y, p0, sel0);
  129. x = drawSelectedText(g, x, y, sel0, p1);
  130. }
  131. } else if (sel1 >= p0 && sel1 <= p1) {
  132. x = drawSelectedText(g, x, y, p0, sel1);
  133. x = drawUnselectedText(g, x, y, sel1, p1);
  134. } else {
  135. x = drawUnselectedText(g, x, y, p0, p1);
  136. }
  137. }
  138. return x;
  139. }
  140. /**
  141. * Renders the given range in the model as normal unselected
  142. * text.
  143. *
  144. * @param g the graphics context
  145. * @param x the starting X coordinate >= 0
  146. * @param y the starting Y coordinate >= 0
  147. * @param p0 the beginning position in the model >= 0
  148. * @param p1 the ending position in the model >= p0
  149. * @returns the X location of the end of the range >= 0
  150. * @exception BadLocationException if the range is invalid
  151. */
  152. protected int drawUnselectedText(Graphics g, int x, int y,
  153. int p0, int p1) throws BadLocationException {
  154. g.setColor(unselected);
  155. Document doc = getDocument();
  156. doc.getText(p0, p1 - p0, lineBuffer);
  157. return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  158. }
  159. /**
  160. * Renders the given range in the model as selected text. This
  161. * is implemented to render the text in the color specified in
  162. * the hosting component. It assumes the highlighter will render
  163. * the selected background.
  164. *
  165. * @param g the graphics context
  166. * @param x the starting X coordinate >= 0
  167. * @param y the starting Y coordinate >= 0
  168. * @param p0 the beginning position in the model >= 0
  169. * @param p1 the ending position in the model >= p0
  170. * @returns the location of the end of the range.
  171. * @exception BadLocationException if the range is invalid
  172. */
  173. protected int drawSelectedText(Graphics g, int x,
  174. int y, int p0, int p1) throws BadLocationException {
  175. g.setColor(selected);
  176. Document doc = getDocument();
  177. doc.getText(p0, p1 - p0, lineBuffer);
  178. return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  179. }
  180. /**
  181. * Gives access to a buffer that can be used to fetch
  182. * text from the associated document.
  183. *
  184. * @returns the buffer
  185. */
  186. protected final Segment getLineBuffer() {
  187. return lineBuffer;
  188. }
  189. /**
  190. * This is called by the nested wrapped line
  191. * views to determine the break location. This can
  192. * be reimplemented to alter the breaking behavior.
  193. * It will either break at word or character boundaries
  194. * depending upon the break argument given at
  195. * construction.
  196. */
  197. protected int calculateBreakPosition(int p0, int p1) {
  198. int p;
  199. loadText(p0, p1);
  200. if (wordWrap) {
  201. p = p0 + Utilities.getBreakLocation(lineBuffer, metrics,
  202. tabBase, tabBase + getWidth(),
  203. this, p0);
  204. } else {
  205. p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics,
  206. tabBase, tabBase + getWidth(),
  207. this, p0);
  208. }
  209. return p;
  210. }
  211. /**
  212. * Loads all of the children to initialize the view.
  213. * This is called by the <code>setParent</code> method.
  214. * Subclasses can reimplement this to initialize their
  215. * child views in a different manner. The default
  216. * implementation creates a child view for each
  217. * child element.
  218. *
  219. * @param f the view factory
  220. */
  221. protected void loadChildren(ViewFactory f) {
  222. Element e = getElement();
  223. int n = e.getElementCount();
  224. if (n > 0) {
  225. View[] added = new View[n];
  226. for (int i = 0; i < n; i++) {
  227. added[i] = new WrappedLine(e.getElement(i));
  228. }
  229. replace(0, 0, added);
  230. }
  231. }
  232. /**
  233. * Update the child views in response to a
  234. * document event.
  235. */
  236. void updateChildren(DocumentEvent e, Shape a) {
  237. Element elem = getElement();
  238. DocumentEvent.ElementChange ec = e.getChange(elem);
  239. if (ec != null) {
  240. // the structure of this element changed.
  241. Element[] removedElems = ec.getChildrenRemoved();
  242. Element[] addedElems = ec.getChildrenAdded();
  243. View[] added = new View[addedElems.length];
  244. for (int i = 0; i < addedElems.length; i++) {
  245. added[i] = new WrappedLine(addedElems[i]);
  246. }
  247. replace(ec.getIndex(), removedElems.length, added);
  248. // should damge a little more intelligently.
  249. if (a != null) {
  250. preferenceChanged(null, true, true);
  251. getContainer().repaint();
  252. }
  253. }
  254. // update font metrics which may be used by the child views
  255. updateMetrics();
  256. }
  257. /**
  258. * Load the text buffer with the given range
  259. * of text. This is used by the fragments
  260. * broken off of this view as well as this
  261. * view itself.
  262. */
  263. final void loadText(int p0, int p1) {
  264. try {
  265. Document doc = getDocument();
  266. doc.getText(p0, p1 - p0, lineBuffer);
  267. } catch (BadLocationException bl) {
  268. throw new StateInvariantError("Can't get line text");
  269. }
  270. }
  271. final void updateMetrics() {
  272. Component host = getContainer();
  273. Font f = host.getFont();
  274. metrics = host.getFontMetrics(f);
  275. tabSize = getTabSize() * metrics.charWidth('m');
  276. }
  277. // --- TabExpander methods ------------------------------------------
  278. /**
  279. * Returns the next tab stop position after a given reference position.
  280. * This implementation does not support things like centering so it
  281. * ignores the tabOffset argument.
  282. *
  283. * @param x the current position >= 0
  284. * @param tabOffset the position within the text stream
  285. * that the tab occurred at >= 0.
  286. * @return the tab stop, measured in points >= 0
  287. */
  288. public float nextTabStop(float x, int tabOffset) {
  289. int ntabs = ((int) x - tabBase) / tabSize;
  290. return tabBase + ((ntabs + 1) * tabSize);
  291. }
  292. // --- View methods -------------------------------------
  293. /**
  294. * Renders using the given rendering surface and area
  295. * on that surface. This is implemented to stash the
  296. * selection positions, selection colors, and font
  297. * metrics for the nested lines to use.
  298. *
  299. * @param g the rendering surface to use
  300. * @param a the allocated region to render into
  301. *
  302. * @see View#paint
  303. */
  304. public void paint(Graphics g, Shape a) {
  305. Rectangle alloc = (Rectangle) a;
  306. tabBase = alloc.x;
  307. JTextComponent host = (JTextComponent) getContainer();
  308. sel0 = host.getSelectionStart();
  309. sel1 = host.getSelectionEnd();
  310. unselected = (host.isEnabled()) ?
  311. host.getForeground() : host.getDisabledTextColor();
  312. Caret c = host.getCaret();
  313. selected = c.isSelectionVisible() ? host.getSelectedTextColor() : unselected;
  314. g.setFont(host.getFont());
  315. // superclass paints the children
  316. super.paint(g, a);
  317. }
  318. /**
  319. * Sets the size of the view. If the size has changed, layout
  320. * is redone. The size is the full size of the view including
  321. * the inset areas.
  322. *
  323. * @param width the width >= 0
  324. * @param height the height >= 0
  325. */
  326. public void setSize(float width, float height) {
  327. updateMetrics();
  328. if ((int) width != getWidth()) {
  329. // invalidate the view itself since the childrens
  330. // desired widths will be based upon this views width.
  331. preferenceChanged(null, true, true);
  332. widthChanging = true;
  333. }
  334. super.setSize(width, height);
  335. widthChanging = false;
  336. }
  337. /**
  338. * Determines the preferred span for this view along an
  339. * axis. This is implemented to provide the superclass
  340. * behavior after first making sure that the current font
  341. * metrics are cached (for the nested lines which use
  342. * the metrics to determine the height of the potentially
  343. * wrapped lines).
  344. *
  345. * @param axis may be either View.X_AXIS or View.Y_AXIS
  346. * @returns the span the view would like to be rendered into.
  347. * Typically the view is told to render into the span
  348. * that is returned, although there is no guarantee.
  349. * The parent may choose to resize or break the view.
  350. * @see View#getPreferredSpan
  351. */
  352. public float getPreferredSpan(int axis) {
  353. updateMetrics();
  354. return super.getPreferredSpan(axis);
  355. }
  356. /**
  357. * Determines the minimum span for this view along an
  358. * axis. This is implemented to provide the superclass
  359. * behavior after first making sure that the current font
  360. * metrics are cached (for the nested lines which use
  361. * the metrics to determine the height of the potentially
  362. * wrapped lines).
  363. *
  364. * @param axis may be either View.X_AXIS or View.Y_AXIS
  365. * @returns the span the view would like to be rendered into.
  366. * Typically the view is told to render into the span
  367. * that is returned, although there is no guarantee.
  368. * The parent may choose to resize or break the view.
  369. * @see View#getMinimumSpan
  370. */
  371. public float getMinimumSpan(int axis) {
  372. updateMetrics();
  373. return super.getMinimumSpan(axis);
  374. }
  375. /**
  376. * Determines the maximum span for this view along an
  377. * axis. This is implemented to provide the superclass
  378. * behavior after first making sure that the current font
  379. * metrics are cached (for the nested lines which use
  380. * the metrics to determine the height of the potentially
  381. * wrapped lines).
  382. *
  383. * @param axis may be either View.X_AXIS or View.Y_AXIS
  384. * @returns the span the view would like to be rendered into.
  385. * Typically the view is told to render into the span
  386. * that is returned, although there is no guarantee.
  387. * The parent may choose to resize or break the view.
  388. * @see View#getMaximumSpan
  389. */
  390. public float getMaximumSpan(int axis) {
  391. updateMetrics();
  392. return super.getMaximumSpan(axis);
  393. }
  394. /**
  395. * Gives notification that something was inserted into the
  396. * document in a location that this view is responsible for.
  397. * This is implemented to simply update the children.
  398. *
  399. * @param e the change information from the associated document
  400. * @param a the current allocation of the view
  401. * @param f the factory to use to rebuild if the view has children
  402. * @see View#insertUpdate
  403. */
  404. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  405. updateChildren(e, a);
  406. Rectangle alloc = ((a != null) && isAllocationValid()) ?
  407. getInsideAllocation(a) : null;
  408. int pos = e.getOffset();
  409. View v = getViewAtPosition(pos, alloc);
  410. if (v != null) {
  411. v.insertUpdate(e, alloc, f);
  412. }
  413. }
  414. /**
  415. * Gives notification that something was removed from the
  416. * document in a location that this view is responsible for.
  417. * This is implemented to simply update the children.
  418. *
  419. * @param e the change information from the associated document
  420. * @param a the current allocation of the view
  421. * @param f the factory to use to rebuild if the view has children
  422. * @see View#removeUpdate
  423. */
  424. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  425. updateChildren(e, a);
  426. Rectangle alloc = ((a != null) && isAllocationValid()) ?
  427. getInsideAllocation(a) : null;
  428. int pos = e.getOffset();
  429. View v = getViewAtPosition(pos, alloc);
  430. if (v != null) {
  431. v.removeUpdate(e, alloc, f);
  432. }
  433. }
  434. /**
  435. * Gives notification from the document that attributes were changed
  436. * in a location that this view is responsible for.
  437. *
  438. * @param e the change information from the associated document
  439. * @param a the current allocation of the view
  440. * @param f the factory to use to rebuild if the view has children
  441. * @see View#changedUpdate
  442. */
  443. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  444. updateChildren(e, a);
  445. }
  446. // --- variables -------------------------------------------
  447. FontMetrics metrics;
  448. Segment lineBuffer;
  449. boolean widthChanging;
  450. int tabBase;
  451. int tabSize;
  452. boolean wordWrap;
  453. int sel0;
  454. int sel1;
  455. Color unselected;
  456. Color selected;
  457. /**
  458. * Simple view of a line that wraps if it doesn't
  459. * fit withing the horizontal space allocated.
  460. * This class tries to be lightweight by carrying little
  461. * state of it's own and sharing the state of the outer class
  462. * with it's sibblings.
  463. */
  464. class WrappedLine extends View {
  465. WrappedLine(Element elem) {
  466. super(elem);
  467. }
  468. /**
  469. * Calculate the number of lines that will be rendered
  470. * by logical line when it is wrapped.
  471. */
  472. final int calculateLineCount() {
  473. int nlines = 0;
  474. int p1 = getEndOffset();
  475. for (int p0 = getStartOffset(); p0 < p1; ) {
  476. nlines += 1;
  477. int p = calculateBreakPosition(p0, p1);
  478. p0 = (p == p0) ? p1 : p;
  479. }
  480. return nlines;
  481. }
  482. /**
  483. * Determines the preferred span for this view along an
  484. * axis.
  485. *
  486. * @param axis may be either X_AXIS or Y_AXIS
  487. * @returns the span the view would like to be rendered into.
  488. * Typically the view is told to render into the span
  489. * that is returned, although there is no guarantee.
  490. * The parent may choose to resize or break the view.
  491. * @see View#getPreferredSpan
  492. */
  493. public float getPreferredSpan(int axis) {
  494. switch (axis) {
  495. case View.X_AXIS:
  496. return getWidth();
  497. case View.Y_AXIS:
  498. if (nlines == 0 || widthChanging) {
  499. nlines = calculateLineCount();
  500. }
  501. int h = nlines * metrics.getHeight();
  502. return h;
  503. default:
  504. throw new IllegalArgumentException("Invalid axis: " + axis);
  505. }
  506. }
  507. /**
  508. * Renders using the given rendering surface and area on that
  509. * surface. The view may need to do layout and create child
  510. * views to enable itself to render into the given allocation.
  511. *
  512. * @param g the rendering surface to use
  513. * @param a the allocated region to render into
  514. * @see View#paint
  515. */
  516. public void paint(Graphics g, Shape a) {
  517. Rectangle alloc = (Rectangle) a;
  518. int y = alloc.y + metrics.getAscent();
  519. int x = alloc.x;
  520. JTextComponent host = (JTextComponent)getContainer();
  521. Highlighter h = host.getHighlighter();
  522. LayeredHighlighter dh = (h instanceof LayeredHighlighter) ?
  523. (LayeredHighlighter)h : null;
  524. int p1 = getEndOffset();
  525. for (int p0 = getStartOffset(); p0 < p1; ) {
  526. int p = calculateBreakPosition(p0, p1);
  527. if (dh != null) {
  528. if (p == p1) {
  529. dh.paintLayeredHighlights(g, p0, p - 1, a, host, this);
  530. }
  531. else {
  532. dh.paintLayeredHighlights(g, p0, p, a, host, this);
  533. }
  534. }
  535. drawLine(p0, p, g, x, y);
  536. p0 = (p == p0) ? p1 : p;
  537. y += metrics.getHeight();
  538. }
  539. }
  540. /**
  541. * Provides a mapping from the document model coordinate space
  542. * to the coordinate space of the view mapped to it.
  543. *
  544. * @param pos the position to convert
  545. * @param a the allocated region to render into
  546. * @return the bounding box of the given position is returned
  547. * @exception BadLocationException if the given position does not represent a
  548. * valid location in the associated document
  549. * @see View#modelToView
  550. */
  551. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  552. Rectangle alloc = a.getBounds();
  553. alloc.height = metrics.getHeight();
  554. alloc.width = 1;
  555. int p1 = getEndOffset();
  556. int p0 = getStartOffset();
  557. int testP = (b == Position.Bias.Forward) ? pos :
  558. Math.max(p0, pos - 1);
  559. while (p0 < p1) {
  560. int p = calculateBreakPosition(p0, p1);
  561. if ((pos >= p0) && (testP < p)) {
  562. // it's in this line
  563. loadText(p0, pos);
  564. alloc.x += Utilities.getTabbedTextWidth(lineBuffer, metrics,
  565. alloc.x,
  566. WrappedPlainView.this, p0);
  567. return alloc;
  568. }
  569. if (p == p1 && pos == p1) {
  570. // Wants end.
  571. if (pos > p0) {
  572. loadText(p0, pos);
  573. alloc.x += Utilities.getTabbedTextWidth(lineBuffer,
  574. metrics, alloc.x,
  575. WrappedPlainView.this, p0);
  576. }
  577. return alloc;
  578. }
  579. p0 = (p == p0) ? p1 : p;
  580. alloc.y += alloc.height;
  581. }
  582. throw new BadLocationException(null, pos);
  583. }
  584. /**
  585. * Provides a mapping from the view coordinate space to the logical
  586. * coordinate space of the model.
  587. *
  588. * @param x the X coordinate
  589. * @param y the Y coordinate
  590. * @param a the allocated region to render into
  591. * @return the location within the model that best represents the
  592. * given point in the view
  593. * @see View#viewToModel
  594. */
  595. public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
  596. // PENDING(prinz) implement bias properly
  597. bias[0] = Position.Bias.Forward;
  598. Rectangle alloc = (Rectangle) a;
  599. Document doc = getDocument();
  600. int x = (int) fx;
  601. int y = (int) fy;
  602. if (y < alloc.y) {
  603. // above the area covered by this icon, so the the position
  604. // is assumed to be the start of the coverage for this view.
  605. return getStartOffset();
  606. } else if (y > alloc.y + alloc.height) {
  607. // below the area covered by this icon, so the the position
  608. // is assumed to be the end of the coverage for this view.
  609. return getEndOffset() - 1;
  610. } else {
  611. // positioned within the coverage of this view vertically,
  612. // so we figure out which line the point corresponds to.
  613. // if the line is greater than the number of lines contained, then
  614. // simply use the last line as it represents the last possible place
  615. // we can position to.
  616. alloc.height = metrics.getHeight();
  617. int p1 = getEndOffset();
  618. for (int p0 = getStartOffset(); p0 < p1; ) {
  619. int p = calculateBreakPosition(p0, p1);
  620. if ((y >= alloc.y) && (y < (alloc.y + alloc.height))) {
  621. // it's in this line
  622. if (x < alloc.x) {
  623. // point is to the left of the line
  624. return p0;
  625. } else if (x > alloc.x + alloc.width) {
  626. // point is to the right of the line
  627. return p;
  628. } else {
  629. // Determine the offset into the text
  630. int n = Utilities.getTabbedTextOffset(lineBuffer, metrics,
  631. alloc.x, x,
  632. WrappedPlainView.this, p0);
  633. return Math.min(p0 + n, p1 - 1);
  634. }
  635. }
  636. p0 = (p == p0) ? p1 : p;
  637. alloc.y += alloc.height;
  638. }
  639. return getEndOffset() - 1;
  640. }
  641. }
  642. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  643. int n = calculateLineCount();
  644. if (this.nlines != n) {
  645. this.nlines = n;
  646. WrappedPlainView.this.preferenceChanged(this, false, true);
  647. // have to repaint any views after the receiver.
  648. getContainer().repaint();
  649. }
  650. else if (a != null) {
  651. Component c = getContainer();
  652. Rectangle alloc = (Rectangle) a;
  653. c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  654. }
  655. }
  656. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  657. int n = calculateLineCount();
  658. if (this.nlines != n) {
  659. // have to repaint any views after the receiver.
  660. this.nlines = n;
  661. WrappedPlainView.this.preferenceChanged(this, false, true);
  662. getContainer().repaint();
  663. }
  664. else if (a != null) {
  665. Component c = getContainer();
  666. Rectangle alloc = (Rectangle) a;
  667. c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  668. }
  669. }
  670. // --- variables ---------------------------------------
  671. int nlines;
  672. }
  673. }