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      * @return the component that is associated with
  89      * this view
  90      */
  91     protected Component createComponent() {
  92         AttributeSet attr = getElement().getAttributes();
  93         Component comp = StyleConstants.getComponent(attr);
  94         return comp;
  95     }
  96 
  97     /**
  98      * Fetch the component associated with the view.
  99      * @return the component associated with the view
 100      */
 101     public final Component getComponent() {
 102         return createdC;
 103     }
 104 
 105     // --- View methods ---------------------------------------------
 106 
 107     /**
 108      * The real paint behavior occurs naturally from the association
 109      * that the component has with its parent container (the same
 110      * container hosting this view).  This is implemented to do nothing.
 111      *
 112      * @param g the graphics context
 113      * @param a the shape
 114      * @see View#paint
 115      */
 116     public void paint(Graphics g, Shape a) {
 117         if (c != null) {
 118             Rectangle alloc = (a instanceof Rectangle) ?
 119                 (Rectangle) a : a.getBounds();
 120             c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height);
 121         }
 122     }
 123 
 124     /**
 125      * Determines the preferred span for this view along an
 126      * axis.  This is implemented to return the value
 127      * returned by Component.getPreferredSize along the
 128      * axis of interest.
 129      *
 130      * @param axis may be either View.X_AXIS or View.Y_AXIS
 131      * @return   the span the view would like to be rendered into &gt;=0.
 132      *           Typically the view is told to render into the span
 133      *           that is returned, although there is no guarantee.
 134      *           The parent may choose to resize or break the view.
 135      * @exception IllegalArgumentException for an invalid axis
 136      */
 137     public float getPreferredSpan(int axis) {
 138         if ((axis != X_AXIS) && (axis != Y_AXIS)) {
 139             throw new IllegalArgumentException("Invalid axis: " + axis);
 140         }
 141         if (c != null) {
 142             Dimension size = c.getPreferredSize();
 143             if (axis == View.X_AXIS) {
 144                 return size.width;
 145             } else {
 146                 return size.height;
 147             }
 148         }
 149         return 0;
 150     }
 151 
 152     /**
 153      * Determines the minimum span for this view along an
 154      * axis.  This is implemented to return the value
 155      * returned by Component.getMinimumSize along the
 156      * axis of interest.
 157      *
 158      * @param axis may be either View.X_AXIS or View.Y_AXIS
 159      * @return   the span the view would like to be rendered into &gt;=0.
 160      *           Typically the view is told to render into the span
 161      *           that is returned, although there is no guarantee.
 162      *           The parent may choose to resize or break the view.
 163      * @exception IllegalArgumentException for an invalid axis
 164      */
 165     public float getMinimumSpan(int axis) {
 166         if ((axis != X_AXIS) && (axis != Y_AXIS)) {
 167             throw new IllegalArgumentException("Invalid axis: " + axis);
 168         }
 169         if (c != null) {
 170             Dimension size = c.getMinimumSize();
 171             if (axis == View.X_AXIS) {
 172                 return size.width;
 173             } else {
 174                 return size.height;
 175             }
 176         }
 177         return 0;
 178     }
 179 
 180     /**
 181      * Determines the maximum span for this view along an
 182      * axis.  This is implemented to return the value
 183      * returned by Component.getMaximumSize along the
 184      * axis of interest.
 185      *
 186      * @param axis may be either View.X_AXIS or View.Y_AXIS
 187      * @return   the span the view would like to be rendered into &gt;=0.
 188      *           Typically the view is told to render into the span
 189      *           that is returned, although there is no guarantee.
 190      *           The parent may choose to resize or break the view.
 191      * @exception IllegalArgumentException for an invalid axis
 192      */
 193     public float getMaximumSpan(int axis) {
 194         if ((axis != X_AXIS) && (axis != Y_AXIS)) {
 195             throw new IllegalArgumentException("Invalid axis: " + axis);
 196         }
 197         if (c != null) {
 198             Dimension size = c.getMaximumSize();
 199             if (axis == View.X_AXIS) {
 200                 return size.width;
 201             } else {
 202                 return size.height;
 203             }
 204         }
 205         return 0;
 206     }
 207 
 208     /**
 209      * Determines the desired alignment for this view along an
 210      * axis.  This is implemented to give the alignment of the
 211      * embedded component.
 212      *
 213      * @param axis may be either View.X_AXIS or View.Y_AXIS
 214      * @return the desired alignment.  This should be a value
 215      *   between 0.0 and 1.0 where 0 indicates alignment at the
 216      *   origin and 1.0 indicates alignment to the full span
 217      *   away from the origin.  An alignment of 0.5 would be the
 218      *   center of the view.
 219      */
 220     public float getAlignment(int axis) {
 221         if (c != null) {
 222             switch (axis) {
 223             case View.X_AXIS:
 224                 return c.getAlignmentX();
 225             case View.Y_AXIS:
 226                 return c.getAlignmentY();
 227             }
 228         }
 229         return super.getAlignment(axis);
 230     }
 231 
 232     /**
 233      * Sets the parent for a child view.
 234      * The parent calls this on the child to tell it who its
 235      * parent is, giving the view access to things like
 236      * the hosting Container.  The superclass behavior is
 237      * executed, followed by a call to createComponent if
 238      * the parent view parameter is non-null and a component
 239      * has not yet been created. The embedded components parent
 240      * is then set to the value returned by <code>getContainer</code>.
 241      * If the parent view parameter is null, this view is being
 242      * cleaned up, thus the component is removed from its parent.
 243      * <p>
 244      * The changing of the component hierarchy will
 245      * touch the component lock, which is the one thing
 246      * that is not safe from the View hierarchy.  Therefore,
 247      * this functionality is executed immediately if on the
 248      * event thread, or is queued on the event queue if
 249      * called from another thread (notification of change
 250      * from an asynchronous update).
 251      *
 252      * @param p the parent
 253      */
 254     public void setParent(View p) {
 255         super.setParent(p);
 256         if (SwingUtilities.isEventDispatchThread()) {
 257             setComponentParent();
 258         } else {
 259             Runnable callSetComponentParent = new Runnable() {
 260                 public void run() {
 261                     Document doc = getDocument();
 262                     try {
 263                         if (doc instanceof AbstractDocument) {
 264                             ((AbstractDocument)doc).readLock();
 265                         }
 266                         setComponentParent();
 267                         Container host = getContainer();
 268                         if (host != null) {
 269                             preferenceChanged(null, true, true);
 270                             host.repaint();
 271                         }
 272                     } finally {
 273                         if (doc instanceof AbstractDocument) {
 274                             ((AbstractDocument)doc).readUnlock();
 275                         }
 276                     }
 277                 }
 278             };
 279             SwingUtilities.invokeLater(callSetComponentParent);
 280         }
 281     }
 282 
 283     /**
 284      * Set the parent of the embedded component
 285      * with assurance that it is thread-safe.
 286      */
 287     void setComponentParent() {
 288         View p = getParent();
 289         if (p != null) {
 290             Container parent = getContainer();
 291             if (parent != null) {
 292                 if (c == null) {
 293                     // try to build a component
 294                     Component comp = createComponent();
 295                     if (comp != null) {
 296                         createdC = comp;
 297                         c = new Invalidator(comp);
 298                     }
 299                 }
 300                 if (c != null) {
 301                     if (c.getParent() == null) {
 302                         // components associated with the View tree are added
 303                         // to the hosting container with the View as a constraint.
 304                         parent.add(c, this);
 305                         parent.addPropertyChangeListener("enabled", c);
 306                     }
 307                 }
 308             }
 309         } else {
 310             if (c != null) {
 311                 Container parent = c.getParent();
 312                 if (parent != null) {
 313                     // remove the component from its hosting container
 314                     parent.remove(c);
 315                     parent.removePropertyChangeListener("enabled", c);
 316                 }
 317             }
 318         }
 319     }
 320 
 321     /**
 322      * Provides a mapping from the coordinate space of the model to
 323      * that of the view.
 324      *
 325      * @param pos the position to convert &gt;=0
 326      * @param a the allocated region to render into
 327      * @return the bounding box of the given position is returned
 328      * @exception BadLocationException  if the given position does not
 329      *   represent a valid location in the associated document
 330      * @see View#modelToView
 331      */
 332     public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
 333         int p0 = getStartOffset();
 334         int p1 = getEndOffset();
 335         if ((pos >= p0) && (pos <= p1)) {
 336             Rectangle r = a.getBounds();
 337             if (pos == p1) {
 338                 r.x += r.width;
 339             }
 340             r.width = 0;
 341             return r;
 342         }
 343         throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos);
 344     }
 345 
 346     /**
 347      * Provides a mapping from the view coordinate space to the logical
 348      * coordinate space of the model.
 349      *
 350      * @param x the X coordinate &gt;=0
 351      * @param y the Y coordinate &gt;=0
 352      * @param a the allocated region to render into
 353      * @return the location within the model that best represents
 354      *    the given point in the view
 355      * @see View#viewToModel
 356      */
 357     public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
 358         Rectangle alloc = (Rectangle) a;
 359         if (x < alloc.x + (alloc.width / 2)) {
 360             bias[0] = Position.Bias.Forward;
 361             return getStartOffset();
 362         }
 363         bias[0] = Position.Bias.Backward;
 364         return getEndOffset();
 365     }
 366 
 367     // --- member variables ------------------------------------------------
 368 
 369     private Component createdC;
 370     private Invalidator c;
 371 
 372     /**
 373      * This class feeds the invalidate back to the
 374      * hosting View.  This is needed to get the View
 375      * hierarchy to consider giving the component
 376      * a different size (i.e. layout may have been
 377      * cached between the associated view and the
 378      * container hosting this component).
 379      */
 380     @SuppressWarnings("serial") // JDK-implementation class
 381     class Invalidator extends Container implements PropertyChangeListener {
 382 
 383         // NOTE: When we remove this class we are going to have to some
 384         // how enforce setting of the focus traversal keys on the children
 385         // so that they don't inherit them from the JEditorPane. We need
 386         // to do this as JEditorPane has abnormal bindings (it is a focus cycle
 387         // root) and the children typically don't want these bindings as well.
 388 
 389         Invalidator(Component child) {
 390             setLayout(null);
 391             add(child);
 392             cacheChildSizes();
 393         }
 394 
 395         /**
 396          * The components invalid layout needs
 397          * to be propagated through the view hierarchy
 398          * so the views (which position the component)
 399          * can have their layout recomputed.
 400          */
 401         public void invalidate() {
 402             super.invalidate();
 403             if (getParent() != null) {
 404                 preferenceChanged(null, true, true);
 405             }
 406         }
 407 
 408         public void doLayout() {
 409             cacheChildSizes();
 410         }
 411 
 412         public void setBounds(int x, int y, int w, int h) {
 413             super.setBounds(x, y, w, h);
 414             if (getComponentCount() > 0) {
 415                 getComponent(0).setSize(w, h);
 416             }
 417             cacheChildSizes();
 418         }
 419 
 420         public void validateIfNecessary() {
 421             if (!isValid()) {
 422                 validate();
 423              }
 424         }
 425 
 426         private void cacheChildSizes() {
 427             if (getComponentCount() > 0) {
 428                 Component child = getComponent(0);
 429                 min = child.getMinimumSize();
 430                 pref = child.getPreferredSize();
 431                 max = child.getMaximumSize();
 432                 yalign = child.getAlignmentY();
 433                 xalign = child.getAlignmentX();
 434             } else {
 435                 min = pref = max = new Dimension(0, 0);
 436             }
 437         }
 438 
 439         /**
 440          * Shows or hides this component depending on the value of parameter
 441          * <code>b</code>.
 442          * @param b If <code>true</code>, shows this component;
 443          * otherwise, hides this component.
 444          * @see #isVisible
 445          * @since 1.1
 446          */
 447         public void setVisible(boolean b) {
 448             super.setVisible(b);
 449             if (getComponentCount() > 0) {
 450                 getComponent(0).setVisible(b);
 451             }
 452         }
 453 
 454         /**
 455          * Overridden to fix 4759054. Must return true so that content
 456          * is painted when inside a CellRendererPane which is normally
 457          * invisible.
 458          */
 459         public boolean isShowing() {
 460             return true;
 461         }
 462 
 463         public Dimension getMinimumSize() {
 464             validateIfNecessary();
 465             return min;
 466         }
 467 
 468         public Dimension getPreferredSize() {
 469             validateIfNecessary();
 470             return pref;
 471         }
 472 
 473         public Dimension getMaximumSize() {
 474             validateIfNecessary();
 475             return max;
 476         }
 477 
 478         public float getAlignmentX() {
 479             validateIfNecessary();
 480             return xalign;
 481         }
 482 
 483         public float getAlignmentY() {
 484             validateIfNecessary();
 485             return yalign;
 486         }
 487 
 488         public Set<AWTKeyStroke> getFocusTraversalKeys(int id) {
 489             return KeyboardFocusManager.getCurrentKeyboardFocusManager().
 490                     getDefaultFocusTraversalKeys(id);
 491         }
 492 
 493         public void propertyChange(PropertyChangeEvent ev) {
 494             Boolean enable = (Boolean) ev.getNewValue();
 495             if (getComponentCount() > 0) {
 496                 getComponent(0).setEnabled(enable);
 497             }
 498         }
 499 
 500         Dimension min;
 501         Dimension pref;
 502         Dimension max;
 503         float yalign;
 504         float xalign;
 505 
 506     }
 507 
 508 }