001 /* 002 * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle, 003 * Santa Clara, California 95054, U.S.A. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library; if not, write to the Free Software 017 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 018 */ 019 package org.jdesktop.swingx.plaf.basic; 020 021 import org.jdesktop.swingx.JXDatePicker; 022 import org.jdesktop.swingx.JXDatePickerFormatter; 023 import org.jdesktop.swingx.calendar.DateSpan; 024 import org.jdesktop.swingx.calendar.JXMonthView; 025 import org.jdesktop.swingx.plaf.DatePickerUI; 026 027 import javax.swing.*; 028 import javax.swing.plaf.ComponentUI; 029 import javax.swing.plaf.UIResource; 030 import javax.swing.text.View; 031 import java.awt.*; 032 import java.awt.event.*; 033 import java.beans.PropertyChangeEvent; 034 import java.beans.PropertyChangeListener; 035 import java.util.Date; 036 037 /** 038 * @author Joshua Outwater 039 */ 040 public class BasicDatePickerUI extends DatePickerUI { 041 protected JXDatePicker datePicker; 042 private JButton popupButton; 043 private BasicDatePickerPopup popup; 044 private Handler handler; 045 protected PropertyChangeListener propertyChangeListener; 046 protected MouseListener mouseListener; 047 protected MouseMotionListener mouseMotionListener; 048 049 public static ComponentUI createUI(JComponent c) { 050 return new BasicDatePickerUI(); 051 } 052 053 @Override 054 public void installUI(JComponent c) { 055 datePicker = (JXDatePicker)c; 056 datePicker.setLayout(createLayoutManager()); 057 installComponents(); 058 installDefaults(); 059 installKeyboardActions(); 060 installListeners(); 061 } 062 063 @Override 064 public void uninstallUI(JComponent c) { 065 uninstallListeners(); 066 uninstallKeyboardActions(); 067 uninstallDefaults(); 068 uninstallComponents(); 069 datePicker.setLayout(null); 070 datePicker = null; 071 } 072 073 protected void installComponents() { 074 JFormattedTextField editor = datePicker.getEditor(); 075 if (editor == null || editor instanceof UIResource) { 076 datePicker.setEditor(createEditor()); 077 } 078 datePicker.add(datePicker.getEditor()); 079 080 popupButton = createPopupButton(); 081 082 if (popupButton != null) { 083 // this is a trick to get hold of the client prop which 084 // prevents closing of the popup 085 JComboBox box = new JComboBox(); 086 Object preventHide = box.getClientProperty("doNotCancelPopup"); 087 popupButton.putClientProperty("doNotCancelPopup", preventHide); 088 datePicker.add(popupButton); 089 } 090 } 091 092 protected void uninstallComponents() { 093 datePicker.remove(datePicker.getEditor()); 094 095 if (popupButton != null) { 096 datePicker.remove(popupButton); 097 popupButton = null; 098 } 099 } 100 101 protected void installDefaults() { 102 103 } 104 105 protected void uninstallDefaults() { 106 107 } 108 109 protected void installKeyboardActions() { 110 KeyStroke enterKey = 111 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false); 112 113 JFormattedTextField editor = datePicker.getEditor(); 114 InputMap inputMap = editor.getInputMap(JComponent.WHEN_FOCUSED); 115 inputMap.put(enterKey, "COMMIT_EDIT"); 116 117 ActionMap actionMap = editor.getActionMap(); 118 actionMap.put("COMMIT_EDIT", new CommitEditAction()); 119 120 KeyStroke spaceKey = 121 KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false); 122 123 inputMap = popupButton.getInputMap(JComponent.WHEN_FOCUSED); 124 inputMap.put(spaceKey, "TOGGLE_POPUP"); 125 126 actionMap = popupButton.getActionMap(); 127 actionMap.put("TOGGLE_POPUP", new TogglePopupAction()); 128 129 } 130 131 protected void uninstallKeyboardActions() { 132 133 } 134 135 protected void installListeners() { 136 propertyChangeListener = createPropertyChangeListener(); 137 mouseListener = createMouseListener(); 138 mouseMotionListener = createMouseMotionListener(); 139 140 datePicker.addPropertyChangeListener(propertyChangeListener); 141 142 if (popupButton != null) { 143 popupButton.addPropertyChangeListener(propertyChangeListener); 144 popupButton.addMouseListener(mouseListener); 145 popupButton.addMouseMotionListener(mouseMotionListener); 146 } 147 148 } 149 150 protected void uninstallListeners() { 151 datePicker.removePropertyChangeListener(propertyChangeListener); 152 153 if (popupButton != null) { 154 popupButton.removePropertyChangeListener(propertyChangeListener); 155 popupButton.removeMouseListener(mouseListener); 156 popupButton.removeMouseMotionListener(mouseMotionListener); 157 } 158 159 propertyChangeListener = null; 160 mouseListener = null; 161 mouseMotionListener = null; 162 handler = null; 163 } 164 165 private Handler getHandler() { 166 if (handler == null) { 167 handler = new Handler(); 168 } 169 return handler; 170 } 171 172 protected PropertyChangeListener createPropertyChangeListener() { 173 return getHandler(); 174 } 175 176 protected LayoutManager createLayoutManager() { 177 return getHandler(); 178 } 179 180 protected MouseListener createMouseListener() { 181 return getHandler(); 182 } 183 184 protected MouseMotionListener createMouseMotionListener() { 185 return getHandler(); 186 } 187 188 /** 189 * Creates the editor used to edit the date selection. Subclasses should 190 * override this method if they want to substitute in their own editor. 191 * 192 * @return an instance of a JFormattedTextField 193 */ 194 protected JFormattedTextField createEditor() { 195 JFormattedTextField f = new DefaultEditor(new JXDatePickerFormatter()); 196 f.setName("dateField"); 197 f.setColumns(UIManager.getInt("JXDatePicker.numColumns")); 198 f.setBorder(UIManager.getBorder("JXDatePicker.border")); 199 200 return f; 201 } 202 203 protected JButton createPopupButton() { 204 JButton b = new JButton(); 205 b.setName("popupButton"); 206 b.setRolloverEnabled(false); 207 b.setMargin(new Insets(0, 3, 0, 3)); 208 209 Icon icon = UIManager.getIcon("JXDatePicker.arrowDown.image"); 210 if (icon == null) { 211 icon = (Icon)UIManager.get("Tree.expandedIcon"); 212 } 213 b.setIcon(icon); 214 215 return b; 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public Dimension getMinimumSize(JComponent c) { 223 return getPreferredSize(c); 224 } 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public Dimension getPreferredSize(JComponent c) { 231 Dimension dim = datePicker.getEditor().getPreferredSize(); 232 if (popupButton != null) { 233 dim.width += popupButton.getPreferredSize().width; 234 } 235 Insets insets = datePicker.getInsets(); 236 dim.width += insets.left + insets.right; 237 dim.height += insets.top + insets.bottom; 238 return (Dimension)dim.clone(); 239 } 240 241 @Override 242 public int getBaseline(int width, int height) { 243 JFormattedTextField editor = datePicker.getEditor(); 244 View rootView = editor.getUI().getRootView(editor); 245 if (rootView.getViewCount() > 0) { 246 Insets insets = editor.getInsets(); 247 Insets insetsOut = datePicker.getInsets(); 248 int nh = height - insets.top - insets.bottom 249 - insetsOut.top - insetsOut.bottom; 250 int y = insets.top + insetsOut.top; 251 View fieldView = rootView.getView(0); 252 int vspan = (int) fieldView.getPreferredSpan(View.Y_AXIS); 253 if (nh != vspan) { 254 int slop = nh - vspan; 255 y += slop / 2; 256 } 257 FontMetrics fm = editor.getFontMetrics(editor.getFont()); 258 y += fm.getAscent(); 259 return y; 260 } 261 return -1; 262 } 263 264 /** 265 * Action used to commit the current value in the JFormattedTextField. 266 * This action is used by the keyboard bindings. 267 */ 268 private class CommitEditAction extends AbstractAction { 269 public CommitEditAction() { 270 super("CommitEditPopup"); 271 } 272 273 public void actionPerformed(ActionEvent ev) { 274 try { 275 JFormattedTextField editor = datePicker.getEditor(); 276 // Commit the current value. 277 editor.commitEdit(); 278 279 // Reformat the value according to the formatter. 280 editor.setValue(editor.getValue()); 281 datePicker.postActionEvent(); 282 } catch (java.text.ParseException ex) { 283 } 284 } 285 } 286 287 /** 288 * Action used to commit the current value in the JFormattedTextField. 289 * This action is used by the keyboard bindings. 290 */ 291 private class TogglePopupAction extends AbstractAction { 292 public TogglePopupAction() { 293 super("TogglePopup"); 294 } 295 296 public void actionPerformed(ActionEvent ev) { 297 handler.toggleShowPopup(); 298 } 299 } 300 301 private class DefaultEditor extends JFormattedTextField implements UIResource { 302 public DefaultEditor(AbstractFormatter formatter) { 303 super(formatter); 304 } 305 } 306 307 /** 308 * Popup component that shows a JXMonthView component along with controlling 309 * buttons to allow traversal of the months. Upon selection of a date the 310 * popup will automatically hide itself and enter the selection into the 311 * editable field of the JXDatePicker. 312 */ 313 protected class BasicDatePickerPopup extends JPopupMenu 314 implements ActionListener { 315 316 public BasicDatePickerPopup() { 317 JXMonthView monthView = datePicker.getMonthView(); 318 monthView.setActionCommand("MONTH_VIEW"); 319 monthView.addActionListener(this); 320 321 setLayout(new BorderLayout()); 322 add(monthView, BorderLayout.CENTER); 323 JPanel linkPanel = datePicker.getLinkPanel(); 324 if (linkPanel != null) { 325 add(linkPanel, BorderLayout.SOUTH); 326 } 327 } 328 329 public void actionPerformed(ActionEvent ev) { 330 String command = ev.getActionCommand(); 331 if ("MONTH_VIEW".equals(command)) { 332 DateSpan span = datePicker.getMonthView().getSelectedDateSpan(); 333 datePicker.getEditor().setValue(span.getStartAsDate()); 334 setVisible(false); 335 datePicker.postActionEvent(); 336 } 337 } 338 } 339 340 341 private class Handler implements LayoutManager, MouseListener, MouseMotionListener, 342 PropertyChangeListener { 343 private boolean _forwardReleaseEvent = false; 344 345 public void mouseClicked(MouseEvent ev) { 346 } 347 348 public void mousePressed(MouseEvent ev) { 349 if (!datePicker.isEnabled()) { 350 return; 351 } 352 353 if (!datePicker.isEditable()) { 354 JFormattedTextField editor = datePicker.getEditor(); 355 if (editor.isEditValid()) { 356 try { 357 editor.commitEdit(); 358 } catch (java.text.ParseException ex) { 359 } 360 } 361 } 362 toggleShowPopup(); 363 } 364 365 public void mouseReleased(MouseEvent ev) { 366 if (!datePicker.isEnabled() || !datePicker.isEditable()) { 367 return; 368 } 369 370 // Retarget mouse event to the month view. 371 if (_forwardReleaseEvent) { 372 JXMonthView monthView = datePicker.getMonthView(); 373 ev = SwingUtilities.convertMouseEvent(popupButton, ev, 374 monthView); 375 monthView.dispatchEvent(ev); 376 _forwardReleaseEvent = false; 377 } 378 } 379 380 public void mouseEntered(MouseEvent ev) { 381 } 382 383 public void mouseExited(MouseEvent ev) { 384 } 385 386 public void mouseDragged(MouseEvent ev) { 387 if (!datePicker.isEnabled() || !datePicker.isEditable()) { 388 return; 389 } 390 391 _forwardReleaseEvent = true; 392 393 if (!popup.isShowing()) { 394 return; 395 } 396 397 // Retarget mouse event to the month view. 398 JXMonthView monthView = datePicker.getMonthView(); 399 ev = SwingUtilities.convertMouseEvent(popupButton, ev, monthView); 400 monthView.dispatchEvent(ev); 401 } 402 403 public void mouseMoved(MouseEvent ev) { 404 } 405 406 public void toggleShowPopup() { 407 if (popup == null) { 408 popup = new BasicDatePickerPopup(); 409 } 410 if (!popup.isVisible()) { 411 JFormattedTextField editor = datePicker.getEditor(); 412 if (editor.getValue() == null) { 413 editor.setValue(new Date(datePicker.getLinkDate())); 414 } 415 DateSpan span = 416 new DateSpan((java.util.Date)editor.getValue(), 417 (java.util.Date)editor.getValue()); 418 JXMonthView monthView = datePicker.getMonthView(); 419 monthView.setSelectedDateSpan(span); 420 monthView.ensureDateVisible( 421 ((Date)editor.getValue()).getTime()); 422 popup.show(datePicker, 423 0, datePicker.getHeight()); 424 } else { 425 popup.setVisible(false); 426 } 427 } 428 429 public void propertyChange(PropertyChangeEvent e) { 430 String property = e.getPropertyName(); 431 if ("enabled".equals(property)) { 432 boolean isEnabled = datePicker.isEnabled(); 433 popupButton.setEnabled(isEnabled); 434 datePicker.getEditor().setEnabled(isEnabled); 435 } else if ("editable".equals(property)) { 436 boolean isEditable = datePicker.isEditable(); 437 datePicker.getMonthView().setEnabled(isEditable); 438 datePicker.getEditor().setEditable(isEditable); 439 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(property)) { 440 String tip = datePicker.getToolTipText(); 441 datePicker.getEditor().setToolTipText(tip); 442 popupButton.setToolTipText(tip); 443 } else if (JXDatePicker.MONTH_VIEW.equals(property)) { 444 popup = null; 445 } else if (JXDatePicker.LINK_PANEL.equals(property)) { 446 // If the popup is null we haven't shown it yet. 447 JPanel linkPanel = datePicker.getLinkPanel(); 448 if (popup != null) { 449 popup.remove(linkPanel); 450 popup.add(linkPanel, BorderLayout.SOUTH); 451 } 452 } else if (JXDatePicker.EDITOR.equals(property)) { 453 JFormattedTextField oldEditor = (JFormattedTextField)e.getOldValue(); 454 if (oldEditor != null) { 455 datePicker.remove(oldEditor); 456 } 457 458 JFormattedTextField editor = (JFormattedTextField)e.getNewValue(); 459 datePicker.add(editor); 460 datePicker.revalidate(); 461 } else if ("componentOrientation".equals(property)) { 462 datePicker.revalidate(); 463 } 464 } 465 466 public void addLayoutComponent(String name, Component comp) { } 467 468 public void removeLayoutComponent(Component comp) { } 469 470 public Dimension preferredLayoutSize(Container parent) { 471 return parent.getPreferredSize(); 472 } 473 474 public Dimension minimumLayoutSize(Container parent) { 475 return parent.getMinimumSize(); 476 } 477 478 public void layoutContainer(Container parent) { 479 Insets insets = datePicker.getInsets(); 480 int width = datePicker.getWidth() - insets.left - insets.right; 481 int height = datePicker.getHeight() - insets.top - insets.bottom; 482 483 int popupButtonWidth = popupButton != null ? popupButton.getPreferredSize().width : 0; 484 485 boolean ltr = datePicker.getComponentOrientation().isLeftToRight(); 486 487 datePicker.getEditor().setBounds(ltr ? insets.left : insets.left + popupButtonWidth, 488 insets.top, 489 width - popupButtonWidth, 490 height); 491 492 if (popupButton != null) { 493 popupButton.setBounds(ltr ? width - popupButtonWidth + insets.left : insets.left, 494 insets.top, 495 popupButtonWidth, 496 height); 497 } 498 } 499 } 500 }