1. /*
  2. * @(#)GlyphView.java 1.36 04/05/26
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.awt.*;
  9. import java.text.BreakIterator;
  10. import javax.swing.event.*;
  11. import com.sun.java.swing.SwingUtilities2;
  12. /**
  13. * A GlyphView is a styled chunk of text that represents a view
  14. * mapped over an element in the text model. This view is generally
  15. * responsible for displaying text glyphs using character level
  16. * attributes in some way.
  17. * An implementation of the GlyphPainter class is used to do the
  18. * actual rendering and model/view translations. This separates
  19. * rendering from layout and management of the association with
  20. * the model.
  21. * <p>
  22. * The view supports breaking for the purpose of formatting.
  23. * The fragments produced by breaking share the view that has
  24. * primary responsibility for the element (i.e. they are nested
  25. * classes and carry only a small amount of state of their own)
  26. * so they can share its resources.
  27. * <p>
  28. * Since this view
  29. * represents text that may have tabs embedded in it, it implements the
  30. * <code>TabableView</code> interface. Tabs will only be
  31. * expanded if this view is embedded in a container that does
  32. * tab expansion. ParagraphView is an example of a container
  33. * that does tab expansion.
  34. * <p>
  35. *
  36. * @since 1.3
  37. *
  38. * @author Timothy Prinzing
  39. * @version 1.36 05/26/04
  40. */
  41. public class GlyphView extends View implements TabableView, Cloneable {
  42. /**
  43. * Constructs a new view wrapped on an element.
  44. *
  45. * @param elem the element
  46. */
  47. public GlyphView(Element elem) {
  48. super(elem);
  49. offset = 0;
  50. length = 0;
  51. Element parent = elem.getParentElement();
  52. AttributeSet attr = elem.getAttributes();
  53. // if there was an implied CR
  54. impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
  55. // if this is non-empty paragraph
  56. parent != null && parent.getElementCount() > 1);
  57. skipWidth = elem.getName().equals("br");
  58. }
  59. /**
  60. * Creates a shallow copy. This is used by the
  61. * createFragment and breakView methods.
  62. *
  63. * @return the copy
  64. */
  65. protected final Object clone() {
  66. Object o;
  67. try {
  68. o = super.clone();
  69. } catch (CloneNotSupportedException cnse) {
  70. o = null;
  71. }
  72. return o;
  73. }
  74. /**
  75. * Fetch the currently installed glyph painter.
  76. * If a painter has not yet been installed, and
  77. * a default was not yet needed, null is returned.
  78. */
  79. public GlyphPainter getGlyphPainter() {
  80. return painter;
  81. }
  82. /**
  83. * Sets the painter to use for rendering glyphs.
  84. */
  85. public void setGlyphPainter(GlyphPainter p) {
  86. painter = p;
  87. }
  88. /**
  89. * Fetch a reference to the text that occupies
  90. * the given range. This is normally used by
  91. * the GlyphPainter to determine what characters
  92. * it should render glyphs for.
  93. *
  94. * @param p0 the starting document offset >= 0
  95. * @param p1 the ending document offset >= p0
  96. * @return the <code>Segment</code> containing the text
  97. */
  98. public Segment getText(int p0, int p1) {
  99. // When done with the returned Segment it should be released by
  100. // invoking:
  101. // SegmentCache.releaseSharedSegment(segment);
  102. Segment text = SegmentCache.getSharedSegment();
  103. try {
  104. Document doc = getDocument();
  105. doc.getText(p0, p1 - p0, text);
  106. } catch (BadLocationException bl) {
  107. throw new StateInvariantError("GlyphView: Stale view: " + bl);
  108. }
  109. return text;
  110. }
  111. /**
  112. * Fetch the background color to use to render the
  113. * glyphs. If there is no background color, null should
  114. * be returned. This is implemented to call
  115. * <code>StyledDocument.getBackground</code> if the associated
  116. * document is a styled document, otherwise it returns null.
  117. */
  118. public Color getBackground() {
  119. Document doc = getDocument();
  120. if (doc instanceof StyledDocument) {
  121. AttributeSet attr = getAttributes();
  122. if (attr.isDefined(StyleConstants.Background)) {
  123. return ((StyledDocument)doc).getBackground(attr);
  124. }
  125. }
  126. return null;
  127. }
  128. /**
  129. * Fetch the foreground color to use to render the
  130. * glyphs. If there is no foreground color, null should
  131. * be returned. This is implemented to call
  132. * <code>StyledDocument.getBackground</code> if the associated
  133. * document is a StyledDocument. If the associated document
  134. * is not a StyledDocument, the associated components foreground
  135. * color is used. If there is no associated component, null
  136. * is returned.
  137. */
  138. public Color getForeground() {
  139. Document doc = getDocument();
  140. if (doc instanceof StyledDocument) {
  141. AttributeSet attr = getAttributes();
  142. return ((StyledDocument)doc).getForeground(attr);
  143. }
  144. Component c = getContainer();
  145. if (c != null) {
  146. return c.getForeground();
  147. }
  148. return null;
  149. }
  150. /**
  151. * Fetch the font that the glyphs should be based
  152. * upon. This is implemented to call
  153. * <code>StyledDocument.getFont</code> if the associated
  154. * document is a StyledDocument. If the associated document
  155. * is not a StyledDocument, the associated components font
  156. * is used. If there is no associated component, null
  157. * is returned.
  158. */
  159. public Font getFont() {
  160. Document doc = getDocument();
  161. if (doc instanceof StyledDocument) {
  162. AttributeSet attr = getAttributes();
  163. return ((StyledDocument)doc).getFont(attr);
  164. }
  165. Component c = getContainer();
  166. if (c != null) {
  167. return c.getFont();
  168. }
  169. return null;
  170. }
  171. /**
  172. * Determine if the glyphs should be underlined. If true,
  173. * an underline should be drawn through the baseline.
  174. */
  175. public boolean isUnderline() {
  176. AttributeSet attr = getAttributes();
  177. return StyleConstants.isUnderline(attr);
  178. }
  179. /**
  180. * Determine if the glyphs should have a strikethrough
  181. * line. If true, a line should be drawn through the center
  182. * of the glyphs.
  183. */
  184. public boolean isStrikeThrough() {
  185. AttributeSet attr = getAttributes();
  186. return StyleConstants.isStrikeThrough(attr);
  187. }
  188. /**
  189. * Determine if the glyphs should be rendered as superscript.
  190. */
  191. public boolean isSubscript() {
  192. AttributeSet attr = getAttributes();
  193. return StyleConstants.isSubscript(attr);
  194. }
  195. /**
  196. * Determine if the glyphs should be rendered as subscript.
  197. */
  198. public boolean isSuperscript() {
  199. AttributeSet attr = getAttributes();
  200. return StyleConstants.isSuperscript(attr);
  201. }
  202. /**
  203. * Fetch the TabExpander to use if tabs are present in this view.
  204. */
  205. public TabExpander getTabExpander() {
  206. return expander;
  207. }
  208. /**
  209. * Check to see that a glyph painter exists. If a painter
  210. * doesn't exist, a default glyph painter will be installed.
  211. */
  212. protected void checkPainter() {
  213. if (painter == null) {
  214. if (defaultPainter == null) {
  215. // the classname should probably come from a property file.
  216. String classname = "javax.swing.text.GlyphPainter1";
  217. try {
  218. Class c;
  219. ClassLoader loader = getClass().getClassLoader();
  220. if (loader != null) {
  221. c = loader.loadClass(classname);
  222. } else {
  223. c = Class.forName(classname);
  224. }
  225. Object o = c.newInstance();
  226. if (o instanceof GlyphPainter) {
  227. defaultPainter = (GlyphPainter) o;
  228. }
  229. } catch (Throwable e) {
  230. throw new StateInvariantError("GlyphView: Can't load glyph painter: "
  231. + classname);
  232. }
  233. }
  234. setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
  235. getEndOffset()));
  236. }
  237. }
  238. // --- TabableView methods --------------------------------------
  239. /**
  240. * Determines the desired span when using the given
  241. * tab expansion implementation.
  242. *
  243. * @param x the position the view would be located
  244. * at for the purpose of tab expansion >= 0.
  245. * @param e how to expand the tabs when encountered.
  246. * @return the desired span >= 0
  247. * @see TabableView#getTabbedSpan
  248. */
  249. public float getTabbedSpan(float x, TabExpander e) {
  250. checkPainter();
  251. TabExpander old = expander;
  252. expander = e;
  253. if (expander != old) {
  254. // setting expander can change horizontal span of the view,
  255. // so we have to call preferenceChanged()
  256. preferenceChanged(null, true, false);
  257. }
  258. this.x = (int) x;
  259. int p0 = getStartOffset();
  260. int p1 = getEndOffset();
  261. float width = painter.getSpan(this, p0, p1, expander, x);
  262. return width;
  263. }
  264. /**
  265. * Determines the span along the same axis as tab
  266. * expansion for a portion of the view. This is
  267. * intended for use by the TabExpander for cases
  268. * where the tab expansion involves aligning the
  269. * portion of text that doesn't have whitespace
  270. * relative to the tab stop. There is therefore
  271. * an assumption that the range given does not
  272. * contain tabs.
  273. * <p>
  274. * This method can be called while servicing the
  275. * getTabbedSpan or getPreferredSize. It has to
  276. * arrange for its own text buffer to make the
  277. * measurements.
  278. *
  279. * @param p0 the starting document offset >= 0
  280. * @param p1 the ending document offset >= p0
  281. * @return the span >= 0
  282. */
  283. public float getPartialSpan(int p0, int p1) {
  284. checkPainter();
  285. float width = painter.getSpan(this, p0, p1, expander, x);
  286. return width;
  287. }
  288. // --- View methods ---------------------------------------------
  289. /**
  290. * Fetches the portion of the model that this view is responsible for.
  291. *
  292. * @return the starting offset into the model
  293. * @see View#getStartOffset
  294. */
  295. public int getStartOffset() {
  296. Element e = getElement();
  297. return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
  298. }
  299. /**
  300. * Fetches the portion of the model that this view is responsible for.
  301. *
  302. * @return the ending offset into the model
  303. * @see View#getEndOffset
  304. */
  305. public int getEndOffset() {
  306. Element e = getElement();
  307. return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
  308. }
  309. /**
  310. * Lazily initializes the selections field
  311. */
  312. private void initSelections(int p0, int p1) {
  313. int viewPosCount = p1 - p0 + 1;
  314. if (selections == null || viewPosCount > selections.length) {
  315. selections = new byte[viewPosCount];
  316. return;
  317. }
  318. for (int i = 0; i < viewPosCount; selections[i++] = 0);
  319. }
  320. /**
  321. * Renders a portion of a text style run.
  322. *
  323. * @param g the rendering surface to use
  324. * @param a the allocated region to render into
  325. */
  326. public void paint(Graphics g, Shape a) {
  327. checkPainter();
  328. boolean paintedText = false;
  329. Component c = getContainer();
  330. int p0 = getStartOffset();
  331. int p1 = getEndOffset();
  332. Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  333. Color bg = getBackground();
  334. Color fg = getForeground();
  335. if (c instanceof JTextComponent) {
  336. JTextComponent tc = (JTextComponent) c;
  337. if (!tc.isEnabled()) {
  338. fg = tc.getDisabledTextColor();
  339. }
  340. }
  341. if (bg != null) {
  342. g.setColor(bg);
  343. g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
  344. }
  345. if (c instanceof JTextComponent) {
  346. JTextComponent tc = (JTextComponent) c;
  347. Highlighter h = tc.getHighlighter();
  348. if (h instanceof LayeredHighlighter) {
  349. ((LayeredHighlighter)h).paintLayeredHighlights
  350. (g, p0, p1, a, tc, this);
  351. }
  352. }
  353. if (Utilities.isComposedTextElement(getElement())) {
  354. Utilities.paintComposedText(g, a.getBounds(), this);
  355. paintedText = true;
  356. } else if(c instanceof JTextComponent) {
  357. JTextComponent tc = (JTextComponent) c;
  358. Color selFG = tc.getSelectedTextColor();
  359. if (// there's a highlighter (bug 4532590), and
  360. (tc.getHighlighter() != null) &&
  361. // selected text color is different from regular foreground
  362. (selFG != null) && !selFG.equals(fg)) {
  363. Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
  364. if(h.length != 0) {
  365. boolean initialized = false;
  366. int viewSelectionCount = 0;
  367. for (int i = 0; i < h.length; i++) {
  368. Highlighter.Highlight highlight = h[i];
  369. int hStart = highlight.getStartOffset();
  370. int hEnd = highlight.getEndOffset();
  371. if (hStart > p1 || hEnd < p0) {
  372. // the selection is out of this view
  373. continue;
  374. }
  375. if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
  376. continue;
  377. }
  378. if (hStart <= p0 && hEnd >= p1){
  379. // the whole view is selected
  380. paintTextUsingColor(g, a, selFG, p0, p1);
  381. paintedText = true;
  382. break;
  383. }
  384. // the array is lazily created only when the view
  385. // is partially selected
  386. if (!initialized) {
  387. initSelections(p0, p1);
  388. initialized = true;
  389. }
  390. hStart = Math.max(p0, hStart);
  391. hEnd = Math.min(p1, hEnd);
  392. paintTextUsingColor(g, a, selFG, hStart, hEnd);
  393. // the array represents view positions [0, p1-p0+1]
  394. // later will iterate this array and sum its
  395. // elements. Positions with sum == 0 are not selected.
  396. selections[hStart-p0]++;
  397. selections[hEnd-p0]--;
  398. viewSelectionCount++;
  399. }
  400. if (!paintedText && viewSelectionCount > 0) {
  401. // the view is partially selected
  402. int curPos = -1;
  403. int startPos = 0;
  404. int viewLen = p1 - p0;
  405. while (curPos++ < viewLen) {
  406. // searching for the next selection start
  407. while(curPos < viewLen &&
  408. selections[curPos] == 0) curPos++;
  409. if (startPos != curPos) {
  410. // paint unselected text
  411. paintTextUsingColor(g, a, fg,
  412. p0 + startPos, p0 + curPos);
  413. }
  414. int checkSum = 0;
  415. // searching for next start position of unselected text
  416. while (curPos < viewLen &&
  417. (checkSum += selections[curPos]) != 0) curPos++;
  418. startPos = curPos;
  419. }
  420. paintedText = true;
  421. }
  422. }
  423. }
  424. }
  425. if(!paintedText)
  426. paintTextUsingColor(g, a, fg, p0, p1);
  427. }
  428. /**
  429. * Paints the specified region of text in the specified color.
  430. */
  431. final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
  432. // render the glyphs
  433. g.setColor(c);
  434. painter.paint(this, g, a, p0, p1);
  435. // render underline or strikethrough if set.
  436. boolean underline = isUnderline();
  437. boolean strike = isStrikeThrough();
  438. if (underline || strike) {
  439. // calculate x coordinates
  440. Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
  441. View parent = getParent();
  442. if ((parent != null) && (parent.getEndOffset() == p1)) {
  443. // strip whitespace on end
  444. Segment s = getText(p0, p1);
  445. while ((s.count > 0) && (Character.isWhitespace(s.array[s.count-1]))) {
  446. p1 -= 1;
  447. s.count -= 1;
  448. }
  449. SegmentCache.releaseSharedSegment(s);
  450. }
  451. int x0 = alloc.x;
  452. int p = getStartOffset();
  453. if (p != p0) {
  454. x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
  455. }
  456. int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
  457. // calculate y coordinate
  458. int d = (int) painter.getDescent(this);
  459. int y = alloc.y + alloc.height - (int) painter.getDescent(this);
  460. if (underline) {
  461. int yTmp = y;
  462. yTmp += 1;
  463. g.drawLine(x0, yTmp, x1, yTmp);
  464. }
  465. if (strike) {
  466. int yTmp = y;
  467. // move y coordinate above baseline
  468. yTmp -= (int) (painter.getAscent(this) * 0.3f);
  469. g.drawLine(x0, yTmp, x1, yTmp);
  470. }
  471. }
  472. }
  473. /**
  474. * Determines the preferred span for this view along an
  475. * axis.
  476. *
  477. * @param axis may be either View.X_AXIS or View.Y_AXIS
  478. * @return the span the view would like to be rendered into >= 0.
  479. * Typically the view is told to render into the span
  480. * that is returned, although there is no guarantee.
  481. * The parent may choose to resize or break the view.
  482. */
  483. public float getPreferredSpan(int axis) {
  484. if (impliedCR) {
  485. return 0;
  486. }
  487. checkPainter();
  488. int p0 = getStartOffset();
  489. int p1 = getEndOffset();
  490. switch (axis) {
  491. case View.X_AXIS:
  492. if (skipWidth) {
  493. return 0;
  494. }
  495. float width = painter.getSpan(this, p0, p1, expander, this.x);
  496. return Math.max(width, 1);
  497. case View.Y_AXIS:
  498. float h = painter.getHeight(this);
  499. if (isSuperscript()) {
  500. h += h3;
  501. }
  502. return h;
  503. default:
  504. throw new IllegalArgumentException("Invalid axis: " + axis);
  505. }
  506. }
  507. /**
  508. * Determines the desired alignment for this view along an
  509. * axis. For the label, the alignment is along the font
  510. * baseline for the y axis, and the superclasses alignment
  511. * along the x axis.
  512. *
  513. * @param axis may be either View.X_AXIS or View.Y_AXIS
  514. * @return the desired alignment. This should be a value
  515. * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  516. * origin and 1.0 indicates alignment to the full span
  517. * away from the origin. An alignment of 0.5 would be the
  518. * center of the view.
  519. */
  520. public float getAlignment(int axis) {
  521. checkPainter();
  522. if (axis == View.Y_AXIS) {
  523. boolean sup = isSuperscript();
  524. boolean sub = isSubscript();
  525. float h = painter.getHeight(this);
  526. float d = painter.getDescent(this);
  527. float a = painter.getAscent(this);
  528. float align;
  529. if (sup) {
  530. align = 1.0f;
  531. } else if (sub) {
  532. align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
  533. } else {
  534. align = (h > 0) ? (h - d) / h : 0;
  535. }
  536. return align;
  537. }
  538. return super.getAlignment(axis);
  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 >= 0
  545. * @param a the allocated region to render into
  546. * @param b either <code>Position.Bias.Forward</code>
  547. * or <code>Position.Bias.Backward</code>
  548. * @return the bounding box of the given position
  549. * @exception BadLocationException if the given position does not represent a
  550. * valid location in the associated document
  551. * @see View#modelToView
  552. */
  553. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  554. checkPainter();
  555. return painter.modelToView(this, pos, b, a);
  556. }
  557. /**
  558. * Provides a mapping from the view coordinate space to the logical
  559. * coordinate space of the model.
  560. *
  561. * @param x the X coordinate >= 0
  562. * @param y the Y coordinate >= 0
  563. * @param a the allocated region to render into
  564. * @param biasReturn either <code>Position.Bias.Forward</code>
  565. * or <code>Position.Bias.Backward</code> is returned as the
  566. * zero-th element of this array
  567. * @return the location within the model that best represents the
  568. * given point of view >= 0
  569. * @see View#viewToModel
  570. */
  571. public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
  572. checkPainter();
  573. return painter.viewToModel(this, x, y, a, biasReturn);
  574. }
  575. /**
  576. * Determines how attractive a break opportunity in
  577. * this view is. This can be used for determining which
  578. * view is the most attractive to call <code>breakView</code>
  579. * on in the process of formatting. The
  580. * higher the weight, the more attractive the break. A
  581. * value equal to or lower than <code>View.BadBreakWeight</code>
  582. * should not be considered for a break. A value greater
  583. * than or equal to <code>View.ForcedBreakWeight</code> should
  584. * be broken.
  585. * <p>
  586. * This is implemented to forward to the superclass for
  587. * the Y_AXIS. Along the X_AXIS the following values
  588. * may be returned.
  589. * <dl>
  590. * <dt><b>View.ExcellentBreakWeight</b>
  591. * <dd>if there is whitespace proceeding the desired break
  592. * location.
  593. * <dt><b>View.BadBreakWeight</b>
  594. * <dd>if the desired break location results in a break
  595. * location of the starting offset.
  596. * <dt><b>View.GoodBreakWeight</b>
  597. * <dd>if the other conditions don't occur.
  598. * </dl>
  599. * This will normally result in the behavior of breaking
  600. * on a whitespace location if one can be found, otherwise
  601. * breaking between characters.
  602. *
  603. * @param axis may be either View.X_AXIS or View.Y_AXIS
  604. * @param pos the potential location of the start of the
  605. * broken view >= 0. This may be useful for calculating tab
  606. * positions.
  607. * @param len specifies the relative length from <em>pos</em>
  608. * where a potential break is desired >= 0.
  609. * @return the weight, which should be a value between
  610. * View.ForcedBreakWeight and View.BadBreakWeight.
  611. * @see LabelView
  612. * @see ParagraphView
  613. * @see View#BadBreakWeight
  614. * @see View#GoodBreakWeight
  615. * @see View#ExcellentBreakWeight
  616. * @see View#ForcedBreakWeight
  617. */
  618. public int getBreakWeight(int axis, float pos, float len) {
  619. if (axis == View.X_AXIS) {
  620. checkPainter();
  621. int p0 = getStartOffset();
  622. int p1 = painter.getBoundedPosition(this, p0, pos, len);
  623. if (p1 == p0) {
  624. // can't even fit a single character
  625. return View.BadBreakWeight;
  626. }
  627. if (getBreakSpot(p0, p1) != -1) {
  628. return View.ExcellentBreakWeight;
  629. }
  630. // Nothing good to break on.
  631. return View.GoodBreakWeight;
  632. }
  633. return super.getBreakWeight(axis, pos, len);
  634. }
  635. /**
  636. * Breaks this view on the given axis at the given length.
  637. * This is implemented to attempt to break on a whitespace
  638. * location, and returns a fragment with the whitespace at
  639. * the end. If a whitespace location can't be found, the
  640. * nearest character is used.
  641. *
  642. * @param axis may be either View.X_AXIS or View.Y_AXIS
  643. * @param p0 the location in the model where the
  644. * fragment should start it's representation >= 0.
  645. * @param pos the position along the axis that the
  646. * broken view would occupy >= 0. This may be useful for
  647. * things like tab calculations.
  648. * @param len specifies the distance along the axis
  649. * where a potential break is desired >= 0.
  650. * @return the fragment of the view that represents the
  651. * given span, if the view can be broken. If the view
  652. * doesn't support breaking behavior, the view itself is
  653. * returned.
  654. * @see View#breakView
  655. */
  656. public View breakView(int axis, int p0, float pos, float len) {
  657. if (axis == View.X_AXIS) {
  658. checkPainter();
  659. int p1 = painter.getBoundedPosition(this, p0, pos, len);
  660. int breakSpot = getBreakSpot(p0, p1);
  661. if (breakSpot != -1) {
  662. p1 = breakSpot;
  663. }
  664. // else, no break in the region, return a fragment of the
  665. // bounded region.
  666. if (p0 == getStartOffset() && p1 == getEndOffset()) {
  667. return this;
  668. }
  669. GlyphView v = (GlyphView) createFragment(p0, p1);
  670. v.x = (int) pos;
  671. return v;
  672. }
  673. return this;
  674. }
  675. /**
  676. * Returns a location to break at in the passed in region, or -1 if
  677. * there isn't a good location to break at in the specified region.
  678. */
  679. private int getBreakSpot(int p0, int p1) {
  680. Document doc = getDocument();
  681. if (doc != null && Boolean.TRUE.equals(doc.getProperty(
  682. AbstractDocument.MultiByteProperty))) {
  683. return getBreakSpotUseBreakIterator(p0, p1);
  684. }
  685. return getBreakSpotUseWhitespace(p0, p1);
  686. }
  687. /**
  688. * Returns the appropriate place to break based on the last whitespace
  689. * character encountered.
  690. */
  691. private int getBreakSpotUseWhitespace(int p0, int p1) {
  692. Segment s = getText(p0, p1);
  693. for (char ch = s.last(); ch != Segment.DONE; ch = s.previous()) {
  694. if (Character.isWhitespace(ch)) {
  695. // found whitespace
  696. SegmentCache.releaseSharedSegment(s);
  697. return s.getIndex() - s.getBeginIndex() + 1 + p0;
  698. }
  699. }
  700. SegmentCache.releaseSharedSegment(s);
  701. return -1;
  702. }
  703. /**
  704. * Returns the appropriate place to break based on BreakIterator.
  705. */
  706. private int getBreakSpotUseBreakIterator(int p0, int p1) {
  707. // Certain regions require context for BreakIterator, start from
  708. // our parents start offset.
  709. Element parent = getElement().getParentElement();
  710. int parent0;
  711. int parent1;
  712. Container c = getContainer();
  713. BreakIterator breaker;
  714. if (parent == null) {
  715. parent0 = p0;
  716. parent1 = p1;
  717. }
  718. else {
  719. parent0 = parent.getStartOffset();
  720. parent1 = parent.getEndOffset();
  721. }
  722. if (c != null) {
  723. breaker = BreakIterator.getLineInstance(c.getLocale());
  724. }
  725. else {
  726. breaker = BreakIterator.getLineInstance();
  727. }
  728. Segment s = getText(parent0, parent1);
  729. int breakPoint;
  730. // Needed to initialize the Segment.
  731. s.first();
  732. breaker.setText(s);
  733. if (p1 == parent1) {
  734. // This will most likely return the end, the assumption is
  735. // that if parent1 == p1, then we are the last portion of
  736. // a paragraph
  737. breakPoint = breaker.last();
  738. }
  739. else if (p1 + 1 == parent1) {
  740. // assert(s.count > 1)
  741. breakPoint = breaker.next(s.offset + s.count - 2);
  742. if (breakPoint >= s.count + s.offset) {
  743. breakPoint = breaker.preceding(s.offset + s.count - 1);
  744. }
  745. }
  746. else {
  747. breakPoint = breaker.preceding(p1 - parent0 + s.offset + 1);
  748. }
  749. int retValue = -1;
  750. if (breakPoint != BreakIterator.DONE) {
  751. breakPoint = breakPoint - s.offset + parent0;
  752. if (breakPoint > p0) {
  753. if (p0 == parent0 && breakPoint == p0) {
  754. retValue = -1;
  755. }
  756. else if (breakPoint <= p1) {
  757. retValue = breakPoint;
  758. }
  759. }
  760. }
  761. SegmentCache.releaseSharedSegment(s);
  762. return retValue;
  763. }
  764. /**
  765. * Creates a view that represents a portion of the element.
  766. * This is potentially useful during formatting operations
  767. * for taking measurements of fragments of the view. If
  768. * the view doesn't support fragmenting (the default), it
  769. * should return itself.
  770. * <p>
  771. * This view does support fragmenting. It is implemented
  772. * to return a nested class that shares state in this view
  773. * representing only a portion of the view.
  774. *
  775. * @param p0 the starting offset >= 0. This should be a value
  776. * greater or equal to the element starting offset and
  777. * less than the element ending offset.
  778. * @param p1 the ending offset > p0. This should be a value
  779. * less than or equal to the elements end offset and
  780. * greater than the elements starting offset.
  781. * @return the view fragment, or itself if the view doesn't
  782. * support breaking into fragments
  783. * @see LabelView
  784. */
  785. public View createFragment(int p0, int p1) {
  786. checkPainter();
  787. Element elem = getElement();
  788. GlyphView v = (GlyphView) clone();
  789. v.offset = p0 - elem.getStartOffset();
  790. v.length = p1 - p0;
  791. v.painter = painter.getPainter(v, p0, p1);
  792. return v;
  793. }
  794. /**
  795. * Provides a way to determine the next visually represented model
  796. * location that one might place a caret. Some views may not be
  797. * visible, they might not be in the same order found in the model, or
  798. * they just might not allow access to some of the locations in the
  799. * model.
  800. *
  801. * @param pos the position to convert >= 0
  802. * @param a the allocated region to render into
  803. * @param direction the direction from the current position that can
  804. * be thought of as the arrow keys typically found on a keyboard.
  805. * This may be SwingConstants.WEST, SwingConstants.EAST,
  806. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  807. * @return the location within the model that best represents the next
  808. * location visual position.
  809. * @exception BadLocationException
  810. * @exception IllegalArgumentException for an invalid direction
  811. */
  812. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  813. int direction,
  814. Position.Bias[] biasRet)
  815. throws BadLocationException {
  816. return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
  817. }
  818. /**
  819. * Gives notification that something was inserted into
  820. * the document in a location that this view is responsible for.
  821. * This is implemented to call preferenceChanged along the
  822. * axis the glyphs are rendered.
  823. *
  824. * @param e the change information from the associated document
  825. * @param a the current allocation of the view
  826. * @param f the factory to use to rebuild if the view has children
  827. * @see View#insertUpdate
  828. */
  829. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  830. syncCR();
  831. preferenceChanged(null, true, false);
  832. }
  833. /**
  834. * Gives notification that something was removed from the document
  835. * in a location that this view is responsible for.
  836. * This is implemented to call preferenceChanged along the
  837. * axis the glyphs are rendered.
  838. *
  839. * @param e the change information from the associated document
  840. * @param a the current allocation of the view
  841. * @param f the factory to use to rebuild if the view has children
  842. * @see View#removeUpdate
  843. */
  844. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  845. syncCR();
  846. preferenceChanged(null, true, false);
  847. }
  848. /**
  849. * Gives notification from the document that attributes were changed
  850. * in a location that this view is responsible for.
  851. * This is implemented to call preferenceChanged along both the
  852. * horizontal and vertical axis.
  853. *
  854. * @param e the change information from the associated document
  855. * @param a the current allocation of the view
  856. * @param f the factory to use to rebuild if the view has children
  857. * @see View#changedUpdate
  858. */
  859. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  860. syncCR();
  861. preferenceChanged(null, true, true);
  862. }
  863. // checks if the paragraph is empty and updates impliedCR flag
  864. // accordingly
  865. private void syncCR() {
  866. if (impliedCR) {
  867. Element parent = getElement().getParentElement();
  868. impliedCR = (parent != null && parent.getElementCount() > 1);
  869. }
  870. }
  871. // --- variables ------------------------------------------------
  872. /**
  873. * Used by paint() to store highlighted view positions
  874. */
  875. private byte[] selections = null;
  876. int offset;
  877. int length;
  878. // if it is an implied newline character
  879. boolean impliedCR;
  880. private static final String IMPLIED_CR = "CR";
  881. boolean skipWidth;
  882. /**
  883. * how to expand tabs
  884. */
  885. TabExpander expander;
  886. /**
  887. * location for determining tab expansion against.
  888. */
  889. int x;
  890. /**
  891. * Glyph rendering functionality.
  892. */
  893. GlyphPainter painter;
  894. /**
  895. * The prototype painter used by default.
  896. */
  897. static GlyphPainter defaultPainter;
  898. /**
  899. * A class to perform rendering of the glyphs.
  900. * This can be implemented to be stateless, or
  901. * to hold some information as a cache to
  902. * facilitate faster rendering and model/view
  903. * translation. At a minimum, the GlyphPainter
  904. * allows a View implementation to perform its
  905. * duties independant of a particular version
  906. * of JVM and selection of capabilities (i.e.
  907. * shaping for i18n, etc).
  908. *
  909. * @since 1.3
  910. */
  911. public static abstract class GlyphPainter {
  912. /**
  913. * Determine the span the glyphs given a start location
  914. * (for tab expansion).
  915. */
  916. public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
  917. public abstract float getHeight(GlyphView v);
  918. public abstract float getAscent(GlyphView v);
  919. public abstract float getDescent(GlyphView v);
  920. /**
  921. * Paint the glyphs representing the given range.
  922. */
  923. public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
  924. /**
  925. * Provides a mapping from the document model coordinate space
  926. * to the coordinate space of the view mapped to it.
  927. * This is shared by the broken views.
  928. *
  929. * @param v the <code>GlyphView</code> containing the
  930. * destination coordinate space
  931. * @param pos the position to convert
  932. * @param bias either <code>Position.Bias.Forward</code>
  933. * or <code>Position.Bias.Backward</code>
  934. * @param a Bounds of the View
  935. * @return the bounding box of the given position
  936. * @exception BadLocationException if the given position does not represent a
  937. * valid location in the associated document
  938. * @see View#modelToView
  939. */
  940. public abstract Shape modelToView(GlyphView v,
  941. int pos, Position.Bias bias,
  942. Shape a) throws BadLocationException;
  943. /**
  944. * Provides a mapping from the view coordinate space to the logical
  945. * coordinate space of the model.
  946. *
  947. * @param v the <code>GlyphView</code> to provide a mapping for
  948. * @param x the X coordinate
  949. * @param y the Y coordinate
  950. * @param a the allocated region to render into
  951. * @param biasReturn either <code>Position.Bias.Forward</code>
  952. * or <code>Position.Bias.Backward</code>
  953. * is returned as the zero-th element of this array
  954. * @return the location within the model that best represents the
  955. * given point of view
  956. * @see View#viewToModel
  957. */
  958. public abstract int viewToModel(GlyphView v,
  959. float x, float y, Shape a,
  960. Position.Bias[] biasReturn);
  961. /**
  962. * Determines the model location that represents the
  963. * maximum advance that fits within the given span.
  964. * This could be used to break the given view. The result
  965. * should be a location just shy of the given advance. This
  966. * differs from viewToModel which returns the closest
  967. * position which might be proud of the maximum advance.
  968. *
  969. * @param v the view to find the model location to break at.
  970. * @param p0 the location in the model where the
  971. * fragment should start it's representation >= 0.
  972. * @param x the graphic location along the axis that the
  973. * broken view would occupy >= 0. This may be useful for
  974. * things like tab calculations.
  975. * @param len specifies the distance into the view
  976. * where a potential break is desired >= 0.
  977. * @return the maximum model location possible for a break.
  978. * @see View#breakView
  979. */
  980. public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
  981. /**
  982. * Create a painter to use for the given GlyphView. If
  983. * the painter carries state it can create another painter
  984. * to represent a new GlyphView that is being created. If
  985. * the painter doesn't hold any significant state, it can
  986. * return itself. The default behavior is to return itself.
  987. * @param v the <code>GlyphView</code> to provide a painter for
  988. * @param p0 the starting document offset >= 0
  989. * @param p1 the ending document offset >= p0
  990. */
  991. public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
  992. return this;
  993. }
  994. /**
  995. * Provides a way to determine the next visually represented model
  996. * location that one might place a caret. Some views may not be
  997. * visible, they might not be in the same order found in the model, or
  998. * they just might not allow access to some of the locations in the
  999. * model.
  1000. *
  1001. * @param v the view to use
  1002. * @param pos the position to convert >= 0
  1003. * @param b either <code>Position.Bias.Forward</code>
  1004. * or <code>Position.Bias.Backward</code>
  1005. * @param a the allocated region to render into
  1006. * @param direction the direction from the current position that can
  1007. * be thought of as the arrow keys typically found on a keyboard.
  1008. * This may be SwingConstants.WEST, SwingConstants.EAST,
  1009. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  1010. * @param biasRet either <code>Position.Bias.Forward</code>
  1011. * or <code>Position.Bias.Backward</code>
  1012. * is returned as the zero-th element of this array
  1013. * @return the location within the model that best represents the next
  1014. * location visual position.
  1015. * @exception BadLocationException
  1016. * @exception IllegalArgumentException for an invalid direction
  1017. */
  1018. public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
  1019. int direction,
  1020. Position.Bias[] biasRet)
  1021. throws BadLocationException {
  1022. int startOffset = v.getStartOffset();
  1023. int endOffset = v.getEndOffset();
  1024. Segment text;
  1025. switch (direction) {
  1026. case View.NORTH:
  1027. case View.SOUTH:
  1028. if (pos != -1) {
  1029. // Presumably pos is between startOffset and endOffset,
  1030. // since GlyphView is only one line, we won't contain
  1031. // the position to the nort/south, therefore return -1.
  1032. return -1;
  1033. }
  1034. Container container = v.getContainer();
  1035. if (container instanceof JTextComponent) {
  1036. Caret c = ((JTextComponent)container).getCaret();
  1037. Point magicPoint;
  1038. magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
  1039. if (magicPoint == null) {
  1040. biasRet[0] = Position.Bias.Forward;
  1041. return startOffset;
  1042. }
  1043. int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
  1044. return value;
  1045. }
  1046. break;
  1047. case View.EAST:
  1048. if(startOffset == v.getDocument().getLength()) {
  1049. if(pos == -1) {
  1050. biasRet[0] = Position.Bias.Forward;
  1051. return startOffset;
  1052. }
  1053. // End case for bidi text where newline is at beginning
  1054. // of line.
  1055. return -1;
  1056. }
  1057. if(pos == -1) {
  1058. biasRet[0] = Position.Bias.Forward;
  1059. return startOffset;
  1060. }
  1061. if(pos == endOffset) {
  1062. return -1;
  1063. }
  1064. if(++pos == endOffset) {
  1065. // Assumed not used in bidi text, GlyphPainter2 will
  1066. // override as necessary, therefore return -1.
  1067. return -1;
  1068. }
  1069. else {
  1070. biasRet[0] = Position.Bias.Forward;
  1071. }
  1072. return pos;
  1073. case View.WEST:
  1074. if(startOffset == v.getDocument().getLength()) {
  1075. if(pos == -1) {
  1076. biasRet[0] = Position.Bias.Forward;
  1077. return startOffset;
  1078. }
  1079. // End case for bidi text where newline is at beginning
  1080. // of line.
  1081. return -1;
  1082. }
  1083. if(pos == -1) {
  1084. // Assumed not used in bidi text, GlyphPainter2 will
  1085. // override as necessary, therefore return -1.
  1086. biasRet[0] = Position.Bias.Forward;
  1087. return endOffset - 1;
  1088. }
  1089. if(pos == startOffset) {
  1090. return -1;
  1091. }
  1092. biasRet[0] = Position.Bias.Forward;
  1093. return (pos - 1);
  1094. default:
  1095. throw new IllegalArgumentException("Bad direction: " + direction);
  1096. }
  1097. return pos;
  1098. }
  1099. }
  1100. }