1 /* $Id$ */ 2 /*************************************************************************** 3 * (C) Copyright 2003-2010 - Stendhal * 4 *************************************************************************** 5 *************************************************************************** 6 * * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2 of the License, or * 10 * (at your option) any later version. * 11 * * 12 ***************************************************************************/ 13 package games.stendhal.client.gui.layout; 14 15 import java.awt.Component; 16 import java.awt.Container; 17 import java.awt.Dimension; 18 import java.awt.Insets; 19 import java.awt.LayoutManager2; 20 import java.util.Arrays; 21 import java.util.EnumSet; 22 import java.util.IdentityHashMap; 23 import java.util.Map; 24 25 import javax.swing.JComponent; 26 27 /** 28 * A simple layout manager that does what BoxLayout fails to do, 29 * and provides a predictable layout with few hidden interactions. 30 * <p> 31 * Minimum size is properly supported within reasonable limits. 32 * <p> 33 * Maximum size has only a soft support, i.e. maximum size works 34 * as a limiting preferred size, even if the child component itself 35 * would suggest a larger size. 36 * <p> 37 * Component alignment is supported in the direction perpendicular to the 38 * layout direction. 39 * <p> 40 * SBoxLayout supports constraints of type {@link SLayout}. A single constraint 41 * flag can passed as the second parameter to 42 * <code>Container.add(Component c, Object constraint)</code>. Constraints can 43 * be combined using the object returned by {@link #constraint(SLayout...)} as 44 * the constraints object. 45 */ 46 public class SBoxLayout implements LayoutManager2 { 47 /* 48 * Implementation considerations: 49 * - MaxSize is not fully supported. It could be done the same way as MinSize is now 50 * 51 * - Expanding and contracting the components is done by same amount for all 52 * the components that are resized (unless forbidden by minimum/maximum size 53 * constraints). Should it be relative to the component size instead? 54 * 55 * Further refinement: 56 * Layout management in swing is dumb (and not just buggy for the fundamental 57 * layout managers). Any change in a child component will result in revalidation 58 * of the whole component tree until the first validation root (which normally 59 * is the top level window). This is a huge performance problem as changing a 60 * single number in the StatsPanel will result in the whole window layout being 61 * redone - and 3 changes in a second is normal. 62 * Therefore, investigate if it's feasible to make a component that acts as 63 * a validation root, and passes the invalidation upwards only if its size changes. 64 */ 65 66 public static final boolean VERTICAL = false; 67 public static final boolean HORIZONTAL = true; 68 69 /** Common padding width where padding or border is wanted. */ 70 public static final int COMMON_PADDING = 5; 71 72 /** 73 * Create a constraints object. 74 * 75 * @param flags constraint flags 76 * @return constraints object 77 */ constraint(SLayout .... flags)78 public static Object constraint(SLayout ... flags) { 79 EnumSet<SLayout> obj = EnumSet.noneOf(SLayout.class); 80 for (SLayout flag : flags) { 81 obj.add(flag); 82 } 83 return obj; 84 } 85 86 private static final Direction horizontalDirection = new HDirection(); 87 private static final Direction verticalDirection = new VDirection(); 88 89 /** 90 * Layout constraints of the child components. 91 */ 92 private final Map<Component, EnumSet<SLayout>> constraints; 93 94 /** 95 * The direction object. All the dimension calculations are 96 * delegated to this. 97 */ 98 private final Direction d; 99 100 /** Preciously calculated dimension data, or <code>null</code> if it has been invalidated. */ 101 private Dimension cachedMinimum, cachedMaximum, cachedPreferred; 102 103 /** Amount of axially expandable components. */ 104 private int expandable; 105 /** Amount of padding between components. */ 106 private int padding; 107 108 /** 109 * Create a new SBoxLayout. 110 * 111 * @param direction layout direction 112 */ SBoxLayout(boolean direction)113 public SBoxLayout(boolean direction) { 114 constraints = new IdentityHashMap<>(); 115 if (direction == VERTICAL) { 116 d = verticalDirection; 117 } else { 118 d = horizontalDirection; 119 } 120 } 121 122 /** 123 * Create a new SBoxLayout with padding between components. 124 * 125 * @param direction layout direction 126 * @param padding component padding in pixels 127 */ SBoxLayout(boolean direction, int padding)128 public SBoxLayout(boolean direction, int padding) { 129 this(direction); 130 setPadding(padding); 131 } 132 133 /** 134 * Set the padding between the components. Typically you should use either 135 * 0 (the default), or COMMON_PADDING for consistent look. For the padding 136 * around everything use appropriate empty border instead. 137 * 138 * @param padding pixel width of the padding 139 */ setPadding(int padding)140 public final void setPadding(int padding) { 141 this.padding = padding; 142 } 143 144 @Override addLayoutComponent(Component component, Object flags)145 public void addLayoutComponent(Component component, Object flags) { 146 EnumSet<SLayout> constraintFlags = EnumSet.noneOf(SLayout.class); 147 if (flags != null) { 148 if (flags instanceof SLayout) { 149 constraintFlags.add(d.translate((SLayout) flags)); 150 } else if (flags instanceof EnumSet<?>) { 151 translateFlags((EnumSet<?>) flags, constraintFlags); 152 } else { 153 throw new IllegalArgumentException("Invalid flags: " + flags); 154 } 155 156 // Keep count of expandable items 157 if (constraintFlags.contains(SLayout.EXPAND_AXIAL)) { 158 expandable++; 159 } 160 } 161 constraints.put(component, constraintFlags); 162 } 163 164 @SuppressWarnings("unlikely-arg-type") translateFlags(EnumSet<?> rawFlags, EnumSet<SLayout> constraintFlags)165 private void translateFlags(EnumSet<?> rawFlags, EnumSet<SLayout> constraintFlags) { 166 Arrays.stream(SLayout.values()).filter(rawFlags::contains).map(d::translate).forEach(constraintFlags::add); 167 } 168 169 /* 170 * (non-Javadoc) 171 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, java.awt.Component) 172 */ 173 @Override addLayoutComponent(String id, Component component)174 public void addLayoutComponent(String id, Component component) { 175 } 176 177 /** 178 * Add to the primary dimension. 179 * 180 * @param result the dimension to be expanded 181 * @param length the expanding amount 182 */ addToPrimary(Dimension result, int length)183 private void addToPrimary(Dimension result, int length) { 184 d.setPrimary(result, d.getPrimary(result) + length); 185 } 186 187 /** 188 * Expand a <code>Dimension</code> so that it can include 189 * another <code>Dimension</code>. 190 * 191 * @param result The dimension to be expanded 192 * @param dim limiting dimension 193 */ expand(Dimension result, Dimension dim)194 private void expand(Dimension result, Dimension dim) { 195 result.width = Math.max(result.width, dim.width); 196 result.height = Math.max(result.height, dim.height); 197 } 198 199 /* 200 * (non-Javadoc) 201 * @see java.awt.LayoutManager2#getLayoutAlignmentX(java.awt.Container) 202 */ 203 @Override getLayoutAlignmentX(Container target)204 public float getLayoutAlignmentX(Container target) { 205 // The specs don't tell what this actually should do 206 return 0; 207 } 208 209 /* 210 * (non-Javadoc) 211 * @see java.awt.LayoutManager2#getLayoutAlignmentY(java.awt.Container) 212 */ 213 @Override getLayoutAlignmentY(Container target)214 public float getLayoutAlignmentY(Container target) { 215 // The specs don't tell what this actually should do 216 return 0; 217 } 218 219 /** 220 * Get a components preferred dimensions restricted by 221 * the maximum and minimum constraints. 222 * 223 * @param comp the component to examine 224 * @return constraint adjusted preferred dimensions 225 */ getPreferred(Component comp)226 private Dimension getPreferred(Component comp) { 227 Dimension dim = comp.getPreferredSize(); 228 229 expand(dim, comp.getMinimumSize()); 230 shrink(dim, comp.getMaximumSize()); 231 232 return dim; 233 } 234 235 /* 236 * (non-Javadoc) 237 * @see java.awt.LayoutManager2#invalidateLayout(java.awt.Container) 238 */ 239 @Override invalidateLayout(Container target)240 public void invalidateLayout(Container target) { 241 cachedMinimum = null; 242 cachedMaximum = null; 243 cachedPreferred = null; 244 } 245 246 /* 247 * (non-Javadoc) 248 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container) 249 */ 250 @Override layoutContainer(Container parent)251 public void layoutContainer(Container parent) { 252 // Maximum dimensions available for use 253 Dimension realDim = parent.getSize(); 254 Insets insets = parent.getInsets(); 255 256 Dimension preferred = preferredLayoutSize(parent); 257 258 final int stretch = d.getPrimary(realDim) - d.getPrimary(preferred); 259 260 // remove the insets for the actual area we have in use 261 realDim.width -= insets.left + insets.right; 262 realDim.height -= insets.top + insets.bottom; 263 264 Dimension position = new Dimension(insets.left, insets.top); 265 266 // Check the conditions, and pass the task to the proper layout method 267 if (stretch >= 0) { 268 layoutSufficientSpace(parent, realDim, position, stretch); 269 } else { 270 Dimension minDim = minimumLayoutSize(parent); 271 // remove the insets for the actual area we have in use 272 minDim.width -= insets.left + insets.right; 273 minDim.height -= insets.top + insets.bottom; 274 final int squeeze = d.getPrimary(realDim) - d.getPrimary(minDim); 275 if (squeeze < 0) { 276 layoutUnderMinimumSpace(parent, realDim, position); 277 } else { 278 layoutWithSqueeze(parent, realDim, position, stretch); 279 } 280 } 281 } 282 283 /** 284 * Lay out the components, when we have sufficient space to do so. 285 * 286 * @param parent the parent container of the components 287 * @param realDim the dimensions of the space in use 288 * @param startPosition top left corner 289 * @param stretch the total amount to stretch the components 290 */ layoutSufficientSpace(Container parent, Dimension realDim, Dimension startPosition, int stretch)291 private void layoutSufficientSpace(Container parent, Dimension realDim, 292 Dimension startPosition, int stretch) { 293 int remainingStretch = stretch; 294 int remainingExpandable = expandable; 295 296 // Easy - we got at least the dimensions we asked for 297 for (Component c : parent.getComponents()) { 298 // Skip hidden components 299 if (c.isVisible()) { 300 Dimension cPref = getPreferred(c); 301 shrink(cPref, realDim); 302 int xAlign = 0; 303 int yAlign = 0; 304 305 EnumSet<SLayout> flags = constraints.get(c); 306 if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) { 307 d.setSecondary(cPref, d.getSecondary(realDim)); 308 } else { 309 xAlign = getXAlignment(c, realDim); 310 yAlign = getYAlignment(c, realDim); 311 } 312 313 if ((remainingStretch > 0) && flags.contains(SLayout.EXPAND_AXIAL)) { 314 // Stretch the components that allow it, if needed 315 int add = Math.max(1, remainingStretch / remainingExpandable); 316 addToPrimary(cPref, add); 317 remainingStretch -= add; 318 remainingExpandable--; 319 } 320 c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, cPref.width, cPref.height); 321 322 // Move the coordinates of the next component by the size of the 323 // previous + padding 324 shiftByPrimary(startPosition, cPref); 325 addToPrimary(startPosition, padding); 326 } 327 } 328 } 329 330 /** 331 * Lay out the components in a smaller space than the specified minimum. 332 * Just gives the components their required minimum, until the space runs out. 333 * 334 * @param parent the parent container of the components 335 * @param realDim the dimensions of the space in use 336 * @param startPosition top left corner 337 */ layoutUnderMinimumSpace(Container parent, Dimension realDim, Dimension startPosition)338 private void layoutUnderMinimumSpace(Container parent, Dimension realDim, Dimension startPosition) { 339 for (Component c : parent.getComponents()) { 340 // Skip hidden components 341 if (c.isVisible()) { 342 Dimension compSize = c.getMinimumSize(); 343 shrink(compSize, realDim); 344 int xAlign = 0; 345 int yAlign = 0; 346 347 EnumSet<SLayout> flags = constraints.get(c); 348 if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) { 349 d.setSecondary(compSize, d.getSecondary(realDim)); 350 } else { 351 xAlign = getXAlignment(c, realDim); 352 yAlign = getYAlignment(c, realDim); 353 } 354 355 int shrink = d.getPrimary(realDim) - d.getPrimary(compSize) - d.getPrimary(startPosition); 356 if (shrink < 0) { 357 // avoid < 0 sizes 358 shrink = Math.max(-d.getPrimary(compSize), shrink); 359 addToPrimary(compSize, shrink); 360 } 361 c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, compSize.width, compSize.height); 362 363 // Move the coordinates of the next component by the size of the 364 // previous + padding 365 shiftByPrimary(startPosition, compSize); 366 addToPrimary(startPosition, padding); 367 } 368 } 369 } 370 371 /** 372 * Lay out in sufficient, but smaller space than preferred. 373 * Respects the minimum dimensions, and squeezes only the components 374 * that do not break that constraint. 375 * 376 * @param parent the parent container of the components 377 * @param realDim the dimensions of the space in use 378 * @param startPosition top left corner 379 * @param stretch the total amount to stretch the components (negative) 380 */ layoutWithSqueeze(Container parent, Dimension realDim, Dimension startPosition, int stretch)381 private void layoutWithSqueeze(Container parent, Dimension realDim, 382 Dimension startPosition, int stretch) { 383 /* 384 * We can squeeze the components without violating the constraints, 385 * but calculations take a bit of effort. 386 */ 387 int numComponents = parent.getComponents().length; 388 int[] dim; 389 boolean[] violations = new boolean[numComponents]; 390 int numViolations = 0; 391 392 // Only visible components can be squeezed 393 int numVisible = 0; 394 for (Component c : parent.getComponents()) { 395 if (c.isVisible()) { 396 numVisible++; 397 } 398 } 399 400 int numSqueezable; 401 /* 402 * Start by trying to squeeze all, and then mark as 403 * incompressible the components whose size would become 404 * too small. Repeat until only those components that can 405 * take it, get squeezed. 406 */ 407 do { 408 dim = new int[numComponents]; 409 410 numSqueezable = numVisible; 411 for (boolean b : violations) { 412 if (b) { 413 numSqueezable--; 414 } 415 } 416 int remainingSqueeze = -stretch; 417 numViolations = 0; 418 419 for (int i = 0; i < numComponents; i++) { 420 Component c = parent.getComponents()[i]; 421 422 if (c.isVisible()) { 423 Dimension cPref = getPreferred(c); 424 425 int adjust = 0; 426 if (remainingSqueeze > 0 && !violations[i]) { 427 adjust = Math.max(1, remainingSqueeze / numSqueezable); 428 remainingSqueeze -= adjust; 429 numSqueezable--; 430 } 431 dim[i] = d.getPrimary(cPref) - adjust; 432 if (dim[i] < d.getPrimary(c.getMinimumSize())) { 433 violations[i] = true; 434 numViolations++; 435 } 436 } else { 437 dim[i] = 0; 438 } 439 } 440 } while (numViolations != 0); 441 442 // Done with the dimensions, now lay it out 443 for (int i = 0; i < numComponents; i++) { 444 Component c = parent.getComponents()[i]; 445 // skip hidden components 446 if (c.isVisible()) { 447 Dimension cPref = getPreferred(c); 448 shrink(cPref, realDim); 449 int xAlign = 0; 450 int yAlign = 0; 451 452 EnumSet<SLayout> flags = constraints.get(c); 453 if (flags.contains(SLayout.EXPAND_PERPENDICULAR)) { 454 d.setSecondary(cPref, d.getSecondary(realDim)); 455 } else { 456 xAlign = getXAlignment(c, realDim); 457 yAlign = getYAlignment(c, realDim); 458 } 459 d.setPrimary(cPref, dim[i]); 460 c.setBounds(startPosition.width + xAlign, startPosition.height + yAlign, cPref.width, cPref.height); 461 462 // Move the coordinates of the next component by the size of the 463 // previous + padding 464 shiftByPrimary(startPosition, cPref); 465 addToPrimary(startPosition, padding); 466 } 467 } 468 } 469 470 /** 471 * Get the x alignment of a child component. 472 * 473 * @param c component 474 * @param available available space 475 * @return x alignment in pixels 476 */ getXAlignment(Component c, Dimension available)477 private int getXAlignment(Component c, Dimension available) { 478 if (d == horizontalDirection) { 479 return 0; 480 } else { 481 return getPerpendicularAlignment(c, available); 482 } 483 } 484 485 /** 486 * Get the y alignment of a child component. 487 * 488 * @param c component 489 * @param available available space 490 * @return y alignment in pixels 491 */ getYAlignment(Component c, Dimension available)492 private int getYAlignment(Component c, Dimension available) { 493 if (d == horizontalDirection) { 494 return getPerpendicularAlignment(c, available); 495 } else { 496 return 0; 497 } 498 } 499 500 /** 501 * Get the pixel alignment of a component in the perpendicular direction. 502 * 503 * @param c component 504 * @param available size of the container of the component 505 * @return pixel alignment 506 */ getPerpendicularAlignment(Component c, Dimension available)507 private int getPerpendicularAlignment(Component c, Dimension available) { 508 int align = 0; 509 int extra = d.getSecondary(available) - d.getSecondary(c.getPreferredSize()); 510 if (extra > 0) { 511 align = (int) (extra * d.getComponentAlignment(c)); 512 } 513 514 return align; 515 } 516 517 /* 518 * (non-Javadoc) 519 * @see java.awt.LayoutManager2#maximumLayoutSize(java.awt.Container) 520 */ 521 @Override maximumLayoutSize(Container parent)522 public Dimension maximumLayoutSize(Container parent) { 523 /* 524 * The specs are *very* vague about what this should do (and 525 * helpfully name the parameter "target", sigh), but returning 526 * the max size of the whole layout seems to be what's wanted, 527 * and most other tries crash in a way or another. 528 */ 529 if (cachedMaximum != null) { 530 return new Dimension(cachedMaximum); 531 } 532 533 Dimension result = new Dimension(); 534 535 int numVisible = 0; 536 for (Component c : parent.getComponents()) { 537 // Skip hidden components 538 if (c.isVisible()) { 539 numVisible++; 540 d.addComponentDimensions(result, c.getMaximumSize()); 541 } 542 } 543 544 // Take padding in account 545 if (numVisible > 1) { 546 d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding)); 547 } 548 549 // Expand by the insets 550 Insets insets = parent.getInsets(); 551 result.width = safeAdd(result.width, insets.left + insets.right); 552 result.height = safeAdd(result.height, insets.top + insets.bottom); 553 554 cachedMaximum = result; 555 return result; 556 } 557 558 /* 559 * (non-Javadoc) 560 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container) 561 */ 562 @Override minimumLayoutSize(Container parent)563 public Dimension minimumLayoutSize(Container parent) { 564 if (cachedMinimum != null) { 565 return new Dimension(cachedMinimum); 566 } 567 Dimension result = new Dimension(); 568 569 int numVisible = 0; 570 for (Component c : parent.getComponents()) { 571 // Skip hidden components 572 if (c.isVisible()) { 573 numVisible++; 574 d.addComponentDimensions(result, c.getMinimumSize()); 575 } 576 } 577 578 // Take padding in account 579 if (numVisible > 1) { 580 d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding)); 581 } 582 583 // Expand by the insets 584 Insets insets = parent.getInsets(); 585 result.width += insets.left + insets.right; 586 result.height += insets.top + insets.bottom; 587 588 cachedMinimum = result; 589 return result; 590 } 591 592 /* 593 * (non-Javadoc) 594 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container) 595 */ 596 @Override preferredLayoutSize(Container parent)597 public Dimension preferredLayoutSize(Container parent) { 598 if (cachedPreferred != null) { 599 return new Dimension(cachedPreferred); 600 } 601 Dimension result = new Dimension(); 602 603 int numVisible = 0; 604 for (Component c : parent.getComponents()) { 605 // Skip hidden components 606 if (c.isVisible()) { 607 numVisible++; 608 d.addComponentDimensions(result, getPreferred(c)); 609 } 610 } 611 612 // Take padding in account 613 if (numVisible > 1) { 614 d.setPrimary(result, safeAdd(d.getPrimary(result), (numVisible - 1) * padding)); 615 } 616 617 // Expand by the insets 618 Insets insets = parent.getInsets(); 619 result.width = safeAdd(result.width, insets.left + insets.right); 620 result.height = safeAdd(result.height, insets.top + insets.bottom); 621 622 /* 623 * Check the constraints of the parent. Unlike the standard layout 624 * managers we should never suggest sizes outside the range that 625 * the parent should do. 626 */ 627 Dimension maxDim = parent.getMaximumSize(); 628 Dimension minDim = parent.getMinimumSize(); 629 630 /* 631 * Despite what said above, the return value can still be smaller 632 * than the specified minimum values, if the user has set 633 * inconsistent minimum and maximum constraints. 634 */ 635 expand(result, minDim); 636 shrink(result, maxDim); 637 638 cachedPreferred = result; 639 return result; 640 } 641 642 /* 643 * (non-Javadoc) 644 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component) 645 */ 646 @Override removeLayoutComponent(Component component)647 public void removeLayoutComponent(Component component) { 648 EnumSet<SLayout> constr = constraints.get(component); 649 if (constr.contains(SLayout.EXPAND_AXIAL)) { 650 expandable--; 651 } 652 653 constraints.remove(component); 654 } 655 656 /** 657 * Expand a dimension by the primary dimension of another. 658 * 659 * @param result the dimension to be expanded 660 * @param dim the expanding dimension 661 */ shiftByPrimary(Dimension result, Dimension dim)662 private void shiftByPrimary(Dimension result, Dimension dim) { 663 addToPrimary(result, d.getPrimary(dim)); 664 } 665 666 /** 667 * Shrink a <code>Dimension</code> so that it does not exceed 668 * the limits of another. 669 * 670 * @param result The dimension to be shrunk 671 * @param dim limiting dimension 672 */ shrink(Dimension result, Dimension dim)673 private void shrink(Dimension result, Dimension dim) { 674 result.width = Math.min(result.width, dim.width); 675 result.height = Math.min(result.height, dim.height); 676 } 677 678 /** 679 * A safe addition for dimensions. Returns Integer.MAX_VALUE if the 680 * addition would overflow. Some components do set that to their maximum 681 * size so they'd overflow if there are other components or insets. 682 * 683 * @param a 684 * @param b 685 * @return sum of a and b, or Integer.MAX_VALUE 686 */ safeAdd(int a, int b)687 private static int safeAdd(int a, int b) { 688 int tmp = a + b; 689 if (tmp >= 0) { 690 return tmp; 691 } else { 692 return Integer.MAX_VALUE; 693 } 694 } 695 696 /** 697 * An abstraction for various direction dependent operations. 698 */ 699 private interface Direction { 700 /** 701 * Translate X and Y to axial and perpendicular. 702 * @param dir 703 * @return SLayout 704 */ translate(SLayout dir)705 SLayout translate(SLayout dir); 706 707 /** 708 * Get the alignment of the component perpendicular to the layout axis. 709 * 710 * @param component component to examine 711 * @return component alignment 712 */ getComponentAlignment(Component component)713 float getComponentAlignment(Component component); 714 715 /** 716 * Expand a dimension by a component's dimensions. 717 * 718 * @param result the dimension to be expanded 719 * @param dim the dimensions to be added 720 */ addComponentDimensions(Dimension result, Dimension dim)721 void addComponentDimensions(Dimension result, Dimension dim); 722 723 /** 724 * Set primary dimension. 725 * 726 * @param result the dimension to be modified 727 * @param length 728 */ setPrimary(Dimension result, int length)729 void setPrimary(Dimension result, int length); 730 731 /** 732 * Set secondary dimension. 733 * 734 * @param result the dimension to be modified 735 * @param length 736 */ setSecondary(Dimension result, int length)737 void setSecondary(Dimension result, int length); 738 739 /** 740 * Get the dimension along the layout axis. 741 * 742 * @param dim 743 * @return primary dimension 744 */ getPrimary(Dimension dim)745 int getPrimary(Dimension dim); 746 747 /** 748 * Get the dimension perpendicular to the layout axis. 749 * 750 * @param dim 751 * @return secondary dimension 752 */ getSecondary(Dimension dim)753 int getSecondary(Dimension dim); 754 } 755 756 /** 757 * Horizontal direction calculations. 758 */ 759 private static class HDirection implements Direction { 760 @Override translate(SLayout dir)761 public SLayout translate(SLayout dir) { 762 if (dir == SLayout.EXPAND_X) { 763 dir = SLayout.EXPAND_AXIAL; 764 } else if (dir == SLayout.EXPAND_Y) { 765 dir = SLayout.EXPAND_PERPENDICULAR; 766 } 767 768 return dir; 769 } 770 771 @Override addComponentDimensions(Dimension result, Dimension dim)772 public void addComponentDimensions(Dimension result, Dimension dim) { 773 // Avoid integer overflows 774 result.width = safeAdd(result.width, dim.width); 775 result.height = Math.max(result.height, dim.height); 776 } 777 778 @Override getPrimary(Dimension dim)779 public int getPrimary(Dimension dim) { 780 return dim.width; 781 } 782 783 @Override getSecondary(Dimension dim)784 public int getSecondary(Dimension dim) { 785 return dim.height; 786 } 787 788 @Override setPrimary(Dimension result, int length)789 public void setPrimary(Dimension result, int length) { 790 result.width = length; 791 } 792 793 @Override setSecondary(Dimension result, int length)794 public void setSecondary(Dimension result, int length) { 795 result.height = length; 796 } 797 798 @Override getComponentAlignment(Component component)799 public float getComponentAlignment(Component component) { 800 return component.getAlignmentY(); 801 } 802 } 803 804 /** 805 * Vertical dimension calculations. 806 */ 807 private static class VDirection implements Direction { 808 @Override translate(SLayout dir)809 public SLayout translate(SLayout dir) { 810 if (dir == SLayout.EXPAND_X) { 811 dir = SLayout.EXPAND_PERPENDICULAR; 812 } else if (dir == SLayout.EXPAND_Y) { 813 dir = SLayout.EXPAND_AXIAL; 814 } 815 816 return dir; 817 } 818 819 @Override addComponentDimensions(Dimension result, Dimension dim)820 public void addComponentDimensions(Dimension result, Dimension dim) { 821 result.width = Math.max(result.width, dim.width); 822 // Avoid integer overflows 823 result.height = safeAdd(result.height, dim.height); 824 } 825 826 @Override getPrimary(Dimension dim)827 public int getPrimary(Dimension dim) { 828 return dim.height; 829 } 830 831 @Override getSecondary(Dimension dim)832 public int getSecondary(Dimension dim) { 833 return dim.width; 834 } 835 836 @Override setPrimary(Dimension result, int length)837 public void setPrimary(Dimension result, int length) { 838 result.height = length; 839 } 840 841 @Override setSecondary(Dimension result, int length)842 public void setSecondary(Dimension result, int length) { 843 result.width = length; 844 } 845 846 @Override getComponentAlignment(Component component)847 public float getComponentAlignment(Component component) { 848 return component.getAlignmentX(); 849 } 850 } 851 852 /** 853 * An utility component for layout. 854 */ 855 private static class Spring extends JComponent { 856 } 857 858 /** 859 * Add a utility component that expands by default, to a container using 860 * SBoxLayout. Adding it rather than just creating the component is a 861 * workaround for components not passing information about new subcomponents 862 * if the user explicitly specifies the constraints. 863 * 864 * @param target the container where to add a string to 865 * @return A spring with preferred dimensions 0, 0. 866 */ addSpring(Container target)867 public static JComponent addSpring(Container target) { 868 JComponent spring = new Spring(); 869 target.add(spring, SLayout.EXPAND_AXIAL); 870 871 return spring; 872 } 873 874 /** 875 * A convenience method for creating a container using SBoxLayout. 876 * 877 * @param direction layout direction 878 * @return A component using SBoxLayout 879 */ createContainer(boolean direction)880 public static JComponent createContainer(boolean direction) { 881 JComponent container = new Spring(); 882 container.setLayout(new SBoxLayout(direction)); 883 884 return container; 885 } 886 887 /** 888 * A convenience method for creating a container using SBoxLayout with 889 * padding between the components. 890 * 891 * @param direction layout direction 892 * @param padding padding in pixels between the components 893 * @return A component using SBoxLayout 894 */ createContainer(boolean direction, int padding)895 public static JComponent createContainer(boolean direction, int padding) { 896 JComponent container = new Spring(); 897 container.setLayout(new SBoxLayout(direction, padding)); 898 899 return container; 900 } 901 } 902