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