1. /*
  2. * @(#)ComponentView.java 1.36 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.awt.*;
  9. import javax.swing.SwingUtilities;
  10. import javax.swing.event.*;
  11. import javax.swing.OverlayLayout;
  12. /**
  13. * Component decorator that implements the view interface. The
  14. * entire element is used to represent the component. This acts
  15. * as a gateway from the display-only View implementations to
  16. * interactive lightweight components (ie it allows components
  17. * to be embedded into the View hierarchy).
  18. *
  19. * @author Timothy Prinzing
  20. * @version 1.36 11/29/01
  21. */
  22. public class ComponentView extends View {
  23. /**
  24. * Creates a new ComponentView object.
  25. *
  26. * @param elem the element to decorate
  27. */
  28. public ComponentView(Element elem) {
  29. super(elem);
  30. }
  31. /**
  32. * Create the component that is associated with
  33. * this view. This will be called when it has
  34. * been determined that a new component is needed.
  35. * This would result from a call to setParent or
  36. * as a result of being notified that attributes
  37. * have changed.
  38. */
  39. protected Component createComponent() {
  40. AttributeSet attr = getElement().getAttributes();
  41. Component comp = StyleConstants.getComponent(attr);
  42. return comp;
  43. }
  44. /**
  45. * Fetch the component associated with the view.
  46. */
  47. public final Component getComponent() {
  48. return createdC;
  49. }
  50. // --- View methods ---------------------------------------------
  51. /**
  52. * Paints a component view.
  53. * The real paint behavior occurs naturally from the association
  54. * that the component has with its parent container (the same
  55. * container hosting this view), so this simply allows us to
  56. * position the component properly relative to the view. Since
  57. * the coordinate system for the view is simply the parent
  58. * containers, positioning the child component is easy.
  59. *
  60. * @param g the graphics context
  61. * @param a the shape
  62. * @see View#paint
  63. */
  64. public void paint(Graphics g, Shape a) {
  65. if (c != null) {
  66. c.setBounds(a.getBounds());
  67. if (! c.isVisible()) {
  68. c.setVisible(true);
  69. }
  70. }
  71. }
  72. /**
  73. * Determines the preferred span for this view along an
  74. * axis. This is implemented to return the value
  75. * returned by Component.getPreferredSize along the
  76. * axis of interest.
  77. *
  78. * @param axis may be either View.X_AXIS or View.Y_AXIS
  79. * @returns the span the view would like to be rendered into >= 0.
  80. * Typically the view is told to render into the span
  81. * that is returned, although there is no guarantee.
  82. * The parent may choose to resize or break the view.
  83. * @exception IllegalArgumentException for an invalid axis
  84. */
  85. public float getPreferredSpan(int axis) {
  86. if (c != null) {
  87. Dimension size = c.getPreferredSize();
  88. switch (axis) {
  89. case View.X_AXIS:
  90. return size.width;
  91. case View.Y_AXIS:
  92. return size.height;
  93. default:
  94. throw new IllegalArgumentException("Invalid axis: " + axis);
  95. }
  96. }
  97. return 0;
  98. }
  99. /**
  100. * Determines the minimum span for this view along an
  101. * axis. This is implemented to return the value
  102. * returned by Component.getMinimumSize along the
  103. * axis of interest.
  104. *
  105. * @param axis may be either View.X_AXIS or View.Y_AXIS
  106. * @returns the span the view would like to be rendered into >= 0.
  107. * Typically the view is told to render into the span
  108. * that is returned, although there is no guarantee.
  109. * The parent may choose to resize or break the view.
  110. * @exception IllegalArgumentException for an invalid axis
  111. */
  112. public float getMinimumSpan(int axis) {
  113. if (c != null) {
  114. Dimension size = c.getMinimumSize();
  115. switch (axis) {
  116. case View.X_AXIS:
  117. return size.width;
  118. case View.Y_AXIS:
  119. return size.height;
  120. default:
  121. throw new IllegalArgumentException("Invalid axis: " + axis);
  122. }
  123. }
  124. return 0;
  125. }
  126. /**
  127. * Determines the maximum span for this view along an
  128. * axis. This is implemented to return the value
  129. * returned by Component.getMaximumSize along the
  130. * axis of interest.
  131. *
  132. * @param axis may be either View.X_AXIS or View.Y_AXIS
  133. * @returns the span the view would like to be rendered into >= 0.
  134. * Typically the view is told to render into the span
  135. * that is returned, although there is no guarantee.
  136. * The parent may choose to resize or break the view.
  137. * @exception IllegalArgumentException for an invalid axis
  138. */
  139. public float getMaximumSpan(int axis) {
  140. if (c != null) {
  141. Dimension size = c.getMaximumSize();
  142. switch (axis) {
  143. case View.X_AXIS:
  144. return size.width;
  145. case View.Y_AXIS:
  146. return size.height;
  147. default:
  148. throw new IllegalArgumentException("Invalid axis: " + axis);
  149. }
  150. }
  151. return 0;
  152. }
  153. /**
  154. * Determines the desired alignment for this view along an
  155. * axis. This is implemented to give the alignment of the
  156. * embedded component.
  157. *
  158. * @param axis may be either View.X_AXIS or View.Y_AXIS
  159. * @returns the desired alignment. This should be a value
  160. * between 0.0 and 1.0 where 0 indicates alignment at the
  161. * origin and 1.0 indicates alignment to the full span
  162. * away from the origin. An alignment of 0.5 would be the
  163. * center of the view.
  164. */
  165. public float getAlignment(int axis) {
  166. if (c != null) {
  167. switch (axis) {
  168. case View.X_AXIS:
  169. return c.getAlignmentX();
  170. case View.Y_AXIS:
  171. return c.getAlignmentY();
  172. }
  173. }
  174. return super.getAlignment(axis);
  175. }
  176. /**
  177. * Sets the size of the view. This is implemented
  178. * to set the size of the component to match the
  179. * size of the view.
  180. * <p>
  181. * The changing of the component size will
  182. * touch the component lock, which is the one thing
  183. * that is not safe from the View hierarchy. Therefore,
  184. * this functionality is executed immediately if on the
  185. * event thread, or is queued on the event queue if
  186. * called from another thread (notification of change
  187. * from an asynchronous update).
  188. *
  189. * @param width the width >= 0
  190. * @param height the height >= 0
  191. */
  192. public void setSize(float width, float height) {
  193. if (c != null) {
  194. if (SwingUtilities.isEventDispatchThread()) {
  195. c.setSize((int) width, (int) height);
  196. } else {
  197. Runnable callSetComponentSize =
  198. new SafeResizer((int) width, (int) height);
  199. SwingUtilities.invokeLater(callSetComponentSize);
  200. }
  201. }
  202. }
  203. class SafeResizer implements Runnable {
  204. SafeResizer(int width, int height) {
  205. this.width = width;
  206. this.height = height;
  207. }
  208. public void run() {
  209. if (c != null) {
  210. c.setSize(width, height);
  211. }
  212. }
  213. private int width;
  214. private int height;
  215. }
  216. /**
  217. * Sets the parent for a child view.
  218. * The parent calls this on the child to tell it who its
  219. * parent is, giving the view access to things like
  220. * the hosting Container. The superclass behavior is
  221. * executed, followed by a call to createComponent if
  222. * a component has not yet been created and the embedded
  223. * components parent is set to the value returned by
  224. * <code>getContainer</code>.
  225. * <p>
  226. * The changing of the component hierarhcy will
  227. * touch the component lock, which is the one thing
  228. * that is not safe from the View hierarchy. Therefore,
  229. * this functionality is executed immediately if on the
  230. * event thread, or is queued on the event queue if
  231. * called from another thread (notification of change
  232. * from an asynchronous update).
  233. *
  234. * @param p the parent
  235. */
  236. public void setParent(View p) {
  237. super.setParent(p);
  238. if (SwingUtilities.isEventDispatchThread()) {
  239. setComponentParent();
  240. } else {
  241. Runnable callSetComponentParent = new Runnable() {
  242. public void run() {
  243. Document doc = getDocument();
  244. try {
  245. if (doc instanceof AbstractDocument) {
  246. ((AbstractDocument)doc).readLock();
  247. }
  248. setComponentParent();
  249. Container host = getContainer();
  250. if (host != null) {
  251. preferenceChanged(null, true, true);
  252. host.repaint();
  253. }
  254. } finally {
  255. if (doc instanceof AbstractDocument) {
  256. ((AbstractDocument)doc).readUnlock();
  257. }
  258. }
  259. }
  260. };
  261. SwingUtilities.invokeLater(callSetComponentParent);
  262. }
  263. }
  264. /**
  265. * Set the parent of the embedded component
  266. * with assurance that it is thread-safe.
  267. */
  268. void setComponentParent() {
  269. View p = getParent();
  270. if (p == null) {
  271. if (c != null) {
  272. Container parent = c.getParent();
  273. if (parent != null) {
  274. parent.remove(c);
  275. }
  276. }
  277. } else {
  278. if (c == null) {
  279. // try to build a component
  280. Component comp = createComponent();
  281. if (comp != null) {
  282. createdC = comp;
  283. c = new Invalidator(comp);
  284. c.setVisible(false);
  285. }
  286. }
  287. if (c != null) {
  288. if (c.getParent() == null) {
  289. Container parent = getContainer();
  290. parent.add(c);
  291. }
  292. }
  293. }
  294. }
  295. /**
  296. * Provides a mapping from the coordinate space of the model to
  297. * that of the view.
  298. *
  299. * @param pos the position to convert >= 0
  300. * @param a the allocated region to render into
  301. * @return the bounding box of the given position is returned
  302. * @exception BadLocationException if the given position does not
  303. * represent a valid location in the associated document
  304. * @see View#modelToView
  305. */
  306. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  307. int p0 = getStartOffset();
  308. int p1 = getEndOffset();
  309. if ((pos >= p0) && (pos <= p1)) {
  310. Rectangle r = a.getBounds();
  311. if (pos == p1) {
  312. r.x += r.width;
  313. }
  314. r.width = 0;
  315. return r;
  316. }
  317. throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
  318. }
  319. /**
  320. * Provides a mapping from the view coordinate space to the logical
  321. * coordinate space of the model.
  322. *
  323. * @param x the X coordinate >= 0
  324. * @param y the Y coordinate >= 0
  325. * @param a the allocated region to render into
  326. * @return the location within the model that best represents
  327. * the given point in the view
  328. * @see View#viewToModel
  329. */
  330. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  331. Rectangle alloc = (Rectangle) a;
  332. if (x < alloc.x + (alloc.width / 2)) {
  333. bias[0] = Position.Bias.Forward;
  334. return getStartOffset();
  335. }
  336. bias[0] = Position.Bias.Backward;
  337. return getEndOffset();
  338. }
  339. // --- member variables ------------------------------------------------
  340. private Component createdC;
  341. private Component c;
  342. /**
  343. * This class feeds the invalidate back to the
  344. * hosting View. This is needed to get the View
  345. * hierarchy to consider giving the component
  346. * a different size (i.e. layout may have been
  347. * cached between the associated view and the
  348. * container hosting this component).
  349. */
  350. class Invalidator extends Container {
  351. Invalidator(Component child) {
  352. setLayout(new OverlayLayout(this));
  353. add(child);
  354. min = child.getMinimumSize();
  355. pref = child.getPreferredSize();
  356. max = child.getMaximumSize();
  357. yalign = child.getAlignmentY();
  358. xalign = child.getAlignmentX();
  359. }
  360. /**
  361. * The components invalid layout needs
  362. * to be propagated through the view hierarchy
  363. * so the views (which position the component)
  364. * can have their layout recomputed.
  365. */
  366. public void invalidate() {
  367. super.invalidate();
  368. min = super.getMinimumSize();
  369. pref = super.getPreferredSize();
  370. max = super.getMaximumSize();
  371. yalign = super.getAlignmentY();
  372. xalign = super.getAlignmentX();
  373. if (getParent() != null) {
  374. preferenceChanged(null, true, true);
  375. }
  376. }
  377. /**
  378. * Shows or hides this component depending on the value of parameter
  379. * <code>b</code>.
  380. * @param <code>b</code> If <code>true</code>, shows this component;
  381. * otherwise, hides this component.
  382. * @see #isVisible
  383. * @since JDK1.1
  384. */
  385. public void setVisible(boolean b) {
  386. super.setVisible(b);
  387. Component child = this.getComponent(0);
  388. child.setVisible(b);
  389. }
  390. public Dimension getMinimumSize() {
  391. return min;
  392. }
  393. public Dimension getPreferredSize() {
  394. return pref;
  395. }
  396. public Dimension getMaximumSize() {
  397. return max;
  398. }
  399. public float getAlignmentX() {
  400. return xalign;
  401. }
  402. public float getAlignmentY() {
  403. return yalign;
  404. }
  405. Dimension min;
  406. Dimension pref;
  407. Dimension max;
  408. float yalign;
  409. float xalign;
  410. }
  411. }