001 /* 002 * $Id: JXDatePicker.java 3311 2009-03-26 11:55:17Z kleopatra $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx; 022 023 import java.awt.Color; 024 import java.awt.ComponentOrientation; 025 import java.awt.FlowLayout; 026 import java.awt.Font; 027 import java.awt.GradientPaint; 028 import java.awt.Graphics; 029 import java.awt.event.ActionEvent; 030 import java.awt.event.ActionListener; 031 import java.awt.event.MouseAdapter; 032 import java.awt.event.MouseEvent; 033 import java.awt.event.MouseListener; 034 import java.beans.PropertyChangeEvent; 035 import java.beans.PropertyChangeListener; 036 import java.beans.PropertyVetoException; 037 import java.text.DateFormat; 038 import java.text.Format; 039 import java.text.MessageFormat; 040 import java.text.ParseException; 041 import java.text.SimpleDateFormat; 042 import java.util.Calendar; 043 import java.util.Date; 044 import java.util.EventListener; 045 import java.util.Locale; 046 import java.util.TimeZone; 047 import java.util.logging.Logger; 048 049 import javax.swing.AbstractAction; 050 import javax.swing.Action; 051 import javax.swing.JComponent; 052 import javax.swing.JFormattedTextField; 053 import javax.swing.JPanel; 054 import javax.swing.JPopupMenu; 055 import javax.swing.SwingUtilities; 056 import javax.swing.UIManager; 057 import javax.swing.JFormattedTextField.AbstractFormatter; 058 import javax.swing.JFormattedTextField.AbstractFormatterFactory; 059 import javax.swing.text.DefaultFormatterFactory; 060 061 import org.jdesktop.swingx.calendar.DatePickerFormatter; 062 import org.jdesktop.swingx.event.EventListenerMap; 063 import org.jdesktop.swingx.painter.MattePainter; 064 import org.jdesktop.swingx.plaf.DatePickerAddon; 065 import org.jdesktop.swingx.plaf.DatePickerUI; 066 import org.jdesktop.swingx.plaf.LookAndFeelAddons; 067 import org.jdesktop.swingx.plaf.UIManagerExt; 068 import org.jdesktop.swingx.util.Contract; 069 070 /** 071 * A component for entering dates with a user interaction similar to a 072 * JComboBox. The dates can be typed into a text field or selected from a 073 * JXMonthView which opens in a JXPopupMenu on user's request. 074 * <p> 075 * 076 * The date selection is controlled by the JXMonthView's DateSelectionModel. 077 * This allows the use of all its functionality in the JXDatePicker as well. 078 * F.i. restrict the selection to a date in the current or next week: 079 * <p> 080 * 081 * <pre><code> 082 * Appointment appointment = new Appointment(director, 083 * "Be sure to have polished shoes!"); 084 * JXDatePicker picker = new JXDatePicker(); 085 * Calendar calendar = picker.getMonthView().getCalendar(); 086 * // starting today if we are in a hurry 087 * calendar.setTime(new Date()); 088 * picker.getMonthView().setLowerBound(calendar.getTime()); 089 * // end of next week 090 * CalendarUtils.endOfWeek(calendar); 091 * calendar.add(Calendar.WEEK_OF_YEAR); 092 * picker.getMonthView().setUpperBound(calendar.getTime()); 093 * </code></pre> 094 * 095 * Similar to a JXMonthView, the JXDatePicker fires an ActionEvent when the user 096 * actively commits or cancels a selection. Interested client code can add a 097 * ActionListener to be notified by the user action. 098 * 099 * <pre><code> 100 * JXDatePicker picker = new JXDatePicker(new Date()); 101 * ActionListener l = new ActionListener() { 102 * public void actionPerformed(ActionEvent e) { 103 * if (JXDatePicker.COMMIT_KEY.equals(e.getActionCommand)) { 104 * saveDate(picker.getDate()); 105 * } 106 * } 107 * }; 108 * picker.addActionListener(l); 109 * </code></pre> 110 * 111 * Note that ActionListener will <b>not</b> be notified if the user 112 * edits the date text without hitting the Enter key afterwards. To detect both kinds of 113 * date change, interested client code can add a PropertyChangeListener. 114 * 115 * <pre><code> 116 * JXDatePicker picker = new JXDatePicker(new Date()); 117 * PropertyChangeListener listener = new PropertyChangeListener() { 118 * public void propertyChange(PropertyChangeEvent e) { 119 * if ("date".equals(e.getPropertyName()) { 120 * saveDate(picker.getDate()); 121 * } 122 * } 123 * }; 124 * picker.addPropertyChangeListener(listener); 125 * </code></pre> 126 127 * 128 * <p> 129 * The DateFormats used in the JXDatePicker's are initialized to the default 130 * formats of the DatePickerFormatter, as defined by the picker's resourceBundle 131 * DatePicker.properties. Application code can overwrite the picker's default 132 * 133 * <pre><code> 134 * picker.setDateFormats(myCustomFormat, myAlternativeCustomFormat); 135 * </code></pre> 136 * 137 * PENDING JW: explain what the alternatives are for (after understanding it 138 * myself ;-) 139 * <p> 140 * 141 * The selected Date is a bound property of the JXDatePicker. This allows easy 142 * binding to a property of a custom bean when using a binding framework. 143 * <p> 144 * 145 * Keybindings (as installed by the UI-Delegate) 146 * <ul> 147 * <li> ENTER commits the edited or selected value 148 * <li> ESCAPE reverts the edited or selected value 149 * <li> alt-DOWN opens the monthView popup 150 * <li> shift-F5 if monthView is visible, navigates the monthView to today 151 * (no effect otherwise) 152 * <li> F5 commits today 153 * </ul> 154 * 155 * PENDNG JW: support per-OS keybindings to be installed, currently they are 156 * hardcoded in our (single) BasicDatePickerUI. 157 * 158 * @author Joshua Outwater 159 * @author Jeanette Winzenburg 160 * 161 * @see JXMonthView 162 * @see org.jdesktop.swingx.calendar.DateSelectionModel 163 * @see DatePickerFormatter 164 * 165 */ 166 public class JXDatePicker extends JComponent { 167 @SuppressWarnings("unused") 168 private static final Logger LOG = Logger.getLogger(JXDatePicker.class 169 .getName()); 170 static { 171 LookAndFeelAddons.contribute(new DatePickerAddon()); 172 } 173 174 /** 175 * UI Class ID 176 */ 177 public static final String uiClassID = "DatePickerUI"; 178 179 public static final String EDITOR = "editor"; 180 public static final String MONTH_VIEW = "monthView"; 181 182 public static final String LINK_PANEL = "linkPanel"; 183 184 /** action command used for commit actionEvent. */ 185 public static final String COMMIT_KEY = "datePickerCommit"; 186 /** action command used for cancel actionEvent. */ 187 public static final String CANCEL_KEY = "datePickerCancel"; 188 /** action key for navigate home action */ 189 public static final String HOME_NAVIGATE_KEY = "navigateHome"; 190 /** action key for commit home action */ 191 public static final String HOME_COMMIT_KEY = "commitHome"; 192 193 private static final DateFormat[] EMPTY_DATE_FORMATS = new DateFormat[0]; 194 195 /** 196 * The editable date field that displays the date 197 */ 198 private JFormattedTextField _dateField; 199 200 /** 201 * Popup that displays the month view with controls for 202 * traversing/selecting dates. 203 */ 204 private JPanel _linkPanel; 205 private MessageFormat _linkFormat; 206 private Date linkDate; 207 208 private JXMonthView _monthView; 209 private boolean editable = true; 210 private EventListenerMap listenerMap; 211 protected boolean lightWeightPopupEnabled = JPopupMenu.getDefaultLightWeightPopupEnabled(); 212 213 private Date date; 214 215 private PropertyChangeListener monthViewListener; 216 217 218 /** 219 * Intantiates a date picker with no selection and the default 220 * <code>DatePickerFormatter</code>. 221 * <p/> 222 * The date picker is configured with the default time zone and locale 223 * 224 * @see #setTimeZone 225 * @see #getTimeZone 226 */ 227 public JXDatePicker() { 228 this(null, null); 229 } 230 231 232 233 /** 234 * Intantiates a date picker using the specified time as the initial 235 * selection and the default 236 * <code>DatePickerFormatter</code>. 237 * <p/> 238 * The date picker is configured with the default time zone and locale 239 * 240 * @param selected the initially selected date 241 * @see #setTimeZone 242 * @see #getTimeZone 243 */ 244 public JXDatePicker(Date selected) { 245 this(selected, null); 246 } 247 248 /** 249 * Intantiates a date picker with no selection and the default 250 * <code>DatePickerFormatter</code>. 251 * <p/> 252 * The date picker is configured with the default time zone and specified 253 * locale 254 * 255 * @param locale initial Locale 256 * @see #setTimeZone 257 * @see #getTimeZone 258 */ 259 public JXDatePicker(Locale locale) { 260 this(null, locale); 261 } 262 263 /** 264 * Intantiates a date picker using the specified time as the initial 265 * selection and the default 266 * <code>DatePickerFormatter</code>. 267 * <p/> 268 * The date picker is configured with the default time zone and specified locale 269 * 270 * @param selection initially selected Date 271 * @param locale initial Locale 272 * @see #setTimeZone 273 * @see #getTimeZone 274 * 275 */ 276 public JXDatePicker(Date selection, Locale locale) { 277 init(); 278 if (locale != null) { 279 setLocale(locale); 280 } 281 // install the controller before setting the date 282 updateUI(); 283 setDate(selection); 284 } 285 286 /** 287 * Sets the date property. <p> 288 * 289 * Does nothing if the ui vetos the new date - as might happen if 290 * the code tries to set a date which is unselectable in the 291 * monthView's context. The actual value of the new Date is controlled 292 * by the JXMonthView's DateSelectionModel. The default implementation 293 * normalizes the date to the start of the day in the model's calendar's 294 * coordinates, that is all time fields are zeroed. To keep the time fields, 295 * configure the monthView with a SingleDaySelectionModel. 296 * <p> 297 * 298 * At all "stable" (= not editing in date input field nor 299 * in the monthView) times the date is the same in the 300 * JXMonthView, this JXDatePicker and the editor. If a new Date 301 * is set, this invariant is enforced by the DatePickerUI. 302 * <p> 303 * 304 * This is a bound property. 305 * 306 * 307 * @param date the new date to set. 308 * @see #getDate() 309 * @see org.jdesktop.swingx.calendar.DateSelectionModel 310 * @see org.jdesktop.swingx.calendar.SingleDaySelectionModel 311 */ 312 public void setDate(Date date) { 313 /* 314 * JW: 315 * this is a poor woman's constraint property. 316 * Introduces explicit coupling to the ui. 317 * Which is unusual at this place in code. 318 * 319 * If needed the date can be made a formal 320 * constraint property and let the ui add a 321 * VetoablePropertyListener. 322 */ 323 try { 324 date = getUI().getSelectableDate(date); 325 } catch (PropertyVetoException e) { 326 return; 327 } 328 Date old = getDate(); 329 this.date = date; 330 firePropertyChange("date", old, getDate()); 331 } 332 333 334 335 /** 336 * Returns the currently selected date. 337 * 338 * @return Date 339 */ 340 public Date getDate() { 341 return date; 342 } 343 344 /** 345 * 346 */ 347 private void init() { 348 listenerMap = new EventListenerMap(); 349 initMonthView(); 350 351 updateLinkFormat(); 352 linkDate = _monthView.getToday(); 353 _linkPanel = new TodayPanel(); 354 } 355 356 private void initMonthView() { 357 _monthView = new JXMonthView(); 358 // _monthView.setSelectionModel(new SingleDaySelectionModel()); 359 _monthView.setTraversable(true); 360 _monthView.addPropertyChangeListener(getMonthViewListener()); 361 } 362 363 /** 364 * Lazily creates and returns the PropertyChangeListener which listens 365 * for model's calendar properties. 366 * 367 * @return a PropertyChangeListener for monthView's property change notification. 368 */ 369 private PropertyChangeListener getMonthViewListener() { 370 if (monthViewListener == null) { 371 monthViewListener = new PropertyChangeListener() { 372 373 public void propertyChange(PropertyChangeEvent evt) { 374 if ("timeZone".equals(evt.getPropertyName())) { 375 updateTimeZone((TimeZone) evt.getOldValue(), (TimeZone) evt.getNewValue()); 376 } 377 378 } 379 380 }; 381 } 382 return monthViewListener; 383 } 384 385 /** 386 * Callback from monthView timezone changes. <p> 387 * 388 * NOTE: as timeZone is a bound property of this class we need to 389 * guarantee the propertyChangeNotification. As this class doesn't 390 * own this property it must listen to the owner (monthView) and 391 * re-fire the change. 392 * 393 * @param oldValue the old timezone. 394 * @param newValue the new timezone. 395 */ 396 protected void updateTimeZone(TimeZone oldValue, TimeZone newValue) { 397 firePropertyChange("timeZone", oldValue, newValue); 398 } 399 400 /** 401 * Returns the look and feel (L&F) object that renders this component. 402 * 403 * @return the DatePickerUI object that renders this component 404 */ 405 public DatePickerUI getUI() { 406 return (DatePickerUI) ui; 407 } 408 409 /** 410 * Sets the L&F object that renders this component. 411 * 412 * @param ui UI to use for this {@code JXDatePicker} 413 */ 414 public void setUI(DatePickerUI ui) { 415 super.setUI(ui); 416 } 417 418 /** 419 * Resets the UI property with the value from the current look and feel. 420 * 421 * @see UIManager#getUI 422 */ 423 @Override 424 public void updateUI() { 425 setUI((DatePickerUI) LookAndFeelAddons.getUI(this, DatePickerUI.class)); 426 // JW: quick hack around #706-swingx - monthView not updated 427 // is this complete? how about editor (if not uiResource), linkPanel? 428 SwingUtilities.updateComponentTreeUI(getMonthView()); 429 invalidate(); 430 } 431 432 /** 433 * @inheritDoc 434 */ 435 @Override 436 public String getUIClassID() { 437 return uiClassID; 438 } 439 440 /** 441 * Replaces the currently installed formatter and factory used by the 442 * editor. These string formats are defined by the 443 * <code>java.text.SimpleDateFormat</code> class. <p> 444 * 445 * Note: The given formats are internally synched to the picker's current 446 * TimeZone. 447 * 448 * @param formats zero or more not null string formats to use. Note that a 449 * null array is allowed and resets the formatter to use the 450 * localized default formats. 451 * @throws NullPointerException any array element is null. 452 * @see java.text.SimpleDateFormat 453 */ 454 public void setFormats(String... formats) { 455 DateFormat[] dateFormats = null; 456 if (formats != null) { 457 Contract.asNotNull(formats, 458 "the array of format strings must not " 459 + "must not contain null elements"); 460 dateFormats = new DateFormat[formats.length]; 461 for (int counter = formats.length - 1; counter >= 0; counter--) { 462 dateFormats[counter] = new SimpleDateFormat(formats[counter], getLocale()); 463 } 464 } 465 setFormats(dateFormats); 466 } 467 468 /** 469 * Replaces the currently installed formatter and factory used by the 470 * editor.<p> 471 * 472 * Note: The given formats are internally synched to the picker's current 473 * TimeZone. 474 * 475 * @param formats zero or more not null formats to use. Note that a 476 * null array is allowed and resets the formatter to use the 477 * localized default formats. 478 * @throws NullPointerException any of its elements is null. 479 */ 480 public void setFormats(DateFormat... formats) { 481 if (formats != null) { 482 Contract.asNotNull(formats, "the array of formats " + "must not contain null elements"); 483 } 484 485 DateFormat[] old = getFormats(); 486 _dateField.setFormatterFactory(new DefaultFormatterFactory( 487 new DatePickerFormatter(formats, getLocale()))); 488 firePropertyChange("formats", old, getFormats()); 489 } 490 491 /** 492 * Returns an array of the formats used by the installed formatter 493 * if it is a subclass of <code>JXDatePickerFormatter<code>. 494 * <code>javax.swing.JFormattedTextField.AbstractFormatter</code> 495 * and <code>javax.swing.text.DefaultFormatter</code> do not have 496 * support for accessing the formats used. 497 * 498 * @return array of formats guaranteed to be not null, but might be empty. 499 */ 500 public DateFormat[] getFormats() { 501 // Dig this out from the factory, if possible, otherwise return null. 502 AbstractFormatterFactory factory = _dateField.getFormatterFactory(); 503 if (factory != null) { 504 AbstractFormatter formatter = factory.getFormatter(_dateField); 505 if (formatter instanceof DatePickerFormatter) { 506 return ((DatePickerFormatter) formatter).getFormats(); 507 } 508 } 509 return EMPTY_DATE_FORMATS; 510 } 511 512 /** 513 * Return the <code>JXMonthView</code> used in the popup to 514 * select dates from. 515 * 516 * @return the month view component 517 */ 518 public JXMonthView getMonthView() { 519 return _monthView; 520 } 521 522 /** 523 * Set the component to use the specified JXMonthView. If the new JXMonthView 524 * is configured to a different time zone it will affect the time zone of this 525 * component. 526 * 527 * @param monthView month view comopnent. 528 * @throws NullPointerException if view component is null 529 * 530 * @see #setTimeZone 531 * @see #getTimeZone 532 */ 533 public void setMonthView(JXMonthView monthView) { 534 Contract.asNotNull(monthView, "monthView must not be null"); 535 JXMonthView oldMonthView = getMonthView(); 536 TimeZone oldTZ = getTimeZone(); 537 oldMonthView.removePropertyChangeListener(getMonthViewListener()); 538 _monthView = monthView; 539 getMonthView().addPropertyChangeListener(getMonthViewListener()); 540 firePropertyChange(MONTH_VIEW, oldMonthView, getMonthView()); 541 firePropertyChange("timeZone", oldTZ, getTimeZone()); 542 } 543 544 /** 545 * Gets the time zone. This is a convenience method which returns the time zone 546 * of the JXMonthView being used. 547 * 548 * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>. 549 */ 550 public TimeZone getTimeZone() { 551 return _monthView.getTimeZone(); 552 } 553 554 /** 555 * Sets the time zone with the given time zone value. This is a convenience 556 * method which returns the time zone of the JXMonthView being used.<p> 557 * 558 * PENDING JW: currently this property is the only property of the monthView 559 * which is exposed in this api. Not sure why it is here at all. 560 * It's asymetric (to the other properties) and as such should be either removed 561 * or the others which might be relevant to a datePicker exposed as well (probably 562 * hiding the monthView itself as an implementation detail of the ui delegate). 563 * 564 * @param tz The <code>TimeZone</code>. 565 */ 566 public void setTimeZone(TimeZone tz) { 567 _monthView.setTimeZone(tz); 568 } 569 570 571 /** 572 * Returns the date shown in the LinkPanel. 573 * <p> 574 * PENDING JW: the property should be named linkDate - but that's held by the 575 * deprecated long returning method. Maybe revisit if we actually remove the other. 576 * 577 * @return the date shown in the LinkPanel. 578 */ 579 public Date getLinkDay() { 580 return linkDate; 581 } 582 583 /** 584 * Set the date the link will use and the string defining a MessageFormat 585 * to format the link. If no valid date is in the editor when the popup 586 * is displayed the popup will focus on the month the linkDate is in. Calling 587 * this method will replace the currently installed linkPanel and install 588 * a new one with the requested date and format. 589 * 590 * 591 * @param linkDay the Date to set on the LinkPanel 592 * @param linkFormatString String used to format the link 593 * @see java.text.MessageFormat 594 */ 595 public void setLinkDay(Date linkDay, String linkFormatString) { 596 setLinkFormat(new MessageFormat(linkFormatString)); 597 setLinkDay(linkDay); 598 } 599 600 /** 601 * Sets the date shown in the TodayPanel. 602 * 603 * PENDING JW ... quick api hack for testing. Don't recreate the panel if 604 * it had been used 605 * 606 * @param linkDay the date used in the TodayPanel 607 */ 608 public void setLinkDay(Date linkDay) { 609 this.linkDate = linkDay; 610 Format[] formats = getLinkFormat().getFormatsByArgumentIndex(); 611 for (Format format : formats) { 612 if (format instanceof DateFormat) { 613 ((DateFormat) format).setTimeZone(getTimeZone()); 614 } 615 } 616 setLinkPanel(new TodayPanel()); 617 } 618 619 620 /** 621 * @param _linkFormat the _linkFormat to set 622 */ 623 protected void setLinkFormat(MessageFormat _linkFormat) { 624 this._linkFormat = _linkFormat; 625 } 626 627 /** 628 * @return the _linkFormat 629 */ 630 protected MessageFormat getLinkFormat() { 631 return _linkFormat; 632 } 633 634 /** 635 * Update text on the link panel. 636 * 637 */ 638 private void updateLinkFormat() { 639 // PENDING JW: move to ui 640 String linkFormat = UIManagerExt.getString( 641 "JXDatePicker.linkFormat", getLocale()); 642 643 if (linkFormat != null) { 644 setLinkFormat(new MessageFormat(linkFormat)); 645 } else { 646 setLinkFormat(new MessageFormat("{0,date, dd MMMM yyyy}")); 647 } 648 } 649 650 /** 651 * Return the panel that is used at the bottom of the popup. The default 652 * implementation shows a link that displays the current month. 653 * 654 * @return The currently installed link panel 655 */ 656 public JPanel getLinkPanel() { 657 return _linkPanel; 658 } 659 660 /** 661 * Set the panel that will be used at the bottom of the popup. 662 * PENDING JW: why insist on JPanel? JComponent would be enough? 663 * 664 * @param linkPanel The new panel to install in the popup 665 */ 666 public void setLinkPanel(JPanel linkPanel) { 667 JPanel oldLinkPanel = _linkPanel; 668 _linkPanel = linkPanel; 669 firePropertyChange(LINK_PANEL, oldLinkPanel, _linkPanel); 670 } 671 672 /** 673 * Returns the formatted text field used to edit the date selection. 674 * <p> 675 * Clients should NOT use this method. It is provided to temporarily support 676 * the PLAF code. 677 * 678 * @return the formatted text field 679 */ 680 // @Deprecated 681 public JFormattedTextField getEditor() { 682 return _dateField; 683 } 684 685 /** 686 * Sets the editor. <p> 687 * 688 * The default is created and set by the UI delegate. 689 * <p> 690 * Clients should NOT use this method. It is provided to temporarily support 691 * the PLAF code. 692 * 693 * @param editor the formatted input. 694 * @throws NullPointerException if editor is null. 695 * 696 * @see #getEditor 697 */ 698 // @Deprecated 699 public void setEditor(JFormattedTextField editor) { 700 Contract.asNotNull(editor, "editor must not be null"); 701 JFormattedTextField oldEditor = _dateField; 702 _dateField = editor; 703 firePropertyChange(EDITOR, oldEditor, _dateField); 704 } 705 706 @Override 707 public void setComponentOrientation(ComponentOrientation orientation) { 708 super.setComponentOrientation(orientation); 709 _monthView.setComponentOrientation(orientation); 710 } 711 712 /** 713 * Returns true if the current value being edited is valid. 714 * 715 * @return true if the current value being edited is valid. 716 */ 717 public boolean isEditValid() { 718 return _dateField.isEditValid(); 719 } 720 721 /** 722 * Commits the editor's changes and notifies ActionListeners. 723 * 724 * Forces the current value to be taken from the AbstractFormatter and 725 * set as the current value. This has no effect if there is no current 726 * AbstractFormatter installed. 727 * 728 * @throws java.text.ParseException Throws parse exception if the date 729 * can not be parsed. 730 */ 731 public void commitEdit() throws ParseException { 732 try { 733 _dateField.commitEdit(); 734 fireActionPerformed(COMMIT_KEY); 735 } catch (ParseException e) { 736 // re-throw 737 throw e; 738 } 739 } 740 741 /** 742 * Cancels the editor's changes and notifies ActionListeners. 743 * 744 */ 745 public void cancelEdit() { 746 // hmmm... no direct api? 747 _dateField.setValue(_dateField.getValue()); 748 fireActionPerformed(CANCEL_KEY); 749 } 750 751 /** 752 * Sets the editable property. If false, ...? 753 * 754 * The default value is true. 755 * 756 * @param value 757 * @see #isEditable() 758 */ 759 public void setEditable(boolean value) { 760 boolean oldEditable = isEditable(); 761 editable = value; 762 firePropertyChange("editable", oldEditable, editable); 763 if (editable != oldEditable) { 764 repaint(); 765 } 766 } 767 768 /** 769 * Returns the editable property. 770 * 771 * @return {@code true} if the picker is editable; {@code false} otherwise 772 */ 773 public boolean isEditable() { 774 return editable; 775 } 776 777 /** 778 * Returns the font that is associated with the editor of this date picker. 779 */ 780 @Override 781 public Font getFont() { 782 return getEditor().getFont(); 783 } 784 785 /** 786 * Set the font for the editor associated with this date picker. 787 */ 788 @Override 789 public void setFont(final Font font) { 790 getEditor().setFont(font); 791 } 792 793 /** 794 * Sets the <code>lightWeightPopupEnabled</code> property, which 795 * provides a hint as to whether or not a lightweight 796 * <code>Component</code> should be used to contain the 797 * <code>JXDatePicker</code>, versus a heavyweight 798 * <code>Component</code> such as a <code>Panel</code> 799 * or a <code>Window</code>. The decision of lightweight 800 * versus heavyweight is ultimately up to the 801 * <code>JXDatePicker</code>. Lightweight windows are more 802 * efficient than heavyweight windows, but lightweight 803 * and heavyweight components do not mix well in a GUI. 804 * If your application mixes lightweight and heavyweight 805 * components, you should disable lightweight popups. 806 * The default value for the <code>lightWeightPopupEnabled</code> 807 * property is <code>true</code>, unless otherwise specified 808 * by the look and feel. Some look and feels always use 809 * heavyweight popups, no matter what the value of this property. 810 * <p/> 811 * See the article <a href="http://java.sun.com/products/jfc/tsc/articles/mixing/index.html">Mixing Heavy and Light Components</a> 812 * on <a href="http://java.sun.com/products/jfc/tsc"> 813 * <em>The Swing Connection</em></a> 814 * This method fires a property changed event. 815 * 816 * @param aFlag if <code>true</code>, lightweight popups are desired 817 * @beaninfo bound: true 818 * expert: true 819 * description: Set to <code>false</code> to require heavyweight popups. 820 */ 821 public void setLightWeightPopupEnabled(boolean aFlag) { 822 boolean oldFlag = lightWeightPopupEnabled; 823 lightWeightPopupEnabled = aFlag; 824 firePropertyChange("lightWeightPopupEnabled", oldFlag, lightWeightPopupEnabled); 825 } 826 827 /** 828 * Gets the value of the <code>lightWeightPopupEnabled</code> 829 * property. 830 * 831 * @return the value of the <code>lightWeightPopupEnabled</code> 832 * property 833 * @see #setLightWeightPopupEnabled 834 */ 835 public boolean isLightWeightPopupEnabled() { 836 return lightWeightPopupEnabled; 837 } 838 839 /** 840 * Get the baseline for the specified component, or a value less 841 * than 0 if the baseline can not be determined. The baseline is measured 842 * from the top of the component. 843 * 844 * @param width Width of the component to determine baseline for. 845 * @param height Height of the component to determine baseline for. 846 * @return baseline for the specified component 847 */ 848 public int getBaseline(int width, int height) { 849 return ((DatePickerUI) ui).getBaseline(width, height); 850 } 851 852 853 /** 854 * Adds an ActionListener. 855 * <p/> 856 * The ActionListener will receive an ActionEvent when a selection has 857 * been made. 858 * 859 * @param l The ActionListener that is to be notified 860 */ 861 public void addActionListener(ActionListener l) { 862 listenerMap.add(ActionListener.class, l); 863 } 864 865 /** 866 * Removes an ActionListener. 867 * 868 * @param l The action listener to remove. 869 */ 870 public void removeActionListener(ActionListener l) { 871 listenerMap.remove(ActionListener.class, l); 872 } 873 874 @Override 875 @SuppressWarnings("unchecked") 876 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 877 java.util.List<T> listeners = listenerMap.getListeners(listenerType); 878 T[] result; 879 if (!listeners.isEmpty()) { 880 //noinspection unchecked 881 result = (T[]) java.lang.reflect.Array.newInstance(listenerType, listeners.size()); 882 result = listeners.toArray(result); 883 } else { 884 result = super.getListeners(listenerType); 885 } 886 return result; 887 } 888 889 890 /** 891 * Fires an ActionEvent with the given actionCommand 892 * to all listeners. 893 */ 894 protected void fireActionPerformed(String actionCommand) { 895 ActionListener[] listeners = getListeners(ActionListener.class); 896 ActionEvent e = null; 897 898 for (ActionListener listener : listeners) { 899 if (e == null) { 900 e = new ActionEvent(this, 901 ActionEvent.ACTION_PERFORMED, 902 actionCommand); 903 } 904 listener.actionPerformed(e); 905 } 906 } 907 908 /** 909 * Pes: added setLocale method to refresh link text on locale changes 910 */ 911 private final class TodayPanel extends JXPanel { 912 private TodayAction todayAction; 913 private JXHyperlink todayLink; 914 915 TodayPanel() { 916 super(new FlowLayout()); 917 setBackgroundPainter(new MattePainter(new GradientPaint(0, 0, new Color(238, 238, 238), 0, 1, Color.WHITE))); 918 todayAction = new TodayAction(); 919 todayLink = new JXHyperlink(todayAction); 920 todayLink.addMouseListener(createDoubleClickListener()); 921 Color textColor = new Color(16, 66, 104); 922 todayLink.setUnclickedColor(textColor); 923 todayLink.setClickedColor(textColor); 924 add(todayLink); 925 } 926 927 /** 928 * @return 929 */ 930 private MouseListener createDoubleClickListener() { 931 MouseAdapter adapter = new MouseAdapter() { 932 933 @Override 934 public void mousePressed(MouseEvent e) { 935 if (e.getClickCount() != 2) return; 936 todayAction.select = true; 937 } 938 939 }; 940 return adapter; 941 } 942 943 @Override 944 protected void paintComponent(Graphics g) { 945 super.paintComponent(g); 946 g.setColor(new Color(187, 187, 187)); 947 g.drawLine(0, 0, getWidth(), 0); 948 g.setColor(new Color(221, 221, 221)); 949 g.drawLine(0, 1, getWidth(), 1); 950 } 951 952 /** 953 * {@inheritDoc} <p> 954 * Overridden to update the link format and hyperlink text. 955 */ 956 @Override 957 public void setLocale(Locale l) { 958 super.setLocale(l); 959 updateLinkFormat(); 960 todayLink.setText(getLinkFormat().format(new Object[]{getLinkDay()})); 961 } 962 963 private final class TodayAction extends AbstractAction { 964 boolean select; 965 TodayAction() { 966 super(getLinkFormat().format(new Object[]{getLinkDay()})); 967 Calendar cal = _monthView.getCalendar(); 968 cal.setTime(getLinkDay()); 969 putValue(NAME, getLinkFormat().format(new Object[] {cal.getTime()})); 970 } 971 972 public void actionPerformed(ActionEvent ae) { 973 String key = select ? JXDatePicker.HOME_COMMIT_KEY : JXDatePicker.HOME_NAVIGATE_KEY; 974 select = false; 975 Action delegate = getActionMap().get(key); 976 /* 977 * PatrykRy: Commit today date only when commit action is enabled. 978 * Home navigate is always enabled. 979 */ 980 if (delegate != null && delegate.isEnabled()) { 981 delegate.actionPerformed(null); 982 } 983 984 } 985 } 986 } 987 988 989 } 990