/*
 * Copyright 2004-2007 IDCA. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and
 *        the following disclaimer.
 *    2. 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.
 * 
 * THIS SOFTWARE IS PROVIDED BY IDCA 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 IDCA OR
 * CONTRIBUTORS 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.
 * 
 * The views and conclusions contained in the software and documentation are those of the authors and
 * should not be interpreted as representing official policies, either expressed or implied, of IDCA.
 */

package com.idcanet.vasc.frontends.swing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.Spring;
import javax.swing.SpringLayout;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import com.idcanet.fff.SwingImageHelper;
import com.idcanet.vasc.core.AbstractVascFrontend;
import com.idcanet.vasc.core.VascEntry;
import com.idcanet.vasc.core.VascEntryField;
import com.idcanet.vasc.core.VascFrontendData;
import com.idcanet.vasc.core.actions.GlobalVascAction;
import com.idcanet.vasc.core.actions.RowVascAction;
import com.idcanet.vasc.core.entry.VascEntryExporter;
import com.idcanet.vasc.core.entry.VascEntryEventListener.VascEventType;
import com.idcanet.vasc.core.ui.VascColumnValueModelListener;
import com.idcanet.vasc.core.ui.VascUIComponent;
import com.idcanet.vasc.core.ui.VascValueModel;
import com.idcanet.vasc.frontends.swing.ui.SwingBoolean;
import com.idcanet.vasc.frontends.swing.ui.SwingButton;
import com.idcanet.vasc.frontends.swing.ui.SwingColorChooser;
import com.idcanet.vasc.frontends.swing.ui.SwingDate;
import com.idcanet.vasc.frontends.swing.ui.SwingLabel;
import com.idcanet.vasc.frontends.swing.ui.SwingList;
import com.idcanet.vasc.frontends.swing.ui.SwingText;
import com.idcanet.vasc.frontends.swing.ui.SwingTextArea;

/**
 * 
 * @author Willem Cazander
 * @version 1.0 Mar 21, 2007
 */
public class SwingVascFrontend extends AbstractVascFrontend {
	
	private Logger logger = null;
	private JComponent parent = null;

	public SwingVascFrontend(JComponent parent) {
		logger = Logger.getLogger(SwingVascFrontend.class.getName());
		this.parent=parent;
	}

	/**
	 * Add swing implmented ui components
	 */
	protected void addUiComponents() {
		VascFrontendData vfd = getVascEntry().getVascFrontendData();

		// required UI components
		vfd.putVascUIComponent(VascUIComponent.VASC_LABEL,SwingLabel.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_TEXT, SwingText.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_LIST, SwingList.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_BUTTON, SwingButton.class.getName());
		
		// optional UI components
		vfd.putVascUIComponent(VascUIComponent.VASC_BOOLEAN	, SwingBoolean.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_DATE	, SwingDate.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_TEXTAREA, SwingTextArea.class.getName());
		vfd.putVascUIComponent(VascUIComponent.VASC_COLOR,    SwingColorChooser.class.getName());
	}

	public ImageIcon getImageIcon(String imageResource) {
		/// TODO hack beter
		String key = entry.getVascFrontendData().getVascEntryResourceResolver().getTextValue(imageResource);
		//logger.fine("KEY======================="+key);
		
		if (key.startsWith("vasc.entry")) {
			return null;
		}
		
		if (key.indexOf("META-INF")>0 | key.indexOf("resource")>0) {
			return SwingImageHelper.getImageIcon(key);
		} else {
			return null;
		}
	}
	
	

