Frames | No Frames |
1: /* ComponentView.java -- 2: Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package javax.swing.text; 39: 40: import java.awt.Component; 41: import java.awt.Container; 42: import java.awt.Dimension; 43: import java.awt.Graphics; 44: import java.awt.Rectangle; 45: import java.awt.Shape; 46: 47: import javax.swing.SwingUtilities; 48: 49: /** 50: * A {@link View} implementation that is able to render arbitrary 51: * {@link Component}s. This uses the attribute 52: * {@link StyleConstants#ComponentAttribute} to determine the 53: * <code>Component</code> that should be rendered. This <code>Component</code> 54: * becomes a direct child of the <code>JTextComponent</code> that contains 55: * this <code>ComponentView</code>, so this view must not be shared between 56: * multiple <code>JTextComponent</code>s. 57: * 58: * @author Roman Kennke (kennke@aicas.com) 59: * @author original author unknown 60: */ 61: public class ComponentView extends View 62: { 63: 64: /** 65: * A special container that sits between the component and the hosting 66: * container. This is used to propagate invalidate requests and cache 67: * the component's layout sizes. 68: */ 69: private class Interceptor 70: extends Container 71: { 72: Dimension min; 73: Dimension pref; 74: Dimension max; 75: float alignX; 76: float alignY; 77: 78: /** 79: * Creates a new instance that hosts the specified component. 80: */ 81: Interceptor(Component c) 82: { 83: setLayout(null); 84: add(c); 85: cacheComponentSizes(); 86: } 87: 88: /** 89: * Intercepts the normal invalidate call and propagates the invalidate 90: * request up using the View's preferenceChanged(). 91: */ 92: public void invalidate() 93: { 94: super.invalidate(); 95: if (getParent() != null) 96: preferenceChanged(null, true, true); 97: } 98: 99: /** 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: }