- /*
- * @(#)ComponentView.java 1.54 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text;
-
- import java.awt.*;
- import javax.swing.SwingUtilities;
- import javax.swing.event.*;
-
- /**
- * Component decorator that implements the view interface. The
- * entire element is used to represent the component. This acts
- * as a gateway from the display-only View implementations to
- * interactive lightweight components (ie it allows components
- * to be embedded into the View hierarchy).
- * <p>
- * The component is placed relative to the text baseline
- * according to the value returned by
- * <code>Component.getAlignmentY</code>. For Swing components
- * this value can be conveniently set using the method
- * <code>JComponent.setAlignmentY</code>. For example, setting
- * a value of <code>0.75</code> will cause 75 percent of the
- * component to be above the baseline, and 25 percent of the
- * component to be below the baseline.
- * <p>
- * This class is implemented to do the extra work necessary to
- * work properly in the presence of multiple threads (i.e. from
- * asynchronous notification of model changes for example) by
- * ensuring that all component access is done on the event thread.
- * <p>
- * The component used is determined by the return value of the
- * createComponent method. The default implementation of this
- * method is to return the component held as an attribute of
- * the element (by calling StyleConstants.getComponent). A
- * limitation of this behavior is that the component cannot
- * be used by more than one text component (i.e. with a shared
- * model). Subclasses can remove this constraint by implementing
- * the createComponent to actually create a component based upon
- * some kind of specification contained in the attributes. The
- * ObjectView class in the html package is an example of a
- * ComponentView implementation that supports multiple component
- * views of a shared model.
- *
- * @author Timothy Prinzing
- * @version 1.54 12/19/03
- */
- public class ComponentView extends View {
-
- /**
- * Creates a new ComponentView object.
- *
- * @param elem the element to decorate
- */
- public ComponentView(Element elem) {
- super(elem);
- }
-
- /**
- * Create the component that is associated with
- * this view. This will be called when it has
- * been determined that a new component is needed.
- * This would result from a call to setParent or
- * as a result of being notified that attributes
- * have changed.
- */
- protected Component createComponent() {
- AttributeSet attr = getElement().getAttributes();
- Component comp = StyleConstants.getComponent(attr);
- return comp;
- }
-
- /**
- * Fetch the component associated with the view.
- */
- public final Component getComponent() {
- return createdC;
- }
-
- // --- View methods ---------------------------------------------
-
- /**
- * The real paint behavior occurs naturally from the association
- * that the component has with its parent container (the same
- * container hosting this view). This is implemented to do nothing.
- *
- * @param g the graphics context
- * @param a the shape
- * @see View#paint
- */
- public void paint(Graphics g, Shape a) {
- if (c != null) {
- Rectangle alloc = (a instanceof Rectangle) ?
- (Rectangle) a : a.getBounds();
- c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height);
- }
- }
-
- /**
- * Determines the preferred span for this view along an
- * axis. This is implemented to return the value
- * returned by Component.getPreferredSize along the
- * axis of interest.
- *
- * @param axis may be either View.X_AXIS or View.Y_AXIS
- * @return the span the view would like to be rendered into >= 0.
- * Typically the view is told to render into the span
- * that is returned, although there is no guarantee.
- * The parent may choose to resize or break the view.
- * @exception IllegalArgumentException for an invalid axis
- */
- public float getPreferredSpan(int axis) {
- if ((axis != X_AXIS) && (axis != Y_AXIS)) {
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- if (c != null) {
- Dimension size = c.getPreferredSize();
- if (axis == View.X_AXIS) {
- return size.width;
- } else {
- return size.height;
- }
- }
- return 0;
- }
-
- /**
- * Determines the minimum span for this view along an
- * axis. This is implemented to return the value
- * returned by Component.getMinimumSize along the
- * axis of interest.
- *
- * @param axis may be either View.X_AXIS or View.Y_AXIS
- * @return the span the view would like to be rendered into >= 0.
- * Typically the view is told to render into the span
- * that is returned, although there is no guarantee.
- * The parent may choose to resize or break the view.
- * @exception IllegalArgumentException for an invalid axis
- */
- public float getMinimumSpan(int axis) {
- if ((axis != X_AXIS) && (axis != Y_AXIS)) {
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- if (c != null) {
- Dimension size = c.getMinimumSize();
- if (axis == View.X_AXIS) {
- return size.width;
- } else {
- return size.height;
- }
- }
- return 0;
- }
-
- /**
- * Determines the maximum span for this view along an
- * axis. This is implemented to return the value
- * returned by Component.getMaximumSize along the
- * axis of interest.
- *
- * @param axis may be either View.X_AXIS or View.Y_AXIS
- * @return the span the view would like to be rendered into >= 0.
- * Typically the view is told to render into the span
- * that is returned, although there is no guarantee.
- * The parent may choose to resize or break the view.
- * @exception IllegalArgumentException for an invalid axis
- */
- public float getMaximumSpan(int axis) {
- if ((axis != X_AXIS) && (axis != Y_AXIS)) {
- throw new IllegalArgumentException("Invalid axis: " + axis);
- }
- if (c != null) {
- Dimension size = c.getMaximumSize();
- if (axis == View.X_AXIS) {
- return size.width;
- } else {
- return size.height;
- }
- }
- return 0;
- }
-
- /**
- * Determines the desired alignment for this view along an
- * axis. This is implemented to give the alignment of the
- * embedded component.
- *
- * @param axis may be either View.X_AXIS or View.Y_AXIS
- * @return the desired alignment. This should be a value
- * between 0.0 and 1.0 where 0 indicates alignment at the
- * origin and 1.0 indicates alignment to the full span
- * away from the origin. An alignment of 0.5 would be the
- * center of the view.
- */
- public float getAlignment(int axis) {
- if (c != null) {
- switch (axis) {
- case View.X_AXIS:
- return c.getAlignmentX();
- case View.Y_AXIS:
- return c.getAlignmentY();
- }
- }
- return super.getAlignment(axis);
- }
-
- /**
- * Sets the parent for a child view.
- * The parent calls this on the child to tell it who its
- * parent is, giving the view access to things like
- * the hosting Container. The superclass behavior is
- * executed, followed by a call to createComponent if
- * the parent view parameter is non-null and a component
- * has not yet been created. The embedded components parent
- * is then set to the value returned by <code>getContainer</code>.
- * If the parent view parameter is null, this view is being
- * cleaned up, thus the component is removed from its parent.
- * <p>
- * The changing of the component hierarchy will
- * touch the component lock, which is the one thing
- * that is not safe from the View hierarchy. Therefore,
- * this functionality is executed immediately if on the
- * event thread, or is queued on the event queue if
- * called from another thread (notification of change
- * from an asynchronous update).
- *
- * @param p the parent
- */
- public void setParent(View p) {
- super.setParent(p);
- if (SwingUtilities.isEventDispatchThread()) {
- setComponentParent();
- } else {
- Runnable callSetComponentParent = new Runnable() {
- public void run() {
- Document doc = getDocument();
- try {
- if (doc instanceof AbstractDocument) {
- ((AbstractDocument)doc).readLock();
- }
- setComponentParent();
- Container host = getContainer();
- if (host != null) {
- preferenceChanged(null, true, true);
- host.repaint();
- }
- } finally {
- if (doc instanceof AbstractDocument) {
- ((AbstractDocument)doc).readUnlock();
- }
- }
- }
- };
- SwingUtilities.invokeLater(callSetComponentParent);
- }
- }
-
- /**
- * Set the parent of the embedded component
- * with assurance that it is thread-safe.
- */
- void setComponentParent() {
- View p = getParent();
- if (p != null) {
- Container parent = getContainer();
- if (parent != null) {
- if (c == null) {
- // try to build a component
- Component comp = createComponent();
- if (comp != null) {
- createdC = comp;
- c = new Invalidator(comp);
- }
- }
- if (c != null) {
- if (c.getParent() == null) {
- // components associated with the View tree are added
- // to the hosting container with the View as a constraint.
- parent.add(c, this);
- }
- }
- }
- } else {
- if (c != null) {
- Container parent = c.getParent();
- if (parent != null) {
- // remove the component from its hosting container
- parent.remove(c);
- }
- }
- }
- }
-
- /**
- * Provides a mapping from the coordinate space of the model to
- * that of the view.
- *
- * @param pos the position to convert >= 0
- * @param a the allocated region to render into
- * @return the bounding box of the given position is returned
- * @exception BadLocationException if the given position does not
- * represent a valid location in the associated document
- * @see View#modelToView
- */
- public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
- int p0 = getStartOffset();
- int p1 = getEndOffset();
- if ((pos >= p0) && (pos <= p1)) {
- Rectangle r = a.getBounds();
- if (pos == p1) {
- r.x += r.width;
- }
- r.width = 0;
- return r;
- }
- throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
- }
-
- /**
- * Provides a mapping from the view coordinate space to the logical
- * coordinate space of the model.
- *
- * @param x the X coordinate >= 0
- * @param y the Y coordinate >= 0
- * @param a the allocated region to render into
- * @return the location within the model that best represents
- * the given point in the view
- * @see View#viewToModel
- */
- public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
- Rectangle alloc = (Rectangle) a;
- if (x < alloc.x + (alloc.width / 2)) {
- bias[0] = Position.Bias.Forward;
- return getStartOffset();
- }
- bias[0] = Position.Bias.Backward;
- return getEndOffset();
- }
-
- // --- member variables ------------------------------------------------
-
- private Component createdC;
- private Component c;
-
- /**
- * This class feeds the invalidate back to the
- * hosting View. This is needed to get the View
- * hierarchy to consider giving the component
- * a different size (i.e. layout may have been
- * cached between the associated view and the
- * container hosting this component).
- */
- class Invalidator extends Container {
-
- // NOTE: When we remove this class we are going to have to some
- // how enforce setting of the focus traversal keys on the children
- // so that they don't inherit them from the JEditorPane. We need
- // to do this as JEditorPane has abnormal bindings (it is a focus cycle
- // root) and the children typically don't want these bindings as well.
-
- Invalidator(Component child) {
- setLayout(null);
- add(child);
- cacheChildSizes();
- }
-
- /**
- * The components invalid layout needs
- * to be propagated through the view hierarchy
- * so the views (which position the component)
- * can have their layout recomputed.
- */
- public void invalidate() {
- super.invalidate();
- if (getParent() != null) {
- preferenceChanged(null, true, true);
- }
- }
-
- public void doLayout() {
- cacheChildSizes();
- }
-
- public void setBounds(int x, int y, int w, int h) {
- super.setBounds(x, y, w, h);
- if (getComponentCount() > 0) {
- getComponent(0).setSize(w, h);
- }
- cacheChildSizes();
- }
-
- public void validateIfNecessary() {
- if (!isValid()) {
- validate();
- }
- }
-
- private void cacheChildSizes() {
- if (getComponentCount() > 0) {
- Component child = getComponent(0);
- min = child.getMinimumSize();
- pref = child.getPreferredSize();
- max = child.getMaximumSize();
- yalign = child.getAlignmentY();
- xalign = child.getAlignmentX();
- } else {
- min = pref = max = new Dimension(0, 0);
- }
- }
-
- /**
- * Shows or hides this component depending on the value of parameter
- * <code>b</code>.
- * @param <code>b</code> If <code>true</code>, shows this component;
- * otherwise, hides this component.
- * @see #isVisible
- * @since JDK1.1
- */
- public void setVisible(boolean b) {
- super.setVisible(b);
- if (getComponentCount() > 0) {
- getComponent(0).setVisible(b);
- }
- }
-
- /**
- * Overridden to fix 4759054. Must return true so that content
- * is painted when inside a CellRendererPane which is normally
- * invisible.
- */
- public boolean isShowing() {
- return true;
- }
-
- public Dimension getMinimumSize() {
- validateIfNecessary();
- return min;
- }
-
- public Dimension getPreferredSize() {
- validateIfNecessary();
- return pref;
- }
-
- public Dimension getMaximumSize() {
- validateIfNecessary();
- return max;
- }
-
- public float getAlignmentX() {
- validateIfNecessary();
- return xalign;
- }
-
- public float getAlignmentY() {
- validateIfNecessary();
- return yalign;
- }
-
- public java.util.Set getFocusTraversalKeys(int id) {
- return KeyboardFocusManager.getCurrentKeyboardFocusManager().
- getDefaultFocusTraversalKeys(id);
- }
-
- Dimension min;
- Dimension pref;
- Dimension max;
- float yalign;
- float xalign;
-
- }
-
- }
-