Frames | No Frames |
1: /* View.java -- 2: Copyright (C) 2002, 2004, 2005, 2006 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: 39: package javax.swing.text; 40: 41: import java.awt.Container; 42: import java.awt.Graphics; 43: import java.awt.Rectangle; 44: import java.awt.Shape; 45: 46: import javax.swing.SwingConstants; 47: import javax.swing.SwingUtilities; 48: import javax.swing.event.DocumentEvent; 49: 50: public abstract class View implements SwingConstants 51: { 52: public static final int BadBreakWeight = 0; 53: public static final int ExcellentBreakWeight = 2000; 54: public static final int ForcedBreakWeight = 3000; 55: public static final int GoodBreakWeight = 1000; 56: 57: public static final int X_AXIS = 0; 58: public static final int Y_AXIS = 1; 59: 60: private float width, height; 61: private Element elt; 62: private View parent; 63: 64: /** 65: * Creates a new <code>View</code> instance. 66: * 67: * @param elem an <code>Element</code> value 68: */ 69: public View(Element elem) 70: { 71: elt = elem; 72: } 73: 74: public abstract void paint(Graphics g, Shape s); 75: 76: /** 77: * Sets the parent for this view. This is the first method that is beeing 78: * called on a view to setup the view hierarchy. This is also the last method 79: * beeing called when the view is disconnected from the view hierarchy, in 80: * this case <code>parent</code> is null. 81: * 82: * If <code>parent</code> is <code>null</code>, a call to this method also 83: * calls <code>setParent</code> on the children, thus disconnecting them from 84: * the view hierarchy. That means that super must be called when this method 85: * is overridden. 86: * 87: * @param parent the parent to set, <code>null</code> when this view is 88: * beeing disconnected from the view hierarchy 89: */ 90: public void setParent(View parent) 91: { 92: if (parent == null) 93: { 94: int numChildren = getViewCount(); 95: for (int i = 0; i < numChildren; i++) 96: getView(i).setParent(null); 97: } 98: 99: this.parent = parent; 100: } 101: 102: public View getParent() 103: { 104: return parent; 105: } 106: 107: public Container getContainer() 108: { 109: View parent = getParent(); 110: if (parent == null) 111: return null; 112: else 113: return parent.getContainer(); 114: } 115: 116: public Document getDocument() 117: { 118: return getElement().getDocument(); 119: } 120: 121: public Element getElement() 122: { 123: return elt; 124: } 125: 126: /** 127: * Returns the preferred span along the specified axis. Normally the view is 128: * rendered with the span returned here if that is possible. 129: * 130: * @param axis the axis 131: * 132: * @return the preferred span along the specified axis 133: */ 134: public abstract float getPreferredSpan(int axis); 135: 136: /** 137: * Returns the resize weight of this view. A value of <code>0</code> or less 138: * means this view is not resizeable. Positive values make the view 139: * resizeable. The default implementation returns <code>0</code> 140: * unconditionally. 141: * 142: * @param axis the axis 143: * 144: * @return the resizability of this view along the specified axis 145: */ 146: public int getResizeWeight(int axis) 147: { 148: return 0; 149: } 150: 151: /** 152: * Returns the maximum span along the specified axis. The default 153: * implementation will forward to 154: * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)} 155: * returns a value > 0, in which case this returns {@link Integer#MIN_VALUE}. 156: * 157: * @param axis the axis 158: * 159: * @return the maximum span along the specified axis 160: */ 161: public float getMaximumSpan(int axis) 162: { 163: float max = Integer.MAX_VALUE; 164: if (getResizeWeight(axis) <= 0) 165: max = getPreferredSpan(axis); 166: return max; 167: } 168: 169: /** 170: * Returns the minimum span along the specified axis. The default 171: * implementation will forward to 172: * {@link #getPreferredSpan(int)} unless {@link #getResizeWeight(int)} 173: * returns a value > 0, in which case this returns <code>0</code>. 174: * 175: * @param axis the axis 176: * 177: * @return the minimum span along the specified axis 178: */ 179: public float getMinimumSpan(int axis) 180: { 181: float min = 0; 182: if (getResizeWeight(axis) <= 0) 183: min = getPreferredSpan(axis); 184: return min; 185: } 186: 187: public void setSize(float width, float height) 188: { 189: // The default implementation does nothing. 190: } 191: 192: /** 193: * Returns the alignment of this view along the baseline of the parent view. 194: * An alignment of <code>0.0</code> will align this view with the left edge 195: * along the baseline, an alignment of <code>0.5</code> will align it 196: * centered to the baseline, an alignment of <code>1.0</code> will align 197: * the right edge along the baseline. 198: * 199: * The default implementation returns 0.5 unconditionally. 200: * 201: * @param axis the axis 202: * 203: * @return the alignment of this view along the parents baseline for the 204: * specified axis 205: */ 206: public float getAlignment(int axis) 207: { 208: return 0.5f; 209: } 210: 211: public AttributeSet getAttributes() 212: { 213: return getElement().getAttributes(); 214: } 215: 216: public boolean isVisible() 217: { 218: return true; 219: } 220: 221: public int getViewCount() 222: { 223: return 0; 224: } 225: 226: public View getView(int index) 227: { 228: return null; 229: } 230: 231: public ViewFactory getViewFactory() 232: { 233: View parent = getParent(); 234: return parent != null ? parent.getViewFactory() : null; 235: } 236: 237: /** 238: * Replaces a couple of child views with new child views. If 239: * <code>length == 0</code> then this is a simple insertion, if 240: * <code>views == null</code> this only removes some child views. 241: * 242: * @param offset the offset at which to replace 243: * @param length the number of child views to be removed 244: * @param views the new views to be inserted, may be <code>null</code> 245: */ 246: public void replace(int offset, int length, View[] views) 247: { 248: // Default implementation does nothing. 249: } 250: 251: public void insert(int offset, View view) 252: { 253: View[] array = { view }; 254: replace(offset, 1, array); 255: } 256: 257: public void append(View view) 258: { 259: View[] array = { view }; 260: int offset = getViewCount(); 261: replace(offset, 0, array); 262: } 263: 264: public void removeAll() 265: { 266: replace(0, getViewCount(), new View[0]); 267: } 268: 269: public void remove(int index) 270: { 271: replace(index, 1, null); 272: } 273: 274: public View createFragment(int p0, int p1) 275: { 276: // The default implementation doesn't support fragmentation. 277: return this; 278: } 279: 280: public int getStartOffset() 281: { 282: return getElement().getStartOffset(); 283: } 284: 285: public int getEndOffset() 286: { 287: return getElement().getEndOffset(); 288: } 289: 290: public Shape getChildAllocation(int index, Shape a) 291: { 292: return null; 293: } 294: 295: /** 296: * @since 1.4 297: */ 298: public int getViewIndex(float x, float y, Shape allocation) 299: { 300: return -1; 301: } 302: 303: /** 304: * @since 1.4 305: */ 306: public String getToolTipText(float x, float y, Shape allocation) 307: { 308: int index = getViewIndex(x, y, allocation); 309: 310: if (index < -1) 311: return null; 312: 313: Shape childAllocation = getChildAllocation(index, allocation); 314: 315: if (childAllocation.getBounds().contains(x, y)) 316: return getView(index).getToolTipText(x, y, childAllocation); 317: 318: return null; 319: } 320: 321: /** 322: * @since 1.3 323: */ 324: public Graphics getGraphics() 325: { 326: return getContainer().getGraphics(); 327: } 328: 329: public void preferenceChanged(View child, boolean width, boolean height) 330: { 331: if (parent != null) 332: parent.preferenceChanged(this, width, height); 333: } 334: 335: public int getBreakWeight(int axis, float pos, float len) 336: { 337: return BadBreakWeight; 338: } 339: 340: public View breakView(int axis, int offset, float pos, float len) 341: { 342: return this; 343: } 344: 345: /** 346: * @since 1.3 347: */ 348: public int getViewIndex(int pos, Position.Bias b) 349: { 350: return -1; 351: } 352: 353: /** 354: * Receive notification about an insert update to the text model. 355: * 356: * The default implementation of this method does the following: 357: * <ul> 358: * <li>Call {@link #updateChildren} if the element that this view is 359: * responsible for has changed. This makes sure that the children can 360: * correctly represent the model.<li> 361: * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to 362: * the child views.<li> 363: * <li>Call {@link #updateLayout}. Gives the view a chance to either 364: * repair its layout, reschedule layout or do nothing at all.</li> 365: * </ul> 366: * 367: * @param ev the DocumentEvent that describes the change 368: * @param shape the shape of the view 369: * @param vf the ViewFactory for creating child views 370: */ 371: public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) 372: { 373: Element el = getElement(); 374: DocumentEvent.ElementChange ec = ev.getChange(el); 375: if (ec != null) 376: updateChildren(ec, ev, vf); 377: forwardUpdate(ec, ev, shape, vf); 378: updateLayout(ec, ev, shape); 379: } 380: 381: /** 382: * Receive notification about a remove update to the text model. 383: * 384: * The default implementation of this method does the following: 385: * <ul> 386: * <li>Call {@link #updateChildren} if the element that this view is 387: * responsible for has changed. This makes sure that the children can 388: * correctly represent the model.<li> 389: * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to 390: * the child views.<li> 391: * <li>Call {@link #updateLayout}. Gives the view a chance to either 392: * repair its layout, reschedule layout or do nothing at all.</li> 393: * </ul> 394: * 395: * @param ev the DocumentEvent that describes the change 396: * @param shape the shape of the view 397: * @param vf the ViewFactory for creating child views 398: */ 399: public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) 400: { 401: Element el = getElement(); 402: DocumentEvent.ElementChange ec = ev.getChange(el); 403: if (ec != null) 404: updateChildren(ec, ev, vf); 405: forwardUpdate(ec, ev, shape, vf); 406: updateLayout(ec, ev, shape); 407: } 408: 409: /** 410: * Receive notification about a change update to the text model. 411: * 412: * The default implementation of this method does the following: 413: * <ul> 414: * <li>Call {@link #updateChildren} if the element that this view is 415: * responsible for has changed. This makes sure that the children can 416: * correctly represent the model.<li> 417: * <li>Call {@link #forwardUpdate}. This forwards the DocumentEvent to 418: * the child views.<li> 419: * <li>Call {@link #updateLayout}. Gives the view a chance to either 420: * repair its layout, reschedule layout or do nothing at all.</li> 421: * </ul> 422: * 423: * @param ev the DocumentEvent that describes the change 424: * @param shape the shape of the view 425: * @param vf the ViewFactory for creating child views 426: */ 427: public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf) 428: { 429: Element el = getElement(); 430: DocumentEvent.ElementChange ec = ev.getChange(el); 431: if (ec != null) 432: updateChildren(ec, ev, vf); 433: forwardUpdate(ec, ev, shape, vf); 434: updateLayout(ec, ev, shape); 435: } 436: 437: /** 438: * Updates the list of children that is returned by {@link #getView} 439: * and {@link #getViewCount}. 440: * 441: * Element that are specified as beeing added in the ElementChange record are 442: * assigned a view for using the ViewFactory. Views of Elements that 443: * are specified as beeing removed are removed from the list. 444: * 445: * @param ec the ElementChange record that describes the change of the 446: * element 447: * @param ev the DocumentEvent describing the change of the document model 448: * @param vf the ViewFactory to use for creating new views 449: * 450: * @return whether or not the child views represent the child elements of 451: * the element that this view is responsible for. Some views may 452: * create views that are responsible only for parts of the element 453: * that they are responsible for and should then return false. 454: * 455: * @since 1.3 456: */ 457: protected boolean updateChildren(DocumentEvent.ElementChange ec, 458: DocumentEvent ev, 459: ViewFactory vf) 460: { 461: Element[] added = ec.getChildrenAdded(); 462: Element[] removed = ec.getChildrenRemoved(); 463: int index = ec.getIndex(); 464: 465: View[] newChildren = new View[added.length]; 466: for (int i = 0; i < added.length; ++i) 467: newChildren[i] = vf.create(added[i]); 468: replace(index, removed.length, newChildren); 469: 470: return true; 471: } 472: 473: /** 474: * Forwards the DocumentEvent to child views that need to get notified 475: * of the change to the model. This calles {@link #forwardUpdateToView} 476: * for each View that must be forwarded to. 477: * 478: * If <code>ec</code> is not <code>null</code> (this means there have been 479: * structural changes to the element that this view is responsible for) this 480: * method should recognize this and don't notify newly added child views. 481: * 482: * @param ec the ElementChange describing the element changes (may be 483: * <code>null</code> if there were no changes) 484: * @param ev the DocumentEvent describing the changes to the model 485: * @param shape the current allocation of the view 486: * @param vf the ViewFactory used to create new Views 487: * 488: * @since 1.3 489: */ 490: protected void forwardUpdate(DocumentEvent.ElementChange ec, 491: DocumentEvent ev, Shape shape, ViewFactory vf) 492: { 493: int count = getViewCount(); 494: if (count > 0) 495: { 496: int startOffset = ev.getOffset(); 497: int endOffset = startOffset + ev.getLength(); 498: int startIndex = getViewIndex(startOffset, Position.Bias.Backward); 499: int endIndex = getViewIndex(endOffset, Position.Bias.Forward); 500: int index = -1; 501: int addLength = -1; 502: if (ec != null) 503: { 504: index = ec.getIndex(); 505: addLength = ec.getChildrenAdded().length; 506: } 507: 508: if (startIndex >= 0 && endIndex >= 0) 509: { 510: for (int i = startIndex; i <= endIndex; i++) 511: { 512: // Skip newly added child views. 513: if (index >= 0 && i >= index && i < (index+addLength)) 514: continue; 515: View child = getView(i); 516: forwardUpdateToView(child, ev, shape, vf); 517: } 518: } 519: } 520: } 521: 522: /** 523: * Forwards an update event to the given child view. This calls 524: * {@link #insertUpdate}, {@link #removeUpdate} or {@link #changedUpdate}, 525: * depending on the type of document event. 526: * 527: * @param view the View to forward the event to 528: * @param ev the DocumentEvent to forward 529: * @param shape the current allocation of the View 530: * @param vf the ViewFactory used to create new Views 531: * 532: * @since 1.3 533: */ 534: protected void forwardUpdateToView(View view, DocumentEvent ev, Shape shape, 535: ViewFactory vf) 536: { 537: DocumentEvent.EventType type = ev.getType(); 538: if (type == DocumentEvent.EventType.INSERT) 539: view.insertUpdate(ev, shape, vf); 540: else if (type == DocumentEvent.EventType.REMOVE) 541: view.removeUpdate(ev, shape, vf); 542: else if (type == DocumentEvent.EventType.CHANGE) 543: view.changedUpdate(ev, shape, vf); 544: } 545: 546: /** 547: * Updates the layout. 548: * 549: * @param ec the ElementChange that describes the changes to the element 550: * @param ev the DocumentEvent that describes the changes to the model 551: * @param shape the current allocation for this view 552: * 553: * @since 1.3 554: */ 555: protected void updateLayout(DocumentEvent.ElementChange ec, 556: DocumentEvent ev, Shape shape) 557: { 558: if (ec != null && shape != null) 559: preferenceChanged(null, true, true); 560: Container c = getContainer(); 561: if (c != null) 562: c.repaint(); 563: } 564: 565: /** 566: * Maps a position in the document into the coordinate space of the View. 567: * The output rectangle usually reflects the font height but has a width 568: * of zero. 569: * 570: * @param pos the position of the character in the model 571: * @param a the area that is occupied by the view 572: * @param b either {@link Position.Bias#Forward} or 573: * {@link Position.Bias#Backward} depending on the preferred 574: * direction bias. If <code>null</code> this defaults to 575: * <code>Position.Bias.Forward</code> 576: * 577: * @return a rectangle that gives the location of the document position 578: * inside the view coordinate space 579: * 580: * @throws BadLocationException if <code>pos</code> is invalid 581: * @throws IllegalArgumentException if b is not one of the above listed 582: * valid values 583: */ 584: public abstract Shape modelToView(int pos, Shape a, Position.Bias b) 585: throws BadLocationException; 586: 587: /** 588: * Maps a region in the document into the coordinate space of the View. 589: * 590: * @param p1 the beginning position inside the document 591: * @param b1 the direction bias for the beginning position 592: * @param p2 the end position inside the document 593: * @param b2 the direction bias for the end position 594: * @param a the area that is occupied by the view 595: * 596: * @return a rectangle that gives the span of the document region 597: * inside the view coordinate space 598: * 599: * @throws BadLocationException if <code>p1</code> or <code>p2</code> are 600: * invalid 601: * @throws IllegalArgumentException if b1 or b2 is not one of the above 602: * listed valid values 603: */ 604: public Shape modelToView(int p1, Position.Bias b1, 605: int p2, Position.Bias b2, Shape a) 606: throws BadLocationException 607: { 608: if (b1 != Position.Bias.Forward && b1 != Position.Bias.Backward) 609: throw new IllegalArgumentException 610: ("b1 must be either Position.Bias.Forward or Position.Bias.Backward"); 611: if (b2 != Position.Bias.Forward && b2 != Position.Bias.Backward) 612: throw new IllegalArgumentException 613: ("b2 must be either Position.Bias.Forward or Position.Bias.Backward"); 614: Rectangle s1 = (Rectangle) modelToView(p1, a, b1); 615: Rectangle s2 = (Rectangle) modelToView(p2, a, b2); 616: return SwingUtilities.computeUnion(s1.x, s1.y, s1.width, s1.height, s2); 617: } 618: 619: /** 620: * Maps a position in the document into the coordinate space of the View. 621: * The output rectangle usually reflects the font height but has a width 622: * of zero. 623: * 624: * This method is deprecated and calls 625: * {@link #modelToView(int, Position.Bias, int, Position.Bias, Shape)} with 626: * a bias of {@link Position.Bias#Forward}. 627: * 628: * @param pos the position of the character in the model 629: * @param a the area that is occupied by the view 630: * 631: * @return a rectangle that gives the location of the document position 632: * inside the view coordinate space 633: * 634: * @throws BadLocationException if <code>pos</code> is invalid 635: * 636: * @deprecated Use {@link #modelToView(int, Shape, Position.Bias)} instead. 637: */ 638: public Shape modelToView(int pos, Shape a) throws BadLocationException 639: { 640: return modelToView(pos, a, Position.Bias.Forward); 641: } 642: 643: /** 644: * Maps coordinates from the <code>View</code>'s space into a position 645: * in the document model. 646: * 647: * @param x the x coordinate in the view space 648: * @param y the y coordinate in the view space 649: * @param a the allocation of this <code>View</code> 650: * @param b the bias to use 651: * 652: * @return the position in the document that corresponds to the screen 653: * coordinates <code>x, y</code> 654: */ 655: public abstract int viewToModel(float x, float y, Shape a, Position.Bias[] b); 656: 657: /** 658: * Maps coordinates from the <code>View</code>'s space into a position 659: * in the document model. This method is deprecated and only there for 660: * compatibility. 661: * 662: * @param x the x coordinate in the view space 663: * @param y the y coordinate in the view space 664: * @param a the allocation of this <code>View</code> 665: * 666: * @return the position in the document that corresponds to the screen 667: * coordinates <code>x, y</code> 668: * 669: * @deprecated Use {@link #viewToModel(float, float, Shape, Position.Bias[])} 670: * instead. 671: */ 672: public int viewToModel(float x, float y, Shape a) 673: { 674: return viewToModel(x, y, a, new Position.Bias[0]); 675: } 676: 677: /** 678: * Dumps the complete View hierarchy. This method can be used for debugging 679: * purposes. 680: */ 681: protected void dump() 682: { 683: // Climb up the hierarchy to the parent. 684: View parent = getParent(); 685: if (parent != null) 686: parent.dump(); 687: else 688: dump(0); 689: } 690: 691: /** 692: * Dumps the view hierarchy below this View with the specified indentation 693: * level. 694: * 695: * @param indent the indentation level to be used for this view 696: */ 697: void dump(int indent) 698: { 699: for (int i = 0; i < indent; ++i) 700: System.out.print('.'); 701: System.out.println(this + "(" + getStartOffset() + "," + getEndOffset() + ": " + getElement()); 702: 703: int count = getViewCount(); 704: for (int i = 0; i < count; ++i) 705: getView(i).dump(indent + 1); 706: } 707: 708: /** 709: * Returns the document position that is (visually) nearest to the given 710: * document position <code>pos</code> in the given direction <code>d</code>. 711: * 712: * @param pos the document position 713: * @param b the bias for <code>pos</code> 714: * @param a the allocation for this view 715: * @param d the direction, must be either {@link SwingConstants#NORTH}, 716: * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or 717: * {@link SwingConstants#EAST} 718: * @param biasRet an array of {@link Position.Bias} that can hold at least 719: * one element, which is filled with the bias of the return position 720: * on method exit 721: * 722: * @return the document position that is (visually) nearest to the given 723: * document position <code>pos</code> in the given direction 724: * <code>d</code> 725: * 726: * @throws BadLocationException if <code>pos</code> is not a valid offset in 727: * the document model 728: * @throws IllegalArgumentException if <code>d</code> is not a valid direction 729: */ 730: public int getNextVisualPositionFrom(int pos, Position.Bias b, 731: Shape a, int d, 732: Position.Bias[] biasRet) 733: throws BadLocationException 734: { 735: int ret = pos; 736: Rectangle r; 737: View parent; 738: 739: switch (d) 740: { 741: case EAST: 742: // TODO: take component orientation into account? 743: // Note: If pos is below zero the implementation will return 744: // pos + 1 regardless of whether that value is a correct offset 745: // in the document model. However this is what the RI does. 746: ret = Math.min(pos + 1, getEndOffset()); 747: break; 748: case WEST: 749: // TODO: take component orientation into account? 750: ret = Math.max(pos - 1, getStartOffset()); 751: break; 752: case NORTH: 753: // Try to find a suitable offset by examining the area above. 754: parent = getParent(); 755: r = parent.modelToView(pos, a, b).getBounds(); 756: ret = parent.viewToModel(r.x, r.y - 1, a, biasRet); 757: break; 758: case SOUTH: 759: // Try to find a suitable offset by examining the area below. 760: parent = getParent(); 761: r = parent.modelToView(pos, a, b).getBounds(); 762: ret = parent.viewToModel(r.x + r.width, r.y + r.height, a, biasRet); 763: break; 764: default: 765: throw new IllegalArgumentException("Illegal value for d"); 766: } 767: 768: return ret; 769: } 770: }