	/**
	 * @see com.idcanet.vasc.core.VascViewRenderer#renderEdit(com.idcanet.vasc.core.VascEntry, java.lang.Object)
	 */
	public void renderEdit(Object rowBean) throws Exception {
		logger.fine("Rending Edit View");
		
		
		
		rowBean = entry.getVascFrontendData().getVascFrontendHelper().initEditObject(entry, rowBean);
		String beanValue = rowBean.toString();
		if (entry.getDisplayNameFieldId()!=null) {
			
			VascEntryField v = entry.getVascEntryFieldById(entry.getDisplayNameFieldId());
			
			Object vv = v.getVascEntryFieldValue().getValue(v, rowBean);
			if (vv==null) {
				beanValue="";
			} else {
				beanValue=""+vv;
			}
			if (beanValue.length()>30) {
				beanValue=beanValue.substring(0, 30);
			}
		}
		SwingEditDialog dialog = new SwingEditDialog(parent,entry,rowBean,i18n("vasc.dialog.edit.title"),i18n("vasc.dialog.edit.message",beanValue));		
        Object result = dialog.openDialog();
        logger.finest("OPEN closed : "+result);
        if(result==null) {
            return;
        }
        entry.getVascFrontendData().getVascFrontendHelper().mergeObject(entry, result);
	}
	
	public void renderDelete(Object rowBean) throws Exception {
		String beanValue = rowBean.toString();
		if (entry.getDisplayNameFieldId()!=null) {
			VascEntryField v = entry.getVascEntryFieldById(entry.getDisplayNameFieldId());
			beanValue = ""+v.getVascEntryFieldValue().getValue(v, rowBean);
			if (beanValue.length()>30) {
				beanValue=beanValue.substring(0, 30);
			}
		}
		int response = JOptionPane.showOptionDialog(
                parent                       // Center in window.
              , i18n("vasc.dialog.delete.message",beanValue)    // Message
              , i18n("vasc.dialog.delete.title")                // Title in titlebar
              , JOptionPane.YES_NO_OPTION  // Option type
              , JOptionPane.PLAIN_MESSAGE  // messageType
              , null                       // Icon (none)
              , null                    	// Button text as above.
              , null 					   // Default button's label
            );
		if (response==JOptionPane.YES_OPTION) {
			entry.getVascFrontendData().getVascController().getVascBackendControllerResolver().getVascBackendController().getVascBackendById(entry.getBackendId()).delete(rowBean);
			entry.getVascFrontendData().getEntryDataList().remove(rowBean);
			entry.getVascFrontendData().setEntryDataObject(null);
			//entry.getVascFrontendController().getVascFrontendHelper().fireVascEvent(VascEventListener.VascEventType.DATA_UPDATE, rowBean);
		}
	}
	
	
	class SwingEditDialog extends JDialog {
		
		private static final long serialVersionUID = 10L;
		private String headerText = null;
		private Object result = null;
		private Object bean = null;
		
		public SwingEditDialog(JComponent parent,VascEntry entry,Object bean,String title,String headerText) throws Exception {
			super();
			this.headerText = headerText;
			this.bean = bean;
			
			setTitle(i18n(title));
			setModal(true);
			
			JPanel pane = new JPanel();
			pane.setLayout(new BorderLayout());
			
			JPanel header = new JPanel();
			createHeader(header);
			pane.add(header,BorderLayout.NORTH);
			
			JPanel body = new JPanel();
			createBody(body);
			pane.add(body,BorderLayout.CENTER);
			
			JPanel footer = new JPanel();
			createFooter(footer);
			pane.add(footer,BorderLayout.SOUTH);
			
			add(pane);
			setDefaultCloseOperation(DISPOSE_ON_CLOSE);
			
			//Ensure the text field always gets the first focus.
	        //addComponentListener(new ComponentAdapter() {
	        //    public void componentShown(ComponentEvent ce) {
	         //       textField.requestFocusInWindow();
	       //     }
	       /// });
			
			pack();
			setLocationRelativeTo(parent);
		}
		
		
		public Object openDialog() {
			setVisible(true);
			return result;
		}
		
		public void createHeader(JPanel header) {
			JLabel l = new JLabel();
			l.setText(i18n(headerText));
			l.setFont(new Font(null,Font.BOLD, 14));
			//l.setToolTipText(i18n(headerText));
			header.add(l);
		}
		
