GNU Classpath (0.19) | ||
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: 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: throws BadLocationException 318: { 319: int retVal = -1; 320: switch (direction) 321: { 322: case SwingConstants.WEST: 323: case SwingConstants.EAST: 324: retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction, 325: biasRet); 326: break; 327: case SwingConstants.NORTH: 328: case SwingConstants.SOUTH: 329: retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction, 330: biasRet); 331: break; 332: default: 333: throw new IllegalArgumentException("Illegal value for direction."); 334: } 335: return retVal; 336: } 337: 338: /** 339: * Returns the index of the child view that represents the specified 340: * model location. 341: * 342: * @param pos the model location for which to determine the child view index 343: * @param b the bias to be applied to <code>pos</code> 344: * 345: * @return the index of the child view that represents the specified 346: * model location 347: */ 348: public int getViewIndex(int pos, Position.Bias b) 349: { 350: // FIXME: Handle bias somehow. 351: return getViewIndexAtPosition(pos); 352: } 353: 354: /** 355: * Returns <code>true</code> if the specified point lies before the 356: * given <code>Rectangle</code>, <code>false</code> otherwise. 357: * 358: * "Before" is typically defined as being to the left or above. 359: * 360: * @param x the X coordinate of the point 361: * @param y the Y coordinate of the point 362: * @param r the rectangle to test the point against 363: * 364: * @return <code>true</code> if the specified point lies before the 365: * given <code>Rectangle</code>, <code>false</code> otherwise 366: */ 367: protected abstract boolean isBefore(int x, int y, Rectangle r); 368: 369: /** 370: * Returns <code>true</code> if the specified point lies after the 371: * given <code>Rectangle</code>, <code>false</code> otherwise. 372: * 373: * "After" is typically defined as being to the right or below. 374: * 375: * @param x the X coordinate of the point 376: * @param y the Y coordinate of the point 377: * @param r the rectangle to test the point against 378: * 379: * @return <code>true</code> if the specified point lies after the 380: * given <code>Rectangle</code>, <code>false</code> otherwise 381: */ 382: protected abstract boolean isAfter(int x, int y, Rectangle r); 383: 384: /** 385: * Returns the child <code>View</code> at the specified location. 386: * 387: * @param x the X coordinate 388: * @param y the Y coordinate 389: * @param r the inner allocation of this <code>BoxView</code> on entry, 390: * the allocation of the found child on exit 391: * 392: * @return the child <code>View</code> at the specified location 393: */ 394: protected abstract View getViewAtPoint(int x, int y, Rectangle r); 395: 396: /** 397: * Computes the allocation for a child <code>View</code>. The parameter 398: * <code>a</code> stores the allocation of this <code>CompositeView</code> 399: * and is then adjusted to hold the allocation of the child view. 400: * 401: * @param index the index of the child <code>View</code> 402: * @param a the allocation of this <code>CompositeView</code> before the 403: * call, the allocation of the child on exit 404: */ 405: protected abstract void childAllocation(int index, Rectangle a); 406: 407: /** 408: * Returns the child <code>View</code> that contains the given model 409: * position. The given <code>Rectangle</code> gives the parent's allocation 410: * and is changed to the child's allocation on exit. 411: * 412: * @param pos the model position to query the child <code>View</code> for 413: * @param a the parent allocation on entry and the child allocation on exit 414: * 415: * @return the child view at the given model position 416: */ 417: protected View getViewAtPosition(int pos, Rectangle a) 418: { 419: int i = getViewIndexAtPosition(pos); 420: View view = children[i]; 421: childAllocation(i, a); 422: return view; 423: } 424: 425: /** 426: * Returns the index of the child <code>View</code> for the given model 427: * position. 428: * 429: * @param pos the model position for whicht the child <code>View</code> is 430: * queried 431: * 432: * @return the index of the child <code>View</code> for the given model 433: * position 434: */ 435: protected int getViewIndexAtPosition(int pos) 436: { 437: int index = -1; 438: for (int i = 0; i < children.length; i++) 439: { 440: if (children[i].getStartOffset() <= pos 441: && children[i].getEndOffset() > pos) 442: { 443: index = i; 444: break; 445: } 446: } 447: return index; 448: } 449: 450: /** 451: * Returns the allocation that is given to this <code>CompositeView</code> 452: * minus this <code>CompositeView</code>'s insets. 453: * 454: * Also this translates from an immutable allocation to a mutable allocation 455: * that is typically reused and further narrowed, like in 456: * {@link #childAllocation}. 457: * 458: * @param a the allocation given to this <code>CompositeView</code> 459: * 460: * @return the allocation that is given to this <code>CompositeView</code> 461: * minus this <code>CompositeView</code>'s insets or 462: * <code>null</code> if a was <code>null</code> 463: */ 464: protected Rectangle getInsideAllocation(Shape a) 465: { 466: if (a == null) 467: return null; 468: 469: Rectangle alloc = a.getBounds(); 470: // Initialize the inside allocation rectangle. This is done inside 471: // a synchronized block in order to avoid multiple threads creating 472: // this instance simultanously. 473: Rectangle inside; 474: synchronized(this) 475: { 476: inside = insideAllocation; 477: if (inside == null) 478: { 479: inside = new Rectangle(); 480: insideAllocation = inside; 481: } 482: } 483: inside.x = alloc.x + insets.left; 484: inside.y = alloc.y + insets.top; 485: inside.width = alloc.width - insets.left - insets.right; 486: inside.height = alloc.height - insets.top - insets.bottom; 487: return inside; 488: } 489: 490: /** 491: * Sets the insets defined by attributes in <code>attributes</code>. This 492: * queries the attribute keys {@link StyleConstants#SpaceAbove}, 493: * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and 494: * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to 495: * actually set the insets on this <code>CompositeView</code>. 496: * 497: * @param attributes the attributes from which to query the insets 498: */ 499: protected void setParagraphInsets(AttributeSet attributes) 500: { 501: Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); 502: short left = 0; 503: if (l != null) 504: left = l.shortValue(); 505: Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); 506: short right = 0; 507: if (r != null) 508: right = r.shortValue(); 509: Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); 510: short top = 0; 511: if (t != null) 512: top = t.shortValue(); 513: Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); 514: short bottom = 0; 515: if (b != null) 516: bottom = b.shortValue(); 517: setInsets(top, left, bottom, right); 518: } 519: 520: /** 521: * Sets the insets of this <code>CompositeView</code>. 522: * 523: * @param top the top inset 524: * @param left the left inset 525: * @param bottom the bottom inset 526: * @param right the right inset 527: */ 528: protected void setInsets(short top, short left, short bottom, short right) 529: { 530: insets.top = top; 531: insets.left = left; 532: insets.bottom = bottom; 533: insets.right = right; 534: } 535: 536: /** 537: * Returns the left inset of this <code>CompositeView</code>. 538: * 539: * @return the left inset of this <code>CompositeView</code> 540: */ 541: protected short getLeftInset() 542: { 543: return (short) insets.left; 544: } 545: 546: /** 547: * Returns the right inset of this <code>CompositeView</code>. 548: * 549: * @return the right inset of this <code>CompositeView</code> 550: */ 551: protected short getRightInset() 552: { 553: return (short) insets.right; 554: } 555: 556: /** 557: * Returns the top inset of this <code>CompositeView</code>. 558: * 559: * @return the top inset of this <code>CompositeView</code> 560: */ 561: protected short getTopInset() 562: { 563: return (short) insets.top; 564: } 565: 566: /** 567: * Returns the bottom inset of this <code>CompositeView</code>. 568: * 569: * @return the bottom inset of this <code>CompositeView</code> 570: */ 571: protected short getBottomInset() 572: { 573: return (short) insets.bottom; 574: } 575: 576: /** 577: * Returns the next model location that is visible in north or south 578: * direction. 579: * This is used to determine the 580: * placement of the caret when navigating around the document with 581: * the arrow keys. 582: * 583: * @param pos the model position to start search from 584: * @param b the bias for <code>pos</code> 585: * @param a the allocated region for this view 586: * @param direction the direction from the current position, can be one of 587: * the following: 588: * <ul> 589: * <li>{@link SwingConstants#NORTH}</li> 590: * <li>{@link SwingConstants#SOUTH}</li> 591: * </ul> 592: * @param biasRet the bias of the return value gets stored here 593: * 594: * @return the position inside the model that represents the next visual 595: * location 596: * 597: * @throws BadLocationException if <code>pos</code> is not a valid location 598: * inside the document model 599: * @throws IllegalArgumentException if <code>direction</code> is invalid 600: */ 601: protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 602: Shape a, int direction, 603: Position.Bias[] biasRet) 604: throws BadLocationException 605: { 606: // FIXME: Implement this correctly. 607: return pos; 608: } 609: 610: /** 611: * Returns the next model location that is visible in east or west 612: * direction. 613: * This is used to determine the 614: * placement of the caret when navigating around the document with 615: * the arrow keys. 616: * 617: * @param pos the model position to start search from 618: * @param b the bias for <code>pos</code> 619: * @param a the allocated region for this view 620: * @param direction the direction from the current position, can be one of 621: * the following: 622: * <ul> 623: * <li>{@link SwingConstants#EAST}</li> 624: * <li>{@link SwingConstants#WEST}</li> 625: * </ul> 626: * @param biasRet the bias of the return value gets stored here 627: * 628: * @return the position inside the model that represents the next visual 629: * location 630: * 631: * @throws BadLocationException if <code>pos</code> is not a valid location 632: * inside the document model 633: * @throws IllegalArgumentException if <code>direction</code> is invalid 634: */ 635: protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, 636: Shape a, int direction, 637: Position.Bias[] biasRet) 638: throws BadLocationException 639: { 640: // FIXME: Implement this correctly. 641: return pos; 642: } 643: 644: /** 645: * Determines if the next view in horinzontal direction is located to 646: * the east or west of the view at position <code>pos</code>. Usually 647: * the <code>View</code>s are laid out from the east to the west, so 648: * we unconditionally return <code>false</code> here. Subclasses that 649: * support bidirectional text may wish to override this method. 650: * 651: * @param pos the position in the document 652: * @param bias the bias to be applied to <code>pos</code> 653: * 654: * @return <code>true</code> if the next <code>View</code> is located 655: * to the EAST, <code>false</code> otherwise 656: */ 657: protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias) 658: { 659: return false; 660: } 661: }
GNU Classpath (0.19) |