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}