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 }