// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: GroupLayoutManager.java,v 1.5 2007/08/01 11:36:34 spyromus Exp $ // package com.salas.bb.views.feeds.image; import java.awt.*; import java.util.HashSet; import java.util.Set; /** * Group layout manager. Layouts components in groups with group dividers. Group dividers * are also components, which are added to the container (and this layout) with the corresponding * constraint {@link GroupLayoutManager#DIVIDER}. */ public class GroupLayoutManager implements LayoutManager { /** Divider component string used to identify divider components. */ public static final String DIVIDER = "divider"; /** Default left, right, top and bottom gap. */ public static final int DEFAULT_GAP = 5; private final Set<Component> dividers; private int gapH; private int gapV; /** * Creates manager. */ public GroupLayoutManager() { dividers = new HashSet<Component>(); gapH = DEFAULT_GAP; gapV = DEFAULT_GAP; } /** * If the layout manager uses a per-component string, * adds the component <code>comp</code> to the layout, * associating it with the string specified by <code>name</code>. * * @param name the string to be associated with the component * @param comp the component to be added */ public void addLayoutComponent(String name, Component comp) { if (DIVIDER.equals(name)) dividers.add(comp); } /** * Removes the specified component from the layout. * * @param comp the component to be removed */ public void removeLayoutComponent(Component comp) { dividers.remove(comp); } /** * Calculates the minimum size dimensions for the specified * container, given the components it contains. * * @param parent the component to be laid out * @see #preferredLayoutSize */ public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } /** * Calculates the preferred size dimensions for the specified * container, given the components it contains. * * @param parent the container to be laid out * @see #minimumLayoutSize */ public Dimension preferredLayoutSize(Container parent) { // Find first item and get its width int itemWidth = evalItemWidth(parent); // Find the number of columns we can fit int cols = evalAvailableColumns(parent.getBounds().width, itemWidth, gapH); Dimension dim = new Dimension(0, 0); int itemsInRow = 0; int count = parent.getComponentCount(); int rowHeight = 0; int rowWidth = 0; boolean firstDiv = true; for (int i = 0; i < count; i++) { Component c = parent.getComponent(i); if (!c.isVisible()) continue; Dimension pref = c.getPreferredSize(); int height = pref.height; int width = pref.width; if (isDivider(c)) { // Add height and find the max between preferred div size and what we already have dim.height += height + (firstDiv ? 0 : gapV); dim.width = Math.max(dim.width, width); // Reset the flag to let the layout add v-gaps on every next div firstDiv = false; // Reset the items in row counter and the row height / width when starting new group itemsInRow = 0; rowHeight = 0; rowWidth = 0; } else { itemsInRow++; // Add h-gap for second and following items if (itemsInRow != 1) rowWidth += gapH; // Add item width and update the global width rowWidth += width; dim.width = Math.max(dim.width, rowWidth); // If current row height isn't enough to contain the item, increase it if (rowHeight < height) { // Add a vertical gap on first item in a row if (itemsInRow == 1) dim.height += gapV; dim.height += height - rowHeight; rowHeight = height; } // Reset the items in row counter and the row height / width when starting new row if (itemsInRow == cols) { itemsInRow = 0; rowHeight = 0; rowWidth = 0; } } } return dim; } /** * Returns the width of the first non-divider component. * * @param container container. * * @return width or <code>0</code> if no components present. */ private int evalItemWidth(Container container) { int count = container.getComponentCount(); int width = 0; for (int i = 0; width == 0 && i < count; i++) { Component comp = container.getComponent(i); if (!isDivider(comp)) width = comp.getPreferredSize().width; } return width; } /** * Lays out the specified container. * * @param parent the container to be laid out */ public void layoutContainer(Container parent) { int contWidth = parent.getSize().width; // Find first item and get its width int itemWidth = evalItemWidth(parent); // Find the number of columns we can fit int cols = evalAvailableColumns(contWidth, itemWidth, gapH); Rectangle bounds = new Rectangle(); boolean firstDiv = true; int y = 0; int col = 0; int rowHeight = 0; int count = parent.getComponentCount(); for (int i = 0; i < count; i++) { Component component = parent.getComponent(i); if (!component.isVisible()) continue; if (isDivider(component)) { // If there's a row height not added from the last unfinished // row of items, add it y += rowHeight; // Shift second and further dividers a bit with the v-gap if (!firstDiv) y += gapV; Dimension size = component.getPreferredSize(); bounds.x = 0; bounds.y = y; bounds.width = contWidth; bounds.height = size.height; y += size.height; col = 0; firstDiv = false; rowHeight = 0; } else { if (col == 0) y += gapV; Dimension size = component.getPreferredSize(); bounds.x = col * (itemWidth + gapH); bounds.y = y; bounds.width = itemWidth; bounds.height = size.height; // Update the maximum row height if necessary rowHeight = Math.max(rowHeight, size.height); col++; // When reached the end of the row, update the y-coord and properties if (col == cols) { y += rowHeight; rowHeight = 0; col = 0; } } // Set the bounds component.setBounds(bounds); } } /** * Returns <code>TRUE</code> if given component is marked as divider. * * @param component component. * * @return <code>TRUE</code> if given component is marked as divider. */ private boolean isDivider(Component component) { return dividers.contains(component); } /** * Calculates maximum item dimension. Dividers do not count. * * @param components components. * @param dividers map of dividers. * * @return dimension. */ static Dimension evalItemDimension(Component[] components, Set<Component> dividers) { Dimension dim = new Dimension(0, 0); for (Component component : components) { if (component.isVisible() && !dividers.contains(component)) { Dimension cdim = component.getPreferredSize(); dim.width = Math.max(dim.width, cdim.width); dim.height = Math.max(dim.height, cdim.height); } } return dim; } /** * Evaluates maximum number of columns ready to be allocated in a given width. * * @param width width of a container. * @param itemWidth width of a single item. * @param gapH gap between items. * * @return number of columns (greater or equal to 1). */ static int evalAvailableColumns(int width, int itemWidth, int gapH) { width -= gapH; itemWidth += gapH; int cols = width / itemWidth; if (cols < 1) cols = 1; return cols; } }