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

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.desktop.gui.components.DesktopTimeField.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.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.global.AppBeans;
import com.haulmont.cuba.core.global.Messages;
import com.haulmont.cuba.core.global.UserSessionSource;
import com.haulmont.cuba.desktop.gui.executors.impl.DesktopBackgroundWorker;
import com.haulmont.cuba.desktop.sys.DesktopToolTipManager;
import com.haulmont.cuba.desktop.sys.vcl.Flushable;
import com.haulmont.cuba.gui.components.DateField;
import com.haulmont.cuba.gui.components.TimeField;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.impl.WeakItemChangeListener;
import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.text.MaskFormatter;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

public class DesktopTimeField extends DesktopAbstractField<JFormattedTextField> implements TimeField {

    private final Logger log = LoggerFactory.getLogger(DesktopTimeField.class);

    private boolean showSeconds;
    private String timeFormat;
    private String mask;
    private MaskFormatter formatter;
    private boolean updatingInstance;
    private Datasource datasource;
    private Object prevValue;
    private DateField.Resolution resolution;

    protected static final int DEFAULT_DIGIT_WIDTH = 23;
    private int digitWidth;

    protected Datasource.ItemChangeListener itemChangeListener;
    protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener;

    public DesktopTimeField() {
        UserSessionSource uss = AppBeans.get(UserSessionSource.NAME);

        digitWidth = getDigitWidth();

        timeFormat = Datatypes.getFormatStringsNN(uss.getLocale()).getTimeFormat();
        resolution = DateField.Resolution.MIN;
        formatter = new MaskFormatter();
        formatter.setPlaceholderCharacter('_');
        impl = new FlushableFormattedTextField(formatter);
        FieldListener listener = new FieldListener();
        impl.addFocusListener(listener);
        impl.addKeyListener(listener);

        setShowSeconds(timeFormat.contains("ss"));
    }

    @Override
    public boolean getShowSeconds() {
        return showSeconds;
    }

