001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.ui.input.datebox;
029
030import org.opencms.gwt.client.I_CmsHasInit;
031import org.opencms.gwt.client.Messages;
032import org.opencms.gwt.client.ui.CmsPopup;
033import org.opencms.gwt.client.ui.I_CmsAutoHider;
034import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
035import org.opencms.gwt.client.ui.input.CmsRadioButton;
036import org.opencms.gwt.client.ui.input.CmsRadioButtonGroup;
037import org.opencms.gwt.client.ui.input.CmsTextBox;
038import org.opencms.gwt.client.ui.input.I_CmsFormWidget;
039import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry;
040import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory;
041import org.opencms.util.CmsStringUtil;
042
043import java.util.Date;
044import java.util.Map;
045
046import com.google.common.base.Optional;
047import com.google.gwt.core.client.GWT;
048import com.google.gwt.core.client.Scheduler;
049import com.google.gwt.core.client.Scheduler.ScheduledCommand;
050import com.google.gwt.dom.client.Element;
051import com.google.gwt.dom.client.EventTarget;
052import com.google.gwt.dom.client.Style.Unit;
053import com.google.gwt.event.dom.client.BlurEvent;
054import com.google.gwt.event.dom.client.BlurHandler;
055import com.google.gwt.event.dom.client.ClickEvent;
056import com.google.gwt.event.dom.client.ClickHandler;
057import com.google.gwt.event.dom.client.FocusEvent;
058import com.google.gwt.event.dom.client.FocusHandler;
059import com.google.gwt.event.dom.client.HasKeyPressHandlers;
060import com.google.gwt.event.dom.client.KeyCodes;
061import com.google.gwt.event.dom.client.KeyPressEvent;
062import com.google.gwt.event.dom.client.KeyPressHandler;
063import com.google.gwt.event.dom.client.KeyUpEvent;
064import com.google.gwt.event.dom.client.KeyUpHandler;
065import com.google.gwt.event.logical.shared.CloseEvent;
066import com.google.gwt.event.logical.shared.CloseHandler;
067import com.google.gwt.event.logical.shared.ValueChangeEvent;
068import com.google.gwt.event.logical.shared.ValueChangeHandler;
069import com.google.gwt.event.shared.HandlerRegistration;
070import com.google.gwt.i18n.client.DateTimeFormat;
071import com.google.gwt.uibinder.client.UiBinder;
072import com.google.gwt.uibinder.client.UiField;
073import com.google.gwt.user.client.Command;
074import com.google.gwt.user.client.DOM;
075import com.google.gwt.user.client.Event;
076import com.google.gwt.user.client.Event.NativePreviewEvent;
077import com.google.gwt.user.client.Event.NativePreviewHandler;
078import com.google.gwt.user.client.ui.Composite;
079import com.google.gwt.user.client.ui.FlowPanel;
080import com.google.gwt.user.client.ui.HasValue;
081import com.google.gwt.user.client.ui.PopupPanel;
082import com.google.gwt.user.client.ui.RootPanel;
083import com.google.gwt.user.client.ui.UIObject;
084import com.google.gwt.user.datepicker.client.CalendarUtil;
085import com.google.gwt.user.datepicker.client.DatePicker;
086
087/**
088 * A text box that shows a date time picker widget when the user clicks on it.
089 */
090public class CmsDateBox extends Composite
091implements HasValue<Date>, I_CmsFormWidget, I_CmsHasInit, HasKeyPressHandlers, I_CmsHasDateBoxEventHandlers {
092
093    /**
094     * Drag and drop event preview handler.<p>
095     *
096     * To be used while dragging.<p>
097     */
098    protected class CloseEventPreviewHandler implements NativePreviewHandler {
099
100        /**
101         * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
102         */
103        public void onPreviewNativeEvent(NativePreviewEvent event) {
104
105            Event nativeEvent = Event.as(event.getNativeEvent());
106            switch (DOM.eventGetType(nativeEvent)) {
107                case Event.ONMOUSEMOVE:
108                    break;
109                case Event.ONMOUSEUP:
110                    break;
111                case Event.ONKEYDOWN:
112                    break;
113                case Event.ONMOUSEWHEEL:
114                    hidePopup();
115                    break;
116                default:
117                    // do nothing
118            }
119        }
120
121    }
122
123    /**
124     * This inner class implements the handler for the date box widget.<p>
125     */
126    protected class CmsDateBoxHandler
127    implements ClickHandler, FocusHandler, BlurHandler, KeyUpHandler, ValueChangeHandler<Date>,
128    CloseHandler<PopupPanel> {
129
130        /**
131         * @see com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event.dom.client.BlurEvent)
132         */
133        public void onBlur(BlurEvent event) {
134
135            UIObject source = (UIObject)event.getSource();
136            if (m_box.getElement().isOrHasChild(source.getElement())) {
137                onDateBoxBlur();
138            } else if (m_time.getElement().isOrHasChild(source.getElement())) {
139                onTimeBlur();
140            }
141        }
142
143        /**
144         * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
145         */
146        public void onClick(ClickEvent event) {
147
148            if (event.getSource() == m_box) {
149                onDateBoxClick();
150            } else if (event.getSource() == m_time) {
151                onTimeClick();
152            } else if ((event.getSource() == m_am) || (event.getSource() == m_pm)) {
153                onAmPmClick();
154            }
155        }
156
157        /**
158         * @see com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt.event.logical.shared.CloseEvent)
159         */
160        public void onClose(CloseEvent<PopupPanel> event) {
161
162            m_box.setPreventShowError(false);
163
164        }
165
166        /**
167         * @see com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event.dom.client.FocusEvent)
168         */
169        public void onFocus(FocusEvent event) {
170
171            UIObject source = (UIObject)event.getSource();
172            if (m_time.getElement().isOrHasChild(source.getElement())) {
173                onFocusTimeBox();
174            }
175        }
176
177        /**
178         * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
179         */
180        public void onKeyUp(KeyUpEvent event) {
181
182            if (event.getSource() == m_box) {
183                onDateBoxKeyPress(event);
184            } else if (event.getSource() == m_time) {
185                onTimeKeyPressed(event);
186            }
187        }
188
189        /**
190         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
191         */
192        public void onValueChange(ValueChangeEvent<Date> event) {
193
194            onPickerValueChanged();
195        }
196    }
197
198    /** The ui-binder interface for this composite. */
199    interface I_CmsDateBoxUiBinder extends UiBinder<FlowPanel, CmsDateBox> {
200        // GWT interface, nothing to do here
201    }
202
203    /** Dummy value used for invalid dates. */
204    public static final Date INVALID_DATE = new Date(Integer.MIN_VALUE + 37); // can't use Long.MIN_VALUE since this leads to an invalid Date object in GWT
205
206    /** Format used to parse the configured time in fixed-time mode. */
207    public static final DateTimeFormat TIME_FORMAT_24H = DateTimeFormat.getFormat("HH:mm");
208
209    /** The widget type identifier for this widget. */
210    public static final String WIDGET_TYPE = "datebox";
211
212    /** The ui-binder instance. */
213    private static I_CmsDateBoxUiBinder uiBinder = GWT.create(I_CmsDateBoxUiBinder.class);
214
215    /** The am radio button. */
216    @UiField
217    protected CmsRadioButton m_am;
218
219    /** The radio button group for am/pm selection. */
220    protected CmsRadioButtonGroup m_ampmGroup;
221
222    /** The auto hide parent. */
223    protected I_CmsAutoHider m_autoHideParent;
224
225    /** The input field to show the result of picking a date. */
226    @UiField
227    protected CmsTextBox m_box;
228
229    /** The panel for the date time picker. */
230    @UiField
231    protected FlowPanel m_dateTimePanel;
232
233    /** The gwt date picker. */
234    @UiField
235    protected DatePicker m_picker;
236
237    /** The pm radio button. */
238    @UiField
239    protected CmsRadioButton m_pm;
240
241    /** Event preview handler registration. */
242    protected HandlerRegistration m_previewHandlerRegistration;
243
244    /** The text box to input the time. */
245    @UiField
246    protected CmsTextBox m_time;
247
248    /** The panel for the time selection. */
249    @UiField
250    protected FlowPanel m_timeField;
251
252    /** True if invalid values should be allowed. */
253    private boolean m_allowInvalidValue;
254
255    /** The value for show date only. */
256    private boolean m_dateOnly;
257
258    /** The time to use for the widget value when used in date-only mode. */
259    private String m_fixedTime;
260
261    /** The initial date shown, when the date picker is opened and no date was set before. */
262    private Date m_initialDate;
263
264    /** Signals whether the date box is valid or not. */
265    private boolean m_isValidDateBox;
266
267    /** Signals whether the time field is valid or not. */
268    private boolean m_isValidTime;
269
270    /** The old value for fire event decision. */
271    private Date m_oldValue;
272
273    /** The popup panel to show the the date time picker widget in. */
274    private CmsPopup m_popup;
275
276    /**
277     * The event preview handler.<p>
278     *
279     * Blurs the time box if the user clicks outside of it.<p>
280     */
281    private NativePreviewHandler m_previewHandler = new NativePreviewHandler() {
282
283        /**
284         * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
285         */
286        public void onPreviewNativeEvent(NativePreviewEvent event) {
287
288            previewNativeEvent(event);
289        }
290    };
291
292    /** Stores the preview handler. */
293    private HandlerRegistration m_previewRegistration;
294
295    /** The date used for enable and disable the text box. */
296    private Date m_tmpValue;
297
298    /**
299     * Create a new date box widget with the date time picker.
300     */
301    public CmsDateBox() {
302
303        initWidget(uiBinder.createAndBindUi(this));
304        m_box.colorWhite();
305
306        m_popup = new CmsPopup(Messages.get().key(Messages.GUI_DATEBOX_TITLE_0));
307        m_ampmGroup = new CmsRadioButtonGroup();
308
309        m_am.setText(Messages.get().key(Messages.GUI_DATE_AM_0));
310        m_am.setGroup(m_ampmGroup);
311        m_pm.setText(Messages.get().key(Messages.GUI_DATE_PM_0));
312        m_pm.setGroup(m_ampmGroup);
313
314        if (!CmsDateConverter.is12HourPresentation()) {
315            m_pm.setVisible(false);
316            m_am.setVisible(false);
317        }
318
319        CmsDateBoxHandler dateBoxHandler = new CmsDateBoxHandler();
320        m_picker.addValueChangeHandler(dateBoxHandler);
321
322        m_box.addBlurHandler(dateBoxHandler);
323        m_box.addClickHandler(dateBoxHandler);
324        m_box.addKeyUpHandler(dateBoxHandler);
325        m_am.addClickHandler(dateBoxHandler);
326        m_pm.addClickHandler(dateBoxHandler);
327        m_time.addClickHandler(dateBoxHandler);
328        m_time.addBlurHandler(dateBoxHandler);
329        m_time.addKeyUpHandler(dateBoxHandler);
330        m_time.addFocusHandler(dateBoxHandler);
331
332        m_popup.add(m_dateTimePanel);
333        m_popup.setWidth(0);
334        m_popup.setModal(true);
335        m_popup.removePadding();
336        m_popup.setBackgroundColor(I_CmsLayoutBundle.INSTANCE.constants().css().backgroundColorDialog());
337        m_popup.addDialogClose(new Command() {
338
339            public void execute() {
340
341                m_box.setPreventShowError(false);
342            }
343        });
344        m_popup.addCloseHandler(dateBoxHandler);
345        m_popup.addAutoHidePartner(m_box.getElement());
346        m_popup.setAutoHideEnabled(true);
347    }
348
349    /**
350     * Initializes this class.<p>
351     */
352    public static void initClass() {
353
354        // registers a factory for creating new instances of this widget
355        CmsWidgetFactoryRegistry.instance().registerFactory(WIDGET_TYPE, new I_CmsFormWidgetFactory() {
356
357            /**
358             * @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory#createWidget(java.util.Map, com.google.common.base.Optional)
359             */
360            public I_CmsFormWidget createWidget(Map<String, String> widgetParams, Optional<String> defaultValue) {
361
362                return new CmsDateBox();
363            }
364        });
365    }
366
367    /**
368     * @see org.opencms.gwt.client.ui.input.datebox.I_CmsHasDateBoxEventHandlers#addCmsDateBoxEventHandler(org.opencms.gwt.client.ui.input.datebox.I_CmsDateBoxEventHandler)
369     */
370    public HandlerRegistration addCmsDateBoxEventHandler(I_CmsDateBoxEventHandler handler) {
371
372        return addHandler(handler, CmsDateBoxEvent.TYPE);
373    }
374
375    /**
376     * @see com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler(com.google.gwt.event.dom.client.KeyPressHandler)
377     */
378    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
379
380        return m_box.addHandler(handler, KeyPressEvent.getType());
381    }
382
383    /**
384     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
385     */
386    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Date> handler) {
387
388        return addHandler(handler, ValueChangeEvent.getType());
389    }
390
391    /**
392     * Returns true if invalid values should be allowed.<p>
393     *
394     * If invalid values are allowed, methods returning a Date will return a special dummy date in case the date is invalid.
395     *
396     * @return true if invalid values should be allowed
397     */
398    public boolean allowInvalidValue() {
399
400        return m_allowInvalidValue;
401    }
402
403    /**
404     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getApparentValue()
405     */
406    public String getApparentValue() {
407
408        return getFormValueAsString();
409    }
410
411    /**
412     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType()
413     */
414    public FieldType getFieldType() {
415
416        return I_CmsFormWidget.FieldType.DATE;
417    }
418
419    /**
420     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue()
421     */
422    public Object getFormValue() {
423
424        return getValue();
425    }
426
427    /**
428     * Returns the value of the date box as String in form of a long.<p>
429     *
430     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString()
431     */
432    public String getFormValueAsString() {
433
434        Date value = getValue();
435        if (value == null) {
436            return "";
437        } else if (allowInvalidValue() && value.equals(INVALID_DATE)) {
438            return "INVALID_DATE";
439        }
440        String result = String.valueOf(getValue().getTime());
441        return result;
442    }
443
444    /**
445     * Returns the text box of this widget.<p>
446     *
447     * @return the CmsText Box
448     */
449    public CmsTextBox getTextField() {
450
451        return m_box;
452    }
453
454    /**
455     * @see com.google.gwt.user.client.ui.HasValue#getValue()
456     */
457    public Date getValue() {
458
459        Date date = null;
460        if (isEnabled()) {
461            try {
462                if (m_dateOnly) {
463                    date = CmsDateConverter.toDayDate(m_box.getText());
464                    if (m_fixedTime != null) {
465                        date = CmsDateConverter.getDateWithTime(date, m_fixedTime, TIME_FORMAT_24H);
466                    }
467                } else {
468                    date = CmsDateConverter.toDate(m_box.getText());
469                }
470                setErrorMessage(null);
471            } catch (Exception e) {
472                setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_DATE_FORMAT_0));
473                if (allowInvalidValue()) {
474                    // empty is actually a valid value for date fields, so we have to return a different value to distinguish between the cases
475                    return INVALID_DATE;
476                }
477            }
478        }
479        return date;
480    }
481
482    /**
483     * Returns the date value as formated String or an empty String if the date value is null.<p>
484     *
485     * @return the date value as formated String
486     */
487    public String getValueAsFormatedString() {
488
489        return CmsDateConverter.toString(getValue());
490    }
491
492    /**
493     * Returns <code>true</code> if the box and the time input fields don't have any errors.<p>
494     *
495     * @return <code>true</code> if the box and the time input fields don't have any errors
496     */
497    public boolean hasErrors() {
498
499        if (m_box.hasError() || m_time.hasError()) {
500            return true;
501        }
502        return false;
503    }
504
505    /**
506     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled()
507     */
508    public boolean isEnabled() {
509
510        return m_box.isEnabled();
511    }
512
513    /**
514     * Checks if the String in the date box input field is a valid date format.<p>
515     *
516     * @return <code>true</code> if the String in the date box input field is a valid date format
517     */
518    public boolean isValideDateBox() {
519
520        try {
521            CmsDateConverter.toDate(m_box.getText());
522            return true;
523        } catch (Exception e) {
524            return false;
525        }
526    }
527
528    /**
529     * Updates the date box when the user has clicked on the time field.<p>
530     */
531    public void onTimeClick() {
532
533        updateFromPicker();
534    }
535
536    /**
537     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset()
538     */
539    public void reset() {
540
541        setValue(null);
542    }
543
544    /**
545     * Enables or disables whether invalid values are allowed.<p>
546     *
547     * If invalid values are allowed, they will be returned as a special dummy date (INVALID_DATE).
548     *
549     * @param allowInvalidValue true if invalid values should be allowed
550     */
551    public void setAllowInvalidValue(boolean allowInvalidValue) {
552
553        m_allowInvalidValue = allowInvalidValue;
554    }
555
556    /**
557     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setAutoHideParent(org.opencms.gwt.client.ui.I_CmsAutoHider)
558     */
559    public void setAutoHideParent(I_CmsAutoHider autoHideParent) {
560
561        m_autoHideParent = autoHideParent;
562    }
563
564    /**
565     * Sets the value if the date only should be shown.
566     * @param dateOnly if the date only should be shown
567     */
568    public void setDateOnly(boolean dateOnly) {
569
570        if (m_dateOnly != dateOnly) {
571            m_dateOnly = dateOnly;
572            if (m_dateOnly) {
573                m_time.removeFromParent();
574                m_am.removeFromParent();
575                m_pm.removeFromParent();
576            } else {
577                m_timeField.add(m_time);
578                m_timeField.add(m_am);
579                m_timeField.add(m_pm);
580            }
581        }
582    }
583
584    /**
585     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean)
586     */
587    public void setEnabled(boolean enabled) {
588
589        if (!enabled) {
590            m_tmpValue = getValue();
591            m_box.setFormValueAsString(Messages.get().key(Messages.GUI_INPUT_NOT_USED_0));
592        } else {
593            setValue(m_tmpValue);
594        }
595        m_box.setEnabled(enabled);
596    }
597
598    /**
599     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String)
600     */
601    public void setErrorMessage(String errorMessage) {
602
603        m_box.setErrorMessage(errorMessage);
604    }
605
606    /**
607     * Sets the time to be used when the widget is in date-only mode.
608     *
609     * @param time the time to use when the widget is in date-only mode
610     */
611    public void setFixedTime(String time) {
612
613        m_fixedTime = time;
614    }
615
616    /**
617     * Expects the value as String in form of a long.<p>
618     *
619     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String)
620     */
621    public void setFormValueAsString(String value) {
622
623        if (!CmsStringUtil.isEmpty(value)) {
624            try {
625                long time = Long.parseLong(value);
626                setValue(new Date(time));
627            } catch (NumberFormatException e) {
628                // if the String value is none long number make the field empty
629                setValue(null);
630            }
631        } else {
632            // if the value is <code>null</code> make the field empty
633            setValue(null);
634        }
635    }
636
637    /**
638     * Sets the initial date shown, when the date picker is opened and no date was set before.<p>
639     *
640     * @param initialDate the initial date
641     */
642    public void setInitialDate(Date initialDate) {
643
644        m_initialDate = initialDate;
645    }
646
647    /**
648     * Sets the name of the input field.<p>
649     *
650     * @param name of the input field
651     */
652    public void setName(String name) {
653
654        m_time.setName(name);
655
656    }
657
658    /**
659     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object)
660     */
661    public void setValue(Date value) {
662
663        setValue(value, false);
664    }
665
666    /**
667     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean)
668     */
669    public void setValue(Date value, boolean fireEvents) {
670
671        m_tmpValue = value;
672        if (fireEvents) {
673            fireChange(value, false);
674        }
675        if (value == null) {
676            m_box.setFormValueAsString("");
677        } else if (m_dateOnly) {
678            m_box.setFormValueAsString(CmsDateConverter.toDateString(value));
679        } else {
680            m_box.setFormValueAsString(CmsDateConverter.toString(value));
681        }
682    }
683
684    /**
685     * Updates the updates the close behavior and sets the value of the date box to the value from the picker.<p>
686     */
687    protected void executeTimeAction() {
688
689        if (isValidTime()) {
690            updateFromPicker();
691        }
692        updateCloseBehavior();
693    }
694
695    /**
696     * Fires the value change event if needed.<p>
697     *
698     * @param newValue the new value
699     * @param isTyping true if the user is currently typing
700     */
701    protected void fireChange(Date newValue, boolean isTyping) {
702
703        ValueChangeEvent.<Date> fireIfNotEqual(this, m_oldValue, CalendarUtil.copyDate(newValue));
704        CmsDateBoxEvent.fire(this, newValue, isTyping);
705        m_oldValue = newValue;
706    }
707
708    /**
709     * Hides the date time popup.<p>
710     */
711    protected void hidePopup() {
712
713        if (CmsDateConverter.validateTime(getTimeText())) {
714            // before hiding the date picker remove the date box popup from the auto hide partners of the parent popup
715            if (m_autoHideParent != null) {
716                m_autoHideParent.removeAutoHidePartner(getElement());
717            }
718            m_popup.hide();
719            if (m_previewHandlerRegistration != null) {
720                m_previewHandlerRegistration.removeHandler();
721            }
722            m_previewHandlerRegistration = null;
723        }
724    }
725
726    /**
727     * If the am or pm radio button is clicked update the date box from the date time picker.<p>
728     */
729    protected void onAmPmClick() {
730
731        updateFromPicker();
732    }
733
734    /**
735     * The date box on blur action.<p>
736     *
737     * If the date box loses the focus the date time picker should be updated from the date box value.<p>
738     */
739    protected void onDateBoxBlur() {
740
741        if (!m_popup.isShowing()) {
742            updateFromTextBox(false);
743        }
744        updateCloseBehavior();
745    }
746
747    /**
748     * The date box on click action.<p>
749     *
750     * If the date box is clicked the time date picker should be shown.<p>
751     */
752    protected void onDateBoxClick() {
753
754        if (!m_popup.isShowing()) {
755            showPopup();
756        }
757    }
758
759    /**
760     * The date box on key down action.<p>
761     * <ul>
762     * <li>If enter or tab is pressed in the date box the date time
763     * picker should be updated with the value from the date box.</li>
764     * <li>If the escape key is pressed the picker should be hided.</li>
765     * <li>If the up key is pressed the value should be taken from the date box.</li>
766     * <li>If the down key is pressed the picker should be hided.</li>
767     * </ul>
768     *
769     * @param event the key down event
770     */
771    protected void onDateBoxKeyPress(KeyUpEvent event) {
772
773        switch (event.getNativeEvent().getKeyCode()) {
774            case KeyCodes.KEY_CTRL:
775            case KeyCodes.KEY_SHIFT:
776            case KeyCodes.KEY_ALT:
777            case KeyCodes.KEY_LEFT:
778            case KeyCodes.KEY_RIGHT:
779                break;
780            case KeyCodes.KEY_ENTER:
781            case KeyCodes.KEY_TAB:
782            case KeyCodes.KEY_ESCAPE:
783            case KeyCodes.KEY_UP:
784                updateFromTextBox(false);
785                hidePopup();
786                break;
787            case KeyCodes.KEY_DOWN:
788                showPopup();
789                break;
790            default:
791                hidePopup();
792                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
793
794                    public void execute() {
795
796                        updateCloseBehavior();
797                        if (isValideDateBox() || allowInvalidValue()) {
798                            setErrorMessage(null);
799                            fireChange(getValue(), true);
800                        }
801                    }
802                });
803                break;
804        }
805    }
806
807    /**
808     * Adds the preview handler.<p>
809     */
810    protected void onFocusTimeBox() {
811
812        m_previewRegistration = Event.addNativePreviewHandler(m_previewHandler);
813    }
814
815    /**
816     * If the value of the picker changes, the value of the date time picker should be updated.<p>
817     */
818    protected void onPickerValueChanged() {
819
820        setErrorMessage(null);
821        updateFromPicker();
822    }
823
824    /**
825     * If the time field loses the focus the entered time should be checked.<p>
826     */
827    protected void onTimeBlur() {
828
829        if (m_previewRegistration != null) {
830            m_previewRegistration.removeHandler();
831        }
832        checkTime();
833    }
834
835    /**
836     * If the user presses enter in the time field the value of the
837     * picker should be updated and the the popup should be closed.<p>
838     *
839     * In any other case the popup should be prevented to being closed.<p>
840     *
841     * @param event the key pressed event
842     */
843    protected void onTimeKeyPressed(KeyUpEvent event) {
844
845        switch (event.getNativeEvent().getKeyCode()) {
846            case KeyCodes.KEY_CTRL:
847            case KeyCodes.KEY_SHIFT:
848            case KeyCodes.KEY_ALT:
849            case KeyCodes.KEY_LEFT:
850            case KeyCodes.KEY_RIGHT:
851                break;
852            case KeyCodes.KEY_ENTER:
853                updateFromPicker();
854                hidePopup();
855                break;
856            default:
857                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
858
859                    public void execute() {
860
861                        executeTimeAction();
862                        fireChange(getValue(), false);
863                    }
864                });
865                break;
866        }
867    }
868
869    /**
870     * Blurs the time box if the user clicks outside of it.<p>
871     *
872     * @param event the native preview event
873     */
874    protected void previewNativeEvent(NativePreviewEvent event) {
875
876        Event nativeEvent = Event.as(event.getNativeEvent());
877        if ((nativeEvent.getTypeInt() == Event.ONCLICK)) {
878            EventTarget target = nativeEvent.getEventTarget();
879            if (!Element.is(target)) {
880                return;
881            }
882            Element element = Element.as(target);
883            if (!m_time.getElement().isOrHasChild(element)) {
884                m_time.blur();
885            }
886        }
887    }
888
889    /**
890     * Updates the auto hide partner from the parent widget.<p>
891     *
892     * If there is any invalid user input the parent widget should not be closed automatically.<p>
893     */
894    protected void updateCloseBehavior() {
895
896        if (isEnabled()) {
897            if (!m_isValidTime && isValidTime()) {
898                m_isValidTime = true;
899                m_popup.setAutoHideEnabled(true);
900            } else if (m_isValidTime && !isValidTime()) {
901                m_isValidTime = false;
902                m_popup.setAutoHideEnabled(false);
903            }
904
905            if (!m_isValidDateBox && isValideDateBox()) {
906                m_isValidDateBox = true;
907                if (m_autoHideParent != null) {
908                    m_autoHideParent.removeAutoHidePartner(RootPanel.getBodyElement().getParentElement());
909                }
910            } else if (m_isValidDateBox && !isValideDateBox()) {
911                m_isValidDateBox = false;
912                if (m_autoHideParent != null) {
913                    m_autoHideParent.addAutoHidePartner(RootPanel.getBodyElement().getParentElement());
914                }
915            }
916        }
917    }
918
919    /**
920     * Validates the time and prints out an error message if the time format is incorrect.<p>
921     */
922    private void checkTime() {
923
924        if (!isValidTime()) {
925            m_time.setErrorMessageWidth((m_popup.getOffsetWidth() - 32) + Unit.PX.toString());
926            m_time.setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_TIME_FORMAT_0));
927        } else {
928            m_time.setErrorMessage(null);
929        }
930        updateCloseBehavior();
931    }
932
933    /**
934     * Returns the time text field value as string.<p>
935     *
936     * @return the time text field value as string
937     */
938    private String getTimeText() {
939
940        String timeAsString = m_time.getText().trim();
941        if (CmsDateConverter.is12HourPresentation()) {
942            if (!(timeAsString.contains(CmsDateConverter.AM) || timeAsString.contains(CmsDateConverter.PM))) {
943                if (m_am.isChecked()) {
944                    timeAsString = timeAsString + " " + CmsDateConverter.AM;
945                } else {
946                    timeAsString = timeAsString + " " + CmsDateConverter.PM;
947                }
948            }
949        }
950        return timeAsString;
951    }
952
953    /**
954     * Checks if the String in the time input field is a valid time format.<p>
955     *
956     * @return <code>true</code> if the String in the time input field is a valid time format
957     */
958    private boolean isValidTime() {
959
960        return m_dateOnly || CmsDateConverter.validateTime(getTimeText());
961    }
962
963    /**
964     * Sets the value of the date picker.<p>
965     *
966     * @param date the value to set
967     * @param fireEvents signals whether the value changed event should be fired or not
968     */
969    private void setPickerValue(Date date, boolean fireEvents) {
970
971        if (date == null) {
972            date = new Date();
973        }
974        m_picker.setValue(date, fireEvents);
975        m_picker.setCurrentMonth(date);
976        m_time.setFormValueAsString(CmsDateConverter.cutSuffix(CmsDateConverter.getTime(date)).trim());
977        if (CmsDateConverter.isAm(date)) {
978            m_ampmGroup.selectButton(m_am);
979        } else {
980            m_ampmGroup.selectButton(m_pm);
981        }
982    }
983
984    /**
985     * Shows the date picker popup.<p>
986     */
987    private void showPopup() {
988
989        updateFromTextBox(true);
990        m_box.setPreventShowError(true);
991        m_popup.showRelativeTo(m_box);
992        if (m_previewHandlerRegistration != null) {
993            m_previewHandlerRegistration.removeHandler();
994        }
995        m_previewHandlerRegistration = Event.addNativePreviewHandler(new CloseEventPreviewHandler());
996    }
997
998    /**
999     * Sets the value of the date box.<p>
1000     */
1001    private void updateFromPicker() {
1002
1003        checkTime();
1004        Date date = m_picker.getValue();
1005        if (!m_dateOnly) {
1006            String timeAsString = getTimeText();
1007            date = CmsDateConverter.getDateWithTime(date, timeAsString);
1008        }
1009        setValue(date);
1010        setErrorMessage(null);
1011        fireChange(date, false);
1012    }
1013
1014    /**
1015     * Updates the picker if the user manually modified the date of the text box.<p>
1016     *
1017     * @param initial flag indicating if the date box is being initialized
1018     */
1019    private void updateFromTextBox(boolean initial) {
1020
1021        Date date = getValue();
1022        if (initial && (date == null)) {
1023            date = m_initialDate;
1024        }
1025        setPickerValue(date, false);
1026        m_time.setErrorMessage(null);
1027        fireChange(getValue(), false);
1028    }
1029}