View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Font;
24  import java.awt.GridBagConstraints;
25  import java.awt.GridBagLayout;
26  import java.awt.Insets;
27  import java.awt.event.ItemEvent;
28  import java.awt.event.ItemListener;
29  import java.text.DateFormatSymbols;
30  import java.text.SimpleDateFormat;
31  import java.util.Calendar;
32  import java.util.Date;
33  import java.util.GregorianCalendar;
34  
35  import javax.swing.DefaultComboBoxModel;
36  import javax.swing.JComboBox;
37  import javax.swing.JComponent;
38  import javax.swing.JDialog;
39  import javax.swing.JLabel;
40  import javax.swing.border.CompoundBorder;
41  import javax.swing.border.EmptyBorder;
42  
43  import com.cosylab.gui.components.util.CosyUIElements;
44  import com.cosylab.gui.components.util.FontHelper;
45  import com.cosylab.gui.property.editors.PropertyEditor;
46  
47  
48  /**
49   * This class provides user-friendly entry of dates. The component consists of
50   * three combo boxes containing year, month and day values. All values are
51   * validated and always display a valid date. This is then provided by getDate
52   * and setDate methods. User can listen to PropertyChangeEvent to get notifications 
53   * when a date was changed When setting or getting the date from or to
54   * java.util.Date class, only YEAR, MONTH and DAY_OF_MONTH properties are set.
55   * 
56   *
57   * @author <a href="mailto:ales.pucelj@cosylab.com">Ales Pucelj</a>
58   * @version $id$
59   *
60   * @see java.util.Calendar GregorianCalendar class is used to calculate the
61   *      dates. Since only the number of years is unlimited, it is set to 20 by
62   *      default, allowing years 2000 through 2019 inclusively to be chosen.
63   *      This can, however, be modified.
64   */
65  public class SimpleDateSelector extends JComponent implements PropertyEditor
66  {
67  	private static final long serialVersionUID = 1L;
68  	private int numberOfYears = 20;
69  	private JLabel dayLabel;
70  	private JLabel monthLabel;
71  	private JLabel yearLabel;
72  	private final Calendar calendar = new GregorianCalendar();
73  	int startingYear = 2000;
74  	JComboBox daySelector;
75  	JComboBox monthSelector;
76  	JComboBox yearSelector;
77  
78  	/**
79  	 * Listens for changes of combo boxes.
80  	 */
81  	private final class DateChangeListener implements ItemListener
82  	{
83  		/**
84  		 * Updates selected values on changes in combo boxes.
85  		 *
86  		 * @param e ItemEvent
87  		 *
88  		 * @see java.awt.event.ItemListener#itemStateChanged(ItemEvent)
89  		 */
90  		public final void itemStateChanged(ItemEvent e)
91  		{
92  			if (e.getStateChange() != ItemEvent.SELECTED) {
93  				return;
94  			}
95  
96  			final SimpleDateSelector owner = SimpleDateSelector.this;
97              Date old=owner.getDate();
98  			if (e.getSource() == owner.daySelector) {
99  				owner.setDay(owner.daySelector.getSelectedIndex() + 1);
100                 firePropertyChange("date", old, owner.getDate());
101 				return;
102 			}
103 
104 			if (e.getSource() == owner.monthSelector) {
105 				owner.setMonth(owner.monthSelector.getSelectedIndex());
106                 firePropertyChange("date", old, owner.getDate());
107 				return;
108 			}
109 
110 			if (e.getSource() == owner.yearSelector) {
111 				owner.setYear(owner.yearSelector.getSelectedIndex()
112 				    + owner.startingYear);
113                 firePropertyChange("date", old, owner.getDate());
114 			}
115 		}
116 	}
117 
118 	/**
119 	 * Default constructor for SimpleDateSelector.
120 	 */
121 	public SimpleDateSelector()
122 	{
123 		this(new Date());
124 	}
125 
126 	/**
127 	 * Constructor for SimpleDateSelector.
128 	 *
129 	 * @param date Date Initial date to set.
130 	 */
131 	public SimpleDateSelector(Date date)
132 	{
133 		this(date, new GregorianCalendar().get(Calendar.YEAR) - 10, 25);
134 	}
135 
136 	/**
137 	 * Constructor for SimpleDateSelector.
138 	 *
139 	 * @param date Date Initial date to set.
140 	 * @param fromYear int Initial starting year.
141 	 * @param yearRange int Number of years to allow.
142 	 */
143 	public SimpleDateSelector(Date date, int fromYear, int yearRange)
144 	{
145 		super();
146 
147 		createComponents();
148 
149 		setStartingYear(fromYear);
150 		setYearRange(fromYear, yearRange);
151 
152 		setDate(date);
153 	}
154 
155 	/**
156 	 * Constructor for SimpleDateSelector.
157 	 *
158 	 * @param fromYear int Initial starting year.
159 	 * @param yearRange int Number of years to allow.
160 	 */
161 	public SimpleDateSelector(int fromYear, int yearRange)
162 	{
163 		this(new Date(), fromYear, yearRange);
164 	}
165 
166 	/**
167 	 * Constructor for SimpleDateSelector.
168 	 *
169 	 * @param day int
170 	 * @param month int
171 	 * @param year int
172 	 */
173 	public SimpleDateSelector(int day, int month, int year)
174 	{
175 		this(new GregorianCalendar(year, month, day).getTime());
176 	}
177 
178 	/**
179 	 * Constructor for SimpleDateSelector.
180 	 *
181 	 * @param day int
182 	 * @param month int
183 	 * @param year int
184 	 * @param fromYear int
185 	 * @param yearRange int
186 	 */
187 	public SimpleDateSelector(int day, int month, int year, int fromYear,
188 	    int yearRange)
189 	{
190 		this(new GregorianCalendar(year, month, day).getTime(), fromYear,
191 		    yearRange);
192 	}
193 
194 	/**
195 	 * Internal helper routine to adjust the component font.
196 	 *
197 	 * @param c JComponent
198 	 */
199 	private final void setComponentFont(JComponent c)
200 	{
201 		c.setFont(FontHelper.getFontWithStyle(Font.PLAIN, c.getFont()));
202 	}
203 
204 	/**
205 	 * Creates and lays out the components that represent this panel.
206 	 */
207 	private final void createComponents()
208 	{
209 		setLayout(new GridBagLayout());
210 
211 		setBorder(new CompoundBorder(CosyUIElements.getPlainBorder(true),
212 		        new EmptyBorder(3, 3, 3, 3)));
213 
214 		dayLabel = new JLabel("Day");
215 		setComponentFont(dayLabel);
216 		add(dayLabel, createConstraints(0, 0, 0.1,0, 1, 1, 4));
217 
218 		monthLabel = new JLabel("Month");
219 		setComponentFont(monthLabel);
220 		add(monthLabel, createConstraints(1, 0, 1.0,0, 1, 1, 4));
221 
222 		yearLabel = new JLabel("Year");
223 		setComponentFont(yearLabel);
224 		add(yearLabel, createConstraints(2, 0, 0.1,0, 1, 1, 4));
225 
226 		daySelector = new JComboBox();
227 		
228 		setComponentFont(daySelector);
229 		add(daySelector, createConstraints(0, 1, 0.1,1, 1, 1, 4));
230 
231 		monthSelector = new JComboBox();
232 		setComponentFont(monthSelector);
233 		populateMonths();
234 		add(monthSelector, createConstraints(1, 1, 1.0,1, 1, 1, 4));
235 
236 		yearSelector = new JComboBox();
237 		setComponentFont(yearSelector);
238 		add(yearSelector, createConstraints(2, 1, 0.1,1, 1, 1, 4));
239 
240 		DateChangeListener dcl = new DateChangeListener();
241 
242 		daySelector.addItemListener(dcl);
243 		monthSelector.addItemListener(dcl);
244 		yearSelector.addItemListener(dcl);
245 	}
246 
247 	/**
248 	 * Internal helper routine returns GridBagConstraints object.
249 	 *
250 	 * @param x int
251 	 * @param y int
252 	 * @param ratio double
253 	 * @param top int
254 	 * @param bottom int
255 	 * @param right int
256 	 *
257 	 * @return java.awt.GridBagConstraints
258 	 */
259 	private final GridBagConstraints createConstraints(int x, int y,
260 	    double ratio, double weightY, int top, int bottom, int right)
261 	{
262 		GridBagConstraints constraints = new GridBagConstraints();
263 		constraints.gridx = x;
264 		constraints.gridy = y;
265 		constraints.weightx = ratio;
266 		constraints.weighty = weightY;
267 		constraints.fill = GridBagConstraints.BOTH;
268 		constraints.insets = new Insets(top, 0, bottom, right);
269 		constraints.anchor = GridBagConstraints.WEST;
270 
271 		return constraints;
272 	}
273 
274 	/**
275 	 * Returns the currently selected date as java.util.Date.  Creation date:
276 	 *
277 	 * @return java.util.Date
278 	 */
279 	public Date getDate()
280 	{
281 		return calendar.getTime();
282 	}
283 
284 	/**
285 	 * Returns number of the selected day in current month, starting with 1.
286 	 *
287 	 * @return int
288 	 */
289 	public int getDay()
290 	{
291 		return calendar.get(Calendar.DAY_OF_MONTH);
292 	}
293 
294 	/**
295 	 * Returns the index of the currently selected month starting with 1.
296 	 *
297 	 * @return int
298 	 */
299 	public int getMonth()
300 	{
301 		return calendar.get(Calendar.MONTH);
302 	}
303 
304 	/**
305 	 * Returns the currently selected year.
306 	 *
307 	 * @return int
308 	 */
309 	public int getYear()
310 	{
311 		return calendar.get(Calendar.YEAR);
312 	}
313 
314 	/**
315 	 * Returns the number of year to display in the year selector.
316 	 *
317 	 * @return int
318 	 */
319 	public int getNumberOfYears()
320 	{
321 		return numberOfYears;
322 	}
323 
324 	/**
325 	 * Returns the starting year to display in the year selector. No year lower
326 	 * than this can be selected by the user or set using setDate method.
327 	 *
328 	 * @return int
329 	 */
330 	public int getStartingYear()
331 	{
332 		return startingYear;
333 	}
334 
335 	/**
336 	 * Sets the date to display.
337 	 *
338 	 * @param date java.util.Date
339 	 */
340 	public void setDate(Date date)
341 	{
342 		Date old = calendar.getTime();
343 
344 		calendar.setTime(date);
345 		setYear(getYear());
346 
347 		updateComponents();
348 
349 		firePropertyChange("date", old, date);
350 	}
351 
352 	/**
353 	 * Sets the day to the specified index. To ensure compatibility with
354 	 * <code>java.util.Calendar</code> class, first day of month is specified
355 	 * as 1. If the day set is outside allowed range, the value will be set to
356 	 * the nearest allowed value.
357 	 *
358 	 * @param day int
359 	 */
360 	public void setDay(int day)
361 	{
362 		if (day == getDay()) {
363 			return;
364 		}
365 
366 		day = Math.max(day, calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
367 		day = Math.min(day, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
368 
369 		int old = getDay();
370 
371 		calendar.set(Calendar.DAY_OF_MONTH, day);
372 
373 		//		daySelector.setSelectedIndex(getDay() - 1);
374 		//		daySelector.repaint();
375 		firePropertyChange("day", old, day);
376 	}
377 
378 	/**
379 	 * Shows / hides the Year/Month/Day labels
380 	 *
381 	 * @param visible the visibility value
382 	 */
383 	public void setLabelsVisible(boolean visible)
384 	{
385 		yearLabel.setVisible(visible);
386 		monthLabel.setVisible(visible);
387 		dayLabel.setVisible(visible);
388 	}
389 
390 	/**
391 	 * Sets the currently selected month. January has the index of 0.
392 	 *
393 	 * @param month int
394 	 */
395 	public void setMonth(int month)
396 	{
397 		if (month == getMonth()) {
398 			return;
399 		}
400 
401 		month = Math.max(month, calendar.getActualMinimum(Calendar.MONTH));
402 		month = Math.min(month, calendar.getActualMaximum(Calendar.MONTH));
403 
404 		int old = getMonth();
405 
406 		calendar.set(Calendar.MONTH, month);
407 
408 		updateComponents();
409 
410 		firePropertyChange("month", old, month);
411 	}
412 
413 	/**
414 	 * Sets the currently selected year. Years are specified absolutely,
415 	 * although only years between startingYear and numberOfYears will be
416 	 * displayed. If the year is not within this range, the closest value will
417 	 * be set.
418 	 *
419 	 * @param year int
420 	 */
421 	public void setYear(int year)
422 	{
423 		if (year == getYear()) {
424 			return;
425 		}
426 
427 		year = Math.max(year, startingYear);
428 		year = Math.min(year, startingYear + numberOfYears);
429 
430 		int old = getYear();
431 
432 		calendar.set(Calendar.YEAR, year);
433 
434 		updateComponents();
435 
436 		firePropertyChange("year", old, year);
437 	}
438 
439 	/**
440 	 * Sets the enabled state of this component. If the component is disabled,
441 	 * all the combo boxes are also disabled.
442 	 *
443 	 * @param how boolean
444 	 */
445 	public void setEnabled(boolean how)
446 	{
447 		super.setEnabled(how);
448 		daySelector.setEnabled(how);
449 		monthSelector.setEnabled(how);
450 		yearSelector.setEnabled(how);
451 	}
452 
453 	/**
454 	 * Sets the starting year to be displayed in the year selector. Only the
455 	 * years between startingYear and startingYear+numberOfYears inclusively
456 	 * can be selected by the user or by calling the setDate method.
457 	 *
458 	 * @param from int
459 	 */
460 	public void setStartingYear(int from)
461 	{
462 		setYearRange(from, startingYear);
463 	}
464 
465 	/**
466 	 * Sets the number of years to display in the year selector. User can only
467 	 * select the years shown in the year selector.
468 	 *
469 	 * @param range int
470 	 */
471 	public void setNumberOfYears(int range)
472 	{
473 		setYearRange(startingYear, range);
474 	}
475 
476 	/**
477 	 * Sets new available year range.
478 	 *
479 	 * @param from int
480 	 * @param range int
481 	 */
482 	public void setYearRange(int from, int range)
483 	{
484 		int old = getYear();
485 
486 		startingYear = (from < 1) ? 1 : from;
487 		numberOfYears = Math.max(1, range);
488 
489 		populateYearNames();
490 
491 		setYear(old);
492 
493 		updateComponents();
494 	}
495 
496 	/**
497 	 * Internal helper routine updates the correct number of days for the
498 	 * selected month.
499 	 */
500 	private final void populateDays()
501 	{
502 		int min = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
503 		int max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
504 
505 		int nDays = max - min + 1;
506 
507 		int old = getDay();
508 		String[] dayNumbers = new String[nDays];
509 
510 		for (int i = 0; i < nDays; i++) {
511 			dayNumbers[i] = String.valueOf(i + min);
512 		}
513 
514 		daySelector.setModel(new DefaultComboBoxModel(dayNumbers));
515 		setDay(old);
516 	}
517 
518 	/**
519 	 * Internal helper method populates the monthSelector with month labels.
520 	 */
521 	private final void populateMonths()
522 	{
523 		final SimpleDateFormat sdf = new SimpleDateFormat("MMMM");
524 		final DateFormatSymbols dfs = sdf.getDateFormatSymbols();
525 
526 		monthSelector.setModel(new DefaultComboBoxModel(dfs.getMonths()));
527 
528 		/* **** BUGFIX BEGIN
529 		 * noticed in: jdk 1.4.0
530 		 * DateFormatSymbols.getDateFormatSymbols().getMonths() returns 13
531 		 * labels with last label being empty string. We remove all elements
532 		 * above index 12.
533 		 */
534 		while (monthSelector.getModel().getSize() > 12) {
535 			monthSelector.removeItemAt(12);
536 		}
537 
538 		/* **** BUGFIX END */
539 	}
540 
541 	/**
542 	 * Internal helper method populates the yearSelector with year labels for
543 	 * the specified range.
544 	 */
545 	private final void populateYearNames()
546 	{
547 		String[] yearNames = new String[numberOfYears];
548 
549 		for (int i = 0; i < yearNames.length; i++) {
550 			yearNames[i] = String.valueOf(startingYear + i);
551 		}
552 
553 		yearSelector.setModel(new DefaultComboBoxModel(yearNames));
554 	}
555 
556 	/**
557 	 * Updates the display after setting the value properties.
558 	 */
559 	private final void updateComponents()
560 	{
561 		populateDays();
562 
563 		daySelector.setSelectedIndex(getDay() - 1);
564 		monthSelector.setSelectedIndex(getMonth());
565 
566 		if (getYear() < startingYear) {
567 			setYear(startingYear);
568 		}
569 
570 		yearSelector.setSelectedIndex(getYear() - startingYear);
571 	}
572 
573 	/* (non-Javadoc)
574 	 * @see com.cosylab.gui.property.PropertyEditor#getPropertyValue()
575 	 */
576 	public Object getPropertyValue()
577 	{
578 		return getDate();
579 	}
580 
581 	/* (non-Javadoc)
582 	 * @see com.cosylab.gui.property.PropertyEditor#setPropertyValue(java.lang.Object)
583 	 */
584 	public boolean setPropertyValue(Object value)
585 	{
586 		if (value instanceof Date) {
587 			setDate((Date)value);
588 
589 			return true;
590 		}
591 
592 		return false;
593 	}
594 
595 	/* (non-Javadoc)
596 	 * @see com.cosylab.gui.property.PropertyEditor#getDescription()
597 	 */
598 	public String getDescription()
599 	{
600 		return null;
601 	}
602 
603 	/* (non-Javadoc)
604 	 * @see com.cosylab.gui.property.PropertyEditor#setDescription(java.lang.String)
605 	 */
606 	public void setDescription(String description)
607 	{
608 	}
609 	
610 	public void setCompactView(boolean compact) {
611 		dayLabel.setVisible(!compact);
612 		monthLabel.setVisible(!compact);
613 		yearLabel.setVisible(!compact);
614 	}
615 	
616 	/**
617 	 * Run test applet.
618 	 *
619 	 * @param args command line parameters
620 	 */
621 	public static void main(String[] args)
622 	{
623 		JDialog dialog = new JDialog();
624 		dialog.setModal(true);
625 		dialog.setSize(300, 100);
626 		dialog.getContentPane().setLayout(new BorderLayout());
627 
628 		SimpleDateSelector dc = new SimpleDateSelector(new Date(), 1995, 100);
629 		
630 		dialog.getContentPane().add(dc, BorderLayout.CENTER);
631 		
632 		//		dc.setDate((new Date(System.currentTimeMillis())));
633 		//		dc.setStartingYear(1998);
634 		//		dc.setNumberOfYears(500);
635 		dialog.setVisible(true);
636 
637 		System.exit(0);
638 	}
639 }
640 
641 /* __oOo__ */