GNU Classpath (0.20) | |
Frames | No Frames |
1: /* CompositeView.java -- An abstract view that manages child views 2: Copyright (C) 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: 39: package javax.swing.text; 40: 41: import java.awt.Insets; 42: import java.awt.Rectangle; 43: import java.awt.Shape; 44: 45: import javax.swing.SwingConstants; 46: 47: /** 48: * An abstract base implementation of {@link View} that manages child 49: * <code>View</code>s. 50: * 51: * @author Roman Kennke (roman@kennke.org) 52: */ 53: public abstract class CompositeView 54: extends View 55: { 56: 57: /** 58: * The child views of this <code>CompositeView</code>. 59: */ 60: View[] children; 61: 62: /** 63: * The allocation of this <code>View</code> minus its insets. This is 64: * initialized in {@link #getInsideAllocation} and reused and modified in 65: * {@link #childAllocation(int, Rectangle)}. 66: */ 67: Rectangle insideAllocation; 68: 69: /** 70: * The insets of this <code>CompositeView</code>. This is initialized 71: * in {@link #setInsets}. 72: */ 73: Insets insets; 74: 75: /** 76: * Creates a new <code>CompositeView</code> for the given 77: * <code>Element</code>. 78: * 79: * @param element the element that is rendered by this CompositeView 80: */ 81: public CompositeView(Element element) 82: { 83: super(element); 84: children = new View[0]; 85: insets = new Insets(0, 0, 0, 0); 86: } 87: 88: /** 89: * Loads the child views of this <code>CompositeView</code>. This method 90: * is called from {@link #setParent} to initialize the child views of 91: * this composite view. 92: * 93: * @param f the view factory to use for creating new child views 94: * 95: * @see #setParent 96: */ 97: protected void loadChildren(ViewFactory f) 98: { 99: Element el = getElement(); 100: int count = el.getElementCount(); 101: View[] newChildren = new View[count]; 102: for (int i = 0; i < count; ++i) 103: { 104: Element child = el.getElement(i); 105: View view = f.create(child); 106: newChildren[i] = view; 107: } 108: replace(0, getViewCount(), newChildren); 109: } 110: 111: /** 112: * Sets the parent of this <code>View</code>. 113: * In addition to setting the parent, this calls {@link #loadChildren}, if 114: * this <code>View</code> does not already have its children initialized. 115: * 116: * @param parent the parent to set 117: */ 118: public void setParent(View parent) 119: { 120: super.setParent(parent); 121: if (parent != null && ((children == null) || children.length == 0)) 122: loadChildren(getViewFactory()); 123: } 124: 125: /** 126: * Returns the number of child views. 127: * 128: * @return the number of child views 129: */ 130: public int getViewCount() 131: { 132: return children.length; 133: } 134: 135: /** 136: * Returns the child view at index <code>n</code>. 137: * 138: * @param n the index of the requested child view 139: * 140: * @return the child view at index <code>n</code> 141: */ 142: public View getView(int n) 143: { 144: return children[n]; 145: } 146: 147: /** 148: * Replaces child views by some other child views. If there are no views to 149: * remove (<code>length == 0</code>), the result is a simple insert, if 150: * there are no children to add (<code>view == null</code>) the result 151: * is a simple removal. 152: * 153: * @param offset the start offset from where to remove children 154: * @param length the number of children to remove 155: * @param views the views that replace the removed children 156: */ 157: public void replace(int offset, int length, View[] views) 158: { 159: // Check for null views to add. 160: for (int i = 0; i < views.length; ++i) 161: if (views[i] == null) 162: throw new NullPointerException("Added views must not be null"); 163: 164: int endOffset = offset + length; 165: 166: // First we set the parent of the removed children to null. 167: for (int i = offset; i < endOffset; ++i) 168: children[i].setParent(null); 169: 170: View[] newChildren = new View[children.length - length + views.length]; 171: System.arraycopy(children, 0, newChildren, 0, offset); 172: System.arraycopy(views, 0, newChildren, offset, views.length); 173: System.arraycopy(children, offset + length, newChildren, 174: offset + views.length, 175: children.length - (offset + length)); 176: children = newChildren; 177: 178: // Finally we set the parent of the added children to this. 179: for (int i = 0; i < views.length; ++i) 180: views[i].setParent(this); 181: } 182: 183: /** 184: * Returns the allocation for the specified child <code>View</code>. 185: * 186: * @param index the index of the child view 187: * @param a the allocation for this view 188: * 189: * @return the allocation for the specified child <code>View</code> 190: */ 191: public Shape getChildAllocation(int index, Shape a) 192: { 193: Rectangle r = getInsideAllocation(a); 194: childAllocation(index, r); 195: return r; 196: } 197: 198: /** 199: * Maps a position in the document into the coordinate space of the View. 200: * The output rectangle usually reflects the font height but has a width 201: * of zero. 202: * 203: * @param pos the position of the character in the model 204: * @param a the area that is occupied by the view 205: * @param bias either {@link Position.Bias#Forward} or 206: * {@link Position.Bias#Backward} depending on the preferred 207: * direction bias. If <code>null</code> this defaults to 208: * <code>Position.Bias.Forward</code> 209: * 210: * @return a rectangle that gives the location of the document position 211: * inside the view coordinate space 212: * 213: * @throws BadLocationException if <code>pos</code> is invalid 214: * @throws IllegalArgumentException if b is not one of the above listed 215: * valid values 216: */ 217: public Shape modelToView(int pos, Shape a, Position.Bias bias) 218: throws BadLocationException 219: { 220: int childIndex = getViewIndex(pos, bias); 221: if (childIndex != -1) 222: { 223: View child = getView(childIndex); 224: Rectangle r = a.getBounds(); 225: childAllocation(childIndex, r); 226: Shape result = child.modelToView(pos, r, bias); 227: if (result == null) 228: throw new AssertionError("" + child.getClass().getName() 229: + ".modelToView() must not return null"); 230: return result; 231: } 232: else 233: throw new BadLocationException("No child view for the specified location", 234: pos); 235: } 236: 237: /** 238: * Maps a region in the document into the coordinate space of the View. 239: * 240: * @param p1 the beginning position inside the document 241: * @param b1 the direction bias for the beginning position 242: * @param p2 the end position inside the document 243: * @param b2 the direction bias for the end position 244: * @param a the area that is occupied by the view 245: * 246: * @return a rectangle that gives the span of the document region 247: * inside the view coordinate space 248: * 249: * @throws BadLocationException if <code>p1</code> or <code>p2</code> are 250: * invalid 251: * @throws IllegalArgumentException if b1 or b2 is not one of the above 252: * listed valid values 253: */ 254: public Shape modelToView(int p1, Position.Bias b1, 255: int p2, Position.Bias b2, Shape a) 256: throws BadLocationException 257: { 258: // TODO: This is most likely not 100% ok, figure out what else is to 259: // do here. 260: return super.modelToView(p1, b1, p2, b2, a); 261: } 262: 263: /** 264: * Maps coordinates from the <code>View</code>'s space into a position 265: * in the document model. 266: * 267: * @param x the x coordinate in the view space, x >= 0 268: * @param y the y coordinate in the view space, y >= 0 269: * @param a the allocation of this <code>View</code> 270: * @param b the bias to use 271: * 272: * @return the position in the document that corresponds to the screen 273: * coordinates <code>x, y</code> >= 0 274: */ 275: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 276: { 277: if (x >= 0 && y >= 0) 278: { 279: Rectangle r = getInsideAllocation(a); 280: View view = getViewAtPoint((int) x, (int) y, r); 281: return view.viewToModel(x, y, a, b); 282: } 283: return 0; 284: } 285: 286: /** 287: * Returns the next model location that is visible in eiter north / south 288: * direction or east / west direction. This is used to determine the placement 289: * of the caret when navigating around the document with the arrow keys. This 290: * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom} 291: * and {@link #getNextEastWestVisualPositionFrom}. 292: * 293: * @param pos 294: * the model position to start search from 295: * @param b 296: * the bias for <code>pos</code> 297: * @param a 298: * the allocated region for this view 299: * @param direction 300: * the direction from the current position, can be one of the 301: * following: 302: * <ul> 303: * <li>{@link SwingConstants#WEST}</li> 304: * <li>{@link SwingConstants#EAST}</li> 305: * <li>{@link SwingConstants#NORTH}</li> 306: * <li>{@link SwingConstants#SOUTH}</li> 307: * </ul> 308: * @param biasRet 309: * the bias of the return value gets stored here 310: * @return the position inside the model that represents the next visual 311: * location 312: * @throws BadLocationException 313: * if <code>pos</code> is not a valid location inside the document 314: * model 315: * @throws IllegalArgumentException 316: * if <code>direction</code> is invalid 317: */ 318: public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 319: int direction, Position.Bias[] biasRet) 320: throws BadLocationException 321: { 322: int retVal = -1; 323: switch (direction) 324: { 325: case SwingConstants.WEST: 326: case SwingConstants.EAST: 327: retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction, 328: biasRet); 329: break; 330: case SwingConstants.NORTH: 331: case SwingConstants.SOUTH: 332: retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction, 333: biasRet); 334: break; 335: default: 336: throw new IllegalArgumentException("Illegal value for direction."); 337: } 338: return retVal; 339: } 340: 341: /** 342: * Returns the index of the child view that represents the specified 343: * model location. 344: * 345: * @param pos the model location for which to determine the child view index 346: * @param b the bias to be applied to <code>pos</code> 347: * 348: * @return the index of the child view that represents the specified 349: * model location 350: */ 351: public int getViewIndex(int pos, Position.Bias b) 352: { 353: // FIXME: Handle bias somehow. 354: return getViewIndexAtPosition(pos); 355: } 356: 357: /** 358: * Returns <code>true</code> if the specified point lies before the 359: * given <code>Rectangle</code>, <code>false</code> otherwise. 360: * 361: * "Before" is typically defined as being to the left or above. 362: * 363: * @param x the X coordinate of the point 364: * @param y the Y coordinate of the point 365: * @param r the rectangle to test the point against 366: * 367: * @return <code>true</code> if the specified point lies before the 368: * given <code>Rectangle</code>, <code>false</code> otherwise 369: */ 370: protected abstract boolean isBefore(int x, int y, Rectangle r); 371: 372: /** 373: * Returns <code>true</code> if the specified point lies after the 374: * given <code>Rectangle</code>, <code>false</code> otherwise. 375: * 376: * "After" is typically defined as being to the right or below. 377: * 378: * @param x the X coordinate of the point 379: * @param y the Y coordinate of the point 380: * @param r the rectangle to test the point against 381: * 382: * @return <code>true</code> if the specified point lies after the 383: * given <code>Rectangle</code>, <code>false</code> otherwise 384: */ 385: protected abstract boolean isAfter(int x, int y, Rectangle r); 386: 387: /** 388: * Returns the child <code>View</code> at the specified location. 389: * 390: * @param x the X coordinate 391: * @param y the Y coordinate 392: * @param r the inner allocation of this <code>BoxView</code> on entry, 393: * the allocation of the found child on exit 394: * 395: * @return the child <code>View</code> at the specified location 396: */ 397: protected abstract View getViewAtPoint(int x, int y, Rectangle r); 398: 399: /** 400: * Computes the allocation for a child <code>View</code>. The parameter 401: * <code>a</code> stores the allocation of this <code>CompositeView</code> 402: * and is then adjusted to hold the allocation of the child view. 403: * 404: * @param index the index of the child <code>View</code> 405: * @param a the allocation of this <code>CompositeView</code> before the 406: * call, the allocation of the child on exit 407: */ 408: protected abstract void childAllocation(int index, Rectangle a); 409: 410: /** 411: * Returns the child <code>View</code> that contains the given model 412: * position. The given <code>Rectangle</code> gives the parent's allocation 413: * and is changed to the child's allocation on exit. 414: * 415: * @param pos the model position to query the child <code>View</code> for 416: * @param a the parent allocation on entry and the child allocation on exit 417: * 418: * @return the child view at the given model position 419: */ 420: protected View getViewAtPosition(int pos, Rectangle a) 421: { 422: int i = getViewIndexAtPosition(pos); 423: View view = children[i]; 424: childAllocation(i, a); 425: return view; 426: } 427: 428: /** 429: * Returns the index of the child <code>View</code> for the given model 430: * position. 431: * 432: * @param pos the model position for whicht the child <code>View</code> is 433: * queried 434: * 435: * @return the index of the child <code>View</code> for the given model 436: * position 437: */ 438: protected int getViewIndexAtPosition(int pos) 439: { 440: int index = -1; 441: for (int i = 0; i < children.length; i++) 442: { 443: if (children[i].getStartOffset() <= pos 444: && children[i].getEndOffset() > pos) 445: { 446: index = i; 447: break; 448: } 449: } 450: return index; 451: } 452: 453: /** 454: * Returns the allocation that is given to this <code>CompositeView</code> 455: * minus this <code>CompositeView</code>'s insets. 456: * 457: * Also this translates from an immutable allocation to a mutable allocation 458: * that is typically reused and further narrowed, like in 459: * {@link #childAllocation}. 460: * 461: * @param a the allocation given to this <code>CompositeView</code> 462: * 463: * @return the allocation that is given to this <code>CompositeView</code> 464: * minus this <code>CompositeView</code>'s insets or 465: * <code>null</code> if a was <code>null</code> 466: */ 467: protected Rectangle getInsideAllocation(Shape a) 468: { 469: if (a == null) 470: return null; 471: 472: Rectangle alloc = a.getBounds(); 473: // Initialize the inside allocation rectangle. This is done inside 474: // a synchronized block in order to avoid multiple threads creating 475: // this instance simultanously. 476: Rectangle inside; 477: synchronized(this) 478: { 479: inside = insideAllocation; 480: if (inside == null) 481: { 482: inside = new Rectangle(); 483: insideAllocation = inside; 484: } 485: } 486: inside.x = alloc.x + insets.left; 487: inside.y = alloc.y + insets.top; 488: inside.width = alloc.width - insets.left - insets.right; 489: inside.height = alloc.height - insets.top - insets.bottom; 490: return inside; 491: } 492: 493: /** 494: * Sets the insets defined by attributes in <code>attributes</code>. This 495: * queries the attribute keys {@link StyleConstants#SpaceAbove}, 496: * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and 497: * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to 498: * actually set the insets on this <code>CompositeView</code>. 499: * 500: * @param attributes the attributes from which to query the insets 501: */ 502: protected void setParagraphInsets(AttributeSet attributes) 503: { 504: Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); 505: short left = 0; 506: if (l != null) 507: left = l.shortValue(); 508: Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); 509: short right = 0; 510: if (r != null) 511: right = r.shortValue(); 512: Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); 513: short top = 0; 514: if (t != null) 515: top = t.shortValue(); 516: Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); 517: short bottom = 0; 518: if (b != null) 519: bottom = b.shortValue(); 520: setInsets(top, left, bottom, right); 521: } 522: 523: /** 524: * Sets the insets of this <code>CompositeView</code>. 525: * 526: * @param top the top inset 527: * @param left the left inset 528: * @param bottom the bottom inset 529: * @param right the right inset 530: */ 531: protected void setInsets(short top, short left, short bottom, short right) 532: { 533: insets.top = top; 534: insets.left = left; 535: insets.bottom = bottom; 536: insets.right = right; 537: } 538: 539: /** 540: * Returns the left inset of this <code>CompositeView</code>. 541: * 542: * @return the left inset of this <code>CompositeView</code> 543: */ 544: protected short getLeftInset() 545: { 546: return (short) insets.left; 547: } 548: 549: /** 550: * Returns the right inset of this <code>CompositeView</code>. 551: * 552: * @return the right inset of this <code>CompositeView</code> 553: */ 554: protected short getRightInset() 555: { 556: return (short) insets.right; 557: } 558: 559: /** 560: * Returns the top inset of this <code>CompositeView</code>. 561: * 562: * @return the top inset of this <code>CompositeView</code> 563: */ 564: protected short getTopInset() 565: { 566: return (short) insets.top; 567: } 568: 569: /** 570: * Returns the bottom inset of this <code>CompositeView</code>. 571: * 572: * @return the bottom inset of this <code>CompositeView</code> 573: */ 574: protected short getBottomInset() 575: { 576: return (short) insets.bottom; 577: } 578: 579: /** 580: * Returns the next model location that is visible in north or south 581: * direction. 582: * This is used to determine the 583: * placement of the caret when navigating around the document with 584: * the arrow keys. 585: * 586: * @param pos the model position to start search from 587: * @param b the bias for <code>pos</code> 588: * @param a the allocated region for this view 589: * @param direction the direction from the current position, can be one of 590: * the following: 591: * <ul> 592: * <li>{@link SwingConstants#NORTH}</li> 593: * <li>{@link SwingConstants#SOUTH}</li> 594: * </ul> 595: * @param biasRet the bias of the return value gets stored here 596: * 597: * @return the position inside the model that represents the next visual 598: * location 599: * 600: * @throws BadLocationException if <code>pos</code> is not a valid location 601: * inside the document model 602: * @throws IllegalArgumentException if <code>direction</code> is invalid 603: */ 604: protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 605: Shape a, int direction, 606: Position.Bias[] biasRet) 607: throws BadLocationException 608: { 609: // FIXME: Implement this correctly. 610: return pos; 611: } 612: 613: /** 614: * Returns the next model location that is visible in east or west 615: * direction. 616: * This is used to determine the 617: * placement of the caret when navigating around the document with 618: * the arrow keys. 619: * 620: * @param pos the model position to start search from 621: * @param b the bias for <code>pos</code> 622: * @param a the allocated region for this view 623: * @param direction the direction from the current position, can be one of 624: * the following: 625: * <ul> 626: * <li>{@link SwingConstants#EAST}</li> 627: * <li>{@link SwingConstants#WEST}</li> 628: * </ul> 629: * @param biasRet the bias of the return value gets stored here 630: * 631: * @return the position inside the model that represents the next visual 632: * location 633: * 634: * @throws BadLocationException if <code>pos</code> is not a valid location 635: * inside the document model 636: * @throws IllegalArgumentException if <code>direction</code> is invalid 637: */ 638: protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, 639: Shape a, int direction, 640: Position.Bias[] biasRet) 641: throws BadLocationException 642: { 643: // FIXME: Implement this correctly. 644: return pos; 645: } 646: 647: /** 648: * Determines if the next view in horinzontal direction is located to 649: * the east or west of the view at position <code>pos</code>. Usually 650: * the <code>View</code>s are laid out from the east to the west, so 651: * we unconditionally return <code>false</code> here. Subclasses that 652: * support bidirectional text may wish to override this method. 653: * 654: * @param pos the position in the document 655: * @param bias the bias to be applied to <code>pos</code> 656: * 657: * @return <code>true</code> if the next <code>View</code> is located 658: * to the EAST, <code>false</code> otherwise 659: */ 660: protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias) 661: { 662: return false; 663: } 664: }
GNU Classpath (0.20) |