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