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