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