1. /*
  2. * @(#)DefaultHighlighter.java 1.39 03/12/19
  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.util.Vector;
  9. import java.awt.*;
  10. import javax.swing.plaf.*;
  11. import javax.swing.*;
  12. /**
  13. * Implements the Highlighter interfaces. Implements a simple highlight
  14. * painter that renders in a solid color.
  15. *
  16. * @author Timothy Prinzing
  17. * @version 1.39 12/19/03
  18. * @see Highlighter
  19. */
  20. public class DefaultHighlighter extends LayeredHighlighter {
  21. /**
  22. * Creates a new DefaultHighlighther object.
  23. */
  24. public DefaultHighlighter() {
  25. drawsLayeredHighlights = true;
  26. }
  27. // ---- Highlighter methods ----------------------------------------------
  28. /**
  29. * Renders the highlights.
  30. *
  31. * @param g the graphics context
  32. */
  33. public void paint(Graphics g) {
  34. // PENDING(prinz) - should cull ranges not visible
  35. int len = highlights.size();
  36. for (int i = 0; i < len; i++) {
  37. HighlightInfo info = (HighlightInfo) highlights.elementAt(i);
  38. if (!(info instanceof LayeredHighlightInfo)) {
  39. // Avoid allocing unless we need it.
  40. Rectangle a = component.getBounds();
  41. Insets insets = component.getInsets();
  42. a.x = insets.left;
  43. a.y = insets.top;
  44. a.width -= insets.left + insets.right;
  45. a.height -= insets.top + insets.bottom;
  46. for (; i < len; i++) {
  47. info = (HighlightInfo)highlights.elementAt(i);
  48. if (!(info instanceof LayeredHighlightInfo)) {
  49. Highlighter.HighlightPainter p = info.getPainter();
  50. p.paint(g, info.getStartOffset(), info.getEndOffset(),
  51. a, component);
  52. }
  53. }
  54. }
  55. }
  56. }
  57. /**
  58. * Called when the UI is being installed into the
  59. * interface of a JTextComponent. Installs the editor, and
  60. * removes any existing highlights.
  61. *
  62. * @param c the editor component
  63. * @see Highlighter#install
  64. */
  65. public void install(JTextComponent c) {
  66. component = c;
  67. removeAllHighlights();
  68. }
  69. /**
  70. * Called when the UI is being removed from the interface of
  71. * a JTextComponent.
  72. *
  73. * @param c the component
  74. * @see Highlighter#deinstall
  75. */
  76. public void deinstall(JTextComponent c) {
  77. component = null;
  78. }
  79. /**
  80. * Adds a highlight to the view. Returns a tag that can be used
  81. * to refer to the highlight.
  82. *
  83. * @param p0 the start offset of the range to highlight >= 0
  84. * @param p1 the end offset of the range to highlight >= p0
  85. * @param p the painter to use to actually render the highlight
  86. * @return an object that can be used as a tag
  87. * to refer to the highlight
  88. * @exception BadLocationException if the specified location is invalid
  89. */
  90. public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException {
  91. Document doc = component.getDocument();
  92. HighlightInfo i = (getDrawsLayeredHighlights() &&
  93. (p instanceof LayeredHighlighter.LayerPainter)) ?
  94. new LayeredHighlightInfo() : new HighlightInfo();
  95. i.painter = p;
  96. i.p0 = doc.createPosition(p0);
  97. i.p1 = doc.createPosition(p1);
  98. highlights.addElement(i);
  99. safeDamageRange(p0, p1);
  100. return i;
  101. }
  102. /**
  103. * Removes a highlight from the view.
  104. *
  105. * @param tag the reference to the highlight
  106. */
  107. public void removeHighlight(Object tag) {
  108. if (tag instanceof LayeredHighlightInfo) {
  109. LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  110. if (lhi.width > 0 && lhi.height > 0) {
  111. component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
  112. }
  113. }
  114. else {
  115. HighlightInfo info = (HighlightInfo) tag;
  116. safeDamageRange(info.p0, info.p1);
  117. }
  118. highlights.removeElement(tag);
  119. }
  120. /**
  121. * Removes all highlights.
  122. */
  123. public void removeAllHighlights() {
  124. TextUI mapper = component.getUI();
  125. if (getDrawsLayeredHighlights()) {
  126. int len = highlights.size();
  127. if (len != 0) {
  128. int minX = 0;
  129. int minY = 0;
  130. int maxX = 0;
  131. int maxY = 0;
  132. int p0 = -1;
  133. int p1 = -1;
  134. for (int i = 0; i < len; i++) {
  135. HighlightInfo hi = (HighlightInfo)highlights.elementAt(i);
  136. if (hi instanceof LayeredHighlightInfo) {
  137. LayeredHighlightInfo info = (LayeredHighlightInfo)hi;
  138. minX = Math.min(minX, info.x);
  139. minY = Math.min(minY, info.y);
  140. maxX = Math.max(maxX, info.x + info.width);
  141. maxY = Math.max(maxY, info.y + info.height);
  142. }
  143. else {
  144. if (p0 == -1) {
  145. p0 = hi.p0.getOffset();
  146. p1 = hi.p1.getOffset();
  147. }
  148. else {
  149. p0 = Math.min(p0, hi.p0.getOffset());
  150. p1 = Math.max(p1, hi.p1.getOffset());
  151. }
  152. }
  153. }
  154. if (minX != maxX && minY != maxY) {
  155. component.repaint(minX, minY, maxX - minX, maxY - minY);
  156. }
  157. if (p0 != -1) {
  158. try {
  159. safeDamageRange(p0, p1);
  160. } catch (BadLocationException e) {}
  161. }
  162. highlights.removeAllElements();
  163. }
  164. }
  165. else if (mapper != null) {
  166. int len = highlights.size();
  167. if (len != 0) {
  168. int p0 = Integer.MAX_VALUE;
  169. int p1 = 0;
  170. for (int i = 0; i < len; i++) {
  171. HighlightInfo info = (HighlightInfo) highlights.elementAt(i);
  172. p0 = Math.min(p0, info.p0.getOffset());
  173. p1 = Math.max(p1, info.p1.getOffset());
  174. }
  175. try {
  176. safeDamageRange(p0, p1);
  177. } catch (BadLocationException e) {}
  178. highlights.removeAllElements();
  179. }
  180. }
  181. }
  182. /**
  183. * Changes a highlight.
  184. *
  185. * @param tag the highlight tag
  186. * @param p0 the beginning of the range >= 0
  187. * @param p1 the end of the range >= p0
  188. * @exception BadLocationException if the specified location is invalid
  189. */
  190. public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException {
  191. Document doc = component.getDocument();
  192. if (tag instanceof LayeredHighlightInfo) {
  193. LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  194. if (lhi.width > 0 && lhi.height > 0) {
  195. component.repaint(lhi.x, lhi.y, lhi.width, lhi.height);
  196. }
  197. // Mark the highlights region as invalid, it will reset itself
  198. // next time asked to paint.
  199. lhi.width = lhi.height = 0;
  200. lhi.p0 = doc.createPosition(p0);
  201. lhi.p1 = doc.createPosition(p1);
  202. safeDamageRange(Math.min(p0, p1), Math.max(p0, p1));
  203. }
  204. else {
  205. HighlightInfo info = (HighlightInfo) tag;
  206. int oldP0 = info.p0.getOffset();
  207. int oldP1 = info.p1.getOffset();
  208. if (p0 == oldP0) {
  209. safeDamageRange(Math.min(oldP1, p1),
  210. Math.max(oldP1, p1));
  211. } else if (p1 == oldP1) {
  212. safeDamageRange(Math.min(p0, oldP0),
  213. Math.max(p0, oldP0));
  214. } else {
  215. safeDamageRange(oldP0, oldP1);
  216. safeDamageRange(p0, p1);
  217. }
  218. info.p0 = doc.createPosition(p0);
  219. info.p1 = doc.createPosition(p1);
  220. }
  221. }
  222. /**
  223. * Makes a copy of the highlights. Does not actually clone each highlight,
  224. * but only makes references to them.
  225. *
  226. * @return the copy
  227. * @see Highlighter#getHighlights
  228. */
  229. public Highlighter.Highlight[] getHighlights() {
  230. int size = highlights.size();
  231. if (size == 0) {
  232. return noHighlights;
  233. }
  234. Highlighter.Highlight[] h = new Highlighter.Highlight[size];
  235. highlights.copyInto(h);
  236. return h;
  237. }
  238. /**
  239. * When leaf Views (such as LabelView) are rendering they should
  240. * call into this method. If a highlight is in the given region it will
  241. * be drawn immediately.
  242. *
  243. * @param g Graphics used to draw
  244. * @param p0 starting offset of view
  245. * @param p1 ending offset of view
  246. * @param viewBounds Bounds of View
  247. * @param editor JTextComponent
  248. * @param view View instance being rendered
  249. */
  250. public void paintLayeredHighlights(Graphics g, int p0, int p1,
  251. Shape viewBounds,
  252. JTextComponent editor, View view) {
  253. for (int counter = highlights.size() - 1; counter >= 0; counter--) {
  254. Object tag = highlights.elementAt(counter);
  255. if (tag instanceof LayeredHighlightInfo) {
  256. LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag;
  257. int start = lhi.getStartOffset();
  258. int end = lhi.getEndOffset();
  259. if ((p0 < start && p1 > start) ||
  260. (p0 >= start && p0 < end)) {
  261. lhi.paintLayeredHighlights(g, p0, p1, viewBounds,
  262. editor, view);
  263. }
  264. }
  265. }
  266. }
  267. /**
  268. * Queues damageRange() call into event dispatch thread
  269. * to be sure that views are in consistent state.
  270. */
  271. private void safeDamageRange(final Position p0, final Position p1) {
  272. safeDamager.damageRange(p0, p1);
  273. }
  274. /**
  275. * Queues damageRange() call into event dispatch thread
  276. * to be sure that views are in consistent state.
  277. */
  278. private void safeDamageRange(int a0, int a1) throws BadLocationException {
  279. Document doc = component.getDocument();
  280. safeDamageRange(doc.createPosition(a0), doc.createPosition(a1));
  281. }
  282. /**
  283. * If true, highlights are drawn as the Views draw the text. That is
  284. * the Views will call into <code>paintLayeredHighlight</code> which
  285. * will result in a rectangle being drawn before the text is drawn
  286. * (if the offsets are in a highlighted region that is). For this to
  287. * work the painter supplied must be an instance of
  288. * LayeredHighlightPainter.
  289. */
  290. public void setDrawsLayeredHighlights(boolean newValue) {
  291. drawsLayeredHighlights = newValue;
  292. }
  293. public boolean getDrawsLayeredHighlights() {
  294. return drawsLayeredHighlights;
  295. }
  296. // ---- member variables --------------------------------------------
  297. private final static Highlighter.Highlight[] noHighlights =
  298. new Highlighter.Highlight[0];
  299. private Vector highlights = new Vector(); // Vector<HighlightInfo>
  300. private JTextComponent component;
  301. private boolean drawsLayeredHighlights;
  302. private SafeDamager safeDamager = new SafeDamager();
  303. /**
  304. * Default implementation of LayeredHighlighter.LayerPainter that can
  305. * be used for painting highlights.
  306. * <p>
  307. * As of 1.4 this field is final.
  308. */
  309. public static final LayeredHighlighter.LayerPainter DefaultPainter = new DefaultHighlightPainter(null);
  310. /**
  311. * Simple highlight painter that fills a highlighted area with
  312. * a solid color.
  313. */
  314. public static class DefaultHighlightPainter extends LayeredHighlighter.LayerPainter {
  315. /**
  316. * Constructs a new highlight painter. If <code>c</code> is null,
  317. * the JTextComponent will be queried for its selection color.
  318. *
  319. * @param c the color for the highlight
  320. */
  321. public DefaultHighlightPainter(Color c) {
  322. color = c;
  323. }
  324. /**
  325. * Returns the color of the highlight.
  326. *
  327. * @return the color
  328. */
  329. public Color getColor() {
  330. return color;
  331. }
  332. // --- HighlightPainter methods ---------------------------------------
  333. /**
  334. * Paints a highlight.
  335. *
  336. * @param g the graphics context
  337. * @param offs0 the starting model offset >= 0
  338. * @param offs1 the ending model offset >= offs1
  339. * @param bounds the bounding box for the highlight
  340. * @param c the editor
  341. */
  342. public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
  343. Rectangle alloc = bounds.getBounds();
  344. try {
  345. // --- determine locations ---
  346. TextUI mapper = c.getUI();
  347. Rectangle p0 = mapper.modelToView(c, offs0);
  348. Rectangle p1 = mapper.modelToView(c, offs1);
  349. // --- render ---
  350. Color color = getColor();
  351. if (color == null) {
  352. g.setColor(c.getSelectionColor());
  353. }
  354. else {
  355. g.setColor(color);
  356. }
  357. if (p0.y == p1.y) {
  358. // same line, render a rectangle
  359. Rectangle r = p0.union(p1);
  360. g.fillRect(r.x, r.y, r.width, r.height);
  361. } else {
  362. // different lines
  363. int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
  364. g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
  365. if ((p0.y + p0.height) != p1.y) {
  366. g.fillRect(alloc.x, p0.y + p0.height, alloc.width,
  367. p1.y - (p0.y + p0.height));
  368. }
  369. g.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height);
  370. }
  371. } catch (BadLocationException e) {
  372. // can't render
  373. }
  374. }
  375. // --- LayerPainter methods ----------------------------
  376. /**
  377. * Paints a portion of a highlight.
  378. *
  379. * @param g the graphics context
  380. * @param offs0 the starting model offset >= 0
  381. * @param offs1 the ending model offset >= offs1
  382. * @param bounds the bounding box of the view, which is not
  383. * necessarily the region to paint.
  384. * @param c the editor
  385. * @param view View painting for
  386. * @return region drawing occured in
  387. */
  388. public Shape paintLayer(Graphics g, int offs0, int offs1,
  389. Shape bounds, JTextComponent c, View view) {
  390. Color color = getColor();
  391. if (color == null) {
  392. g.setColor(c.getSelectionColor());
  393. }
  394. else {
  395. g.setColor(color);
  396. }
  397. if (offs0 == view.getStartOffset() &&
  398. offs1 == view.getEndOffset()) {
  399. // Contained in view, can just use bounds.
  400. Rectangle alloc;
  401. if (bounds instanceof Rectangle) {
  402. alloc = (Rectangle)bounds;
  403. }
  404. else {
  405. alloc = bounds.getBounds();
  406. }
  407. g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
  408. return alloc;
  409. }
  410. else {
  411. // Should only render part of View.
  412. try {
  413. // --- determine locations ---
  414. Shape shape = view.modelToView(offs0, Position.Bias.Forward,
  415. offs1,Position.Bias.Backward,
  416. bounds);
  417. Rectangle r = (shape instanceof Rectangle) ?
  418. (Rectangle)shape : shape.getBounds();
  419. g.fillRect(r.x, r.y, r.width, r.height);
  420. return r;
  421. } catch (BadLocationException e) {
  422. // can't render
  423. }
  424. }
  425. // Only if exception
  426. return null;
  427. }
  428. private Color color;
  429. }
  430. class HighlightInfo implements Highlighter.Highlight {
  431. public int getStartOffset() {
  432. return p0.getOffset();
  433. }
  434. public int getEndOffset() {
  435. return p1.getOffset();
  436. }
  437. public Highlighter.HighlightPainter getPainter() {
  438. return painter;
  439. }
  440. Position p0;
  441. Position p1;
  442. Highlighter.HighlightPainter painter;
  443. }
  444. /**
  445. * LayeredHighlightPainter is used when a drawsLayeredHighlights is
  446. * true. It maintains a rectangle of the region to paint.
  447. */
  448. class LayeredHighlightInfo extends HighlightInfo {
  449. void union(Shape bounds) {
  450. if (bounds == null)
  451. return;
  452. Rectangle alloc;
  453. if (bounds instanceof Rectangle) {
  454. alloc = (Rectangle)bounds;
  455. }
  456. else {
  457. alloc = bounds.getBounds();
  458. }
  459. if (width == 0 || height == 0) {
  460. x = alloc.x;
  461. y = alloc.y;
  462. width = alloc.width;
  463. height = alloc.height;
  464. }
  465. else {
  466. width = Math.max(x + width, alloc.x + alloc.width);
  467. height = Math.max(y + height, alloc.y + alloc.height);
  468. x = Math.min(x, alloc.x);
  469. width -= x;
  470. y = Math.min(y, alloc.y);
  471. height -= y;
  472. }
  473. }
  474. /**
  475. * Restricts the region based on the receivers offsets and messages
  476. * the painter to paint the region.
  477. */
  478. void paintLayeredHighlights(Graphics g, int p0, int p1,
  479. Shape viewBounds, JTextComponent editor,
  480. View view) {
  481. int start = getStartOffset();
  482. int end = getEndOffset();
  483. // Restrict the region to what we represent
  484. p0 = Math.max(start, p0);
  485. p1 = Math.min(end, p1);
  486. // Paint the appropriate region using the painter and union
  487. // the effected region with our bounds.
  488. union(((LayeredHighlighter.LayerPainter)painter).paintLayer
  489. (g, p0, p1, viewBounds, editor, view));
  490. }
  491. int x;
  492. int y;
  493. int width;
  494. int height;
  495. }
  496. /**
  497. * This class invokes <code>mapper.damageRange</code> in
  498. * EventDispatchThread. The only one instance per Highlighter
  499. * is cretaed. When a number of ranges should be damaged
  500. * it collects them into queue and damages
  501. * them in consecutive order in <code>run</code>
  502. * call.
  503. */
  504. class SafeDamager implements Runnable {
  505. private Vector p0 = new Vector(10);
  506. private Vector p1 = new Vector(10);
  507. private Document lastDoc = null;
  508. /**
  509. * Executes range(s) damage and cleans range queue.
  510. */
  511. public synchronized void run() {
  512. if (component != null) {
  513. TextUI mapper = component.getUI();
  514. if (mapper != null && lastDoc == component.getDocument()) {
  515. // the Document should be the same to properly
  516. // display highlights
  517. int len = p0.size();
  518. for (int i = 0; i < len; i++){
  519. mapper.damageRange(component,
  520. ((Position)p0.get(i)).getOffset(),
  521. ((Position)p1.get(i)).getOffset());
  522. }
  523. }
  524. }
  525. p0.clear();
  526. p1.clear();
  527. // release reference
  528. lastDoc = null;
  529. }
  530. /**
  531. * Adds the range to be damaged into the range queue. If the
  532. * range queue is empty (the first call or run() was already
  533. * invoked) then adds this class instance into EventDispatch
  534. * queue.
  535. *
  536. * The method also tracks if the current document changed or
  537. * component is null. In this case it removes all ranges added
  538. * before from range queue.
  539. */
  540. public synchronized void damageRange(Position pos0, Position pos1) {
  541. if (component == null) {
  542. p0.clear();
  543. lastDoc = null;
  544. return;
  545. }
  546. boolean addToQueue = p0.isEmpty();
  547. Document curDoc = component.getDocument();
  548. if (curDoc != lastDoc) {
  549. if (!p0.isEmpty()) {
  550. p0.clear();
  551. p1.clear();
  552. }
  553. lastDoc = curDoc;
  554. }
  555. p0.add(pos0);
  556. p1.add(pos1);
  557. if (addToQueue) {
  558. SwingUtilities.invokeLater(this);
  559. }
  560. }
  561. }
  562. }