1. /*
  2. * @(#)Utilities.java 1.40 03/01/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.lang.reflect.Method;
  9. import java.awt.Rectangle;
  10. import java.awt.Graphics;
  11. import java.awt.FontMetrics;
  12. import java.awt.Shape;
  13. import java.awt.Toolkit;
  14. import java.awt.Graphics2D;
  15. import java.awt.font.FontRenderContext;
  16. import java.awt.font.TextLayout;
  17. import java.awt.font.TextAttribute;
  18. import java.text.*;
  19. import javax.swing.SwingConstants;
  20. /**
  21. * A collection of methods to deal with various text
  22. * related activities.
  23. *
  24. * @author Timothy Prinzing
  25. * @version 1.40 01/23/03
  26. */
  27. public class Utilities {
  28. /**
  29. * Draws the given text, expanding any tabs that are contained
  30. * using the given tab expansion technique. This particular
  31. * implementation renders in a 1.1 style coordinate system
  32. * where ints are used and 72dpi is assumed.
  33. *
  34. * @param s the source of the text
  35. * @param x the X origin >= 0
  36. * @param y the Y origin >= 0
  37. * @param g the graphics context
  38. * @param e how to expand the tabs. If this value is null,
  39. * tabs will be expanded as a space character.
  40. * @param startOffset starting offset of the text in the document >= 0
  41. * @return the X location at the end of the rendered text
  42. */
  43. public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
  44. TabExpander e, int startOffset) {
  45. FontMetrics metrics = g.getFontMetrics();
  46. int nextX = x;
  47. char[] txt = s.array;
  48. int txtOffset = s.offset;
  49. int flushLen = 0;
  50. int flushIndex = s.offset;
  51. int n = s.offset + s.count;
  52. for (int i = txtOffset; i < n; i++) {
  53. if (txt[i] == '\t') {
  54. if (flushLen > 0) {
  55. g.drawChars(txt, flushIndex, flushLen, x, y);
  56. flushLen = 0;
  57. }
  58. flushIndex = i + 1;
  59. if (e != null) {
  60. nextX = (int) e.nextTabStop((float) nextX, startOffset + i - txtOffset);
  61. } else {
  62. nextX += metrics.charWidth(' ');
  63. }
  64. x = nextX;
  65. } else if ((txt[i] == '\n') || (txt[i] == '\r')) {
  66. if (flushLen > 0) {
  67. g.drawChars(txt, flushIndex, flushLen, x, y);
  68. flushLen = 0;
  69. }
  70. flushIndex = i + 1;
  71. x = nextX;
  72. } else {
  73. flushLen += 1;
  74. nextX += metrics.charWidth(txt[i]);
  75. }
  76. }
  77. if (flushLen > 0) {
  78. g.drawChars(txt, flushIndex, flushLen, x, y);
  79. }
  80. return nextX;
  81. }
  82. /**
  83. * Determines the width of the given segment of text taking tabs
  84. * into consideration. This is implemented in a 1.1 style coordinate
  85. * system where ints are used and 72dpi is assumed.
  86. *
  87. * @param s the source of the text
  88. * @param metrics the font metrics to use for the calculation
  89. * @param x the X origin >= 0
  90. * @param e how to expand the tabs. If this value is null,
  91. * tabs will be expanded as a space character.
  92. * @param startOffset starting offset of the text in the document >= 0
  93. * @return the width of the text
  94. */
  95. public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x,
  96. TabExpander e, int startOffset) {
  97. int nextX = x;
  98. char[] txt = s.array;
  99. int txtOffset = s.offset;
  100. int n = s.offset + s.count;
  101. for (int i = txtOffset; i < n; i++) {
  102. if (txt[i] == '\t') {
  103. if (e != null) {
  104. nextX = (int) e.nextTabStop((float) nextX,
  105. startOffset + i - txtOffset);
  106. } else {
  107. nextX += metrics.charWidth(' ');
  108. }
  109. } else if(txt[i] != '\n') {
  110. nextX += metrics.charWidth(txt[i]);
  111. }
  112. // Ignore newlines, they take up space and we shouldn't be
  113. // counting them.
  114. }
  115. return nextX - x;
  116. }
  117. /**
  118. * Determines the relative offset into the given text that
  119. * best represents the given span in the view coordinate
  120. * system. This is implemented in a 1.1 style coordinate
  121. * system where ints are used and 72dpi is assumed.
  122. *
  123. * @param s the source of the text
  124. * @param metrics the font metrics to use for the calculation
  125. * @param x0 the starting view location representing the start
  126. * of the given text >= 0.
  127. * @param x the target view location to translate to an
  128. * offset into the text >= 0.
  129. * @param e how to expand the tabs. If this value is null,
  130. * tabs will be expanded as a space character.
  131. * @param startOffset starting offset of the text in the document >= 0
  132. * @return the offset into the text >= 0
  133. */
  134. public static final int getTabbedTextOffset(Segment s, FontMetrics metrics,
  135. int x0, int x, TabExpander e,
  136. int startOffset) {
  137. return getTabbedTextOffset(s, metrics, x0, x, e, startOffset, true);
  138. }
  139. public static final int getTabbedTextOffset(Segment s,
  140. FontMetrics metrics,
  141. int x0, int x, TabExpander e,
  142. int startOffset,
  143. boolean round) {
  144. if (x0 >= x) {
  145. // x before x0, return.
  146. return 0;
  147. }
  148. int currX = x0;
  149. int nextX = currX;
  150. // s may be a shared segment, so it is copied prior to calling
  151. // the tab expander
  152. char[] txt = s.array;
  153. int txtOffset = s.offset;
  154. int txtCount = s.count;
  155. int n = s.offset + s.count;
  156. for (int i = s.offset; i < n; i++) {
  157. if (txt[i] == '\t') {
  158. if (e != null) {
  159. nextX = (int) e.nextTabStop((float) nextX,
  160. startOffset + i - txtOffset);
  161. } else {
  162. nextX += metrics.charWidth(' ');
  163. }
  164. } else {
  165. nextX += metrics.charWidth(txt[i]);
  166. }
  167. if ((x >= currX) && (x < nextX)) {
  168. // found the hit position... return the appropriate side
  169. if ((round == false) || ((x - currX) < (nextX - x))) {
  170. return i - txtOffset;
  171. } else {
  172. return i + 1 - txtOffset;
  173. }
  174. }
  175. currX = nextX;
  176. }
  177. // didn't find, return end offset
  178. return txtCount;
  179. }
  180. /**
  181. * Determine where to break the given text to fit
  182. * within the the given span. This tries to find a
  183. * whitespace boundary.
  184. * @param s the source of the text
  185. * @param metrics the font metrics to use for the calculation
  186. * @param x0 the starting view location representing the start
  187. * of the given text.
  188. * @param x the target view location to translate to an
  189. * offset into the text.
  190. * @param e how to expand the tabs. If this value is null,
  191. * tabs will be expanded as a space character.
  192. * @param startOffset starting offset in the document of the text
  193. * @return the offset into the given text
  194. */
  195. public static final int getBreakLocation(Segment s, FontMetrics metrics,
  196. int x0, int x, TabExpander e,
  197. int startOffset) {
  198. char[] txt = s.array;
  199. int txtOffset = s.offset;
  200. int txtCount = s.count;
  201. int index = Utilities.getTabbedTextOffset(s, metrics, x0, x,
  202. e, startOffset, false);
  203. for (int i = txtOffset + Math.min(index, txtCount - 1);
  204. i >= txtOffset; i--) {
  205. char ch = txt[i];
  206. if (Character.isWhitespace(ch)) {
  207. // found whitespace, break here
  208. index = i - txtOffset + 1;
  209. break;
  210. }
  211. }
  212. return index;
  213. }
  214. /**
  215. * Determines the starting row model position of the row that contains
  216. * the specified model position. The component given must have a
  217. * size to compute the result. If the component doesn't have a size
  218. * a value of -1 will be returned.
  219. *
  220. * @param c the editor
  221. * @param offs the offset in the document >= 0
  222. * @return the position >= 0 if the request can be computed, otherwise
  223. * a value of -1 will be returned.
  224. * @exception BadLocationException if the offset is out of range
  225. */
  226. public static final int getRowStart(JTextComponent c, int offs) throws BadLocationException {
  227. Rectangle r = c.modelToView(offs);
  228. if (r == null) {
  229. return -1;
  230. }
  231. int lastOffs = offs;
  232. int y = r.y;
  233. while ((r != null) && (y == r.y)) {
  234. offs = lastOffs;
  235. lastOffs -= 1;
  236. r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
  237. }
  238. return offs;
  239. }
  240. /**
  241. * Determines the ending row model position of the row that contains
  242. * the specified model position. The component given must have a
  243. * size to compute the result. If the component doesn't have a size
  244. * a value of -1 will be returned.
  245. *
  246. * @param c the editor
  247. * @param offs the offset in the document >= 0
  248. * @return the position >= 0 if the request can be computed, otherwise
  249. * a value of -1 will be returned.
  250. * @exception BadLocationException if the offset is out of range
  251. */
  252. public static final int getRowEnd(JTextComponent c, int offs) throws BadLocationException {
  253. Rectangle r = c.modelToView(offs);
  254. if (r == null) {
  255. return -1;
  256. }
  257. int n = c.getDocument().getLength();
  258. int lastOffs = offs;
  259. int y = r.y;
  260. while ((r != null) && (y == r.y)) {
  261. offs = lastOffs;
  262. lastOffs += 1;
  263. r = (lastOffs <= n) ? c.modelToView(lastOffs) : null;
  264. }
  265. return offs;
  266. }
  267. /**
  268. * Determines the position in the model that is closest to the given
  269. * view location in the row above. The component given must have a
  270. * size to compute the result. If the component doesn't have a size
  271. * a value of -1 will be returned.
  272. *
  273. * @param c the editor
  274. * @param offs the offset in the document >= 0
  275. * @param x the X coordinate >= 0
  276. * @return the position >= 0 if the request can be computed, otherwise
  277. * a value of -1 will be returned.
  278. * @exception BadLocationException if the offset is out of range
  279. */
  280. public static final int getPositionAbove(JTextComponent c, int offs, int x) throws BadLocationException {
  281. int lastOffs = getRowStart(c, offs) - 1;
  282. if (lastOffs < 0) {
  283. return -1;
  284. }
  285. int bestSpan = Short.MAX_VALUE;
  286. int y = 0;
  287. Rectangle r = null;
  288. if (lastOffs >= 0) {
  289. r = c.modelToView(lastOffs);
  290. y = r.y;
  291. }
  292. while ((r != null) && (y == r.y)) {
  293. int span = Math.abs(r.x - x);
  294. if (span < bestSpan) {
  295. offs = lastOffs;
  296. bestSpan = span;
  297. }
  298. lastOffs -= 1;
  299. r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
  300. }
  301. return offs;
  302. }
  303. /**
  304. * Determines the position in the model that is closest to the given
  305. * view location in the row below. The component given must have a
  306. * size to compute the result. If the component doesn't have a size
  307. * a value of -1 will be returned.
  308. *
  309. * @param c the editor
  310. * @param offs the offset in the document >= 0
  311. * @param x the X coordinate >= 0
  312. * @return the position >= 0 if the request can be computed, otherwise
  313. * a value of -1 will be returned.
  314. * @exception BadLocationException if the offset is out of range
  315. */
  316. public static final int getPositionBelow(JTextComponent c, int offs, int x) throws BadLocationException {
  317. int lastOffs = getRowEnd(c, offs) + 1;
  318. if (lastOffs <= 0) {
  319. return -1;
  320. }
  321. int bestSpan = Short.MAX_VALUE;
  322. int n = c.getDocument().getLength();
  323. int y = 0;
  324. Rectangle r = null;
  325. if (lastOffs <= n) {
  326. r = c.modelToView(lastOffs);
  327. y = r.y;
  328. }
  329. while ((r != null) && (y == r.y)) {
  330. int span = Math.abs(x - r.x);
  331. if (span < bestSpan) {
  332. offs = lastOffs;
  333. bestSpan = span;
  334. }
  335. lastOffs += 1;
  336. r = (lastOffs <= n) ? c.modelToView(lastOffs) : null;
  337. }
  338. return offs;
  339. }
  340. /**
  341. * Determines the start of a word for the given model location.
  342. * Uses BreakIterator.getWordInstance() to actually get the words.
  343. *
  344. * @param c the editor
  345. * @param offs the offset in the document >= 0
  346. * @return the location in the model of the word start >= 0
  347. * @exception BadLocationException if the offset is out of range
  348. */
  349. public static final int getWordStart(JTextComponent c, int offs) throws BadLocationException {
  350. Document doc = c.getDocument();
  351. Element line = getParagraphElement(c, offs);
  352. if (line == null) {
  353. throw new BadLocationException("No word at " + offs, offs);
  354. }
  355. int lineStart = line.getStartOffset();
  356. int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  357. String s = doc.getText(lineStart, lineEnd - lineStart);
  358. if(s != null && s.length() > 0) {
  359. BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  360. words.setText(s);
  361. int wordPosition = offs - lineStart;
  362. if(wordPosition >= words.last()) {
  363. wordPosition = words.last() - 1;
  364. }
  365. words.following(wordPosition);
  366. offs = lineStart + words.previous();
  367. }
  368. return offs;
  369. }
  370. /**
  371. * Determines the end of a word for the given location.
  372. * Uses BreakIterator.getWordInstance() to actually get the words.
  373. *
  374. * @param c the editor
  375. * @param offs the offset in the document >= 0
  376. * @return the location in the model of the word end >= 0
  377. * @exception BadLocationException if the offset is out of range
  378. */
  379. public static final int getWordEnd(JTextComponent c, int offs) throws BadLocationException {
  380. Document doc = c.getDocument();
  381. Element line = getParagraphElement(c, offs);
  382. if (line == null) {
  383. throw new BadLocationException("No word at " + offs, offs);
  384. }
  385. int lineStart = line.getStartOffset();
  386. int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  387. String s = doc.getText(lineStart, lineEnd - lineStart);
  388. if(s != null && s.length() > 0) {
  389. BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  390. words.setText(s);
  391. int wordPosition = offs - lineStart;
  392. if(wordPosition >= words.last()) {
  393. wordPosition = words.last() - 1;
  394. }
  395. offs = lineStart + words.following(wordPosition);
  396. }
  397. return offs;
  398. }
  399. /**
  400. * Determines the start of the next word for the given location.
  401. * Uses BreakIterator.getWordInstance() to actually get the words.
  402. *
  403. * @param c the editor
  404. * @param offs the offset in the document >= 0
  405. * @return the location in the model of the word start >= 0
  406. * @exception BadLocationException if the offset is out of range
  407. */
  408. public static final int getNextWord(JTextComponent c, int offs) throws BadLocationException {
  409. int nextWord;
  410. Element line = getParagraphElement(c, offs);
  411. for (nextWord = getNextWordInParagraph(c, line, offs, false);
  412. nextWord == BreakIterator.DONE;
  413. nextWord = getNextWordInParagraph(c, line, offs, true)) {
  414. // didn't find in this line, try the next line
  415. offs = line.getEndOffset();
  416. line = getParagraphElement(c, offs);
  417. }
  418. return nextWord;
  419. }
  420. /**
  421. * Finds the next word in the given elements text. The first
  422. * parameter allows searching multiple paragraphs where even
  423. * the first offset is desired.
  424. * Returns the offset of the next word, or BreakIterator.DONE
  425. * if there are no more words in the element.
  426. */
  427. static int getNextWordInParagraph(JTextComponent c, Element line, int offs, boolean first) throws BadLocationException {
  428. if (line == null) {
  429. throw new BadLocationException("No more words", offs);
  430. }
  431. Document doc = line.getDocument();
  432. int lineStart = line.getStartOffset();
  433. int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  434. if ((offs >= lineEnd) || (offs < lineStart)) {
  435. throw new BadLocationException("No more words", offs);
  436. }
  437. String s = doc.getText(lineStart, lineEnd - lineStart);
  438. BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  439. words.setText(s);
  440. if ((first && (words.first() == (offs - lineStart))) &&
  441. (! Character.isWhitespace(s.charAt(words.first())))) {
  442. return offs;
  443. }
  444. int wordPosition = words.following(offs - lineStart);
  445. if ((wordPosition == BreakIterator.DONE) ||
  446. (wordPosition >= s.length())) {
  447. // there are no more words on this line.
  448. return BreakIterator.DONE;
  449. }
  450. // if we haven't shot past the end... check to
  451. // see if the current boundary represents whitespace.
  452. // if so, we need to try again
  453. char ch = s.charAt(wordPosition);
  454. if (! Character.isWhitespace(ch)) {
  455. return lineStart + wordPosition;
  456. }
  457. // it was whitespace, try again. The assumption
  458. // is that it must be a word start if the last
  459. // one had whitespace following it.
  460. wordPosition = words.next();
  461. if (wordPosition != BreakIterator.DONE) {
  462. offs = lineStart + wordPosition;
  463. if (offs != lineEnd) {
  464. return offs;
  465. }
  466. }
  467. return BreakIterator.DONE;
  468. }
  469. /**
  470. * Determine the start of the prev word for the given location.
  471. * Uses BreakIterator.getWordInstance() to actually get the words.
  472. *
  473. * @param c the editor
  474. * @param offs the offset in the document >= 0
  475. * @return the location in the model of the word start >= 0
  476. * @exception BadLocationException if the offset is out of range
  477. */
  478. public static final int getPreviousWord(JTextComponent c, int offs) throws BadLocationException {
  479. int prevWord;
  480. Element line = getParagraphElement(c, offs);
  481. for (prevWord = getPrevWordInParagraph(c, line, offs);
  482. prevWord == BreakIterator.DONE;
  483. prevWord = getPrevWordInParagraph(c, line, offs)) {
  484. // didn't find in this line, try the prev line
  485. offs = line.getStartOffset() - 1;
  486. line = getParagraphElement(c, offs);
  487. }
  488. return prevWord;
  489. }
  490. /**
  491. * Finds the previous word in the given elements text. The first
  492. * parameter allows searching multiple paragraphs where even
  493. * the first offset is desired.
  494. * Returns the offset of the next word, or BreakIterator.DONE
  495. * if there are no more words in the element.
  496. */
  497. static int getPrevWordInParagraph(JTextComponent c, Element line, int offs) throws BadLocationException {
  498. if (line == null) {
  499. throw new BadLocationException("No more words", offs);
  500. }
  501. Document doc = line.getDocument();
  502. int lineStart = line.getStartOffset();
  503. int lineEnd = line.getEndOffset();
  504. if ((offs > lineEnd) || (offs < lineStart)) {
  505. throw new BadLocationException("No more words", offs);
  506. }
  507. String s = doc.getText(lineStart, lineEnd - lineStart);
  508. BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
  509. words.setText(s);
  510. if (words.following(offs - lineStart) == BreakIterator.DONE) {
  511. words.last();
  512. }
  513. int wordPosition = words.previous();
  514. if (wordPosition == (offs - lineStart)) {
  515. wordPosition = words.previous();
  516. }
  517. if (wordPosition == BreakIterator.DONE) {
  518. // there are no more words on this line.
  519. return BreakIterator.DONE;
  520. }
  521. // if we haven't shot past the end... check to
  522. // see if the current boundary represents whitespace.
  523. // if so, we need to try again
  524. char ch = s.charAt(wordPosition);
  525. if (! Character.isWhitespace(ch)) {
  526. return lineStart + wordPosition;
  527. }
  528. // it was whitespace, try again. The assumption
  529. // is that it must be a word start if the last
  530. // one had whitespace following it.
  531. wordPosition = words.previous();
  532. if (wordPosition != BreakIterator.DONE) {
  533. return lineStart + wordPosition;
  534. }
  535. return BreakIterator.DONE;
  536. }
  537. /**
  538. * Determines the element to use for a paragraph/line.
  539. *
  540. * @param c the editor
  541. * @param offs the starting offset in the document >= 0
  542. * @return the element
  543. */
  544. public static final Element getParagraphElement(JTextComponent c, int offs) {
  545. Document doc = c.getDocument();
  546. if (doc instanceof StyledDocument) {
  547. return ((StyledDocument)doc).getParagraphElement(offs);
  548. }
  549. Element map = doc.getDefaultRootElement();
  550. int index = map.getElementIndex(offs);
  551. Element paragraph = map.getElement(index);
  552. if ((offs >= paragraph.getStartOffset()) && (offs < paragraph.getEndOffset())) {
  553. return paragraph;
  554. }
  555. return null;
  556. }
  557. static boolean isComposedTextElement(Document doc, int offset) {
  558. Element elem = doc.getDefaultRootElement();
  559. while (!elem.isLeaf()) {
  560. elem = elem.getElement(elem.getElementIndex(offset));
  561. }
  562. return isComposedTextElement(elem);
  563. }
  564. static boolean isComposedTextElement(Element elem) {
  565. AttributeSet as = elem.getAttributes();
  566. return isComposedTextAttributeDefined(as);
  567. }
  568. static boolean isComposedTextAttributeDefined(AttributeSet as) {
  569. return ((as != null) &&
  570. (as.isDefined(StyleConstants.ComposedTextAttribute)));
  571. }
  572. /**
  573. * Draws the given composed text passed from an input method.
  574. *
  575. * @param attr the attributes containing the composed text
  576. * @param g the graphics context
  577. * @param x the X origin
  578. * @param y the Y origin
  579. * @param p0 starting offset in the composed text to be rendered
  580. * @param p1 ending offset in the composed text to be rendered
  581. * @return the new insertion position
  582. */
  583. static int drawComposedText(AttributeSet attr, Graphics g, int x, int y,
  584. int p0, int p1) throws BadLocationException {
  585. Graphics2D g2d = (Graphics2D)g;
  586. AttributedString as = (AttributedString)attr.getAttribute(
  587. StyleConstants.ComposedTextAttribute);
  588. as.addAttribute(TextAttribute.FONT, g.getFont());
  589. if (p0 >= p1)
  590. return x;
  591. AttributedCharacterIterator aci = as.getIterator(null, p0, p1);
  592. // Create text layout
  593. TextLayout layout = new TextLayout(aci, g2d.getFontRenderContext());
  594. // draw
  595. layout.draw(g2d, x, y);
  596. return x + (int)layout.getAdvance();
  597. }
  598. /**
  599. * Paints the composed text in a GlyphView
  600. */
  601. static void paintComposedText(Graphics g, Rectangle alloc, GlyphView v) {
  602. if (g instanceof Graphics2D) {
  603. Graphics2D g2d = (Graphics2D) g;
  604. int p0 = v.getStartOffset();
  605. int p1 = v.getEndOffset();
  606. AttributeSet attrSet = v.getElement().getAttributes();
  607. AttributedString as =
  608. (AttributedString)attrSet.getAttribute(StyleConstants.ComposedTextAttribute);
  609. int start = v.getElement().getStartOffset();
  610. int y = alloc.y + (int) v.getGlyphPainter().getAscent(v);
  611. int x = alloc.x;
  612. //Add text attributes
  613. as.addAttribute(TextAttribute.FONT, v.getFont());
  614. as.addAttribute(TextAttribute.FOREGROUND, v.getForeground());
  615. if (StyleConstants.isBold(v.getAttributes())) {
  616. as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
  617. }
  618. if (StyleConstants.isItalic(v.getAttributes())) {
  619. as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
  620. }
  621. if (v.isUnderline()) {
  622. as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
  623. }
  624. if (v.isStrikeThrough()) {
  625. as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
  626. }
  627. if (v.isSuperscript()) {
  628. as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
  629. }
  630. if (v.isSubscript()) {
  631. as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
  632. }
  633. // draw
  634. AttributedCharacterIterator aci = as.getIterator(null, p0 - start, p1 - start);
  635. TextLayout layout = new TextLayout(aci, g2d.getFontRenderContext());
  636. layout.draw(g2d, x, y);
  637. }
  638. }
  639. /*
  640. * Convenience function for determining ComponentOrientation. Helps us
  641. * avoid having Munge directives throughout the code.
  642. */
  643. static boolean isLeftToRight( java.awt.Component c ) {
  644. return c.getComponentOrientation().isLeftToRight();
  645. }
  646. /**
  647. * Provides a way to determine the next visually represented model
  648. * location that one might place a caret. Some views may not be visible,
  649. * they might not be in the same order found in the model, or they just
  650. * might not allow access to some of the locations in the model.
  651. * <p>
  652. * This implementation assumes the views are layed out in a logical
  653. * manner. That is, that the view at index x + 1 is visually after
  654. * the View at index x, and that the View at index x - 1 is visually
  655. * before the View at x. There is support for reversing this behavior
  656. * only if the passed in <code>View</code> is an instance of
  657. * <code>CompositeView</code>. The <code>CompositeView</code>
  658. * must then override the <code>flipEastAndWestAtEnds</code> method.
  659. *
  660. * @param v View to query
  661. * @param pos the position to convert >= 0
  662. * @param a the allocated region to render into
  663. * @param direction the direction from the current position that can
  664. * be thought of as the arrow keys typically found on a keyboard;
  665. * this may be one of the following:
  666. * <ul>
  667. * <li><code>SwingConstants.WEST</code>
  668. * <li><code>SwingConstants.EAST</code>
  669. * <li><code>SwingConstants.NORTH</code>
  670. * <li><code>SwingConstants.SOUTH</code>
  671. * </ul>
  672. * @param biasRet an array contain the bias that was checked
  673. * @return the location within the model that best represents the next
  674. * location visual position
  675. * @exception BadLocationException
  676. * @exception IllegalArgumentException if <code>direction</code> is invalid
  677. */
  678. static int getNextVisualPositionFrom(View v, int pos, Position.Bias b,
  679. Shape alloc, int direction,
  680. Position.Bias[] biasRet)
  681. throws BadLocationException {
  682. if (v.getViewCount() == 0) {
  683. // Nothing to do.
  684. return pos;
  685. }
  686. boolean top = (direction == SwingConstants.NORTH ||
  687. direction == SwingConstants.WEST);
  688. int retValue;
  689. if (pos == -1) {
  690. // Start from the first View.
  691. int childIndex = (top) ? v.getViewCount() - 1 : 0;
  692. View child = v.getView(childIndex);
  693. Shape childBounds = v.getChildAllocation(childIndex, alloc);
  694. retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
  695. direction, biasRet);
  696. if (retValue == -1 && !top && v.getViewCount() > 1) {
  697. // Special case that should ONLY happen if first view
  698. // isn't valid (can happen when end position is put at
  699. // beginning of line.
  700. child = v.getView(1);
  701. childBounds = v.getChildAllocation(1, alloc);
  702. retValue = child.getNextVisualPositionFrom(-1, biasRet[0],
  703. childBounds,
  704. direction, biasRet);
  705. }
  706. }
  707. else {
  708. int increment = (top) ? -1 : 1;
  709. int childIndex;
  710. if (b == Position.Bias.Backward && pos > 0) {
  711. childIndex = v.getViewIndex(pos - 1, Position.Bias.Forward);
  712. }
  713. else {
  714. childIndex = v.getViewIndex(pos, Position.Bias.Forward);
  715. }
  716. View child = v.getView(childIndex);
  717. Shape childBounds = v.getChildAllocation(childIndex, alloc);
  718. retValue = child.getNextVisualPositionFrom(pos, b, childBounds,
  719. direction, biasRet);
  720. if ((direction == SwingConstants.EAST ||
  721. direction == SwingConstants.WEST) &&
  722. (v instanceof CompositeView) &&
  723. ((CompositeView)v).flipEastAndWestAtEnds(pos, b)) {
  724. increment *= -1;
  725. }
  726. childIndex += increment;
  727. if (retValue == -1 && childIndex >= 0 &&
  728. childIndex < v.getViewCount()) {
  729. child = v.getView(childIndex);
  730. childBounds = v.getChildAllocation(childIndex, alloc);
  731. retValue = child.getNextVisualPositionFrom(
  732. -1, b, childBounds, direction, biasRet);
  733. // If there is a bias change, it is a fake position
  734. // and we should skip it. This is usually the result
  735. // of two elements side be side flowing the same way.
  736. if (retValue == pos && biasRet[0] != b) {
  737. return getNextVisualPositionFrom(v, pos, biasRet[0],
  738. alloc, direction,
  739. biasRet);
  740. }
  741. }
  742. else if (retValue != -1 && biasRet[0] != b &&
  743. ((increment == 1 && child.getEndOffset() == retValue) ||
  744. (increment == -1 &&
  745. child.getStartOffset() == retValue)) &&
  746. childIndex >= 0 && childIndex < v.getViewCount()) {
  747. // Reached the end of a view, make sure the next view
  748. // is a different direction.
  749. child = v.getView(childIndex);
  750. childBounds = v.getChildAllocation(childIndex, alloc);
  751. Position.Bias originalBias = biasRet[0];
  752. int nextPos = child.getNextVisualPositionFrom(
  753. -1, b, childBounds, direction, biasRet);
  754. if (biasRet[0] == b) {
  755. retValue = nextPos;
  756. }
  757. else {
  758. biasRet[0] = originalBias;
  759. }
  760. }
  761. }
  762. return retValue;
  763. }
  764. }