    private Object validateRawValue(String value) throws NumberFormatException, ParseException {
        if (value.equals(StringUtils.replaceChars(mask, "#U", "__"))) {
            return null;
        }

        SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);
        sdf.setLenient(false);
        try {
            Date parsedValue = sdf.parse(value);

            Class targetType = null;
            if (datasource != null && metaPropertyPath != null) {
                targetType = metaPropertyPath.getRangeJavaClass();
            }

            if (targetType == java.sql.Time.class) {
                return new Time(parsedValue.getTime());
            }
            if (targetType == java.sql.Date.class) {
                log.warn("Do not use java.sql.Date with time field");
                return new java.sql.Date(parsedValue.getTime());
            }

            return parsedValue;
        } catch (ParseException e) {
            showValidationMessage();
            return prevValue;
        }
    }

    private void showValidationMessage() {
        Messages messages = AppBeans.get(Messages.NAME);
        DesktopComponentsHelper.getTopLevelFrame(this).showNotification(messages.getMainMessage("validationFail"),
                com.haulmont.cuba.gui.components.Frame.NotificationType.TRAY);
    }

    public void setResolution(DateField.Resolution resolution) {
        this.resolution = resolution;
        if (resolution.ordinal() <= DateField.Resolution.SEC.ordinal()) {
            setShowSeconds(true);
        } else if (resolution.ordinal() <= DateField.Resolution.MIN.ordinal()) {
            setShowSeconds(false);
        } else if (resolution.ordinal() <= DateField.Resolution.HOUR.ordinal()) {
            StringBuilder builder = new StringBuilder(timeFormat);
            if (timeFormat.contains("mm")) {
                int minutesIndex = builder.indexOf("mm");
                builder.delete(minutesIndex > 0 ? --minutesIndex : minutesIndex, minutesIndex + 3);
                timeFormat = builder.toString();
            }
            setShowSeconds(false);
        }
    }

    public DateField.Resolution getResolution() {
        return resolution;
    }

    @Override
    public void setShowSeconds(boolean showSeconds) {
        this.showSeconds = showSeconds;
        if (showSeconds) {
            if (!timeFormat.contains("ss")) {
                int minutesIndex = timeFormat.indexOf("mm");
                StringBuilder builder = new StringBuilder(timeFormat);
                builder.insert(minutesIndex + 2, ":ss");
                timeFormat = builder.toString();
            }
        } else {
            if (timeFormat.contains("ss")) {
                int secondsIndex = timeFormat.indexOf("ss");
                StringBuilder builder = new StringBuilder(timeFormat);
                builder.delete(secondsIndex > 0 ? --secondsIndex : secondsIndex, secondsIndex + 3);
                timeFormat = builder.toString();
            }
        }
        updateTimeFormat();
        updateWidth();
    }

    private void updateTimeFormat() {
        mask = StringUtils.replaceChars(timeFormat, "Hhmsa", "####U");
        try {
            formatter.setMask(mask);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        impl.setValue(impl.getValue());
    }

    @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);

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

        itemPropertyChangeListener = e -> {
            if (updatingInstance) {
                return;
            }
            if (e.getProperty().equals(metaPropertyPath.toString())) {
                updateComponent(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 = InstanceUtils.getValueEx(datasource.getItem(), metaPropertyPath.getPath());
                updateComponent(value);
                fireChangeListeners(value);
            }
        }

        initRequired(metaPropertyPath);

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

        initBeanValidator();
    }

    @Override
    public void setFormat(String timeFormat) {
        this.timeFormat = timeFormat;
        showSeconds = timeFormat.contains("ss");
        updateTimeFormat();
        updateWidth();
    }

    @Override
    public String getFormat() {
        return timeFormat;
    }

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

        requestContainerUpdate();
    }

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

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

            requestContainerUpdate();
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Date getValue() {
        try {
            return new SimpleDateFormat(timeFormat).parse(impl.getText());
        } catch (ParseException e) {
            return null;
        }
    }

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

        if (!Objects.equals(prevValue, value)) {
            updateInstance(value);
            updateComponent(value);
            fireChangeListeners(value);
        }
    }

    protected void setValueInternal(Object value) {
        boolean editable = isEditable();
        setEditable(true);

        setValue(value);

        setEditable(editable);
    }

    protected void updateComponent(Object value) {
        updatingInstance = true;
        if (value == null) {
            impl.setValue("");
            updatingInstance = false;
        } else {
            try {
                if (value instanceof Date) {
                    SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);
                    impl.setValue(sdf.format(value));
                }
            } finally {
                updatingInstance = false;
            }
        }
        updateMissingValueState();
    }

    protected void updateInstance(Object value) {
        if (updatingInstance)
            return;

        if (Objects.equals(prevValue, value))
            return;

        updatingInstance = true;
        try {
            if (datasource != null && metaProperty != null && datasource.getItem() != null)
                InstanceUtils.setValueEx(datasource.getItem(), metaPropertyPath.getPath(), value);
        } finally {
            updatingInstance = false;
        }
    }

    protected void updateWidth() {
        int width = isAmPmUsed() ? digitWidth : 0;
        if (showSeconds) {
            width = width + digitWidth;
        }
        int height = impl.getPreferredSize().height;

        switch (resolution) {
        case HOUR:
            impl.setMinimumSize(new Dimension(digitWidth + width, height));
            break;
        case MIN:
        case SEC:
            impl.setMinimumSize(new Dimension(digitWidth * 2 + width, height));
        }
    }

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

    @Override
    protected void setEditableToComponent(boolean editable) {
        impl.setEditable(editable);

        updateMissingValueState();
    }

    @Override
    public void updateMissingValueState() {
        Object implValue = impl.getValue();
        boolean value = required && isEditableWithParent()
                && (implValue == null || implValue instanceof String && StringUtils.isBlank((String) implValue));
        decorateMissingValue(impl, value);
    }

    public boolean isAmPmUsed() {
        return timeFormat.contains("a");
    }

    @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;
    }

    protected class FieldListener implements FocusListener, KeyListener {
        private static final int ENTER_CODE = 10;

        @Override
        public void focusGained(FocusEvent e) {
        }

        @Override
        public void focusLost(FocusEvent e) {
            fireEvent();
        }

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
            if (ENTER_CODE == e.getKeyCode())
                fireEvent();
        }

        @Override
        public void keyReleased(KeyEvent e) {
        }

        private void fireEvent() {
            flush();
        }
    }

    protected void flush() {
        if (isEditable() && isEnabled()) {
            Object newValue;
            try {
                newValue = validateRawValue(getImpl().getText());
            } catch (Exception e) {
                showValidationMessage();
                newValue = prevValue;
            }
            if ("".equals(newValue))
                newValue = null;

            if (!Objects.equals(prevValue, newValue))
                setValue(newValue);
            else
                updateComponent(newValue);
        }
    }

    protected class FlushableFormattedTextField extends JFormattedTextField implements Flushable {

        public FlushableFormattedTextField(MaskFormatter formatter) {
            super(formatter);
        }

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

    protected int getDigitWidth() {
        UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
        if (lafDefaults.getDimension("TimeField.digitWidth") != null) { // take it from desktop theme
            return (int) lafDefaults.getDimension("TimeField.digitWidth").getWidth();
        }
        return DEFAULT_DIGIT_WIDTH;
    }
}