View Javadoc

1   /*
2    *  File: TimeChooserPanel.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.util.Date;
14  import java.util.List;
15  import java.util.Vector;
16  
17  import org.eclipse.swt.SWT;
18  import org.eclipse.swt.events.KeyEvent;
19  import org.eclipse.swt.events.KeyListener;
20  import org.eclipse.swt.events.MouseEvent;
21  import org.eclipse.swt.events.MouseListener;
22  import org.eclipse.swt.events.PaintEvent;
23  import org.eclipse.swt.events.PaintListener;
24  import org.eclipse.swt.graphics.Color;
25  import org.eclipse.swt.graphics.GC;
26  import org.eclipse.swt.graphics.Point;
27  import org.eclipse.swt.graphics.Rectangle;
28  import org.eclipse.swt.layout.GridData;
29  import org.eclipse.swt.layout.GridLayout;
30  import org.eclipse.swt.widgets.Canvas;
31  import org.eclipse.swt.widgets.Composite;
32  import org.eclipse.swt.widgets.Display;
33  import org.eclipse.swt.widgets.Event;
34  import org.eclipse.swt.widgets.Listener;
35  
36  import de.jaret.util.date.JaretDate;
37  import de.jaret.util.swt.SwtGraphicsHelper;
38  
39  /***
40   * The TimeChooserPanel is intended to be used as a dropdown for the TimeFieldCombo (@see
41   * de.jaret.swt.util.datechooser.TimeChooser). However if it seems useful it is possible to be used as a standalone
42   * control for selecting a time.
43   * <p>
44   * Time is represented as the time part of a java.util.Date.
45   * </p>
46   * 
47   * @author Peter Kliem
48   * @version $Id: TimeChooserPanel.java 576 2007-10-03 12:57:38Z olk $
49   */
50  public class TimeChooserPanel extends Composite implements MouseListener {
51      /*** currently selected time in this date. */
52      protected Date _date;
53  
54      /*** color for marking selected time in the panel. */
55      protected static final Color MARKERCOLOR = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE);
56  
57      /*** the time grid canvas. */
58      protected TimeGrid _timeGrid;
59  
60      /*** listener list. */
61      protected List<IDateChooserListener> _listenerList;
62  
63      /*** currennt col width in the chooser. */
64      protected int _columnWidth;
65      /*** current row heigth in the chooser. */
66      protected int _rowHeight;
67  
68      /***
69       * Constructor for the TimeChooserPanel.
70       * 
71       * @param parent Composite parent
72       * @param style style bits selection will need a double click.
73       */
74      public TimeChooserPanel(Composite parent, int style) {
75          super(parent, style);
76  
77          // create the controls for the chooser panel
78          createControls();
79  
80          // to avoid a null date set the current date
81          setDate(new Date());
82  
83          // key listener for keyboard control
84          addKeyListener(new KeyListener() {
85              public void keyPressed(KeyEvent event) {
86                  handleKeyPressed(event);
87              }
88  
89              public void keyReleased(KeyEvent arg0) {
90              }
91          });
92  
93          // mousewheel
94          Listener listener = new Listener() {
95              public void handleEvent(Event event) {
96                  switch (event.type) {
97                  case SWT.MouseWheel:
98                      int count = -event.count / 3;
99                      int colIdx = event.x / _columnWidth;
100                     switch (colIdx) {
101                     case 0:
102                         rollAMPM(count);
103                         break;
104                     case 1:
105                         rollHours(count);
106                         break;
107                     case 2:
108                         rollMinutes(count);
109                         break;
110 
111                     default:
112                         break;
113                     }
114                     break;
115                 default:
116                     throw new RuntimeException("unsupported event");
117                 }
118             }
119 
120         };
121 
122         addListener(SWT.MouseWheel, listener);
123 
124     }
125 
126     /***
127      * {@inheritDoc}
128      */
129     public void dispose() {
130         super.dispose();
131     }
132 
133     /***
134      * {@inheritDoc} Propagate the change to the elements.
135      */
136     public void setBackground(Color color) {
137         super.setBackground(color);
138         _timeGrid.setBackground(color);
139     }
140 
141     /***
142      * Handles the key events of the panel.
143      * 
144      * @param event KeyEvent to be processed
145      */
146     private void handleKeyPressed(KeyEvent event) {
147         if ((event.stateMask & SWT.SHIFT) != 0) {
148             switch (event.keyCode) {
149             case SWT.ARROW_DOWN:
150                 rollMinutes(1);
151                 break;
152             case SWT.ARROW_UP:
153                 rollMinutes(-1);
154                 break;
155             default:
156                 // do nothing
157                 break;
158             }
159         } else {
160             switch (event.keyCode) {
161             case SWT.ESC:
162                 fireChoosingCanceled();
163                 break;
164             case SWT.CR:
165                 fireDateChosen(getDate());
166                 break;
167             case SWT.ARROW_DOWN:
168                 rollHours(1);
169                 break;
170             case SWT.ARROW_UP:
171                 rollHours(-1);
172                 break;
173             default:
174                 // do nothing
175                 break;
176             }
177 
178         }
179     }
180 
181     /***
182      * intermediate change: update monthlabel etc.
183      */
184     private void intermediateChange() {
185         redraw();
186         fireIntermediateChange(getDate());
187         forceFocus(); // return the focus to the chooser panel (key listening)
188     }
189 
190     /***
191      * Set the currently selected date. A value of <code>null</code> will be transformed to the current date.
192      * 
193      * @param date Date to be displayed
194      */
195     public void setDate(Date date) {
196         // null is not really a displayable date .. use a current date instead
197         if (date == null) {
198             date = new Date();
199         }
200         if (!date.equals(_date)) {
201             _date = date;
202             redraw();
203         }
204     }
205 
206     /***
207      * Get the selected Date.
208      * 
209      * @return the currently selected date.
210      */
211     public Date getDate() {
212         return _date;
213     }
214 
215     /***
216      * {@inheritDoc} Also redraws the grid.
217      */
218     public void redraw() {
219         super.redraw();
220         _timeGrid.redraw();
221     }
222 
223     /***
224      * Create the controls that compose the timechooserpanel.
225      */
226     private void createControls() {
227         GridLayout gridLayout = new GridLayout();
228         gridLayout.numColumns = 1;
229         setLayout(gridLayout);
230 
231         // main time grid
232         _timeGrid = new TimeGrid(this, SWT.NULL);
233         GridData gd = new GridData(GridData.FILL_BOTH);
234         _timeGrid.setLayoutData(gd);
235         _timeGrid.addMouseListener(this);
236         _timeGrid.setBackground(getBackground());
237     }
238 
239     /***
240      * The TimeGrid is a private member class extending Canvas.
241      * 
242      * @author Peter Kliem
243      * @version $Id: TimeChooserPanel.java 576 2007-10-03 12:57:38Z olk $
244      */
245     class TimeGrid extends Canvas {
246 
247         /***
248          * Construct a time grid.
249          * 
250          * @param parent parent composite
251          * @param style style bits
252          */
253         public TimeGrid(Composite parent, int style) {
254             super(parent, style);
255             addPaintListener(new PaintListener() {
256                 public void paintControl(PaintEvent event) {
257                     onPaint(event);
258                 }
259 
260             });
261         }
262 
263         protected Point _sizeCache;
264 
265         /***
266          * {@inheritDoc}
267          */
268         public Point computeSize(int arg0, int arg1, boolean arg2) {
269             if (_sizeCache == null) {
270                 GC gc = new GC(this);
271                 Point extent = gc.stringExtent(">=12");
272                 gc.dispose();
273                 _sizeCache = new Point(extent.x * 3, extent.y * 12);
274             }
275 
276             return _sizeCache;
277         }
278 
279         /***
280          * Do any calculations necessary to support drawing.
281          */
282         public void updateInternals() {
283             _columnWidth = getClientArea().width / 3;
284             _rowHeight = getClientArea().height / 12;
285         }
286 
287         /***
288          * The paint method.
289          * 
290          * @param event the pain tevent
291          */
292         private void onPaint(PaintEvent event) {
293             // do preparing calculations
294             updateInternals();
295 
296             GC gc = event.gc;
297 
298             drawMarks(gc);
299 
300             for (int i = 0; i < 12; i++) {
301                 int y = i * _rowHeight;
302                 drawAMPM(gc, y, i);
303                 drawHour(gc, y, i);
304                 drawMinute(gc, y, i);
305             }
306             // int x = 0;
307             // gc.drawLine(x, 0, x, getClientArea().height);
308             // x += _columnWidth;
309             // gc.drawLine(x, 0, x, getClientArea().height);
310             // x += _columnWidth;
311             // gc.drawLine(x, 0, x, getClientArea().height);
312             // x += _columnWidth;
313             // gc.drawLine(x, 0, x, getClientArea().height);
314             // x += _columnWidth;
315         }
316 
317         private void drawMarks(GC gc) {
318             Color bg = gc.getBackground();
319             gc.setBackground(MARKERCOLOR);
320             JaretDate date = new JaretDate(_date);
321             // 0/12
322             int y = date.getHours() < 12 ? 0 : _rowHeight;
323             Rectangle mark = new Rectangle(0, y, _columnWidth, _rowHeight);
324             gc.fillRectangle(mark);
325             // hour
326             int hour = date.getHours();
327             hour -= (hour >= 12 ? 12 : 0);
328             y = hour * _rowHeight;
329             mark = new Rectangle(_columnWidth, y, _columnWidth, _rowHeight);
330             gc.fillRectangle(mark);
331             // minutes
332             y = date.getMinutes() * getClientArea().height / 60;
333             mark = new Rectangle(2 * _columnWidth, y, 2 * _columnWidth, _rowHeight);
334             gc.fillRectangle(mark);
335 
336             gc.setBackground(bg);
337         }
338 
339         private void drawAMPM(GC gc, int y, int i) {
340             if (i < 2) {
341                 int x = 0;
342                 String hour = i == 0 ? "<12" : ">=12";
343                 SwtGraphicsHelper.drawStringRightAlignedVTop(gc, hour, x + _columnWidth, y);
344             }
345         }
346 
347         private void drawMinute(GC gc, int y, int i) {
348             int x = 2 * _columnWidth;
349             String minute = i * 5 + "";
350             SwtGraphicsHelper.drawStringRightAlignedVTop(gc, minute, x + _columnWidth, y);
351         }
352 
353         private void drawHour(GC gc, int y, int i) {
354             JaretDate date = new JaretDate(_date);
355             int x = _columnWidth;
356             String hour = date.getHours() < 12 ? (i + "") : ((i + 12) + "");
357             SwtGraphicsHelper.drawStringRightAlignedVTop(gc, hour, x + _columnWidth, y);
358         }
359 
360     }
361 
362     /***
363      * {@inheritDoc}
364      */
365     public void mouseDoubleClick(MouseEvent event) {
366         boolean success = setTimeByLocation(event.x, event.y);
367         if (success) {
368             fireDateChosen(getDate());
369         }
370     }
371 
372     /***
373      * Set the time of the date by a clicked location.
374      * 
375      * @param x x
376      * @param y y
377      * @return true if the click was successful in selecting a new time
378      */
379     private boolean setTimeByLocation(int x, int y) {
380         int colIdx = x / _columnWidth;
381         int rowIdx = y / _rowHeight;
382         boolean success = false;
383         JaretDate d = new JaretDate(_date);
384 
385         switch (colIdx) {
386         case 0:
387             if (rowIdx == 0) {
388                 success = true;
389                 if (d.getHours() >= 12) {
390                     d.setHours(d.getHours() - 12);
391                 }
392             } else if (rowIdx == 1) {
393                 success = true;
394                 if (d.getHours() < 12) {
395                     d.setHours(d.getHours() + 12);
396                 }
397             }
398             break;
399         case 1:
400             int h = d.getHours() < 12 ? rowIdx : rowIdx + 12;
401             d.setHours(h);
402             success = true;
403             break;
404         case 2:
405             int min = rowIdx * 5;
406             d.setMinutes(min);
407             success = true;
408             break;
409         default:
410             success = false;
411             break;
412         }
413 
414         if (success) {
415             redraw();
416             _date = d.getDate();
417         }
418 
419         return success;
420     }
421 
422     /***
423      * Roll the minutes by count * 5 Minutes.
424      * 
425      * @param count units to roll
426      */
427     private void rollMinutes(int count) {
428         JaretDate d = new JaretDate(_date);
429         if (count < 0 && d.getMinutes() > 0) {
430             int m = d.getMinutes() + count * 5;
431             m = m < 0 ? 0 : m;
432             d.setMinutes(m);
433             _date = d.getDate();
434             fireIntermediateChange(_date);
435             redraw();
436         } else if (count > 0 && d.getMinutes() < 59) {
437             int m = d.getMinutes() + count * 5;
438             m = m > 55 ? 55 : m;
439             d.setMinutes(m);
440             _date = d.getDate();
441             fireIntermediateChange(_date);
442             redraw();
443         }
444 
445     }
446 
447     /***
448      * Roll the hour field by count units. Will flip over the am/pm section if necessary.
449      * 
450      * @param count units to roll
451      */
452     private void rollHours(int count) {
453         JaretDate d = new JaretDate(_date);
454         if (count < 0 && d.getHours() > 0) {
455             int h = d.getHours() + count;
456             h = h < 0 ? 0 : h;
457             d.setHours(h);
458             _date = d.getDate();
459             fireIntermediateChange(_date);
460             redraw();
461         } else if (count > 0 && d.getHours() < 23) {
462             int h = d.getHours() + count;
463             h = h > 23 ? 23 : h;
464             d.setHours(h);
465             _date = d.getDate();
466             fireIntermediateChange(_date);
467             redraw();
468         }
469     }
470 
471     /***
472      * Roll the am/pm section by count units.
473      * 
474      * @param count units to roll
475      */
476     private void rollAMPM(int count) {
477         JaretDate d = new JaretDate(_date);
478         if (count < 0 && d.getHours() >= 12) {
479             d.setHours(d.getHours() - 12);
480             _date = d.getDate();
481             fireIntermediateChange(_date);
482             redraw();
483         } else if (count > 0 && d.getHours() < 12) {
484             d.setHours(d.getHours() + 12);
485             _date = d.getDate();
486             fireIntermediateChange(_date);
487             redraw();
488         }
489     }
490 
491     /***
492      * {@inheritDoc}
493      */
494     public void mouseDown(MouseEvent event) {
495         boolean success = setTimeByLocation(event.x, event.y);
496         if (success) {
497             fireIntermediateChange(getDate());
498         }
499     }
500 
501     /***
502      * {@inheritDoc}
503      */
504     public void mouseUp(MouseEvent event) {
505         // nothing to do
506     }
507 
508     /***
509      * Add a DateChooserListener to be informed about changes.
510      * 
511      * @param listener the DateChooserListener to be added
512      */
513     public synchronized void addDateChooserListener(IDateChooserListener listener) {
514         if (_listenerList == null) {
515             _listenerList = new Vector<IDateChooserListener>();
516         }
517         _listenerList.add(listener);
518     }
519 
520     /***
521      * Remove a DateChooserListener.
522      * 
523      * @param listener the DateChooserListener to be removed
524      */
525     public synchronized void remDateChooserListener(IDateChooserListener listener) {
526         if (_listenerList == null) {
527             return;
528         }
529         _listenerList.remove(listener);
530     }
531 
532     /***
533      * Inform listeners about a chosing action.
534      * 
535      * @param date date chosen
536      */
537     protected void fireDateChosen(Date date) {
538         if (_listenerList != null) {
539             for (IDateChooserListener listener : _listenerList) {
540                 listener.dateChosen(date);
541             }
542         }
543     }
544 
545     /***
546      * Inform listeners about an intermediate date change.
547      * 
548      * @param date date currently selected
549      */
550     protected void fireIntermediateChange(Date date) {
551         if (_listenerList != null) {
552             for (IDateChooserListener listener : _listenerList) {
553                 listener.dateIntermediateChange(date);
554             }
555         }
556     }
557 
558     /***
559      * Inform listeners about cancellation f the choosing.
560      */
561     protected void fireChoosingCanceled() {
562         if (_listenerList != null) {
563             for (IDateChooserListener listener : _listenerList) {
564                 listener.choosingCanceled();
565             }
566         }
567     }
568 
569 }