com.haulmont.cuba.desktop.gui.components.DesktopDateField.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.desktop.gui.components.DesktopDateField.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.haulmont.cuba.desktop.gui.components;

import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.datatypes.Datatypes;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.chile.core.model.utils.InstanceUtils;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.annotation.IgnoreUserTimeZone;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.desktop.App;
import com.haulmont.cuba.desktop.gui.executors.impl.DesktopBackgroundWorker;
import com.haulmont.cuba.desktop.sys.DesktopToolTipManager;
import com.haulmont.cuba.desktop.sys.layout.BoxLayoutAdapter;
import com.haulmont.cuba.desktop.sys.layout.MigBoxLayoutAdapter;
import com.haulmont.cuba.desktop.sys.vcl.DatePicker.DatePicker;
import com.haulmont.cuba.desktop.sys.vcl.Flushable;
import com.haulmont.cuba.desktop.sys.vcl.FocusableComponent;
import com.haulmont.cuba.gui.components.DateField;
import com.haulmont.cuba.gui.components.Frame.NotificationType;
import com.haulmont.cuba.gui.components.RequiredValueMissingException;
import com.haulmont.cuba.gui.components.ValidationException;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.impl.WeakItemChangeListener;
import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener;
import com.haulmont.cuba.security.global.UserSession;
import org.apache.commons.lang.StringUtils;
import org.jdesktop.swingx.JXDatePicker;

import javax.persistence.TemporalType;
import javax.swing.*;
import javax.validation.constraints.Future;
import javax.validation.constraints.Past;
import java.awt.*;
import java.text.ParseException;
import java.util.*;
import java.util.List;

public class DesktopDateField extends DesktopAbstractField<JPanel> implements DateField {
    protected Messages messages;
    protected Resolution resolution;
    protected Datasource datasource;
    protected String dateTimeFormat;
    protected String dateFormat;
    protected String timeFormat;

    protected boolean updatingInstance;

    protected JXDatePicker datePicker;
    protected DesktopTimeField timeField;
    protected boolean valid = true;

    protected Object prevValue = null;

    protected TimeZone timeZone;
    protected UserSession userSession;

    protected Datasource.ItemChangeListener itemChangeListener;
    protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener;
    protected Date startDate;
    protected Date endDate;

    protected boolean updateTimeFieldResolution = false;

    public DesktopDateField() {
        impl = new FocusableComposition();

        messages = AppBeans.get(Messages.NAME);
        initComponentParts();
        setResolution(Resolution.MIN);

        UserSessionSource sessionSource = AppBeans.get(UserSessionSource.NAME);
        userSession = sessionSource.getUserSession();
        Locale locale = userSession.getLocale();
        setDateFormat(Datatypes.getFormatStringsNN(locale).getDateTimeFormat());
        DesktopComponentsHelper.adjustDateFieldSize(impl);
    }

    protected void initComponentParts() {
        BoxLayoutAdapter adapter = new MigBoxLayoutAdapter(impl);
        adapter.setSpacing(false);
        adapter.setFlowDirection(BoxLayoutAdapter.FlowDirection.X);
        adapter.setExpandLayout(true);
        impl.setLayout(adapter.getLayout());

        datePicker = new FlushableDatePicker();

        Dimension size = getDefaultDimension();
        datePicker.setPreferredSize(size);
        datePicker.setMinimumSize(size);

        timeField = new DesktopTimeField();
        timeField.addValueChangeListener(e -> {
            if (!checkRange(constructDate())) {
                return;
            }

            if (!updateTimeFieldResolution) {
                updateInstance();
            }
        });

        datePicker.addPropertyChangeListener(evt -> {
            if ("date".equals(evt.getPropertyName())) {
                if (!checkRange(constructDate())) {
                    return;
                }

                updateInstance();
                updateMissingValueState();
            }
        });
    }

