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