001    /* ComponentView.java -- 
002       Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.swing.text;
039    
040    import java.awt.Component;
041    import java.awt.Container;
042    import java.awt.Dimension;
043    import java.awt.Graphics;
044    import java.awt.Rectangle;
045    import java.awt.Shape;
046    
047    import 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     */
061    public 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    }