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}