    protected void updateLayout() {
        impl.removeAll();
        impl.add(datePicker, "growx, w 100%");
        if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            impl.add(timeField.getImpl());
        }
    }

    @Override
    public void setId(String id) {
        super.setId(id);

        if (id != null && App.getInstance().isTestMode()) {
            timeField.setId("timepart");
            datePicker.setName("datepart");
        }
    }

    @Override
    public Resolution getResolution() {
        return resolution;
    }

    @Override
    public void setResolution(Resolution resolution) {
        _setResolution(resolution);
        updateLayout();
    }

    protected void _setResolution(Resolution resolution) {
        this.resolution = resolution;
        if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            timeField.setResolution(resolution);
            // while changing resolution, timeField loses its value, so we need to set it again
            updateTimeFieldResolution = true;
            Date value = datePicker.getDate();
            if (value == null) {
                timeField.setValue(null);
            } else {
                timeField.setValue(extractTime(value));
            }
            updateTimeFieldResolution = false;
        }
    }

    @Override
    public String getDateFormat() {
        return dateTimeFormat;
    }

    @Override
    public void setDateFormat(String dateFormat) {
        dateTimeFormat = dateFormat;
        StringBuilder date = new StringBuilder(dateFormat);
        StringBuilder time = new StringBuilder(dateFormat);
        int timeStartPos = findTimeStartPos(dateFormat);
        if (timeStartPos >= 0) {
            time.delete(0, timeStartPos);
            timeFormat = StringUtils.trimToEmpty(time.toString());
            timeField.setFormat(timeFormat);
            _setResolution(resolution);
            date.delete(timeStartPos, dateFormat.length());
        } else if (resolution.ordinal() < Resolution.DAY.ordinal()) {
            _setResolution(Resolution.DAY);
        }

        this.dateFormat = StringUtils.trimToEmpty(date.toString());
        datePicker.setFormats(this.dateFormat);

        updateLayout();
    }

    protected int findTimeStartPos(String dateTimeFormat) {
        List<Integer> positions = new ArrayList<>();

        char[] signs = new char[] { 'H', 'h', 'm', 's' };
        for (char sign : signs) {
            int pos = dateTimeFormat.indexOf(sign);
            if (pos > -1) {
                positions.add(pos);
            }
        }
        return positions.isEmpty() ? -1 : Collections.min(positions);
    }

    @Override
    public TimeZone getTimeZone() {
        return timeZone;
    }

    @Override
    public void setTimeZone(TimeZone timeZone) {
        TimeZone prevTimeZone = this.timeZone;
        if (prevTimeZone == null && timeZone == null) {
            return;
        }
        Date value = getValue();
        this.timeZone = timeZone;
        datePicker.setTimeZone(timeZone);
        if (value != null && !Objects.equals(prevTimeZone, timeZone)) {
            updateComponent(value);
        }
    }

    @Override
    public void setRangeStart(Date value) {
        startDate = value;
    }

    @Override
    public Date getRangeStart() {
        return startDate;
    }

    @Override
    public void setRangeEnd(Date value) {
        endDate = value;
    }

    @Override
    public Date getRangeEnd() {
        return endDate;
    }

    protected boolean checkRange(Date value) {
        if (updatingInstance) {
            return true;
        }

        if (value != null) {
            Date rangeStart = getRangeStart();
            if (rangeStart != null && value.before(rangeStart)) {
                handleDateOutOfRange(value);
                return false;
            }

            Date rangeEnd = getRangeEnd();
            if (rangeEnd != null && value.after(rangeEnd)) {
                handleDateOutOfRange(value);
                return false;
            }
        }

        return true;
    }

    protected void handleDateOutOfRange(Date value) {
        if (getFrame() != null) {
            getFrame().showNotification(messages.getMainMessage("dateField.dateOutOfRangeMessage"),
                    NotificationType.TRAY);
        }

        updatingInstance = true;
        try {
            datePicker.setDate((Date) prevValue);
            if (prevValue == null) {
                timeField.setValue(null);
            } else {
                timeField.setValue(extractTime((Date) prevValue));
            }
        } finally {
            updatingInstance = false;
        }
    }

    @Override
    public void requestFocus() {
        SwingUtilities.invokeLater(() -> {
            datePicker.requestFocus();
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public Date getValue() {
        try {
            return constructDate();
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void setValue(Object value) {
        DesktopBackgroundWorker.checkSwingUIAccess();

        if (!Objects.equals(prevValue, value)) {
            Date targetDate = (Date) value;

            updateInstance(targetDate);
            updateComponent((Date) value);
            fireChangeListeners(value);
        }
    }

    @Override
    public void validate() throws ValidationException {
        if (!isVisible() || !isEditableWithParent() || !isEnabled())
            return;

        try {
            constructDate();
            super.validate();
        } catch (RequiredValueMissingException e) {
            throw e;
        } catch (Exception e) {
            throw new ValidationException(e);
        }
    }

    @Override
    public Datasource getDatasource() {
        return datasource;
    }

    @Override
    public MetaProperty getMetaProperty() {
        return metaProperty;
    }

    @Override
    public MetaPropertyPath getMetaPropertyPath() {
        return metaPropertyPath;
    }

    @Override
    public void setDatasource(Datasource datasource, String property) {
        this.datasource = datasource;

        if (datasource == null) {
            setValue(null);
            return;
        }

        resolveMetaPropertyPath(datasource.getMetaClass(), property);
        if (metaProperty.getRange().isDatatype()
                && metaProperty.getRange().asDatatype().getJavaClass().equals(Date.class) && timeZone == null) {
            MetadataTools metadataTools = AppBeans.get(MetadataTools.class);
            Boolean ignoreUserTimeZone = metadataTools.getMetaAnnotationValue(metaProperty,
                    IgnoreUserTimeZone.class);
            if (!Boolean.TRUE.equals(ignoreUserTimeZone)) {
                timeZone = userSession.getTimeZone();
            }
        }

        itemChangeListener = e -> {
            if (updatingInstance) {
                return;
            }
            Date value = getEntityValue(e.getItem());
            updateComponent(value);
            fireChangeListeners(value);
        };
        //noinspection unchecked
        datasource.addItemChangeListener(new WeakItemChangeListener(datasource, itemChangeListener));

        itemPropertyChangeListener = e -> {
            if (updatingInstance) {
                return;
            }
            if (e.getProperty().equals(metaPropertyPath.toString())) {
                updateComponent((Date) e.getValue());
                fireChangeListeners(e.getValue());
            }
        };
        //noinspection unchecked
        datasource.addItemPropertyChangeListener(
                new WeakItemPropertyChangeListener(datasource, itemPropertyChangeListener));

        if (datasource.getState() == Datasource.State.VALID && datasource.getItem() != null) {
            if (property.equals(metaPropertyPath.toString())) {
                Date value = getEntityValue(datasource.getItem());
                updateComponent(value);
                fireChangeListeners(value);
            }
        }

        initRequired(metaPropertyPath);
        initDateFormat(metaProperty);

        if (metaProperty.isReadOnly()) {
            setEditable(false);
        }

        initBeanValidator();
        setDateRangeByProperty(metaProperty);
    }

    protected void initDateFormat(MetaProperty metaProperty) {
        TemporalType tt = null;
        if (metaProperty.getRange().asDatatype().getJavaClass().equals(java.sql.Date.class)) {
            tt = TemporalType.DATE;
        } else if (metaProperty.getAnnotations() != null) {
            tt = (TemporalType) metaProperty.getAnnotations().get(MetadataTools.TEMPORAL_ANN_NAME);
        }

        setResolution(tt == TemporalType.DATE ? DateField.Resolution.DAY : Resolution.MIN);

        MessageTools messageTools = AppBeans.get(MessageTools.NAME);
        String formatStr = messageTools.getDefaultDateFormat(tt);
        setDateFormat(formatStr);
    }

    protected void setDateRangeByProperty(MetaProperty metaProperty) {
        if (metaProperty.getAnnotations().get(Past.class.getName()) != null) {
            TimeSource timeSource = AppBeans.get(TimeSource.NAME);
            Date currentTimestamp = timeSource.currentTimestamp();

            Calendar calendar = Calendar.getInstance(userSession.getLocale());
            calendar.setTime(currentTimestamp);
            calendar.set(Calendar.HOUR_OF_DAY, 23);
            calendar.set(Calendar.MINUTE, 59);
            calendar.set(Calendar.SECOND, 59);
            calendar.set(Calendar.MILLISECOND, 999);

            setRangeEnd(calendar.getTime());
        } else if (metaProperty.getAnnotations().get(Future.class.getName()) != null) {
            TimeSource timeSource = AppBeans.get(TimeSource.NAME);
            Date currentTimestamp = timeSource.currentTimestamp();

            Calendar calendar = Calendar.getInstance(userSession.getLocale());
            calendar.setTime(currentTimestamp);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            if (metaProperty.getRange().asDatatype().getJavaClass().equals(java.sql.Date.class)) {
                calendar.add(Calendar.DATE, 1);
            }

            setRangeStart(calendar.getTime());
        }
    }

    protected Date getEntityValue(Entity item) {
        return InstanceUtils.getValueEx(item, metaPropertyPath.getPath());
    }

    protected void updateComponent(Date value) {
        updatingInstance = true;
        try {
            setDateParts(value);
            valid = true;
        } finally {
            updatingInstance = false;
        }
        updateMissingValueState();
    }

    protected void fireChangeListeners(Object newValue) {
        if (!Objects.equals(prevValue, newValue)) {
            Object oldValue = prevValue;

            prevValue = newValue;

            fireValueChanged(oldValue, newValue);
        }
    }

    protected void setDateParts(Date value) {
        datePicker.setDate(value);
        if (value == null) {
            timeField.setValueInternal(null);
        } else {
            timeField.setValueInternal(extractTime(value));
        }
    }

    @Override
    public void updateEnabled() {
        super.updateEnabled();

        datePicker.setEnabled(isEnabledWithParent());
        timeField.setEnabled(isEnabledWithParent());
    }

    @Override
    protected void setEditableToComponent(boolean editable) {
        datePicker.setEditable(editable);
        timeField.setEditable(editable);
        updateMissingValueState();
    }

    @Override
    protected void setCaptionToComponent(String caption) {
        super.setCaptionToComponent(caption);

        requestContainerUpdate();
    }

    @Override
    public String getDescription() {
        return datePicker.getToolTipText();
    }

    @Override
    public void setDescription(String description) {
        if (!Objects.equals(this.getDescription(), description)) {
            datePicker.getEditor().setToolTipText(description);
            timeField.setDescription(description);
            DesktopToolTipManager.getInstance().registerTooltip(datePicker.getEditor());

            requestContainerUpdate();
        }
    }

    protected void updateInstance() {
        if (updatingInstance) {
            return;
        }

        updatingInstance = true;
        try {
            if (datasource != null && metaPropertyPath != null) {
                Date value = constructDate();
                if (Objects.equals(prevValue, value)) {
                    valid = true;
                    return;
                }
                setValueToDs(value);
            }
            valid = true;
        } catch (RuntimeException e) {
            valid = false;
        } finally {
            updatingInstance = false;
        }
        if (valid) {
            Object newValue = getValue();
            fireChangeListeners(newValue);
        }
    }

    protected void updateInstance(Date value) {
        if (updatingInstance) {
            return;
        }

        updatingInstance = true;
        try {
            if (datasource != null && metaPropertyPath != null) {
                setValueToDs(value);
            }
            valid = true;
        } catch (RuntimeException e) {
            valid = false;
        } finally {
            updatingInstance = false;
        }
    }

    protected void setValueToDs(Date value) {
        if (datasource.getItem() != null) {
            Object obj = value;
            Datatype<Object> datatype = metaProperty.getRange().asDatatype();
            if (!datatype.getJavaClass().equals(Date.class)) {
                String str = Datatypes.getNN(Date.class).format(value);
                try {
                    obj = datatype.parse(str);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }
            InstanceUtils.setValueEx(datasource.getItem(), metaPropertyPath.getPath(), obj);
        }
    }

    protected Date constructDate() {
        final Date datePickerDate = datePicker.getDate();
        if (datePickerDate == null) {
            return null;
        }

        Calendar dateCalendar = Calendar.getInstance(userSession.getLocale());
        if (timeZone != null) {
            dateCalendar.setTimeZone(timeZone);
        }
        dateCalendar.setTime(datePickerDate);
        if (timeField.getValue() == null) {
            dateCalendar.set(Calendar.HOUR_OF_DAY, 0);
            dateCalendar.set(Calendar.MINUTE, 0);
            dateCalendar.set(Calendar.SECOND, 0);
        } else {
            Calendar timeCalendar = Calendar.getInstance(userSession.getLocale());
            timeCalendar.setTime(timeField.<Date>getValue());

            dateCalendar.set(Calendar.HOUR_OF_DAY, timeCalendar.get(Calendar.HOUR_OF_DAY));
            dateCalendar.set(Calendar.MINUTE, timeCalendar.get(Calendar.MINUTE));
            dateCalendar.set(Calendar.SECOND, timeCalendar.get(Calendar.SECOND));
        }

        //noinspection UnnecessaryLocalVariable
        return dateCalendar.getTime();
    }

    protected Date extractTime(Date date) {
        Calendar dateCalendar = Calendar.getInstance(userSession.getLocale());
        if (timeZone != null) {
            dateCalendar.setTimeZone(timeZone);
        }
        dateCalendar.setTime(date);

        Calendar timeCalendar = Calendar.getInstance(userSession.getLocale());
        timeCalendar.setTimeInMillis(0);

        timeCalendar.set(Calendar.HOUR_OF_DAY, dateCalendar.get(Calendar.HOUR_OF_DAY));
        timeCalendar.set(Calendar.MINUTE, dateCalendar.get(Calendar.MINUTE));
        timeCalendar.set(Calendar.SECOND, dateCalendar.get(Calendar.SECOND));

        return timeCalendar.getTime();
    }

    protected boolean isHourUsed() {
        return resolution != null && resolution.ordinal() <= Resolution.HOUR.ordinal();
    }

    protected boolean isMinUsed() {
        return resolution != null && resolution.ordinal() <= Resolution.MIN.ordinal();
    }

    public JXDatePicker getDatePicker() {
        return datePicker;
    }

    public DesktopTimeField getTimeField() {
        return timeField;
    }

    @Override
    public void updateMissingValueState() {
        boolean value = required && isEditableWithParent() && datePicker.getEditor().getValue() == null;
        decorateMissingValue(datePicker.getEditor(), value);
        if (isHourUsed()) {
            decorateMissingValue(timeField.getImpl(), value);
            timeField.getImpl().repaint();
        }
    }

    protected void flush() {
        if (isEditable() && isEnabled()) {
            try {
                datePicker.getEditor().commitEdit();
            } catch (ParseException e) {
                return;
            }

            updateInstance();
            updateMissingValueState();
        }
    }

    protected Dimension getDefaultDimension() {
        UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
        if (lafDefaults.getDimension("DateField.dimension") != null) { // take it from desktop theme
            return lafDefaults.getDimension("DateField.dimension");
        }
        return new Dimension(110, DesktopComponentsHelper.FIELD_HEIGHT);
    }

    @Override
    public void commit() {
        // do nothing
    }

    @Override
    public void discard() {
        // do nothing
    }

    @Override
    public boolean isBuffered() {
        // do nothing
        return false;
    }

    @Override
    public void setBuffered(boolean buffered) {
        // do nothing
    }

    @Override
    public boolean isModified() {
        // do nothing
        return false;
    }

    public class FocusableComposition extends JPanel implements FocusableComponent, Flushable {

        @Override
        public void focus() {
            DesktopDateField.this.datePicker.requestFocus();
        }

        @Override
        public void flushValue() {
            flush();
        }
    }

    public class FlushableDatePicker extends DatePicker implements Flushable {

        @Override
        public void flushValue() {
            flush();
        }
    }
}