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