/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * 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.
 *
 * <BR><BR>
 *
 * 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.
 *
 * <BR><BR>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.util;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;


/** This class provides a visual component that has the form of a list, as provided by JList but uses JCheckBox for data selection. Thus several elements can be 'ticked' in the list, and this selection returned using the method getSelected().<BR>Parts of this code modified from Trevor Harmon's posting on www.dejanews.com.
 * @author John Thompson
 * @version 2.3
 */ 
public class CheckList
    extends JList
{
    /** The border used when a list row is not in focus. */
    static private Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

    private boolean show_selected_row = true;


    /** Constructor. */
    public CheckList(boolean show_selected_row)
    {
	super();
	this.show_selected_row = show_selected_row;

	setCellRenderer(new CheckListCellRenderer());
	setModel(new DefaultListModel());
	setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	addMouseListener(new CheckListMouseListener());
    }


    public void addEntry(CheckListEntry entry)
    {
	DefaultListModel model = (DefaultListModel) getModel();

	// Add the entry in alpabetical order
	String name = entry.toString();
	for (int i = 0; i < model.size(); i++) {
	    Object sibling = model.getElementAt(i);
	    if (name.compareTo(sibling.toString()) <= 0) {
		model.add(i, entry);
		return;
	    }
	}

	model.addElement(entry);
    }


    public void clearTicked()
    {
	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    ((CheckListEntry) model.get(i)).setSelected(false);
	}
	updateUI();
    }

    /** Retrieve all the entries from the list
     */
    public ArrayList getAll()
    {
	ArrayList result = new ArrayList();
	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    CheckListEntry entry = (CheckListEntry) model.get(i);
	    result.add(entry.getObject());
	}
	return result;
    }
	

    /** Retrieve the currently ticked entries from this list.
     * @return An <strong>ArrayList</strong> containing only those entries from the initial list that are checked.
     * @see org.greenstone.gatherer.checklist.Entry
     */
    public ArrayList getTicked()
    {
	ArrayList result = new ArrayList();
	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    CheckListEntry entry = (CheckListEntry) model.get(i);
	    if (entry.isSelected()) {
		result.add(entry.getObject());
	    }
	}
	return result;
    }


    /** This is different from isSelectionEmpty! */
    public boolean isNothingTicked()
    {
	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    if (((CheckListEntry) model.get(i)).isSelected()) {
		return false;
	    }
	}

	return true;
    }


    public void setListData(ArrayList list_data)
    {
 	// Create a new model.
	setModel(new DefaultListModel());

	// Add the items from the list to the model
 	for (int i = 0; i < list_data.size(); i++) {
 	    Object list_object = list_data.get(i);
 	    if (list_object instanceof CheckListEntry) {
 		addEntry((CheckListEntry) list_object);
 	    }
	    else {
		addEntry(new CheckListEntry(list_object));
	    }
 	}
    }


    public void setTickedObjects(Object[] objects)
    {
	if (objects == null) {
	    return;
	}

	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    CheckListEntry entry = (CheckListEntry) model.get(i);
	    for (int j = 0; j < objects.length; j++) {
		if (entry.getObject().equals(objects[j])) {
		    entry.setSelected(true);
		}
	    }
	}
	updateUI();
    }

    public void setAllTicked() {
	DefaultListModel model = (DefaultListModel) getModel();
	for (int i = 0; i < model.size(); i++) {
	    CheckListEntry entry = (CheckListEntry) model.get(i);
	    entry.setSelected(true);
	}
	updateUI();
    }

    /** A custom list cell renderer for producing rows which contain clickable check boxes. */
    protected class CheckListCellRenderer 
	implements ListCellRenderer
    {
	/** Return a component that has been configured to display the specified value. That component's paint method is then called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, this method is called to generate a component on which getPreferredSize  can be invoked.
	 * @param list The </strong>JList</strong> we're painting.
	 * @param value The value returned by list.getModel().getElementAt(index), as an <strong>Object</strong>.
	 * @param index The cells index as an <i>int</i>.
	 * @param is_selected <i>true</i> if the specified cell was selected, <i>false</i> otherwise.
	 * @param cell_has_focus <i>true</i> if and only if the specified cell has the focus.
	 * @return A <strong>Component</strong> whose paint() method will render the specified value.
	 */
	public Component getListCellRendererComponent(JList list, Object value, int index, boolean is_selected, boolean cell_has_focus) {
	    JCheckBox checkbox = (JCheckBox) value;
	    if (show_selected_row) {
		checkbox.setBackground(is_selected ? list.getSelectionBackground() : list.getBackground());
		checkbox.setForeground(is_selected ? list.getSelectionForeground() : list.getForeground());
		checkbox.setBorderPainted(true);
	    }
	    else {
		checkbox.setBackground(list.getBackground());
		checkbox.setForeground(list.getForeground());
		checkbox.setBorderPainted(false);
	    }
	    checkbox.setEnabled(list.isEnabled());
	    checkbox.setFont(list.getFont());
	    checkbox.setFocusPainted(false);
	    checkbox.setBorder((is_selected) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder);
	    return checkbox;
	}
    }


    /** Listens for clicks apon the checks within the list, and updates as necessary. */
    private class CheckListMouseListener 
	extends MouseAdapter
    {
	/** Called whenever the mouse is clicked over our list. We find the nearest checkbox and change its state.
	 * @param e A <strong>MouseEvent</strong> containing everything you ever wanted to know about the mouse event but were afraid to ask.
	 */
	public void mousePressed(MouseEvent e)
	{
	    if (!isEnabled()) return;
	    JList list = (JList) e.getSource();
	    int index = list.locationToIndex(e.getPoint());
	    CheckListEntry checkbox = (CheckListEntry) list.getModel().getElementAt(index);
	    if (!checkbox.isFixed()) {
		checkbox.setSelected(!checkbox.isSelected());
	    }
	    checkbox.grabFocus();

	    // We need to cause a ListSelectionEvent -- this is ugly but I can't find a better way quickly
	    list.removeSelectionInterval(0, 0);
	    list.setSelectionInterval(index, index);
	}
    }
}
