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