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