1 /* 2 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text; 26 27 import java.util.*; 28 import java.util.List; 29 import java.awt.*; 30 import javax.swing.SwingUtilities; 31 import javax.swing.event.DocumentEvent; 32 33 /** 34 * A box that does layout asynchronously. This 35 * is useful to keep the GUI event thread moving by 36 * not doing any layout on it. The layout is done 37 * on a granularity of operations on the child views. 38 * After each child view is accessed for some part 39 * of layout (a potentially time consuming operation) 40 * the remaining tasks can be abandoned or a new higher 41 * priority task (i.e. to service a synchronous request 42 * or a visible area) can be taken on. 43 * <p> 44 * While the child view is being accessed 45 * a read lock is acquired on the associated document 46 * so that the model is stable while being accessed. 47 * 48 * @author Timothy Prinzing 49 * @since 1.3 50 */ 51 public class AsyncBoxView extends View { 52 53 /** 54 * Construct a box view that does asynchronous layout. 55 * 56 * @param elem the element of the model to represent 57 * @param axis the axis to tile along. This can be 58 * either X_AXIS or Y_AXIS. 59 */ 60 public AsyncBoxView(Element elem, int axis) { 61 super(elem); 62 stats = new ArrayList<ChildState>(); 63 this.axis = axis; 64 locator = new ChildLocator(); 65 flushTask = new FlushTask(); 66 minorSpan = Short.MAX_VALUE; 67 estimatedMajorSpan = false; 68 } 69 70 /** 71 * Fetch the major axis (the axis the children 72 * are tiled along). This will have a value of 73 * either X_AXIS or Y_AXIS. 74 */ 75 public int getMajorAxis() { 76 return axis; 77 } 78 79 /** 80 * Fetch the minor axis (the axis orthogonal 81 * to the tiled axis). This will have a value of 82 * either X_AXIS or Y_AXIS. 83 */ 84 public int getMinorAxis() { 85 return (axis == X_AXIS) ? Y_AXIS : X_AXIS; 86 } 87 88 /** 89 * Get the top part of the margin around the view. 90 */ 91 public float getTopInset() { 92 return topInset; 93 } 94 95 /** 96 * Set the top part of the margin around the view. 97 * 98 * @param i the value of the inset 99 */ 100 public void setTopInset(float i) { 101 topInset = i; 102 } 103 104 /** 105 * Get the bottom part of the margin around the view. 106 */ 107 public float getBottomInset() { 108 return bottomInset; 109 } 110 111 /** 112 * Set the bottom part of the margin around the view. 113 * 114 * @param i the value of the inset 115 */ 116 public void setBottomInset(float i) { 117 bottomInset = i; 118 } 119 120 /** 121 * Get the left part of the margin around the view. 122 */ 123 public float getLeftInset() { 124 return leftInset; 125 } 126 127 /** 128 * Set the left part of the margin around the view. 129 * 130 * @param i the value of the inset 131 */ 132 public void setLeftInset(float i) { 133 leftInset = i; 134 } 135 136 /** 137 * Get the right part of the margin around the view. 138 */ 139 public float getRightInset() { 140 return rightInset; 141 } 142 143 /** 144 * Set the right part of the margin around the view. 145 * 146 * @param i the value of the inset 147 */ 148 public void setRightInset(float i) { 149 rightInset = i; 150 } 151 152 /** 153 * Fetch the span along an axis that is taken up by the insets. 154 * 155 * @param axis the axis to determine the total insets along, 156 * either X_AXIS or Y_AXIS. 157 * @since 1.4 158 */ 159 protected float getInsetSpan(int axis) { 160 float margin = (axis == X_AXIS) ? 161 getLeftInset() + getRightInset() : getTopInset() + getBottomInset(); 162 return margin; 163 } 164 165 /** 166 * Set the estimatedMajorSpan property that determines if the 167 * major span should be treated as being estimated. If this 168 * property is true, the value of setSize along the major axis 169 * will change the requirements along the major axis and incremental 170 * changes will be ignored until all of the children have been updated 171 * (which will cause the property to automatically be set to false). 172 * If the property is false the value of the majorSpan will be 173 * considered to be accurate and incremental changes will be 174 * added into the total as they are calculated. 175 * 176 * @since 1.4 177 */ 178 protected void setEstimatedMajorSpan(boolean isEstimated) { 179 estimatedMajorSpan = isEstimated; 180 } 181 182 /** 183 * Is the major span currently estimated? 184 * 185 * @since 1.4 186 */ 187 protected boolean getEstimatedMajorSpan() { 188 return estimatedMajorSpan; 189 } 190 191 /** 192 * Fetch the object representing the layout state of 193 * of the child at the given index. 194 * 195 * @param index the child index. This should be a 196 * value >= 0 and < getViewCount(). 197 */ 198 protected ChildState getChildState(int index) { 199 synchronized(stats) { 200 if ((index >= 0) && (index < stats.size())) { 201 return stats.get(index); 202 } 203 return null; 204 } 205 } 206 207 /** 208 * Fetch the queue to use for layout. 209 */ 210 protected LayoutQueue getLayoutQueue() { 211 return LayoutQueue.getDefaultQueue(); 212 } 213 214 /** 215 * New ChildState records are created through 216 * this method to allow subclasses the extend 217 * the ChildState records to do/hold more 218 */ 219 protected ChildState createChildState(View v) { 220 return new ChildState(v); 221 } 222 223 /** 224 * Requirements changed along the major axis. 225 * This is called by the thread doing layout for 226 * the given ChildState object when it has completed 227 * fetching the child views new preferences. 228 * Typically this would be the layout thread, but 229 * might be the event thread if it is trying to update 230 * something immediately (such as to perform a 231 * model/view translation). 232 * <p> 233 * This is implemented to mark the major axis as having 234 * changed so that a future check to see if the requirements 235 * need to be published to the parent view will consider 236 * the major axis. If the span along the major axis is 237 * not estimated, it is updated by the given delta to reflect 238 * the incremental change. The delta is ignored if the 239 * major span is estimated. 240 */ 241 protected synchronized void majorRequirementChange(ChildState cs, float delta) { 242 if (estimatedMajorSpan == false) { 243 majorSpan += delta; 244 } 245 majorChanged = true; 246 } 247 248 /** 249 * Requirements changed along the minor axis. 250 * This is called by the thread doing layout for 251 * the given ChildState object when it has completed 252 * fetching the child views new preferences. 253 * Typically this would be the layout thread, but 254 * might be the GUI thread if it is trying to update 255 * something immediately (such as to perform a 256 * model/view translation). 257 */ 258 protected synchronized void minorRequirementChange(ChildState cs) { 259 minorChanged = true; 260 } 261 262 /** 263 * Publish the changes in preferences upward to the parent 264 * view. This is normally called by the layout thread. 265 */ 266 protected void flushRequirementChanges() { 267 AbstractDocument doc = (AbstractDocument) getDocument(); 268 try { 269 doc.readLock(); 270 271 View parent = null; 272 boolean horizontal = false; 273 boolean vertical = false; 274 275 synchronized(this) { 276 // perform tasks that iterate over the children while 277 // preventing the collection from changing. 278 synchronized(stats) { 279 int n = getViewCount(); 280 if ((n > 0) && (minorChanged || estimatedMajorSpan)) { 281 LayoutQueue q = getLayoutQueue(); 282 ChildState min = getChildState(0); 283 ChildState pref = getChildState(0); 284 float span = 0f; 285 for (int i = 1; i < n; i++) { 286 ChildState cs = getChildState(i); 287 if (minorChanged) { 288 if (cs.min > min.min) { 289 min = cs; 290 } 291 if (cs.pref > pref.pref) { 292 pref = cs; 293 } 294 } 295 if (estimatedMajorSpan) { 296 span += cs.getMajorSpan(); 297 } 298 } 299 300 if (minorChanged) { 301 minRequest = min; 302 prefRequest = pref; 303 } 304 if (estimatedMajorSpan) { 305 majorSpan = span; 306 estimatedMajorSpan = false; 307 majorChanged = true; 308 } 309 } 310 } 311 312 // message preferenceChanged 313 if (majorChanged || minorChanged) { 314 parent = getParent(); 315 if (parent != null) { 316 if (axis == X_AXIS) { 317 horizontal = majorChanged; 318 vertical = minorChanged; 319 } else { 320 vertical = majorChanged; 321 horizontal = minorChanged; 322 } 323 } 324 majorChanged = false; 325 minorChanged = false; 326 } 327 } 328 329 // propagate a preferenceChanged, using the 330 // layout thread. 331 if (parent != null) { 332 parent.preferenceChanged(this, horizontal, vertical); 333 334 // probably want to change this to be more exact. 335 Component c = getContainer(); 336 if (c != null) { 337 c.repaint(); 338 } 339 } 340 } finally { 341 doc.readUnlock(); 342 } 343 } 344 345 /** 346 * Calls the superclass to update the child views, and 347 * updates the status records for the children. This 348 * is expected to be called while a write lock is held 349 * on the model so that interaction with the layout 350 * thread will not happen (i.e. the layout thread 351 * acquires a read lock before doing anything). 352 * 353 * @param offset the starting offset into the child views >= 0 354 * @param length the number of existing views to replace >= 0 355 * @param views the child views to insert 356 */ 357 public void replace(int offset, int length, View[] views) { 358 synchronized(stats) { 359 // remove the replaced state records 360 for (int i = 0; i < length; i++) { 361 ChildState cs = stats.remove(offset); 362 float csSpan = cs.getMajorSpan(); 363 364 cs.getChildView().setParent(null); 365 if (csSpan != 0) { 366 majorRequirementChange(cs, -csSpan); 367 } 368 } 369 370 // insert the state records for the new children 371 LayoutQueue q = getLayoutQueue(); 372 if (views != null) { 373 for (int i = 0; i < views.length; i++) { 374 ChildState s = createChildState(views[i]); 375 stats.add(offset + i, s); 376 q.addTask(s); 377 } 378 } 379 380 // notify that the size changed 381 q.addTask(flushTask); 382 } 383 } 384 385 /** 386 * Loads all of the children to initialize the view. 387 * This is called by the {@link #setParent setParent} 388 * method. Subclasses can reimplement this to initialize 389 * their child views in a different manner. The default 390 * implementation creates a child view for each 391 * child element. 392 * <p> 393 * Normally a write-lock is held on the Document while 394 * the children are being changed, which keeps the rendering 395 * and layout threads safe. The exception to this is when 396 * the view is initialized to represent an existing element 397 * (via this method), so it is synchronized to exclude 398 * preferenceChanged while we are initializing. 399 * 400 * @param f the view factory 401 * @see #setParent 402 */ 403 protected void loadChildren(ViewFactory f) { 404 Element e = getElement(); 405 int n = e.getElementCount(); 406 if (n > 0) { 407 View[] added = new View[n]; 408 for (int i = 0; i < n; i++) { 409 added[i] = f.create(e.getElement(i)); 410 } 411 replace(0, 0, added); 412 } 413 } 414 415 /** 416 * Fetches the child view index representing the given position in 417 * the model. This is implemented to fetch the view in the case 418 * where there is a child view for each child element. 419 * 420 * @param pos the position >= 0 421 * @return index of the view representing the given position, or 422 * -1 if no view represents that position 423 */ 424 protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) { 425 boolean isBackward = (b == Position.Bias.Backward); 426 pos = (isBackward) ? Math.max(0, pos - 1) : pos; 427 Element elem = getElement(); 428 return elem.getElementIndex(pos); 429 } 430 431 /** 432 * Update the layout in response to receiving notification of 433 * change from the model. This is implemented to note the 434 * change on the ChildLocator so that offsets of the children 435 * will be correctly computed. 436 * 437 * @param ec changes to the element this view is responsible 438 * for (may be null if there were no changes). 439 * @param e the change information from the associated document 440 * @param a the current allocation of the view 441 * @see #insertUpdate 442 * @see #removeUpdate 443 * @see #changedUpdate 444 */ 445 protected void updateLayout(DocumentEvent.ElementChange ec, 446 DocumentEvent e, Shape a) { 447 if (ec != null) { 448 // the newly inserted children don't have a valid 449 // offset so the child locator needs to be messaged 450 // that the child prior to the new children has 451 // changed size. 452 int index = Math.max(ec.getIndex() - 1, 0); 453 ChildState cs = getChildState(index); 454 locator.childChanged(cs); 455 } 456 } 457 458 // --- View methods ------------------------------------ 459 460 /** 461 * Sets the parent of the view. 462 * This is reimplemented to provide the superclass 463 * behavior as well as calling the <code>loadChildren</code> 464 * method if this view does not already have children. 465 * The children should not be loaded in the 466 * constructor because the act of setting the parent 467 * may cause them to try to search up the hierarchy 468 * (to get the hosting Container for example). 469 * If this view has children (the view is being moved 470 * from one place in the view hierarchy to another), 471 * the <code>loadChildren</code> method will not be called. 472 * 473 * @param parent the parent of the view, null if none 474 */ 475 public void setParent(View parent) { 476 super.setParent(parent); 477 if ((parent != null) && (getViewCount() == 0)) { 478 ViewFactory f = getViewFactory(); 479 loadChildren(f); 480 } 481 } 482 483 /** 484 * Child views can call this on the parent to indicate that 485 * the preference has changed and should be reconsidered 486 * for layout. This is reimplemented to queue new work 487 * on the layout thread. This method gets messaged from 488 * multiple threads via the children. 489 * 490 * @param child the child view 491 * @param width true if the width preference has changed 492 * @param height true if the height preference has changed 493 * @see javax.swing.JComponent#revalidate 494 */ 495 public synchronized void preferenceChanged(View child, boolean width, boolean height) { 496 if (child == null) { 497 getParent().preferenceChanged(this, width, height); 498 } else { 499 if (changing != null) { 500 View cv = changing.getChildView(); 501 if (cv == child) { 502 // size was being changed on the child, no need to 503 // queue work for it. 504 changing.preferenceChanged(width, height); 505 return; 506 } 507 } 508 int index = getViewIndex(child.getStartOffset(), 509 Position.Bias.Forward); 510 ChildState cs = getChildState(index); 511 cs.preferenceChanged(width, height); 512 LayoutQueue q = getLayoutQueue(); 513 q.addTask(cs); 514 q.addTask(flushTask); 515 } 516 } 517 518 /** 519 * Sets the size of the view. This should cause 520 * layout of the view if the view caches any layout 521 * information. 522 * <p> 523 * Since the major axis is updated asynchronously and should be 524 * the sum of the tiled children the call is ignored for the major 525 * axis. Since the minor axis is flexible, work is queued to resize 526 * the children if the minor span changes. 527 * 528 * @param width the width >= 0 529 * @param height the height >= 0 530 */ 531 public void setSize(float width, float height) { 532 setSpanOnAxis(X_AXIS, width); 533 setSpanOnAxis(Y_AXIS, height); 534 } 535 536 /** 537 * Retrieves the size of the view along an axis. 538 * 539 * @param axis may be either <code>View.X_AXIS</code> or 540 * <code>View.Y_AXIS</code> 541 * @return the current span of the view along the given axis, >= 0 542 */ 543 float getSpanOnAxis(int axis) { 544 if (axis == getMajorAxis()) { 545 return majorSpan; 546 } 547 return minorSpan; 548 } 549 550 /** 551 * Sets the size of the view along an axis. Since the major 552 * axis is updated asynchronously and should be the sum of the 553 * tiled children the call is ignored for the major axis. Since 554 * the minor axis is flexible, work is queued to resize the 555 * children if the minor span changes. 556 * 557 * @param axis may be either <code>View.X_AXIS</code> or 558 * <code>View.Y_AXIS</code> 559 * @param span the span to layout to >= 0 560 */ 561 void setSpanOnAxis(int axis, float span) { 562 float margin = getInsetSpan(axis); 563 if (axis == getMinorAxis()) { 564 float targetSpan = span - margin; 565 if (targetSpan != minorSpan) { 566 minorSpan = targetSpan; 567 568 // mark all of the ChildState instances as needing to 569 // resize the child, and queue up work to fix them. 570 int n = getViewCount(); 571 if (n != 0) { 572 LayoutQueue q = getLayoutQueue(); 573 for (int i = 0; i < n; i++) { 574 ChildState cs = getChildState(i); 575 cs.childSizeValid = false; 576 q.addTask(cs); 577 } 578 q.addTask(flushTask); 579 } 580 } 581 } else { 582 // along the major axis the value is ignored 583 // unless the estimatedMajorSpan property is 584 // true. 585 if (estimatedMajorSpan) { 586 majorSpan = span - margin; 587 } 588 } 589 } 590 591 /** 592 * Render the view using the given allocation and 593 * rendering surface. 594 * <p> 595 * This is implemented to determine whether or not the 596 * desired region to be rendered (i.e. the unclipped 597 * area) is up to date or not. If up-to-date the children 598 * are rendered. If not up-to-date, a task to build 599 * the desired area is placed on the layout queue as 600 * a high priority task. This keeps by event thread 601 * moving by rendering if ready, and postponing until 602 * a later time if not ready (since paint requests 603 * can be rescheduled). 604 * 605 * @param g the rendering surface to use 606 * @param alloc the allocated region to render into 607 * @see View#paint 608 */ 609 public void paint(Graphics g, Shape alloc) { 610 synchronized (locator) { 611 locator.setAllocation(alloc); 612 locator.paintChildren(g); 613 } 614 } 615 616 /** 617 * Determines the preferred span for this view along an 618 * axis. 619 * 620 * @param axis may be either View.X_AXIS or View.Y_AXIS 621 * @return the span the view would like to be rendered into >= 0. 622 * Typically the view is told to render into the span 623 * that is returned, although there is no guarantee. 624 * The parent may choose to resize or break the view. 625 * @exception IllegalArgumentException for an invalid axis type 626 */ 627 public float getPreferredSpan(int axis) { 628 float margin = getInsetSpan(axis); 629 if (axis == this.axis) { 630 return majorSpan + margin; 631 } 632 if (prefRequest != null) { 633 View child = prefRequest.getChildView(); 634 return child.getPreferredSpan(axis) + margin; 635 } 636 637 // nothing is known about the children yet 638 return margin + 30; 639 } 640 641 /** 642 * Determines the minimum span for this view along an 643 * axis. 644 * 645 * @param axis may be either View.X_AXIS or View.Y_AXIS 646 * @return the span the view would like to be rendered into >= 0. 647 * Typically the view is told to render into the span 648 * that is returned, although there is no guarantee. 649 * The parent may choose to resize or break the view. 650 * @exception IllegalArgumentException for an invalid axis type 651 */ 652 public float getMinimumSpan(int axis) { 653 if (axis == this.axis) { 654 return getPreferredSpan(axis); 655 } 656 if (minRequest != null) { 657 View child = minRequest.getChildView(); 658 return child.getMinimumSpan(axis); 659 } 660 661 // nothing is known about the children yet 662 if (axis == X_AXIS) { 663 return getLeftInset() + getRightInset() + 5; 664 } else { 665 return getTopInset() + getBottomInset() + 5; 666 } 667 } 668 669 /** 670 * Determines the maximum span for this view along an 671 * axis. 672 * 673 * @param axis may be either View.X_AXIS or View.Y_AXIS 674 * @return the span the view would like to be rendered into >= 0. 675 * Typically the view is told to render into the span 676 * that is returned, although there is no guarantee. 677 * The parent may choose to resize or break the view. 678 * @exception IllegalArgumentException for an invalid axis type 679 */ 680 public float getMaximumSpan(int axis) { 681 if (axis == this.axis) { 682 return getPreferredSpan(axis); 683 } 684 return Integer.MAX_VALUE; 685 } 686 687 688 /** 689 * Returns the number of views in this view. Since 690 * the default is to not be a composite view this 691 * returns 0. 692 * 693 * @return the number of views >= 0 694 * @see View#getViewCount 695 */ 696 public int getViewCount() { 697 synchronized(stats) { 698 return stats.size(); 699 } 700 } 701 702 /** 703 * Gets the nth child view. Since there are no 704 * children by default, this returns null. 705 * 706 * @param n the number of the view to get, >= 0 && < getViewCount() 707 * @return the view 708 */ 709 public View getView(int n) { 710 ChildState cs = getChildState(n); 711 if (cs != null) { 712 return cs.getChildView(); 713 } 714 return null; 715 } 716 717 /** 718 * Fetches the allocation for the given child view. 719 * This enables finding out where various views 720 * are located, without assuming the views store 721 * their location. This returns null since the 722 * default is to not have any child views. 723 * 724 * @param index the index of the child, >= 0 && < getViewCount() 725 * @param a the allocation to this view. 726 * @return the allocation to the child 727 */ 728 public Shape getChildAllocation(int index, Shape a) { 729 Shape ca = locator.getChildAllocation(index, a); 730 return ca; 731 } 732 733 /** 734 * Returns the child view index representing the given position in 735 * the model. By default a view has no children so this is implemented 736 * to return -1 to indicate there is no valid child index for any 737 * position. 738 * 739 * @param pos the position >= 0 740 * @return index of the view representing the given position, or 741 * -1 if no view represents that position 742 * @since 1.3 743 */ 744 public int getViewIndex(int pos, Position.Bias b) { 745 return getViewIndexAtPosition(pos, b); 746 } 747 748 /** 749 * Provides a mapping from the document model coordinate space 750 * to the coordinate space of the view mapped to it. 751 * 752 * @param pos the position to convert >= 0 753 * @param a the allocated region to render into 754 * @param b the bias toward the previous character or the 755 * next character represented by the offset, in case the 756 * position is a boundary of two views. 757 * @return the bounding box of the given position is returned 758 * @exception BadLocationException if the given position does 759 * not represent a valid location in the associated document 760 * @exception IllegalArgumentException for an invalid bias argument 761 * @see View#viewToModel 762 */ 763 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 764 int index = getViewIndex(pos, b); 765 Shape ca = locator.getChildAllocation(index, a); 766 767 // forward to the child view, and make sure we don't 768 // interact with the layout thread by synchronizing 769 // on the child state. 770 ChildState cs = getChildState(index); 771 synchronized (cs) { 772 View cv = cs.getChildView(); 773 Shape v = cv.modelToView(pos, ca, b); 774 return v; 775 } 776 } 777 778 /** 779 * Provides a mapping from the view coordinate space to the logical 780 * coordinate space of the model. The biasReturn argument will be 781 * filled in to indicate that the point given is closer to the next 782 * character in the model or the previous character in the model. 783 * <p> 784 * This is expected to be called by the GUI thread, holding a 785 * read-lock on the associated model. It is implemented to 786 * locate the child view and determine it's allocation with a 787 * lock on the ChildLocator object, and to call viewToModel 788 * on the child view with a lock on the ChildState object 789 * to avoid interaction with the layout thread. 790 * 791 * @param x the X coordinate >= 0 792 * @param y the Y coordinate >= 0 793 * @param a the allocated region to render into 794 * @return the location within the model that best represents the 795 * given point in the view >= 0. The biasReturn argument will be 796 * filled in to indicate that the point given is closer to the next 797 * character in the model or the previous character in the model. 798 */ 799 public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) { 800 int pos; // return position 801 int index; // child index to forward to 802 Shape ca; // child allocation 803 804 // locate the child view and it's allocation so that 805 // we can forward to it. Make sure the layout thread 806 // doesn't change anything by trying to flush changes 807 // to the parent while the GUI thread is trying to 808 // find the child and it's allocation. 809 synchronized (locator) { 810 index = locator.getViewIndexAtPoint(x, y, a); 811 ca = locator.getChildAllocation(index, a); 812 } 813 814 // forward to the child view, and make sure we don't 815 // interact with the layout thread by synchronizing 816 // on the child state. 817 ChildState cs = getChildState(index); 818 synchronized (cs) { 819 View v = cs.getChildView(); 820 pos = v.viewToModel(x, y, ca, biasReturn); 821 } 822 return pos; 823 } 824 825 /** 826 * Provides a way to determine the next visually represented model 827 * location that one might place a caret. Some views may not be visible, 828 * they might not be in the same order found in the model, or they just 829 * might not allow access to some of the locations in the model. 830 * This method enables specifying a position to convert 831 * within the range of >=0. If the value is -1, a position 832 * will be calculated automatically. If the value < -1, 833 * the {@code BadLocationException} will be thrown. 834 * 835 * @param pos the position to convert 836 * @param a the allocated region to render into 837 * @param direction the direction from the current position that can 838 * be thought of as the arrow keys typically found on a keyboard; 839 * this may be one of the following: 840 * <ul style="list-style-type:none"> 841 * <li><code>SwingConstants.WEST</code></li> 842 * <li><code>SwingConstants.EAST</code></li> 843 * <li><code>SwingConstants.NORTH</code></li> 844 * <li><code>SwingConstants.SOUTH</code></li> 845 * </ul> 846 * @param biasRet an array contain the bias that was checked 847 * @return the location within the model that best represents the next 848 * location visual position 849 * @exception BadLocationException the given position is not a valid 850 * position within the document 851 * @exception IllegalArgumentException if <code>direction</code> is invalid 852 */ 853 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 854 int direction, 855 Position.Bias[] biasRet) 856 throws BadLocationException { 857 if (pos < -1 || pos > getDocument().getLength()) { 858 throw new BadLocationException("invalid position", pos); 859 } 860 return Utilities.getNextVisualPositionFrom( 861 this, pos, b, a, direction, biasRet); 862 } 863 864 // --- variables ----------------------------------------- 865 866 /** 867 * The major axis against which the children are 868 * tiled. 869 */ 870 int axis; 871 872 /** 873 * The children and their layout statistics. 874 */ 875 List<ChildState> stats; 876 877 /** 878 * Current span along the major axis. This 879 * is also the value returned by getMinimumSize, 880 * getPreferredSize, and getMaximumSize along 881 * the major axis. 882 */ 883 float majorSpan; 884 885 /** 886 * Is the span along the major axis estimated? 887 */ 888 boolean estimatedMajorSpan; 889 890 /** 891 * Current span along the minor axis. This 892 * is what layout was done against (i.e. things 893 * are flexible in this direction). 894 */ 895 float minorSpan; 896 897 /** 898 * Object that manages the offsets of the 899 * children. All locking for management of 900 * child locations is on this object. 901 */ 902 protected ChildLocator locator; 903 904 float topInset; 905 float bottomInset; 906 float leftInset; 907 float rightInset; 908 909 ChildState minRequest; 910 ChildState prefRequest; 911 boolean majorChanged; 912 boolean minorChanged; 913 Runnable flushTask; 914 915 /** 916 * Child that is actively changing size. This often 917 * causes a preferenceChanged, so this is a cache to 918 * possibly speed up the marking the state. It also 919 * helps flag an opportunity to avoid adding to flush 920 * task to the layout queue. 921 */ 922 ChildState changing; 923 924 /** 925 * A class to manage the effective position of the 926 * child views in a localized area while changes are 927 * being made around the localized area. The AsyncBoxView 928 * may be continuously changing, but the visible area 929 * needs to remain fairly stable until the layout thread 930 * decides to publish an update to the parent. 931 * @since 1.3 932 */ 933 public class ChildLocator { 934 935 /** 936 * construct a child locator. 937 */ 938 public ChildLocator() { 939 lastAlloc = new Rectangle(); 940 childAlloc = new Rectangle(); 941 } 942 943 /** 944 * Notification that a child changed. This can effect 945 * whether or not new offset calculations are needed. 946 * This is called by a ChildState object that has 947 * changed it's major span. This can therefore be 948 * called by multiple threads. 949 */ 950 public synchronized void childChanged(ChildState cs) { 951 if (lastValidOffset == null) { 952 lastValidOffset = cs; 953 } else if (cs.getChildView().getStartOffset() < 954 lastValidOffset.getChildView().getStartOffset()) { 955 lastValidOffset = cs; 956 } 957 } 958 959 /** 960 * Paint the children that intersect the clip area. 961 */ 962 public synchronized void paintChildren(Graphics g) { 963 Rectangle clip = g.getClipBounds(); 964 float targetOffset = (axis == X_AXIS) ? 965 clip.x - lastAlloc.x : clip.y - lastAlloc.y; 966 int index = getViewIndexAtVisualOffset(targetOffset); 967 int n = getViewCount(); 968 float offs = getChildState(index).getMajorOffset(); 969 for (int i = index; i < n; i++) { 970 ChildState cs = getChildState(i); 971 cs.setMajorOffset(offs); 972 Shape ca = getChildAllocation(i); 973 if (intersectsClip(ca, clip)) { 974 synchronized (cs) { 975 View v = cs.getChildView(); 976 v.paint(g, ca); 977 } 978 } else { 979 // done painting intersection 980 break; 981 } 982 offs += cs.getMajorSpan(); 983 } 984 } 985 986 /** 987 * Fetch the allocation to use for a child view. 988 * This will update the offsets for all children 989 * not yet updated before the given index. 990 */ 991 public synchronized Shape getChildAllocation(int index, Shape a) { 992 if (a == null) { 993 return null; 994 } 995 setAllocation(a); 996 ChildState cs = getChildState(index); 997 if (lastValidOffset == null) { 998 lastValidOffset = getChildState(0); 999 } 1000 if (cs.getChildView().getStartOffset() > 1001 lastValidOffset.getChildView().getStartOffset()) { 1002 // offsets need to be updated 1003 updateChildOffsetsToIndex(index); 1004 } 1005 Shape ca = getChildAllocation(index); 1006 return ca; 1007 } 1008 1009 /** 1010 * Fetches the child view index at the given point. 1011 * This is called by the various View methods that 1012 * need to calculate which child to forward a message 1013 * to. This should be called by a block synchronized 1014 * on this object, and would typically be followed 1015 * with one or more calls to getChildAllocation that 1016 * should also be in the synchronized block. 1017 * 1018 * @param x the X coordinate >= 0 1019 * @param y the Y coordinate >= 0 1020 * @param a the allocation to the View 1021 * @return the nearest child index 1022 */ 1023 public int getViewIndexAtPoint(float x, float y, Shape a) { 1024 setAllocation(a); 1025 float targetOffset = (axis == X_AXIS) ? x - lastAlloc.x : y - lastAlloc.y; 1026 int index = getViewIndexAtVisualOffset(targetOffset); 1027 return index; 1028 } 1029 1030 /** 1031 * Fetch the allocation to use for a child view. 1032 * <em>This does not update the offsets in the ChildState 1033 * records.</em> 1034 */ 1035 protected Shape getChildAllocation(int index) { 1036 ChildState cs = getChildState(index); 1037 if (! cs.isLayoutValid()) { 1038 cs.run(); 1039 } 1040 if (axis == X_AXIS) { 1041 childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset(); 1042 childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset(); 1043 childAlloc.width = (int) cs.getMajorSpan(); 1044 childAlloc.height = (int) cs.getMinorSpan(); 1045 } else { 1046 childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset(); 1047 childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset(); 1048 childAlloc.height = (int) cs.getMajorSpan(); 1049 childAlloc.width = (int) cs.getMinorSpan(); 1050 } 1051 childAlloc.x += (int)getLeftInset(); 1052 childAlloc.y += (int)getRightInset(); 1053 return childAlloc; 1054 } 1055 1056 /** 1057 * Copy the currently allocated shape into the Rectangle 1058 * used to store the current allocation. This would be 1059 * a floating point rectangle in a Java2D-specific implementation. 1060 */ 1061 protected void setAllocation(Shape a) { 1062 if (a instanceof Rectangle) { 1063 lastAlloc.setBounds((Rectangle) a); 1064 } else { 1065 lastAlloc.setBounds(a.getBounds()); 1066 } 1067 setSize(lastAlloc.width, lastAlloc.height); 1068 } 1069 1070 /** 1071 * Locate the view responsible for an offset into the box 1072 * along the major axis. Make sure that offsets are set 1073 * on the ChildState objects up to the given target span 1074 * past the desired offset. 1075 * 1076 * @return index of the view representing the given visual 1077 * location (targetOffset), or -1 if no view represents 1078 * that location 1079 */ 1080 protected int getViewIndexAtVisualOffset(float targetOffset) { 1081 int n = getViewCount(); 1082 if (n > 0) { 1083 boolean lastValid = (lastValidOffset != null); 1084 1085 if (lastValidOffset == null) { 1086 lastValidOffset = getChildState(0); 1087 } 1088 if (targetOffset > majorSpan) { 1089 // should only get here on the first time display. 1090 if (!lastValid) { 1091 return 0; 1092 } 1093 int pos = lastValidOffset.getChildView().getStartOffset(); 1094 int index = getViewIndex(pos, Position.Bias.Forward); 1095 return index; 1096 } else if (targetOffset > lastValidOffset.getMajorOffset()) { 1097 // roll offset calculations forward 1098 return updateChildOffsets(targetOffset); 1099 } else { 1100 // no changes prior to the needed offset 1101 // this should be a binary search 1102 float offs = 0f; 1103 for (int i = 0; i < n; i++) { 1104 ChildState cs = getChildState(i); 1105 float nextOffs = offs + cs.getMajorSpan(); 1106 if (targetOffset < nextOffs) { 1107 return i; 1108 } 1109 offs = nextOffs; 1110 } 1111 } 1112 } 1113 return n - 1; 1114 } 1115 1116 /** 1117 * Move the location of the last offset calculation forward 1118 * to the desired offset. 1119 */ 1120 int updateChildOffsets(float targetOffset) { 1121 int n = getViewCount(); 1122 int targetIndex = n - 1; 1123 int pos = lastValidOffset.getChildView().getStartOffset(); 1124 int startIndex = getViewIndex(pos, Position.Bias.Forward); 1125 float start = lastValidOffset.getMajorOffset(); 1126 float lastOffset = start; 1127 for (int i = startIndex; i < n; i++) { 1128 ChildState cs = getChildState(i); 1129 cs.setMajorOffset(lastOffset); 1130 lastOffset += cs.getMajorSpan(); 1131 if (targetOffset < lastOffset) { 1132 targetIndex = i; 1133 lastValidOffset = cs; 1134 break; 1135 } 1136 } 1137 1138 return targetIndex; 1139 } 1140 1141 /** 1142 * Move the location of the last offset calculation forward 1143 * to the desired index. 1144 */ 1145 void updateChildOffsetsToIndex(int index) { 1146 int pos = lastValidOffset.getChildView().getStartOffset(); 1147 int startIndex = getViewIndex(pos, Position.Bias.Forward); 1148 float lastOffset = lastValidOffset.getMajorOffset(); 1149 for (int i = startIndex; i <= index; i++) { 1150 ChildState cs = getChildState(i); 1151 cs.setMajorOffset(lastOffset); 1152 lastOffset += cs.getMajorSpan(); 1153 } 1154 } 1155 1156 boolean intersectsClip(Shape childAlloc, Rectangle clip) { 1157 Rectangle cs = (childAlloc instanceof Rectangle) ? 1158 (Rectangle) childAlloc : childAlloc.getBounds(); 1159 if (cs.intersects(clip)) { 1160 // Make sure that lastAlloc also contains childAlloc, 1161 // this will be false if haven't yet flushed changes. 1162 return lastAlloc.intersects(cs); 1163 } 1164 return false; 1165 } 1166 1167 /** 1168 * The location of the last offset calculation 1169 * that is valid. 1170 */ 1171 protected ChildState lastValidOffset; 1172 1173 /** 1174 * The last seen allocation (for repainting when changes 1175 * are flushed upward). 1176 */ 1177 protected Rectangle lastAlloc; 1178 1179 /** 1180 * A shape to use for the child allocation to avoid 1181 * creating a lot of garbage. 1182 */ 1183 protected Rectangle childAlloc; 1184 } 1185 1186 /** 1187 * A record representing the layout state of a 1188 * child view. It is runnable as a task on another 1189 * thread. All access to the child view that is 1190 * based upon a read-lock on the model should synchronize 1191 * on this object (i.e. The layout thread and the GUI 1192 * thread can both have a read lock on the model at the 1193 * same time and are not protected from each other). 1194 * Access to a child view hierarchy is serialized via 1195 * synchronization on the ChildState instance. 1196 * @since 1.3 1197 */ 1198 public class ChildState implements Runnable { 1199 1200 /** 1201 * Construct a child status. This needs to start 1202 * out as fairly large so we don't falsely begin with 1203 * the idea that all of the children are visible. 1204 * @since 1.4 1205 */ 1206 public ChildState(View v) { 1207 child = v; 1208 minorValid = false; 1209 majorValid = false; 1210 childSizeValid = false; 1211 child.setParent(AsyncBoxView.this); 1212 } 1213 1214 /** 1215 * Fetch the child view this record represents 1216 */ 1217 public View getChildView() { 1218 return child; 1219 } 1220 1221 /** 1222 * Update the child state. This should be 1223 * called by the thread that desires to spend 1224 * time updating the child state (intended to 1225 * be the layout thread). 1226 * <p> 1227 * This acquires a read lock on the associated 1228 * document for the duration of the update to 1229 * ensure the model is not changed while it is 1230 * operating. The first thing to do would be 1231 * to see if any work actually needs to be done. 1232 * The following could have conceivably happened 1233 * while the state was waiting to be updated: 1234 * <ol> 1235 * <li>The child may have been removed from the 1236 * view hierarchy. 1237 * <li>The child may have been updated by a 1238 * higher priority operation (i.e. the child 1239 * may have become visible). 1240 * </ol> 1241 */ 1242 public void run () { 1243 AbstractDocument doc = (AbstractDocument) getDocument(); 1244 try { 1245 doc.readLock(); 1246 if (minorValid && majorValid && childSizeValid) { 1247 // nothing to do 1248 return; 1249 } 1250 if (child.getParent() == AsyncBoxView.this) { 1251 // this may overwrite anothers threads cached 1252 // value for actively changing... but that just 1253 // means it won't use the cache if there is an 1254 // overwrite. 1255 synchronized(AsyncBoxView.this) { 1256 changing = this; 1257 } 1258 updateChild(); 1259 synchronized(AsyncBoxView.this) { 1260 changing = null; 1261 } 1262 1263 // setting the child size on the minor axis 1264 // may have caused it to change it's preference 1265 // along the major axis. 1266 updateChild(); 1267 } 1268 } finally { 1269 doc.readUnlock(); 1270 } 1271 } 1272 1273 void updateChild() { 1274 boolean minorUpdated = false; 1275 synchronized(this) { 1276 if (! minorValid) { 1277 int minorAxis = getMinorAxis(); 1278 min = child.getMinimumSpan(minorAxis); 1279 pref = child.getPreferredSpan(minorAxis); 1280 max = child.getMaximumSpan(minorAxis); 1281 minorValid = true; 1282 minorUpdated = true; 1283 } 1284 } 1285 if (minorUpdated) { 1286 minorRequirementChange(this); 1287 } 1288 1289 boolean majorUpdated = false; 1290 float delta = 0.0f; 1291 synchronized(this) { 1292 if (! majorValid) { 1293 float old = span; 1294 span = child.getPreferredSpan(axis); 1295 delta = span - old; 1296 majorValid = true; 1297 majorUpdated = true; 1298 } 1299 } 1300 if (majorUpdated) { 1301 majorRequirementChange(this, delta); 1302 locator.childChanged(this); 1303 } 1304 1305 synchronized(this) { 1306 if (! childSizeValid) { 1307 float w; 1308 float h; 1309 if (axis == X_AXIS) { 1310 w = span; 1311 h = getMinorSpan(); 1312 } else { 1313 w = getMinorSpan(); 1314 h = span; 1315 } 1316 childSizeValid = true; 1317 child.setSize(w, h); 1318 } 1319 } 1320 1321 } 1322 1323 /** 1324 * What is the span along the minor axis. 1325 */ 1326 public float getMinorSpan() { 1327 if (max < minorSpan) { 1328 return max; 1329 } 1330 // make it the target width, or as small as it can get. 1331 return Math.max(min, minorSpan); 1332 } 1333 1334 /** 1335 * What is the offset along the minor axis 1336 */ 1337 public float getMinorOffset() { 1338 if (max < minorSpan) { 1339 // can't make the child this wide, align it 1340 float align = child.getAlignment(getMinorAxis()); 1341 return ((minorSpan - max) * align); 1342 } 1343 return 0f; 1344 } 1345 1346 /** 1347 * What is the span along the major axis. 1348 */ 1349 public float getMajorSpan() { 1350 return span; 1351 } 1352 1353 /** 1354 * Get the offset along the major axis 1355 */ 1356 public float getMajorOffset() { 1357 return offset; 1358 } 1359 1360 /** 1361 * This method should only be called by the ChildLocator, 1362 * it is simply a convenient place to hold the cached 1363 * location. 1364 */ 1365 public void setMajorOffset(float offs) { 1366 offset = offs; 1367 } 1368 1369 /** 1370 * Mark preferences changed for this child. 1371 * 1372 * @param width true if the width preference has changed 1373 * @param height true if the height preference has changed 1374 * @see javax.swing.JComponent#revalidate 1375 */ 1376 public void preferenceChanged(boolean width, boolean height) { 1377 if (axis == X_AXIS) { 1378 if (width) { 1379 majorValid = false; 1380 } 1381 if (height) { 1382 minorValid = false; 1383 } 1384 } else { 1385 if (width) { 1386 minorValid = false; 1387 } 1388 if (height) { 1389 majorValid = false; 1390 } 1391 } 1392 childSizeValid = false; 1393 } 1394 1395 /** 1396 * Has the child view been laid out. 1397 */ 1398 public boolean isLayoutValid() { 1399 return (minorValid && majorValid && childSizeValid); 1400 } 1401 1402 // minor axis 1403 private float min; 1404 private float pref; 1405 private float max; 1406 private boolean minorValid; 1407 1408 // major axis 1409 private float span; 1410 private float offset; 1411 private boolean majorValid; 1412 1413 private View child; 1414 private boolean childSizeValid; 1415 } 1416 1417 /** 1418 * Task to flush requirement changes upward 1419 */ 1420 class FlushTask implements Runnable { 1421 1422 public void run() { 1423 flushRequirementChanges(); 1424 } 1425 1426 } 1427 1428 }