Frames | No Frames |
1: /* FlowView.java -- A composite View 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.Container; 42: import java.awt.Graphics; 43: import java.awt.Rectangle; 44: import java.awt.Shape; 45: import java.util.Iterator; 46: import java.util.Vector; 47: 48: import javax.swing.SwingConstants; 49: import javax.swing.event.DocumentEvent; 50: 51: /** 52: * A <code>View</code> that can flows it's children into it's layout space. 53: * 54: * The <code>FlowView</code> manages a set of logical views (that are 55: * the children of the {@link #layoutPool} field). These are translated 56: * at layout time into a set of physical views. These are the views that 57: * are managed as the real child views. Each of these child views represents 58: * a row and are laid out within a box using the superclasses behaviour. 59: * The concrete implementation of the rows must be provided by subclasses. 60: * 61: * @author Roman Kennke (roman@kennke.org) 62: */ 63: public abstract class FlowView extends BoxView 64: { 65: /** 66: * A strategy for translating the logical views of a <code>FlowView</code> 67: * into the real views. 68: */ 69: public static class FlowStrategy 70: { 71: /** 72: * Creates a new instance of <code>FlowStragegy</code>. 73: */ 74: public FlowStrategy() 75: { 76: // Nothing to do here. 77: } 78: 79: /** 80: * Receives notification from a <code>FlowView</code> that some content 81: * has been inserted into the document at a location that the 82: * <code>FlowView</code> is responsible for. 83: * 84: * The default implementation simply calls {@link #layout}. 85: * 86: * @param fv the flow view that sends the notification 87: * @param e the document event describing the change 88: * @param alloc the current allocation of the flow view 89: */ 90: public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) 91: { 92: layout(fv); 93: } 94: 95: /** 96: * Receives notification from a <code>FlowView</code> that some content 97: * has been removed from the document at a location that the 98: * <code>FlowView</code> is responsible for. 99: * 100: * The default implementation simply calls {@link #layout}. 101: * 102: * @param fv the flow view that sends the notification 103: * @param e the document event describing the change 104: * @param alloc the current allocation of the flow view 105: */ 106: public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) 107: { 108: layout(fv); 109: } 110: 111: /** 112: * Receives notification from a <code>FlowView</code> that some attributes 113: * have changed in the document at a location that the 114: * <code>FlowView</code> is responsible for. 115: * 116: * The default implementation simply calls {@link #layout}. 117: * 118: * @param fv the flow view that sends the notification 119: * @param e the document event describing the change 120: * @param alloc the current allocation of the flow view 121: */ 122: public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) 123: { 124: layout(fv); 125: } 126: 127: /** 128: * Returns the logical view of the managed <code>FlowView</code>. 129: * 130: * @param fv the flow view for which to return the logical view 131: * 132: * @return the logical view of the managed <code>FlowView</code> 133: */ 134: public View getLogicalView(FlowView fv) 135: { 136: return fv.layoutPool; 137: } 138: 139: /** 140: * Performs the layout for the whole view. By default this rebuilds 141: * all the physical views from the logical views of the managed FlowView. 142: * 143: * This is called by {@link FlowView#layout} to update the layout of 144: * the view. 145: * 146: * @param fv the flow view for which we perform the layout 147: */ 148: public void layout(FlowView fv) 149: { 150: fv.removeAll(); 151: Element el = fv.getElement(); 152: 153: int rowStart = el.getStartOffset(); 154: int end = el.getEndOffset(); 155: int rowIndex = 0; 156: while (rowStart >= 0 && rowStart < end) 157: { 158: View row = fv.createRow(); 159: fv.append(row); 160: rowStart = layoutRow(fv, rowIndex, rowStart); 161: rowIndex++; 162: } 163: } 164: 165: /** 166: * Lays out one row of the flow view. This is called by {@link #layout} 167: * to fill one row with child views until the available span is exhausted. 168: * 169: * @param fv the flow view for which we perform the layout 170: * @param rowIndex the index of the row 171: * @param pos the start position for the row 172: * 173: * @return the start position of the next row 174: */ 175: protected int layoutRow(FlowView fv, int rowIndex, int pos) 176: { 177: int spanLeft = fv.getFlowSpan(rowIndex); 178: if (spanLeft <= 0) 179: return -1; 180: 181: int offset = pos; 182: View row = fv.getView(rowIndex); 183: int flowAxis = fv.getFlowAxis(); 184: 185: while (spanLeft > 0) 186: { 187: View child = createView(fv, offset, spanLeft, rowIndex); 188: if (child == null) 189: { 190: offset = -1; 191: break; 192: } 193: 194: int span = (int) child.getPreferredSpan(flowAxis); 195: if (span > spanLeft) 196: { 197: offset = -1; 198: break; 199: } 200: 201: row.append(child); 202: spanLeft -= span; 203: offset = child.getEndOffset(); 204: } 205: return offset; 206: } 207: 208: /** 209: * Creates physical views that form the rows of the flow view. This 210: * can be an entire view from the logical view (if it fits within the 211: * available span), a fragment of such a view (if it doesn't fit in the 212: * available span and can be broken down) or <code>null</code> (if it does 213: * not fit in the available span and also cannot be broken down). 214: * 215: * @param fv the flow view 216: * @param offset the start offset for the view to be created 217: * @param spanLeft the available span 218: * @param rowIndex the index of the row 219: * 220: * @return a view to fill the row with, or <code>null</code> if there 221: * is no view or view fragment that fits in the available span 222: */ 223: protected View createView(FlowView fv, int offset, int spanLeft, 224: int rowIndex) 225: { 226: // Find the logical element for the given offset. 227: View logicalView = getLogicalView(fv); 228: 229: int viewIndex = logicalView.getViewIndex(offset, Position.Bias.Forward); 230: if (viewIndex == -1) 231: return null; 232: 233: View child = logicalView.getView(viewIndex); 234: int flowAxis = fv.getFlowAxis(); 235: int span = (int) child.getPreferredSpan(flowAxis); 236: 237: if (span <= spanLeft) 238: return child; 239: else if (child.getBreakWeight(flowAxis, offset, spanLeft) 240: > BadBreakWeight) 241: // FIXME: What to do with the pos parameter here? 242: return child.breakView(flowAxis, offset, 0, spanLeft); 243: else 244: return null; 245: } 246: } 247: 248: /** 249: * This special subclass of <code>View</code> is used to represent 250: * the logical representation of this view. It does not support any 251: * visual representation, this is handled by the physical view implemented 252: * in the <code>FlowView</code>. 253: */ 254: class LogicalView extends View 255: { 256: /** 257: * The child views of this logical view. 258: */ 259: Vector children; 260: 261: /** 262: * Creates a new LogicalView instance. 263: */ 264: LogicalView(Element el) 265: { 266: super(el); 267: children = new Vector(); 268: } 269: 270: /** 271: * Returns the container that holds this view. The logical view returns 272: * the enclosing FlowView's container here. 273: * 274: * @return the container that holds this view 275: */ 276: public Container getContainer() 277: { 278: return FlowView.this.getContainer(); 279: } 280: 281: /** 282: * Returns the number of child views of this logical view. 283: * 284: * @return the number of child views of this logical view 285: */ 286: public int getViewCount() 287: { 288: return children.size(); 289: } 290: 291: /** 292: * Returns the child view at the specified index. 293: * 294: * @param index the index 295: * 296: * @return the child view at the specified index 297: */ 298: public View getView(int index) 299: { 300: return (View) children.get(index); 301: } 302: 303: /** 304: * Replaces some child views with other child views. 305: * 306: * @param offset the offset at which to replace child views 307: * @param length the number of children to remove 308: * @param views the views to be inserted 309: */ 310: public void replace(int offset, int length, View[] views) 311: { 312: if (length > 0) 313: { 314: for (int count = 0; count < length; ++count) 315: children.remove(offset); 316: } 317: 318: int endOffset = offset + views.length; 319: for (int i = offset; i < endOffset; ++i) 320: { 321: children.add(i, views[i - offset]); 322: // Set the parent of the child views to the flow view itself so 323: // it has something to resolve. 324: views[i - offset].setParent(FlowView.this); 325: } 326: } 327: 328: /** 329: * Returns the index of the child view that contains the specified 330: * position in the document model. 331: * 332: * @param pos the position for which we are searching the child view 333: * @param b the bias 334: * 335: * @return the index of the child view that contains the specified 336: * position in the document model 337: */ 338: public int getViewIndex(int pos, Position.Bias b) 339: { 340: int index = -1; 341: int i = 0; 342: for (Iterator it = children.iterator(); it.hasNext(); i++) 343: { 344: View child = (View) it.next(); 345: if (child.getStartOffset() >= pos 346: && child.getEndOffset() < pos) 347: { 348: index = i; 349: break; 350: } 351: } 352: return index; 353: } 354: 355: /** 356: * Throws an AssertionError because it must never be called. LogicalView 357: * only serves as a holder for child views and has no visual 358: * representation. 359: */ 360: public float getPreferredSpan(int axis) 361: { 362: throw new AssertionError("This method must not be called in " 363: + "LogicalView."); 364: } 365: 366: /** 367: * Throws an AssertionError because it must never be called. LogicalView 368: * only serves as a holder for child views and has no visual 369: * representation. 370: */ 371: public Shape modelToView(int pos, Shape a, Position.Bias b) 372: throws BadLocationException 373: { 374: throw new AssertionError("This method must not be called in " 375: + "LogicalView."); 376: } 377: 378: /** 379: * Throws an AssertionError because it must never be called. LogicalView 380: * only serves as a holder for child views and has no visual 381: * representation. 382: */ 383: public void paint(Graphics g, Shape s) 384: { 385: throw new AssertionError("This method must not be called in " 386: + "LogicalView."); 387: } 388: 389: /** 390: * Throws an AssertionError because it must never be called. LogicalView 391: * only serves as a holder for child views and has no visual 392: * representation. 393: */ 394: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 395: { 396: throw new AssertionError("This method must not be called in " 397: + "LogicalView."); 398: } 399: 400: /** 401: * Returns the document position that is (visually) nearest to the given 402: * document position <code>pos</code> in the given direction <code>d</code>. 403: * 404: * @param c the text component 405: * @param pos the document position 406: * @param b the bias for <code>pos</code> 407: * @param d the direction, must be either {@link SwingConstants#NORTH}, 408: * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or 409: * {@link SwingConstants#EAST} 410: * @param biasRet an array of {@link Position.Bias} that can hold at least 411: * one element, which is filled with the bias of the return position 412: * on method exit 413: * 414: * @return the document position that is (visually) nearest to the given 415: * document position <code>pos</code> in the given direction 416: * <code>d</code> 417: * 418: * @throws BadLocationException if <code>pos</code> is not a valid offset in 419: * the document model 420: */ 421: public int getNextVisualPositionFrom(JTextComponent c, int pos, 422: Position.Bias b, int d, 423: Position.Bias[] biasRet) 424: throws BadLocationException 425: { 426: assert false : "getNextVisualPositionFrom() must not be called in " 427: + "LogicalView"; 428: return 0; 429: } 430: } 431: 432: /** 433: * The shared instance of FlowStrategy. 434: */ 435: static final FlowStrategy sharedStrategy = new FlowStrategy(); 436: 437: /** 438: * The span of the <code>FlowView</code> that should be flowed. 439: */ 440: protected int layoutSpan; 441: 442: /** 443: * Represents the logical child elements of this view, encapsulated within 444: * one parent view (an instance of a package private <code>LogicalView</code> 445: * class). These will be translated to a set of real views that are then 446: * displayed on screen. This translation is performed by the inner class 447: * {@link FlowStrategy}. 448: */ 449: protected View layoutPool; 450: 451: /** 452: * The <code>FlowStrategy</code> to use for translating between the 453: * logical and physical view. 454: */ 455: protected FlowStrategy strategy; 456: 457: /** 458: * Creates a new <code>FlowView</code> for the given 459: * <code>Element</code> and <code>axis</code>. 460: * 461: * @param element the element that is rendered by this FlowView 462: * @param axis the axis along which the view is tiled, either 463: * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow 464: * axis is orthogonal to this one 465: */ 466: public FlowView(Element element, int axis) 467: { 468: super(element, axis); 469: strategy = sharedStrategy; 470: } 471: 472: /** 473: * Returns the axis along which the view should be flowed. This is 474: * orthogonal to the axis along which the boxes are tiled. 475: * 476: * @return the axis along which the view should be flowed 477: */ 478: public int getFlowAxis() 479: { 480: int axis = getAxis(); 481: int flowAxis; 482: 483: if (axis == X_AXIS) 484: flowAxis = Y_AXIS; 485: else 486: flowAxis = X_AXIS; 487: 488: return flowAxis; 489: 490: } 491: 492: /** 493: * Returns the span of the flow for the specified child view. A flow 494: * layout can be shaped by providing different span values for different 495: * child indices. The default implementation returns the entire available 496: * span inside the view. 497: * 498: * @param index the index of the child for which to return the span 499: * 500: * @return the span of the flow for the specified child view 501: */ 502: public int getFlowSpan(int index) 503: { 504: return layoutSpan; 505: } 506: 507: /** 508: * Returns the location along the flow axis where the flow span starts 509: * given a child view index. The flow can be shaped by providing 510: * different values here. 511: * 512: * @param index the index of the child for which to return the flow location 513: * 514: * @return the location along the flow axis where the flow span starts 515: */ 516: public int getFlowStart(int index) 517: { 518: return getLeftInset(); // TODO: Is this correct? 519: } 520: 521: /** 522: * Creates a new view that represents a row within a flow. 523: * 524: * @return a view for a new row 525: */ 526: protected abstract View createRow(); 527: 528: /** 529: * Loads the children of this view. The <code>FlowView</code> does not 530: * directly load its children. Instead it creates a logical view 531: * (@{link #layoutPool}) which is filled by the logical child views. 532: * The real children are created at layout time and each represent one 533: * row. 534: * 535: * This method is called by {@link View#setParent} in order to initialize 536: * the view. 537: * 538: * @param vf the view factory to use for creating the child views 539: */ 540: protected void loadChildren(ViewFactory vf) 541: { 542: if (layoutPool == null) 543: { 544: layoutPool = new LogicalView(getElement()); 545: 546: Element el = getElement(); 547: int count = el.getElementCount(); 548: for (int i = 0; i < count; ++i) 549: { 550: Element childEl = el.getElement(i); 551: View childView = vf.create(childEl); 552: layoutPool.append(childView); 553: } 554: } 555: } 556: 557: /** 558: * Performs the layout of this view. If the span along the flow axis changed, 559: * this first calls {@link FlowStrategy#layout} in order to rebuild the 560: * rows of this view. Then the superclass's behaviour is called to arrange 561: * the rows within the box. 562: * 563: * @param width the width of the view 564: * @param height the height of the view 565: */ 566: protected void layout(int width, int height) 567: { 568: boolean rebuild = false; 569: 570: int flowAxis = getFlowAxis(); 571: if (flowAxis == X_AXIS) 572: { 573: rebuild = !(width == layoutSpan); 574: layoutSpan = width; 575: } 576: else 577: { 578: rebuild = !(height == layoutSpan); 579: layoutSpan = height; 580: } 581: 582: if (rebuild) 583: strategy.layout(this); 584: 585: // TODO: If the span along the box axis has changed in the process of 586: // relayouting the rows (that is, if rows have been added or removed), 587: // call preferenceChanged in order to throw away cached layout information 588: // of the surrounding BoxView. 589: 590: super.layout(width, height); 591: } 592: 593: /** 594: * Receice notification that some content has been inserted in the region 595: * that this view is responsible for. This calls 596: * {@link FlowStrategy#insertUpdate}. 597: * 598: * @param changes the document event describing the changes 599: * @param a the current allocation of the view 600: * @param vf the view factory that is used for creating new child views 601: */ 602: public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 603: { 604: // First we must send the insertUpdate to the logical view so it can 605: // be updated accordingly. 606: layoutPool.insertUpdate(changes, a, vf); 607: strategy.insertUpdate(this, changes, getInsideAllocation(a)); 608: } 609: 610: /** 611: * Receice notification that some content has been removed from the region 612: * that this view is responsible for. This calls 613: * {@link FlowStrategy#removeUpdate}. 614: * 615: * @param changes the document event describing the changes 616: * @param a the current allocation of the view 617: * @param vf the view factory that is used for creating new child views 618: */ 619: public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 620: { 621: strategy.removeUpdate(this, changes, getInsideAllocation(a)); 622: } 623: 624: /** 625: * Receice notification that some attributes changed in the region 626: * that this view is responsible for. This calls 627: * {@link FlowStrategy#changedUpdate}. 628: * 629: * @param changes the document event describing the changes 630: * @param a the current allocation of the view 631: * @param vf the view factory that is used for creating new child views 632: */ 633: public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 634: { 635: strategy.changedUpdate(this, changes, getInsideAllocation(a)); 636: } 637: 638: /** 639: * Returns the index of the child <code>View</code> for the given model 640: * position. 641: * 642: * This is implemented to iterate over the children of this 643: * view (the rows) and return the index of the first view that contains 644: * the given position. 645: * 646: * @param pos the model position for whicht the child <code>View</code> is 647: * queried 648: * 649: * @return the index of the child <code>View</code> for the given model 650: * position 651: */ 652: protected int getViewIndexAtPosition(int pos) 653: { 654: // First make sure we have a valid layout. 655: if (!isAllocationValid()) 656: layout(getWidth(), getHeight()); 657: 658: int count = getViewCount(); 659: int result = -1; 660: 661: for (int i = 0; i < count; ++i) 662: { 663: View child = getView(i); 664: int start = child.getStartOffset(); 665: int end = child.getEndOffset(); 666: if (start <= pos && end > pos) 667: { 668: result = i; 669: break; 670: } 671: } 672: return result; 673: } 674: }