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