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