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