View Javadoc

1   /*
2    *  File: DateChooserPanel.java 
3    *  Copyright (c) 2004-2007  Peter Kliem (Peter.Kliem@jaret.de)
4    *  A commercial license is available, see http://www.jaret.de.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Common Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/cpl-v10.html
10   */
11  package de.jaret.util.ui.datechooser;
12  
13  import java.text.DateFormat;
14  import java.text.DateFormatSymbols;
15  import java.util.Calendar;
16  import java.util.Date;
17  import java.util.GregorianCalendar;
18  import java.util.List;
19  import java.util.Locale;
20  import java.util.Vector;
21  
22  import org.eclipse.swt.SWT;
23  import org.eclipse.swt.events.KeyEvent;
24  import org.eclipse.swt.events.KeyListener;
25  import org.eclipse.swt.events.MouseAdapter;
26  import org.eclipse.swt.events.MouseEvent;
27  import org.eclipse.swt.events.MouseListener;
28  import org.eclipse.swt.events.MouseTrackAdapter;
29  import org.eclipse.swt.events.PaintEvent;
30  import org.eclipse.swt.events.PaintListener;
31  import org.eclipse.swt.events.SelectionAdapter;
32  import org.eclipse.swt.events.SelectionEvent;
33  import org.eclipse.swt.graphics.Color;
34  import org.eclipse.swt.graphics.Font;
35  import org.eclipse.swt.graphics.GC;
36  import org.eclipse.swt.graphics.Point;
37  import org.eclipse.swt.layout.GridData;
38  import org.eclipse.swt.layout.GridLayout;
39  import org.eclipse.swt.widgets.Button;
40  import org.eclipse.swt.widgets.Canvas;
41  import org.eclipse.swt.widgets.Composite;
42  import org.eclipse.swt.widgets.Display;
43  import org.eclipse.swt.widgets.Event;
44  import org.eclipse.swt.widgets.Label;
45  import org.eclipse.swt.widgets.Listener;
46  
47  import de.jaret.util.date.holidayenumerator.HolidayEnumerator;
48  import de.jaret.util.swt.SwtGraphicsHelper;
49  
50  /***
51   * The DateChooserPanel is intended to be used as a dropdown for the DateFieldCombo (@see
52   * de.jaret.swt.util.datechooser.DateFieldCombo). However if it seems useful it is possible to be used as a standalone
53   * control for selecting a date.
54   * 
55   * It features
56   * <ul>
57   * <li>selectable locale which is used for the determination of the first day of the week and the weekday/months
58   * abbreviations</li>
59   * <li>display of week of the year selectable</li>
60   * <li>keyboard control (cursor keys navigate in the day panel, SHIFT-Cursor-left/right navigate month, ESC cancels, t
61   * sets the date to the current date)</li>
62   * <li>actions by the users can be watched by a
63   * 
64   * @see de.jaret.swt.util.datechooser.IDateChooserListener (intermdiate change, cancel, selection)</li>
65   * <li>optional a
66   * @see de.jaret.util.date.HolidayEnumerator can be set for highlighting holidays in the day panel</li>
67   * </ul>
68   * TODO selectable font
69   * @author Peter Kliem
70   * @version $Id: DateChooserPanel.java 576 2007-10-03 12:57:38Z olk $
71   */
72  public class DateChooserPanel extends Composite implements MouseListener {
73      /*** currently selected date. */
74      protected Date _date;
75  
76      /*** date format symbols instance. */
77      protected static DateFormatSymbols _dateFormatSymbols;
78  
79      /*** calendar instance. */
80      protected static Calendar _calendar;
81  
82      /*** default color for marking selected date in the panel. */
83      protected static final Color DEFAULTMARKERCOLOR = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE);
84  
85      /*** default color for drawing holidays. */
86      protected static final Color DEFAULTHOLIDAYCOLOR = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
87  
88      /*** default color for drawing special days. */
89      protected static final Color DEFAULTSPECIALDAYCOLOR = Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW);
90  
91      /*** actual color for the background marking. */
92      protected Color _markerColor = DEFAULTMARKERCOLOR;
93      /*** actual color for the foreground of holidays. */
94      protected Color _holidayColor = DEFAULTHOLIDAYCOLOR;
95      /*** actual color for the foreground of special days. */
96      protected Color _specialDayColor = DEFAULTSPECIALDAYCOLOR;
97  
98      /***
99       * true: a columnn showing the number of the week in the year should be displayed.
100      */
101     protected boolean _displayWeeks = false;
102 
103     /*** true: a single click will select. */
104     protected boolean _oneClickSelection;
105 
106     /*** holiday enumerator. */
107     protected HolidayEnumerator _holidayEnumerator;
108 
109     /*** provider for additional day information. */
110     protected IAdditionalDayInformationProvider _dayInformationProvider;
111 
112     /*** label displaying the month. */
113     protected Label _monthLabel;
114 
115     /*** label displaying teh current date. */
116     protected Label _todayLabel;
117 
118     /*** increment month button. */
119     protected Button _incMonthButton;
120 
121     /*** decrement month button. */
122     protected Button _decMonthButton;
123 
124     /*** the daygrid canvas. */
125     protected DayGrid _dayGrid;
126 
127     /*** listener list. */
128     protected List<IDateChooserListener> _listenerList;
129 
130     /*** locale for the panel. */
131     protected Locale _locale;
132 
133     /*** font for bold weekday headings. */
134     private Font _weekdayFont;
135     /*** font for the smaller weeknumbers. */
136     private Font _weekNumberFont;
137 
138     /***
139      * Constructor for the DateChooserPanel.
140      * 
141      * @param parent Composite parent
142      * @param style style bits
143      * @param oneClickSelection if true, a single mouse click will be considered a valid selection. Otherwise a
144      * selection will need a double click.
145      * @param displayWeeks if true the panel will display a week of the year column
146      * @param locale Locale to be used
147      */
148     public DateChooserPanel(Composite parent, int style, boolean oneClickSelection, boolean displayWeeks, Locale locale) {
149         super(parent, style);
150         _oneClickSelection = oneClickSelection;
151         _displayWeeks = displayWeeks;
152         _locale = locale;
153 
154         // helpers needed to be initialized with the correct locale
155         _dateFormatSymbols = new DateFormatSymbols(_locale);
156         _calendar = new GregorianCalendar(_locale);
157 
158         // create the controls for the chooser panel
159         createControls();
160 
161         // to avoid a null date set the current date
162         setDate(new Date());
163 
164         // key listener for keyboard control
165         addKeyListener(new KeyListener() {
166             public void keyPressed(KeyEvent event) {
167                 handleKeyPressed(event);
168             }
169 
170             public void keyReleased(KeyEvent arg0) {
171             }
172         });
173 
174         // mousewheel
175         Listener listener = new Listener() {
176             public void handleEvent(Event event) {
177                 switch (event.type) {
178                 case SWT.MouseWheel:
179                     int count = -event.count / 3;
180                     if ((event.stateMask & SWT.SHIFT) != 0) {
181                         rollMonth(count);
182                     } else {
183                         rollDay(count);
184                     }
185                     break;
186                 default:
187                     throw new RuntimeException("unsupported event");
188 
189                 }
190             }
191         };
192 
193         addListener(SWT.MouseWheel, listener);
194     }
195 
196     /***
197      * Constructor for the DateChooserPanel. OneClickselection defaults to <code>false</code>, displayWeeks defaults
198      * to <code>true</code>. The Locale used is the default locale.
199      * 
200      * @param parent Composite parent
201      * @param style style bits
202      */
203     public DateChooserPanel(Composite parent, int style) {
204         this(parent, style, false, true, Locale.getDefault());
205     }
206 
207     /***
208      * {@inheritDoc}
209      */
210     public void dispose() {
211         super.dispose();
212         if (_weekdayFont != null) {
213             _weekdayFont.dispose();
214         }
215         if (_weekNumberFont != null) {
216             _weekNumberFont.dispose();
217         }
218     }
219 
220     /***
221      * {@inheritDoc} Propagate the change to the elements.
222      */
223     public void setBackground(Color color) {
224         super.setBackground(color);
225         _monthLabel.setBackground(color);
226         _todayLabel.setBackground(color);
227         _dayGrid.setBackground(color);
228     }
229 
230     /***
231      * Handles the key events of the panel.
232      * 
233      * @param event KeyEvent to be processed
234      */
235     private void handleKeyPressed(KeyEvent event) {
236         if ((event.stateMask & SWT.SHIFT) != 0) {
237             switch (event.keyCode) {
238             case SWT.ARROW_RIGHT:
239                 incMonth();
240                 break;
241             case SWT.ARROW_LEFT:
242                 decMonth();
243                 break;
244 
245             default:
246                 // do nothing
247                 break;
248             }
249         } else {
250             switch (event.keyCode) {
251             case SWT.ESC:
252                 fireChoosingCanceled();
253                 break;
254             case SWT.CR:
255                 fireDateChosen(getDate());
256                 break;
257             case SWT.ARROW_RIGHT:
258                 incDay();
259                 break;
260             case SWT.ARROW_LEFT:
261                 decDay();
262                 break;
263             case SWT.ARROW_DOWN:
264                 incWeek();
265                 break;
266             case SWT.ARROW_UP:
267                 decWeek();
268                 break;
269             case 't':
270             case 'T':
271                 today();
272                 break;
273 
274             default:
275                 // do nothing
276                 break;
277             }
278 
279         }
280     }
281 
282     /***
283      * Increase month.
284      */
285     private void incMonth() {
286         _calendar.add(Calendar.MONTH, 1);
287         intermediateChange();
288     }
289 
290     /***
291      * Decrease month.
292      * 
293      */
294     private void decMonth() {
295         _calendar.add(Calendar.MONTH, -1);
296         intermediateChange();
297     }
298 
299     /***
300      * Add or subtract a number of month.
301      * 
302      * @param count number of month to add/substract
303      */
304     private void rollMonth(int count) {
305         _calendar.add(Calendar.MONTH, count);
306         intermediateChange();
307     }
308 
309     /***
310      * Increase day.
311      * 
312      */
313     private void incDay() {
314         _calendar.add(Calendar.DAY_OF_MONTH, 1);
315         intermediateChange();
316     }
317 
318     /***
319      * Decrease day.
320      * 
321      */
322     private void decDay() {
323         _calendar.add(Calendar.DAY_OF_MONTH, -1);
324         intermediateChange();
325     }
326 
327     /***
328      * Add or substract a number of days.
329      * 
330      * @param count number of days
331      */
332     private void rollDay(int count) {
333         _calendar.add(Calendar.DAY_OF_MONTH, count);
334         intermediateChange();
335     }
336 
337     /***
338      * Iincrease week.
339      * 
340      */
341     private void incWeek() {
342         _calendar.add(Calendar.DAY_OF_MONTH, 7);
343         intermediateChange();
344     }
345 
346     /***
347      * Decrease week.
348      * 
349      */
350     private void decWeek() {
351         _calendar.add(Calendar.DAY_OF_MONTH, -7);
352         intermediateChange();
353     }
354 
355     /***
356      * Set the selected date to today.
357      * 
358      */
359     private void today() {
360         setDate(new Date());
361         fireIntermediateChange(getDate());
362     }
363 
364     /***
365      * intermediate change: update monthlabel etc.
366      */
367     private void intermediateChange() {
368         updateMonthLabel();
369         redraw();
370         fireIntermediateChange(getDate());
371         forceFocus(); // return the focus to the chooser panel (key listening)
372     }
373 
374     /***
375      * Set the currently selected date. A value of <code>null</code> will be transformed to the current date.
376      * 
377      * @param date Date to be displayed
378      */
379     public void setDate(Date date) {
380         // null is not really a displayable date .. use a current date instead
381         if (date == null) {
382             date = new Date();
383         }
384         if (!date.equals(_date)) {
385             _date = date;
386             _calendar.setTime(_date);
387             updateMonthLabel();
388             redraw();
389         }
390     }
391 
392     /***
393      * Get the selected Date.
394      * 
395      * @return the currently selected date.
396      */
397     public Date getDate() {
398         _date = _calendar.getTime();
399         return _date;
400     }
401 
402     /***
403      * {@inheritDoc} Also redraws the grid.
404      */
405     public void redraw() {
406         super.redraw();
407         _dayGrid.redraw();
408     }
409 
410     /***
411      * Create the controls that compose the datechooserpanel.
412      */
413     private void createControls() {
414         GridLayout gridLayout = new GridLayout();
415         gridLayout.numColumns = 3;
416         setLayout(gridLayout);
417 
418         // month dec/inc and label
419         _decMonthButton = new Button(this, SWT.ARROW | SWT.LEFT);
420         GridData gd = new GridData();
421         _decMonthButton.setLayoutData(gd);
422         _decMonthButton.addSelectionListener(new SelectionAdapter() {
423 
424             public void widgetSelected(SelectionEvent arg0) {
425                 decMonth();
426             }
427 
428         });
429 
430         _monthLabel = new Label(this, SWT.CENTER);
431         _monthLabel.setText(getMonthText());
432         gd = new GridData(GridData.FILL_HORIZONTAL);
433         _monthLabel.setLayoutData(gd);
434         _monthLabel.setBackground(getBackground());
435 
436         _incMonthButton = new Button(this, SWT.ARROW | SWT.RIGHT);
437         gd = new GridData(GridData.HORIZONTAL_ALIGN_END);
438         _incMonthButton.setLayoutData(gd);
439         _incMonthButton.addSelectionListener(new SelectionAdapter() {
440 
441             public void widgetSelected(SelectionEvent arg0) {
442                 incMonth();
443             }
444         });
445 
446         // main day grid
447         _dayGrid = new DayGrid(this, SWT.NULL);
448         gd = new GridData(GridData.FILL_BOTH);
449         gd.horizontalSpan = 3;
450         _dayGrid.setLayoutData(gd);
451         _dayGrid.addMouseListener(this);
452         _dayGrid.setBackground(getBackground());
453 
454         // today label
455         _todayLabel = new Label(this, SWT.NULL);
456         gd = new GridData(GridData.FILL_HORIZONTAL);
457         gd.horizontalSpan = 3;
458         _todayLabel.setLayoutData(gd);
459         _todayLabel.setBackground(getBackground());
460 
461         // init he text of the today label with the current locale
462         DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, _locale);
463         _todayLabel.setText(df.format(new Date()));
464 
465         _todayLabel.addMouseListener(new MouseAdapter() {
466             @Override
467             public void mouseDown(MouseEvent e) {
468                 today();
469             }
470         });
471 
472     }
473 
474     /***
475      * Upate the month label.
476      * 
477      */
478     private void updateMonthLabel() {
479         _monthLabel.setText(getMonthText());
480     }
481 
482     /***
483      * Assemble text for the month label (month + year).
484      * 
485      * @return text for the label
486      */
487     private String getMonthText() {
488         int month = _calendar.get(Calendar.MONTH);
489         int year = _calendar.get(Calendar.YEAR);
490         String monthShort = _dateFormatSymbols.getShortMonths()[month];
491         return monthShort + " " + year;
492     }
493 
494     /***
495      * The DayGrid is a private member class extending Canvas. It draws the grid of days and the headings. Whenever the
496      * date in the surrounding DateChooserPanel is changed the method <code>updateInternals</code> has to be called to
497      * update all calculated fields.
498      * 
499      * @author Peter Kliem
500      * @version $Id: DateChooserPanel.java 576 2007-10-03 12:57:38Z olk $
501      */
502     class DayGrid extends Canvas {
503 
504         /***
505          * Construct a day grid.
506          * 
507          * @param parent parent composite
508          * @param style style bits
509          */
510         public DayGrid(Composite parent, int style) {
511             super(parent, style);
512             addPaintListener(new PaintListener() {
513                 public void paintControl(PaintEvent event) {
514                     onPaint(event);
515                 }
516 
517             });
518             addListener(SWT.Traverse, new Listener() {
519                 public void handleEvent(Event event) {
520                     handleTraverse(event);
521                 }
522             });
523             addMouseTrackListener(new MouseTrackAdapter() {
524                 public void mouseHover(MouseEvent event) {
525                     // If the mouse hovers, determine if the date marks a
526                     // special day
527                     // and set the appropriate tooltip. Otherwise clear the
528                     // tooltip.
529                     Date date = dateForLocation(event.x, event.y);
530                     if (date == null) {
531                         setToolTipText(null);
532                     } else {
533                         setToolTipText(getTooltipText(date));
534                     }
535 
536                 }
537 
538             });
539         }
540 
541         /***
542          * Create the tooltip text for a date.
543          * 
544          * @param date date
545          * @return the tooltip text or <code>null</code>
546          */
547         protected String getTooltipText(Date date) {
548             if (date == null) {
549                 return null;
550             }
551             if (_dayInformationProvider != null) {
552                 String text = _dayInformationProvider.getToolTipText(date);
553                 if (text != null) {
554                     return text;
555                 }
556             }
557             if (_holidayEnumerator != null) {
558                 String text = _holidayEnumerator.getDayName(date);
559                 if (text != null) {
560                     return text;
561                 }
562             }
563             return null;
564         }
565 
566         /***
567          * {@inheritDoc}
568          */
569         public Point computeSize(int arg0, int arg1, boolean arg2) {
570             // TODO calculate size
571             return new Point(200, 150);
572         }
573 
574         void handleTraverse(Event event) {
575             /*
576              * if (isSingleLine() && event.detail == SWT.TRAVERSE_TAB_NEXT) { event.doit = true; }
577              */}
578 
579         int width;
580 
581         int height;
582 
583         int columnWidth;
584 
585         int rowHeight;
586 
587         int posOfFirstInMonth;
588 
589         int daysInMonth;
590 
591         int day;
592 
593         int month;
594 
595         int weekColumnWidth;
596 
597         /***
598          * Do any calculations necessary to support drawing.
599          */
600         public void updateInternals() {
601             // width and heigth of the panel
602             width = getClientArea().width;
603             height = getClientArea().height;
604 
605             /***
606              * Calculation of the outline of the month and the day to display. Some of these are only necessary if the
607              * month has changed. This optimization is an open todo. TODO move some calculations
608              */
609             daysInMonth = _calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
610             day = _calendar.get(Calendar.DAY_OF_MONTH);
611             month = _calendar.get(Calendar.MONTH);
612             // int weekday = _calendar.get(Calendar.DAY_OF_WEEK);
613             // int dayPos = getWeekdayPos(weekday);
614 
615             Calendar tmpCalendar = (Calendar) _calendar.clone();
616             tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
617             posOfFirstInMonth = getWeekdayPos(tmpCalendar.get(Calendar.DAY_OF_WEEK));
618 
619             // calculate the column width and row height
620             weekColumnWidth = _displayWeeks ? width / 8 : 0;
621             columnWidth = (width - weekColumnWidth) / 7;
622             rowHeight = height / 7;
623 
624         }
625 
626         /***
627          * Retrieve the font to use for the weekday labels.
628          * 
629          * @param gc GC
630          * @return the font
631          */
632         protected Font getWeekdayFont(GC gc) {
633             if (_weekdayFont == null) {
634                 _weekdayFont = new Font(Display.getCurrent(), gc.getFont().getFontData()[0].getName(), gc.getFont()
635                         .getFontData()[0].getHeight(), SWT.BOLD);
636             }
637             return _weekdayFont;
638         }
639 
640         /***
641          * Retrieve the font to use for the week number labels.
642          * 
643          * @param gc GC
644          * @return the font
645          */
646         protected Font getWeekNumberFont(GC gc) {
647             if (_weekNumberFont == null) {
648                 _weekNumberFont = new Font(Display.getCurrent(), gc.getFont().getFontData()[0].getName(), (int) (gc
649                         .getFont().getFontData()[0].getHeight() * 0.9), SWT.NORMAL);
650             }
651             return _weekNumberFont;
652         }
653 
654         /***
655          * The paint method. TODO clipping rect optimizations
656          * 
657          * @param event the pain tevent
658          */
659         private void onPaint(PaintEvent event) {
660             // do preparing calculations
661             updateInternals();
662 
663             // weekdays are coded from 1 to 7 beginning with sunday
664             GC gc = event.gc;
665             Font oldFont = gc.getFont();
666             gc.setFont(getWeekdayFont(gc));
667 
668             for (int weekday = 1; weekday < 8; weekday++) {
669                 int pos = getWeekdayPos(weekday);
670                 boolean isWeekend = false;
671                 if (weekday == Calendar.SATURDAY || weekday == Calendar.SUNDAY) {
672                     isWeekend = true;
673                 }
674                 Color color = isWeekend ? Display.getCurrent().getSystemColor(SWT.COLOR_RED) : Display.getCurrent()
675                         .getSystemColor(SWT.COLOR_BLACK);
676                 drawCell(gc, pos, 0, getWeekdayString(weekday), color, false);
677             }
678             gc.setFont(oldFont);
679 
680             int[] weeks = new int[6];
681             Calendar tmpCalendar = new GregorianCalendar();
682             tmpCalendar.setTime(_date);
683             tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
684             tmpCalendar.add(Calendar.DAY_OF_MONTH, -posOfFirstInMonth);
685             for (int dy = 0; dy < 6; dy++) {
686                 weeks[dy] = tmpCalendar.get(Calendar.WEEK_OF_YEAR);
687                 for (int dx = 0; dx < 7; dx++) {
688                     int paintDay = tmpCalendar.get(Calendar.DAY_OF_MONTH);
689                     int paintMonth = tmpCalendar.get(Calendar.MONTH);
690                     // determine the foreground color of the cell
691                     Color color = Display.getCurrent().getSystemColor(SWT.COLOR_BLACK);
692                     if (paintMonth != month) {
693                         color = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);
694                     } else if (_holidayEnumerator != null) {
695                         if (_holidayEnumerator.isHoliday(tmpCalendar.getTime())) {
696                             color = _holidayColor;
697                         } else if (_holidayEnumerator.isSpecialDay(tmpCalendar.getTime())) {
698                             color = _specialDayColor;
699                         }
700                     }
701                     Font normalFont = gc.getFont();
702                     // handle bold painting if told so by an additional information provider
703                     if (_dayInformationProvider != null && _dayInformationProvider.renderBold(tmpCalendar.getTime())) {
704                         // bold font
705                         gc.setFont(getWeekdayFont(gc));
706                     } else {
707                         gc.setFont(normalFont);
708                     }
709                     drawCell(gc, dx, dy + 1, Integer.toString(paintDay), color, paintDay == day && paintMonth == month);
710                     tmpCalendar.add(Calendar.DAY_OF_MONTH, 1);
711                     gc.setFont(normalFont);
712                 }
713             }
714             oldFont = gc.getFont();
715             gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY));
716             gc.setFont(getWeekNumberFont(gc));
717             for (int dy = 0; dy < 6; dy++) {
718                 int y = (dy + 1) * rowHeight;
719                 int rx = columnWidth - (int) (columnWidth * 0.2);
720                 SwtGraphicsHelper.drawStringRightAlignedVCenter(gc, Integer.toString(weeks[dy]), rx, y + rowHeight / 2);
721             }
722             gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
723 
724             gc.setFont(oldFont);
725 
726         }
727 
728         /***
729          * Draw a cell on the panel.
730          * 
731          * @param gc GC
732          * @param pos x
733          * @param row y
734          * @param string String to paint
735          * @param foreground foreground color
736          * @param mark is marked
737          */
738         private void drawCell(GC gc, int pos, int row, String string, Color foreground, boolean mark) {
739             int x = pos * columnWidth + weekColumnWidth;
740             int y = row * rowHeight;
741             Color oldFG = gc.getForeground();
742             Color oldBG = gc.getBackground();
743             gc.setForeground(foreground);
744             if (mark) {
745                 gc.setBackground(_markerColor);
746                 gc.fillRectangle(x, y, columnWidth - 1, rowHeight - 1);
747             }
748             SwtGraphicsHelper.drawStringCenteredVCenter(gc, string, x, x + columnWidth, y + rowHeight / 2);
749             gc.setForeground(oldFG);
750             gc.setBackground(oldBG);
751         }
752 
753         /***
754          * Calculate the day at position x,y.
755          * 
756          * @param x x coordinate
757          * @param y ycoordinate
758          * @return the day (first = 1) or -1 if no valid day could be determined
759          */
760         protected int dayForLocation(int x, int y) {
761             int row = y / rowHeight - 1;
762             int col = (x - weekColumnWidth) / columnWidth;
763 
764             int day = row * 7 + col + 1;
765             day -= posOfFirstInMonth;
766             if (day < 1 || day > _calendar.getActualMaximum(Calendar.DAY_OF_MONTH)) {
767                 day = -1;
768             }
769             return day;
770         }
771 
772         /***
773          * Calculate the date for a given location.
774          * 
775          * @param x x coordinate
776          * @param y y coordinate
777          * @return the date for the location
778          */
779         protected Date dateForLocation(int x, int y) {
780             int row = y / rowHeight - 1;
781             int col = (x - weekColumnWidth) / columnWidth;
782 
783             int day = row * 7 + col + 1;
784             day -= posOfFirstInMonth;
785             Calendar tmpCalendar = new GregorianCalendar();
786             tmpCalendar.setTime(getDate());
787             tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
788             tmpCalendar.add(Calendar.DAY_OF_MONTH, day - 1);
789             return tmpCalendar.getTime();
790         }
791 
792         /***
793          * Retrieve short weekday representation.
794          * 
795          * @param weekday coded weekday
796          * @return short string representaion
797          */
798         private String getWeekdayString(int weekday) {
799             return _dateFormatSymbols.getShortWeekdays()[weekday];
800         }
801 
802         /***
803          * @param weekday coded weekday (1-7)
804          * @return column position in the day grid (0-6)
805          */
806         private int getWeekdayPos(int weekday) {
807             int firstDay = _calendar.getFirstDayOfWeek();
808             int pos = weekday - firstDay;
809             if (pos < 0) {
810                 pos += 7;
811             }
812             return pos;
813         }
814 
815     }
816 
817     /***
818      * {@inheritDoc}
819      */
820     public void mouseDoubleClick(MouseEvent event) {
821         boolean success = setDateByLocation(event.x, event.y);
822         if (success) {
823             fireDateChosen(getDate());
824         }
825     }
826 
827     /***
828      * {@inheritDoc}
829      */
830     public void mouseDown(MouseEvent event) {
831         boolean success = setDateByLocation(event.x, event.y);
832         if (_oneClickSelection && success) {
833             fireDateChosen(getDate());
834         } else {
835             fireIntermediateChange(getDate());
836         }
837     }
838 
839     /***
840      * {@inheritDoc}
841      */
842     public void mouseUp(MouseEvent event) {
843         // nothing to do
844     }
845 
846     /***
847      * Sets the calendar day for a given location. This will succeed if a valid day (i.e. a day in the current month is
848      * selected).
849      * 
850      * @param x x coordinate
851      * @param y y coordinate
852      * @return true if a valid day could be selected
853      */
854     protected boolean setDateByLocation(int x, int y) {
855         int day = _dayGrid.dayForLocation(x, y);
856         if (day > 0) {
857             _calendar.set(Calendar.DAY_OF_MONTH, day);
858             _dayGrid.redraw();
859             return true;
860         } else {
861             return false;
862         }
863     }
864 
865     /***
866      * Add a DateChooserListener to be informed about changes.
867      * 
868      * @param listener the DateChooserListener to be added
869      */
870     public synchronized void addDateChooserListener(IDateChooserListener listener) {
871         if (_listenerList == null) {
872             _listenerList = new Vector<IDateChooserListener>();
873         }
874         _listenerList.add(listener);
875     }
876 
877     /***
878      * Remove a DateChooserListener.
879      * 
880      * @param listener the DateChooserListener to be removed
881      */
882     public synchronized void remDateChooserListener(IDateChooserListener listener) {
883         if (_listenerList == null) {
884             return;
885         }
886         _listenerList.remove(listener);
887     }
888 
889     /***
890      * Inform listeners about a chosing action.
891      * 
892      * @param date date chosen
893      */
894     protected void fireDateChosen(Date date) {
895         if (_listenerList != null) {
896             for (IDateChooserListener listener : _listenerList) {
897                 listener.dateChosen(date);
898             }
899         }
900     }
901 
902     /***
903      * Inform listeners about an intermediate date change.
904      * 
905      * @param date date currently selected
906      */
907     protected void fireIntermediateChange(Date date) {
908         if (_listenerList != null) {
909             for (IDateChooserListener listener : _listenerList) {
910                 listener.dateIntermediateChange(date);
911             }
912         }
913     }
914 
915     /***
916      * Inform listeners about cancellation f the choosing.
917      */
918     protected void fireChoosingCanceled() {
919         if (_listenerList != null) {
920             for (IDateChooserListener listener : _listenerList) {
921                 listener.choosingCanceled();
922             }
923         }
924     }
925 
926     /***
927      * Retrieve the HolidayEnumerator.
928      * 
929      * @return Returns the HolidayEnumerator.
930      */
931     public HolidayEnumerator getHolidayEnumerator() {
932         return _holidayEnumerator;
933     }
934 
935     /***
936      * Set the HolidayEnumerator. A value of <code>null</code> is valid meaning no HolidayEnumerator should be used.
937      * 
938      * @param holidayEnumerator The HolidayEnumerator to set.
939      */
940     public void setHolidayEnumerator(HolidayEnumerator holidayEnumerator) {
941         _holidayEnumerator = holidayEnumerator;
942         redraw();
943     }
944 
945     /***
946      * Retrieve the additional information provider.
947      * 
948      * @return the information provider
949      */
950     public IAdditionalDayInformationProvider getAdditionalDayInformationProvider() {
951         return _dayInformationProvider;
952     }
953 
954     /***
955      * Set an additional provider for day information.
956      * 
957      * @param informationProvider the information provider
958      */
959     public void setAdditionalDayInformationProvider(IAdditionalDayInformationProvider informationProvider) {
960         _dayInformationProvider = informationProvider;
961         redraw();
962     }
963 
964     /***
965      * @return true if weeks should be displayed
966      */
967     public boolean isDisplayWeeks() {
968         return _displayWeeks;
969     }
970 
971     /***
972      * Set whether a column showing the week of the year should be displayed.
973      * 
974      * @param displayWeeks if set to true a column with the number of the week in the year is displayed.
975      */
976     public void setDisplayWeeks(boolean displayWeeks) {
977         _displayWeeks = displayWeeks;
978         redraw();
979     }
980 
981     /***
982      * @return true if a single click will select the date
983      */
984     public boolean isOneClickSelection() {
985         return _oneClickSelection;
986     }
987 
988     /***
989      * Set whether a single or a double click will select the date.
990      * 
991      * @param oneClickSelection if set to true one click will select the date. If set to false the date selection
992      * requires a double click.
993      */
994     public void setOneClickSelection(boolean oneClickSelection) {
995         _oneClickSelection = oneClickSelection;
996     }
997 
998     /***
999      * Retrieve the color used for painting the background of the marked day.
1000      * 
1001      * @return the marker color
1002      */
1003     public Color getMarkerColor() {
1004         return _markerColor;
1005     }
1006 
1007     /***
1008      * Set the color to paint the background of the marked day.
1009      * 
1010      * @param markerColor color for the marker
1011      */
1012     public void setMarkerColor(Color markerColor) {
1013         _markerColor = markerColor;
1014     }
1015 
1016     /***
1017      * Retrieve the color used for painting the foreground of a holiday.
1018      * 
1019      * @return the holiday color
1020      */
1021     public Color getHolidayColor() {
1022         return _holidayColor;
1023     }
1024 
1025     /***
1026      * Set the color to paint the foreground of holidays.
1027      * 
1028      * @param holidayColor holiday color
1029      */
1030     public void setHolidayColor(Color holidayColor) {
1031         _holidayColor = holidayColor;
1032     }
1033 
1034     /***
1035      * Retrieve the color used for painting the foreground of a special day.
1036      * 
1037      * @return the special day color
1038      */
1039     public Color getSpecialDayColor() {
1040         return _specialDayColor;
1041     }
1042 
1043     /***
1044      * Set the color to paint the foreground of special days.
1045      * 
1046      * @param specialDayColor color for special days
1047      */
1048     public void setSpecialDayColor(Color specialDayColor) {
1049         _specialDayColor = specialDayColor;
1050     }
1051 }