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