Display a Calendar in a JComboBox

import java.awt.*;
import java.awt.event.*;
import java.util.Calendar;
import java.text.SimpleDateFormat;

import javax.swing.*;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.border.EmptyBorder;

import com.sun.java.swing.plaf.motif.MotifComboBoxUI;
import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;


public class DateComboBox extends JComboBox {

    protected SimpleDateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy");
    public void setDateFormat(SimpleDateFormat dateFormat) {
	this.dateFormat = dateFormat;
    }
    public void setSelectedItem(Object item) {
	// Could put extra logic here or in renderer when item is instanceof Date, Calendar, or String
	// Dont keep a list ... just the currently selected item
	removeAllItems(); // hides the popup if visible
	addItem(item);
	super.setSelectedItem(item);
    }

    public void updateUI() {
	ComboBoxUI cui = (ComboBoxUI) UIManager.getUI(this);
	if (cui instanceof MetalComboBoxUI) {
	    cui = new MetalDateComboBoxUI();
	} else if (cui instanceof MotifComboBoxUI) {
	    cui = new MotifDateComboBoxUI();
	} else if (cui instanceof WindowsComboBoxUI) {
	    cui = new WindowsDateComboBoxUI();
	}
        setUI(cui);
    }

    // Inner classes are used purely to keep DateComboBox component in one file
    //////////////////////////////////////////////////////////////
    // UI Inner classes -- one for each supported Look and Feel
    //////////////////////////////////////////////////////////////

    class MetalDateComboBoxUI extends MetalComboBoxUI {
	protected ComboPopup createPopup() {
	    return new DatePopup( comboBox );
	}
    }

    class WindowsDateComboBoxUI extends WindowsComboBoxUI {
	protected ComboPopup createPopup() {
	    return new DatePopup( comboBox );
	}
    }

    class MotifDateComboBoxUI extends MotifComboBoxUI {
	protected ComboPopup createPopup() {
	    return new DatePopup( comboBox );
	}
    }

    //////////////////////////////////////////////////////////////
    // DatePopup inner class
    //////////////////////////////////////////////////////////////

