001package jmri.util.swing; 002 003import java.awt.*; 004import javax.swing.JScrollPane; 005import javax.swing.SwingUtilities; 006 007/** 008 * FlowLayout subclass that fully supports wrapping of components. 009 */ 010public class WrapLayout extends FlowLayout { 011 012 /** 013 * Constructs a new <code>WrapLayout</code> with a left alignment and a 014 * default 5-unit horizontal and vertical gap. 015 */ 016 public WrapLayout() { 017 super(); 018 } 019 020 /** 021 * Constructs a new <code>FlowLayout</code> with the specified alignment and 022 * a default 5-unit horizontal and vertical gap. The value of the alignment 023 * argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>, 024 * or <code>WrapLayout</code>. 025 * 026 * @param align the alignment value 027 */ 028 public WrapLayout(int align) { 029 super(align); 030 } 031 032 /** 033 * Creates a new flow layout manager with the indicated alignment and the 034 * indicated horizontal and vertical gaps. 035 * <p> 036 * The value of the alignment argument must be one of 037 * <code>WrapLayout</code>, <code>WrapLayout</code>, or 038 * <code>WrapLayout</code>. 039 * 040 * @param align the alignment value 041 * @param hgap the horizontal gap between components 042 * @param vgap the vertical gap between components 043 */ 044 public WrapLayout(int align, int hgap, int vgap) { 045 super(align, hgap, vgap); 046 } 047 048 /** 049 * Returns the preferred dimensions for this layout given the 050 * <i>visible</i> components in the specified target container. 051 * 052 * @param target the component which needs to be laid out 053 * @return the preferred dimensions to lay out the subcomponents of the 054 * specified container 055 */ 056 @Override 057 public Dimension preferredLayoutSize(Container target) { 058 return layoutSize(target, true); 059 } 060 061 /** 062 * Returns the minimum dimensions needed to layout the <i>visible</i> 063 * components contained in the specified target container. 064 * 065 * @param target the component which needs to be laid out 066 * @return the minimum dimensions to lay out the subcomponents of the 067 * specified container 068 */ 069 @Override 070 public Dimension minimumLayoutSize(Container target) { 071 Dimension minimum = layoutSize(target, false); 072 minimum.width -= (getHgap() + 1); 073 return minimum; 074 } 075 076 /** 077 * Returns the minimum or preferred dimension needed to layout the target 078 * container. 079 * 080 * @param target target to get layout size for 081 * @param preferred should preferred size be calculated 082 * @return the dimension to layout the target container 083 */ 084 private Dimension layoutSize(Container target, boolean preferred) { 085 synchronized (target.getTreeLock()) { 086 // Each row must fit with the width allocated to the containter. 087 // When the container width = 0, the preferred width of the container 088 // has not yet been calculated so lets ask for the maximum. 089 090 Container container = target; 091 092 while (container.getSize().width == 0 && container.getParent() != null) { 093 container = container.getParent(); 094 } 095 096 int targetWidth = container.getSize().width; 097 098 if (targetWidth == 0) { 099 targetWidth = Integer.MAX_VALUE; 100 } 101 102 int hgap = getHgap(); 103 int vgap = getVgap(); 104 Insets insets = target.getInsets(); 105 int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); 106 int maxWidth = targetWidth - horizontalInsetsAndGap; 107 108 // Fit components into the allowed width 109 Dimension dim = new Dimension(0, 0); 110 int rowWidth = 0; 111 int rowHeight = 0; 112 113 int nmembers = target.getComponentCount(); 114 115 for (int i = 0; i < nmembers; i++) { 116 Component m = target.getComponent(i); 117 118 if (m.isVisible()) { 119 Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); 120 121 // Can't add the component to current row. Start a new row. 122 if (rowWidth + d.width > maxWidth) { 123 addRow(dim, rowWidth, rowHeight); 124 rowWidth = 0; 125 rowHeight = 0; 126 } 127 128 // Add a horizontal gap for all components after the first 129 if (rowWidth != 0) { 130 rowWidth += hgap; 131 } 132 133 rowWidth += d.width; 134 rowHeight = Math.max(rowHeight, d.height); 135 } 136 } 137 138 addRow(dim, rowWidth, rowHeight); 139 140 dim.width += horizontalInsetsAndGap; 141 dim.height += insets.top + insets.bottom + vgap * 2; 142 143 // When using a scroll pane or the DecoratedLookAndFeel we need to 144 // make sure the preferred size is less than the size of the 145 // target containter so shrinking the container size works 146 // correctly. Removing the horizontal gap is an easy way to do this. 147 Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); 148 149 if (scrollPane != null && target.isValid()) { 150 dim.width -= (hgap + 1); 151 } 152 153 return dim; 154 } 155 } 156 157 /* 158 * A new row has been completed. Use the dimensions of this row 159 * to update the preferred size for the container. 160 * 161 * @param dim update the width and height when appropriate 162 * @param rowWidth the width of the row to add 163 * @param rowHeight the height of the row to add 164 */ 165 private void addRow(Dimension dim, int rowWidth, int rowHeight) { 166 dim.width = Math.max(dim.width, rowWidth); 167 168 if (dim.height > 0) { 169 dim.height += getVgap(); 170 } 171 172 dim.height += rowHeight; 173 } 174}