/* * Copyright (c) 2011, Nikolaus Moll * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the jo-widgets.org nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL jo-widgets.org BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.jowidgets.impl.layout.tablelayout; import java.util.LinkedList; import java.util.List; import org.jowidgets.api.layout.tablelayout.ITableLayout; import org.jowidgets.api.layout.tablelayout.ITableLayoutBuilder.Alignment; import org.jowidgets.api.layout.tablelayout.ITableLayoutBuilder.ColumnMode; import org.jowidgets.api.layout.tablelayout.ITableRowLayout; import org.jowidgets.api.layout.tablelayout.ITableRowLayoutFactoryBuilder; import org.jowidgets.common.types.Dimension; import org.jowidgets.common.types.Rectangle; public final class TableLayoutImpl implements ITableLayout { private final int columnCount; private final int layoutMinRows; private final int[] widths; private final int[] gaps; private final Alignment[] alignments; private final int verticalGap; private final ColumnMode[] modes; private int layoutHashCode; private int availableWidth; private final LinkedList<ITableRowLayout> layouters; private Dimension preferredSize; private boolean layouting; private boolean internalLayouting; private final List<Integer> growingColumns; private int usedIndex; public TableLayoutImpl( final int[] widths, final int[] gaps, final ColumnMode[] modes, final Alignment[] alignments, final int verticalGap, final int layoutMinRows) { this.columnCount = widths.length; this.modes = modes; this.widths = widths; this.gaps = gaps; this.verticalGap = verticalGap; this.layoutMinRows = layoutMinRows; this.alignments = alignments; layouters = new LinkedList<ITableRowLayout>(); layouting = true; growingColumns = new LinkedList<Integer>(); for (int i = 0; i < widths.length; i++) { if (modes[i] == ColumnMode.GROWING) { growingColumns.add(Integer.valueOf(i)); } } usedIndex = -1; internalLayouting = false; invalidate(); } @Override public void addRowLayout(final ITableRowLayout rowLayout) { layouters.add(rowLayout); if (usedIndex < 0) { getUsedIndex(); } } private void getUsedIndex() { for (int i = 0; i < layouters.size(); i++) { if (!layouters.get(i).isIgnoredInCalculations()) { usedIndex = i; return; } } usedIndex = -1; } @Override public void removeRowLayout(final ITableRowLayout rowLayout) { layouters.remove(rowLayout); getUsedIndex(); } @Override public int[] getWidths() { return widths; } @Override public int[] getGaps() { return gaps; } ColumnMode[] getModes() { return modes; } @Override public Alignment[] getAlignments() { return alignments; } @Override public void invalidate() { if (!layouting) { return; } calculateLayout(); } public int getAvailableWidth() { return availableWidth; } @Override public int getLayoutHashCode() { return layoutHashCode; } private int calculateHashCode() { int result = 17; result = 31 * result + layouters.size(); result = 31 * result + availableWidth; for (int i = 0; i < widths.length; i++) { result = 31 * result + widths[i]; } for (int i = 0; i < gaps.length; i++) { result = 31 * result + gaps[i]; } return result; } public void calculateLayout() { if (internalLayouting) { return; } if (layouters.size() < layoutMinRows) { preferredSize = new Dimension(0, 0); return; } int currentIndex = usedIndex; if (currentIndex < 0) { currentIndex = layouters.size() - 1; } final ITableRowLayout rowLayout = layouters.get(currentIndex); final Rectangle clientArea = rowLayout.getContainer().getClientArea(); availableWidth = clientArea.getWidth(); for (int i = 0; i < widths.length; i++) { if (modes[i] != ColumnMode.FIXED) { widths[i] = 0; } } int minHeight = 0; for (final ITableRowLayout layouter : layouters) { if (layouter.isIgnoredInCalculations()) { continue; } for (int i = 0; i < columnCount; i++) { if (modes[i] == ColumnMode.HIDDEN) { continue; } final Dimension size = layouter.getPreferredSize(i); if (size == null) { continue; } widths[i] = Math.max(widths[i], size.getWidth()); minHeight = Math.max(minHeight, size.getHeight()); } } int currentWidth = 0; for (final int width : widths) { currentWidth = currentWidth + width; } for (final int gap : gaps) { currentWidth = currentWidth + gap; } final int usedWidth = currentWidth; if (availableWidth > currentWidth) { final int additionalWidth = availableWidth - currentWidth; if (growingColumns.size() > 0) { int columnAdditional = additionalWidth / growingColumns.size(); for (final Integer index : growingColumns) { widths[index] = widths[index] + columnAdditional; currentWidth = currentWidth + columnAdditional; columnAdditional = Math.min(columnAdditional, availableWidth - currentWidth); } } } preferredSize = new Dimension(usedWidth, minHeight + 2 * verticalGap); final int newLayoutHashCode = calculateHashCode(); if (newLayoutHashCode != layoutHashCode) { layoutHashCode = newLayoutHashCode; internalLayouting = true; for (final ITableRowLayout layouter : layouters) { layouter.layout(); } internalLayouting = false; } } @Override public Dimension getPreferredSize() { if (preferredSize == null) { calculateLayout(); } return preferredSize; } public int getColumnCount() { return columnCount; } @Override public void beginLayout() { layouting = false; } @Override public void endLayout() { if (!layouting) { layoutHashCode = 0; layouting = true; calculateLayout(); } } @Override public void validate() { if (availableWidth > 0) { if (growingColumns.isEmpty() || layouters.size() < layoutMinRows) { return; } } if (layoutHashCode != calculateHashCode()) { invalidate(); return; } if (usedIndex >= 0 && layouters.size() > usedIndex) { final ITableRowLayout rowLayout = layouters.get(usedIndex); final Rectangle clientArea = rowLayout.getContainer().getClientArea(); if (availableWidth != clientArea.getWidth()) { invalidate(); } } else { invalidate(); } } @Override public ITableRowLayoutFactoryBuilder rowBuilder() { return new TableRowLayoutFactoryBuilder(this); } }