/* *************************************************************************** * * File: CalendarWidget.java * Package: ca.janeg.calendar * * Contains: ButtonActionListener * CalendarModel * CalendarSelectionListener * InputListener * * References: 'Java Rules' by Douglas Dunn * Addison-Wesley, 2002 (Chapter 5, section 13 - 19) * * 'Professional Java Custom UI Components' * by Kenneth F. Krutsch, David S. Cargo, Virginia Howlett * WROX Press, 2001 (Chapter 1-3) * * Date Author Changes * ------------ ------------- ---------------------------------------------- * Oct 24, 2002 Jane Griscti Created * Oct 27, 2002 jg Cleaned up calendar display * Oct 30, 2002 jg added ctor CalendarComboBox( Calendar ) * Oct 31, 2002 jg Added listeners and Popup * Nov 1, 2002 jg Cleaned up InputListener code to only accept * valid dates * Nov 2, 2002 jg modified getPopup() to handle display when * component is positioned at the bottom of the screen * Nov 3, 2002 jg changed some instance variables to class variables * Mar 29, 2003 jg added setDate() contributed by James Waldrop * Apr 29, 2006 David Underhill can now specify the format of the date to be displayed * can now specify whether or not to collapse upon selection * added ability to notify listeners when a selection is made * locked input so users must use the popup * *************************************************************************** */ //package webcal.util; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JFormattedTextField; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.Popup; import javax.swing.PopupFactory; import javax.swing.SwingConstants; import javax.swing.border.LineBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.table.DefaultTableModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumn; import java.util.Vector; /** * A custom component that mimics a combo box, displaying * a perpetual calendar rather than a 'list'. * * @author Jane Griscti jane@janeg.ca * @version 1.0 Oct 24, 2002 */ public class CalendarComboBox extends JPanel { // -- class fields private static final DateFormatSymbols dfs = new DateFormatSymbols(); private static final String[] months = dfs.getMonths(); private static final String[] dayNames = new String[ 7 ]; private static final Toolkit toolkit = Toolkit.getDefaultToolkit(); private static final Dimension screenSize = toolkit.getScreenSize(); private static final PopupFactory factory = PopupFactory.getSharedInstance(); // -- instance fields used with 'combo-box' panel private final JPanel inputPanel = new JPanel(); private final JFormattedTextField input; private final BasicArrowButton comboBtn = new BasicArrowButton( SwingConstants.SOUTH ); // -- instance fields used with calendar panel private final JPanel calPanel = new JPanel(); private final JTextField calLabel = new JTextField( 11 ); private final Calendar current = new GregorianCalendar(); private final CalendarModel display = new CalendarModel( 6, 6 ); private final JTable table = new JTable( display ); private final BasicArrowButton nextBtn = new BasicArrowButton( SwingConstants.EAST ); private final BasicArrowButton prevBtn = new BasicArrowButton( SwingConstants.WEST ); private final BasicArrowButton closeCalendarBtn = new BasicArrowButton( SwingConstants.NORTH ); private Popup popup; private final boolean collapseOnSelectionEnabled; private boolean collapseOnSelection; private Vector ccbListeners = new Vector(); /** * Create a new calendar combo-box object set with today's date. */ public CalendarComboBox(){ this( new GregorianCalendar() ); } /** * Create a new calendar combo-box object set with the given date. * * @param cal a calendar object * @see java.util.GregorianCalendar */ public CalendarComboBox( final Calendar cal ){ this( cal, new java.text.SimpleDateFormat("dd-MMM-yyyy") ); } /** * Create a new calendar combo-box object set with the given date. * * @param cal a calendar object * @param format how to display the selected date * @see java.util.GregorianCalendar */ public CalendarComboBox( final Calendar cal, final java.text.SimpleDateFormat format ){ this( cal, format, true ); } /** * Create a new calendar combo-box object set with the given date. * * @param cal a calendar object * @param format how to display the selected date * @param closeOnSelection if true, then when a date is selected by the user the combo box will collapse * @see java.util.GregorianCalendar */ public CalendarComboBox( final Calendar cal, final java.text.SimpleDateFormat format, final boolean collapseOnSelection ){ super(); this.collapseOnSelectionEnabled = collapseOnSelection; this.collapseOnSelection = collapseOnSelectionEnabled; // set the calendar and input box date Date date = cal.getTime(); current.setTime( date ); input = new JFormattedTextField( new javax.swing.text.DateFormatter( format ) ); input.setValue( date ); // create the GUI elements and assign listeners buildInputPanel(); buildCalendarDisplay(); registerListeners(); // intially, only display the input panel add( inputPanel ); } /* * Creates a field and 'combo box' button above the calendar * to allow user input. */ private void buildInputPanel(){ inputPanel.setLayout( new BoxLayout( inputPanel, BoxLayout.X_AXIS ) ); input.setEditable( false ); input.setColumns( 11 ); inputPanel.add( input ); comboBtn.setActionCommand( "combo" ); inputPanel.add( comboBtn ); } /* * Builds the calendar panel to be displayed in the popup */ private void buildCalendarDisplay(){ // Allow for individual cell selection and turn off // grid lines. table.setCellSelectionEnabled(true); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setShowGrid( false ); // Calendar (table) column headers // Set column headers to weekday names as given by // the default Locale. // // Need to re-map the retreived names. If used as is, // the table model ends up with an extra empty column as // the returned names begin at index 1, not zero. String[] names = dfs.getShortWeekdays(); for( int i=1; i actualDays ){ // overwrite any left over values from old month display.setValueAt( "", row, col ); }else{ display.setValueAt( new Integer( day ), row, col ); day++; } } } // set the month, year label calLabel.setText( months[ cal.get( Calendar.MONTH ) ] + " " + cal.get( Calendar.YEAR ) ); // set the calendar selection table.changeSelection( cal.get( Calendar.WEEK_OF_MONTH ) - 1, cal.get( Calendar.DAY_OF_WEEK ) - 1, false, false ); } /* * Gets a Popup to hold the calendar display and determines * it's position on the screen. */ private Popup getPopup(){ Point p = input.getLocationOnScreen(); Dimension inputSize = input.getPreferredSize(); Dimension calendarSize = calPanel.getPreferredSize(); if( ( p.y + calendarSize.height ) < screenSize.height) { // will fit below input panel popup = factory.getPopup( input, calPanel, p.x, p.y + (int)inputSize.height ); } else { // need to fit it above input panel popup = factory.getPopup( input, calPanel, p.x, p.y - (int)calendarSize.height ); } return popup; } /* * Returns the currently selected date as a Calendar object. * * @return Calendar the currently selected calendar date */ public Calendar getDate(){ return current; } /* * Returns the currently selected date as a GregorianCalendar object. * @return GregorianCalendar the currently selected calendar date */ public GregorianCalendar getDateGC(){ GregorianCalendar ret = new GregorianCalendar(); ret.setTime( current.getTime() ); return ret; } /** * Sets the current date and updates the UI to reflect the new date. * @param newDate the new date as a Date object. * @see Date * @author James Waldrop */ public void setDate(Date newDate) { current.setTime(newDate); input.setValue(current.getTime()); } /* * Creates a custom model to back the table. */ private class CalendarModel extends DefaultTableModel { public CalendarModel( int row, int col ){ super( row, col ); } /** * Overrides the method to return an Integer class * type for all columns. The numbers are automatically * right-aligned by a default renderer that's supplied * as part of JTable. */ public Class getColumnClass( int column ){ return Integer.class; } /** * Overrides the method to disable cell editing. * The default is editable. */ public boolean isCellEditable( int row, int col ){ return false; } } /* * Captures the 'prevBtn', 'nextBtn', 'comboBtn' and * 'closeCalendarBtn' actions. * * The combo button is disabled when the popup is shown * and enabled when the popup is hidden. Failure to do * so results in the popup screen area not being cleared * correctly if the user clicks the button while the popup * is being displayed. */ private class ButtonActionListener implements ActionListener { public void actionPerformed( ActionEvent e ){ String cmd = e.getActionCommand(); collapseOnSelection = false; //don't collapse when changing months! if( cmd.equals( "prevBtn" ) ){ current.add( Calendar.MONTH, -1 ); input.setValue( current.getTime() ); }else if( cmd.equals( "nextBtn" ) ){ current.add( Calendar.MONTH, 1 ); input.setValue( current.getTime() ); }else if( cmd.equals( "close" ) ){ popup.hide(); comboBtn.setEnabled( true ); }else{ comboBtn.setEnabled( false ); popup = getPopup(); popup.show(); } updateTable( current ); collapseOnSelection = collapseOnSelectionEnabled; //restore the user's preference as to collapsing } } /* * Captures a user selection in the calendar display and * changes the value in the 'combo box' to match the selected date. * */ private class CalendarSelectionListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent e){ if ( !e.getValueIsAdjusting() ) { int row = table.getSelectedRow(); int col = table.getSelectedColumn(); Object value = null; try{ value = display.getValueAt(row, col); }catch( ArrayIndexOutOfBoundsException ex ){ // ignore, happens when the calendar is // displayed for the first time } if( value instanceof Integer ){ int day = ( (Integer)value ).intValue(); current.set( Calendar.DATE, day ); input.setValue( current.getTime() ); if( collapseOnSelection ) { //collapse this if the user wants it to collapse on selection popup.hide(); comboBtn.setEnabled( true ); } notifyListenersOfSelectionChange(); //notify listeners that a selection has been made } } } } /* * Captures user input in the 'combo box' * If the input is a valid date and the user pressed * ENTER or TAB, the calendar selection is updated */ private class InputListener extends KeyAdapter { public void keyTyped(KeyEvent e) { DateFormat df = DateFormat.getDateInstance(); Date date = null; try{ date = df.parse( input.getText() ); }catch( ParseException ex ){ // ignore invalid dates } // change the calendar selection if the date is valid // and the user hit ENTER or TAB char c = e.getKeyChar(); if( date != null && ( c == KeyEvent.VK_ENTER || c == KeyEvent.VK_TAB ) ) { current.setTime( date ); updateTable( current ); } } } /** * Register a listener for this object * @param listener the object which is listening */ public void addCalendarComboBoxListener( CalendarComboBoxListener listener ) { ccbListeners.add( listener ); } /** * Notifies all listeners that a value selection has been made */ public void notifyListenersOfSelectionChange() { for( CalendarComboBoxListener listener : ccbListeners ) listener.selectionMade(); } /** * Remove a registered listener from this object * @param listener the object which is listening to be removed */ public void removeCalendarComboBoxListener( CalendarComboBoxListener listener ) { ccbListeners.remove( listener ); } }