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