KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mc4j > console > swing > editor > CalendarComboBox


1 package org.mc4j.console.swing.editor;
2
3 /* ***************************************************************************
4  *
5  * File: CalendarWidget.java
6  * Package: ca.janeg.calendar
7  *
8  * Contains: ButtonActionListener
9  * CalendarModel
10  * CalendarSelectionListener
11  * InputListener
12  *
13  * References: 'Java Rules' by Douglas Dunn
14  * Addison-Wesley, 2002 (Chapter 5, section 13 - 19)
15  *
16  * 'Professional Java Custom UI Components'
17  * by Kenneth F. Krutsch, David S. Cargo, Virginia Howlett
18  * WROX Press, 2001 (Chapter 1-3)
19  *
20  * Date Author Changes
21  * ------------ ------------- ----------------------------------------------
22  * Oct 24, 2002 Jane Griscti Created
23  * Oct 27, 2002 jg Cleaned up calendar display
24  * Oct 30, 2002 jg added ctor CalendarComboBox( Calendar )
25  * Oct 31, 2002 jg Added listeners and Popup
26  * Nov 1, 2002 jg Cleaned up InputListener code to only accept
27  * valid dates
28  * Nov 2, 2002 jg modified getPopup() to handle display when
29  * component is positioned at the bottom of the screen
30  * Nov 3, 2002 jg changed some instance variables to class variables
31  * Mar 29, 2003 jg added setDate() contributed by James Waldrop
32  * *************************************************************************** */

33
34 import java.awt.BorderLayout JavaDoc;
35 import java.awt.Dimension JavaDoc;
36 import java.awt.Font JavaDoc;
37 import java.awt.Point JavaDoc;
38 import java.awt.event.ActionEvent JavaDoc;
39 import java.awt.event.ActionListener JavaDoc;
40 import java.awt.event.KeyAdapter JavaDoc;
41 import java.awt.event.KeyEvent JavaDoc;
42 import java.awt.event.WindowAdapter JavaDoc;
43 import java.awt.event.WindowEvent JavaDoc;
44 import java.text.DateFormat JavaDoc;
45 import java.text.DateFormatSymbols JavaDoc;
46 import java.text.ParseException JavaDoc;
47 import java.util.Calendar JavaDoc;
48 import java.util.Date JavaDoc;
49 import java.util.GregorianCalendar JavaDoc;
50
51 import javax.swing.BorderFactory JavaDoc;
52 import javax.swing.BoxLayout JavaDoc;
53 import javax.swing.JFormattedTextField JavaDoc;
54 import javax.swing.JFrame JavaDoc;
55 import javax.swing.JPanel JavaDoc;
56 import javax.swing.JPopupMenu JavaDoc;
57 import javax.swing.JTable JavaDoc;
58 import javax.swing.JTextField JavaDoc;
59 import javax.swing.ListSelectionModel JavaDoc;
60 import javax.swing.SwingConstants JavaDoc;
61 import javax.swing.event.ListSelectionEvent JavaDoc;
62 import javax.swing.event.ListSelectionListener JavaDoc;
63 import javax.swing.plaf.basic.BasicArrowButton JavaDoc;
64 import javax.swing.table.DefaultTableModel JavaDoc;
65 import javax.swing.table.JTableHeader JavaDoc;
66 import javax.swing.table.TableColumn JavaDoc;
67
68 /**
69  * A custom component that mimics a combo box, displaying
70  * a perpetual calendar rather than a 'list'.
71  *
72  * @author Jane Griscti jane@janeg.ca
73  * @version 1.0 Oct 24, 2002
74  */