		public void createBody(JPanel body) throws Exception {
			body.setLayout(new SpringLayout());
			int column = 0;
	        for (VascEntryField c:entry.getVascEntryFields()) {
	        	entry.getVascFrontendData().getVascFrontendHelper().initEditObjectColumn(c, bean);
	        	if (c.isEdit()==false) {
	        		continue;
	        	}
	        	
	      
	        	//if (c.isEditReadOnly()==true) {
	        	
	        	for (int i=0;i<c.getVascEntryFieldType().getUIComponentCount(c);i++) {
	        		
	        		VascUIComponent label = c.getVascEntryFieldType().provideLabelUIComponent(i,c);
	        		VascValueModel model = new VascValueModel();
	        		model.setValue(i18n(c.getName()));
	        		label.createComponent(entry,c,model,body);
	        		
	        		VascUIComponent editor = c.getVascEntryFieldType().provideEditorUIComponent(i,c);
	        		model = new VascValueModel(c.getVascEntryFieldType().provideEditorVascValueModel(i,c));
	        		model.setValue(c.getVascEntryFieldValue().getValue(c, bean));
	        		model.addListener(new VascColumnValueModelListener(c,bean));
	        		Object g = editor.createComponent(entry,c,model,body);
	        		
	        		column++;
	        		if (i==0) {
	        			entry.getVascFrontendData().addFieldVascUIComponents(c, editor,g);
	        		}
	        	}
	        }
	        //JComponent,  rows, cols, initX, initY ,xPad, yPad 
			SpringUtilities.makeCompactGrid(body, column,2,  6,6, 6,6);
	        
		}
		public void createFooter(JPanel footer) {

			JButton saveButton = new JButton();
	        saveButton.setIcon(getImageIcon("vasc.dialog.save.image"));
	        saveButton.setText(i18n("vasc.dialog.save.name"));
	        saveButton.setToolTipText(i18n("vasc.dialog.save.tooltip"));
			saveButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent event) {
					if(entry.getVascFrontendData().getVascFrontendHelper().setUIComponentsBeanErrors(entry, bean)) {
						return;
					}
					result = bean;
					setVisible(false);
				}
			});
			footer.add(saveButton);
			
			JButton cancelButton = new JButton();
	        cancelButton.setIcon(getImageIcon("vasc.dialog.cancel.image"));
	        cancelButton.setText(i18n("vasc.dialog.cancel.name"));
	        cancelButton.setToolTipText(i18n("vasc.dialog.cancel.tooltip"));
			cancelButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent event) {
					result = null;
					setVisible(false);
				}
			});
			footer.add(cancelButton);
		}
	}
	
	
	
	
	/**
	 * @see com.idcanet.vasc.core.VascViewRenderer#renderExport(com.idcanet.vasc.core.VascEntry, com.idcanet.vasc.core.VascDataExporter)
	 */
	public void renderExport(VascEntryExporter exporter) throws Exception {
		
		String fileName = null;
		JFileChooser c = new JFileChooser();
	    int rVal = c.showSaveDialog(null);
	      if (rVal == JFileChooser.APPROVE_OPTION) {
	    	  fileName = c.getSelectedFile().getAbsolutePath();
	       // filename.setText(c.getSelectedFile().getName());
	        //dir.setText(c.getCurrentDirectory().toString());
	      }
	      if (rVal == JFileChooser.CANCEL_OPTION) {
	       return;
	      }
		
        logger.fine("FileName: "+fileName);
        if (fileName == null) {
            return;
        }
        OutputStream out = new FileOutputStream(fileName);
        try {
        	exporter.doExport(out, entry);
        } catch (Exception e) {
        	entry.getVascFrontendData().getVascFrontendHelper().handleException(e, entry);
        } finally {
        	if (out!=null) {
        		out.close();
        	}
        }
	}


	/**
	 * @see com.idcanet.vasc.core.VascViewRenderer#renderView(com.idcanet.vasc.core.VascEntry)
	 */
	public void renderView() throws Exception {
				
		JPanel topPanel = new JPanel();
		topPanel.setLayout(new BorderLayout());
		//topPanel.setBackground(Color.PINK);
		
		JPanel n = new JPanel();
		topPanel.add(n,BorderLayout.NORTH);
		renderHeader(n);
		
		JPanel c = new JPanel();
		c.setLayout(new BorderLayout());
		topPanel.add(c,BorderLayout.CENTER);
		renderBody(c);
		
		JPanel f = new JPanel();
		//f.setBackground(Color.BLUE);
		topPanel.add(f,BorderLayout.SOUTH);
		renderFooter(f);
		
		//parent.setBackground(Color.CYAN);
		parent.setLayout(new BorderLayout());
		parent.add(topPanel);
	}

	private void renderHeader(JComponent parent2) {
				
		JPanel header = new JPanel();
		header.setLayout(new BorderLayout());
		header.setBackground(Color.WHITE);
		header.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		
		if(entry.getHeaderImage()!=null) {
			JLabel l = new JLabel();
			// TODO: hack images working
			//l.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(getClass().getResource(entry.getHeaderImage())).getScaledInstance(32, 32, Image.SCALE_SMOOTH)));
			if (entry.getHeaderDescription()!=null) {
				l.setToolTipText(i18n(entry.getHeaderDescription()));
			}
			header.add(l,BorderLayout.WEST);
		}
		
		if(entry.getHeaderName()!=null) {
			JLabel l = new JLabel(i18n(entry.getHeaderName()));
			l.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
			l.setFont(new Font(null,Font.BOLD, 18));
			if (entry.getHeaderDescription()!=null) {
				l.setToolTipText(i18n(entry.getHeaderDescription()));
			}
			header.add(l,BorderLayout.CENTER);
		}
		
		JPanel top = new JPanel();
		//top.setBackground(Color.BLUE);
		for (GlobalVascAction action:entry.getGlobalActions()) {
			JButton but = new JButton();
			but.setText(i18n(action.getName()));
			but.setToolTipText(i18n(action.getToolTip()));
			but.addActionListener(new GlobalActionListener(action));
			but.setIcon(getImageIcon(action.getImage()));
			top.add(but);
		}
		
		//	create options
		JPanel optionPanel = new JPanel();
		//top.setBackground(Color.GREEN);
        //for(VascUserOption option:entry.getUserOptions()) {
        //	option.createUserOptionRenderer(entry);
        //}
        
        //top.add(header,BorderLayout.NORTH);
        parent2.setLayout(new BorderLayout());
        parent2.add(header,BorderLayout.NORTH);
        parent2.add(optionPanel,BorderLayout.CENTER);
        parent2.add(top,BorderLayout.EAST);
	}
	
	private void renderBody(JComponent parent2) {
		
		VascColumnModel model = new VascColumnModel();
		//TODO: entry.getVascEntryController().addEventListener(model);
		
		JTable table = new JTable();
        //TODO: remove this extra model for sorting, AND fixup a real sorting stuff
		TableSorter tableSorter = new TableSorter(); 
		// this regs the listeners for the sorting
		tableSorter.setTableHeader(table.getTableHeader());
		tableSorter.setTableModel(model);
		
		
		table.setModel(tableSorter);
		table.getTableHeader().setReorderingAllowed(false);
		
		// remove auto columns :(
		int cols = table.getColumnModel().getColumnCount();
		for (int i=0;i<cols;i++) {
			TableColumn c = table.getColumnModel().getColumn(0); // idd, just remove index 0 every time
			table.removeColumn(c);
		}
		table.revalidate();
		
		TableCellRenderer renderer = new JComponentTableHeaderCellRenderer();
		int counter=0;
		for(VascEntryField c:entry.getVascEntryFields()) {
			if (c.isList()==false) {
				continue;
			}
			TableColumn t = new TableColumn();
			if (c.getSizeList()!=null) {
				t.setPreferredWidth(c.getSizeList());
			} else {
				t.setPreferredWidth(200);
			}
			t.setHeaderValue(c);
			t.setHeaderRenderer(renderer);
			t.setModelIndex(counter);
			table.addColumn(t);
			counter++;
		}
		table.getSelectionModel().addListSelectionListener(new entrySectionListener(table,tableSorter));
		table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
		table.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				if (e.getClickCount() == 2) {				
					Object o = entry.getVascFrontendData().getEntryDataObject();
					if (o==null) {
						return;
					}
					try {
						// TODO: fix this
						entry.getVascFrontendData().getVascFrontend().renderEdit(o);
					} catch (Exception ee) {
						entry.getVascFrontendData().getVascFrontendHelper().handleException(ee, entry);
					}
				}
			}
		});
		JScrollPane scrollPane = new JScrollPane(table);
		parent2.add(scrollPane);
	}
	class entrySectionListener implements ListSelectionListener {
        JTable table;
        TableSorter tableSorter;
        entrySectionListener(JTable table,TableSorter tableSorter) {
            this.table = table;
            this.tableSorter=tableSorter;
        }
        public void valueChanged(ListSelectionEvent e) {
            if (e.getValueIsAdjusting()) {
            	return;
            }
            int rowIndex = table.getSelectedRow();
            if (rowIndex!=-1) {
            	// temp; gets index by sorter
            	rowIndex = tableSorter.modelIndex(rowIndex);
            	Object data = entry.getVascFrontendData().getEntryDataList().get(rowIndex);
            	entry.getVascFrontendData().setEntryDataObject(data);
            } else {
            	entry.getVascFrontendData().setEntryDataObject(null);
            }
        }
    }
	class JComponentTableHeaderCellRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = 10L;
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,boolean hasFocus, int row, int column) {
			VascEntryField c = (VascEntryField)value;
			setText(i18n(c.getName()));
			setToolTipText(i18n(c.getDescription()));
			
			if(c.getImage()!=null) {
				setIcon(getImageIcon(c.getImage()));
			} else {
				setIcon(null);
			}
			
			if (entry != null) {
	            JTableHeader header = table.getTableHeader();
	            if (header != null) {
	                setForeground(header.getForeground());
	                setBackground(header.getBackground());
	                setFont(header.getFont());
	            }
			}
			setBorder(UIManager.getBorder("TableHeader.cellBorder"));
			return this;
		}
	}
	
	
	private void renderFooter(JComponent parent2) {
		logger.finest("Creating footer");
		JPanel panel = new JPanel();
		for(RowVascAction action:entry.getRowActions()) {
			JButton but = new JButton();
			but.setText(i18n(action.getName()));
			but.setToolTipText(i18n(action.getToolTip()));
			but.setIcon(getImageIcon(action.getImage()));
			but.addActionListener(new RowActionListener(action));
			panel.add(but);
		}
		parent2.setLayout(new BorderLayout());
		parent2.add(panel,BorderLayout.WEST);
	}
	
	
	class RowActionListener implements ActionListener {
		
		private RowVascAction action = null;
		
		public RowActionListener(RowVascAction action) {
			this.action=action;
		}
		
		public void actionPerformed(ActionEvent event) {
			logger.fine("Row Action");
        	try {
        		action.doRowAction(entry, entry.getVascFrontendData().getEntryDataObject());
        	} catch (Exception e) {
        		entry.getVascFrontendData().getVascFrontendHelper().handleException(e, entry);
        	}
		}
	}

	class GlobalActionListener implements ActionListener {
		
		private GlobalVascAction action = null;
		
		public GlobalActionListener(GlobalVascAction action) {
			this.action=action;
		}
		
		public void actionPerformed(ActionEvent event) {
			logger.fine("Global Action");
        	try {
        		action.doGlobalAction(entry);
        	} catch (Exception e) {
        		entry.getVascFrontendData().getVascFrontendHelper().handleException(e, entry);
        	}
		}
	}
	
	class VascColumnModel extends AbstractTableModel { //implements VascEventListener  {
		private static final long serialVersionUID = 10L;

		public void vascEvent(VascEventType e,Object o) {
			if (e==VascEventType.DATA_UPDATE) {
				fireTableDataChanged();
			}
		}
		
		/**
		 * @see javax.swing.entry.entryModel#getColumnCount()
		 */
		public int getColumnCount() {
			int result = 0;
			for(VascEntryField c:entry.getVascEntryFields()) {
				if (c.isList()==false) {
					continue;
				}
				result++;
			}
			return result;
		}

		/**
		 * @see javax.swing.entry.entryModel#getRowCount()
		 */
		public int getRowCount() {
			if (entry.getVascFrontendData().getEntryDataList()==null) {
				return 0;
			}
			return entry.getVascFrontendData().getEntryDataList().size();
		}

		/**
		 * @see javax.swing.entry.entryModel#getValueAt(int, int)
		 */
		public Object getValueAt(int rowIndex, int columnIndex) {
			Object bean = entry.getVascFrontendData().getEntryDataList().get(rowIndex);
			logger.finer("Rending column; "+columnIndex+" bean: "+bean);
			
			// TODO: this is slowing....
			List<VascEntryField> list = new ArrayList<VascEntryField>();
			for(VascEntryField c:entry.getVascEntryFields()) {
				if (c.isList()==false) {
					continue;
					
				}
				list.add(c);
			}	
			
			VascEntryField vtc = list.get(columnIndex);
			try {
				//if (vtc.getVascColumnRenderer()!=null) {
//					return vtc.getVascColumnRenderer().rendererColumn(vtc,bean);
				//} else {
					return ""+vtc.getVascEntryFieldValue().getValue(vtc,bean);
			//	}
			} catch (Exception e) {
				return "Error";
			}
		}
	}
}

