001 /* 002 * $Id: JXDatePicker.java,v 1.23 2006/05/14 02:36:13 dmouse Exp $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx; 022 023 import org.jdesktop.swingx.calendar.DateSpan; 024 import org.jdesktop.swingx.calendar.JXMonthView; 025 import org.jdesktop.swingx.painter.gradient.BasicGradientPainter; 026 import org.jdesktop.swingx.plaf.DatePickerUI; 027 import org.jdesktop.swingx.plaf.JXDatePickerAddon; 028 import org.jdesktop.swingx.plaf.LookAndFeelAddons; 029 030 import javax.swing.*; 031 import javax.swing.JFormattedTextField.AbstractFormatter; 032 import javax.swing.JFormattedTextField.AbstractFormatterFactory; 033 import javax.swing.text.DefaultFormatterFactory; 034 import java.awt.*; 035 import java.awt.event.ActionEvent; 036 import java.awt.event.ActionListener; 037 import java.text.DateFormat; 038 import java.text.MessageFormat; 039 import java.text.ParseException; 040 import java.text.SimpleDateFormat; 041 import java.util.Date; 042 import java.util.TimeZone; 043 044 /** 045 * A component that combines a button, an editable field and a JXMonthView 046 * component. The user can select a date from the calendar component, which 047 * appears when the button is pressed. The selection from the calendar 048 * component will be displayed in editable field. Values may also be modified 049 * manually by entering a date into the editable field using one of the 050 * supported date formats. 051 * 052 * @author Joshua Outwater 053 */ 054 public class JXDatePicker extends JComponent { 055 056 static { 057 LookAndFeelAddons.contribute(new JXDatePickerAddon()); 058 } 059 060 /** 061 * UI Class ID 062 */ 063 public static final String uiClassID = "DatePickerUI"; 064 065 public static final String EDITOR = "editor"; 066 public static final String MONTH_VIEW = "monthView"; 067 public static final String DATE_IN_MILLIS ="dateInMillis"; 068 public static final String LINK_PANEL = "linkPanel"; 069 070 /** The editable date field that displays the date */ 071 private JFormattedTextField _dateField; 072 073 /** 074 * Popup that displays the month view with controls for 075 * traversing/selecting dates. 076 */ 077 private JPanel _linkPanel; 078 private long _linkDate; 079 private MessageFormat _linkFormat; 080 private JXMonthView _monthView; 081 private String _actionCommand = "selectionChanged"; 082 private boolean editable = true; 083 084 /** 085 * Create a new date picker using the current date as the initial 086 * selection and the default abstract formatter 087 * <code>JXDatePickerFormatter</code>. 088 * 089 * The date picker is configured with the default time zone and locale 090 * 091 * @see #setTimeZone 092 * @see #getTimeZone 093 */ 094 public JXDatePicker() { 095 this(System.currentTimeMillis()); 096 } 097 098 /** 099 * Create a new date picker using the specified time as the initial 100 * selection and the default abstract formatter 101 * <code>JXDatePickerFormatter</code>. 102 * 103 * The date picker is configured with the default time zone and locale 104 * 105 * @param millis initial time in milliseconds 106 * @see #setTimeZone 107 * @see #getTimeZone 108 */ 109 public JXDatePicker(long millis) { 110 _monthView = new JXMonthView(); 111 _monthView.setTraversable(true); 112 113 _linkFormat = new MessageFormat(UIManager.getString("JXDatePicker.linkFormat")); 114 115 _linkDate = System.currentTimeMillis(); 116 _linkPanel = new TodayPanel(); 117 118 updateUI(); 119 120 _dateField.setValue(new Date(millis)); 121 } 122 123 /** 124 * @inheritDoc 125 */ 126 public DatePickerUI getUI() { 127 return (DatePickerUI)ui; 128 } 129 130 /** 131 * Sets the L&F object that renders this component. 132 * 133 * @param ui 134 */ 135 public void setUI(DatePickerUI ui) { 136 super.setUI(ui); 137 } 138 139 /** 140 * Resets the UI property with the value from the current look and feel. 141 * 142 * @see UIManager#getUI 143 */ 144 @Override 145 public void updateUI() { 146 setUI((DatePickerUI)UIManager.getUI(this)); 147 invalidate(); 148 } 149 150 /** 151 * @inheritDoc 152 */ 153 @Override 154 public String getUIClassID() { 155 return uiClassID; 156 } 157 158 /** 159 * Replaces the currently installed formatter and factory used by the 160 * editor. These string formats are defined by the 161 * <code>java.text.SimpleDateFormat</code> class. 162 * 163 * @param formats The string formats to use. 164 * @see java.text.SimpleDateFormat 165 */ 166 public void setFormats(String[] formats) { 167 DateFormat[] dateFormats = new DateFormat[formats.length]; 168 for (int counter = formats.length - 1; counter >= 0; counter--) { 169 dateFormats[counter] = new SimpleDateFormat(formats[counter]); 170 } 171 setFormats(dateFormats); 172 } 173 174 /** 175 * Replaces the currently installed formatter and factory used by the 176 * editor. 177 * 178 * @param formats The date formats to use. 179 */ 180 public void setFormats(DateFormat[] formats) { 181 _dateField.setFormatterFactory(new DefaultFormatterFactory( 182 new JXDatePickerFormatter(formats))); 183 } 184 185 /** 186 * Returns an array of the formats used by the installed formatter 187 * if it is a subclass of <code>JXDatePickerFormatter<code>. 188 * <code>javax.swing.JFormattedTextField.AbstractFormatter</code> 189 * and <code>javax.swing.text.DefaultFormatter</code> do not have 190 * support for accessing the formats used. 191 * 192 * @return array of formats or null if unavailable. 193 */ 194 public DateFormat[] getFormats() { 195 // Dig this out from the factory, if possible, otherwise return null. 196 AbstractFormatterFactory factory = _dateField.getFormatterFactory(); 197 if (factory != null) { 198 AbstractFormatter formatter = factory.getFormatter(_dateField); 199 if (formatter instanceof JXDatePickerFormatter) { 200 return ((JXDatePickerFormatter)formatter).getFormats(); 201 } 202 } 203 return null; 204 } 205 206 /** 207 * Set the currently selected date. 208 * 209 * @param date date 210 */ 211 public void setDate(Date date) { 212 _dateField.setValue(date); 213 } 214 215 /** 216 * Set the currently selected date. 217 * 218 * @param millis milliseconds 219 */ 220 public void setDateInMillis(long millis) { 221 _dateField.setValue(new Date(millis)); 222 } 223 224 /** 225 * Returns the currently selected date. 226 * 227 * @return Date 228 */ 229 public Date getDate() { 230 return (Date)_dateField.getValue(); 231 } 232 233 /** 234 * Returns the currently selected date in milliseconds. 235 * 236 * @return the date in milliseconds 237 */ 238 public long getDateInMillis() { 239 return ((Date)_dateField.getValue()).getTime(); 240 } 241 242 /** 243 * Return the <code>JXMonthView</code> used in the popup to 244 * select dates from. 245 * 246 * @return the month view component 247 */ 248 public JXMonthView getMonthView() { 249 return _monthView; 250 } 251 252 /** 253 * Set the component to use the specified JXMonthView. If the new JXMonthView 254 * is configured to a different time zone it will affect the time zone of this 255 * component. 256 * 257 * @param monthView month view comopnent 258 * @see #setTimeZone 259 * @see #getTimeZone 260 */ 261 public void setMonthView(JXMonthView monthView) { 262 JXMonthView oldMonthView = _monthView; 263 _monthView = monthView; 264 firePropertyChange(MONTH_VIEW, oldMonthView, _monthView); 265 } 266 267 /** 268 * Gets the time zone. This is a convenience method which returns the time zone 269 * of the JXMonthView being used. 270 * 271 * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>. 272 */ 273 public TimeZone getTimeZone() { 274 return _monthView.getTimeZone(); 275 } 276 277 /** 278 * Sets the time zone with the given time zone value. This is a convenience 279 * method which returns the time zone of the JXMonthView being used. 280 * 281 * @param tz The <code>TimeZone</code>. 282 */ 283 public void setTimeZone(TimeZone tz) { 284 _monthView.setTimeZone(tz); 285 286 } 287 288 public long getLinkDate() { 289 return _linkDate; 290 } 291 292 /** 293 * Set the date the link will use and the string defining a MessageFormat 294 * to format the link. If no valid date is in the editor when the popup 295 * is displayed the popup will focus on the month the linkDate is in. Calling 296 * this method will replace the currently installed linkPanel and install 297 * a new one with the requested date and format. 298 * 299 * @param linkDate Date in milliseconds 300 * @param linkFormatString String used to format the link 301 * @see java.text.MessageFormat 302 */ 303 public void setLinkDate(long linkDate, String linkFormatString) { 304 _linkDate = linkDate; 305 _linkFormat = new MessageFormat(linkFormatString); 306 setLinkPanel(new TodayPanel()); 307 } 308 309 /** 310 * Return the panel that is used at the bottom of the popup. The default 311 * implementation shows a link that displays the current month. 312 * 313 * @return The currently installed link panel 314 */ 315 public JPanel getLinkPanel() { 316 return _linkPanel; 317 } 318 319 /** 320 * Set the panel that will be used at the bottom of the popup. 321 * 322 * @param linkPanel The new panel to install in the popup 323 */ 324 public void setLinkPanel(JPanel linkPanel) { 325 JPanel oldLinkPanel = _linkPanel; 326 _linkPanel = linkPanel; 327 firePropertyChange(LINK_PANEL, oldLinkPanel, _linkPanel); 328 } 329 330 /** 331 * Returns the formatted text field used to edit the date selection. 332 * 333 * @return the formatted text field 334 */ 335 public JFormattedTextField getEditor() { 336 return _dateField; 337 } 338 339 public void setEditor(JFormattedTextField editor) { 340 JFormattedTextField oldEditor = _dateField; 341 _dateField = editor; 342 firePropertyChange(EDITOR, oldEditor, _dateField); 343 } 344 345 @Override 346 public void setComponentOrientation(ComponentOrientation orientation) { 347 super.setComponentOrientation(orientation); 348 _monthView.setComponentOrientation(orientation); 349 } 350 351 /** 352 * Returns true if the current value being edited is valid. 353 * 354 * @return true if the current value being edited is valid. 355 */ 356 public boolean isEditValid() { 357 return _dateField.isEditValid(); 358 } 359 360 /** 361 * Forces the current value to be taken from the AbstractFormatter and 362 * set as the current value. This has no effect if there is no current 363 * AbstractFormatter installed. 364 */ 365 public void commitEdit() throws ParseException { 366 _dateField.commitEdit(); 367 } 368 369 public void setEditable(boolean value) { 370 boolean oldEditable = isEditable(); 371 editable = value; 372 firePropertyChange("editable", oldEditable, editable); 373 if (editable != oldEditable) { 374 repaint(); 375 } 376 } 377 378 public boolean isEditable() { 379 return editable; 380 } 381 382 /** 383 * Get the baseline for the specified component, or a value less 384 * than 0 if the baseline can not be determined. The baseline is measured 385 * from the top of the component. 386 * 387 * @param width Width of the component to determine baseline for. 388 * @param height Height of the component to determine baseline for. 389 * @return baseline for the specified component 390 */ 391 public int getBaseline(int width, int height) { 392 return ((DatePickerUI)ui).getBaseline(width, height); 393 } 394 /** 395 * Returns the string currently used to identiy fired ActionEvents. 396 * 397 * @return String The string used for identifying ActionEvents. 398 */ 399 public String getActionCommand() { 400 return _actionCommand; 401 } 402 403 /** 404 * Sets the string used to identify fired ActionEvents. 405 * 406 * @param actionCommand The string used for identifying ActionEvents. 407 */ 408 public void setActionCommand(String actionCommand) { 409 _actionCommand = actionCommand; 410 } 411 412 /** 413 * Adds an ActionListener. 414 * <p> 415 * The ActionListener will receive an ActionEvent when a selection has 416 * been made. 417 * 418 * @param l The ActionListener that is to be notified 419 */ 420 public void addActionListener(ActionListener l) { 421 listenerList.add(ActionListener.class, l); 422 } 423 424 /** 425 * Removes an ActionListener. 426 * 427 * @param l The action listener to remove. 428 */ 429 public void removeActionListener(ActionListener l) { 430 listenerList.remove(ActionListener.class, l); 431 } 432 433 /** 434 * Fires an ActionEvent to all listeners. 435 */ 436 protected void fireActionPerformed() { 437 Object[] listeners = listenerList.getListenerList(); 438 ActionEvent e = null; 439 for (int i = listeners.length - 2; i >= 0; i -=2) { 440 if (listeners[i] == ActionListener.class) { 441 if (e == null) { 442 e = new ActionEvent(JXDatePicker.this, 443 ActionEvent.ACTION_PERFORMED, 444 _actionCommand); 445 } 446 ((ActionListener)listeners[i + 1]).actionPerformed(e); 447 } 448 } 449 } 450 451 public void postActionEvent() { 452 fireActionPerformed(); 453 } 454 455 private final class TodayPanel extends JXPanel { 456 TodayPanel() { 457 super(new FlowLayout()); 458 setBackgroundPainter(new BasicGradientPainter(0, 0, new Color(238, 238, 238), 0, 1, Color.WHITE)); 459 JXHyperlink todayLink = new JXHyperlink(new TodayAction()); 460 Color textColor = new Color(16, 66, 104); 461 todayLink.setUnclickedColor(textColor); 462 todayLink.setClickedColor(textColor); 463 add(todayLink); 464 } 465 466 @Override 467 protected void paintComponent(Graphics g) { 468 super.paintComponent(g); 469 470 g.setColor(new Color(187, 187, 187)); 471 g.drawLine(0, 0, getWidth(), 0); 472 g.setColor(new Color(221, 221, 221)); 473 g.drawLine(0, 1, getWidth(), 1); 474 } 475 476 private final class TodayAction extends AbstractAction { 477 TodayAction() { 478 super(_linkFormat.format(new Object[] { new Date(_linkDate) })); 479 } 480 481 public void actionPerformed(ActionEvent ae) { 482 DateSpan span = new DateSpan(_linkDate, _linkDate); 483 _monthView.ensureDateVisible(span.getStart()); 484 } 485 } 486 } 487 }