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