/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2013 University of Dundee. All rights reserved. * * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.agents.dataBrowser.layout; import java.awt.Component; import java.awt.Dimension; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageDisplay; import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageNode; import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageSet; import org.openmicroscopy.shoola.agents.dataBrowser.browser.Thumbnail; import org.openmicroscopy.shoola.agents.util.ViewerSorter; import omero.gateway.model.DataObject; /** * Helper class providing methods to lay out the nodes. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * @since OME3.0 */ public class LayoutUtils { /** The default number of items per row. */ public static final int DEFAULT_PER_ROW = 10; /** The minimum value for dimension.*/ private static final int MIN = 1; /** * Scales the thumbnail of the specified new node. * * @param newNode The new node to handle. * @param oldNode The original node. */ private static void scaleImage(ImageNode newNode, ImageNode oldNode) { Thumbnail th = newNode.getThumbnail(); th.scale(oldNode.getThumbnail().getScalingFactor()); } /** * Sets the size of the specified {@link ImageSet} node if it doesn't * have child. * * @param node The {@link ImageSet} node to lay out. */ static void noChildLayout(ImageDisplay node) { node.getInternalDesktop().setPreferredSize( node.getTitleBar().getMinimumSize()); node.setVisible(true); } /** * Finds out the dimensions of the largest child node in the specified * {@link ImageDisplay}. * This method calculates the area of each child node and then returns * the dimensions of the child having the largest area. * Note that the returned <code>Dimension</code> object can have * <code>0</code>-width and height. In particular this is always the * case if the specified parent <code>node</code> has no children. * * @param node The parent node. Mustn't be <code>null</code>. * @return The dimensions of the child having the largest area. * @see #max(Dimension, Dimension) */ static Dimension maxChildDim(ImageDisplay node) { Dimension maxDim = new Dimension(0, 0); Component[] comps = node.getInternalDesktop().getComponents(); Component c; for (int i = 0; i < comps.length; i++) { c = comps[i]; if (c instanceof ImageDisplay) { maxDim = max(maxDim, c.getPreferredSize()); } } return maxDim; } /** * Finds out the dimensions of the largest child node in the specified * collection. * This method calculates the area of each child node and then returns * the dimensions of the child having the largest area. * Note that the returned <code>Dimension</code> object can have * <code>0</code>-width and height. In particular this is always the * case if the specified parent <code>node</code> has no children. * * @param images The collection to handle. * @return See above, */ static Dimension maxChildDim(Collection images) { Dimension maxDim = new Dimension(0, 0); Iterator children = images.iterator(); ImageDisplay child; while (children.hasNext()) { child = (ImageDisplay) children.next(); maxDim = max(maxDim, child.getPreferredSize()); } return maxDim; //[0, 0] if no children. } /** * Lays out the specified images in a square grid. * * @param root The root node. * @param imageNodes The collection of images to lay out. */ static void doSquareGridLayout(ImageDisplay root, List imageNodes) { Iterator children = imageNodes.iterator(); ImageDisplay child; Dimension maxDim = maxChildDim(imageNodes); int n = imageNodes.size(); n = (int) Math.floor(Math.sqrt(n))+1; Dimension d; try { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (!children.hasNext()) //Done, less than n^2 children. return; //Go to finally. child = (ImageDisplay) children.next(); d = child.getPreferredSize(); child.setBounds(j*maxDim.width, i*maxDim.height, d.width, d.height); } } } finally { Rectangle bounds = root.getContentsBounds(); d = bounds.getSize(); root.getInternalDesktop().setSize(d); root.getInternalDesktop().setPreferredSize(d); } } /** * Lays out all child nodes in the specified parent <code>node</code> * in a square grid. * The size of each cell in the grid will be that of the largest child * in the parent <code>node</code>. * * @param node The parent node. Mustn't be <code>null</code>. * @param sorter The node sorter. * @param itemsPerRow The number of items per row. */ static void doSquareGridLayout(ImageDisplay node, ViewerSorter sorter, int itemsPerRow) { //First find out the max dim among children. Dimension maxDim = maxChildDim(node); //Then figure out the number of columns, which is the same as the //number of rows. int n = node.getChildrenDisplay().size(); if (n == 0) { //Node with no children. node.getInternalDesktop().setPreferredSize( node.getTitleBar().getMinimumSize()); node.setVisible(true); return; } Component[] comps = node.getInternalDesktop().getComponents(); List l = new ArrayList(); for (int i = 0; i < comps.length; i++) if (comps[i] instanceof ImageDisplay) l.add(comps[i]); Dimension dd = node.getSize(); if (dd.width == 0 || dd.height == 0 && node.getParentDisplay() != null) dd = node.getParentDisplay().getSize(); l = sorter.sort(l); if (dd.width >= MIN && dd.height >= MIN) { if (maxDim.width != 0) n = dd.width/maxDim.width; if (n == 0) { n = DEFAULT_PER_ROW; } if (itemsPerRow >= 1) n = itemsPerRow; } else { if (itemsPerRow >= 1) { n = itemsPerRow; } else { n = l.size(); if (n > DEFAULT_PER_ROW) n = (int) Math.floor(Math.sqrt(n))+1; //See note. } } //Finally do layout. Dimension d; ImageDisplay child; Iterator children = l.iterator(); try { int i = 0; while (children.hasNext()) { for (int j = 0; j < n; j++) { if (!children.hasNext()) //Done, less than n^2 children. return; //Go to finally. child = (ImageDisplay) children.next(); d = child.getPreferredSize(); child.setBounds(j*maxDim.width, i*maxDim.height, d.width, d.height); } i++; } } finally { Rectangle bounds = node.getContentsBounds(); d = bounds.getSize(); node.getInternalDesktop().setSize(d); node.getInternalDesktop().setPreferredSize(d); } } /** * Relays out the node when refreshing the display. * * @param node The node to refresh. * @param oldNode The node previously displayed. * @param newNodes The nodes to layout. * @param oldNodes The previously displayed out nodes. */ static void redoLayout(ImageSet node, ImageSet oldNode, Collection newNodes, Collection oldNodes) { int n = newNodes.size(); if (n == 0) { //Node with no children. node.getInternalDesktop().setPreferredSize( node.getTitleBar().getMinimumSize()); node.setVisible(true); return; } Iterator children = newNodes.iterator(); ImageDisplay child, oldChild; Object ho, oho, pho, poho; Iterator j; long id, pid; Class klass, pKlass; while (children.hasNext()) { child = (ImageDisplay) children.next(); ho = child.getHierarchyObject(); klass = ho.getClass(); pho = child.getParentDisplay().getHierarchyObject(); pKlass = pho.getClass(); if (ho instanceof DataObject) { if (pho instanceof DataObject) { j = oldNodes.iterator(); id = ((DataObject) ho).getId(); pid = ((DataObject) pho).getId(); while (j.hasNext()) { oldChild = (ImageDisplay) j.next(); oho = oldChild.getHierarchyObject(); if (oldChild.getParentDisplay() != null) { poho = oldChild.getParentDisplay().getHierarchyObject(); if (oho instanceof DataObject) { if (((DataObject) oho).getId() == id && oho.getClass().equals(klass)) { if (((DataObject) poho).getId() == pid && poho.getClass().equals(pKlass)) { if (child instanceof ImageNode) { scaleImage((ImageNode) child, (ImageNode) oldChild); } child.setBounds(oldChild.getBounds()); } } } } } } else { //pho not a dataobject j = oldNodes.iterator(); id = ((DataObject) ho).getId(); while (j.hasNext()) { oldChild = (ImageDisplay) j.next(); oho = oldChild.getHierarchyObject(); if (oldChild.getParentDisplay() != null) { if (oho instanceof DataObject) { if (((DataObject) oho).getId() == id && oho.getClass().equals(klass)) { if (child instanceof ImageNode) { scaleImage((ImageNode) child, (ImageNode) oldChild); } child.setBounds(oldChild.getBounds()); } } } } } } } if (oldNode == null) { Rectangle bounds = node.getContentsBounds(); Dimension d = bounds.getSize(); node.getInternalDesktop().setSize(d); node.getInternalDesktop().setPreferredSize(d); } else { Dimension d = oldNode.getRestoreSize(); if (oldNode.isCollapsed()) { Rectangle r = oldNode.getBounds(); Rectangle bounds = new Rectangle(r.x, r.y, d.width, d.height); node.setBounds(bounds); node.setPreferredSize(d); node.getInternalDesktop().setSize(d); node.getInternalDesktop().setPreferredSize(d); node.setCollapsed(true); } else { node.setBounds(oldNode.getBounds()); node.getInternalDesktop().setSize(d); node.getInternalDesktop().setPreferredSize(d); } } } /** * Returns the object with the largest associated area. * This method calculates the <code>area = width x height</code> of each * <code>Dimension</code> object and then returns the one having the * largest area. * * @param a The first dimensions. Mustn't be <code>null</code>. * @param b The second dimensions. Mustn't be <code>null</code>. * @return The one between <code>a</code> and <code>b</code> with the * largest associated area. */ public static Dimension max(Dimension a, Dimension b) { int w = a.width; int h = a.height; if (b.width > w) w = b.width; if (b.height > h) h = b.height; return new Dimension (w, h); } //NOTE: Let A be the function that calculates the area of a Dimension, //sz the number of children, and r = sqr(sz). //The required area for the layout mustn't be less than sz*A(maxDim). //B/c: r < [r]+1 => sz=r^2 < ([r]+1)^2 //Then: sz*A(maxDim) < [([r]+1)^2]*A(maxDim) /* for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (!children.hasNext()) //Done, less than n^2 children. return; //Go to finally. child = (ImageDisplay) children.next(); d = child.getPreferredSize(); child.setBounds(j*maxDim.width, i*maxDim.height, d.width, d.height); } } */ }