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