1
2
3
4
5
6
7
8
9
10
11 package de.jaret.util.ui.datechooser;
12
13 import java.text.NumberFormat;
14 import java.util.ArrayList;
15 import java.util.Calendar;
16 import java.util.Date;
17 import java.util.GregorianCalendar;
18 import java.util.List;
19 import java.util.Vector;
20
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.DisposeEvent;
23 import org.eclipse.swt.events.DisposeListener;
24 import org.eclipse.swt.events.FocusEvent;
25 import org.eclipse.swt.events.FocusListener;
26 import org.eclipse.swt.events.KeyEvent;
27 import org.eclipse.swt.events.KeyListener;
28 import org.eclipse.swt.events.SelectionAdapter;
29 import org.eclipse.swt.events.SelectionEvent;
30 import org.eclipse.swt.events.ShellAdapter;
31 import org.eclipse.swt.events.ShellEvent;
32 import org.eclipse.swt.events.VerifyEvent;
33 import org.eclipse.swt.events.VerifyListener;
34 import org.eclipse.swt.graphics.Color;
35 import org.eclipse.swt.graphics.Point;
36 import org.eclipse.swt.layout.FillLayout;
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.Composite;
41 import org.eclipse.swt.widgets.Display;
42 import org.eclipse.swt.widgets.Event;
43 import org.eclipse.swt.widgets.Listener;
44 import org.eclipse.swt.widgets.Shell;
45 import org.eclipse.swt.widgets.Text;
46
47 import de.jaret.util.date.JaretDate;
48
49 /***
50 * A time field with an attached timechooser in a combobox style.
51 *
52 * @author Peter Kliem
53 * @version $Id: TimeChooser.java 587 2007-10-14 12:32:10Z olk $
54 */
55 public class TimeChooser extends Composite implements FocusListener, IDateChooserListener {
56 /*** Invalid input behaviour: keep the textual input and mark the field. */
57 public static final int KEEP_AND_MARK = 0;
58
59 /*** Invalid input behaviour: reset the date to the last valid input. */
60 public static final int RESET_TO_LASTVALID = 1;
61
62 /*** Color used to mark invalid input. */
63 public static final Color MARKER_COLOR = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
64
65 /*** behaviour on invalid input. */
66 protected int _invalidInputBehaviour = KEEP_AND_MARK;
67
68 /*** if true: editable. */
69 protected boolean _editable = true;
70
71 /*** if true: enabled. */
72 private boolean _enabled = true;
73
74 /*** if true: select all in textfield on focus gain. */
75 private boolean _selectAllOnFocusGained = true;
76
77 /***
78 * If true mousewheel will roll in the textfield.
79 */
80 private boolean _textfieldMouseWheelEnable = true;
81
82 /*** listener list of interestedlisteners. */
83 protected List<IDateChooserListener> _listenerList = new Vector<IDateChooserListener>();
84
85 /*** the date value manipulated by the control. */
86 protected Date _date = new Date();
87
88 /*** text field widgets used. */
89 protected Text _textField;
90
91 /*** dropdown button. */
92 protected Button _dropdownButton;
93
94 /*** shell for the drop down. */
95 protected Shell _dropDownShell;
96
97 /*** dropped state. */
98 protected boolean _dropped = false;
99
100 /*** flag regeistering that drop down is about to happen. */
101 boolean _goingToDropDown = false;
102
103 /*** TimeChooserPanel in the dropdown. */
104 protected TimeChooserPanel _chooserPanel;
105
106 /*** FieldIdentifier used for field rolling. */
107 protected IFieldIdentifier _fieldIdentifier;
108
109 /*** initial bg color of the textfield. */
110 private Color _textfieldBGColor;
111
112 /*** flag to help keeping focus listeners happy. */
113 private boolean _hasFocus = false;
114
115 /*** number format for time formating. */
116 protected NumberFormat _twoDigitNF;
117
118 /*** date chooser to synchronize the date with. */
119 protected DateChooser _dateChooser;
120
121 /*** true if the current niput is valid. */
122 protected boolean _hasValidInput = true;
123
124 /***
125 * Constructor for the time chooser.
126 *
127 * @param parent Composite parent
128 * @param style style
129 */
130 public TimeChooser(Composite parent, int style) {
131 super(parent, style);
132 createControls();
133
134 _twoDigitNF = NumberFormat.getNumberInstance();
135 _twoDigitNF.setMinimumIntegerDigits(2);
136 _twoDigitNF.setMaximumIntegerDigits(2);
137
138 updateTextField(_date);
139
140 addDisposeListener(new DisposeListener() {
141 public void widgetDisposed(DisposeEvent e) {
142 onDispose();
143 }
144 });
145 }
146
147 /***
148 * create the controls (a text field and the drop down button).
149 */
150 private void createControls() {
151 GridLayout gridLayout = new GridLayout();
152 gridLayout.numColumns = 2;
153 gridLayout.horizontalSpacing = 0;
154 gridLayout.verticalSpacing = 0;
155 gridLayout.marginHeight = 0;
156 gridLayout.marginWidth = 0;
157 setLayout(gridLayout);
158
159 _textField = new Text(this, SWT.BORDER | SWT.RIGHT);
160 GridData gd = new GridData(GridData.FILL_HORIZONTAL);
161 _textField.setLayoutData(gd);
162
163 _textField.addFocusListener(this);
164
165 _textfieldBGColor = _textField.getBackground();
166
167 _textField.addVerifyListener(new VerifyListener() {
168
169 public void verifyText(VerifyEvent e) {
170 if (Character.isDigit(e.character) || e.character < 32 || e.character == 127 || e.character == ':') {
171 return;
172 }
173 e.doit = false;
174 }
175
176 });
177
178
179 _textField.addKeyListener(new KeyListener() {
180
181 public void keyPressed(KeyEvent keyEvent) {
182 if ((keyEvent.stateMask & SWT.CTRL) != 0 && keyEvent.keyCode == 32) {
183 setDropped(!isDropped());
184 keyEvent.doit = false;
185 } else if (keyEvent.keyCode == SWT.CR) {
186
187 _textField.traverse(SWT.TRAVERSE_TAB_NEXT);
188 } else if (keyEvent.keyCode == SWT.ARROW_UP) {
189 rollField(1);
190 keyEvent.doit = false;
191 } else if (keyEvent.keyCode == SWT.ARROW_DOWN) {
192 rollField(-1);
193 keyEvent.doit = false;
194 } else if (keyEvent.keyCode == ':') {
195 System.out.println("::::");
196 }
197 }
198
199 public void keyReleased(KeyEvent arg0) {
200 }
201
202 });
203
204
205 Listener listener = new Listener() {
206
207 public void handleEvent(Event event) {
208 switch (event.type) {
209 case SWT.MouseWheel:
210 int count = -event.count / 3;
211 if (_textfieldMouseWheelEnable) {
212 rollField(count);
213 }
214 break;
215 default:
216 throw new RuntimeException("unsupported event");
217
218 }
219 }
220 };
221
222 addListener(SWT.MouseWheel, listener);
223
224 _dropdownButton = new Button(this, SWT.ARROW | SWT.DOWN);
225 gd = new GridData();
226 _dropdownButton.setLayoutData(gd);
227
228 _dropdownButton.addSelectionListener(new SelectionAdapter() {
229
230 public void widgetSelected(SelectionEvent arg0) {
231 setDropped(!isDropped());
232 }
233 });
234
235 }
236
237 /***
238 * dispose has to take care of some additional disposals.
239 */
240 public void onDispose() {
241 if (_dropDownShell != null) {
242 _dropDownShell.dispose();
243 }
244 }
245
246 /***
247 * Roll the field (if identifiable) by the given delta.
248 *
249 * @param delta delta to roll the field
250 */
251 private void rollField(int delta) {
252 if (validateInput()) {
253
254 int caretpos = _textField.getCaretPosition();
255 if (caretpos < 3) {
256
257 Calendar cal = new GregorianCalendar();
258 cal.setTime(_date);
259 cal.roll(Calendar.HOUR_OF_DAY, delta);
260 setDate(cal.getTime());
261 } else {
262
263 Calendar cal = new GregorianCalendar();
264 cal.setTime(_date);
265 cal.roll(Calendar.MINUTE, delta);
266 setDate(cal.getTime());
267 }
268 }
269 }
270
271 /***
272 * Check whether the drop down is dropped down.
273 *
274 * @return true if the dropdow is dropped down
275 */
276 public boolean isDropped() {
277 return _dropped;
278 }
279
280 /***
281 * Set the state of the dropdown.
282 *
283 * @param dropped if true the dropdowbn will be displayed.
284 */
285 public void setDropped(boolean dropped) {
286 if (dropped != _dropped) {
287 _dropped = dropped;
288 if (_dropped && _editable && _enabled) {
289 if (_dropDownShell == null) {
290 _dropDownShell = createDropDown();
291 }
292
293 _goingToDropDown = true;
294
295 _chooserPanel.setDate(getDate());
296 Point size = _dropdownButton.getSize();
297 Point dispLocation = toDisplay(_dropdownButton.getLocation());
298 Point dropDownSize = _dropDownShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
299 int dsWidth = dropDownSize.x;
300 int dsHeight = dropDownSize.y;
301 int locx = dispLocation.x + size.x - dsWidth;
302 int locy = dispLocation.y + size.y + 3;
303
304
305 if (locx < 0) {
306 locx = 0;
307 } else if (locx + dsWidth > Display.getCurrent().getBounds().width) {
308 locx = Display.getCurrent().getBounds().width - dsWidth;
309 }
310
311 if (locy + dsHeight > Display.getCurrent().getBounds().height) {
312 locy = dispLocation.y - dsHeight - 3;
313 }
314
315 _dropDownShell.setLocation(locx, locy);
316 _dropDownShell.setSize(dsWidth, dsHeight);
317 _dropDownShell.pack();
318 _dropDownShell.layout(true);
319
320 _dropDownShell.setVisible(true);
321 _dropDownShell.setActive();
322 _chooserPanel.forceFocus();
323 resetMark();
324 } else if (_dropDownShell != null) {
325 _dropDownShell.setVisible(false);
326 _goingToDropDown = false;
327 _textField.setFocus();
328 }
329 }
330
331 }
332
333 /***
334 * Create the dropdown shell and the chooser panel.
335 *
336 * @return the created Shell
337 */
338 private Shell createDropDown() {
339 Shell dropDown = new Shell(getShell(), SWT.NO_TRIM | SWT.BORDER);
340 dropDown.setLayout(new FillLayout());
341 _chooserPanel = new TimeChooserPanel(dropDown, SWT.NULL | SWT.BORDER);
342 _chooserPanel.setDate(getDate());
343 _chooserPanel.addDateChooserListener(this);
344 _chooserPanel.setBackground(getBackground());
345 _chooserPanel.addFocusListener(this);
346
347
348
349
350
351 dropDown.addShellListener(new ShellAdapter() {
352 public void shellDeactivated(ShellEvent event) {
353 setDropped(false);
354 }
355 });
356 return dropDown;
357 }
358
359 /***
360 * Get the date portion of the selected date in an eventually wired DateChooser and set it on the date.
361 *
362 * @param date date to correct
363 * @return corrected date
364 */
365 private Date correctDate(Date date) {
366 if (_dateChooser != null && _dateChooser.getDateInternal() != null) {
367 JaretDate d = new JaretDate(_dateChooser.getDateInternal());
368 JaretDate time = new JaretDate(date);
369 d.setHours(time.getHours());
370 d.setMinutes(time.getMinutes());
371 date = d.getDate();
372 }
373 return date;
374 }
375
376 /***
377 * Retrieve the current selected time (as the time in the date). If a datechooser is set the date part will be taken
378 * from the date chooser.
379 *
380 * @return Returns the time in a date.
381 */
382 public Date getDate() {
383 _date = correctDate(_date);
384 return _date;
385 }
386
387 /***
388 * Retrieve internal date field without adaption by a possible set datechooser.
389 *
390 * @return date
391 */
392 protected Date getDateInternal() {
393 return _date;
394 }
395
396 /***
397 * Set the date.
398 *
399 * @param date The date to set.
400 */
401 public void setDate(Date date) {
402 _date = date;
403 updateTextField(_date);
404 }
405
406 /***
407 * Format the time in the date as a String.
408 *
409 * @param date date containing the time
410 * @return String hh:mm
411 */
412 protected String formatTime(Date date) {
413 JaretDate d = new JaretDate(date);
414 return _twoDigitNF.format(d.getHours()) + ":" + _twoDigitNF.format(d.getMinutes());
415 }
416
417 /***
418 * Update the text in the textfield.
419 *
420 * @param date date to update the field with
421 */
422 private void updateTextField(Date date) {
423 if (date != null) {
424 int caretpos = _textField.getCaretPosition();
425 _textField.setText(formatTime(date));
426 _textField.setSelection(caretpos, caretpos);
427 } else {
428 _textField.setText("");
429 }
430 }
431
432 /***
433 * Set the input in the textfield direct.
434 *
435 * @param text new text of the textfield
436 */
437 public void setText(String text) {
438 _textField.setText(text);
439 }
440
441 /***
442 * Select the text fields contents.
443 *
444 */
445 public void selectAll() {
446 _textField.selectAll();
447 }
448
449 /***
450 * Set the selection on the textfield.
451 *
452 * @param pos position
453 */
454 public void setSelection(int pos) {
455 _textField.setSelection(pos);
456 }
457
458 /***
459 * Clear selection on the textfield.
460 */
461 public void clearSelection() {
462 _textField.clearSelection();
463 }
464
465 /***
466 * Cut operation of the textfield.
467 */
468 public void cut() {
469 _textField.cut();
470 }
471
472 /***
473 * Copy operation of the textfield.
474 */
475 public void copy() {
476 _textField.copy();
477 }
478
479 /***
480 * Paste operation of the textfield.
481 */
482 public void paste() {
483 _textField.paste();
484 }
485
486 /***
487 * {@inheritDoc} The textfield will get the focus.
488 */
489 public boolean setFocus() {
490 super.setFocus();
491 return _textField.setFocus();
492 }
493
494 /***
495 * {@inheritDoc} The textfield will get the focus.
496 */
497 public boolean forceFocus() {
498 return _textField.forceFocus();
499 }
500
501 /***
502 * Access to the embedded textfield widget.
503 *
504 * @return the textfield
505 */
506 public Text getTextField() {
507 return _textField;
508 }
509
510
511 /***
512 * {@inheritDoc} If a date has been chosen in the panel, close dropdaown, selection finished.
513 */
514 public void dateChosen(Date date) {
515 setDate(_chooserPanel.getDate());
516 setDropped(false);
517 fireDateChosen(correctDate(date));
518 }
519
520 /***
521 * {@inheritDoc} Propagate cancelling.
522 */
523 public void choosingCanceled() {
524 updateTextField(_date);
525 setDropped(false);
526 fireChoosingCanceled();
527 }
528
529 /***
530 * {@inheritDoc} Do an update on the textfield.
531 */
532 public void dateIntermediateChange(Date date) {
533 updateTextField(date);
534 fireIntermediateChange(correctDate(date));
535 }
536
537 /***
538 * {@inheritDoc} Do nothing.
539 */
540 public void inputInvalid() {
541
542 }
543
544
545
546
547 /***
548 * {@inheritDoc} On gaining focus on the textfield, select its content. If the datechooser does not already own the
549 * focus, notify other listeners.
550 */
551 public void focusGained(FocusEvent evt) {
552
553 if (evt.widget.equals(_textField) && _selectAllOnFocusGained) {
554 _textField.selectAll();
555 }
556 if (!_hasFocus) {
557 _hasFocus = true;
558 super.notifyListeners(SWT.FocusIn, new Event());
559 }
560 }
561
562 /***
563 * {@inheritDoc} On loosing focus validate the input and check whether the focus will be going to the dropdown. In
564 * latter case do not notify other listeners.
565 */
566 public void focusLost(FocusEvent evt) {
567
568 validateInput();
569
570
571
572 if (Display.getCurrent().getCursorControl() == _dropdownButton) {
573 _goingToDropDown = true;
574 }
575
576 if (_hasFocus && !_goingToDropDown) {
577 _hasFocus = false;
578 super.notifyListeners(SWT.FocusOut, new Event());
579 }
580
581 }
582
583
584
585 /***
586 * Parse an input string in the format hh:mm and set this time to the given date.
587 *
588 * @param text text to parse
589 * @param date date to aply the parsed time to
590 * @return date with time of the parsed string or <code>null</code> to indicate a parse error
591 */
592 protected Date parseTime(String text, Date date) {
593 JaretDate d = new JaretDate(date);
594 try {
595 String hourString = text.substring(0, 2);
596 String minuteString = text.substring(3);
597 int h = Integer.parseInt(hourString);
598 int m = Integer.parseInt(minuteString);
599 d.setHours(h);
600 d.setMinutes(m);
601 } catch (Exception e) {
602 return null;
603 }
604 return d.getDate();
605 }
606
607 /***
608 * Validate the input currently present in the textfield. Resets a mark if set and handles input behaviour for
609 * invalid inputs.
610 *
611 * @return true if valid
612 */
613 public boolean validateInput() {
614 boolean valid = false;
615 String text = _textField.getText();
616 Date date = null;
617 date = parseTime(text, _date);
618 valid = date != null;
619
620 if (date != null) {
621
622 setDate(date);
623 resetMark();
624
625 } else {
626 switch (_invalidInputBehaviour) {
627 case KEEP_AND_MARK:
628 setMark();
629 break;
630 case RESET_TO_LASTVALID:
631 updateTextField(_date);
632 break;
633 default:
634 throw new RuntimeException("Invalid InputBehaviour set");
635 }
636 }
637 if (!valid && _hasValidInput) {
638 _hasValidInput = valid;
639 fireInputInvalid();
640 }
641 _hasValidInput = valid;
642
643 return valid;
644 }
645
646 /***
647 * Reset the background color of the textfield.
648 */
649 private void resetMark() {
650 _textField.setBackground(_textfieldBGColor);
651 }
652
653 /***
654 * Set the background color of the textfield to the marker color.
655 *
656 */
657 private void setMark() {
658 _textField.setBackground(MARKER_COLOR);
659 }
660
661 /***
662 * @return Returns the invalidInputBehaviour.
663 */
664 public int getInvalidInputBehaviour() {
665 return _invalidInputBehaviour;
666 }
667
668 /***
669 * @param invalidInputBehaviour The invalidInputBehaviour to set.
670 */
671 public void setInvalidInputBehaviour(int invalidInputBehaviour) {
672 _invalidInputBehaviour = invalidInputBehaviour;
673 }
674
675 /***
676 * @return Returns the editable state.
677 */
678 public boolean isEditable() {
679 return _editable;
680 }
681
682 /***
683 * Set the editable state. If set to false the textfiled be set to editable(false) and the dropdown will be
684 * disabled.
685 *
686 * @param editable The editable state to be set.
687 */
688 public void setEditable(boolean editable) {
689 _editable = editable;
690 _textField.setEditable(editable);
691 setDropped(false);
692 }
693
694 /***
695 * @return the enabled state of the widget.
696 */
697 public boolean isEnabled() {
698 return _enabled;
699 }
700
701 /***
702 * Set the enabled state of the widget.
703 *
704 * @param enabled the enabled state to set
705 */
706 public void setEnabled(boolean enabled) {
707 super.setEnabled(enabled);
708 _enabled = enabled;
709 _textField.setEnabled(enabled);
710 _dropdownButton.setEnabled(enabled);
711 setDropped(false);
712 }
713
714 /***
715 * Return the chooser panel used by the DateChooser.
716 *
717 * @return DateChooserPanel used by the date chooser.
718 */
719 public TimeChooserPanel getTimeChooserPanel() {
720
721 if (_dropDownShell == null) {
722 _dropDownShell = createDropDown();
723 }
724 return _chooserPanel;
725 }
726
727 /***
728 * Add a DateChooserListener to be informed about changes.
729 *
730 * @param listener the DateChooserListener to be added
731 */
732 public void addDateChooserListener(IDateChooserListener listener) {
733 if (_listenerList == null) {
734 _listenerList = new ArrayList<IDateChooserListener>();
735 }
736 _listenerList.add(listener);
737 }
738
739 /***
740 * Remove a DateChooserListener.
741 *
742 * @param listener the DateChooserListener to be removed
743 */
744 public void remDateChooserListener(IDateChooserListener listener) {
745 if (_listenerList == null) {
746 return;
747 }
748 _listenerList.remove(listener);
749 }
750
751 /***
752 * Inform listeners that a date has been chosen.
753 *
754 * @param date chosen date
755 */
756 protected void fireDateChosen(Date date) {
757 if (_listenerList != null) {
758 for (IDateChooserListener listener : _listenerList) {
759 listener.dateChosen(date);
760 }
761 }
762 }
763
764 /***
765 * Inform listeners about an intermediate change of the date.
766 *
767 * @param date current date
768 */
769 protected void fireIntermediateChange(Date date) {
770 if (_listenerList != null) {
771 for (IDateChooserListener listener : _listenerList) {
772 listener.dateIntermediateChange(date);
773 }
774 }
775 }
776
777 /***
778 * Inform listeners that the choosing has been cancelled.
779 */
780 protected void fireChoosingCanceled() {
781 if (_listenerList != null) {
782 for (IDateChooserListener listener : _listenerList) {
783 listener.choosingCanceled();
784 }
785 }
786 }
787
788 /***
789 * Inform listeners that the current input has become invalid.
790 */
791 protected void fireInputInvalid() {
792 if (_listenerList != null) {
793 for (IDateChooserListener listener : _listenerList) {
794 listener.inputInvalid();
795 }
796 }
797 }
798
799 /***
800 * @return Returns the selectAllOnFocusGained.
801 */
802 public boolean isSelectAllOnFocusGained() {
803 return _selectAllOnFocusGained;
804 }
805
806 /***
807 * @param selectAllOnFocusGained The selectAllOnFocusGained to set.
808 */
809 public void setSelectAllOnFocusGained(boolean selectAllOnFocusGained) {
810 _selectAllOnFocusGained = selectAllOnFocusGained;
811 }
812
813 /***
814 * @return Returns the fieldIdentifier.
815 */
816 public IFieldIdentifier getFieldIdentifier() {
817 return _fieldIdentifier;
818 }
819
820 /***
821 * @param fieldIdentifier The fieldIdentifier to set.
822 */
823 public void setFieldIdentifier(IFieldIdentifier fieldIdentifier) {
824 _fieldIdentifier = fieldIdentifier;
825 }
826
827 /***
828 * Retrieve state of mousewheel support on textfield.
829 *
830 * @return true if enabled
831 */
832 public boolean isTextfieldMouseWheelEnable() {
833 return _textfieldMouseWheelEnable;
834 }
835
836 /***
837 * Enable/Disable mousewheel for rolling on text field. Default is true.
838 *
839 * @param mouseWheelEnable true for enable
840 */
841 public void setTextfieldMouseWheelEnable(boolean mouseWheelEnable) {
842 _textfieldMouseWheelEnable = mouseWheelEnable;
843 }
844
845 /***
846 * Retrieve a date chooser that has been set for synchronizing the date part of the returned date.
847 *
848 * @return date chooser or <code>null</code>
849 */
850 public DateChooser getDateChooser() {
851 return _dateChooser;
852 }
853
854 /***
855 * Set a date chooser that supplies the date part for any returned date (as long as the datechooser provides a
856 * date).
857 *
858 * @param dateChooser date chooser to sync with
859 */
860 public void setDateChooser(DateChooser dateChooser) {
861 _dateChooser = dateChooser;
862 }
863
864 }