001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.client.util;
019:
020: import java.awt.BorderLayout;
021: import java.awt.Color;
022: import java.awt.FlowLayout;
023: import java.awt.GridLayout;
024: import java.awt.LayoutManager;
025: import java.awt.event.ActionEvent;
026: import java.awt.event.ActionListener;
027: import java.awt.event.FocusEvent;
028: import java.awt.event.FocusListener;
029: import java.awt.event.ItemEvent;
030: import java.awt.event.ItemListener;
031: import java.awt.event.KeyEvent;
032: import java.awt.event.KeyListener;
033: import java.awt.event.MouseEvent;
034: import java.awt.event.MouseListener;
035: import java.util.Calendar;
036: import java.util.Date;
037: import java.util.GregorianCalendar;
038:
039: import javax.swing.BorderFactory;
040: import javax.swing.JButton;
041: import javax.swing.JComboBox;
042: import javax.swing.JLabel;
043: import javax.swing.JPanel;
044: import javax.swing.border.Border;
045:
046: import de.finix.contelligent.client.i18n.Resources;
047:
048: /**
049: * Custom dialog box to enter dates. The <code>DateChooser</code> class
050: * presents a calendar and allows the user to visually select a day, month and
051: * year so that it is impossible to enter an invalid date.
052: */
053: public class DateChooser extends JPanel implements ItemListener,
054: MouseListener, FocusListener, KeyListener, ActionListener {
055: /** Names of the months. */
056: private static final String[] MONTHS = new String[] { "January",
057: "February", "March", "April", "May", "June", "July",
058: "August", "September", "October", "November", "December" };
059:
060: /** Names of the days of the week. */
061: private static final String[] DAYS = new String[] { "Sun", "Mon",
062: "Tue", "Wed", "Thu", "Fri", "Sat" };
063:
064: /**
065: * Text color of the days of the weeks, used as column headers in the
066: * calendar.
067: */
068: private static final Color WEEK_DAYS_FOREGROUND = Color.black;
069:
070: /** Text color of the days' numbers in the calendar. */
071: private static final Color DAYS_FOREGROUND = Color.blue;
072:
073: /** Background color of the selected day in the calendar. */
074: private static final Color SELECTED_DAY_FOREGROUND = Color.white;
075:
076: /** Text color of the selected day in the calendar. */
077: private static final Color SELECTED_DAY_BACKGROUND = Color.blue;
078:
079: /** Empty border, used when the calendar does not have the focus. */
080: private static final Border EMPTY_BORDER = BorderFactory
081: .createEmptyBorder(1, 1, 1, 1);
082:
083: /**
084: * Border used to highlight the selected day when the calendar has the
085: * focus.
086: */
087: private static final Border FOCUSED_BORDER = BorderFactory
088: .createLineBorder(Color.yellow, 1);
089:
090: /** First year that can be selected. */
091: private static final int FIRST_YEAR = 1900;
092:
093: /** Last year that can be selected. */
094: private static final int LAST_YEAR = 2100;
095:
096: /** Auxiliary variable to compute dates. */
097: private GregorianCalendar calendar;
098:
099: /**
100: * Calendar, as a matrix of labels. The first row represents the first week
101: * of the month, the second row, the second week, and so on. Each column
102: * represents a day of the week, the first is Sunday, and the last is
103: * Saturday. The label's text is the number of the corresponding day.
104: */
105: private JLabel[][] days;
106:
107: /**
108: * Day selection control. It is just a panel that can receive the focus. The
109: * actual user interaction is driven by the <code>DateChooser</code>
110: * class.
111: */
112: private FocusablePanel daysGrid;
113:
114: /** Month selection control. */
115: private JComboBox month;
116:
117: /** Year selection control. */
118: private JComboBox year;
119:
120: private JComboBox hour;
121:
122: private JComboBox minute;
123:
124: private JButton today;
125:
126: /**
127: * Day of the week (0=Sunday) corresponding to the first day of the selected
128: * month. Used to calculate the position, in the calendar ({@link #days}),
129: * corresponding to a given day.
130: */
131: private int offset;
132:
133: /** Last day of the selected month. */
134: private int lastDay;
135:
136: /** Selected day. */
137: private JLabel day;
138:
139: /**
140: * <code>true</code> if the "Ok" button was clicked to close the dialog
141: * box, <code>false</code> otherwise.
142: */
143: private boolean okClicked;
144:
145: /**
146: * Custom panel that can receive the focus. Used to implement the calendar
147: * control.
148: */
149: private static class FocusablePanel extends JPanel {
150: /**
151: * Constructs a new <code>FocusablePanel</code> with the given layout
152: * manager.
153: *
154: * @param layout
155: * layout manager
156: */
157: public FocusablePanel(LayoutManager layout) {
158: super (layout);
159: }
160:
161: /**
162: * Always returns <code>true</code>, since
163: * <code>FocusablePanel</code> can receive the focus.
164: *
165: * @return <code>true</code>
166: */
167: public boolean isFocusTraversable() {
168: return true;
169: }
170: }
171:
172: /**
173: * Initializes this <code>DateChooser</code> object. Creates the controls,
174: * registers listeners and initializes the dialog box.
175: */
176: private void construct() {
177: calendar = new GregorianCalendar();
178:
179: month = new JComboBox(MONTHS);
180: month.addItemListener(this );
181:
182: year = new JComboBox();
183: for (int i = FIRST_YEAR; i <= LAST_YEAR; i++)
184: year.addItem(Integer.toString(i));
185: year.addItemListener(this );
186:
187: hour = new JComboBox();
188: for (int i = 0; i < 24; i++) {
189: hour.addItem(Integer.toString(i));
190: }
191:
192: minute = new JComboBox();
193: for (int i = 0; i < 60; i++) {
194: minute.addItem(i < 10 ? "0" + Integer.toString(i) : Integer
195: .toString(i));
196: }
197:
198: days = new JLabel[7][7];
199: for (int i = 0; i < 7; i++) {
200: days[0][i] = new JLabel(DAYS[i], JLabel.RIGHT);
201: days[0][i].setForeground(WEEK_DAYS_FOREGROUND);
202: }
203: for (int i = 1; i < 7; i++)
204: for (int j = 0; j < 7; j++) {
205: days[i][j] = new JLabel(" ", JLabel.RIGHT);
206: days[i][j].setForeground(DAYS_FOREGROUND);
207: days[i][j].setBackground(SELECTED_DAY_BACKGROUND);
208: days[i][j].setBorder(EMPTY_BORDER);
209: days[i][j].addMouseListener(this );
210: }
211:
212: today = new JButton(Resources.getLocalString("today"));
213: today.addActionListener(this );
214:
215: JPanel monthYear = new JPanel();
216: monthYear.setLayout(new FlowLayout(FlowLayout.LEFT));
217: monthYear.add(month);
218: monthYear.add(year);
219: monthYear.add(today);
220:
221: JPanel hourMinute = new JPanel();
222: hourMinute.setLayout(new FlowLayout(FlowLayout.LEFT));
223: hourMinute.add(new JLabel(Resources
224: .getLocalString("time_of_day")
225: + ": "));
226: hourMinute.add(hour);
227: hourMinute.add(new JLabel(":"));
228: hourMinute.add(minute);
229:
230: daysGrid = new FocusablePanel(new GridLayout(7, 7, 5, 0));
231: daysGrid.addFocusListener(this );
232: daysGrid.addKeyListener(this );
233: for (int i = 0; i < 7; i++)
234: for (int j = 0; j < 7; j++)
235: daysGrid.add(days[i][j]);
236: daysGrid.setBackground(Color.white);
237: daysGrid.setBorder(BorderFactory.createLoweredBevelBorder());
238: JPanel daysPanel = new JPanel();
239: daysPanel.add(daysGrid);
240:
241: // Container dialog = getContentPane();
242: add(monthYear, BorderLayout.NORTH);
243: add(daysPanel, BorderLayout.WEST);
244: add(hourMinute, BorderLayout.SOUTH);
245:
246: // pack();
247: // setResizable( false );
248: }
249:
250: public void setEnabled(boolean enabled) {
251: super .setEnabled(enabled);
252: month.setEnabled(enabled);
253: year.setEnabled(enabled);
254: today.setEnabled(enabled);
255: daysGrid.setEnabled(enabled);
256: hour.setEnabled(enabled);
257: minute.setEnabled(enabled);
258: for (int i = 1; i < 7; i++)
259: for (int j = 0; j < 7; j++) {
260: days[i][j].setEnabled(enabled);
261: }
262: if (!enabled) {
263: setSelected(null); // Unselect date
264: }
265: }
266:
267: /**
268: * Gets the selected day, as an <code>int</code>. Parses the text of the
269: * selected label in the calendar to get the day.
270: *
271: * @return the selected day or -1 if there is no day selected
272: */
273: private int getSelectedDay() {
274: if (day == null)
275: return -1;
276: try {
277: return Integer.parseInt(day.getText());
278: } catch (NumberFormatException e) {
279: }
280: return -1;
281: }
282:
283: /**
284: * Sets the selected day. The day is specified as the label control, in the
285: * calendar, corresponding to the day to select.
286: *
287: * @param newDay
288: * day to select
289: */
290: private void setSelected(JLabel newDay) {
291: if (day != null) {
292: day.setForeground(DAYS_FOREGROUND);
293: day.setOpaque(false);
294: day.setBorder(EMPTY_BORDER);
295: }
296: day = newDay;
297: if (day != null) {
298: day.setForeground(SELECTED_DAY_FOREGROUND);
299: day.setOpaque(true);
300: if (daysGrid.hasFocus())
301: day.setBorder(FOCUSED_BORDER);
302: }
303: }
304:
305: /**
306: * Sets the selected day. The day is specified as the number of the day, in
307: * the month, to selected. The function compute the corresponding control to
308: * select.
309: *
310: * @param newDay
311: * day to select
312: */
313: private void setSelected(int newDay) {
314: setSelected(days[(newDay + offset - 1) / 7 + 1][(newDay
315: + offset - 1) % 7]);
316: }
317:
318: /**
319: * Updates the calendar. This function updates the calendar panel to reflect
320: * the month and year selected. It keeps the same day of the month that was
321: * selected, except if it is beyond the last day of the month. In this case,
322: * the last day of the month is selected.
323: */
324: private void update() {
325: int iday = getSelectedDay();
326: for (int i = 0; i < 7; i++) {
327: days[1][i].setText(" ");
328: days[5][i].setText(" ");
329: days[6][i].setText(" ");
330: }
331: calendar.set(Calendar.DATE, 1);
332: calendar.set(Calendar.MONTH, month.getSelectedIndex()
333: + Calendar.JANUARY);
334: calendar.set(Calendar.YEAR, year.getSelectedIndex()
335: + FIRST_YEAR);
336:
337: offset = calendar.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY;
338: lastDay = calendar.getActualMaximum(Calendar.DATE);
339: for (int i = 0; i < lastDay; i++)
340: days[(i + offset) / 7 + 1][(i + offset) % 7].setText(String
341: .valueOf(i + 1));
342: if (iday != -1) {
343: if (iday > lastDay)
344: iday = lastDay;
345: setSelected(iday);
346: }
347: }
348:
349: /**
350: * Called when the calendar gains the focus. Just re-sets the selected day
351: * so that it is redrawn with the border that indicate focus.
352: */
353: public void focusGained(FocusEvent e) {
354: setSelected(day);
355: }
356:
357: /**
358: * Called when the calendar loses the focus. Just re-sets the selected day
359: * so that it is redrawn without the border that indicate focus.
360: */
361: public void focusLost(FocusEvent e) {
362: setSelected(day);
363: }
364:
365: /**
366: * Called when a new month or year is selected. Updates the calendar to
367: * reflect the selection.
368: */
369: public void itemStateChanged(ItemEvent e) {
370: update();
371: }
372:
373: /**
374: * Called when a key is pressed and the calendar has the focus. Handles the
375: * arrow keys so that the user can select a day using the keyboard.
376: */
377: public void keyPressed(KeyEvent e) {
378: int iday = getSelectedDay();
379: switch (e.getKeyCode()) {
380: case KeyEvent.VK_LEFT:
381: if (iday > 1)
382: setSelected(iday - 1);
383: break;
384: case KeyEvent.VK_RIGHT:
385: if (iday < lastDay)
386: setSelected(iday + 1);
387: break;
388: case KeyEvent.VK_UP:
389: if (iday > 7)
390: setSelected(iday - 7);
391: break;
392: case KeyEvent.VK_DOWN:
393: if (iday <= lastDay - 7)
394: setSelected(iday + 7);
395: break;
396: }
397: }
398:
399: /**
400: * Called when the mouse is clicked on a day in the calendar. Selects the
401: * clicked day.
402: */
403: public void mouseClicked(MouseEvent e) {
404: if (isEnabled()) {
405: JLabel day = (JLabel) e.getSource();
406: if (!day.getText().equals(" "))
407: setSelected(day);
408: daysGrid.requestFocus();
409: }
410: }
411:
412: public void keyReleased(KeyEvent e) {
413: }
414:
415: public void keyTyped(KeyEvent e) {
416: }
417:
418: public void mouseEntered(MouseEvent e) {
419: }
420:
421: public void mouseExited(MouseEvent e) {
422: }
423:
424: public void mousePressed(MouseEvent e) {
425: }
426:
427: public void mouseReleased(MouseEvent e) {
428: }
429:
430: public DateChooser() {
431: super ();
432: setLayout(new BorderLayout());
433: construct();
434: }
435:
436: /**
437: * Selects a date. Displays the dialog box, with a given date as the
438: * selected date, and allows the user select a new date.
439: *
440: * @param date
441: * initial date
442: *
443: * @return the new date selected or <code>null</code> if the user press
444: * "Cancel" or closes the dialog box
445: */
446: public void select(Date date) {
447: calendar.setTime(date);
448: int _day = calendar.get(Calendar.DATE);
449: int _month = calendar.get(Calendar.MONTH);
450: int _year = calendar.get(Calendar.YEAR);
451: int _hour = calendar.get(Calendar.HOUR_OF_DAY);
452: int _minute = calendar.get(Calendar.MINUTE);
453:
454: year.setSelectedIndex(_year - FIRST_YEAR);
455: month.setSelectedIndex(_month - Calendar.JANUARY);
456: setSelected(_day);
457: hour.setSelectedIndex(_hour);
458: minute.setSelectedIndex(_minute);
459: }
460:
461: public Date getDate() {
462: calendar.set(Calendar.DATE, getSelectedDay());
463: calendar.set(Calendar.MONTH, month.getSelectedIndex()
464: + Calendar.JANUARY);
465: calendar.set(Calendar.YEAR, year.getSelectedIndex()
466: + FIRST_YEAR);
467: calendar.set(Calendar.HOUR_OF_DAY, hour.getSelectedIndex());
468: calendar.set(Calendar.MINUTE, minute.getSelectedIndex());
469: return calendar.getTime();
470: }
471:
472: public void actionPerformed(ActionEvent e) {
473: if (e.getSource() == today) {
474: select(new Date());
475: }
476: }
477:
478: }
|