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