75 public class CalendarComboBox extends JPanel JavaDoc {
76
77     // -- class fields
78
private static final DateFormatSymbols JavaDoc dfs = new DateFormatSymbols JavaDoc();
79     private static final String JavaDoc[] months = dfs.getMonths();
80     private static final String JavaDoc[] dayNames = new String JavaDoc[7];
81
82     // -- instance fields used with 'combo-box' panel
83
private final JPanel JavaDoc inputPanel = new JPanel JavaDoc();
84
85     private final JFormattedTextField JavaDoc input = new JFormattedTextField JavaDoc(new Date JavaDoc());
86     private final BasicArrowButton JavaDoc comboBtn = new BasicArrowButton JavaDoc(SwingConstants.SOUTH);
87
88     // -- instance fields used with calendar panel
89
protected final JPanel JavaDoc calPanel = new JPanel JavaDoc();
90     private final JTextField JavaDoc calLabel = new JTextField JavaDoc(11);
91     private final Calendar JavaDoc current = new GregorianCalendar JavaDoc();
92     private final CalendarModel display = new CalendarModel(6, 6);
93     private final JTable JavaDoc table = new JTable JavaDoc(display);
94
95     private final BasicArrowButton JavaDoc nextBtn = new BasicArrowButton JavaDoc(SwingConstants.EAST);
96     private final BasicArrowButton JavaDoc prevBtn = new BasicArrowButton JavaDoc(SwingConstants.WEST);
97     private final BasicArrowButton JavaDoc closeCalendarBtn = new BasicArrowButton JavaDoc(SwingConstants.NORTH);
98
99     private JPopupMenu JavaDoc popup;
100
101     /**
102      * Create a new calendar combo-box object set with today's date.
103      */

104     public CalendarComboBox() {
105         this(new GregorianCalendar JavaDoc(), false);
106     }
107
108     /**
109      * Create a new calendar component, defining if the component should use
110      * the entire panel or just what would normally be an appropriate size.
111      * When enclosing this Component in a table or some other externally sized
112      * Container, use fillComponent of true.
113      *
114      * @param fillComponent true if the input box should take the entire area
115      * of this compoennt, false otherwise
116      */

117     public CalendarComboBox(boolean fillComponent) {
118         this(new GregorianCalendar JavaDoc(), fillComponent);
119     }
120
121     /**
122      * Create a new calendar combo-box object set with the given date.
123      *
124      * @param cal a calendar object
125      * @see java.util.GregorianCalendar
126      */

127     public CalendarComboBox(final Calendar JavaDoc cal, boolean fillComponent) {
128         super();
129
130         // set the calendar and input box date
131
Date JavaDoc date = cal.getTime();
132         current.setTime(date);
133         input.setValue(date);
134
135         // create the GUI elements and assign listeners
136
buildInputPanel();
137         
138
139         // intially, only display the input panel
140
if (fillComponent) {
141             setLayout(new BorderLayout JavaDoc());
142             add(inputPanel, BorderLayout.CENTER);
143         } else {
144             add(inputPanel);
145         }
146
147         buildCalendarDisplay();
148         registerListeners();
149     }
150
151     /*
152      * Creates a field and 'combo box' button above the calendar
153      * to allow user input.
154      */

155     private void buildInputPanel() {
156         inputPanel.setLayout(new BoxLayout JavaDoc(inputPanel, BoxLayout.X_AXIS));
157
158         input.setColumns(12);
159         inputPanel.add(input);
160
161         comboBtn.setActionCommand("combo");
162         inputPanel.add(comboBtn);
163     }
164
165     /*
166      * Builds the calendar panel to be displayed in the popup
167      */

168     private void buildCalendarDisplay() {
169
170         // Allow for individual cell selection and turn off
171
// grid lines.
172
table.setCellSelectionEnabled(true);
173         table.getTableHeader().setResizingAllowed(false);
174         table.getTableHeader().setReorderingAllowed(false);
175         table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
176         table.setShowGrid(false);
177
178         // Calendar (table) column headers
179
// Set column headers to weekday names as given by
180
// the default Locale.
181
//
182
// Need to re-map the retreived names. If used as is,
183
// the table model ends up with an extra empty column as
184
// the returned names begin at index 1, not zero.
185
String JavaDoc[] names = dfs.getShortWeekdays();
186
187         for (int i = 1; i < names.length; i++) {
188             dayNames[i - 1] = "" + names[i].charAt(0);
189         }
190
191         display.setColumnIdentifiers(dayNames);
192         table.setModel(display);
193
194         // Set the column widths. Need to turn
195
// auto resizing off to make this work.
196
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
197         int count = table.getColumnCount();
198
199         for (int i = 0; i < count; i++) {
200             TableColumn JavaDoc col = table.getColumnModel().getColumn(i);
201             col.setPreferredWidth(20);
202         }
203
204         // Column headers are only displayed automatically
205
// if the table is put in a JScrollPane. Don't want
206
// to use one here, so need to add the headers
207
// manually.
208
JTableHeader JavaDoc header = table.getTableHeader();
209         header.setFont(header.getFont().deriveFont(Font.BOLD));
210
211         JPanel JavaDoc panel = new JPanel JavaDoc();
212         panel.setLayout(new BoxLayout JavaDoc(panel, BoxLayout.Y_AXIS));
213         panel.add(header);
214         panel.add(table);
215
216         //calPanel.setBorder( new LineBorder( Color.BLACK ) );
217
calPanel.setLayout(new BorderLayout JavaDoc());
218         calPanel.add(buildCalendarNavigationPanel(), BorderLayout.NORTH);
219         calPanel.add(panel);
220     }
221
222     /*
223      * Creates a small panel above the month table to display the month and
224      * year along with the 'prevBtn', 'nextBtn' month selection buttons
225      * and a 'closeCalendarBtn'.
226      */

227     private JPanel JavaDoc buildCalendarNavigationPanel() {
228         JPanel JavaDoc panel = new JPanel JavaDoc() {
229
230         };
231         //panel.setLayout( new BoxLayout( panel, BoxLayout.X_AXIS ) );
232
panel.setLayout(new BorderLayout JavaDoc());
233
234         // Add a text display of the selected month and year.
235
// A JTextField is used for the label instead of a JLabel
236
// as it is easier to ensure a consistent size; JLabel
237
// expands and contracts with the text size
238
calLabel.setEditable(false);
239         calLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
240         int fontSize = calLabel.getFont().getSize();
241         calLabel.setFont(calLabel.getFont().deriveFont(Font.PLAIN, fontSize - 1));
242         panel.add(calLabel, BorderLayout.CENTER);
243
244         // set button commands and add to panel
245
prevBtn.setActionCommand("prevBtn");
246         nextBtn.setActionCommand("nextBtn");
247         closeCalendarBtn.setActionCommand("close");
248
249         panel.add(prevBtn, BorderLayout.WEST);
250         panel.add(nextBtn, BorderLayout.EAST);
251         //panel.add( closeCalendarBtn );
252

253         return panel;
254     }
255
256     /*
257      * Register all required listeners with appropriate
258      * components
259      */

260     private void registerListeners() {
261
262         ButtonActionListener btnListener = new ButtonActionListener();
263
264         // 'Combo-box' listeners
265
input.addKeyListener(new InputListener());
266         comboBtn.addActionListener(btnListener);
267
268         // Calendar (table) selection listener
269
// Must be added to both the table selection model
270
// and the column selection model; otherwise, new
271
// column selections on the same row are not recognized
272
CalendarSelectionListener listener = new CalendarSelectionListener();
273         table.getSelectionModel().addListSelectionListener(listener);
274         table.getColumnModel().getSelectionModel()
275             .addListSelectionListener(listener);
276
277         // Calendar navigation listeners
278
prevBtn.addActionListener(btnListener);
279         nextBtn.addActionListener(btnListener);
280         closeCalendarBtn.addActionListener(btnListener);
281
282     }
283
284     /*
285      * Fill the table model with the days in the selected month.
286      * Rows in the table correspond to 'weeks', columns to 'days'.
287      *
288      * Strategy:
289      * 1. get the first calendar day in the new month
290      * 2. find it's position in the first week of the month to
291      * determine the starting column for the day numbers
292      * 3. find the actual number of days in the month
293      * 4. fill the calendar with the day values, erasing any days
294      * left over from the old month
295      */

296     private void updateTable(Calendar JavaDoc cal) {
297
298         Calendar JavaDoc dayOne = new GregorianCalendar JavaDoc(cal.get(Calendar.YEAR),
299             cal.get(Calendar.MONTH),
300             1);
301
302         // compute the number of days in the month and
303
// the start column for the first day in the first week
304
int actualDays = cal.getActualMaximum(Calendar.DATE);
305         int startIndex = dayOne.get(Calendar.DAY_OF_WEEK) - 1;
306
307         // fill the calendar for the new month
308
int day = 1;
309         for (int row = 0; row < 6; row++) {
310             for (int col = 0; col < 7; col++) {
311                 if ((col < startIndex && row == 0) || day > actualDays) {
312                     // overwrite any left over values from old month
313
display.setValueAt("", row, col);
314                 } else {
315                     display.setValueAt(new Integer JavaDoc(day), row, col);
316                     day++;
317                 }
318             }
319         }
320
321         // set the month, year label
322
calLabel.setText(months[cal.get(Calendar.MONTH)] +
323             ", " + cal.get(Calendar.YEAR));
324
325         // set the calendar selection
326
table.changeSelection(cal.get(Calendar.WEEK_OF_MONTH) - 1,
327             cal.get(Calendar.DAY_OF_WEEK) - 1,
328             false, false);
329     }
330
331     /*
332      * Gets a Popup to hold the calendar display and determines
333      * it's position on the screen.
334      */

335     private JPopupMenu JavaDoc getPopup() {
336         Point JavaDoc p = input.getLocationOnScreen();
337         Dimension JavaDoc inputSize = input.getPreferredSize();
338         Dimension JavaDoc calendarSize = calPanel.getPreferredSize();
339
340         /*
341         if( ( p.y + calendarSize.height ) < screenSize.height) {
342             // will fit below input panel
343             popup = factory.getPopup( input, calPanel,
344                                        p.x, p.y + (int)inputSize.height );
345         } else {
346             // need to fit it above input panel
347             popup = factory.getPopup( input, calPanel,
348                                       p.x, p.y - (int)calendarSize.height );
349         }
350         */

351
352         // Greg Hinkle: Use JPopupMenu as it can automatically close when
353
// there is an outside click or it loses focus.
354
JPopupMenu JavaDoc menu = new JPopupMenu JavaDoc();
355         menu.add(calPanel);
356
357         return menu;
358     }
359
360     /*
361      * Returns the currently selected date as a <code>Calendar</code> object.
362      *
363      * @return Calendar the currently selected calendar date
364      */

365     public Calendar JavaDoc getDate() {
366         return current;
367     }
368
369     /**
370      * Sets the current date and updates the UI to reflect the new date.
371      *
372      * @param newDate the new date as a <code>Date</code> object.
373      * @author James Waldrop
374      * @see Date
375      */

376     public void setDate(Date JavaDoc newDate) {
377         current.setTime(newDate);
378         input.setValue(current.getTime());
379     }
380
381     /*
382      * Creates a custom model to back the table.
383      */

384     private class CalendarModel extends DefaultTableModel JavaDoc {
385
386         public CalendarModel(int row, int col) {
387             super(row, col);
388         }
389
390         /**
391          * Overrides the method to return an Integer class
392          * type for all columns. The numbers are automatically
393          * right-aligned by a default renderer that's supplied
394          * as part of JTable.
395          */

396         public Class JavaDoc getColumnClass(int column) {
397             return Integer JavaDoc.class;
398         }
399
400         /**
401          * Overrides the method to disable cell editing.
402          * The default is editable.
403          */

404         public boolean isCellEditable(int row, int col) {
405             return false;
406         }
407     }
408
409     /*
410      * Captures the 'prevBtn', 'nextBtn', 'comboBtn' and
411      * 'closeCalendarBtn' actions.
412      *
413      * The combo button is disabled when the popup is shown
414      * and enabled when the popup is hidden. Failure to do
415      * so results in the popup screen area not being cleared
416      * correctly if the user clicks the button while the popup
417      * is being displayed.
418      */

419     private class ButtonActionListener implements ActionListener JavaDoc {
420         public void actionPerformed(ActionEvent JavaDoc e) {
421             String JavaDoc cmd = e.getActionCommand();
422
423             if (cmd.equals("prevBtn")) {
424                 current.add(Calendar.MONTH, -1);
425                 input.setValue(current.getTime());
426             } else if (cmd.equals("nextBtn")) {
427                 current.add(Calendar.MONTH, 1);
428                 input.setValue(current.getTime());
429             } else if (cmd.equals("close")) {
430                 popup.hide();
431                 comboBtn.setEnabled(true);
432             } else {
433                 //comboBtn.setEnabled( false );
434
popup = getPopup();
435                 calPanel.setPreferredSize(new Dimension JavaDoc((int) inputPanel.getPreferredSize().getWidth(), (int) calPanel.getPreferredSize().getHeight()));
436                 popup.show(inputPanel, 0, inputPanel.getHeight());
437             }
438
439             updateTable(current);
440         }
441     }
442
443     /*
444      * Captures a user selection in the calendar display and
445      * changes the value in the 'combo box' to match the selected date.
446      *
447      */

448     private class CalendarSelectionListener implements ListSelectionListener JavaDoc {
449
450         public void valueChanged(ListSelectionEvent JavaDoc e) {
451             if (!e.getValueIsAdjusting()) {
452                 int row = table.getSelectedRow();
453                 int col = table.getSelectedColumn();
454
455                 Object JavaDoc value = null;
456                 try {
457                     value = display.getValueAt(row, col);
458                 } catch (ArrayIndexOutOfBoundsException JavaDoc ex) {
459                     // ignore, happens when the calendar is
460
// displayed for the first time
461
}
462
463                 if (value instanceof Integer JavaDoc) {
464                     int day = ((Integer JavaDoc) value).intValue();
465                     current.set(Calendar.DATE, day);
466                     input.setValue(current.getTime());
467                 }
468             }
469         }
470     }
471
472     /*
473      * Captures user input in the 'combo box'
474      * If the input is a valid date and the user pressed
475      * ENTER or TAB, the calendar selection is updated
476      */

477     private class InputListener extends KeyAdapter JavaDoc {
478         public void keyTyped(KeyEvent JavaDoc e) {
479
480             DateFormat JavaDoc df = DateFormat.getDateInstance();
481             Date JavaDoc date = null;
482
483             try {
484                 date = df.parse(input.getText());
485             } catch (ParseException JavaDoc ex) {
486                 // ignore invalid dates
487
}
488
489             // change the calendar selection if the date is valid
490
// and the user hit ENTER or TAB
491
char c = e.getKeyChar();
492             if (date != null &&
493                 (c == KeyEvent.VK_ENTER || c == KeyEvent.VK_TAB)) {
494                 current.setTime(date);
495                 updateTable(current);
496             }
497         }
498     }
499
500     public static void main(String JavaDoc[] args) {
501         JFrame JavaDoc frame = new JFrame JavaDoc("Calendar combo box test");
502         frame.getContentPane().add(new CalendarComboBox(true));
503
504         frame.setVisible(true);
505         frame.addWindowListener(new WindowAdapter JavaDoc() {
506             public void windowClosing(WindowEvent JavaDoc e) {
507                 System.exit(0);
508             }
509         });
510     }
511 }
Popular Tags