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