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