Source code: com/theotherbell/ui/DatePicker.java
1 /***********************************************************
2
3 DatePicker.java
4 Copyright (C) 2003 Brenda Bell
5
6 ***********************************************************/
7
8 /***********************************************************
9
10 This file is part of JavaDatePicker.
11
12 JavaDatePicker is free software; you can redistribute it
13 and/or modify it under the terms of the GNU Lesser
14 General Public License as published by the Free Software
15 Foundation; either version 2 of the License, or (at your
16 option) any later version.
17
18 JavaDatePicker is distributed in the hope that it will
19 be useful, but WITHOUT ANY WARRANTY; without even the
20 implied warranty of MERCHANTABILITY or FITNESS FOR A
21 PARTICULAR PURPOSE. See the GNU Lesser General Public
22 License for more details.
23
24 You should have received a copy of the GNU Lesser
25 General Public License along with JavaDatePicker; if
26 not, write to the Free Software Foundation, Inc., 59
27 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28
29 ***********************************************************/
30
31 package com.theotherbell.ui;
32
33 import java.awt.*;
34 import java.awt.event.*;
35 import java.util.GregorianCalendar;
36 import java.util.Date;
37 import java.util.Calendar;
38 import java.text.DateFormat;
39 import java.text.FieldPosition;
40
41 import javax.swing.*;
42 import javax.swing.plaf.BorderUIResource;
43
44 import org.netbeans.lib.awtextra.AbsoluteConstraints;
45 import org.netbeans.lib.awtextra.AbsoluteLayout;
46
47 /**
48 * GUI component that allows the user to choose a date from a calendar.
49 * Usage is illustrated in the sample code below:
50 * <PRE>
51 * JDialog dlg = new JDialog(new Frame(), true);
52 * DatePicker dp = new DatePicker();
53 * dp.setHideOnSelect(false);
54 * dlg.getContentPane().add(dp);
55 * dlg.pack();
56 * dlg.show();
57 * System.out.println(dp.getDate().toString());
58 * dlg.dispose();
59 * System.exit(0);
60 * </PRE>
61 * @author B. Bell
62 * @version 1.1a
63 */
64 public final class DatePicker extends JPanel {
65
66 /**
67 * X coordinate for upper left corner of week 1 day 1.
68 */
69 private static final int startX = 10;
70
71 /**
72 * Y coordinate for upper left corner of week 1 day 1.
73 */
74 private static final int startY = 60;
75
76 /**
77 * Small font.
78 */
79 private static final Font smallFont = new Font("Dialog", Font.PLAIN, 10);
80
81 /**
82 * Large font.
83 */
84 private static final Font largeFont = new Font("Dialog", Font.PLAIN, 12);
85
86 /**
87 * Insets for day components and small buttons.
88 */
89 private static final Insets insets = new Insets(2, 2, 2, 2);
90
91 /**
92 * Highlighted color.
93 */
94 private static final Color highlight = new Color(255, 255, 204);
95
96 /**
97 * Enabled color.
98 */
99 private static final Color white = new Color(255, 255, 255);
100
101 /**
102 * Disabled color.
103 */
104 private static final Color gray = new Color(204, 204, 204);
105
106 /**
107 * Most recently selected day component.
108 */
109 private Component selectedDay = null;
110
111 /**
112 * Currently selected date.
113 */
114 private GregorianCalendar selectedDate = null;
115
116 /**
117 * Tracks the original date set when the component was created.
118 */
119 private GregorianCalendar originalDate = null;
120
121 /**
122 * When true, the panel will be hidden as soon as a day is
123 * selected by clicking the day or clicking the Today button.
124 */
125 private boolean hideOnSelect = true;
126
127 /**
128 * When clicked, displays the previous month.
129 */
130 private final JButton backButton = new JButton();
131
132 /**
133 * Displays the currently selected month and year.
134 */
135 private final JLabel monthAndYear = new JLabel();
136
137 /**
138 * When clicked, displays the next month.
139 */
140 private final JButton forwardButton = new JButton();
141
142 /**
143 * Column headings for the days of the week.
144 */
145 private final JTextField[] dayHeadings = new JTextField[]{
146 new JTextField("S"),
147 new JTextField("M"),
148 new JTextField("T"),
149 new JTextField("W"),
150 new JTextField("T"),
151 new JTextField("F"),
152 new JTextField("S")};
153
154 /**
155 * 2-dimensional array for 6 weeks of 7 days each.
156 */
157 private final JTextField[][] daysInMonth = new JTextField[][]{
158 {new JTextField(),
159 new JTextField(),
160 new JTextField(),
161 new JTextField(),
162 new JTextField(),
163 new JTextField(),
164 new JTextField()},
165 {new JTextField(),
166 new JTextField(),
167 new JTextField(),
168 new JTextField(),
169 new JTextField(),
170 new JTextField(),
171 new JTextField()},
172 {new JTextField(),
173 new JTextField(),
174 new JTextField(),
175 new JTextField(),
176 new JTextField(),
177 new JTextField(),
178 new JTextField()},
179 {new JTextField(),
180 new JTextField(),
181 new JTextField(),
182 new JTextField(),
183 new JTextField(),
184 new JTextField(),
185 new JTextField()},
186 {new JTextField(),
187 new JTextField(),
188 new JTextField(),
189 new JTextField(),
190 new JTextField(),
191 new JTextField(),
192 new JTextField()},
193 {new JTextField(),
194 new JTextField(),
195 new JTextField(),
196 new JTextField(),
197 new JTextField(),
198 new JTextField(),
199 new JTextField()}
200 };
201
202 /**
203 * When clicked, sets the selected day to the current date.
204 */
205 private final JButton todayButton = new JButton();
206
207 /**
208 * When clicked, hides the calendar and sets the selected date to "empty".
209 */
210 private final JButton cancelButton = new JButton();
211
212 /**
213 * Default constructor that sets the currently selected date to the
214 * current date.
215 */
216 public DatePicker() {
217 super();
218 selectedDate = getToday();
219 init();
220 }
221
222 /**
223 * Alternate constructor that sets the currently selected date to the
224 * specified date if non-null.
225 * @param initialDate
226 */
227 public DatePicker(final Date initialDate) {
228 super();
229 if (null == initialDate)
230 selectedDate = getToday();
231 else
232 (selectedDate = new GregorianCalendar()).setTime(initialDate);
233 originalDate = new GregorianCalendar(
234 selectedDate.get(Calendar.YEAR),
235 selectedDate.get(Calendar.MONTH),
236 selectedDate.get(Calendar.DATE));
237 init();
238 }
239
240 /**
241 * Returns true if the panel will be made invisible after a day is
242 * selected.
243 * @return true or false
244 */
245 public boolean isHideOnSelect() {
246 return hideOnSelect;
247 }
248
249 /**
250 * Controls whether the panel will be made invisible after a day is
251 * selected.
252 * @param hideOnSelect
253 */
254 public void setHideOnSelect(final boolean hideOnSelect) {
255 if (this.hideOnSelect != hideOnSelect) {
256 this.hideOnSelect = hideOnSelect;
257 initButtons(false);
258 }
259 }
260
261 /**
262 * Returns the currently selected date.
263 * @return date
264 */
265 public Date getDate() {
266 if (null != selectedDate)
267 return selectedDate.getTime();
268 return null;
269 }
270
271 /**
272 * Initializes the panel components according to the current value
273 * of selectedDate.
274 */
275 private void init() {
276 setLayout(new AbsoluteLayout());
277 this.setMinimumSize(new Dimension(161, 226));
278 this.setMaximumSize(getMinimumSize());
279 this.setPreferredSize(getMinimumSize());
280 this.setBorder(new BorderUIResource.EtchedBorderUIResource());
281
282 backButton.setFont(smallFont);
283 backButton.setText("<");
284 backButton.setMargin(insets);
285 backButton.setDefaultCapable(false);
286 backButton.addActionListener(new ActionListener() {
287 public void actionPerformed(final ActionEvent evt) {
288 onBackClicked(evt);
289 }
290 });
291 add(backButton, new AbsoluteConstraints(10, 10, 20, 20));
292
293 monthAndYear.setFont(largeFont);
294 monthAndYear.setHorizontalAlignment(JTextField.CENTER);
295 monthAndYear.setText(formatDateText(selectedDate.getTime()));
296 add(monthAndYear, new AbsoluteConstraints(30, 10, 100, 20));
297
298 forwardButton.setFont(smallFont);
299 forwardButton.setText(">");
300 forwardButton.setMargin(insets);
301 forwardButton.setDefaultCapable(false);
302 forwardButton.addActionListener(new ActionListener() {
303 public void actionPerformed(final ActionEvent evt) {
304 onForwardClicked(evt);
305 }
306 });
307 add(forwardButton, new AbsoluteConstraints(130, 10, 20, 20));
308
309 // layout the column headings for the days of the week
310 int x = startX;
311 for (int ii = 0; ii < dayHeadings.length; ii++) {
312 dayHeadings[ii].setBackground(gray);
313 dayHeadings[ii].setEditable(false);
314 dayHeadings[ii].setFont(smallFont);
315 dayHeadings[ii].setHorizontalAlignment(JTextField.CENTER);
316 dayHeadings[ii].setFocusable(false);
317 add(dayHeadings[ii], new AbsoluteConstraints(x, 40, 21, 21));
318 x += 20;
319 }
320
321 // layout the days of the month
322 x = startX;
323 int y = startY;
324 for (int ii = 0; ii < daysInMonth.length; ii++) {
325 for (int jj = 0; jj < daysInMonth[ii].length; jj++) {
326 daysInMonth[ii][jj].setBackground(gray);
327 daysInMonth[ii][jj].setEditable(false);
328 daysInMonth[ii][jj].setFont(smallFont);
329 daysInMonth[ii][jj].setHorizontalAlignment(JTextField.RIGHT);
330 daysInMonth[ii][jj].setText("");
331 daysInMonth[ii][jj].setFocusable(false);
332 daysInMonth[ii][jj].addMouseListener(new MouseAdapter() {
333 public void mouseClicked(final MouseEvent evt) {
334 onDayClicked(evt);
335 }
336 });
337 add(daysInMonth[ii][jj], new AbsoluteConstraints(x, y, 21, 21));
338 x += 20;
339 }
340 x = startX;
341 y += 20;
342 }
343
344 initButtons(true);
345
346 calculateCalendar();
347 }
348
349 /**
350 * Initializes Today and Cancel buttons dependent on whether hideOnSelect
351 * is set; if the panel will stay open, the Cancel button is invisible.
352 * @param firstTime
353 */
354 private void initButtons(final boolean firstTime) {
355 if (firstTime) {
356 final Dimension buttonSize = new Dimension(68, 24);
357 todayButton.setFont(largeFont);
358 todayButton.setText("Today");
359 todayButton.setMargin(insets);
360 todayButton.setMaximumSize(buttonSize);
361 todayButton.setMinimumSize(buttonSize);
362 todayButton.setPreferredSize(buttonSize);
363 todayButton.setDefaultCapable(true);
364 todayButton.setSelected(true);
365 todayButton.addActionListener(new ActionListener() {
366 public void actionPerformed(final ActionEvent evt) {
367 onToday(evt);
368 }
369 });
370
371 cancelButton.setFont(largeFont);
372 cancelButton.setText("Cancel");
373 cancelButton.setMargin(insets);
374 cancelButton.setMaximumSize(buttonSize);
375 cancelButton.setMinimumSize(buttonSize);
376 cancelButton.setPreferredSize(buttonSize);
377 cancelButton.addActionListener(new ActionListener() {
378 public void actionPerformed(final ActionEvent evt) {
379 onCancel(evt);
380 }
381 });
382 } else {
383 this.remove(todayButton);
384 this.remove(cancelButton);
385 }
386
387 if (hideOnSelect) {
388 add(todayButton, new AbsoluteConstraints(25, 190, 52, -1));
389 add(cancelButton, new AbsoluteConstraints(87, 190, 52, -1));
390 } else {
391 add(todayButton, new AbsoluteConstraints(55, 190, 52, -1));
392 }
393 }
394
395 /**
396 * Event handler for the Today button that sets the currently selected
397 * date to the current date.
398 * @param evt
399 */
400 private void onToday(final java.awt.event.ActionEvent evt) {
401 selectedDate = getToday();
402 setVisible(!hideOnSelect);
403 if (isVisible()) { // don't bother with calculation if not visible
404 monthAndYear.setText(formatDateText(selectedDate.getTime()));
405 calculateCalendar();
406 }
407 }
408
409 /**
410 * Event handler for the Cancel button that unsets the currently selected date.
411 * @param evt
412 */
413 private void onCancel(final ActionEvent evt) {
414 selectedDate = originalDate;
415 setVisible(!hideOnSelect);
416 }
417
418 /**
419 * Event handler for the forward button that increments the currently
420 * selected month.
421 * @param evt
422 */
423 private void onForwardClicked(final java.awt.event.ActionEvent evt) {
424 final int day = selectedDate.get(Calendar.DATE);
425 selectedDate.set(Calendar.DATE, 1);
426 selectedDate.add(Calendar.MONTH, 1);
427 selectedDate.set(Calendar.DATE,
428 Math.min(day, calculateDaysInMonth(selectedDate)));
429 monthAndYear.setText(formatDateText(selectedDate.getTime()));
430 calculateCalendar();
431 }
432
433 /**
434 * Event handler for the back button that decrements the currently selected
435 * month.
436 * @param evt
437 */
438 private void onBackClicked(final java.awt.event.ActionEvent evt) {
439 final int day = selectedDate.get(Calendar.DATE);
440 selectedDate.set(Calendar.DATE, 1);
441 selectedDate.add(Calendar.MONTH, -1);
442 selectedDate.set(Calendar.DATE,
443 Math.min(day, calculateDaysInMonth(selectedDate)));
444 monthAndYear.setText(formatDateText(selectedDate.getTime()));
445 calculateCalendar();
446 }
447
448 /**
449 * Event handler that sets the currently selected date to the clicked day.
450 * @param evt
451 */
452 private void onDayClicked(final java.awt.event.MouseEvent evt) {
453 final javax.swing.JTextField fld = (javax.swing.JTextField) evt.getSource();
454 if (!"".equals(fld.getText())) {
455 if (null != selectedDay) {
456 selectedDay.setBackground(white);
457 }
458 fld.setBackground(highlight);
459 selectedDay = fld;
460 selectedDate.set(
461 Calendar.DATE,
462 Integer.parseInt(fld.getText()));
463 setVisible(!hideOnSelect);
464 }
465 }
466
467 /**
468 * Returns the current date.
469 * @return date
470 */
471 private static GregorianCalendar getToday() {
472 final GregorianCalendar gc = new GregorianCalendar();
473 gc.set(Calendar.HOUR_OF_DAY, 0);
474 gc.set(Calendar.MINUTE, 0);
475 gc.set(Calendar.SECOND, 0);
476 gc.set(Calendar.MILLISECOND, 0);
477 return gc;
478 }
479
480 /**
481 * Calculates the days of the month.
482 */
483 private void calculateCalendar() {
484 // clear the selected date
485 if (null != selectedDay) {
486 selectedDay.setBackground(white);
487 selectedDay = null;
488 }
489
490 // get the first day of the selected year and month
491 final GregorianCalendar c = new GregorianCalendar(
492 selectedDate.get(Calendar.YEAR),
493 selectedDate.get(Calendar.MONTH),
494 1);
495
496 // figure out the maximum number of days in the month
497 final int maxDay = calculateDaysInMonth(c);
498
499 // figure out the day that should be selected in this month
500 // based on the previously selected day and the maximum number
501 // of days in the month
502 final int selectedDay = Math.min(maxDay, selectedDate.get(
503 Calendar.DATE));
504
505 // clear the days up to the first day of the month
506 int dow = c.get(Calendar.DAY_OF_WEEK);
507 for (int dd = 0; dd < dow; dd++) {
508 daysInMonth[0][dd].setText("");
509 daysInMonth[0][dd].setBackground(gray);
510 }
511
512 // construct the days in the selected month
513 int week;
514 do {
515 week = c.get(Calendar.WEEK_OF_MONTH);
516 dow = c.get(Calendar.DAY_OF_WEEK);
517 final JTextField fld = this.daysInMonth[week - 1][dow - 1];
518 fld.setText(Integer.toString(c.get(Calendar.DATE)));
519 if (selectedDay == c.get(Calendar.DATE)) {
520 fld.setBackground(highlight);
521 this.selectedDay = fld;
522 } else
523 fld.setBackground(white);
524 if (c.get(Calendar.DATE) >= maxDay)
525 break;
526 c.add(Calendar.DATE, 1);
527 } while (c.get(Calendar.DATE) <= maxDay);
528
529 // clear all the days after the last day of the month
530 week--;
531 for (int ww = week; ww < daysInMonth.length; ww++) {
532 for (int dd = dow; dd < daysInMonth[ww].length; dd++) {
533 daysInMonth[ww][dd].setText("");
534 daysInMonth[ww][dd].setBackground(gray);
535 }
536 dow = 0;
537 }
538
539 // set the currently selected date
540 c.set(Calendar.DATE, selectedDay);
541 selectedDate = c;
542 }
543
544 /**
545 * Calculates the number of days in the specified month.
546 * @param c
547 * @return number of days in the month
548 */
549 private static int calculateDaysInMonth(final Calendar c) {
550 int daysInMonth = 0;
551 switch (c.get(Calendar.MONTH)) {
552 case 0:
553 case 2:
554 case 4:
555 case 6:
556 case 7:
557 case 9:
558 case 11:
559 daysInMonth = 31;
560 break;
561 case 3:
562 case 5:
563 case 8:
564 case 10:
565 daysInMonth = 30;
566 break;
567 case 1:
568 final int year = c.get(Calendar.YEAR);
569 daysInMonth =
570 (0 == year % 1000) ? 29 :
571 (0 == year % 100) ? 28 :
572 (0 == year % 4) ? 29 : 28;
573 break;
574 }
575 return daysInMonth;
576 }
577
578 /**
579 * Returns a short string representation of the specified date (January, 2001).
580 * @param dt
581 * @return short string
582 */
583 private static String formatDateText(final Date dt) {
584 final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
585
586 final StringBuffer mm = new StringBuffer();
587 final StringBuffer yy = new StringBuffer();
588 final FieldPosition mmfp = new FieldPosition(DateFormat.MONTH_FIELD);
589 final FieldPosition yyfp = new FieldPosition(DateFormat.YEAR_FIELD);
590 df.format(dt, mm, mmfp);
591 df.format(dt, yy, yyfp);
592 return (mm.toString().substring(mmfp.getBeginIndex(), mmfp.getEndIndex()) +
593 " " + yy.toString().substring(yyfp.getBeginIndex(), yyfp.getEndIndex()));
594 }
595
596 }