001/* ComponentView.java -- 
002   Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.swing.text;
039
040import java.awt.Component;
041import java.awt.Container;
042import java.awt.Dimension;
043import java.awt.Graphics;
044import java.awt.Rectangle;
045import java.awt.Shape;
046
047import javax.swing.SwingUtilities;
048
049/**
050 * A {@link View} implementation that is able to render arbitrary
051 * {@link Component}s. This uses the attribute
052 * {@link StyleConstants#ComponentAttribute} to determine the
053 * <code>Component</code> that should be rendered. This <code>Component</code>
054 * becomes a direct child of the <code>JTextComponent</code> that contains
055 * this <code>ComponentView</code>, so this view must not be shared between
056 * multiple <code>JTextComponent</code>s.
057 *
058 * @author Roman Kennke (kennke@aicas.com)
059 * @author original author unknown
060 */
061public class ComponentView extends View
062{
063
064  /**
065   * A special container that sits between the component and the hosting
066   * container. This is used to propagate invalidate requests and cache
067   * the component's layout sizes.
068   */
069  private class Interceptor
070    extends Container
071  {
072    Dimension min;
073    Dimension pref;
074    Dimension max;
075    float alignX;
076    float alignY;
077
078    /**
079     * Creates a new instance that hosts the specified component.
080     */
081    Interceptor(Component c)
082    {
083      setLayout(null);
084      add(c);
085      cacheComponentSizes();
086    }
087
088    /**
089     * Intercepts the normal invalidate call and propagates the invalidate
090     * request up using the View's preferenceChanged().
091     */
092    public void invalidate()
093    {
094      super.invalidate();
095      if (getParent() != null)
096        preferenceChanged(null, true, true);
097    }
098
099    /**
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}