001/* ======================================================================== 002 * JCommon : a free general purpose class library for the Java(tm) platform 003 * ======================================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jcommon/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * --------------------- 028 * DateChooserPanel.java 029 * --------------------- 030 * (C) Copyright 2000-2004, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: DateChooserPanel.java,v 1.11 2007/11/02 17:50:36 taqua Exp $ 036 * 037 * Changes (from 26-Oct-2001) 038 * -------------------------- 039 * 26-Oct-2001 : Changed package to com.jrefinery.ui.* (DG); 040 * 08-Dec-2001 : Dropped the getMonths() method (DG); 041 * 13-Oct-2002 : Fixed errors reported by Checkstyle (DG); 042 * 02-Nov-2005 : Fixed a bug where the current day-of-the-month is past 043 * the end of the newly selected month when the month or year 044 * combo boxes are changed - see bug id 1344319 (DG); 045 * 046 */ 047 048package org.jfree.ui; 049 050import java.awt.BorderLayout; 051import java.awt.Color; 052import java.awt.Font; 053import java.awt.GridLayout; 054import java.awt.Insets; 055import java.awt.event.ActionEvent; 056import java.awt.event.ActionListener; 057import java.text.DateFormatSymbols; 058import java.util.Calendar; 059import java.util.Date; 060import javax.swing.BorderFactory; 061import javax.swing.JButton; 062import javax.swing.JComboBox; 063import javax.swing.JLabel; 064import javax.swing.JPanel; 065import javax.swing.SwingConstants; 066import javax.swing.UIManager; 067 068import org.jfree.date.SerialDate; 069 070/** 071 * A panel that allows the user to select a date. 072 * 073 * @author David Gilbert 074 */ 075public class DateChooserPanel extends JPanel implements ActionListener { 076 077 /** 078 * The date selected in the panel. 079 */ 080 private Calendar chosenDate; 081 082 /** 083 * The color for the selected date. 084 */ 085 private Color chosenDateButtonColor; 086 087 /** 088 * The color for dates in the current month. 089 */ 090 private Color chosenMonthButtonColor; 091 092 /** 093 * The color for dates that are visible, but not in the current month. 094 */ 095 private Color chosenOtherButtonColor; 096 097 /** 098 * The first day-of-the-week. 099 */ 100 private int firstDayOfWeek; 101 102 /** 103 * The range used for selecting years. 104 */ 105 private int yearSelectionRange = 20; 106 107 /** 108 * The font used to display the date. 109 */ 110 private Font dateFont = new Font("SansSerif", Font.PLAIN, 10); 111 112 /** 113 * A combo for selecting the month. 114 */ 115 private JComboBox monthSelector; 116 117 /** 118 * A combo for selecting the year. 119 */ 120 private JComboBox yearSelector; 121 122 /** 123 * A button for selecting today's date. 124 */ 125 private JButton todayButton; 126 127 /** 128 * An array of buttons used to display the days-of-the-month. 129 */ 130 private JButton[] buttons; 131 132 /** 133 * A flag that indicates whether or not we are currently refreshing the 134 * buttons. 135 */ 136 private boolean refreshing = false; 137 138 /** 139 * The ordered set of all seven days of a week, 140 * beginning with the 'firstDayOfWeek'. 141 */ 142 private int[] WEEK_DAYS; 143 144 /** 145 * Constructs a new date chooser panel, using today's date as the initial 146 * selection. 147 */ 148 public DateChooserPanel() { 149 this(Calendar.getInstance(), false); 150 } 151 152 /** 153 * Constructs a new date chooser panel. 154 * 155 * @param calendar the calendar controlling the date. 156 * @param controlPanel a flag that indicates whether or not the 'today' 157 * button should appear on the panel. 158 */ 159 public DateChooserPanel(final Calendar calendar, 160 final boolean controlPanel) { 161 162 super(new BorderLayout()); 163 164 this.chosenDateButtonColor = UIManager.getColor("textHighlight"); 165 this.chosenMonthButtonColor = UIManager.getColor("control"); 166 this.chosenOtherButtonColor = UIManager.getColor("controlShadow"); 167 168 // the default date is today... 169 this.chosenDate = calendar; 170 this.firstDayOfWeek = calendar.getFirstDayOfWeek(); 171 this.WEEK_DAYS = new int[7]; 172 for (int i = 0; i < 7; i++) { 173 this.WEEK_DAYS[i] = ((this.firstDayOfWeek + i - 1) % 7) + 1; 174 } 175 176 add(constructSelectionPanel(), BorderLayout.NORTH); 177 add(getCalendarPanel(), BorderLayout.CENTER); 178 if (controlPanel) { 179 add(constructControlPanel(), BorderLayout.SOUTH); 180 } 181 setDate(calendar.getTime()); 182 } 183 184 /** 185 * Sets the date chosen in the panel. 186 * 187 * @param theDate the new date. 188 */ 189 public void setDate(final Date theDate) { 190 191 this.chosenDate.setTime(theDate); 192 this.monthSelector.setSelectedIndex(this.chosenDate.get( 193 Calendar.MONTH)); 194 refreshYearSelector(); 195 refreshButtons(); 196 197 } 198 199 /** 200 * Returns the date selected in the panel. 201 * 202 * @return the selected date. 203 */ 204 public Date getDate() { 205 return this.chosenDate.getTime(); 206 } 207 208 /** 209 * Handles action-events from the date panel. 210 * 211 * @param e information about the event that occurred. 212 */ 213 public void actionPerformed(final ActionEvent e) { 214 215 if (e.getActionCommand().equals("monthSelectionChanged")) { 216 final JComboBox c = (JComboBox) e.getSource(); 217 218 // In most cases, changing the month will not change the selected 219 // day. But if the selected day is 29, 30 or 31 and the newly 220 // selected month doesn't have that many days, we revert to the 221 // last day of the newly selected month... 222 int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH); 223 this.chosenDate.set(Calendar.DAY_OF_MONTH, 1); 224 this.chosenDate.set(Calendar.MONTH, c.getSelectedIndex()); 225 int maxDayOfMonth = this.chosenDate.getActualMaximum( 226 Calendar.DAY_OF_MONTH); 227 this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth, 228 maxDayOfMonth)); 229 refreshButtons(); 230 } 231 else if (e.getActionCommand().equals("yearSelectionChanged")) { 232 if (!this.refreshing) { 233 final JComboBox c = (JComboBox) e.getSource(); 234 final Integer y = (Integer) c.getSelectedItem(); 235 236 // in most cases, changing the year will not change the 237 // selected day. But if the selected day is Feb 29, and the 238 // newly selected year is not a leap year, we revert to 239 // Feb 28... 240 int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH); 241 this.chosenDate.set(Calendar.DAY_OF_MONTH, 1); 242 this.chosenDate.set(Calendar.YEAR, y.intValue()); 243 int maxDayOfMonth = this.chosenDate.getActualMaximum( 244 Calendar.DAY_OF_MONTH); 245 this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth, 246 maxDayOfMonth)); 247 refreshYearSelector(); 248 refreshButtons(); 249 } 250 } 251 else if (e.getActionCommand().equals("todayButtonClicked")) { 252 setDate(new Date()); 253 } 254 else if (e.getActionCommand().equals("dateButtonClicked")) { 255 final JButton b = (JButton) e.getSource(); 256 final int i = Integer.parseInt(b.getName()); 257 final Calendar cal = getFirstVisibleDate(); 258 cal.add(Calendar.DATE, i); 259 setDate(cal.getTime()); 260 } 261 } 262 263 /** 264 * Returns a panel of buttons, each button representing a day in the month. 265 * This is a sub-component of the DatePanel. 266 * 267 * @return the panel. 268 */ 269 private JPanel getCalendarPanel() { 270 271 final JPanel p = new JPanel(new GridLayout(7, 7)); 272 final DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(); 273 final String[] weekDays = dateFormatSymbols.getShortWeekdays(); 274 275 for (int i = 0; i < this.WEEK_DAYS.length; i++) { 276 p.add(new JLabel(weekDays[this.WEEK_DAYS[i]], 277 SwingConstants.CENTER)); 278 } 279 280 this.buttons = new JButton[42]; 281 for (int i = 0; i < 42; i++) { 282 final JButton b = new JButton(""); 283 b.setMargin(new Insets(1, 1, 1, 1)); 284 b.setName(Integer.toString(i)); 285 b.setFont(this.dateFont); 286 b.setFocusPainted(false); 287 b.setActionCommand("dateButtonClicked"); 288 b.addActionListener(this); 289 this.buttons[i] = b; 290 p.add(b); 291 } 292 return p; 293 294 } 295 296 /** 297 * Returns the button color according to the specified date. 298 * 299 * @param theDate the date. 300 * @return the color. 301 */ 302 private Color getButtonColor(final Calendar theDate) { 303 if (equalDates(theDate, this.chosenDate)) { 304 return this.chosenDateButtonColor; 305 } 306 else if (theDate.get(Calendar.MONTH) == this.chosenDate.get( 307 Calendar.MONTH)) { 308 return this.chosenMonthButtonColor; 309 } 310 else { 311 return this.chosenOtherButtonColor; 312 } 313 } 314 315 /** 316 * Returns true if the two dates are equal (time of day is ignored). 317 * 318 * @param c1 the first date. 319 * @param c2 the second date. 320 * @return boolean. 321 */ 322 private boolean equalDates(final Calendar c1, final Calendar c2) { 323 if ((c1.get(Calendar.DATE) == c2.get(Calendar.DATE)) 324 && (c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH)) 325 && (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR))) { 326 return true; 327 } 328 else { 329 return false; 330 } 331 } 332 333 /** 334 * Returns the first date that is visible in the grid. This should always 335 * be in the month preceding the month of the selected date. 336 * 337 * @return the date. 338 */ 339 private Calendar getFirstVisibleDate() { 340 final Calendar c = Calendar.getInstance(); 341 c.set(this.chosenDate.get(Calendar.YEAR), this.chosenDate.get( 342 Calendar.MONTH), 1); 343 c.add(Calendar.DATE, -1); 344 while (c.get(Calendar.DAY_OF_WEEK) != getFirstDayOfWeek()) { 345 c.add(Calendar.DATE, -1); 346 } 347 return c; 348 } 349 350 /** 351 * Returns the first day of the week (controls the labels in the date 352 * panel). 353 * 354 * @return the first day of the week. 355 */ 356 private int getFirstDayOfWeek() { 357 return this.firstDayOfWeek; 358 } 359 360 /** 361 * Update the button labels and colors to reflect date selection. 362 */ 363 private void refreshButtons() { 364 final Calendar c = getFirstVisibleDate(); 365 for (int i = 0; i < 42; i++) { 366 final JButton b = this.buttons[i]; 367 b.setText(Integer.toString(c.get(Calendar.DATE))); 368 b.setBackground(getButtonColor(c)); 369 c.add(Calendar.DATE, 1); 370 } 371 } 372 373 /** 374 * Changes the contents of the year selection JComboBox to reflect the 375 * chosen date and the year range. 376 */ 377 private void refreshYearSelector() { 378 if (!this.refreshing) { 379 this.refreshing = true; 380 this.yearSelector.removeAllItems(); 381 final Integer[] years = getYears(this.chosenDate.get( 382 Calendar.YEAR)); 383 for (int i = 0; i < years.length; i++) { 384 this.yearSelector.addItem(years[i]); 385 } 386 this.yearSelector.setSelectedItem(new Integer(this.chosenDate.get( 387 Calendar.YEAR))); 388 this.refreshing = false; 389 } 390 } 391 392 /** 393 * Returns a vector of years preceding and following the specified year. 394 * The number of years preceding and following is determined by the 395 * yearSelectionRange attribute. 396 * 397 * @param chosenYear the selected year. 398 * @return a vector of years. 399 */ 400 private Integer[] getYears(final int chosenYear) { 401 final int size = this.yearSelectionRange * 2 + 1; 402 final int start = chosenYear - this.yearSelectionRange; 403 404 final Integer[] years = new Integer[size]; 405 for (int i = 0; i < size; i++) { 406 years[i] = new Integer(i + start); 407 } 408 return years; 409 } 410 411 /** 412 * Constructs a panel containing two JComboBoxes (for the month and year) 413 * and a button (to reset the date to TODAY). 414 * 415 * @return the panel. 416 */ 417 private JPanel constructSelectionPanel() { 418 final JPanel p = new JPanel(); 419 420 final int minMonth = this.chosenDate.getMinimum(Calendar.MONTH); 421 final int maxMonth = this.chosenDate.getMaximum(Calendar.MONTH); 422 final String[] months = new String[maxMonth - minMonth + 1]; 423 System.arraycopy(SerialDate.getMonths(), minMonth, months, 0, 424 months.length); 425 426 this.monthSelector = new JComboBox(months); 427 this.monthSelector.addActionListener(this); 428 this.monthSelector.setActionCommand("monthSelectionChanged"); 429 p.add(this.monthSelector); 430 431 this.yearSelector = new JComboBox(getYears(0)); 432 this.yearSelector.addActionListener(this); 433 this.yearSelector.setActionCommand("yearSelectionChanged"); 434 p.add(this.yearSelector); 435 436 return p; 437 } 438 439 /** 440 * Returns a panel that appears at the bottom of the calendar panel - 441 * contains a button for selecting today's date. 442 * 443 * @return the panel. 444 */ 445 private JPanel constructControlPanel() { 446 447 final JPanel p = new JPanel(); 448 p.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); 449 this.todayButton = new JButton("Today"); 450 this.todayButton.addActionListener(this); 451 this.todayButton.setActionCommand("todayButtonClicked"); 452 p.add(this.todayButton); 453 return p; 454 455 } 456 457 /** 458 * Returns the color for the currently selected date. 459 * 460 * @return a color. 461 */ 462 public Color getChosenDateButtonColor() { 463 return this.chosenDateButtonColor; 464 } 465 466 /** 467 * Redefines the color for the currently selected date. 468 * 469 * @param chosenDateButtonColor the new color 470 */ 471 public void setChosenDateButtonColor(final Color chosenDateButtonColor) { 472 if (chosenDateButtonColor == null) { 473 throw new NullPointerException("UIColor must not be null."); 474 } 475 final Color oldValue = this.chosenDateButtonColor; 476 this.chosenDateButtonColor = chosenDateButtonColor; 477 refreshButtons(); 478 firePropertyChange("chosenDateButtonColor", oldValue, 479 chosenDateButtonColor); 480 } 481 482 /** 483 * Returns the color for the buttons representing the current month. 484 * 485 * @return the color for the current month. 486 */ 487 public Color getChosenMonthButtonColor() { 488 return this.chosenMonthButtonColor; 489 } 490 491 /** 492 * Defines the color for the buttons representing the current month. 493 * 494 * @param chosenMonthButtonColor the color for the current month. 495 */ 496 public void setChosenMonthButtonColor(final Color chosenMonthButtonColor) { 497 if (chosenMonthButtonColor == null) { 498 throw new NullPointerException("UIColor must not be null."); 499 } 500 final Color oldValue = this.chosenMonthButtonColor; 501 this.chosenMonthButtonColor = chosenMonthButtonColor; 502 refreshButtons(); 503 firePropertyChange("chosenMonthButtonColor", oldValue, 504 chosenMonthButtonColor); 505 } 506 507 /** 508 * Returns the color for the buttons representing the other months. 509 * 510 * @return a color. 511 */ 512 public Color getChosenOtherButtonColor() { 513 return this.chosenOtherButtonColor; 514 } 515 516 /** 517 * Redefines the color for the buttons representing the other months. 518 * 519 * @param chosenOtherButtonColor a color. 520 */ 521 public void setChosenOtherButtonColor(final Color chosenOtherButtonColor) { 522 if (chosenOtherButtonColor == null) { 523 throw new NullPointerException("UIColor must not be null."); 524 } 525 final Color oldValue = this.chosenOtherButtonColor; 526 this.chosenOtherButtonColor = chosenOtherButtonColor; 527 refreshButtons(); 528 firePropertyChange("chosenOtherButtonColor", oldValue, 529 chosenOtherButtonColor); 530 } 531 532 /** 533 * Returns the range of years available for selection (defaults to 20). 534 * 535 * @return The range. 536 */ 537 public int getYearSelectionRange() { 538 return this.yearSelectionRange; 539 } 540 541 /** 542 * Sets the range of years available for selection. 543 * 544 * @param yearSelectionRange the range. 545 */ 546 public void setYearSelectionRange(final int yearSelectionRange) { 547 final int oldYearSelectionRange = this.yearSelectionRange; 548 this.yearSelectionRange = yearSelectionRange; 549 refreshYearSelector(); 550 firePropertyChange("yearSelectionRange", oldYearSelectionRange, 551 yearSelectionRange); 552 } 553}