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 }