    class DatePopup implements ComboPopup, MouseMotionListener,
			       MouseListener, KeyListener, PopupMenuListener {

	protected JComboBox comboBox;
	protected Calendar calendar;
	protected JPopupMenu popup;
	protected JLabel monthLabel;
	protected JPanel days = null;
	protected SimpleDateFormat monthFormat = new SimpleDateFormat("MMM yyyy");

	protected Color selectedBackground;
	protected Color selectedForeground;
	protected Color background;
	protected Color foreground;

	public DatePopup(JComboBox comboBox) {
	    this.comboBox = comboBox;
	    calendar = Calendar.getInstance();
	    //String[] dts = {dt};
		int yr  = calendar.get(Calendar.YEAR);
		int mn  = calendar.get(Calendar.MONTH);
		int dts  = calendar.get(Calendar.DATE);
		String dt = dts+"/"+mn+"/"+yr;
	    comboBox.addItem(dt);
	    // check Look and Feel
	    background = UIManager.getColor("ComboBox.background");
	    foreground = UIManager.getColor("ComboBox.foreground");
	    selectedBackground = UIManager.getColor("ComboBox.selectionBackground");
	    selectedForeground = UIManager.getColor("ComboBox.selectionForeground");

	    initializePopup();
	}

	//========================================
	// begin ComboPopup method implementations
	//
        public void show() {
	    try {
		// if setSelectedItem() was called with a valid date, adjust the calendar
		calendar.setTime( dateFormat.parse( comboBox.getSelectedItem().toString() ) );
	    } catch (Exception e) {}
	    updatePopup();
	    popup.show(comboBox, 0, comboBox.getHeight());
        }

	public void hide() {
	    popup.setVisible(false);
	}

	protected JList list = new JList();
	public JList getList() {
	    return list;
	}

	public MouseListener getMouseListener() {
	    return this;
	}

	public MouseMotionListener getMouseMotionListener() {
	    return this;
	}

	public KeyListener getKeyListener() {
	    return this;
	}

	public boolean isVisible() {
	    return popup.isVisible();
	}

	public void uninstallingUI() {
	    popup.removePopupMenuListener(this);
	}


	public void mousePressed( MouseEvent e ) {}
        public void mouseReleased( MouseEvent e ) {}
	// something else registered for MousePressed
	public void mouseClicked(MouseEvent e) {
            if ( !SwingUtilities.isLeftMouseButton(e) )
                return;
            if ( !comboBox.isEnabled() )
                return;
	    if ( comboBox.isEditable() ) {
		comboBox.getEditor().getEditorComponent().requestFocus();
	    } else {
		comboBox.requestFocus();
	    }
	    togglePopup();
	}

	protected boolean mouseInside = false;
	public void mouseEntered(MouseEvent e) {
	    mouseInside = true;
	}
	public void mouseExited(MouseEvent e) {
	    mouseInside = false;
	}

	// MouseMotionListener
	public void mouseDragged(MouseEvent e) {}
	public void mouseMoved(MouseEvent e) {}

	// KeyListener
	public void keyPressed(KeyEvent e) {}
	public void keyTyped(KeyEvent e) {}
	public void keyReleased( KeyEvent e ) {
	    if ( e.getKeyCode() == KeyEvent.VK_SPACE ||
		 e.getKeyCode() == KeyEvent.VK_ENTER ) {
		togglePopup();
	    }
	}

	/**
	 * Variables hideNext and mouseInside are used to
	 * hide the popupMenu by clicking the mouse in the JComboBox
	 */
	public void popupMenuCanceled(PopupMenuEvent e) {}
	protected boolean hideNext = false;
	public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
	    hideNext = mouseInside;
	}
	public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}


	protected void togglePopup() {
	    if ( isVisible() || hideNext ) {
		hide();
	    } else {
		show();
	    }
	    hideNext = false;
	}


	// Note *** did not use JButton because Popup closes when pressed
	protected JLabel createUpdateButton(final int field, final int amount) {
	    final JLabel label = new JLabel();
	    final Border selectedBorder = new EtchedBorder();
	    final Border unselectedBorder = new EmptyBorder(selectedBorder.getBorderInsets(new JLabel()));
	    label.setBorder(unselectedBorder);
	    label.setForeground(foreground);
	    label.addMouseListener(new MouseAdapter() {
		    public void mouseReleased(MouseEvent e) {
			calendar.add(field, amount);
			updatePopup();
		    }
		    public void mouseEntered(MouseEvent e) {
			label.setBorder(selectedBorder);
		    }
		    public void mouseExited(MouseEvent e) {
			label.setBorder(unselectedBorder);
		    }
		});
	    return label;
	}


	protected void initializePopup() {
	    JPanel header = new JPanel(); // used Box, but it wasn't Opaque
	    header.setLayout(new BoxLayout(header, BoxLayout.X_AXIS));
	    header.setBackground(background);
	    header.setOpaque(true);

	    JLabel label;
	    label = createUpdateButton(Calendar.YEAR, -1);
	    label.setText("<<");
	    label.setToolTipText("Previous Year");

	    header.add(Box.createHorizontalStrut(12));
	    header.add(label);
	    header.add(Box.createHorizontalStrut(12));

	    label = createUpdateButton(Calendar.MONTH, -1);
	    label.setText("<");
	    label.setToolTipText("Previous Month");
	    header.add(label);

	    monthLabel = new JLabel("", JLabel.CENTER);
	    monthLabel.setForeground(foreground);
	    header.add(Box.createHorizontalGlue());
	    header.add(monthLabel);
	    header.add(Box.createHorizontalGlue());

	    label = createUpdateButton(Calendar.MONTH, 1);
	    label.setText(">");
	    label.setToolTipText("Next Month");
	    header.add(label);

	    label = createUpdateButton(Calendar.YEAR, 1);
	    label.setText(">>");
	    label.setToolTipText("Next Year");

	    header.add(Box.createHorizontalStrut(12));
	    header.add(label);
	    header.add(Box.createHorizontalStrut(12));

	    popup = new JPopupMenu();
	    popup.setBorder(BorderFactory.createLineBorder(Color.black));
	    popup.setLayout(new BorderLayout());
	    popup.setBackground(background);
	    popup.addPopupMenuListener(this);
	    popup.add(BorderLayout.NORTH, header);
	}

	// update the Popup when either the month or the year of the calendar has been changed
	protected void updatePopup() {
	    monthLabel.setText( monthFormat.format(calendar.getTime()) );
	    if (days != null) {
		popup.remove(days);
	    }
	    days = new JPanel(new GridLayout(0, 7));
	    days.setBackground(background);
	    days.setOpaque(true);

	    Calendar setupCalendar = (Calendar) calendar.clone();
	    setupCalendar.set(Calendar.DAY_OF_WEEK, setupCalendar.getFirstDayOfWeek());
	    for (int i = 0; i < 7; i++) {
		int dayInt = setupCalendar.get(Calendar.DAY_OF_WEEK);
		JLabel label = new JLabel();
		label.setHorizontalAlignment(JLabel.CENTER);
		label.setForeground(foreground);
		if (dayInt == Calendar.SUNDAY) {
		    label.setText("Sun");
		} else if (dayInt == Calendar.MONDAY) {
		    label.setText("Mon");
		} else if (dayInt == Calendar.TUESDAY) {
		    label.setText("Tue");
		} else if (dayInt == Calendar.WEDNESDAY) {
		    label.setText("Wed");
		} else if (dayInt == Calendar.THURSDAY) {
		    label.setText("Thu");
		} else if (dayInt == Calendar.FRIDAY) {
		    label.setText("Fri");
		} else if (dayInt == Calendar.SATURDAY){
		    label.setText("Sat");
		}
		days.add(label);
		setupCalendar.roll(Calendar.DAY_OF_WEEK, true);
	    }

	    setupCalendar = (Calendar) calendar.clone();
	    setupCalendar.set(Calendar.DAY_OF_MONTH, 1);
	    int first = setupCalendar.get(Calendar.DAY_OF_WEEK);
	    for (int i = 0; i < (first - 1); i++) {
		days.add(new JLabel(""));
	    }
	    for (int i = 1; i <= setupCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
		final int day = i;
		final JLabel label = new JLabel(String.valueOf(day));
		label.setHorizontalAlignment(JLabel.CENTER);
		label.setForeground(foreground);
		label.addMouseListener(new MouseListener() {
			public void mousePressed(MouseEvent e) {}
			public void mouseClicked(MouseEvent e) {}
			public void mouseReleased(MouseEvent e) {
			    label.setOpaque(false);
			    label.setBackground(background);
			    label.setForeground(foreground);
			    calendar.set(Calendar.DAY_OF_MONTH, day);
			    comboBox.setSelectedItem(dateFormat.format(calendar.getTime()));
			    hide();
			    comboBox.requestFocus();
			}
			public void mouseEntered(MouseEvent e) {
			    label.setOpaque(true);
			    label.setBackground(selectedBackground);
			    label.setForeground(selectedForeground);
			}
			public void mouseExited(MouseEvent e) {
			    label.setOpaque(false);
			    label.setBackground(background);
			    label.setForeground(foreground);
			}
		    });

		days.add(label);
	    }

	    popup.add(BorderLayout.CENTER, days);
	    popup.pack();
	}
    }

    public static void main(String args[])
    {
		JFrame f = new JFrame();
		Container c = f.getContentPane();
		c.setLayout(new FlowLayout());
		c.add(new JLabel("Date:"));
		DateComboBox dcb = new DateComboBox();
		c.add(dcb);
		f.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
		    	System.exit(0);
			}
	    });
		f.setSize(200, 100);
		f.show();
    }

}
