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 }