Source for javax.swing.text.ComponentView

   1: /* ComponentView.java --
   2:    Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package javax.swing.text;
  39: 
  40: import java.awt.Component;
  41: import java.awt.Container;
  42: import java.awt.Dimension;
  43: import java.awt.Graphics;
  44: import java.awt.Rectangle;
  45: import java.awt.Shape;
  46: 
  47: import javax.swing.SwingUtilities;
  48: 
  49: /**
  50:  * A {@link View} implementation that is able to render arbitrary
  51:  * {@link Component}s. This uses the attribute
  52:  * {@link StyleConstants#ComponentAttribute} to determine the
  53:  * <code>Component</code> that should be rendered. This <code>Component</code>
  54:  * becomes a direct child of the <code>JTextComponent</code> that contains
  55:  * this <code>ComponentView</code>, so this view must not be shared between
  56:  * multiple <code>JTextComponent</code>s.
  57:  *
  58:  * @author Roman Kennke (kennke@aicas.com)
  59:  * @author original author unknown
  60:  */
  61: public class ComponentView extends View
  62: {
  63: 
  64:   /**
  65:    * A special container that sits between the component and the hosting
  66:    * container. This is used to propagate invalidate requests and cache
  67:    * the component's layout sizes.
  68:    */
  69:   private class Interceptor
  70:     extends Container
  71:   {
  72:     Dimension min;
  73:     Dimension pref;
  74:     Dimension max;
  75:     float alignX;
  76:     float alignY;
  77: 
  78:     /**
  79:      * Creates a new instance that hosts the specified component.
  80:      */
  81:     Interceptor(Component c)
  82:     {
  83:       setLayout(null);
  84:       add(c);
  85:       cacheComponentSizes();
  86:     }
  87: 
  88:     /**
  89:      * Intercepts the normal invalidate call and propagates the invalidate
  90:      * request up using the View's preferenceChanged().
  91:      */
  92:     public void invalidate()
  93:     {
  94:       super.invalidate();
  95:       if (getParent() != null)
  96:         preferenceChanged(null, true, true);
  97:     }
  98: 
  99:     /**
 100:      * This is overridden to simply cache the layout sizes.
 101:      */
 102:     public void doLayout()
 103:     {
 104:       cacheComponentSizes();
 105:     }
 106: 
 107:     /**
 108:      * Overridden to also reshape the component itself.
 109:      */
 110:     public void reshape(int x, int y, int w, int h)
 111:     {
 112:       super.reshape(x, y, w, h);
 113:       if (getComponentCount() > 0)
 114:         getComponent(0).setSize(w, h);
 115:       cacheComponentSizes();
 116:     }
 117: 
 118:     /**
 119:      * Overridden to also show the component.
 120:      */
 121:     public void show()
 122:     {
 123:       super.show();
 124:       if (getComponentCount() > 0)
 125:         getComponent(0).setVisible(true);
 126:     }
 127: 
 128:     /**
 129:      * Overridden to also hide the component.
 130:      */
 131:     public void hide()
 132:     {
 133:       super.hide();
 134:       if (getComponentCount() > 0)
 135:         getComponent(0).setVisible(false);
 136:     }
 137: 
 138:     /**
 139:      * Overridden to return the cached value.
 140:      */
 141:     public Dimension getMinimumSize()
 142:     {
 143:       maybeValidate();
 144:       return min;
 145:     }
 146: 
 147:     /**
 148:      * Overridden to return the cached value.
 149:      */
 150:     public Dimension getPreferredSize()
 151:     {
 152:       maybeValidate();
 153:       return pref;
 154:     }
 155: 
 156:     /**
 157:      * Overridden to return the cached value.
 158:      */
 159:     public Dimension getMaximumSize()
 160:     {
 161:       maybeValidate();
 162:       return max;
 163:     }
 164: 
 165:     /**
 166:      * Overridden to return the cached value.
 167:      */
 168:     public float getAlignmentX()
 169:     {
 170:       maybeValidate();
 171:       return alignX;
 172:     }
 173: 
 174:     /**
 175:      * Overridden to return the cached value.
 176:      */
 177:     public float getAlignmentY()
 178:     {
 179:       maybeValidate();
 180:       return alignY;
 181:     }
 182: 
 183:     /**
 184:      * Validates the container only when necessary.
 185:      */
 186:     private void maybeValidate()
 187:     {
 188:       if (! isValid())
 189:         validate();
 190:     }
 191: 
 192:     /**
 193:      * Fetches the component layout sizes into the cache.
 194:      */
 195:     private void cacheComponentSizes()
 196:     {
 197:       if (getComponentCount() > 0)
 198:         {
 199:           Component c = getComponent(0);
 200:           min = c.getMinimumSize();
 201:           pref = c.getPreferredSize();
 202:           max = c.getMaximumSize();
 203:           alignX = c.getAlignmentX();
 204:           alignY = c.getAlignmentY();
 205:         }
 206:     }
 207:   }
 208: 
 209:   /**
 210:    * The component that is displayed by this view.
 211:    */
 212:   private Component comp;
 213: 
 214:   /**
 215:    * The intercepting container.
 216:    */
 217:   private Interceptor interceptor;
 218: 
 219:   /**
 220:    * Creates a new instance of <code>ComponentView</code> for the specified
 221:    * <code>Element</code>.
 222:    *
 223:    * @param elem the element that this <code>View</code> is rendering
 224:    */
 225:   public ComponentView(Element elem)
 226:   {
 227:     super(elem);
 228:   }
 229: 
 230:   /**
 231:    * Creates the <code>Component</code> that this <code>View</code> is
 232:    * rendering. The <code>Component</code> is determined using
 233:    * the {@link StyleConstants#ComponentAttribute} of the associated
 234:    * <code>Element</code>.
 235:    *
 236:    * @return the component that is rendered
 237:    */
 238:   protected Component createComponent()
 239:   {
 240:     return StyleConstants.getComponent(getElement().getAttributes());
 241:   }
 242: 
 243:   /**
 244:    * Returns the alignment of this <code>View</code> along the specified axis.
 245:    *
 246:    * @param axis either {@link View#X_AXIS} or {@link View#Y_AXIS}
 247:    *
 248:    * @return the alignment of this <code>View</code> along the specified axis
 249:    */
 250:   public float getAlignment(int axis)
 251:   {
 252:     float align = 0.0F;
 253:     // I'd rather throw an IllegalArgumentException for illegal axis,
 254:     // but the Harmony testsuite indicates fallback to super behaviour.
 255:     if (interceptor != null && (axis == X_AXIS || axis == Y_AXIS))
 256:       {
 257:         if (axis == X_AXIS)
 258:           align = interceptor.getAlignmentX();
 259:         else if (axis == Y_AXIS)
 260:           align = interceptor.getAlignmentY();
 261:         else
 262:           assert false : "Must not reach here";
 263:       }
 264:     else
 265:       align = super.getAlignment(axis);
 266:     return align;
 267:   }
 268: 
 269:   /**
 270:    * Returns the <code>Component</code> that is rendered by this
 271:    * <code>ComponentView</code>.
 272:    *
 273:    * @return the <code>Component</code> that is rendered by this
 274:    *         <code>ComponentView</code>
 275:    */
 276:   public final Component getComponent()
 277:   {
 278:     return comp;
 279:   }
 280: 
 281:   /**
 282:    * Returns the maximum span of this <code>View</code> along the specified
 283:    * axis.
 284:    *
 285:    * This will return {@link Component#getMaximumSize()} for the specified
 286:    * axis.
 287:    *
 288:    * @return the maximum span of this <code>View</code> along the specified
 289:    *         axis
 290:    */
 291:   public float getMaximumSpan(int axis)
 292:   {
 293:     if (axis != X_AXIS && axis != Y_AXIS)
 294:       throw new IllegalArgumentException("Illegal axis");
 295:     float span = 0;
 296:     if (interceptor != null)
 297:       {
 298:         if (axis == X_AXIS)
 299:           span = interceptor.getMaximumSize().width;
 300:         else if (axis == Y_AXIS)
 301:           span = interceptor.getMaximumSize().height;
 302:         else
 303:           assert false : "Must not reach here";
 304:       }
 305:     return span;
 306:   }
 307: 
 308:   public float getMinimumSpan(int axis)
 309:   {
 310:     if (axis != X_AXIS && axis != Y_AXIS)
 311:       throw new IllegalArgumentException("Illegal axis");
 312:     float span = 0;
 313:     if (interceptor != null)
 314:       {
 315:         if (axis == X_AXIS)
 316:           span = interceptor.getMinimumSize().width;
 317:         else if (axis == Y_AXIS)
 318:           span = interceptor.getMinimumSize().height;
 319:         else
 320:           assert false : "Must not reach here";
 321:       }
 322:     return span;
 323:   }
 324: 
 325:   public float getPreferredSpan(int axis)
 326:   {
 327:     if (axis != X_AXIS && axis != Y_AXIS)
 328:       throw new IllegalArgumentException("Illegal axis");
 329:     float span = 0;
 330:     if (interceptor != null)
 331:       {
 332:         if (axis == X_AXIS)
 333:           span = interceptor.getPreferredSize().width;
 334:         else if (axis == Y_AXIS)
 335:           span = interceptor.getPreferredSize().height;
 336:         else
 337:           assert false : "Must not reach here";
 338:       }
 339:     return span;
 340:   }
 341: 
 342:   public Shape modelToView(int pos, Shape a, Position.Bias b)
 343:     throws BadLocationException
 344:   {
 345:     int p0 = getStartOffset();
 346:     int p1 = getEndOffset();
 347:     if (pos >= p0 && pos <= p1)
 348:       {
 349:         Rectangle viewRect = a.getBounds();
 350:         if (pos == p1)
 351:           viewRect.x += viewRect.width;
 352:         viewRect.width = 0;
 353:         return viewRect;
 354:       }
 355:     else
 356:       throw new BadLocationException("Illegal position", pos);
 357:   }
 358: 
 359:   /**
 360:    * The real painting behavour is performed by normal component painting,
 361:    * triggered by the text component that hosts this view. This method does
 362:    * not paint by itself. However, it sets the size of the component according
 363:    * to the allocation that is passed here.
 364:    *
 365:    * @param g the graphics context
 366:    * @param a the allocation of the child
 367:    */
 368:   public void paint(Graphics g, Shape a)
 369:   {
 370:     if (interceptor != null)
 371:       {
 372:         Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
 373:         interceptor.setBounds(r.x, r.y, r.width, r.height);
 374:       }
 375:   }
 376: 
 377:   /**
 378:    * This sets up the component when the view is added to its parent, or
 379:    * cleans up the view when it is removed from its parent.
 380:    *
 381:    * When this view is added to a parent view, the component of this view
 382:    * is added to the container that hosts this view. When <code>p</code> is
 383:    * <code>null</code>, then the view is removed from it's parent and we have
 384:    * to also remove the component from it's parent container.
 385:    *
 386:    * @param p the parent view or <code>null</code> if this view is removed
 387:    *        from it's parent
 388:    */
 389:   public void setParent(final View p)
 390:   {
 391:     super.setParent(p);
 392:     if (SwingUtilities.isEventDispatchThread())
 393:       setParentImpl();
 394:     else
 395:       SwingUtilities.invokeLater
 396:       (new Runnable()
 397:        {
 398:          public void run()
 399:          {
 400:            Document doc = getDocument();
 401:            try
 402:              {
 403:                if (doc instanceof AbstractDocument)
 404:                  ((AbstractDocument) doc).readLock();
 405:                setParentImpl();
 406:                Container host = getContainer();
 407:                if (host != null)
 408:                  {
 409:                    preferenceChanged(null, true, true);
 410:                    host.repaint();
 411:                  }
 412:              }
 413:            finally
 414:              {
 415:                if (doc instanceof AbstractDocument)
 416:                  ((AbstractDocument) doc).readUnlock();
 417:              }
 418:          }
 419:        });
 420:   }
 421: 
 422:   /**
 423:    * The implementation of {@link #setParent}. This is package private to
 424:    * avoid a synthetic accessor method.
 425:    */
 426:   void setParentImpl()
 427:   {
 428:     View p = getParent();
 429:     if (p != null)
 430:       {
 431:         Container c = getContainer();
 432:         if (c != null)
 433:           {
 434:             if (interceptor == null)
 435:               {
 436:                 // Create component and put it inside the interceptor.
 437:                 Component created = createComponent();
 438:                 if (created != null)
 439:                   {
 440:                     comp = created;
 441:                     interceptor = new Interceptor(comp);
 442:                   }
 443:               }
 444:             if (interceptor != null)
 445:               {
 446:                 // Add the interceptor to the hosting container.
 447:                 if (interceptor.getParent() == null)
 448:                   c.add(interceptor, this);
 449:               }
 450:           }
 451:       }
 452:     else
 453:       {
 454:         if (interceptor != null)
 455:           {
 456:             Container parent = interceptor.getParent();
 457:             if (parent != null)
 458:               parent.remove(interceptor);
 459:           }
 460:       }
 461:   }
 462: 
 463:   /**
 464:    * Maps coordinates from the <code>View</code>'s space into a position
 465:    * in the document model.
 466:    *
 467:    * @param x the x coordinate in the view space
 468:    * @param y the y coordinate in the view space
 469:    * @param a the allocation of this <code>View</code>
 470:    * @param b the bias to use
 471:    *
 472:    * @return the position in the document that corresponds to the screen
 473:    *         coordinates <code>x, y</code>
 474:    */
 475:   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 476:   {
 477:     int pos;
 478:     // I'd rather do the following. The harmony testsuite indicates
 479:     // that a simple cast is performed.
 480:     //Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
 481:     Rectangle r = (Rectangle) a;
 482:     if (x < r.x + r.width / 2)
 483:       {
 484:         b[0] = Position.Bias.Forward;
 485:         pos = getStartOffset();
 486:       }
 487:     else
 488:       {
 489:         b[0] = Position.Bias.Backward;
 490:         pos = getEndOffset();
 491:       }
 492:     return pos;
 493:   }
 494: }