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 }