GNU Classpath (0.20) | |
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: /** 402: * The shared instance of FlowStrategy. 403: */ 404: static final FlowStrategy sharedStrategy = new FlowStrategy(); 405: 406: /** 407: * The span of the <code>FlowView</code> that should be flowed. 408: */ 409: protected int layoutSpan; 410: 411: /** 412: * Represents the logical child elements of this view, encapsulated within 413: * one parent view (an instance of a package private <code>LogicalView</code> 414: * class). These will be translated to a set of real views that are then 415: * displayed on screen. This translation is performed by the inner class 416: * {@link FlowStrategy}. 417: */ 418: protected View layoutPool; 419: 420: /** 421: * The <code>FlowStrategy</code> to use for translating between the 422: * logical and physical view. 423: */ 424: protected FlowStrategy strategy; 425: 426: /** 427: * Creates a new <code>FlowView</code> for the given 428: * <code>Element</code> and <code>axis</code>. 429: * 430: * @param element the element that is rendered by this FlowView 431: * @param axis the axis along which the view is tiled, either 432: * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow 433: * axis is orthogonal to this one 434: */ 435: public FlowView(Element element, int axis) 436: { 437: super(element, axis); 438: strategy = sharedStrategy; 439: } 440: 441: /** 442: * Returns the axis along which the view should be flowed. This is 443: * orthogonal to the axis along which the boxes are tiled. 444: * 445: * @return the axis along which the view should be flowed 446: */ 447: public int getFlowAxis() 448: { 449: int axis = getAxis(); 450: int flowAxis; 451: 452: if (axis == X_AXIS) 453: flowAxis = Y_AXIS; 454: else 455: flowAxis = X_AXIS; 456: 457: return flowAxis; 458: 459: } 460: 461: /** 462: * Returns the span of the flow for the specified child view. A flow 463: * layout can be shaped by providing different span values for different 464: * child indices. The default implementation returns the entire available 465: * span inside the view. 466: * 467: * @param index the index of the child for which to return the span 468: * 469: * @return the span of the flow for the specified child view 470: */ 471: public int getFlowSpan(int index) 472: { 473: return layoutSpan; 474: } 475: 476: /** 477: * Returns the location along the flow axis where the flow span starts 478: * given a child view index. The flow can be shaped by providing 479: * different values here. 480: * 481: * @param index the index of the child for which to return the flow location 482: * 483: * @return the location along the flow axis where the flow span starts 484: */ 485: public int getFlowStart(int index) 486: { 487: return getLeftInset(); // TODO: Is this correct? 488: } 489: 490: /** 491: * Creates a new view that represents a row within a flow. 492: * 493: * @return a view for a new row 494: */ 495: protected abstract View createRow(); 496: 497: /** 498: * Loads the children of this view. The <code>FlowView</code> does not 499: * directly load its children. Instead it creates a logical view 500: * (@{link #layoutPool}) which is filled by the logical child views. 501: * The real children are created at layout time and each represent one 502: * row. 503: * 504: * This method is called by {@link View#setParent} in order to initialize 505: * the view. 506: * 507: * @param vf the view factory to use for creating the child views 508: */ 509: protected void loadChildren(ViewFactory vf) 510: { 511: if (layoutPool == null) 512: { 513: layoutPool = new LogicalView(getElement()); 514: 515: Element el = getElement(); 516: int count = el.getElementCount(); 517: for (int i = 0; i < count; ++i) 518: { 519: Element childEl = el.getElement(i); 520: View childView = vf.create(childEl); 521: layoutPool.append(childView); 522: } 523: } 524: } 525: 526: /** 527: * Performs the layout of this view. If the span along the flow axis changed, 528: * this first calls {@link FlowStrategy#layout} in order to rebuild the 529: * rows of this view. Then the superclass's behaviour is called to arrange 530: * the rows within the box. 531: * 532: * @param width the width of the view 533: * @param height the height of the view 534: */ 535: protected void layout(int width, int height) 536: { 537: boolean rebuild = false; 538: 539: int flowAxis = getFlowAxis(); 540: if (flowAxis == X_AXIS) 541: { 542: rebuild = !(width == layoutSpan); 543: layoutSpan = width; 544: } 545: else 546: { 547: rebuild = !(height == layoutSpan); 548: layoutSpan = height; 549: } 550: 551: if (rebuild) 552: strategy.layout(this); 553: 554: // TODO: If the span along the box axis has changed in the process of 555: // relayouting the rows (that is, if rows have been added or removed), 556: // call preferenceChanged in order to throw away cached layout information 557: // of the surrounding BoxView. 558: 559: super.layout(width, height); 560: } 561: 562: /** 563: * Receice notification that some content has been inserted in the region 564: * that this view is responsible for. This calls 565: * {@link FlowStrategy#insertUpdate}. 566: * 567: * @param changes the document event describing the changes 568: * @param a the current allocation of the view 569: * @param vf the view factory that is used for creating new child views 570: */ 571: public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 572: { 573: // First we must send the insertUpdate to the logical view so it can 574: // be updated accordingly. 575: layoutPool.insertUpdate(changes, a, vf); 576: strategy.insertUpdate(this, changes, getInsideAllocation(a)); 577: } 578: 579: /** 580: * Receice notification that some content has been removed from the region 581: * that this view is responsible for. This calls 582: * {@link FlowStrategy#removeUpdate}. 583: * 584: * @param changes the document event describing the changes 585: * @param a the current allocation of the view 586: * @param vf the view factory that is used for creating new child views 587: */ 588: public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 589: { 590: strategy.removeUpdate(this, changes, getInsideAllocation(a)); 591: } 592: 593: /** 594: * Receice notification that some attributes changed in the region 595: * that this view is responsible for. This calls 596: * {@link FlowStrategy#changedUpdate}. 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 changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf) 603: { 604: strategy.changedUpdate(this, changes, getInsideAllocation(a)); 605: } 606: 607: /** 608: * Returns the index of the child <code>View</code> for the given model 609: * position. 610: * 611: * This is implemented to iterate over the children of this 612: * view (the rows) and return the index of the first view that contains 613: * the given position. 614: * 615: * @param pos the model position for whicht the child <code>View</code> is 616: * queried 617: * 618: * @return the index of the child <code>View</code> for the given model 619: * position 620: */ 621: protected int getViewIndexAtPosition(int pos) 622: { 623: // First make sure we have a valid layout. 624: if (!isAllocationValid()) 625: layout(getWidth(), getHeight()); 626: 627: int count = getViewCount(); 628: int result = -1; 629: 630: for (int i = 0; i < count; ++i) 631: { 632: View child = getView(i); 633: int start = child.getStartOffset(); 634: int end = child.getEndOffset(); 635: if (start <= pos && end > pos) 636: { 637: result = i; 638: break; 639: } 640: } 641: return result; 642: } 643: }
GNU Classpath (0.20) |