001    /* FieldView.java --
002       Copyright (C) 2004, 2006 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    
039    package javax.swing.text;
040    
041    import java.awt.Component;
042    import java.awt.Container;
043    import java.awt.FontMetrics;
044    import java.awt.Graphics;
045    import java.awt.Insets;
046    import java.awt.Rectangle;
047    import java.awt.Shape;
048    
049    import javax.swing.BoundedRangeModel;
050    import javax.swing.JTextField;
051    import javax.swing.SwingUtilities;
052    import javax.swing.event.ChangeEvent;
053    import javax.swing.event.ChangeListener;
054    import javax.swing.event.DocumentEvent;
055    
056    public class FieldView extends PlainView
057    {
058      BoundedRangeModel horizontalVisibility;
059    
060      /** Caches the preferred span of the X axis. It is invalidated by
061       * setting it to -1f. This is done when text in the document
062       * is inserted, removed or changed. The value is corrected as
063       * soon as calculateHorizontalSpan() is called.
064       */
065      float cachedSpan = -1f;
066    
067      public FieldView(Element elem)
068      {
069        super(elem);
070    
071      }
072    
073      /** Checks whether the given container is a JTextField. If so
074       * it retrieves the textfield's horizontalVisibility instance.
075       *
076       * <p>This method should be only called when the view's container
077       * is valid. Naturally that would be the setParent() method however
078       * that method is not overridden in the RI and that is why we chose
079       * paint() instead.</p>
080       */
081      private void checkContainer()
082      {
083        Container c = getContainer();
084    
085        if (c instanceof JTextField)
086          {
087            horizontalVisibility = ((JTextField) c).getHorizontalVisibility();
088    
089            // Provokes a repaint when the BoundedRangeModel's values change
090            // (which is what the RI does).
091            horizontalVisibility.addChangeListener(new ChangeListener(){
092              public void stateChanged(ChangeEvent event) {
093                getContainer().repaint();
094              }
095            });
096    
097            // It turned out that the span calculated at this point is wrong
098            // and needs to be recalculated (e.g. a different font setting is
099            // not taken into account).
100            calculateHorizontalSpan();
101    
102            // Initializes the BoundedRangeModel properly.
103            updateVisibility();
104          }
105    
106      }
107    
108      private void updateVisibility()
109      {
110        JTextField tf = (JTextField) getContainer();
111        Insets insets = tf.getInsets();
112    
113        int width = tf.getWidth() - insets.left - insets.right;
114    
115        horizontalVisibility.setMaximum(Math.max((int) ((cachedSpan != -1f)
116                                                     ? cachedSpan
117                                                     : calculateHorizontalSpan()),
118                                                 width));
119    
120        horizontalVisibility.setExtent(width - 1);
121      }
122    
123      protected FontMetrics getFontMetrics()
124      {
125        Component container = getContainer();
126        return container.getFontMetrics(container.getFont());
127      }
128    
129      /**
130       * Vertically centers the single line of text within the
131       * bounds of the input shape. The returned Rectangle is centered
132       * vertically within <code>shape</code> and has a height of the
133       * preferred span along the Y axis. Horizontal adjustment is done according
134       * to the horizontalAligment property of the component that is rendered.
135       *
136       * @param shape the shape within which the line is beeing centered
137       */
138      protected Shape adjustAllocation(Shape shape)
139      {
140        // Return null when the original allocation is null (like the RI).
141        if (shape == null)
142          return null;
143    
144        Rectangle rectIn = shape.getBounds();
145        // vertical adjustment
146        int height = (int) getPreferredSpan(Y_AXIS);
147        int y = rectIn.y + (rectIn.height - height) / 2;
148        // horizontal adjustment
149        JTextField textField = (JTextField) getContainer();
150        int width = (int) ((cachedSpan != -1f) ? cachedSpan : calculateHorizontalSpan());
151        int x;
152        if (horizontalVisibility != null && horizontalVisibility.getExtent() < width)
153            x = rectIn.x - horizontalVisibility.getValue();
154        else
155          switch (textField.getHorizontalAlignment())
156            {
157            case JTextField.CENTER:
158              x = rectIn.x + (rectIn.width - width) / 2;
159              break;
160            case JTextField.RIGHT:
161              x = rectIn.x + (rectIn.width - width - 1);
162              break;
163            case JTextField.TRAILING:
164              if (textField.getComponentOrientation().isLeftToRight())
165                x = rectIn.x + (rectIn.width - width - 1);
166              else
167                x = rectIn.x;
168              break;
169            case JTextField.LEADING:
170              if (textField.getComponentOrientation().isLeftToRight())
171                x = rectIn.x;
172              else
173                x = rectIn.x + (rectIn.width - width - 1);
174              break;
175            case JTextField.LEFT:
176            default:
177              x = rectIn.x;
178              break;
179            }
180    
181        return new Rectangle(x, y, width, height);
182      }
183    
184      public float getPreferredSpan(int axis)
185      {
186        if (axis != X_AXIS && axis != Y_AXIS)
187          throw new IllegalArgumentException();
188    
189    
190        if (axis == Y_AXIS)
191          return super.getPreferredSpan(axis);
192    
193        if (cachedSpan != -1f)
194          return cachedSpan;
195    
196        return calculateHorizontalSpan();
197      }
198    
199      /** Calculates and sets the horizontal span and stores the value
200       * in cachedSpan.
201       */
202      private float calculateHorizontalSpan()
203      {
204        Segment s = getLineBuffer();
205        Element elem = getElement();
206    
207        try
208          {
209            elem.getDocument().getText(elem.getStartOffset(),
210                                              elem.getEndOffset() - 1,
211                                              s);
212    
213            return cachedSpan = Utilities.getTabbedTextWidth(s, getFontMetrics(), 0, this, s.offset);
214          }
215        catch (BadLocationException e)
216          {
217            // Should never happen
218            AssertionError ae = new AssertionError();
219            ae.initCause(e);
220            throw ae;
221          }
222      }
223    
224      public int getResizeWeight(int axis)
225      {
226        return axis == X_AXIS ? 1 : 0;
227      }
228    
229      public Shape modelToView(int pos, Shape a, Position.Bias bias)
230        throws BadLocationException
231      {
232        Shape newAlloc = adjustAllocation(a);
233        return super.modelToView(pos, newAlloc, bias);
234      }
235    
236      public void paint(Graphics g, Shape s)
237      {
238        if (horizontalVisibility == null)
239          checkContainer();
240    
241        Shape newAlloc = adjustAllocation(s);
242    
243        Shape clip = g.getClip();
244        if (clip != null)
245          {
246            // Reason for this: The allocation area is always determined by the
247            // size of the component (and its insets) regardless of whether
248            // parts of the component are invisible or not (e.g. when the
249            // component is part of a JScrollPane and partly moved out of
250            // the user-visible range). However the clip of the Graphics
251            // instance may be adjusted properly to that condition but
252            // does not handle insets. By calculating the intersection
253            // we get the correct clip to paint the text in all cases.
254            Rectangle r = s.getBounds();
255            Rectangle cb = clip.getBounds();
256            SwingUtilities.computeIntersection(r.x, r.y, r.width, r.height, cb);
257    
258            g.setClip(cb);
259          }
260        else
261          g.setClip(s);
262    
263        super.paint(g, newAlloc);
264        g.setClip(clip);
265    
266      }
267    
268      public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
269      {
270        cachedSpan = -1f;
271    
272        if (horizontalVisibility != null)
273          updateVisibility();
274    
275        Shape newAlloc = adjustAllocation(shape);
276    
277        super.insertUpdate(ev, newAlloc, vf);
278        getContainer().repaint();
279      }
280    
281      public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
282      {
283        cachedSpan = -1f;
284    
285        if (horizontalVisibility != null)
286          updateVisibility();
287    
288        Shape newAlloc = adjustAllocation(shape);
289        super.removeUpdate(ev, newAlloc, vf);
290        getContainer().repaint();
291      }
292    
293      public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
294      {
295        cachedSpan = -1f;
296    
297        if (horizontalVisibility != null)
298          updateVisibility();
299    
300        Shape newAlloc = adjustAllocation(shape);
301        super.changedUpdate(ev, newAlloc, vf);
302        getContainer().repaint();
303      }
304    
305      public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias)
306      {
307        return super.viewToModel(fx, fy, adjustAllocation(a), bias);
308      }
309    
310    }