001/* FieldView.java -- 
002   Copyright (C) 2004, 2006 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
038
039package javax.swing.text;
040
041import java.awt.Component;
042import java.awt.Container;
043import java.awt.FontMetrics;
044import java.awt.Graphics;
045import java.awt.Insets;
046import java.awt.Rectangle;
047import java.awt.Shape;
048
049import javax.swing.BoundedRangeModel;
050import javax.swing.JTextField;
051import javax.swing.SwingUtilities;
052import javax.swing.event.ChangeEvent;
053import javax.swing.event.ChangeListener;
054import javax.swing.event.DocumentEvent;
055
056public 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}