/**
 * A 1.4 file that provides utility methods for creating form- or grid-style
 * layouts with SpringLayout. These utilities are used by several programs, such
 * as SpringBox and SpringCompactGrid.
 */

class SpringUtilities {

  /**
   * Aligns the first <code>rows</code>*<code>cols</code> components of
   * <code>parent</code> in a grid. Each component is as big as the maximum
   * preferred width and height of the components. The parent is made just big
   * enough to fit them all.
   * 
   * @param rows
   *            number of rows
   * @param cols
   *            number of columns
   * @param initialX
   *            x location to start the grid at
   * @param initialY
   *            y location to start the grid at
   * @param xPad
   *            x padding between cells
   * @param yPad
   *            y padding between cells
   */
  public static void makeGrid(Container parent, int rows, int cols,
      int initialX, int initialY, int xPad, int yPad) {
    SpringLayout layout;
    try {
      layout = (SpringLayout) parent.getLayout();
    } catch (ClassCastException exc) {
      System.err
          .println("The first argument to makeGrid must use SpringLayout.");
      return;
    }

    Spring xPadSpring = Spring.constant(xPad);
    Spring yPadSpring = Spring.constant(yPad);
    Spring initialXSpring = Spring.constant(initialX);
    Spring initialYSpring = Spring.constant(initialY);
    int max = rows * cols;

    //Calculate Springs that are the max of the width/height so that all
    //cells have the same size.
    Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0))
        .getWidth();
    Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0))
        .getWidth();
    for (int i = 1; i < max; i++) {
      SpringLayout.Constraints cons = layout.getConstraints(parent
          .getComponent(i));

      maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth());
      maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight());
    }

    //Apply the new width/height Spring. This forces all the
    //components to have the same size.
    for (int i = 0; i < max; i++) {
      SpringLayout.Constraints cons = layout.getConstraints(parent
          .getComponent(i));

      cons.setWidth(maxWidthSpring);
      cons.setHeight(maxHeightSpring);
    }

    //Then adjust the x/y constraints of all the cells so that they
    //are aligned in a grid.
    SpringLayout.Constraints lastCons = null;
    SpringLayout.Constraints lastRowCons = null;
    for (int i = 0; i < max; i++) {
      SpringLayout.Constraints cons = layout.getConstraints(parent
          .getComponent(i));
      if (i % cols == 0) { //start of new row
        lastRowCons = lastCons;
        cons.setX(initialXSpring);
      } else { //x position depends on previous component
        cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST),
            xPadSpring));
      }

      if (i / cols == 0) { //first row
        cons.setY(initialYSpring);
      } else { //y position depends on previous row
        cons.setY(Spring.sum(lastRowCons
            .getConstraint(SpringLayout.SOUTH), yPadSpring));
      }
      lastCons = cons;
    }

    //Set the parent's size.
    SpringLayout.Constraints pCons = layout.getConstraints(parent);
    pCons.setConstraint(SpringLayout.SOUTH, Spring.sum(Spring
        .constant(yPad), lastCons.getConstraint(SpringLayout.SOUTH)));
    pCons.setConstraint(SpringLayout.EAST, Spring.sum(
        Spring.constant(xPad), lastCons
            .getConstraint(SpringLayout.EAST)));
  }

  /* Used by makeCompactGrid. */
  private static SpringLayout.Constraints getConstraintsForCell(int row,
      int col, Container parent, int cols) {
    SpringLayout layout = (SpringLayout) parent.getLayout();
    Component c = parent.getComponent(row * cols + col);
    return layout.getConstraints(c);
  }

  /**
   * Aligns the first <code>rows</code>*<code>cols</code> components of
   * <code>parent</code> in a grid. Each component in a column is as wide as
   * the maximum preferred width of the components in that column; height is
   * similarly determined for each row. The parent is made just big enough to
   * fit them all.
   * 
   * @param rows
   *            number of rows
   * @param cols
   *            number of columns
   * @param initialX
   *            x location to start the grid at
   * @param initialY
   *            y location to start the grid at
   * @param xPad
   *            x padding between cells
   * @param yPad
   *            y padding between cells
   */
  public static void makeCompactGrid(Container parent, int rows, int cols,
      int initialX, int initialY, int xPad, int yPad) {
    SpringLayout layout;
    try {
      layout = (SpringLayout) parent.getLayout();
    } catch (ClassCastException exc) {
      System.err
          .println("The first argument to makeCompactGrid must use SpringLayout.");
      return;
    }

    //Align all cells in each column and make them the same width.
    Spring x = Spring.constant(initialX);
    for (int c = 0; c < cols; c++) {
      Spring width = Spring.constant(0);
      for (int r = 0; r < rows; r++) {
        width = Spring.max(width, getConstraintsForCell(r, c, parent,
            cols).getWidth());
      }
      for (int r = 0; r < rows; r++) {
        SpringLayout.Constraints constraints = getConstraintsForCell(r,
            c, parent, cols);
        constraints.setX(x);
        constraints.setWidth(width);
      }
      x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
    }

    //Align all cells in each row and make them the same height.
    Spring y = Spring.constant(initialY);
    for (int r = 0; r < rows; r++) {
      Spring height = Spring.constant(0);
      for (int c = 0; c < cols; c++) {
        height = Spring.max(height, getConstraintsForCell(r, c, parent,
            cols).getHeight());
      }
      for (int c = 0; c < cols; c++) {
        SpringLayout.Constraints constraints = getConstraintsForCell(r,
            c, parent, cols);
        constraints.setY(y);
        constraints.setHeight(height);
      }
      y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
    }

    //Set the parent's size.
    SpringLayout.Constraints pCons = layout.getConstraints(parent);
    pCons.setConstraint(SpringLayout.SOUTH, y);
    pCons.setConstraint(SpringLayout.EAST, x);
  }
}