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