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