GNU Classpath (0.18) | ||
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}. 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: Shape result = child.modelToView(pos, a, bias); 225: if (result == null) 226: throw new AssertionError("" + child.getClass().getName() 227: + ".modelToView() must not return null"); 228: return result; 229: } 230: else 231: { 232: // FIXME: Handle the case when we have no child view for the given 233: // position. 234: throw new AssertionError("No child views found where child views are " 235: + "expected. pos = " + pos + ", bias = " 236: + bias); 237: } 238: } 239: 240: /** 241: * Maps a region in the document into the coordinate space of the View. 242: * 243: * @param p1 the beginning position inside the document 244: * @param b1 the direction bias for the beginning position 245: * @param p2 the end position inside the document 246: * @param b2 the direction bias for the end position 247: * @param a the area that is occupied by the view 248: * 249: * @return a rectangle that gives the span of the document region 250: * inside the view coordinate space 251: * 252: * @throws BadLocationException if <code>p1</code> or <code>p2</code> are 253: * invalid 254: * @throws IllegalArgumentException if b1 or b2 is not one of the above 255: * listed valid values 256: */ 257: public Shape modelToView(int p1, Position.Bias b1, 258: int p2, Position.Bias b2, Shape a) 259: throws BadLocationException 260: { 261: // TODO: This is most likely not 100% ok, figure out what else is to 262: // do here. 263: return super.modelToView(p1, b1, p2, b2, a); 264: } 265: 266: /** 267: * Maps coordinates from the <code>View</code>'s space into a position 268: * in the document model. 269: * 270: * @param x the x coordinate in the view space 271: * @param y the y coordinate in the view space 272: * @param a the allocation of this <code>View</code> 273: * @param b the bias to use 274: * 275: * @return the position in the document that corresponds to the screen 276: * coordinates <code>x, y</code> 277: */ 278: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 279: { 280: Rectangle r = getInsideAllocation(a); 281: View view = getViewAtPoint((int) x, (int) y, r); 282: return view.viewToModel(x, y, a, b); 283: } 284: 285: /** 286: * Returns the next model location that is visible in eiter north / south 287: * direction or east / west direction. This is used to determine the 288: * placement of the caret when navigating around the document with 289: * the arrow keys. 290: * 291: * This is a convenience method for 292: * {@link #getNextNorthSouthVisualPositionFrom} and 293: * {@link #getNextEastWestVisualPositionFrom}. 294: * 295: * @param pos the model position to start search from 296: * @param b the bias for <code>pos</code> 297: * @param a the allocated region for this view 298: * @param direction the direction from the current position, can be one of 299: * the following: 300: * <ul> 301: * <li>{@link SwingConstants#WEST}</li> 302: * <li>{@link SwingConstants#EAST}</li> 303: * <li>{@link SwingConstants#NORTH}</li> 304: * <li>{@link SwingConstants#SOUTH}</li> 305: * </ul> 306: * @param biasRet the bias of the return value gets stored here 307: * 308: * @return the position inside the model that represents the next visual 309: * location 310: * 311: * @throws BadLocationException if <code>pos</code> is not a valid location 312: * inside the document model 313: * @throws IllegalArgumentException if <code>direction</code> is invalid 314: */ 315: public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 316: int direction, Position.Bias[] biasRet) 317: { 318: int retVal = -1; 319: switch (direction) 320: { 321: case SwingConstants.WEST: 322: case SwingConstants.EAST: 323: retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction, 324: biasRet); 325: break; 326: case SwingConstants.NORTH: 327: case SwingConstants.SOUTH: 328: retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction, 329: biasRet); 330: break; 331: default: 332: throw new IllegalArgumentException("Illegal value for direction."); 333: } 334: return retVal; 335: } 336: 337: /** 338: * Returns the index of the child view that represents the specified 339: * model location. 340: * 341: * @param pos the model location for which to determine the child view index 342: * @param b the bias to be applied to <code>pos</code> 343: * 344: * @return the index of the child view that represents the specified 345: * model location 346: */ 347: public int getViewIndex(int pos, Position.Bias b) 348: { 349: // FIXME: Handle bias somehow. 350: return getViewIndexAtPosition(pos); 351: } 352: 353: /** 354: * Returns <code>true</code> if the specified point lies before the 355: * given <code>Rectangle</code>, <code>false</code> otherwise. 356: * 357: * "Before" is typically defined as being to the left or above. 358: * 359: * @param x the X coordinate of the point 360: * @param y the Y coordinate of the point 361: * @param r the rectangle to test the point against 362: * 363: * @return <code>true</code> if the specified point lies before the 364: * given <code>Rectangle</code>, <code>false</code> otherwise 365: */ 366: protected abstract boolean isBefore(int x, int y, Rectangle r); 367: 368: /** 369: * Returns <code>true</code> if the specified point lies after the 370: * given <code>Rectangle</code>, <code>false</code> otherwise. 371: * 372: * "After" is typically defined as being to the right or below. 373: * 374: * @param x the X coordinate of the point 375: * @param y the Y coordinate of the point 376: * @param r the rectangle to test the point against 377: * 378: * @return <code>true</code> if the specified point lies after the 379: * given <code>Rectangle</code>, <code>false</code> otherwise 380: */ 381: protected abstract boolean isAfter(int x, int y, Rectangle r); 382: 383: /** 384: * Returns the child <code>View</code> at the specified location. 385: * 386: * @param x the X coordinate 387: * @param y the Y coordinate 388: * @param r the inner allocation of this <code>BoxView</code> on entry, 389: * the allocation of the found child on exit 390: * 391: * @return the child <code>View</code> at the specified location 392: */ 393: protected abstract View getViewAtPoint(int x, int y, Rectangle r); 394: 395: /** 396: * Computes the allocation for a child <code>View</code>. The parameter 397: * <code>a</code> stores the allocation of this <code>CompositeView</code> 398: * and is then adjusted to hold the allocation of the child view. 399: * 400: * @param index the index of the child <code>View</code> 401: * @param a the allocation of this <code>CompositeView</code> before the 402: * call, the allocation of the child on exit 403: */ 404: protected abstract void childAllocation(int index, Rectangle a); 405: 406: /** 407: * Returns the child <code>View</code> that contains the given model 408: * position. The given <code>Rectangle</code> gives the parent's allocation 409: * and is changed to the child's allocation on exit. 410: * 411: * @param pos the model position to query the child <code>View</code> for 412: * @param a the parent allocation on entry and the child allocation on exit 413: * 414: * @return the child view at the given model position 415: */ 416: protected View getViewAtPosition(int pos, Rectangle a) 417: { 418: int i = getViewIndexAtPosition(pos); 419: View view = children[i]; 420: childAllocation(i, a); 421: return view; 422: } 423: 424: /** 425: * Returns the index of the child <code>View</code> for the given model 426: * position. 427: * 428: * @param pos the model position for whicht the child <code>View</code> is 429: * queried 430: * 431: * @return the index of the child <code>View</code> for the given model 432: * position 433: */ 434: protected int getViewIndexAtPosition(int pos) 435: { 436: // We have one child view allocated for each child element in 437: // loadChildren(), so this should work. 438: Element el = getElement(); 439: int index = el.getElementIndex(pos); 440: return index; 441: } 442: 443: /** 444: * Returns the allocation that is given to this <code>CompositeView</code> 445: * minus this <code>CompositeView</code>'s insets. 446: * 447: * Also this translates from an immutable allocation to a mutable allocation 448: * that is typically reused and further narrowed, like in 449: * {@link #childAllocation}. 450: * 451: * @param a the allocation given to this <code>CompositeView</code> 452: * 453: * @return the allocation that is given to this <code>CompositeView</code> 454: * minus this <code>CompositeView</code>'s insets or 455: * <code>null</code> if a was <code>null</code> 456: */ 457: protected Rectangle getInsideAllocation(Shape a) 458: { 459: if (a == null) 460: return null; 461: 462: Rectangle alloc = a.getBounds(); 463: // Initialize the inside allocation rectangle. This is done inside 464: // a synchronized block in order to avoid multiple threads creating 465: // this instance simultanously. 466: Rectangle inside; 467: synchronized(this) 468: { 469: inside = insideAllocation; 470: if (inside == null) 471: { 472: inside = new Rectangle(); 473: insideAllocation = inside; 474: } 475: } 476: inside.x = alloc.x - insets.left; 477: inside.y = alloc.y - insets.top; 478: inside.width = alloc.width - insets.left - insets.right; 479: inside.height = alloc.height - insets.top - insets.bottom; 480: return inside; 481: } 482: 483: /** 484: * Sets the insets defined by attributes in <code>attributes</code>. This 485: * queries the attribute keys {@link StyleConstants#SpaceAbove}, 486: * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and 487: * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to 488: * actually set the insets on this <code>CompositeView</code>. 489: * 490: * @param attributes the attributes from which to query the insets 491: */ 492: protected void setParagraphInsets(AttributeSet attributes) 493: { 494: Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); 495: short left = 0; 496: if (l != null) 497: left = l.shortValue(); 498: Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); 499: short right = 0; 500: if (r != null) 501: right = r.shortValue(); 502: Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); 503: short top = 0; 504: if (t != null) 505: top = t.shortValue(); 506: Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); 507: short bottom = 0; 508: if (b != null) 509: bottom = b.shortValue(); 510: setInsets(top, left, bottom, right); 511: } 512: 513: /** 514: * Sets the insets of this <code>CompositeView</code>. 515: * 516: * @param top the top inset 517: * @param left the left inset 518: * @param bottom the bottom inset 519: * @param right the right inset 520: */ 521: protected void setInsets(short top, short left, short bottom, short right) 522: { 523: insets.top = top; 524: insets.left = left; 525: insets.bottom = bottom; 526: insets.right = right; 527: } 528: 529: /** 530: * Returns the left inset of this <code>CompositeView</code>. 531: * 532: * @return the left inset of this <code>CompositeView</code> 533: */ 534: protected short getLeftInset() 535: { 536: return (short) insets.left; 537: } 538: 539: /** 540: * Returns the right inset of this <code>CompositeView</code>. 541: * 542: * @return the right inset of this <code>CompositeView</code> 543: */ 544: protected short getRightInset() 545: { 546: return (short) insets.right; 547: } 548: 549: /** 550: * Returns the top inset of this <code>CompositeView</code>. 551: * 552: * @return the top inset of this <code>CompositeView</code> 553: */ 554: protected short getTopInset() 555: { 556: return (short) insets.top; 557: } 558: 559: /** 560: * Returns the bottom inset of this <code>CompositeView</code>. 561: * 562: * @return the bottom inset of this <code>CompositeView</code> 563: */ 564: protected short getBottomInset() 565: { 566: return (short) insets.bottom; 567: } 568: 569: /** 570: * Returns the next model location that is visible in north or south 571: * direction. 572: * This is used to determine the 573: * placement of the caret when navigating around the document with 574: * the arrow keys. 575: * 576: * @param pos the model position to start search from 577: * @param b the bias for <code>pos</code> 578: * @param a the allocated region for this view 579: * @param direction the direction from the current position, can be one of 580: * the following: 581: * <ul> 582: * <li>{@link SwingConstants#NORTH}</li> 583: * <li>{@link SwingConstants#SOUTH}</li> 584: * </ul> 585: * @param biasRet the bias of the return value gets stored here 586: * 587: * @return the position inside the model that represents the next visual 588: * location 589: * 590: * @throws BadLocationException if <code>pos</code> is not a valid location 591: * inside the document model 592: * @throws IllegalArgumentException if <code>direction</code> is invalid 593: */ 594: protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 595: Shape a, int direction, 596: Position.Bias[] biasRet) 597: { 598: // FIXME: Implement this correctly. 599: return pos; 600: } 601: 602: /** 603: * Returns the next model location that is visible in east or west 604: * direction. 605: * This is used to determine the 606: * placement of the caret when navigating around the document with 607: * the arrow keys. 608: * 609: * @param pos the model position to start search from 610: * @param b the bias for <code>pos</code> 611: * @param a the allocated region for this view 612: * @param direction the direction from the current position, can be one of 613: * the following: 614: * <ul> 615: * <li>{@link SwingConstants#EAST}</li> 616: * <li>{@link SwingConstants#WEST}</li> 617: * </ul> 618: * @param biasRet the bias of the return value gets stored here 619: * 620: * @return the position inside the model that represents the next visual 621: * location 622: * 623: * @throws BadLocationException if <code>pos</code> is not a valid location 624: * inside the document model 625: * @throws IllegalArgumentException if <code>direction</code> is invalid 626: */ 627: protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, 628: Shape a, int direction, 629: Position.Bias[] biasRet) 630: { 631: // FIXME: Implement this correctly. 632: return pos; 633: } 634: 635: /** 636: * Determines if the next view in horinzontal direction is located to 637: * the east or west of the view at position <code>pos</code>. Usually 638: * the <code>View</code>s are laid out from the east to the west, so 639: * we unconditionally return <code>false</code> here. Subclasses that 640: * support bidirectional text may wish to override this method. 641: * 642: * @param pos the position in the document 643: * @param bias the bias to be applied to <code>pos</code> 644: * 645: * @return <code>true</code> if the next <code>View</code> is located 646: * to the EAST, <code>false</code> otherwise 647: */ 648: protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias) 649: { 650: return false; 651: } 652: }
GNU Classpath (0.18) |