001/* AsyncBoxView.java -- A box view that performs layout asynchronously 002 Copyright (C) 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text; 040 041import java.awt.Component; 042import java.awt.Graphics; 043import java.awt.Rectangle; 044import java.awt.Shape; 045import java.util.ArrayList; 046 047import javax.swing.event.DocumentEvent; 048import javax.swing.text.Position.Bias; 049 050/** 051 * A {@link View} implementation that lays out its child views in a box, either 052 * vertically or horizontally. The difference to {@link BoxView} is that the 053 * layout is performed in an asynchronous manner. This helps to keep the 054 * eventqueue free from non-GUI related tasks. 055 * 056 * This view is currently not used in standard text components. In order to 057 * use it you would have to implement a special {@link EditorKit} with a 058 * {@link ViewFactory} that returns this view. For example: 059 * 060 * <pre> 061 * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory 062 * { 063 * public View create(Element el) 064 * { 065 * if (el.getName().equals(AbstractDocument.SectionElementName)) 066 * return new AsyncBoxView(el, View.Y_AXIS); 067 * return super.getViewFactory().create(el); 068 * } 069 * public ViewFactory getViewFactory() { 070 * return this; 071 * } 072 * } 073 * </pre> 074 * 075 * @author Roman Kennke (kennke@aicas.com) 076 * 077 * @since 1.3 078 */ 079public class AsyncBoxView 080 extends View 081{ 082 083 /** 084 * Manages the effective position of child views. That keeps the visible 085 * layout stable while the AsyncBoxView might be changing until the layout 086 * thread decides to publish the new layout. 087 */ 088 public class ChildLocator 089 { 090 091 /** 092 * The last valid location. 093 */ 094 protected ChildState lastValidOffset; 095 096 /** 097 * The last allocation. 098 */ 099 protected Rectangle lastAlloc; 100 101 /** 102 * A Rectangle used for child allocation calculation to avoid creation 103 * of lots of garbage Rectangle objects. 104 */ 105 protected Rectangle childAlloc; 106 107 /** 108 * Creates a new ChildLocator. 109 */ 110 public ChildLocator() 111 { 112 lastAlloc = new Rectangle(); 113 childAlloc = new Rectangle(); 114 } 115 116 /** 117 * Receives notification that a child has changed. This is called by 118 * child state objects that have changed it's major span. 119 * 120 * This sets the {@link #lastValidOffset} field to <code>cs</code> if 121 * the new child state's view start offset is smaller than the start offset 122 * of the current child state's view or when <code>lastValidOffset</code> 123 * is <code>null</code>. 124 * 125 * @param cs the child state object that has changed 126 */ 127 public synchronized void childChanged(ChildState cs) 128 { 129 if (lastValidOffset == null 130 || cs.getChildView().getStartOffset() 131 < lastValidOffset.getChildView().getStartOffset()) 132 { 133 lastValidOffset = cs; 134 } 135 } 136 137 /** 138 * Returns the view index of the view that occupies the specified area, or 139 * <code>-1</code> if there is no such child view. 140 * 141 * @param x the x coordinate (relative to <code>a</code>) 142 * @param y the y coordinate (relative to <code>a</code>) 143 * @param a the current allocation of this view 144 * 145 * @return the view index of the view that occupies the specified area, or 146 * <code>-1</code> if there is no such child view 147 */ 148 public int getViewIndexAtPoint(float x, float y, Shape a) 149 { 150 setAllocation(a); 151 float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x 152 : y - lastAlloc.y; 153 int index = getViewIndexAtVisualOffset(targetOffset); 154 return index; 155 } 156 157 /** 158 * Returns the current allocation for a child view. This updates the 159 * offsets for all children <em>before</em> the requested child view. 160 * 161 * @param index the index of the child view 162 * @param a the current allocation of this view 163 * 164 * @return the current allocation for a child view 165 */ 166 public synchronized Shape getChildAllocation(int index, Shape a) 167 { 168 if (a == null) 169 return null; 170 setAllocation(a); 171 ChildState cs = getChildState(index); 172 if (cs.getChildView().getStartOffset() 173 > lastValidOffset.getChildView().getStartOffset()) 174 { 175 updateChildOffsetsToIndex(index); 176 } 177 Shape ca = getChildAllocation(index); 178 return ca; 179 } 180 181 /** 182 * Paints all child views. 183 * 184 * @param g the graphics context to use 185 */ 186 public synchronized void paintChildren(Graphics g) 187 { 188 Rectangle clip = g.getClipBounds(); 189 float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x 190 : clip.y - lastAlloc.y; 191 int index = getViewIndexAtVisualOffset(targetOffset); 192 int n = getViewCount(); 193 float offs = getChildState(index).getMajorOffset(); 194 for (int i = index; i < n; i++) 195 { 196 ChildState cs = getChildState(i); 197 cs.setMajorOffset(offs); 198 Shape ca = getChildAllocation(i); 199 if (ca.intersects(clip)) 200 { 201 synchronized (cs) 202 { 203 View v = cs.getChildView(); 204 v.paint(g, ca); 205 } 206 } 207 else 208 { 209 // done painting intersection 210 break; 211 } 212 offs += cs.getMajorSpan(); 213 } 214 } 215 216 /** 217 * Returns the current allocation of the child view with the specified 218 * index. Note that this will <em>not</em> update any location information. 219 * 220 * @param index the index of the requested child view 221 * 222 * @return the current allocation of the child view with the specified 223 * index 224 */ 225 protected Shape getChildAllocation(int index) 226 { 227 ChildState cs = getChildState(index); 228 if (! cs.isLayoutValid()) 229 cs.run(); 230 231 if (getMajorAxis() == X_AXIS) 232 { 233 childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset(); 234 childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset(); 235 childAlloc.width = (int) cs.getMajorSpan(); 236 childAlloc.height = (int) cs.getMinorSpan(); 237 } 238 else 239 { 240 childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset(); 241 childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset(); 242 childAlloc.height = (int) cs.getMajorSpan(); 243 childAlloc.width = (int) cs.getMinorSpan(); 244 } 245 return childAlloc; 246 } 247 248 /** 249 * Sets the current allocation for this view. 250 * 251 * @param a the allocation to set 252 */ 253 protected void setAllocation(Shape a) 254 { 255 if (a instanceof Rectangle) 256 lastAlloc.setBounds((Rectangle) a); 257 else 258 lastAlloc.setBounds(a.getBounds()); 259 260 setSize(lastAlloc.width, lastAlloc.height); 261 } 262 263 /** 264 * Returns the index of the view at the specified offset along the major 265 * layout axis. 266 * 267 * @param targetOffset the requested offset 268 * 269 * @return the index of the view at the specified offset along the major 270 * layout axis 271 */ 272 protected int getViewIndexAtVisualOffset(float targetOffset) 273 { 274 int n = getViewCount(); 275 if (n > 0) 276 { 277 if (lastValidOffset == null) 278 lastValidOffset = getChildState(0); 279 if (targetOffset > majorSpan) 280 return 0; 281 else if (targetOffset > lastValidOffset.getMajorOffset()) 282 return updateChildOffsets(targetOffset); 283 else 284 { 285 float offs = 0f; 286 for (int i = 0; i < n; i++) 287 { 288 ChildState cs = getChildState(i); 289 float nextOffs = offs + cs.getMajorSpan(); 290 if (targetOffset < nextOffs) 291 return i; 292 offs = nextOffs; 293 } 294 } 295 } 296 return n - 1; 297 } 298 299 /** 300 * Updates all the child view offsets up to the specified targetOffset. 301 * 302 * @param targetOffset the offset up to which the child view offsets are 303 * updated 304 * 305 * @return the index of the view at the specified offset 306 */ 307 private int updateChildOffsets(float targetOffset) 308 { 309 int n = getViewCount(); 310 int targetIndex = n - 1; 311 int pos = lastValidOffset.getChildView().getStartOffset(); 312 int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); 313 float start = lastValidOffset.getMajorOffset(); 314 float lastOffset = start; 315 for (int i = startIndex; i < n; i++) 316 { 317 ChildState cs = getChildState(i); 318 cs.setMajorOffset(lastOffset); 319 lastOffset += cs.getMajorSpan(); 320 if (targetOffset < lastOffset) 321 { 322 targetIndex = i; 323 lastValidOffset = cs; 324 break; 325 } 326 } 327 return targetIndex; 328 } 329 330 /** 331 * Updates the offsets of the child views up to the specified index. 332 * 333 * @param index the index up to which the offsets are updated 334 */ 335 private void updateChildOffsetsToIndex(int index) 336 { 337 int pos = lastValidOffset.getChildView().getStartOffset(); 338 int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); 339 float lastOffset = lastValidOffset.getMajorOffset(); 340 for (int i = startIndex; i <= index; i++) 341 { 342 ChildState cs = getChildState(i); 343 cs.setMajorOffset(lastOffset); 344 lastOffset += cs.getMajorSpan(); 345 } 346 } 347 } 348 349 /** 350 * Represents the layout state of a child view. 351 */ 352 public class ChildState 353 implements Runnable 354 { 355 356 /** 357 * The child view for this state record. 358 */ 359 private View childView; 360 361 /** 362 * Indicates if the minor axis requirements of this child view are valid 363 * or not. 364 */ 365 private boolean minorValid; 366 367 /** 368 * Indicates if the major axis requirements of this child view are valid 369 * or not. 370 */ 371 private boolean majorValid; 372 373 /** 374 * Indicates if the current child size is valid. This is package private 375 * to avoid synthetic accessor method. 376 */ 377 boolean childSizeValid; 378 379 /** 380 * The child views minimumSpan. This is package private to avoid accessor 381 * method. 382 */ 383 float minimum; 384 385 /** 386 * The child views preferredSpan. This is package private to avoid accessor 387 * method. 388 */ 389 float preferred; 390 391 /** 392 * The current span of the child view along the major axis. 393 */ 394 private float majorSpan; 395 396 /** 397 * The current offset of the child view along the major axis. 398 */ 399 private float majorOffset; 400 401 /** 402 * The current span of the child view along the minor axis. 403 */ 404 private float minorSpan; 405 406 /** 407 * The current offset of the child view along the major axis. 408 */ 409 private float minorOffset; 410 411 /** 412 * The child views maximumSpan. 413 */ 414 private float maximum; 415 416 /** 417 * Creates a new <code>ChildState</code> object for the specified child 418 * view. 419 * 420 * @param view the child view for which to create the state record 421 */ 422 public ChildState(View view) 423 { 424 childView = view; 425 } 426 427 /** 428 * Returns the child view for which this <code>ChildState</code> represents 429 * the layout state. 430 * 431 * @return the child view for this child state object 432 */ 433 public View getChildView() 434 { 435 return childView; 436 } 437 438 /** 439 * Returns <code>true</code> if the current layout information is valid, 440 * <code>false</code> otherwise. 441 * 442 * @return <code>true</code> if the current layout information is valid, 443 * <code>false</code> otherwise 444 */ 445 public boolean isLayoutValid() 446 { 447 return minorValid && majorValid && childSizeValid; 448 } 449 450 /** 451 * Performs the layout update for the child view managed by this 452 * <code>ChildState</code>. 453 */ 454 public void run() 455 { 456 Document doc = getDocument(); 457 if (doc instanceof AbstractDocument) 458 { 459 AbstractDocument abstractDoc = (AbstractDocument) doc; 460 abstractDoc.readLock(); 461 } 462 463 try 464 { 465 466 if (!(minorValid && majorValid && childSizeValid) 467 && childView.getParent() == AsyncBoxView.this) 468 { 469 synchronized(AsyncBoxView.this) 470 { 471 changing = this; 472 } 473 update(); 474 synchronized(AsyncBoxView.this) 475 { 476 changing = null; 477 } 478 // Changing the major axis may cause the minor axis 479 // requirements to have changed, so we need to do this again. 480 update(); 481 } 482 } 483 finally 484 { 485 if (doc instanceof AbstractDocument) 486 { 487 AbstractDocument abstractDoc = (AbstractDocument) doc; 488 abstractDoc.readUnlock(); 489 } 490 } 491 } 492 493 /** 494 * Performs the actual update after the run methods has made its checks 495 * and locked the document. 496 */ 497 private void update() 498 { 499 int majorAxis = getMajorAxis(); 500 boolean minorUpdated = false; 501 synchronized (this) 502 { 503 if (! minorValid) 504 { 505 int minorAxis = getMinorAxis(); 506 minimum = childView.getMinimumSpan(minorAxis); 507 preferred = childView.getPreferredSpan(minorAxis); 508 maximum = childView.getMaximumSpan(minorAxis); 509 minorValid = true; 510 minorUpdated = true; 511 } 512 } 513 if (minorUpdated) 514 minorRequirementChange(this); 515 516 boolean majorUpdated = false; 517 float delta = 0.0F; 518 synchronized (this) 519 { 520 if (! majorValid) 521 { 522 float oldSpan = majorSpan; 523 majorSpan = childView.getPreferredSpan(majorAxis); 524 delta = majorSpan - oldSpan; 525 majorValid = true; 526 majorUpdated = true; 527 } 528 } 529 if (majorUpdated) 530 { 531 majorRequirementChange(this, delta); 532 locator.childChanged(this); 533 } 534 535 synchronized (this) 536 { 537 if (! childSizeValid) 538 { 539 float w; 540 float h; 541 if (majorAxis == X_AXIS) 542 { 543 w = majorSpan; 544 h = getMinorSpan(); 545 } 546 else 547 { 548 w = getMinorSpan(); 549 h = majorSpan; 550 } 551 childSizeValid = true; 552 childView.setSize(w, h); 553 } 554 } 555 } 556 557 /** 558 * Returns the span of the child view along the minor layout axis. 559 * 560 * @return the span of the child view along the minor layout axis 561 */ 562 public float getMinorSpan() 563 { 564 float retVal; 565 if (maximum < minorSpan) 566 retVal = maximum; 567 else 568 retVal = Math.max(minimum, minorSpan); 569 return retVal; 570 } 571 572 /** 573 * Returns the offset of the child view along the minor layout axis. 574 * 575 * @return the offset of the child view along the minor layout axis 576 */ 577 public float getMinorOffset() 578 { 579 float retVal; 580 if (maximum < minorSpan) 581 { 582 float align = childView.getAlignment(getMinorAxis()); 583 retVal = ((minorSpan - maximum) * align); 584 } 585 else 586 retVal = 0f; 587 588 return retVal; 589 } 590 591 /** 592 * Returns the span of the child view along the major layout axis. 593 * 594 * @return the span of the child view along the major layout axis 595 */ 596 597 public float getMajorSpan() 598 { 599 return majorSpan; 600 } 601 602 /** 603 * Returns the offset of the child view along the major layout axis. 604 * 605 * @return the offset of the child view along the major layout axis 606 */ 607 public float getMajorOffset() 608 { 609 return majorOffset; 610 } 611 612 /** 613 * Sets the offset of the child view along the major layout axis. This 614 * should only be called by the ChildLocator of that child view. 615 * 616 * @param offset the offset to set 617 */ 618 public void setMajorOffset(float offset) 619 { 620 majorOffset = offset; 621 } 622 623 /** 624 * Mark the preferences changed for that child. This forwards to 625 * {@link AsyncBoxView#preferenceChanged}. 626 * 627 * @param width <code>true</code> if the width preference has changed 628 * @param height <code>true</code> if the height preference has changed 629 */ 630 public void preferenceChanged(boolean width, boolean height) 631 { 632 if (getMajorAxis() == X_AXIS) 633 { 634 if (width) 635 majorValid = false; 636 if (height) 637 minorValid = false; 638 } 639 else 640 { 641 if (width) 642 minorValid = false; 643 if (height) 644 majorValid = false; 645 } 646 childSizeValid = false; 647 } 648 } 649 650 /** 651 * Flushes the requirements changes upwards asynchronously. 652 */ 653 private class FlushTask implements Runnable 654 { 655 /** 656 * Starts the flush task. This obtains a readLock on the document 657 * and then flushes all the updates using 658 * {@link AsyncBoxView#flushRequirementChanges()} after updating the 659 * requirements. 660 */ 661 public void run() 662 { 663 try 664 { 665 // Acquire a lock on the document. 666 Document doc = getDocument(); 667 if (doc instanceof AbstractDocument) 668 { 669 AbstractDocument abstractDoc = (AbstractDocument) doc; 670 abstractDoc.readLock(); 671 } 672 673 int n = getViewCount(); 674 if (minorChanged && (n > 0)) 675 { 676 LayoutQueue q = getLayoutQueue(); 677 ChildState min = getChildState(0); 678 ChildState pref = getChildState(0); 679 for (int i = 1; i < n; i++) 680 { 681 ChildState cs = getChildState(i); 682 if (cs.minimum > min.minimum) 683 min = cs; 684 if (cs.preferred > pref.preferred) 685 pref = cs; 686 } 687 synchronized (AsyncBoxView.this) 688 { 689 minReq = min; 690 prefReq = pref; 691 } 692 } 693 694 flushRequirementChanges(); 695 } 696 finally 697 { 698 // Release the lock on the document. 699 Document doc = getDocument(); 700 if (doc instanceof AbstractDocument) 701 { 702 AbstractDocument abstractDoc = (AbstractDocument) doc; 703 abstractDoc.readUnlock(); 704 } 705 } 706 } 707 708 } 709 710 /** 711 * The major layout axis. 712 */ 713 private int majorAxis; 714 715 /** 716 * The top inset. 717 */ 718 private float topInset; 719 720 /** 721 * The bottom inset. 722 */ 723 private float bottomInset; 724 725 /** 726 * The left inset. 727 */ 728 private float leftInset; 729 730 /** 731 * Indicates if the major span should be treated as beeing estimated or not. 732 */ 733 private boolean estimatedMajorSpan; 734 735 /** 736 * The right inset. 737 */ 738 private float rightInset; 739 740 /** 741 * The children and their layout statistics. 742 */ 743 private ArrayList childStates; 744 745 /** 746 * The currently changing child state. May be null if there is no child state 747 * updating at the moment. This is package private to avoid a synthetic 748 * accessor method inside ChildState. 749 */ 750 ChildState changing; 751 752 /** 753 * Represents the minimum requirements. This is used in 754 * {@link #getMinimumSpan(int)}. 755 */ 756 ChildState minReq; 757 758 /** 759 * Represents the minimum requirements. This is used in 760 * {@link #getPreferredSpan(int)}. 761 */ 762 ChildState prefReq; 763 764 /** 765 * Indicates that the major axis requirements have changed. 766 */ 767 private boolean majorChanged; 768 769 /** 770 * Indicates that the minor axis requirements have changed. This is package 771 * private to avoid synthetic accessor method. 772 */ 773 boolean minorChanged; 774 775 /** 776 * The current span along the major layout axis. This is package private to 777 * avoid synthetic accessor method. 778 */ 779 float majorSpan; 780 781 /** 782 * The current span along the minor layout axis. This is package private to 783 * avoid synthetic accessor method. 784 */ 785 float minorSpan; 786 787 /** 788 * This tasked is placed on the layout queue to flush updates up to the 789 * parent view. 790 */ 791 private Runnable flushTask; 792 793 /** 794 * The child locator for this view. 795 */ 796 protected ChildLocator locator; 797 798 /** 799 * Creates a new <code>AsyncBoxView</code> that represents the specified 800 * element and layouts its children along the specified axis. 801 * 802 * @param elem the element 803 * @param axis the layout axis 804 */ 805 public AsyncBoxView(Element elem, int axis) 806 { 807 super(elem); 808 majorAxis = axis; 809 childStates = new ArrayList(); 810 flushTask = new FlushTask(); 811 locator = new ChildLocator(); 812 minorSpan = Short.MAX_VALUE; 813 } 814 815 /** 816 * Returns the major layout axis. 817 * 818 * @return the major layout axis 819 */ 820 public int getMajorAxis() 821 { 822 return majorAxis; 823 } 824 825 /** 826 * Returns the minor layout axis, that is the axis orthogonal to the major 827 * layout axis. 828 * 829 * @return the minor layout axis 830 */ 831 public int getMinorAxis() 832 { 833 return majorAxis == X_AXIS ? Y_AXIS : X_AXIS; 834 } 835 836 /** 837 * Returns the view at the specified <code>index</code>. 838 * 839 * @param index the index of the requested child view 840 * 841 * @return the view at the specified <code>index</code> 842 */ 843 public View getView(int index) 844 { 845 View view = null; 846 synchronized(childStates) 847 { 848 if ((index >= 0) && (index < childStates.size())) 849 { 850 ChildState cs = (ChildState) childStates.get(index); 851 view = cs.getChildView(); 852 } 853 } 854 return view; 855 } 856 857 /** 858 * Returns the number of child views. 859 * 860 * @return the number of child views 861 */ 862 public int getViewCount() 863 { 864 synchronized(childStates) 865 { 866 return childStates.size(); 867 } 868 } 869 870 /** 871 * Returns the view index of the child view that represents the specified 872 * model position. 873 * 874 * @param pos the model position for which we search the view index 875 * @param bias the bias 876 * 877 * @return the view index of the child view that represents the specified 878 * model position 879 */ 880 public int getViewIndex(int pos, Position.Bias bias) 881 { 882 int retVal = -1; 883 884 if (bias == Position.Bias.Backward) 885 pos = Math.max(0, pos - 1); 886 887 // TODO: A possible optimization would be to implement a binary search 888 // here. 889 int numChildren = childStates.size(); 890 if (numChildren > 0) 891 { 892 for (int i = 0; i < numChildren; ++i) 893 { 894 View child = ((ChildState) childStates.get(i)).getChildView(); 895 if (child.getStartOffset() <= pos && child.getEndOffset() > pos) 896 { 897 retVal = i; 898 break; 899 } 900 } 901 } 902 return retVal; 903 } 904 905 /** 906 * Returns the top inset. 907 * 908 * @return the top inset 909 */ 910 public float getTopInset() 911 { 912 return topInset; 913 } 914 915 /** 916 * Sets the top inset. 917 * 918 * @param top the top inset 919 */ 920 public void setTopInset(float top) 921 { 922 topInset = top; 923 } 924 925 /** 926 * Returns the bottom inset. 927 * 928 * @return the bottom inset 929 */ 930 public float getBottomInset() 931 { 932 return bottomInset; 933 } 934 935 /** 936 * Sets the bottom inset. 937 * 938 * @param bottom the bottom inset 939 */ 940 public void setBottomInset(float bottom) 941 { 942 bottomInset = bottom; 943 } 944 945 /** 946 * Returns the left inset. 947 * 948 * @return the left inset 949 */ 950 public float getLeftInset() 951 { 952 return leftInset; 953 } 954 955 /** 956 * Sets the left inset. 957 * 958 * @param left the left inset 959 */ 960 public void setLeftInset(float left) 961 { 962 leftInset = left; 963 } 964 965 /** 966 * Returns the right inset. 967 * 968 * @return the right inset 969 */ 970 public float getRightInset() 971 { 972 return rightInset; 973 } 974 975 /** 976 * Sets the right inset. 977 * 978 * @param right the right inset 979 */ 980 public void setRightInset(float right) 981 { 982 rightInset = right; 983 } 984 985 /** 986 * Loads the child views of this view. This is triggered by 987 * {@link #setParent(View)}. 988 * 989 * @param f the view factory to build child views with 990 */ 991 protected void loadChildren(ViewFactory f) 992 { 993 Element e = getElement(); 994 int n = e.getElementCount(); 995 if (n > 0) 996 { 997 View[] added = new View[n]; 998 for (int i = 0; i < n; i++) 999 { 1000 added[i] = f.create(e.getElement(i)); 1001 } 1002 replace(0, 0, added); 1003 } 1004 } 1005 1006 /** 1007 * Returns the span along an axis that is taken up by the insets. 1008 * 1009 * @param axis the axis 1010 * 1011 * @return the span along an axis that is taken up by the insets 1012 * 1013 * @since 1.4 1014 */ 1015 protected float getInsetSpan(int axis) 1016 { 1017 float span; 1018 if (axis == X_AXIS) 1019 span = leftInset + rightInset; 1020 else 1021 span = topInset + bottomInset; 1022 return span; 1023 } 1024 1025 /** 1026 * Sets the <code>estimatedMajorSpan</code> property that determines if 1027 * the major span should be treated as beeing estimated. 1028 * 1029 * @param estimated if the major span should be treated as estimated or not 1030 * 1031 * @since 1.4 1032 */ 1033 protected void setEstimatedMajorSpan(boolean estimated) 1034 { 1035 estimatedMajorSpan = estimated; 1036 } 1037 1038 /** 1039 * Determines whether the major span should be treated as estimated or as 1040 * beeing accurate. 1041 * 1042 * @return <code>true</code> if the major span should be treated as 1043 * estimated, <code>false</code> if the major span should be treated 1044 * as accurate 1045 * 1046 * @since 1.4 1047 */ 1048 protected boolean getEstimatedMajorSpan() 1049 { 1050 return estimatedMajorSpan; 1051 } 1052 1053 /** 1054 * Receives notification from the child states that the requirements along 1055 * the minor axis have changed. 1056 * 1057 * @param cs the child state from which this notification is messaged 1058 */ 1059 protected synchronized void minorRequirementChange(ChildState cs) 1060 { 1061 minorChanged = true; 1062 } 1063 1064 /** 1065 * Receives notification from the child states that the requirements along 1066 * the major axis have changed. 1067 * 1068 * @param cs the child state from which this notification is messaged 1069 */ 1070 protected void majorRequirementChange(ChildState cs, float delta) 1071 { 1072 if (! estimatedMajorSpan) 1073 majorSpan += delta; 1074 majorChanged = true; 1075 } 1076 1077 /** 1078 * Sets the parent for this view. This calls loadChildren if 1079 * <code>parent</code> is not <code>null</code> and there have not been any 1080 * child views initializes. 1081 * 1082 * @param parent the new parent view; <code>null</code> if this view is 1083 * removed from the view hierarchy 1084 * 1085 * @see View#setParent(View) 1086 */ 1087 public void setParent(View parent) 1088 { 1089 super.setParent(parent); 1090 if ((parent != null) && (getViewCount() == 0)) 1091 { 1092 ViewFactory f = getViewFactory(); 1093 loadChildren(f); 1094 } 1095 } 1096 1097 /** 1098 * Sets the size of this view. This is ususally called before {@link #paint} 1099 * is called to make sure the view has a valid layout. 1100 * 1101 * This implementation queues layout requests for every child view if the 1102 * minor axis span has changed. (The major axis span is requested to never 1103 * change for this view). 1104 * 1105 * @param width the width of the view 1106 * @param height the height of the view 1107 */ 1108 public void setSize(float width, float height) 1109 { 1110 float targetSpan; 1111 if (majorAxis == X_AXIS) 1112 targetSpan = height - getTopInset() - getBottomInset(); 1113 else 1114 targetSpan = width - getLeftInset() - getRightInset(); 1115 1116 if (targetSpan != minorSpan) 1117 { 1118 minorSpan = targetSpan; 1119 1120 int n = getViewCount(); 1121 LayoutQueue q = getLayoutQueue(); 1122 for (int i = 0; i < n; i++) 1123 { 1124 ChildState cs = getChildState(i); 1125 cs.childSizeValid = false; 1126 q.addTask(cs); 1127 } 1128 q.addTask(flushTask); 1129 } 1130 } 1131 1132 /** 1133 * Replaces child views with new child views. 1134 * 1135 * This creates ChildState objects for all the new views and adds layout 1136 * requests for them to the layout queue. 1137 * 1138 * @param offset the offset at which to remove/insert 1139 * @param length the number of child views to remove 1140 * @param views the new child views to insert 1141 */ 1142 public void replace(int offset, int length, View[] views) 1143 { 1144 synchronized(childStates) 1145 { 1146 LayoutQueue q = getLayoutQueue(); 1147 for (int i = 0; i < length; i++) 1148 childStates.remove(offset); 1149 1150 for (int i = views.length - 1; i >= 0; i--) 1151 childStates.add(offset, createChildState(views[i])); 1152 1153 // We need to go through the new child states _after_ they have been 1154 // added to the childStates list, otherwise the layout tasks may find 1155 // an incomplete child list. That means we have to loop through 1156 // them again, but what else can we do? 1157 if (views.length != 0) 1158 { 1159 for (int i = 0; i < views.length; i++) 1160 { 1161 ChildState cs = (ChildState) childStates.get(i + offset); 1162 cs.getChildView().setParent(this); 1163 q.addTask(cs); 1164 } 1165 q.addTask(flushTask); 1166 } 1167 } 1168 } 1169 1170 /** 1171 * Paints the view. This requests the {@link ChildLocator} to paint the views 1172 * after setting the allocation on it. 1173 * 1174 * @param g the graphics context to use 1175 * @param s the allocation for this view 1176 */ 1177 public void paint(Graphics g, Shape s) 1178 { 1179 synchronized (locator) 1180 { 1181 locator.setAllocation(s); 1182 locator.paintChildren(g); 1183 } 1184 } 1185 1186 /** 1187 * Returns the preferred span of this view along the specified layout axis. 1188 * 1189 * @return the preferred span of this view along the specified layout axis 1190 */ 1191 public float getPreferredSpan(int axis) 1192 { 1193 float retVal; 1194 if (majorAxis == axis) 1195 retVal = majorSpan; 1196 1197 else if (prefReq != null) 1198 { 1199 View child = prefReq.getChildView(); 1200 retVal = child.getPreferredSpan(axis); 1201 } 1202 1203 // If we have no layout information yet, then return insets + 30 as 1204 // an estimation. 1205 else 1206 { 1207 if (axis == X_AXIS) 1208 retVal = getLeftInset() + getRightInset() + 30; 1209 else 1210 retVal = getTopInset() + getBottomInset() + 30; 1211 } 1212 return retVal; 1213 } 1214 1215 /** 1216 * Maps a model location to view coordinates. 1217 * 1218 * @param pos the model location 1219 * @param a the current allocation of this view 1220 * @param b the bias 1221 * 1222 * @return the view allocation for the specified model location 1223 */ 1224 public Shape modelToView(int pos, Shape a, Bias b) 1225 throws BadLocationException 1226 { 1227 int index = getViewIndexAtPosition(pos, b); 1228 Shape ca = locator.getChildAllocation(index, a); 1229 1230 ChildState cs = getChildState(index); 1231 synchronized (cs) 1232 { 1233 View cv = cs.getChildView(); 1234 Shape v = cv.modelToView(pos, ca, b); 1235 return v; 1236 } 1237 } 1238 1239 /** 1240 * Maps view coordinates to a model location. 1241 * 1242 * @param x the x coordinate (relative to <code>a</code>) 1243 * @param y the y coordinate (relative to <code>a</code>) 1244 * @param b holds the bias of the model location on method exit 1245 * 1246 * @return the model location for the specified view location 1247 */ 1248 public int viewToModel(float x, float y, Shape a, Bias[] b) 1249 { 1250 int pos; 1251 int index; 1252 Shape ca; 1253 1254 synchronized (locator) 1255 { 1256 index = locator.getViewIndexAtPoint(x, y, a); 1257 ca = locator.getChildAllocation(index, a); 1258 } 1259 1260 ChildState cs = getChildState(index); 1261 synchronized (cs) 1262 { 1263 View v = cs.getChildView(); 1264 pos = v.viewToModel(x, y, ca, b); 1265 } 1266 return pos; 1267 } 1268 1269 /** 1270 * Returns the child allocation for the child view with the specified 1271 * <code>index</code>. 1272 * 1273 * @param index the index of the child view 1274 * @param a the current allocation of this view 1275 * 1276 * @return the allocation of the child view 1277 */ 1278 public Shape getChildAllocation(int index, Shape a) 1279 { 1280 Shape ca = locator.getChildAllocation(index, a); 1281 return ca; 1282 } 1283 1284 /** 1285 * Returns the maximum span of this view along the specified axis. 1286 * This is implemented to return the <code>preferredSpan</code> for the 1287 * major axis (that means the box can't be resized along the major axis) and 1288 * {@link Short#MAX_VALUE} for the minor axis. 1289 * 1290 * @param axis the axis 1291 * 1292 * @return the maximum span of this view along the specified axis 1293 */ 1294 public float getMaximumSpan(int axis) 1295 { 1296 float max; 1297 if (axis == majorAxis) 1298 max = getPreferredSpan(axis); 1299 else 1300 max = Short.MAX_VALUE; 1301 return max; 1302 } 1303 1304 /** 1305 * Returns the minimum span along the specified axis. 1306 */ 1307 public float getMinimumSpan(int axis) 1308 { 1309 float min; 1310 if (axis == majorAxis) 1311 min = getPreferredSpan(axis); 1312 else 1313 { 1314 if (minReq != null) 1315 { 1316 View child = minReq.getChildView(); 1317 min = child.getMinimumSpan(axis); 1318 } 1319 else 1320 { 1321 // No layout information yet. Return insets + 5 as some kind of 1322 // estimation. 1323 if (axis == X_AXIS) 1324 min = getLeftInset() + getRightInset() + 5; 1325 else 1326 min = getTopInset() + getBottomInset() + 5; 1327 } 1328 } 1329 return min; 1330 } 1331 1332 /** 1333 * Receives notification that one of the child views has changed its 1334 * layout preferences along one or both axis. 1335 * 1336 * This queues a layout request for that child view if necessary. 1337 * 1338 * @param view the view that has changed its preferences 1339 * @param width <code>true</code> if the width preference has changed 1340 * @param height <code>true</code> if the height preference has changed 1341 */ 1342 public synchronized void preferenceChanged(View view, boolean width, 1343 boolean height) 1344 { 1345 if (view == null) 1346 getParent().preferenceChanged(this, width, height); 1347 else 1348 { 1349 if (changing != null) 1350 { 1351 View cv = changing.getChildView(); 1352 if (cv == view) 1353 { 1354 changing.preferenceChanged(width, height); 1355 return; 1356 } 1357 } 1358 int index = getViewIndexAtPosition(view.getStartOffset(), 1359 Position.Bias.Forward); 1360 ChildState cs = getChildState(index); 1361 cs.preferenceChanged(width, height); 1362 LayoutQueue q = getLayoutQueue(); 1363 q.addTask(cs); 1364 q.addTask(flushTask); 1365 } 1366 } 1367 1368 /** 1369 * Updates the layout for this view. This is implemented to trigger 1370 * {@link ChildLocator#childChanged} for the changed view, if there is 1371 * any. 1372 * 1373 * @param ec the element change, may be <code>null</code> if there were 1374 * no changes to the element of this view 1375 * @param e the document event 1376 * @param a the current allocation of this view 1377 */ 1378 protected void updateLayout(DocumentEvent.ElementChange ec, 1379 DocumentEvent e, Shape a) 1380 { 1381 if (ec != null) 1382 { 1383 int index = Math.max(ec.getIndex() - 1, 0); 1384 ChildState cs = getChildState(index); 1385 locator.childChanged(cs); 1386 } 1387 } 1388 1389 1390 /** 1391 * Returns the <code>ChildState</code> object associated with the child view 1392 * at the specified <code>index</code>. 1393 * 1394 * @param index the index of the child view for which to query the state 1395 * 1396 * @return the child state for the specified child view 1397 */ 1398 protected ChildState getChildState(int index) { 1399 synchronized (childStates) 1400 { 1401 return (ChildState) childStates.get(index); 1402 } 1403 } 1404 1405 /** 1406 * Returns the <code>LayoutQueue</code> used for layouting the box view. 1407 * This simply returns {@link LayoutQueue#getDefaultQueue()}. 1408 * 1409 * @return the <code>LayoutQueue</code> used for layouting the box view 1410 */ 1411 protected LayoutQueue getLayoutQueue() 1412 { 1413 return LayoutQueue.getDefaultQueue(); 1414 } 1415 1416 /** 1417 * Returns the child view index of the view that represents the specified 1418 * position in the document model. 1419 * 1420 * @param pos the position in the model 1421 * @param b the bias 1422 * 1423 * @return the child view index of the view that represents the specified 1424 * position in the document model 1425 */ 1426 protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b) 1427 { 1428 if (b == Position.Bias.Backward) 1429 pos = Math.max(0, pos - 1); 1430 Element elem = getElement(); 1431 return elem.getElementIndex(pos); 1432 } 1433 1434 /** 1435 * Creates a <code>ChildState</code> object for the specified view. 1436 * 1437 * @param v the view for which to create a child state object 1438 * 1439 * @return the created child state 1440 */ 1441 protected ChildState createChildState(View v) 1442 { 1443 return new ChildState(v); 1444 } 1445 1446 /** 1447 * Flushes the requirements changes upwards to the parent view. This is 1448 * called from the layout thread. 1449 */ 1450 protected synchronized void flushRequirementChanges() 1451 { 1452 if (majorChanged || minorChanged) 1453 { 1454 View p = getParent(); 1455 if (p != null) 1456 { 1457 boolean horizontal; 1458 boolean vertical; 1459 if (majorAxis == X_AXIS) 1460 { 1461 horizontal = majorChanged; 1462 vertical = minorChanged; 1463 } 1464 else 1465 { 1466 vertical = majorChanged; 1467 horizontal = minorChanged; 1468 } 1469 1470 p.preferenceChanged(this, horizontal, vertical); 1471 majorChanged = false; 1472 minorChanged = false; 1473 1474 Component c = getContainer(); 1475 if (c != null) 1476 c.repaint(); 1477 } 1478 } 1479 } 1480}