1. /*
  2. * @(#)LabelView.java 1.15 01/11/29
  3. *
  4. * Copyright 2002 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 javax.swing.event.*;
  10. import javax.swing.text.AbstractDocument.BidiElement;
  11. import java.awt.geom.*;
  12. import java.awt.font.*;
  13. import java.text.*;
  14. /**
  15. * A LabelView is a styled chunk of text that represents a view
  16. * mapped over an element in the text model. The view supports breaking
  17. * for the purpose of formatting. The fragments produced
  18. * by breaking share the view that has primary responsibility
  19. * for the element (i.e. they are nested classes and carry only
  20. * a small amount of state of their own) so they can share its
  21. * resources.
  22. * <p>
  23. * This view is generally responsible for displaying character
  24. * level attributes in some way. Since this view represents
  25. * text that may have tabs embedded in it, it implements the
  26. * <code>TabableView</code> interface. Tabs will only be
  27. * expanded if this view is embedded in a container that does
  28. * tab expansion. ParagraphView is an example of a container
  29. * that does tab expansion.
  30. *
  31. * @author Timothy Prinzing
  32. * @author Brian Beck
  33. * @version 1.15 11/29/01
  34. */
  35. public class LabelView extends View /*implements TabableView */{
  36. /**
  37. * Instance of FontRenderContext that is used when a LabelFragment is
  38. * created and there is no associated JTextComponent, or the graphics
  39. * is null.
  40. */
  41. private static final FontRenderContext DefaultRenderContext =
  42. new FontRenderContext
  43. (new AffineTransform(), false, false);
  44. /**
  45. * Constructs a new view wrapped on an element.
  46. *
  47. * @param elem the element
  48. */
  49. public LabelView(Element elem) {
  50. super(elem);
  51. text = new Segment();
  52. }
  53. /**
  54. * Load the text buffer with the given range of text. This is used by
  55. * the fragments broken off of this view as well as this view itself.
  56. */
  57. final void loadText(int p0, int p1) {
  58. try {
  59. Document doc = getDocument();
  60. doc.getText(p0, p1 - p0, text);
  61. } catch (BadLocationException bl) {
  62. throw new StateInvariantError("LabelView: Stale view: " + bl);
  63. }
  64. }
  65. /**
  66. * Synchronize the view's cached properties with the model. This causes
  67. * the font, underline, color, etc. to be recached if the cache has been
  68. * invalidated.
  69. */
  70. final void syncProperties() {
  71. if (font == null) {
  72. setPropertiesFromAttributes();
  73. }
  74. }
  75. /**
  76. * Synchronize the view's cached fragments. Causes the fragments to be
  77. * recreated if the cache has been invalidated.
  78. */
  79. private final void syncFragments() {
  80. if( fragments != null) {
  81. return;
  82. }
  83. int startOffset = getStartOffset();
  84. int endOffset = getEndOffset();
  85. Element elem = getElement();
  86. AbstractDocument doc = (AbstractDocument)getDocument();
  87. Element paragraph = doc.getParagraphElement( startOffset );
  88. int paragraphStart = paragraph.getStartOffset();
  89. int paragraphEnd = paragraph.getEndOffset();
  90. if( endOffset > paragraphEnd )
  91. throw new StateInvariantError("LabelView may not span paragraphs");
  92. Element bidiRoot = doc.getBidiRootElement();
  93. int bidiStartIndex = bidiRoot.getElementIndex( startOffset );
  94. int bidiEndIndex = bidiRoot.getElementIndex( endOffset-1 );
  95. if( bidiStartIndex > bidiEndIndex )
  96. throw new StateInvariantError("0 length element encountered.");
  97. // REMIND (bcb) its not clear that this is the right way to get
  98. // a font render context.
  99. syncProperties();
  100. FontRenderContext frc;
  101. Container container = getContainer();
  102. if (container == null) {
  103. frc = DefaultRenderContext;
  104. }
  105. else {
  106. Graphics2D g2d = (Graphics2D)container.getGraphics();
  107. if (g2d != null) {
  108. frc = g2d.getFontRenderContext();
  109. } else {
  110. frc = DefaultRenderContext;
  111. }
  112. }
  113. fragments = new LabelFragment[ bidiEndIndex - bidiStartIndex + 1 ];
  114. int p0 = startOffset;
  115. for( int i=bidiStartIndex; i<=bidiEndIndex; i++ ) {
  116. BidiElement bidiElem = (BidiElement)bidiRoot.getElement( i );
  117. int bidiStart = bidiElem.getStartOffset();
  118. int bidiEnd = bidiElem.getEndOffset();
  119. int p1 = Math.min( endOffset, bidiEnd );
  120. int contextStart = Math.max( paragraphStart, bidiStart);
  121. int contextEnd = Math.min( paragraphEnd, bidiEnd );
  122. loadText( contextStart, contextEnd );
  123. ExtendedTextLabel glyphs
  124. = StandardExtendedTextLabel.create(text.array,
  125. text.offset, text.count,
  126. text.offset+p0-contextStart,
  127. p1-p0,
  128. bidiElem.isLeftToRight(),
  129. font, frc);
  130. fragments[i-bidiStartIndex]=new LabelFragment(elem,p0,p1,glyphs);
  131. //REMIND(bcb) is this the correct parent?
  132. fragments[i-bidiStartIndex].setParent(this);
  133. p0 = p1;
  134. }
  135. }
  136. /**
  137. * Set whether or not the view is underlined.
  138. */
  139. protected void setUnderline(boolean u) {
  140. underline = u;
  141. }
  142. /**
  143. * Set whether or not the view has a strike/line
  144. * through it.
  145. */
  146. protected void setStrikeThrough(boolean s) {
  147. strike = s;
  148. }
  149. /**
  150. * Set whether or not the view represents a
  151. * superscript.
  152. */
  153. protected void setSuperscript(boolean s) {
  154. superscript = s;
  155. }
  156. /**
  157. * Set whether or not the view represents a
  158. * subscript.
  159. */
  160. protected void setSubscript(boolean s) {
  161. subscript = s;
  162. }
  163. /**
  164. * Set the cached properties from the attributes.
  165. */
  166. protected void setPropertiesFromAttributes() {
  167. AttributeSet attr = getAttributes();
  168. Document d = getDocument();
  169. if (attr != null) {
  170. if (d instanceof StyledDocument) {
  171. StyledDocument doc = (StyledDocument) d;
  172. font = doc.getFont(attr);
  173. fg = doc.getForeground(attr);
  174. if (attr.isDefined(StyleConstants.Background)) {
  175. bg = doc.getBackground(attr);
  176. }
  177. else {
  178. bg = null;
  179. }
  180. setStrikeThrough(StyleConstants.isStrikeThrough(attr));
  181. setSuperscript(StyleConstants.isSuperscript(attr));
  182. setSubscript(StyleConstants.isSubscript(attr));
  183. setUnderline(StyleConstants.isUnderline(attr));
  184. } else {
  185. throw new StateInvariantError("LabelView needs StyledDocument");
  186. }
  187. }
  188. }
  189. /**
  190. * Fetch the FontMetrics used for this view.
  191. */
  192. /* REMIND(bcb) who needs this? Should use line metrics instead.*/
  193. protected FontMetrics getFontMetrics() {
  194. syncProperties();
  195. return Toolkit.getDefaultToolkit().getFontMetrics(font);
  196. }
  197. /**
  198. * Fetch the Font used for this view.
  199. */
  200. protected Font getFont() {
  201. syncProperties();
  202. return font;
  203. }
  204. // --- TabableView methods --------------------------------------
  205. /**
  206. * Determines the desired span when using the given
  207. * tab expansion implementation.
  208. *
  209. * @param x the position the view would be located
  210. * at for the purpose of tab expansion >= 0.
  211. * @param e how to expand the tabs when encountered.
  212. * @return the desired span >= 0
  213. * @see TabableView#getTabbedSpan
  214. */
  215. /* REMIND(bcb) Not Implemented
  216. public float getTabbedSpan(float x, TabExpander e) {
  217. expander = e;
  218. this.x = (int) x;
  219. return getPreferredSpan(X_AXIS, getStartOffset(), getEndOffset(), this.x);
  220. }
  221. */
  222. /**
  223. * Determines the span along the same axis as tab
  224. * expansion for a portion of the view. This is
  225. * intended for use by the TabExpander for cases
  226. * where the tab expansion involves aligning the
  227. * portion of text that doesn't have whitespace
  228. * relative to the tab stop. There is therefore
  229. * an assumption that the range given does not
  230. * contain tabs.
  231. * <p>
  232. * This method can be called while servicing the
  233. * getTabbedSpan or getPreferredSize. It has to
  234. * arrange for its own text buffer to make the
  235. * measurements.
  236. *
  237. * @param p0 the starting document offset >= 0
  238. * @param p1 the ending document offset >= p0
  239. * @return the span >= 0
  240. */
  241. /* REMIND(bcb) Not implemented
  242. public float getPartialSpan(int p0, int p1) {
  243. // PENDING should probably use a static buffer since there
  244. // should be only one thread accessing it.
  245. syncProperties();
  246. int width = 0;
  247. try {
  248. Segment s = new Segment();
  249. getDocument().getText(p0, p1 - p0, s);
  250. width = Utilities.getTabbedTextWidth(s, metrics, x, expander, p0);
  251. } catch (BadLocationException bl) {
  252. }
  253. return width;
  254. }
  255. */
  256. // --- View methods ---------------------------------------------
  257. /**
  258. * Renders a portion of a text style run.
  259. *
  260. * @param g the rendering surface to use
  261. * @param a the allocated region to render into
  262. */
  263. public void paint(Graphics g, Shape a) {
  264. syncFragments();
  265. if( fragments.length > 1 )
  266. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  267. fragments[0].paint(g, a);
  268. }
  269. /**
  270. * Determines the preferred span for this view along an
  271. * axis.
  272. *
  273. * @param axis may be either View.X_AXIS or View.Y_AXIS
  274. * @returns the span the view would like to be rendered into >= 0.
  275. * Typically the view is told to render into the span
  276. * that is returned, although there is no guarantee.
  277. * The parent may choose to resize or break the view.
  278. */
  279. public float getPreferredSpan(int axis) {
  280. syncFragments();
  281. float span = 0;
  282. for( int i=0; i<fragments.length; i++ )
  283. span +=fragments[i].getPreferredSpan(axis);
  284. return span;
  285. }
  286. /**
  287. * Determines the desired alignment for this view along an
  288. * axis. For the label, the alignment is along the font
  289. * baseline for the y axis, and the superclasses alignment
  290. * along the x axis.
  291. *
  292. * @param axis may be either View.X_AXIS or View.Y_AXIS
  293. * @returns the desired alignment. This should be a value
  294. * between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
  295. * origin and 1.0 indicates alignment to the full span
  296. * away from the origin. An alignment of 0.5 would be the
  297. * center of the view.
  298. */
  299. public float getAlignment(int axis) {
  300. syncFragments();
  301. if( fragments.length > 1 )
  302. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  303. return fragments[0].getAlignment( axis );
  304. }
  305. /**
  306. * Provides a mapping from the document model coordinate space
  307. * to the coordinate space of the view mapped to it.
  308. *
  309. * @param pos the position to convert >= 0
  310. * @param a the allocated region to render into
  311. * @return the bounding box of the given position
  312. * @exception BadLocationException if the given position does not represent a
  313. * valid location in the associated document
  314. * @see View#modelToView
  315. */
  316. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  317. syncFragments();
  318. if( fragments.length > 1 )
  319. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  320. return fragments[0].modelToView(pos, a, b);
  321. }
  322. /**
  323. * Provides a mapping from the view coordinate space to the logical
  324. * coordinate space of the model.
  325. *
  326. * @param x the X coordinate >= 0
  327. * @param y the Y coordinate >= 0
  328. * @param a the allocated region to render into
  329. * @return the location within the model that best represents the
  330. * given point of view >= 0
  331. * @see View#viewToModel
  332. */
  333. public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
  334. syncFragments();
  335. if( fragments.length > 1 )
  336. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  337. return fragments[0].viewToModel(x, y, a, biasReturn);
  338. }
  339. /**
  340. * Provides a way to determine the next visually represented model
  341. * location that one might place a caret. Some views may not be
  342. * visible, they might not be in the same order found in the model, or
  343. * they just might not allow access to some of the locations in the
  344. * model.
  345. *
  346. * @param pos the position to convert >= 0
  347. * @param a the allocated region to render into
  348. * @param direction the direction from the current position that can
  349. * be thought of as the arrow keys typically found on a keyboard.
  350. * This may be SwingConstants.WEST, SwingConstants.EAST,
  351. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  352. * @return the location within the model that best represents the next
  353. * location visual position.
  354. * @exception BadLocationException
  355. * @exception IllegalArgumentException for an invalid direction
  356. */
  357. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  358. int direction,
  359. Position.Bias[] biasRet)
  360. throws BadLocationException {
  361. syncFragments();
  362. if( fragments.length > 1 )
  363. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  364. return fragments[0].getNextVisualPositionFrom(pos,b,a,direction,
  365. biasRet);
  366. }
  367. /**
  368. * Determines how attractive a break opportunity in
  369. * this view is. This can be used for determining which
  370. * view is the most attractive to call <code>breakView</code>
  371. * on in the process of formatting. The
  372. * higher the weight, the more attractive the break. A
  373. * value equal to or lower than <code>View.BadBreakWeight</code>
  374. * should not be considered for a break. A value greater
  375. * than or equal to <code>View.ForcedBreakWeight</code> should
  376. * be broken.
  377. * <p>
  378. * This is implemented to forward to the superclass for
  379. * the Y_AXIS. Along the X_AXIS the following values
  380. * may be returned.
  381. * <dl>
  382. * <dt><b>View.ExcellentBreakWeight</b>
  383. * <dd>if there is whitespace proceeding the desired break
  384. * location.
  385. * <dt><b>View.BadBreakWeight</b>
  386. * <dd>if the desired break location results in a break
  387. * location of the starting offset.
  388. * <dt><b>View.GoodBreakWeight</b>
  389. * <dd>if the other conditions don't occur.
  390. * </dl>
  391. * This will normally result in the behavior of breaking
  392. * on a whitespace location if one can be found, otherwise
  393. * breaking between characters.
  394. *
  395. * @param axis may be either View.X_AXIS or View.Y_AXIS
  396. * @param pos the potential location of the start of the
  397. * broken view >= 0. This may be useful for calculating tab
  398. * positions.
  399. * @param len specifies the relative length from <em>pos</em>
  400. * where a potential break is desired >= 0.
  401. * @return the weight, which should be a value between
  402. * View.ForcedBreakWeight and View.BadBreakWeight.
  403. * @see LabelView
  404. * @see ParagraphView
  405. * @see BadBreakWeight
  406. * @see GoodBreakWeight
  407. * @see ExcellentBreakWeight
  408. * @see ForcedBreakWeight
  409. */
  410. public int getBreakWeight(int axis, float pos, float len) {
  411. syncFragments();
  412. if( fragments.length > 1 )
  413. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  414. return fragments[0].getBreakWeight(axis, pos, len);
  415. }
  416. /**
  417. * Breaks this view on the given axis at the given length.
  418. * This is implemented to attempt to break on a whitespace
  419. * location, and returns a fragment with the whitespace at
  420. * the end. If a whitespace location can't be found, the
  421. * nearest character is used.
  422. *
  423. * @param axis may be either View.X_AXIS or View.Y_AXIS
  424. * @param p0 the location in the model where the
  425. * fragment should start it's representation >= 0.
  426. * @param pos the position along the axis that the
  427. * broken view would occupy >= 0. This may be useful for
  428. * things like tab calculations.
  429. * @param len specifies the distance along the axis
  430. * where a potential break is desired >= 0.
  431. * @return the fragment of the view that represents the
  432. * given span, if the view can be broken. If the view
  433. * doesn't support breaking behavior, the view itself is
  434. * returned.
  435. * @see View#breakView
  436. */
  437. public View breakView(int axis, int p0, float pos, float len) {
  438. syncFragments();
  439. if( fragments.length > 1 )
  440. throw new StateInvariantError("Method invalid for multi-directional LabelView");
  441. return fragments[0].breakView( axis, p0, pos, len );
  442. }
  443. /**
  444. * Creates a view that represents a portion of the element.
  445. * This is potentially useful during formatting operations
  446. * for taking measurements of fragments of the view. If
  447. * the view doesn't support fragmenting (the default), it
  448. * should return itself.
  449. * <p>
  450. * This view does support fragmenting. It is implemented
  451. * to return a nested class that shares state in this view
  452. * representing only a portion of the view.
  453. *
  454. * @param p0 the starting offset >= 0. This should be a value
  455. * greater or equal to the element starting offset and
  456. * less than the element ending offset.
  457. * @param p1 the ending offset > p0. This should be a value
  458. * less than or equal to the elements end offset and
  459. * greater than the elements starting offset.
  460. * @returns the view fragment, or itself if the view doesn't
  461. * support breaking into fragments.
  462. * @see LabelView
  463. */
  464. public View createFragment(int p0, int p1) /*throws BadLocationException*/ {
  465. syncFragments();
  466. for( int i=0; i<fragments.length; i++ ) {
  467. View v = fragments[i];
  468. int fragmentStart = v.getStartOffset();
  469. int fragmentEnd = v.getEndOffset();
  470. if( p0 < fragmentStart ) {
  471. return this;
  472. //throw new BadLocationException("Fragment start outside of range",
  473. // p0);
  474. } else if( p0 == fragmentStart ) {
  475. if( p1 < fragmentEnd ) {
  476. return v.createFragment( p0, p1 );
  477. }
  478. else if( p1 == fragmentEnd ) {
  479. return v;
  480. } else if( p1 > fragmentEnd ) {
  481. throw new StateInvariantError("frags can't span dir boundaries");
  482. }
  483. } else if( p0 < fragmentEnd ) {
  484. if( p1 <= fragmentEnd ) {
  485. return v.createFragment( p0, p1 );
  486. } else {
  487. throw new StateInvariantError("frags can't span dir boundaries");
  488. }
  489. }
  490. }
  491. //throw new BadLocationException();
  492. return this;
  493. }
  494. /**
  495. * Gives notification from the document that attributes were changed
  496. * in a location that this view is responsible for.
  497. *
  498. * @param e the change information from the associated document
  499. * @param a the current allocation of the view
  500. * @param f the factory to use to rebuild if the view has children
  501. * @see View#changedUpdate
  502. */
  503. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  504. // Null font indicates attribute cache needs updating
  505. font = null;
  506. // REMIND(bcb) the fragment only needs to be invalidated if the
  507. // font has changed.
  508. fragments = null;
  509. }
  510. /**
  511. * Gives notification that something was added to the document
  512. * in a location that this view is responsible for.
  513. *
  514. * @param changes the change information from the associated document
  515. * @param a the current allocation of the view
  516. * @param f the factory to use to rebuild if the view has children
  517. * @see View#insertUpdate
  518. */
  519. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  520. fragments = null;
  521. super.insertUpdate(e, a, f);
  522. }
  523. /**
  524. * Gives notification that something was removed from the document
  525. * in a location that this view is responsible for.
  526. *
  527. * @param changes the change information from the associated document
  528. * @param a the current allocation of the view
  529. * @param f the factory to use to rebuild if the view has children
  530. * @see View#removeUpdate
  531. */
  532. public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
  533. fragments = null;
  534. super.removeUpdate(changes, a, f);
  535. }
  536. public String toString() {
  537. String s = "LabelView: elem = " + getElement().toString();
  538. if( fragments == null )
  539. s += "\tfragments = null\n";
  540. else {
  541. for( int i=0; i<fragments.length; i++ ) {
  542. s += "\tfragment = " + fragments[i].toString() + "\n";
  543. }
  544. }
  545. return s;
  546. }
  547. // --- variables ------------------------------------------------
  548. /**
  549. * Property cache
  550. */
  551. Font font;
  552. Color fg;
  553. Color bg;
  554. boolean underline;
  555. boolean strike;
  556. boolean superscript;
  557. boolean subscript;
  558. Segment text;
  559. /**
  560. * Cache of LabelFragments, one for each directional run represented
  561. * by this view. The LabelFragments do all the interesting work. In
  562. * general, LabelView delegates its work to these fragments.
  563. */
  564. LabelFragment[] fragments;
  565. /**
  566. * how to expand tabs
  567. */
  568. TabExpander expander;
  569. /**
  570. * location for determining tab expansion against.
  571. */
  572. int x;
  573. /**
  574. * A label view that represents a run of text of a single style and
  575. * single direction.
  576. */
  577. class LabelFragment extends View /*implements TabableView*/{
  578. /**
  579. * Constructs a new view wrapped on an element.
  580. *
  581. * @param elem the element
  582. * @param p0 the beginning of the range
  583. * @param p1 the end of the range
  584. * @param glyphs glyphs representing this view's text
  585. */
  586. public LabelFragment(Element elem, int p0, int p1,
  587. ExtendedTextLabel glyphs) {
  588. super(elem);
  589. offset = (short) (p0 - elem.getStartOffset());
  590. length = (short) (p1 - p0);
  591. this.glyphs = glyphs;
  592. //REMIND(bcb) it may be cheaper/better to either pass this in or
  593. //store it in the glyph vector. Also AbstractDoc.isLeftToRight is
  594. //broken because it can't detect mixed direction text.
  595. Document d = getDocument();
  596. if( d instanceof AbstractDocument ) {
  597. rightToLeft = !((AbstractDocument)d).isLeftToRight(p0, p1);
  598. }
  599. }
  600. // --- TabableView methods --------------------------------------
  601. /**
  602. * Determines the desired span when using the given
  603. * tab expansion implementation.
  604. *
  605. * @param x the position the view would be located
  606. * at for the purpose of tab expansion >= 0.
  607. * @param e how to expand the tabs when encountered.
  608. * @return the desired span >= 0
  609. * @see TabableView#getTabbedSpan
  610. */
  611. /* REMIND(bcb) not yet ready for tab support.
  612. public float getTabbedSpan(float x, TabExpander e) {
  613. LabelView.this.expander = e;
  614. this.x = (int) x;
  615. return LabelView.this.getPreferredSpan(X_AXIS, getStartOffset(),
  616. getEndOffset(), this.x);
  617. }
  618. */
  619. /**
  620. * Determine the span along the same axis as tab
  621. * expansion for a portion of the view. This is
  622. * intended for use by the TabExpander for cases
  623. * where the tab expansion involves aligning the
  624. * portion of text that doesn't have whitespace
  625. * relative to the tab stop. There is therefore
  626. * an assumption that the range given does not
  627. * contain tabs.
  628. */
  629. /* REMIND(bcb) not yet ready for tab support.
  630. public float getPartialSpan(int p0, int p1) {
  631. return LabelView.this.getPartialSpan(p0, p1);
  632. }
  633. */
  634. // --- View methods ----------------------------
  635. /**
  636. * This returns the attributes of the LabelView that created
  637. * the receiver.
  638. */
  639. public AttributeSet getAttributes() {
  640. return LabelView.this.getAttributes();
  641. }
  642. /**
  643. * Fetches the starting offset of the portion of the model that this
  644. * view is responsible for.
  645. *
  646. * @return the starting offset into the model
  647. * @see View#getStartOffset
  648. */
  649. public int getStartOffset() {
  650. Element e = getElement();
  651. return e.getStartOffset() + offset;
  652. }
  653. /**
  654. * Fetches the ending offset of the portion of the model that this
  655. * view is responsible for.
  656. *
  657. * @return the ending offset into the model
  658. * @see View#getEndOffset
  659. */
  660. public int getEndOffset() {
  661. Element e = getElement();
  662. return e.getStartOffset() + offset + length;
  663. }
  664. /**
  665. * Renders a portion of a text style run.
  666. *
  667. * @param g the rendering surface to use
  668. * @param a the allocated region to render into
  669. */
  670. public void paint(Graphics g, Shape a) {
  671. syncProperties();
  672. if (LabelView.this.bg != null) {
  673. Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
  674. a.getBounds();
  675. g.setColor(LabelView.this.bg);
  676. g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
  677. }
  678. Component comp = getContainer();
  679. if (comp instanceof JTextComponent) {
  680. JTextComponent c = (JTextComponent) comp;
  681. Highlighter h = c.getHighlighter();
  682. if (h instanceof LayeredHighlighter) {
  683. ((LayeredHighlighter)h).paintLayeredHighlights
  684. (g, getStartOffset(), getEndOffset(), a, c, this);
  685. }
  686. }
  687. if (Utilities.isComposedTextElement(getElement())) {
  688. paintComposedText((Graphics2D)g, a.getBounds(), getStartOffset(), getEndOffset());
  689. } else {
  690. paintText( (Graphics2D)g, a );
  691. }
  692. }
  693. void paintText(Graphics2D g, Shape a) {
  694. Rectangle alloc = a.getBounds();
  695. float y = (float)alloc.y + glyphs.getLineMetrics().getAscent();
  696. g.setFont( font );
  697. g.setColor( fg );
  698. glyphs.draw( g, (float)alloc.x, y );
  699. if( underline || strike ) {
  700. LineMetrics metrics = glyphs.getLineMetrics();
  701. float lineY;
  702. float thickness;
  703. if( underline ) {
  704. lineY = y + metrics.getUnderlineOffset();
  705. thickness = metrics.getUnderlineThickness();
  706. } else {
  707. lineY = y + metrics.getStrikethroughOffset();
  708. thickness = metrics.getStrikethroughThickness();
  709. }
  710. Rectangle2D line
  711. = new Rectangle2D.Float( (float)alloc.x, lineY,
  712. (float)alloc.width, thickness );
  713. g.fill( line );
  714. }
  715. }
  716. /**
  717. * Paint the selection highlight over the portion of this view
  718. * that is selected.
  719. */
  720. void paintSelection(Graphics g, Shape a) {
  721. JTextComponent c = (JTextComponent)getContainer();
  722. //System.out.println("paintSel c = " + c);
  723. //System.out.println("paintSel parent = " + getParent());
  724. if(c == null)
  725. return;
  726. Color selBG = c.getSelectionColor();
  727. //System.out.println("paintSel: selBG = " + selBG);
  728. if( selBG == null )
  729. return;
  730. int p0 = getStartOffset();
  731. int p1 = getEndOffset();
  732. Position.Bias[] selStartBias = new Position.Bias[1];
  733. Position.Bias[] selEndBias = new Position.Bias[1];
  734. int selStart = c.getSelectionStart(selStartBias);
  735. int selEnd = c.getSelectionEnd(selEndBias);
  736. //System.out.println( "\tSelection start = " + selStart + " selection end = " + selEnd);
  737. if(selStart == selEnd) {
  738. // There is no selection.
  739. return;
  740. }
  741. int pMin;
  742. int pMax;
  743. if(selStart <= p0) {
  744. pMin = p0;
  745. selStartBias[0] = Position.Bias.Forward;
  746. }
  747. else
  748. pMin = Math.min(selStart, p1);
  749. if(selEnd >= p1) {
  750. pMax = p1;
  751. selEndBias[0] = Position.Bias.Backward;
  752. }
  753. else
  754. pMax = Math.max(selEnd, p0);
  755. // If pMin == pMax (also == p0), selection isn't in this
  756. // block.
  757. if(pMin == pMax)
  758. return;
  759. /* paint the selection's background */
  760. try {
  761. Rectangle rMax = modelToView(pMax,a,selEndBias[0]).getBounds();
  762. Rectangle rMin = modelToView(pMin,a,selStartBias[0]).getBounds();
  763. Rectangle r = rMin.union(rMax);
  764. g.setColor( selBG );
  765. /*
  766. System.out.println("\tPMin = " + pMin + " PMax = " + pMax);
  767. System.out.println("\tRMin = " + rMin);
  768. System.out.println("\tRMax = " + rMax);
  769. System.out.println("\tR = " + r);
  770. */
  771. g.fillRect(r.x, r.y, r.width, r.height);
  772. } catch (BadLocationException e ) {}
  773. }
  774. /**
  775. * Determines the preferred span for this view along an
  776. * axis.
  777. *
  778. * @param axis may be either X_AXIS or Y_AXIS
  779. * @returns the span the view would like to be rendered into.
  780. * Typically the view is told to render into the span
  781. * that is returned, although there is no guarantee.
  782. * The parent may choose to resize or break the view.
  783. */
  784. public float getPreferredSpan(int axis) {
  785. Rectangle2D bounds = glyphs.getLogicalBounds( 0f, 0f );
  786. switch (axis) {
  787. case View.X_AXIS:
  788. return Math.max((float)bounds.getWidth(), 1f);
  789. case View.Y_AXIS:
  790. return (float)bounds.getHeight();
  791. default:
  792. throw new IllegalArgumentException("Invalid axis: " + axis);
  793. }
  794. }
  795. /**
  796. * Determines the desired alignment for this view along an
  797. * axis. For the label, the alignment is along the font
  798. * baseline for the y axis, and the superclasses alignment
  799. * along the x axis.
  800. *
  801. * @param axis may be either X_AXIS or Y_AXIS
  802. * @returns the desired alignment. This should be a value
  803. * between 0.0 and 1.0 where 0 indicates alignment at the
  804. * origin and 1.0 indicates alignment to the full span
  805. * away from the origin. An alignment of 0.5 would be the
  806. * center of the view.
  807. */
  808. public float getAlignment(int axis) {
  809. if (axis == View.Y_AXIS) {
  810. LineMetrics metrics = glyphs.getLineMetrics();
  811. return metrics.getAscent() / metrics.getHeight();
  812. }
  813. return super.getAlignment(axis);
  814. }
  815. /**
  816. * Provides a mapping from the document model coordinate space
  817. * to the coordinate space of the view mapped to it.
  818. *
  819. * @param pos the position to convert
  820. * @param a the allocated region to render into
  821. * @return the bounding box of the given position
  822. * @exception BadLocationException if the given position does not represent a
  823. * valid location in the associated document
  824. * @see View#modelToView
  825. */
  826. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  827. int startOffset = getStartOffset();
  828. int endOffset = getEndOffset();
  829. // Make sure this is a position we're responsible for.
  830. if( (pos > endOffset || pos < startOffset)
  831. || (pos == endOffset && b == Position.Bias.Forward)
  832. || (pos == startOffset && b == Position.Bias.Backward) ) {
  833. String s = "modelToView - Position (" + pos + "," + b
  834. + ") not in view's range of ("
  835. + getStartOffset() + "," + getEndOffset() + ").";
  836. throw new BadLocationException(s, pos);
  837. }
  838. // Convert pos into an index
  839. int index = pos - startOffset;
  840. float width = 0;
  841. if( rightToLeft ) {
  842. if( b == Position.Bias.Forward ) {
  843. float charX = glyphs.getCharX( index );
  844. float advance = glyphs.getCharAdvance( index );
  845. width = charX + advance;
  846. } else {
  847. width = glyphs.getCharX( index-1 );
  848. }
  849. } else {
  850. if( b == Position.Bias.Forward ) {
  851. width = glyphs.getCharX( index );
  852. } else {
  853. float charX = glyphs.getCharX( index-1 );
  854. float advance = glyphs.getCharAdvance( index-1 );
  855. width = charX + advance;
  856. }
  857. }
  858. Rectangle alloc = a.getBounds();
  859. float height = glyphs.getLineMetrics().getHeight();
  860. //REMIND(bcb) should we be returning old geom or new 2D geom
  861. return new Rectangle(alloc.x + (int)width, alloc.y, 0, (int)height);
  862. }
  863. /**
  864. * Provides a mapping from the view coordinate space to the logical
  865. * coordinate space of the model.
  866. *
  867. * @param x the X coordinate
  868. * @param y the Y coordinate
  869. * @param a the allocated region to render into
  870. * @return the location within the model that best represents the
  871. * given point of view
  872. * @see View#viewToModel
  873. */
  874. public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
  875. Rectangle alloc = (a instanceof Rectangle)
  876. ? (Rectangle)a : a.getBounds();
  877. //System.out.println("LabelView.viewToModel(x="+x+", y="+y+ ", shape = "+alloc);
  878. // Check for points out of our visual bounds and return one of
  879. // the two end offsets.
  880. if( x < alloc.x ) {
  881. if( rightToLeft ) {
  882. biasReturn[0] = Position.Bias.Backward;
  883. return getEndOffset();
  884. } else {
  885. biasReturn[0] = Position.Bias.Forward;
  886. return getStartOffset();
  887. }
  888. }
  889. if( x >= alloc.x + alloc.width ) {
  890. if( rightToLeft ) {
  891. biasReturn[0] = Position.Bias.Forward;
  892. return getStartOffset();
  893. } else {
  894. biasReturn[0] = Position.Bias.Backward;
  895. return getEndOffset();
  896. }
  897. }
  898. float width = x - alloc.x;
  899. int index = glyphs.getCharIndexAtWidth(width);
  900. if (index >= glyphs.getNumCharacters()) {
  901. // This will usually happen when representing and end of
  902. // line, or end of document.
  903. if (rightToLeft) {
  904. // Could really be either here.
  905. biasReturn[0] = Position.Bias.Forward;
  906. return getStartOffset();
  907. }
  908. else {
  909. biasReturn[0] = Position.Bias.Backward;
  910. return getEndOffset();
  911. }
  912. }
  913. float charX = glyphs.getCharX( index );
  914. float advance = glyphs.getCharAdvance( index );
  915. int offset = getStartOffset();
  916. if( width < (charX + (advance2)) ){
  917. if( rightToLeft ) {
  918. offset += index + 1;
  919. biasReturn[0] = Position.Bias.Backward;
  920. } else {
  921. offset += index;
  922. biasReturn[0] = Position.Bias.Forward;
  923. }
  924. } else {
  925. if( rightToLeft ) {
  926. offset += index;
  927. biasReturn[0] = Position.Bias.Forward;
  928. } else {
  929. offset += index + 1;
  930. biasReturn[0] = Position.Bias.Backward;
  931. }
  932. }
  933. //System.out.println("offset = " + offset + " bias = " + biasReturn[0]);
  934. return offset;
  935. }
  936. /**
  937. * Provides a way to determine the next visually represented model
  938. * location that one might place a caret. Some views may not be
  939. * visible, they might not be in the same order found in the model, or
  940. * they just might not allow access to some of the locations in the
  941. * model.
  942. *
  943. * @param pos the position to convert >= 0
  944. * @param a the allocated region to render into
  945. * @param direction the direction from the current position that can
  946. * be thought of as the arrow keys typically found on a keyboard.
  947. * This may be SwingConstants.WEST, SwingConstants.EAST,
  948. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  949. * @return the location within the model that best represents the next
  950. * location visual position.
  951. * @exception BadLocationException
  952. * @exception IllegalArgumentException for an invalid direction */
  953. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  954. int direction,
  955. Position.Bias[] biasRet)
  956. throws BadLocationException {
  957. //System.out.println("LabelView:getNextVisPos pos = " + pos
  958. // + " bias = " + b + " direction = " + direction);
  959. int startOffset = getStartOffset();
  960. int endOffset = getEndOffset();
  961. switch (direction) {
  962. case NORTH:
  963. break;
  964. case SOUTH:
  965. break;
  966. case EAST:
  967. if(startOffset == getDocument().getLength()) {
  968. if(pos == -1) {
  969. biasRet[0] = Position.Bias.Forward;
  970. return startOffset;
  971. }
  972. // End case for bidi text where newline is at beginning
  973. // of line.
  974. return -1;
  975. }
  976. if(rightToLeft) {
  977. if(pos == -1) {
  978. loadText(endOffset - 1, endOffset);
  979. if(text.array[text.offset] == '\n') {
  980. biasRet[0] = Position.Bias.Forward;
  981. return endOffset - 1;
  982. }
  983. biasRet[0] = Position.Bias.Backward;
  984. return endOffset;
  985. }
  986. if(pos == startOffset) {
  987. return -1;
  988. }
  989. biasRet[0] = Position.Bias.Forward;
  990. while( (--pos >= startOffset)
  991. && (glyphs.getCharAdvance(pos-startOffset) == 0) );
  992. if( pos < startOffset )
  993. return -1;
  994. else
  995. return pos;
  996. }
  997. if(pos == -1) {
  998. biasRet[0] = Position.Bias.Forward;
  999. return startOffset;
  1000. }
  1001. if(pos == endOffset) {
  1002. return -1;
  1003. }
  1004. if(++pos == endOffset) {
  1005. loadText(endOffset - 1, endOffset);
  1006. if(text.array[text.offset] == '\n') {
  1007. return -1;
  1008. }
  1009. biasRet[0] = Position.Bias.Backward;
  1010. }
  1011. else {
  1012. biasRet[0] = Position.Bias.Forward;
  1013. }
  1014. return pos;
  1015. case WEST:
  1016. if(startOffset == getDocument().getLength()) {
  1017. if(pos == -1) {
  1018. biasRet[0] = Position.Bias.Forward;
  1019. return startOffset;
  1020. }
  1021. // End case for bidi text where newline is at beginning
  1022. // of line.
  1023. return -1;
  1024. }
  1025. if(rightToLeft) {
  1026. if(pos == -1) {
  1027. biasRet[0] = Position.Bias.Forward;
  1028. return startOffset;
  1029. }
  1030. if(pos == endOffset) {
  1031. return -1;
  1032. }
  1033. if(++pos == endOffset) {
  1034. loadText(endOffset - 1, endOffset);
  1035. if(text.array[text.offset] == '\n') {
  1036. return -1;
  1037. }
  1038. biasRet[0] = Position.Bias.Backward;
  1039. }
  1040. else {
  1041. biasRet[0] = Position.Bias.Forward;
  1042. }
  1043. return pos;
  1044. }
  1045. if(pos == -1) {
  1046. loadText(endOffset - 1, endOffset);
  1047. if(text.array[text.offset] == '\n') {
  1048. biasRet[0] = Position.Bias.Forward;
  1049. return endOffset - 1;
  1050. }
  1051. biasRet[0] = Position.Bias.Backward;
  1052. return endOffset;
  1053. }
  1054. if(pos == startOffset) {
  1055. return -1;
  1056. }
  1057. biasRet[0] = Position.Bias.Forward;
  1058. return (pos - 1);
  1059. default:
  1060. throw new IllegalArgumentException("Bad direction: " + direction);
  1061. }
  1062. return pos;
  1063. }
  1064. /**
  1065. * Gives notification from the document that attributes were changed
  1066. * in a location that this view is responsible for.
  1067. *
  1068. * @param e the change information from the associated document
  1069. * @param a the current allocation of the view
  1070. * @param f the factory to use to rebuild if the view has children
  1071. * @see View#changedUpdate
  1072. */
  1073. /* REMIND(bcb) this should not be necessary.
  1074. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  1075. LabelView.this.changedUpdate(e, a, f);
  1076. }
  1077. */
  1078. /**
  1079. * @see LabelView#getBreakWeight
  1080. */
  1081. public int getBreakWeight(int axis, float pos, float len) {
  1082. int p0 = getStartOffset();
  1083. int p1 = getEndOffset();
  1084. if (axis == View.X_AXIS) {
  1085. syncProperties();
  1086. loadText(p0, p1);
  1087. // REMIND(bcb) The spec says len will always be >= 0.
  1088. // Currently Currently ParagraphView doesn't respect this.
  1089. // Until its fixed, this check will protect us.
  1090. if( len < 0 )
  1091. return BadBreakWeight;
  1092. //int index = glyphs.getCharIndexAtWidth(len );
  1093. int index = glyphs.getLineBreakIndex( 0, len );
  1094. if (index == 0) {
  1095. // break is at the start offset
  1096. return BadBreakWeight;
  1097. }
  1098. for (int i = text.offset + Math.min(index, text.count - 1);
  1099. i >= text.offset; i--) {
  1100. char ch = text.array[i];
  1101. if (Character.isWhitespace(ch)) {
  1102. // found whitespace
  1103. return ExcellentBreakWeight;
  1104. }
  1105. }
  1106. // no whitespace
  1107. return GoodBreakWeight;
  1108. }
  1109. return super.getBreakWeight(axis, pos, len);
  1110. }
  1111. /**
  1112. * Breaks this view on the given axis at the given length.
  1113. *
  1114. * @param axis may be either X_AXIS or Y_AXIS
  1115. * @param offset the location in the model where the
  1116. * fragment should start it's representation.
  1117. * @param pos the position along the axis that the
  1118. * broken view would occupy. This may be useful for
  1119. * things like tab calculations.
  1120. * @param len specifies the distance along the axis
  1121. * where a potential break is desired.
  1122. * @param a the current allocation of the view
  1123. * @return the fragment of the view that represents the
  1124. * given span, if the view can be broken. If the view
  1125. * doesn't support breaking behavior, the view itself is
  1126. * returned.
  1127. * @see View#breakView
  1128. */
  1129. public View breakView(int axis, int p0, float pos, float len) {
  1130. if (axis == View.X_AXIS) {
  1131. syncProperties();
  1132. loadText(p0, getEndOffset());
  1133. int index = glyphs.getLineBreakIndex(p0-getStartOffset(), len);
  1134. //int index = glyphs.getCharIndexAtWidth( len);
  1135. for (int i = text.offset + Math.min(index, text.count - 1);
  1136. i >= text.offset; i--) {
  1137. char ch = text.array[i];
  1138. if (Character.isWhitespace(ch)) {
  1139. // found whitespace, break here
  1140. index = i - text.offset + 1;
  1141. break;
  1142. }
  1143. }
  1144. int p1 = p0 + index;
  1145. LabelFragment frag = (LabelFragment)LabelView.this
  1146. .createFragment(p0, p1);
  1147. frag.x = (int) pos;
  1148. return frag;
  1149. }
  1150. return this;
  1151. }
  1152. /**
  1153. * Creates a view that represents a portion of the element.
  1154. * This is potentially useful during formatting operations
  1155. * for taking measurements of fragments of the view. If
  1156. * the view doesn't support fragmenting (the default), it
  1157. * should return itself.
  1158. * <p>
  1159. * This view does support fragmenting. It is implemented
  1160. * to return a nested class that shares state in this view
  1161. * representing only a portion of the view.
  1162. *
  1163. * @param p0 the starting offset >= 0. This should be a value
  1164. * greater or equal to the element starting offset and
  1165. * less than the element ending offset.
  1166. * @param p1 the ending offset > p0. This should be a value
  1167. * less than or equal to the elements end offset and
  1168. * greater than the elements starting offset.
  1169. * @returns the view fragment, or itself if the view doesn't
  1170. * support breaking into fragments.
  1171. * @see LabelView
  1172. */
  1173. public View createFragment(int p0, int p1) {
  1174. //System.out.println("createfrag("+p0+","+p1+")");
  1175. //REMIND(bcb) this is the brute force way to create a fragment
  1176. //from another fragment. It should be possible to do this without
  1177. //reshaping.
  1178. int startOffset = getStartOffset();
  1179. int endOffset = getEndOffset();
  1180. AbstractDocument doc = (AbstractDocument)getDocument();
  1181. Element paragraph = doc.getParagraphElement( startOffset );
  1182. int paragraphStart = paragraph.getStartOffset();
  1183. int paragraphEnd = paragraph.getEndOffset();
  1184. if( endOffset > paragraphEnd )
  1185. throw new StateInvariantError("LabelFragment may not span paragraphs");
  1186. Element bidiRoot = doc.getBidiRootElement();
  1187. int index = bidiRoot.getElementIndex( startOffset );
  1188. BidiElement bidiElem = (BidiElement)bidiRoot.getElement( index );
  1189. if( bidiElem.getEndOffset() < endOffset )
  1190. throw new StateInvariantError("LabelFragments may not span directional boundaries.");
  1191. // REMIND (bcb) its not clear that this is the right way to get
  1192. // a font render context.
  1193. syncProperties();
  1194. FontRenderContext frc;
  1195. Container container = getContainer();
  1196. if (container == null) {
  1197. frc = DefaultRenderContext;
  1198. }
  1199. else {
  1200. frc = ((Graphics2D)container.getGraphics()).
  1201. getFontRenderContext();
  1202. }
  1203. int bidiStart = bidiElem.getStartOffset();
  1204. int bidiEnd = bidiElem.getEndOffset();
  1205. int contextStart = Math.max( paragraphStart, bidiStart);
  1206. int contextEnd = Math.min( paragraphEnd, bidiEnd );
  1207. loadText( contextStart, contextEnd );
  1208. ExtendedTextLabel glyphs
  1209. = StandardExtendedTextLabel.create(text.array,
  1210. text.offset, text.count,
  1211. text.offset+p0-contextStart,
  1212. p1-p0,
  1213. bidiElem.isLeftToRight(),
  1214. font, frc);
  1215. return new LabelFragment( getElement(), p0, p1, glyphs );
  1216. }
  1217. /**
  1218. * Fetches the container hosting the view. This is useful for
  1219. * things like scheduling a repaint, finding out the host
  1220. * components font, etc. The hosting LabelView is used to
  1221. * satisfy the request since it is connected to the view
  1222. * hierarchy for the life of the element it represents where
  1223. * the fragments are fairly transient.
  1224. *
  1225. * @return the container, null if none
  1226. */
  1227. public Container getContainer() {
  1228. return LabelView.this.getContainer();
  1229. }
  1230. public String toString() {
  1231. String s = "LabelFrag: elem(" + getStartOffset() + ", "
  1232. + getEndOffset() + ")\n";
  1233. return s;
  1234. }
  1235. /**
  1236. * Paints the composed text in this element.
  1237. */
  1238. void paintComposedText(Graphics2D g2d, Rectangle alloc, int p0, int p1) {
  1239. AttributeSet attrSet = getElement().getAttributes();
  1240. AttributedString as =
  1241. (AttributedString)attrSet.getAttribute(StyleConstants.ComposedTextAttribute);
  1242. int start = getElement().getStartOffset();
  1243. int y = alloc.y + (int)glyphs.getLineMetrics().getAscent();
  1244. int x = alloc.x;
  1245. /*
  1246. * Add text attributes
  1247. */
  1248. as.addAttribute(TextAttribute.FONT, font);
  1249. as.addAttribute(TextAttribute.FOREGROUND, fg);
  1250. if (StyleConstants.isBold(attrSet)) {
  1251. as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
  1252. }
  1253. if (StyleConstants.isItalic(attrSet)) {
  1254. as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
  1255. }
  1256. if (underline) {
  1257. as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
  1258. }
  1259. if (strike) {
  1260. as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
  1261. }
  1262. if (superscript) {
  1263. as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
  1264. }
  1265. if (subscript) {
  1266. as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
  1267. }
  1268. // draw
  1269. AttributedCharacterIterator aci = as.getIterator(null, p0 - start, p1 - start);
  1270. TextLayout layout = new TextLayout(aci, g2d.getFontRenderContext());
  1271. layout.draw(g2d, x, y);
  1272. }
  1273. // ---- variables ---------------------------------
  1274. short offset;
  1275. short length;
  1276. int x;
  1277. boolean rightToLeft;
  1278. ExtendedTextLabel glyphs;
  1279. }
  1280. }