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