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