001 /* 002 * $Id: BasicDatePickerUI.java 3301 2009-03-16 11:37:50Z kleopatra $ 003 * 004 * Copyright 2006 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.plaf.basic; 022 023 import java.awt.BorderLayout; 024 import java.awt.Component; 025 import java.awt.Container; 026 import java.awt.Dimension; 027 import java.awt.FontMetrics; 028 import java.awt.Insets; 029 import java.awt.KeyboardFocusManager; 030 import java.awt.LayoutManager; 031 import java.awt.event.ActionEvent; 032 import java.awt.event.ActionListener; 033 import java.awt.event.FocusEvent; 034 import java.awt.event.FocusListener; 035 import java.awt.event.MouseEvent; 036 import java.awt.event.MouseListener; 037 import java.awt.event.MouseMotionListener; 038 import java.beans.PropertyChangeEvent; 039 import java.beans.PropertyChangeListener; 040 import java.beans.PropertyVetoException; 041 import java.text.DateFormat; 042 import java.text.ParseException; 043 import java.util.Calendar; 044 import java.util.Date; 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.ActionMap; 052 import javax.swing.Icon; 053 import javax.swing.InputMap; 054 import javax.swing.JButton; 055 import javax.swing.JComboBox; 056 import javax.swing.JComponent; 057 import javax.swing.JFormattedTextField; 058 import javax.swing.JPopupMenu; 059 import javax.swing.KeyStroke; 060 import javax.swing.SwingUtilities; 061 import javax.swing.UIManager; 062 import javax.swing.JFormattedTextField.AbstractFormatter; 063 import javax.swing.JFormattedTextField.AbstractFormatterFactory; 064 import javax.swing.border.Border; 065 import javax.swing.plaf.ComponentUI; 066 import javax.swing.plaf.UIResource; 067 import javax.swing.text.DefaultFormatterFactory; 068 import javax.swing.text.View; 069 070 import org.jdesktop.swingx.JXDatePicker; 071 import org.jdesktop.swingx.JXMonthView; 072 import org.jdesktop.swingx.SwingXUtilities; 073 import org.jdesktop.swingx.calendar.CalendarUtils; 074 import org.jdesktop.swingx.calendar.DatePickerFormatter; 075 import org.jdesktop.swingx.calendar.DateSelectionModel; 076 import org.jdesktop.swingx.calendar.DatePickerFormatter.DatePickerFormatterUIResource; 077 import org.jdesktop.swingx.event.DateSelectionEvent; 078 import org.jdesktop.swingx.event.DateSelectionListener; 079 import org.jdesktop.swingx.event.DateSelectionEvent.EventType; 080 import org.jdesktop.swingx.plaf.DatePickerUI; 081 082 /** 083 * The basic implementation of a <code>DatePickerUI</code>. 084 * <p> 085 * 086 * 087 * @author Joshua Outwater 088 * @author Jeanette Winzenburg 089 */ 090 public class BasicDatePickerUI extends DatePickerUI { 091 092 @SuppressWarnings("all") 093 private static final Logger LOG = Logger.getLogger(BasicDatePickerUI.class 094 .getName()); 095 096 protected JXDatePicker datePicker; 097 private JButton popupButton; 098 private BasicDatePickerPopup popup; 099 private Handler handler; 100 /* 101 * shared listeners 102 */ 103 protected PropertyChangeListener propertyChangeListener; 104 private FocusListener focusListener; 105 106 /* 107 * listener's for the arrow button 108 */ 109 protected MouseListener mouseListener; 110 protected MouseMotionListener mouseMotionListener; 111 112 /* 113 * listeners for the picker's editor 114 */ 115 private ActionListener editorActionListener; 116 private EditorCancelAction editorCancelAction; 117 private PropertyChangeListener editorPropertyListener; 118 119 /** 120 * listeners for the picker's monthview 121 */ 122 private DateSelectionListener monthViewSelectionListener; 123 private ActionListener monthViewActionListener; 124 private PropertyChangeListener monthViewPropertyListener; 125 126 private PopupRemover popupRemover; 127 128 129 @SuppressWarnings({"UnusedDeclaration"}) 130 public static ComponentUI createUI(JComponent c) { 131 return new BasicDatePickerUI(); 132 } 133 134 @Override 135 public void installUI(JComponent c) { 136 datePicker = (JXDatePicker)c; 137 datePicker.setLayout(createLayoutManager()); 138 installComponents(); 139 installDefaults(); 140 installKeyboardActions(); 141 installListeners(); 142 } 143 144 @Override 145 public void uninstallUI(JComponent c) { 146 uninstallListeners(); 147 uninstallKeyboardActions(); 148 uninstallDefaults(); 149 uninstallComponents(); 150 datePicker.setLayout(null); 151 datePicker = null; 152 } 153 154 protected void installComponents() { 155 156 JFormattedTextField editor = datePicker.getEditor(); 157 if (SwingXUtilities.isUIInstallable(editor)) { 158 DateFormat[] formats = getCustomFormats(editor); 159 // we are not yet listening ... 160 datePicker.setEditor(createEditor()); 161 if (formats != null) { 162 datePicker.setFormats(formats); 163 } 164 } 165 updateFromEditorChanged(null, false); 166 167 popupButton = createPopupButton(); 168 if (popupButton != null) { 169 // this is a trick to get hold of the client prop which 170 // prevents closing of the popup 171 JComboBox box = new JComboBox(); 172 Object preventHide = box.getClientProperty("doNotCancelPopup"); 173 popupButton.putClientProperty("doNotCancelPopup", preventHide); 174 datePicker.add(popupButton); 175 } 176 updateChildLocale(datePicker.getLocale()); 177 178 } 179 180 /** 181 * Checks and returns custom formats on the editor, if any. 182 * 183 * @param editor the editor to check 184 * @return the custom formats uses in the editor or null if it had 185 * used defaults as defined in the datepicker properties 186 */ 187 private DateFormat[] getCustomFormats(JFormattedTextField editor) { 188 DateFormat[] formats = null; 189 if (editor != null) { 190 AbstractFormatterFactory factory = editor.getFormatterFactory(); 191 if (factory != null) { 192 AbstractFormatter formatter = factory.getFormatter(editor); 193 if (!(formatter instanceof DatePickerFormatterUIResource)) { 194 formats = ((DatePickerFormatter) formatter).getFormats(); 195 } 196 } 197 198 } 199 return formats; 200 } 201 202 protected void uninstallComponents() { 203 JFormattedTextField editor = datePicker.getEditor(); 204 if (editor != null) { 205 datePicker.remove(editor); 206 } 207 208 if (popupButton != null) { 209 datePicker.remove(popupButton); 210 popupButton = null; 211 } 212 } 213 214 /** 215 * Installs DatePicker default properties. 216 */ 217 protected void installDefaults() { 218 // PENDING JW: currently this is for testing only. 219 boolean zoomable = Boolean.TRUE.equals(UIManager.get("JXDatePicker.forceZoomable")); 220 if (zoomable) { 221 datePicker.getMonthView().setZoomable(true); 222 } 223 } 224 225 protected void uninstallDefaults() { 226 227 } 228 229 protected void installKeyboardActions() { 230 // install picker's actions 231 ActionMap pickerMap = datePicker.getActionMap(); 232 pickerMap.put(JXDatePicker.CANCEL_KEY, createCancelAction()); 233 pickerMap.put(JXDatePicker.COMMIT_KEY, createCommitAction()); 234 pickerMap.put(JXDatePicker.HOME_NAVIGATE_KEY, createHomeAction(false)); 235 pickerMap.put(JXDatePicker.HOME_COMMIT_KEY, createHomeAction(true)); 236 TogglePopupAction popupAction = createTogglePopupAction(); 237 pickerMap.put("TOGGLE_POPUP", popupAction); 238 239 InputMap pickerInputMap = datePicker.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 240 pickerInputMap.put(KeyStroke.getKeyStroke("ENTER"), JXDatePicker.COMMIT_KEY); 241 pickerInputMap.put(KeyStroke.getKeyStroke("ESCAPE"), JXDatePicker.CANCEL_KEY); 242 // PENDING: get from LF 243 pickerInputMap.put(KeyStroke.getKeyStroke("F5"), JXDatePicker.HOME_COMMIT_KEY); 244 pickerInputMap.put(KeyStroke.getKeyStroke("shift F5"), JXDatePicker.HOME_NAVIGATE_KEY); 245 pickerInputMap.put(KeyStroke.getKeyStroke("alt DOWN"), "TOGGLE_POPUP"); 246 247 installLinkPanelKeyboardActions(); 248 } 249 250 protected void uninstallKeyboardActions() { 251 uninstallLinkPanelKeyboardActions(datePicker.getLinkPanel()); 252 } 253 254 255 /** 256 * Installs actions and key bindings on the datePicker's linkPanel. Does 257 * nothing if the linkPanel is null. 258 * 259 * PRE: keybindings installed on picker. 260 */ 261 protected void installLinkPanelKeyboardActions() { 262 if (datePicker.getLinkPanel() == null) 263 return; 264 ActionMap map = datePicker.getLinkPanel().getActionMap(); 265 map.put(JXDatePicker.HOME_COMMIT_KEY, datePicker.getActionMap().get( 266 JXDatePicker.HOME_COMMIT_KEY)); 267 map.put(JXDatePicker.HOME_NAVIGATE_KEY, datePicker.getActionMap().get( 268 JXDatePicker.HOME_NAVIGATE_KEY)); 269 InputMap inputMap = datePicker.getLinkPanel().getInputMap( 270 JComponent.WHEN_IN_FOCUSED_WINDOW); 271 // PENDING: get from LF 272 inputMap.put(KeyStroke.getKeyStroke("F5"), 273 JXDatePicker.HOME_COMMIT_KEY); 274 inputMap.put(KeyStroke.getKeyStroke("shift F5"), 275 JXDatePicker.HOME_NAVIGATE_KEY); 276 } 277 278 279 /** 280 * Uninstalls actions and key bindings from linkPanel. Does nothing if the 281 * linkPanel is null. 282 * 283 * @param panel the component to uninstall 284 * 285 */ 286 protected void uninstallLinkPanelKeyboardActions(JComponent panel) { 287 if (panel == null) return; 288 ActionMap map = panel.getActionMap(); 289 map.remove(JXDatePicker.HOME_COMMIT_KEY); 290 map.remove(JXDatePicker.HOME_NAVIGATE_KEY); 291 InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 292 // PENDING: get from LF 293 inputMap.remove(KeyStroke.getKeyStroke("F5")); 294 inputMap.remove(KeyStroke.getKeyStroke("shift F5")); 295 296 } 297 298 /** 299 * Creates and installs all listeners to all components. 300 * 301 */ 302 protected void installListeners() { 303 /* 304 * create the listeners. 305 */ 306 // propertyListener for datePicker 307 propertyChangeListener = createPropertyChangeListener(); 308 309 // mouseListener (for popup button only) ? 310 mouseListener = createMouseListener(); 311 mouseMotionListener = createMouseMotionListener(); 312 313 // shared focuslistener (installed to picker and editor) 314 focusListener = createFocusListener(); 315 316 // editor related listeners 317 editorActionListener = createEditorActionListener(); 318 editorPropertyListener = createEditorPropertyListener(); 319 320 // montheView related listeners 321 monthViewSelectionListener = createMonthViewSelectionListener(); 322 monthViewActionListener = createMonthViewActionListener(); 323 monthViewPropertyListener = createMonthViewPropertyListener(); 324 325 popupRemover = new PopupRemover(); 326 /* 327 * install the listeners 328 */ 329 // picker 330 datePicker.addPropertyChangeListener(propertyChangeListener); 331 datePicker.addFocusListener(focusListener); 332 333 if (popupButton != null) { 334 // JW: which property do we want to monitor? 335 popupButton.addPropertyChangeListener(propertyChangeListener); 336 popupButton.addMouseListener(mouseListener); 337 popupButton.addMouseMotionListener(mouseMotionListener); 338 } 339 340 updateEditorListeners(null); 341 // JW the following does more than installing the listeners .. 342 // synchs properties of datepicker to monthView's 343 // prepares monthview for usage in popup 344 // synch the date 345 // Relies on being the last thing done in the install .. 346 // 347 updateFromMonthViewChanged(null); 348 } 349 /** 350 * Uninstalls and nulls all listeners which had been installed 351 * by this delegate. 352 * 353 */ 354 protected void uninstallListeners() { 355 // datePicker 356 datePicker.removePropertyChangeListener(propertyChangeListener); 357 datePicker.removeFocusListener(focusListener); 358 359 // monthView 360 datePicker.getMonthView().getSelectionModel().removeDateSelectionListener(monthViewSelectionListener); 361 datePicker.getMonthView().removeActionListener(monthViewActionListener); 362 datePicker.getMonthView().removePropertyChangeListener(propertyChangeListener); 363 364 // JW: when can that be null? 365 // maybe in the very beginning? if some code calls ui.uninstall 366 // before ui.install? The editor is created by the ui. 367 if (datePicker.getEditor() != null) { 368 uninstallEditorListeners(datePicker.getEditor()); 369 } 370 if (popupButton != null) { 371 popupButton.removePropertyChangeListener(propertyChangeListener); 372 popupButton.removeMouseListener(mouseListener); 373 popupButton.removeMouseMotionListener(mouseMotionListener); 374 } 375 376 popupRemover.unload(); 377 378 popupRemover = null; 379 propertyChangeListener = null; 380 mouseListener = null; 381 mouseMotionListener = null; 382 383 editorActionListener = null; 384 editorPropertyListener = null; 385 386 monthViewSelectionListener = null; 387 monthViewActionListener = null; 388 monthViewPropertyListener = null; 389 390 handler = null; 391 } 392 393 // --------------------- wiring listeners 394 /** 395 * Wires the picker's monthView related listening. Removes all 396 * listeners from the given old view and adds the listeners to 397 * the current monthView. <p> 398 * 399 * @param oldMonthView 400 */ 401 protected void updateMonthViewListeners(JXMonthView oldMonthView) { 402 DateSelectionModel oldModel = null; 403 if (oldMonthView != null) { 404 oldMonthView.removePropertyChangeListener(monthViewPropertyListener); 405 oldMonthView.removeActionListener(monthViewActionListener); 406 oldModel = oldMonthView.getSelectionModel(); 407 } 408 datePicker.getMonthView().addPropertyChangeListener(monthViewPropertyListener); 409 datePicker.getMonthView().addActionListener(monthViewActionListener); 410 updateSelectionModelListeners(oldModel); 411 } 412 413 414 /** 415 * Wires the picker's editor related listening and actions. Removes 416 * listeners/actions from the old editor and adds them to 417 * the new editor. <p> 418 * 419 * @param oldEditor the pickers editor before the change 420 */ 421 protected void updateEditorListeners(JFormattedTextField oldEditor) { 422 if (oldEditor != null) { 423 uninstallEditorListeners(oldEditor); 424 } 425 datePicker.getEditor().addPropertyChangeListener(editorPropertyListener); 426 datePicker.getEditor().addActionListener(editorActionListener); 427 datePicker.getEditor().addFocusListener(focusListener); 428 editorCancelAction = new EditorCancelAction(datePicker.getEditor()); 429 } 430 431 /** 432 * Uninstalls all listeners and actions which have been installed 433 * by this delegate from the given editor. 434 * 435 * @param oldEditor the editor to uninstall. 436 */ 437 private void uninstallEditorListeners(JFormattedTextField oldEditor) { 438 oldEditor.removePropertyChangeListener(editorPropertyListener); 439 oldEditor.removeActionListener(editorActionListener); 440 oldEditor.removeFocusListener(focusListener); 441 if (editorCancelAction != null) { 442 editorCancelAction.uninstall(); 443 editorCancelAction = null; 444 } 445 } 446 447 /** 448 * Wires monthView's selection model listening. Removes the 449 * selection listener from the old model and add to the new model. 450 * 451 * @param oldModel the dateSelectionModel before the change, may be null. 452 */ 453 protected void updateSelectionModelListeners(DateSelectionModel oldModel) { 454 if (oldModel != null) { 455 oldModel.removeDateSelectionListener(monthViewSelectionListener); 456 } 457 datePicker.getMonthView().getSelectionModel() 458 .addDateSelectionListener(monthViewSelectionListener); 459 460 } 461 462 463 // ---------------- component creation 464 /** 465 * Creates the editor used to edit the date selection. The editor is 466 * configured with the default DatePickerFormatter marked as UIResource. 467 * 468 * @return an instance of a JFormattedTextField 469 */ 470 protected JFormattedTextField createEditor() { 471 JFormattedTextField f = new DefaultEditor( 472 new DatePickerFormatterUIResource(datePicker.getLocale())); 473 f.setName("dateField"); 474 // this produces a fixed pref widths, looking a bit funny 475 // int columns = UIManagerExt.getInt("JXDatePicker.numColumns", null); 476 // if (columns > 0) { 477 // f.setColumns(columns); 478 // } 479 // that's always 0 as it comes from the resourcebundle 480 // f.setColumns(UIManager.getInt("JXDatePicker.numColumns")); 481 Border border = UIManager.getBorder("JXDatePicker.border"); 482 if (border != null) { 483 f.setBorder(border); 484 } 485 return f; 486 } 487 488 protected JButton createPopupButton() { 489 JButton b = new JButton(); 490 b.setName("popupButton"); 491 b.setRolloverEnabled(false); 492 b.setMargin(new Insets(0, 3, 0, 3)); 493 494 Icon icon = UIManager.getIcon("JXDatePicker.arrowIcon"); 495 if (icon == null) { 496 icon = (Icon)UIManager.get("Tree.expandedIcon"); 497 } 498 b.setIcon(icon); 499 b.setFocusable(false); 500 return b; 501 } 502 503 private class DefaultEditor extends JFormattedTextField implements UIResource { 504 505 private Dimension prefSizeCache; 506 private int prefEmptyInset; 507 508 509 public DefaultEditor(AbstractFormatter formatter) { 510 super(formatter); 511 } 512 513 514 @Override 515 public Dimension getPreferredSize() { 516 Dimension preferredSize = super.getPreferredSize(); 517 if (getColumns() <= 0) { 518 if (getValue() == null) { 519 if (prefSizeCache != null) { 520 preferredSize.width = prefSizeCache.width; 521 preferredSize.height = prefSizeCache.height; 522 } else { 523 prefEmptyInset = preferredSize.width; 524 preferredSize.width = prefEmptyInset + getNullWidth(); 525 } 526 } else { 527 preferredSize.width += Math.max(prefEmptyInset, 4); 528 prefSizeCache = new Dimension(preferredSize); 529 } 530 } 531 return preferredSize; 532 } 533 534 535 /** 536 * @return 537 */ 538 private int getNullWidth() { 539 JFormattedTextField field = new JFormattedTextField(getFormatter()); 540 field.setMargin(getMargin()); 541 field.setBorder(getBorder()); 542 field.setFont(getFont()); 543 field.setValue(new Date()); 544 return field.getPreferredSize().width; 545 } 546 547 548 } 549 550 // ---------------- Layout 551 /** 552 * {@inheritDoc} 553 */ 554 @Override 555 public Dimension getMinimumSize(JComponent c) { 556 return getPreferredSize(c); 557 } 558 559 /** 560 * {@inheritDoc} 561 */ 562 @Override 563 public Dimension getPreferredSize(JComponent c) { 564 Dimension dim = getEditorPreferredSize(); 565 if (popupButton != null) { 566 dim.width += popupButton.getPreferredSize().width; 567 } 568 Insets insets = datePicker.getInsets(); 569 dim.width += insets.left + insets.right; 570 dim.height += insets.top + insets.bottom; 571 return (Dimension)dim.clone(); 572 } 573 574 /** 575 * Returns a preferred size for the editor. If the selected date 576 * is null, returns a reasonable minimal width. <p> 577 * 578 * PENDING: how to find the "reasonable" width is open to discussion. 579 * This implementation creates another datepicker, feeds it with 580 * the formats and asks its prefWidth. <p> 581 * 582 * That hack blows in some contexts (see Issue #763) - as a very quick 583 * replacement create a editor only. 584 * 585 * PENDING: there's a resource property JXDatePicker.numColumns - why 586 * don't we use it? 587 * 588 * @return the editor's preferred size 589 */ 590 private Dimension getEditorPreferredSize() { 591 Dimension dim = datePicker.getEditor().getPreferredSize(); 592 if (datePicker.getDate() == null) { 593 // JFormattedTextField field = createEditor(new DatePickerFormatterUIResource( 594 // datePicker.getFormats(), 595 // datePicker.getLocale())); 596 // field.setValue(new Date()); 597 // dim.width = Math.max(field.getPreferredSize().width, dim.width); 598 // the editor tends to collapsing for empty values 599 // JW: better do this in a custom editor? 600 // seems to produce #763 601 // JXDatePicker picker = new JXDatePicker(new Date()); 602 // picker.setFormats(datePicker.getFormats()); 603 // dim.width = picker.getEditor().getPreferredSize().width; 604 } 605 return dim; 606 } 607 608 @Override 609 public int getBaseline(int width, int height) { 610 JFormattedTextField editor = datePicker.getEditor(); 611 View rootView = editor.getUI().getRootView(editor); 612 if (rootView.getViewCount() > 0) { 613 Insets insets = editor.getInsets(); 614 Insets insetsOut = datePicker.getInsets(); 615 int nh = height - insets.top - insets.bottom 616 - insetsOut.top - insetsOut.bottom; 617 int y = insets.top + insetsOut.top; 618 View fieldView = rootView.getView(0); 619 int vspan = (int) fieldView.getPreferredSpan(View.Y_AXIS); 620 if (nh != vspan) { 621 int slop = nh - vspan; 622 y += slop / 2; 623 } 624 FontMetrics fm = editor.getFontMetrics(editor.getFont()); 625 y += fm.getAscent(); 626 return y; 627 } 628 return -1; 629 } 630 631 632 //------------------------------- controller methods/classes 633 634 /** 635 * {@inheritDoc} 636 */ 637 @Override 638 public Date getSelectableDate(Date date) throws PropertyVetoException { 639 Date cleaned = date == null ? null : 640 datePicker.getMonthView().getSelectionModel().getNormalizedDate(date); 641 if (CalendarUtils.areEqual(cleaned, datePicker.getDate())) { 642 // one place to interrupt the update spiral 643 throw new PropertyVetoException("date not selectable", null); 644 } 645 if (cleaned == null) return cleaned; 646 if (datePicker.getMonthView().isUnselectableDate(cleaned)) { 647 throw new PropertyVetoException("date not selectable", null); 648 } 649 return cleaned; 650 } 651 652 //-------------------- update methods called from listeners 653 /** 654 * Updates internals after picker's date property changed. 655 */ 656 protected void updateFromDateChanged() { 657 Date visibleHook = datePicker.getDate() != null ? 658 datePicker.getDate() : datePicker.getLinkDay(); 659 datePicker.getMonthView().ensureDateVisible(visibleHook); 660 datePicker.getEditor().setValue(datePicker.getDate()); 661 } 662 663 /** 664 * Updates date related properties in picker/monthView 665 * after a change in the editor's value. Reverts the 666 * value if the new date is unselectable. 667 * 668 * @param oldDate the editor value before the change 669 * @param newDate the editor value after the change 670 */ 671 protected void updateFromValueChanged(Date oldDate, Date newDate) { 672 if ((newDate != null) && datePicker.getMonthView().isUnselectableDate(newDate)) { 673 revertValue(oldDate); 674 return; 675 } 676 // the other place to interrupt the update spiral 677 if (!CalendarUtils.areEqual(newDate, datePicker.getMonthView().getSelectionDate())) { 678 datePicker.getMonthView().setSelectionDate(newDate); 679 } 680 datePicker.setDate(newDate); 681 } 682 683 /** 684 * PENDING: currently this resets at once - but it's a no-no, 685 * because it happens during notification 686 * 687 * @param oldDate the old date to revert to 688 */ 689 private void revertValue(Date oldDate) { 690 datePicker.getEditor().setValue(oldDate); 691 } 692 /** 693 * Updates date related properties picker/editor 694 * after a change in the monthView's 695 * selection. 696 * 697 * Here: does nothing if the change is intermediate. 698 * 699 * PENDNG JW: shouldn't we listen to actionEvents then? 700 * 701 * @param eventType the type of the selection change 702 * @param adjusting flag to indicate whether the the selection change 703 * is intermediate 704 */ 705 protected void updateFromSelectionChanged(EventType eventType, boolean adjusting) { 706 if (adjusting) return; 707 updateEditorValue(); 708 } 709 710 /** 711 * Updates internals after the picker's monthView has changed. <p> 712 * 713 * Cleans to popup. Wires the listeners. Updates date. 714 * Updates formats' timezone. 715 * 716 * @param oldMonthView the picker's monthView before the change, 717 * may be null. 718 */ 719 protected void updateFromMonthViewChanged(JXMonthView oldMonthView) { 720 popup = null; 721 updateMonthViewListeners(oldMonthView); 722 TimeZone oldTimeZone = null; 723 if (oldMonthView != null) { 724 oldMonthView.setComponentInputMapEnabled(false); 725 oldTimeZone = oldMonthView.getTimeZone(); 726 } 727 datePicker.getMonthView().setComponentInputMapEnabled(true); 728 updateTimeZone(oldTimeZone); 729 updateEditorValue(); 730 } 731 732 733 /** 734 * Updates internals after the picker's editor property 735 * has changed. <p> 736 * 737 * Updates the picker's children. Removes the old editor and 738 * adds the new editor. Wires the editor listeners, it the flag 739 * set. Typically, this method is called during installing the 740 * componentUI with the flag set to false and true at all other 741 * moments. 742 * 743 * 744 * @param oldEditor the picker's editor before the change, 745 * may be null. 746 * @param updateListeners a flag to indicate whether the listeners 747 * are ready for usage. 748 */ 749 protected void updateFromEditorChanged(JFormattedTextField oldEditor, 750 boolean updateListeners) { 751 if (oldEditor != null) { 752 datePicker.remove(oldEditor); 753 oldEditor.putClientProperty("doNotCancelPopup", null); 754 } 755 datePicker.add(datePicker.getEditor()); 756 // this is a trick to get hold of the client prop which 757 // prevents closing of the popup 758 JComboBox box = new JComboBox(); 759 Object preventHide = box.getClientProperty("doNotCancelPopup"); 760 datePicker.getEditor().putClientProperty("doNotCancelPopup", preventHide); 761 762 updateEditorValue(); 763 if (updateListeners) { 764 updateEditorListeners(oldEditor); 765 datePicker.revalidate(); 766 } 767 } 768 769 770 /** 771 * Updates internals after the selection model changed. 772 * 773 * @param oldModel the model before the change. 774 */ 775 protected void updateFromSelectionModelChanged(DateSelectionModel oldModel) { 776 updateSelectionModelListeners(oldModel); 777 updateEditorValue(); 778 } 779 780 /** 781 * Sets the editor value to the model's selectedDate. 782 */ 783 private void updateEditorValue() { 784 datePicker.getEditor().setValue(datePicker.getMonthView().getSelectionDate()); 785 } 786 787 //---------------------- updating other properties 788 789 790 /** 791 * Updates properties which depend on the picker's editable. <p> 792 * 793 */ 794 protected void updateFromEditableChanged() { 795 boolean isEditable = datePicker.isEditable(); 796 datePicker.getMonthView().setEnabled(isEditable); 797 datePicker.getEditor().setEditable(isEditable); 798 /* 799 * PatrykRy: Commit today date is not allowed if datepicker is not editable! 800 */ 801 setActionEnabled(JXDatePicker.HOME_COMMIT_KEY, isEditable); 802 // for consistency, synch navigation as well 803 setActionEnabled(JXDatePicker.HOME_NAVIGATE_KEY, isEditable); 804 } 805 806 /** 807 * 808 * @param key 809 * @param enabled 810 */ 811 private void setActionEnabled(String key, boolean enabled) { 812 Action action = datePicker.getActionMap().get(key); 813 if (action != null) { 814 action.setEnabled(enabled); 815 } 816 } 817 818 /** 819 * Updates the picker's formats to the given TimeZone. 820 * @param zone the timezone to set on the formats. 821 */ 822 protected void updateFormatsFromTimeZone(TimeZone zone) { 823 for (DateFormat format : datePicker.getFormats()) { 824 format.setTimeZone(zone); 825 } 826 } 827 828 /** 829 * Updates picker's timezone dependent properties on change notification 830 * from the associated monthView. 831 * 832 * PENDING JW: DatePicker needs to send notification on timezone change? 833 * 834 * @param old the timezone before the change. 835 */ 836 protected void updateTimeZone(TimeZone old) { 837 updateFormatsFromTimeZone(datePicker.getTimeZone()); 838 updateLinkDate(); 839 } 840 841 /** 842 * Updates the picker's linkDate to be in synch with monthView's today. 843 */ 844 protected void updateLinkDate() { 845 datePicker.setLinkDay(datePicker.getMonthView().getToday()); 846 } 847 848 /** 849 * Called form property listener, updates all components locale, formats 850 * etc. 851 * 852 * @author PeS 853 */ 854 protected void updateLocale() { 855 Locale locale = datePicker.getLocale(); 856 updateFormatLocale(locale); 857 updateChildLocale(locale); 858 } 859 860 private void updateFormatLocale(Locale locale) { 861 if (locale != null) { 862 // PENDING JW: timezone? 863 if (getCustomFormats(datePicker.getEditor()) == null) { 864 datePicker.getEditor().setFormatterFactory( 865 new DefaultFormatterFactory( 866 new DatePickerFormatterUIResource(locale))); 867 } 868 } 869 } 870 871 private void updateChildLocale(Locale locale) { 872 if (locale != null) { 873 datePicker.getEditor().setLocale(locale); 874 datePicker.getLinkPanel().setLocale(locale); 875 datePicker.getMonthView().setLocale(locale); 876 } 877 } 878 879 /** 880 * @param oldLinkPanel 881 * 882 */ 883 protected void updateLinkPanel(JComponent oldLinkPanel) { 884 if (oldLinkPanel != null) { 885 uninstallLinkPanelKeyboardActions(oldLinkPanel); 886 } 887 installLinkPanelKeyboardActions(); 888 if (popup != null) { 889 popup.updateLinkPanel(oldLinkPanel); 890 } 891 } 892 893 894 //------------------- methods called by installed actions 895 896 /** 897 * 898 */ 899 protected void commit() { 900 hidePopup(); 901 try { 902 datePicker.commitEdit(); 903 } catch (ParseException ex) { 904 // can't help it 905 } 906 } 907 908 /** 909 * 910 */ 911 protected void cancel() { 912 hidePopup(); 913 datePicker.cancelEdit(); 914 } 915 916 /** 917 * PENDING: widened access for debugging - need api to 918 * control popup visibility? 919 */ 920 public void hidePopup() { 921 if (popup != null) popup.setVisible(false); 922 } 923 924 public boolean isPopupVisible() { 925 if (popup != null) { 926 return popup.isVisible(); 927 } 928 return false; 929 } 930 /** 931 * Navigates to linkDate. If commit, the linkDate is selected 932 * and committed. If not commit, the linkDate is scrolled to visible, if the 933 * monthview is open, does nothing for invisible monthView. 934 * 935 * @param commit boolean to indicate whether the linkDate should be 936 * selected and committed 937 */ 938 protected void home(boolean commit) { 939 if (commit) { 940 Calendar cal = datePicker.getMonthView().getCalendar(); 941 cal.setTime(datePicker.getLinkDay()); 942 datePicker.getMonthView().setSelectionDate(cal.getTime()); 943 datePicker.getMonthView().commitSelection(); 944 } else { 945 datePicker.getMonthView().ensureDateVisible(datePicker.getLinkDay()); 946 } 947 } 948 949 //---------------------- other stuff 950 951 /** 952 * Creates and returns the action for committing the picker's 953 * input. 954 * 955 * @return 956 */ 957 private Action createCommitAction() { 958 Action action = new AbstractAction() { 959 960 public void actionPerformed(ActionEvent e) { 961 commit(); 962 } 963 964 }; 965 return action; 966 } 967 968 /** 969 * Creates and returns the action for cancel the picker's 970 * edit. 971 * 972 * @return 973 */ 974 private Action createCancelAction() { 975 Action action = new AbstractAction() { 976 977 public void actionPerformed(ActionEvent e) { 978 cancel(); 979 } 980 981 }; 982 return action; 983 } 984 985 private Action createHomeAction(final boolean commit) { 986 Action action = new AbstractAction( ) { 987 988 public void actionPerformed(ActionEvent e) { 989 home(commit); 990 991 } 992 993 }; 994 return action ; 995 } 996 /** 997 * The wrapper for the editor cancel action. 998 * 999 * PENDING: Need to extend TestAction? 1000 * 1001 */ 1002 public class EditorCancelAction extends AbstractAction { 1003 private JFormattedTextField editor; 1004 private Action cancelAction; 1005 public static final String TEXT_CANCEL_KEY = "reset-field-edit"; 1006 1007 public EditorCancelAction(JFormattedTextField field) { 1008 install(field); 1009 } 1010 1011 /** 1012 * Resets the contained editors actionMap to original and 1013 * nulls all fields. <p> 1014 * NOTE: after calling this method the action must not be 1015 * used! Create a new one for the same or another editor. 1016 * 1017 */ 1018 public void uninstall() { 1019 editor.getActionMap().remove(TEXT_CANCEL_KEY); 1020 cancelAction = null; 1021 editor = null; 1022 } 1023 1024 /** 1025 * @param editor 1026 */ 1027 private void install(JFormattedTextField editor) { 1028 this.editor = editor; 1029 cancelAction = editor.getActionMap().get(TEXT_CANCEL_KEY); 1030 editor.getActionMap().put(TEXT_CANCEL_KEY, this); 1031 } 1032 1033 public void actionPerformed(ActionEvent e) { 1034 cancelAction.actionPerformed(null); 1035 cancel(); 1036 } 1037 1038 } 1039 1040 /** 1041 * Creates and returns the action which toggles the visibility of the popup. 1042 * 1043 * @return the action which toggles the visibility of the popup. 1044 */ 1045 protected TogglePopupAction createTogglePopupAction() { 1046 return new TogglePopupAction(); 1047 } 1048 1049 1050 /** 1051 * Toggles the popups visibility after preparing internal state. 1052 * 1053 * 1054 */ 1055 public void toggleShowPopup() { 1056 if (popup == null) { 1057 popup = createMonthViewPopup(); 1058 } 1059 if (popup.isVisible()) { 1060 popup.setVisible(false); 1061 } else { 1062 // PENDING JW: Issue 757-swing - datePicker firing focusLost on opening 1063 // not with following line - but need to run tests 1064 datePicker.getEditor().requestFocusInWindow(); 1065 // datePicker.requestFocusInWindow(); 1066 SwingUtilities.invokeLater(new Runnable() { 1067 public void run() { 1068 popup.show(datePicker, 1069 0, datePicker.getHeight()); 1070 } 1071 }); 1072 } 1073 1074 } 1075 1076 /** 1077 * 1078 */ 1079 private BasicDatePickerPopup createMonthViewPopup() { 1080 BasicDatePickerPopup popup = new BasicDatePickerPopup(); 1081 popup.setLightWeightPopupEnabled(datePicker.isLightWeightPopupEnabled()); 1082 return popup; 1083 } 1084 /** 1085 * Action used to commit the current value in the JFormattedTextField. 1086 * This action is used by the keyboard bindings. 1087 */ 1088 private class TogglePopupAction extends AbstractAction { 1089 public TogglePopupAction() { 1090 super("TogglePopup"); 1091 } 1092 1093 public void actionPerformed(ActionEvent ev) { 1094 toggleShowPopup(); 1095 } 1096 } 1097 1098 1099 /** 1100 * Popup component that shows a JXMonthView component along with controlling 1101 * buttons to allow traversal of the months. Upon selection of a date the 1102 * popup will automatically hide itself and enter the selection into the 1103 * editable field of the JXDatePicker. 1104 * 1105 */ 1106 protected class BasicDatePickerPopup extends JPopupMenu { 1107 1108 public BasicDatePickerPopup() { 1109 setLayout(new BorderLayout()); 1110 add(datePicker.getMonthView(), BorderLayout.CENTER); 1111 updateLinkPanel(null); 1112 } 1113 1114 /** 1115 * @param oldLinkPanel 1116 */ 1117 public void updateLinkPanel(JComponent oldLinkPanel) { 1118 if (oldLinkPanel != null) { 1119 remove(oldLinkPanel); 1120 } 1121 if (datePicker.getLinkPanel() != null) { 1122 add(datePicker.getLinkPanel(), BorderLayout.SOUTH); 1123 } 1124 1125 } 1126 } 1127 1128 /** 1129 * PENDING: JW - I <b>really</b> hate the one-in-all. Wont touch 1130 * it for now, maybe later. As long as we have it, the new 1131 * listeners (dateSelection) are here too, for consistency. 1132 * Adding the Layout here as well is ... , IMO. 1133 */ 1134 private class Handler implements LayoutManager, MouseListener, MouseMotionListener, 1135 PropertyChangeListener, DateSelectionListener, ActionListener, FocusListener { 1136 1137 //------------- implement Mouse/MotionListener 1138 private boolean _forwardReleaseEvent = false; 1139 1140 public void mouseClicked(MouseEvent ev) { 1141 } 1142 1143 public void mousePressed(MouseEvent ev) { 1144 if (!datePicker.isEnabled()) { 1145 return; 1146 } 1147 // PENDING JW: why do we need a mouseListener? the 1148 // arrowbutton should have the toggleAction installed? 1149 // Hmm... maybe doesn't ... check! 1150 // reason might be that we want to open on pressed 1151 // typically (or LF-dependent?), 1152 // the button's action is invoked on released. 1153 toggleShowPopup(); 1154 } 1155 1156 public void mouseReleased(MouseEvent ev) { 1157 if (!datePicker.isEnabled() || !datePicker.isEditable()) { 1158 return; 1159 } 1160 1161 // Retarget mouse event to the month view. 1162 if (_forwardReleaseEvent) { 1163 JXMonthView monthView = datePicker.getMonthView(); 1164 ev = SwingUtilities.convertMouseEvent(popupButton, ev, 1165 monthView); 1166 monthView.dispatchEvent(ev); 1167 _forwardReleaseEvent = false; 1168 } 1169 } 1170 1171 public void mouseEntered(MouseEvent ev) { 1172 } 1173 1174 public void mouseExited(MouseEvent ev) { 1175 } 1176 1177 public void mouseDragged(MouseEvent ev) { 1178 if (!datePicker.isEnabled() || !datePicker.isEditable()) { 1179 return; 1180 } 1181 1182 _forwardReleaseEvent = true; 1183 1184 if (!popup.isShowing()) { 1185 return; 1186 } 1187 1188 // Retarget mouse event to the month view. 1189 JXMonthView monthView = datePicker.getMonthView(); 1190 ev = SwingUtilities.convertMouseEvent(popupButton, ev, monthView); 1191 monthView.dispatchEvent(ev); 1192 } 1193 1194 public void mouseMoved(MouseEvent ev) { 1195 } 1196 //------------------ implement DateSelectionListener 1197 1198 public void valueChanged(DateSelectionEvent ev) { 1199 updateFromSelectionChanged(ev.getEventType(), ev.isAdjusting()); 1200 } 1201 1202 //------------------ implement propertyChangeListener 1203 /** 1204 * {@inheritDoc} 1205 */ 1206 public void propertyChange(PropertyChangeEvent e) { 1207 if (e.getSource() == datePicker) { 1208 datePickerPropertyChange(e); 1209 } else 1210 if (e.getSource() == datePicker.getEditor()) { 1211 editorPropertyChange(e); 1212 } else 1213 if (e.getSource() == datePicker.getMonthView()) { 1214 monthViewPropertyChange(e); 1215 } else 1216 if (e.getSource() == popupButton) { 1217 buttonPropertyChange(e); 1218 } else 1219 // PENDING - move back, ... 1220 if ("value".equals(e.getPropertyName())) { 1221 throw new IllegalStateException( 1222 "editor listening is moved to dedicated propertyChangeLisener"); 1223 } 1224 } 1225 1226 /** 1227 * Handles property changes from datepicker's editor. 1228 * 1229 * @param e the PropertyChangeEvent object describing the event source 1230 * and the property that has changed 1231 */ 1232 private void editorPropertyChange(PropertyChangeEvent evt) { 1233 if ("value".equals(evt.getPropertyName())) { 1234 updateFromValueChanged((Date) evt.getOldValue(), (Date) evt 1235 .getNewValue()); 1236 } 1237 1238 } 1239 1240 /** 1241 * Handles property changes from DatePicker. 1242 * @param e the PropertyChangeEvent object describing the 1243 * event source and the property that has changed 1244 */ 1245 private void datePickerPropertyChange(PropertyChangeEvent e) { 1246 String property = e.getPropertyName(); 1247 if ("date".equals(property)) { 1248 updateFromDateChanged(); 1249 } else if ("enabled".equals(property)) { 1250 boolean isEnabled = datePicker.isEnabled(); 1251 popupButton.setEnabled(isEnabled); 1252 datePicker.getEditor().setEnabled(isEnabled); 1253 } else if ("editable".equals(property)) { 1254 updateFromEditableChanged(); 1255 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(property)) { 1256 String tip = datePicker.getToolTipText(); 1257 datePicker.getEditor().setToolTipText(tip); 1258 popupButton.setToolTipText(tip); 1259 } else if (JXDatePicker.MONTH_VIEW.equals(property)) { 1260 updateFromMonthViewChanged((JXMonthView) e.getOldValue()); 1261 } else if (JXDatePicker.LINK_PANEL.equals(property)) { 1262 updateLinkPanel((JComponent) e.getOldValue()); 1263 } else if (JXDatePicker.EDITOR.equals(property)) { 1264 updateFromEditorChanged((JFormattedTextField) e.getOldValue(), true); 1265 } else if ("componentOrientation".equals(property)) { 1266 datePicker.revalidate(); 1267 } else if ("lightWeightPopupEnabled".equals(property)) { 1268 // Force recreation of the popup when this property changes. 1269 if (popup != null) { 1270 popup.setVisible(false); 1271 } 1272 popup = null; 1273 } else if ("formats".equals(property)) { 1274 updateFormatsFromTimeZone(datePicker.getTimeZone()); 1275 } 1276 else if ("locale".equals(property)) { 1277 updateLocale(); 1278 } 1279 } 1280 1281 /** 1282 * Handles propertyChanges from the picker's monthView. 1283 * 1284 * @param e the PropertyChangeEvent object describing the event source 1285 * and the property that has changed 1286 */ 1287 private void monthViewPropertyChange(PropertyChangeEvent e) { 1288 if ("selectionModel".equals(e.getPropertyName())) { 1289 updateFromSelectionModelChanged((DateSelectionModel) e.getOldValue()); 1290 } else if ("timeZone".equals(e.getPropertyName())) { 1291 updateTimeZone((TimeZone) e.getOldValue()); 1292 } else if ("today".equals(e.getPropertyName())) { 1293 updateLinkDate(); 1294 } 1295 } 1296 1297 /** 1298 * Handles propertyChanges from the picker's popupButton. 1299 * 1300 * PENDING: does nothing, kept while refactoring .. which 1301 * properties from the button do we want to handle? 1302 * 1303 * @param e the PropertyChangeEvent object describing the event source 1304 * and the property that has changed. 1305 */ 1306 private void buttonPropertyChange(PropertyChangeEvent e) { 1307 } 1308 1309 //-------------- implement LayoutManager 1310 1311 public void addLayoutComponent(String name, Component comp) { } 1312 1313 public void removeLayoutComponent(Component comp) { } 1314 1315 public Dimension preferredLayoutSize(Container parent) { 1316 return parent.getPreferredSize(); 1317 } 1318 1319 public Dimension minimumLayoutSize(Container parent) { 1320 return parent.getMinimumSize(); 1321 } 1322 1323 public void layoutContainer(Container parent) { 1324 Insets insets = datePicker.getInsets(); 1325 int width = datePicker.getWidth() - insets.left - insets.right; 1326 int height = datePicker.getHeight() - insets.top - insets.bottom; 1327 1328 int popupButtonWidth = popupButton != null ? popupButton.getPreferredSize().width : 0; 1329 1330 boolean ltr = datePicker.getComponentOrientation().isLeftToRight(); 1331 1332 datePicker.getEditor().setBounds(ltr ? insets.left : insets.left + popupButtonWidth, 1333 insets.top, 1334 width - popupButtonWidth, 1335 height); 1336 1337 if (popupButton != null) { 1338 popupButton.setBounds(ltr ? width - popupButtonWidth + insets.left : insets.left, 1339 insets.top, 1340 popupButtonWidth, 1341 height); 1342 } 1343 } 1344 1345 // ------------- implement actionListener (listening to monthView actionEvent) 1346 1347 public void actionPerformed(ActionEvent e) { 1348 if (e == null) return; 1349 if (e.getSource() == datePicker.getMonthView()) { 1350 monthViewActionPerformed(e); 1351 } else if (e.getSource() == datePicker.getEditor()) { 1352 editorActionPerformed(e); 1353 } 1354 } 1355 1356 /** 1357 * Listening to actionEvents fired by the picker's editor. 1358 * 1359 * @param e 1360 */ 1361 private void editorActionPerformed(ActionEvent e) { 1362 // pass the commit on to the picker. 1363 commit(); 1364 } 1365 1366 /** 1367 * Listening to actionEvents fired by the picker's monthView. 1368 * 1369 * @param e 1370 */ 1371 private void monthViewActionPerformed(ActionEvent e) { 1372 if (JXMonthView.CANCEL_KEY.equals(e.getActionCommand())) { 1373 cancel(); 1374 } else if (JXMonthView.COMMIT_KEY.equals(e.getActionCommand())) { 1375 commit(); 1376 } 1377 } 1378 1379 //------------------- focusListener 1380 1381 /** 1382 * Issue #573-swingx - F2 in table doesn't focus the editor. 1383 * 1384 * Do the same as combo: manually pass-on the focus to the editor. 1385 * 1386 */ 1387 public void focusGained(FocusEvent e) { 1388 if (e.isTemporary()) return; 1389 popupRemover.load(); 1390 if (e.getSource() == datePicker) { 1391 datePicker.getEditor().requestFocusInWindow(); 1392 } 1393 } 1394 1395 /** 1396 * #565-swingx: popup not hidden if clicked into combo. 1397 * The problem is that the combo uses the same trick as 1398 * this datepicker to prevent auto-closing of the popup 1399 * if focus is transfered back to the picker's editor. 1400 * 1401 * The idea is to hide the popup manually when the 1402 * permanentFocusOwner changes to somewhere else. 1403 * 1404 * JW: doesn't work - we only get the temporary lost, 1405 * but no permanent loss if the focus is transfered from 1406 * the focusOwner to a new permanentFocusOwner. 1407 * 1408 * OOOkaay ... looks like exclusively related to a combo: 1409 * we do get the expected focusLost if the focus is 1410 * transferred permanently from the temporary focusowner 1411 * to a new "normal" permanentFocusOwner (like a textfield), 1412 * we don't get it if transfered to a tricksing owner (like 1413 * a combo or picker). So can't do anything here. 1414 * 1415 * listen to keyboardFocusManager? 1416 */ 1417 public void focusLost(FocusEvent e) { 1418 1419 } 1420 } 1421 1422 public class PopupRemover implements PropertyChangeListener { 1423 1424 private KeyboardFocusManager manager; 1425 private boolean loaded; 1426 1427 public void load() { 1428 if (manager != KeyboardFocusManager.getCurrentKeyboardFocusManager()) { 1429 unload(); 1430 manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 1431 } 1432 if (!loaded) { 1433 manager.addPropertyChangeListener("permanentFocusOwner", this); 1434 loaded = true; 1435 } 1436 } 1437 1438 /** 1439 * @param b 1440 */ 1441 private void unload(boolean nullManager) { 1442 if (manager != null) { 1443 manager.removePropertyChangeListener("permanentFocusOwner", this); 1444 if (nullManager) { 1445 manager = null; 1446 } 1447 } 1448 loaded = false; 1449 } 1450 1451 public void unload() { 1452 unload(true); 1453 } 1454 1455 public void propertyChange(PropertyChangeEvent evt) { 1456 if (!isPopupVisible()) { 1457 unload(false); 1458 return; 1459 } 1460 Component comp = manager.getPermanentFocusOwner(); 1461 if ((comp != null) && !SwingXUtilities.isDescendingFrom(comp, datePicker)) { 1462 unload(false); 1463 // on hiding the popup the focusmanager transfers 1464 // focus back to the old permanentFocusOwner 1465 // before showing the popup, that is the picker 1466 // or the editor. So we have to force it back ... 1467 hidePopup(); 1468 comp.requestFocusInWindow(); 1469 // this has no effect as focus changes are asynchronous 1470 // inHide = false; 1471 } 1472 } 1473 1474 1475 } 1476 1477 1478 // ------------------ listener creation 1479 1480 /** 1481 * Creates and returns the property change listener for the 1482 * picker's monthView 1483 * @return the listener for monthView properties 1484 */ 1485 protected PropertyChangeListener createMonthViewPropertyListener() { 1486 return getHandler(); 1487 } 1488 1489 /** 1490 * Creates and returns the focuslistener for picker and editor. 1491 * @return the focusListener 1492 */ 1493 protected FocusListener createFocusListener() { 1494 return getHandler(); 1495 } 1496 1497 1498 /** 1499 * Creates and returns the ActionListener for the picker's editor. 1500 * @return the Actionlistener for the editor. 1501 */ 1502 protected ActionListener createEditorActionListener() { 1503 return getHandler(); 1504 } 1505 1506 /** 1507 * Creates and returns the ActionListener for the picker's monthView. 1508 * 1509 * @return the Actionlistener for the monthView. 1510 */ 1511 protected ActionListener createMonthViewActionListener() { 1512 return getHandler(); 1513 } 1514 1515 /** 1516 * Returns the listener for the dateSelection. 1517 * 1518 * @return the date selection listener 1519 */ 1520 protected DateSelectionListener createMonthViewSelectionListener() { 1521 return getHandler(); 1522 } 1523 1524 /** 1525 * @return a propertyChangeListener listening to 1526 * editor property changes 1527 */ 1528 protected PropertyChangeListener createEditorPropertyListener() { 1529 return getHandler(); 1530 } 1531 1532 /** 1533 * Lazily creates and returns the shared all-mighty listener of everything 1534 * 1535 * @return the shared listener. 1536 */ 1537 private Handler getHandler() { 1538 if (handler == null) { 1539 handler = new Handler(); 1540 } 1541 return handler; 1542 } 1543 1544 protected PropertyChangeListener createPropertyChangeListener() { 1545 return getHandler(); 1546 } 1547 1548 protected LayoutManager createLayoutManager() { 1549 return getHandler(); 1550 } 1551 1552 protected MouseListener createMouseListener() { 1553 return getHandler(); 1554 } 1555 1556 protected MouseMotionListener createMouseMotionListener() { 1557 return getHandler(); 1558 } 1559 1560 1561 //------------ utility methods 1562 1563 1564 }