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 }