// DateTimeChooser.java
//
// Created: February 2006
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard.chooser;

import org.tulsoft.tools.gui.SwingUtils;

import com.toedter.calendar.JCalendar;
import com.toedter.components.JSpinField;

import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Component;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;

import java.util.Date;
import java.util.Calendar;

/**
 * A Swing component that lets choose date and time. It is just a thin
 * wrapper around an open-source package <em>jcalendar</em>, written
 * by <a href="http://www.toedter.com">Kai Toedter</a>, and released
 * under GNU Lesser General Public License. <p>
 *
 * Regarding functionality, the wrapper is similar to the standard
 * Java <tt>JColorChooser</tt>. Which means that a date-time chooser
 * can be either instantiate as a standalone component, or as part of
 * a modal dialog. In former case, the user can listen to the
 * date-time changes in the chooser by implementing a standard
 * <tt>ChangeListener</tt>, in the later case the returned value of
 * {@link #showDialog showDialog} contains the selected date-time. <p>
 *
 * It also allows to add a user-defined <em>preview panel</em> that
 * can indicate (in any user-defined format) directly in the chooser
 * window what date is selected. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: DateTimeChooser.java,v 1.4 2006/02/20 05:51:10 senger Exp $
 */

public class DateTimeChooser
    extends JPanel
    implements PropertyChangeListener {

    protected transient ChangeEvent changeEvent = null;
    protected EventListenerList lList = new EventListenerList();

    protected Date initialDate = null;

    // chooser components
    protected JCalendar calendar;
    protected JSpinField hours;
    protected JSpinField mins;
    protected JSpinField secs;
    protected JPanel previewPanel;

    protected static final Insets BREATH_BOTTOM = new Insets (0,0,10,0);

    /*************************************************************************
     * Creates a date-time chooser with the curent date as an initial date. 
     *************************************************************************/
    public DateTimeChooser() {
	this (null);
    }

    /*************************************************************************
     * Creates a date-time chooser with the given date as an initial date. 
     *************************************************************************/
    public DateTimeChooser (Date initialDate) {
	this.initialDate = initialDate;
	createItself();
    }

    /*************************************************************************
     * Creates this component...
     *************************************************************************/
    protected void createItself() {
	setLayout (new GridBagLayout());

	JButton now = new JButton ("Set to now");
	now.addActionListener (new ActionListener() {
		public void actionPerformed (ActionEvent e) {
		    DateTimeChooser.this.setDate (new Date());
		}
	    });
	JButton zero = new JButton ("Clear time");
	zero.addActionListener (new ActionListener() {
		public void actionPerformed (ActionEvent e) {
		    hours.setValue (0);
		    mins.setValue (0);
		    secs.setValue (0);
		}
	    });
	JPanel buttons = new JPanel();
	buttons.add (now);
	buttons.add (zero);

 	calendar = new JCalendar();
	calendar.setWeekOfYearVisible (false); 
	calendar.addPropertyChangeListener (this);
	calendar.getYearChooser().addPropertyChangeListener (this);

	JLabel hoursLabel = new JLabel ("Hours: ");
	hours = new JSpinField (0, 23);
 	hours.adjustWidthToMaximumValue();
	hours.addPropertyChangeListener (this);

	JLabel minsLabel = new JLabel (" Mins: ");
	mins = new JSpinField (0, 59);
 	mins.adjustWidthToMaximumValue();
	mins.addPropertyChangeListener (this);

	JLabel secsLabel = new JLabel (" Secs: ");
	secs = new JSpinField (0, 59);
 	secs.adjustWidthToMaximumValue();
	secs.addPropertyChangeListener (this);

	setDate (initialDate == null ? new Date() : initialDate);

	// put it together
 	SwingUtils.addComponent
	    (this, calendar,
	     0, 0, 6, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0);
 	SwingUtils.addComponent
	    (this, hoursLabel,
	     0, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, hours,
	     1, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, minsLabel,
	     2, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, mins,
	     3, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, secsLabel,
	     4, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, secs,
	     5, 1, 1, 1, GridBagConstraints.NONE, GridBagConstraints.NORTHWEST, 0.0, 0.0, BREATH_BOTTOM);
 	SwingUtils.addComponent
	    (this, buttons,
	     0, 2, 6, 1, GridBagConstraints.NONE, GridBagConstraints.CENTER, 0.0, 0.0, BREATH_BOTTOM);
    }

    /*************************************************************************
     * Gets the current value from the date-time chooser.
     *************************************************************************/
    public Date getDate() {
	Calendar cal = Calendar.getInstance();
	cal.setTime (calendar.getDate());
	cal.set (Calendar.HOUR_OF_DAY, hours.getValue());
	cal.set (Calendar.MINUTE,      mins.getValue());
	cal.set (Calendar.SECOND,      secs.getValue());
	return cal.getTime();
    }

    /*************************************************************************
     * Sets the given date as a new value for the date-time chooser. <p>
     *
     * @param newDate to be set into the chooser
     *************************************************************************/
    public void setDate (Date newDate) {
	Calendar cal = Calendar.getInstance();
	cal.setTime (newDate);
 	calendar.setDate (newDate);
	hours.setValue (cal.get (Calendar.HOUR_OF_DAY));
	mins.setValue (cal.get (Calendar.MINUTE));
	secs.setValue (cal.get (Calendar.SECOND));
    }

    /*************************************************************************
     * Returns a date that was used to initiate this date-time chooser
     * instance. It can be null.
     *************************************************************************/
    public Date getInitialDate() {
	return initialDate;
    }

    /*************************************************************************
     * Returns the preview panel that shows a currently chosen date. <p>
     *
     * @return the current preview panel
     *************************************************************************/
    public JPanel getPreviewPanel() {
	return previewPanel;
    }

    /*************************************************************************
     * Sets the current preview panel. Usually a preview panel
     * implements <tt>ChangeListener</tt> interface and registers its
     * wish to be notified when a new date is selected in the
     * chooser. <p>
     *
     * @param preview is a panel used to display currently selected date
     *************************************************************************/
    public void setPreviewPanel (JPanel preview) {
	if (previewPanel != null)
	    remove (previewPanel);
	previewPanel = preview;
	if (previewPanel != null) {
	    SwingUtils.addComponent
		(this, previewPanel,
		 0, 10, 10, 1, GridBagConstraints.BOTH, GridBagConstraints.NORTHWEST, 1.0, 1.0);
	}
    }

    /*************************************************************************
     * Shows a modal date-time chooser dialog and blocks until the
     * dialog is hidden. The dialog has three buttons: OK, Empty,
     * Cancel. <p>
     *
     * If the user presses the "OK" button, then this method
     * hides/disposes the dialog and returns the selected date. <p>
     *
     * If the user presses the "Cancel" button or closes the dialog
     * without pressing "OK", then this method hides/disposes the
     * dialog and returns the initial date (which could have been
     * null). <p>
     * 
     * If the user presses the "Empty" button, then this method
     * hides/disposes the dialog and returns null. It indicates that
     * no date is selected (even though that might have been a date as
     * an initial value). <p>
     *
     * @param parent is the parent Component for the dialog
     * @param title contains the dialog's title
     * @param initialDate is shown when the dialog starts; if this is
     * null the current date is shown
     *
     * @return the selected date (if OK pressed), the initial date (if
     * Cancel presed), or null (if Empty pressed)
     *
     *************************************************************************/
    public static Date showDialog (Component parent,
				   String title,
				   Date initialDate) {
	return showDialog (parent, title, new DateTimeChooser (initialDate));
    }

    /*************************************************************************
     * Shows a modal date-time chooser dialog and blocks until the
     * dialog is hidden. The dialog has three buttons: OK, Empty,
     * Cancel. See details how the buttons are dealt with in {@link
     * #showDialog(Component,String,Date) showDialog}. <p>
     *
     * This method allows to create an instance of a date-time chooser
     * separately, and perhaps to customize it (e.g. by calling
     * <tt>chooser.setPreviewPanel (myPreviewPanel)</tt>) before it is
     * used in a modal dialog. <p>
     *
     * @param parent is the parent Component for the dialog
     * @param title contains the dialog's title
     * @param chooser is the chooser instance that was created
     * separately and will be used in this dialog
     *
     * @return the selected date (if OK pressed), the initial date (if
     * Cancel presed), or null (if Empty pressed)
     *
     *************************************************************************/
    public static Date showDialog (Component parent,
				   String title,
				   DateTimeChooser chooser) {
	String[] buttons = new String[] { "OK", "Empty", "Cancel"};
	int selected =
	    JOptionPane.showOptionDialog (parent,
					  chooser,
					  title,
					  JOptionPane.YES_NO_OPTION,
					  JOptionPane.PLAIN_MESSAGE,
					  null,
					  buttons,
					  null);
	if (selected == 0)
	    return chooser.getDate();         // 'ok' selected
	else if (selected == 1)
	    return null;                      // 'empty' selected
	else
	    return chooser.getInitialDate();  // cancelled
    }

    /*************************************************************************
     * Adds a listener that is notified when a new date is
     * selected. <p>
     *
     * @param listener to be added
     *************************************************************************/
    public void addChangeListener (ChangeListener listener) {
	lList.add (ChangeListener.class, listener);
    }

    /*************************************************************************
     * Remove a listener that has been previously added by {@link
     * #addChangeListener}. <p>
     *
     * @param listener to be added
     *************************************************************************/
    public void removeChangeListener (ChangeListener listener) {
	lList.remove (ChangeListener.class, listener);
    }

    /*************************************************************************
     * Returns an array of all the <code>ChangeListener</code>s. <p>
     *
     * @return all of the <code>ChangeListener</code>s added, or an empty
     *         array if no listeners have been added
     *************************************************************************/
    public ChangeListener[] getChangeListeners() {
        return (ChangeListener[])lList
	    .getListeners (ChangeListener.class);
    }

    /*************************************************************************
     * Runs each <code>ChangeListener</code>'s
     * <code>stateChanged</code> method. <p>
     *
     * @see EventListenerList
     *************************************************************************/
    protected void fireStateChanged() {
        Object[] listeners = lList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -=2 ) {
            if (listeners[i] == ChangeListener.class) {
                if (changeEvent == null) {
                    changeEvent = new ChangeEvent (this);
                }
                ((ChangeListener)listeners [i+1]).stateChanged(changeEvent);
            }
        }
    }

    /*************************************************************************
     * Implementing PropertyChangeListener. This implementation only
     * propagates all property change events as a ChangeEvent.
     *************************************************************************/
    public void propertyChange (PropertyChangeEvent evt) {
	fireStateChanged();
    }

}
