edu.wisc.ssec.mcidasv.ui.JCalendarDateEditor.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.ssec.mcidasv.ui.JCalendarDateEditor.java

Source

/*
 * This file is part of McIDAS-V
 *
 * Copyright 2007-2019
 * Space Science and Engineering Center (SSEC)
 * University of Wisconsin - Madison
 * 1225 W. Dayton Street, Madison, WI 53706, USA
 * http://www.ssec.wisc.edu/mcidas
 * 
 * All Rights Reserved
 * 
 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
 * some McIDAS-V source code is based on IDV and VisAD source code.  
 * 
 * McIDAS-V is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * McIDAS-V is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 */

package edu.wisc.ssec.mcidasv.ui;

import static java.text.DateFormat.getDateInstance;
import static javax.swing.UIManager.getColor;

import com.toedter.calendar.DateUtil;
import com.toedter.calendar.IDateEditor;

import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;

import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.MaskFormatter;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 * This class is just a {@link com.toedter.calendar.JTextFieldDateEditor} that
 * allows the user to enter either the day within (current) year or a
 * McIDAS-X style {@literal "julian day"} ({@code YYYYDDD} or {@code YYDDD}),
 * in addition to the formatting allowed by {@code JTextFieldDateEditor}.
 */
public class JCalendarDateEditor extends JFormattedTextField
        implements IDateEditor, CaretListener, FocusListener, ActionListener {

    private static final long serialVersionUID = 1L;

    /** Match day of year. */
    private static final Pattern dayOnly = Pattern.compile("\\d{1,3}");

    /** Match {@code YYYYDDD}. */
    private static final Pattern yearDay = Pattern.compile("\\d{7}");

    /** Match {@code YYDDD} dates. */
    private static final Pattern badYearDay = Pattern.compile("\\d{5}");

    private static final Logger logger = LoggerFactory.getLogger(JCalendarDateEditor.class);

    protected Date date;

    protected SimpleDateFormat dateFormatter;

    /** Parse {@code DDD} dates (even if they are one or two digits). */
    private final SimpleDateFormat dayOfYear;

    /** Parse {@code YYYYDDD} dates. */
    private final SimpleDateFormat yearAndDay;

    /** Parse {@code YYDDD} dates. */
    private final SimpleDateFormat badYearAndDay;

    protected MaskFormatter maskFormatter;

    protected String datePattern;

    protected String maskPattern;

    protected char placeholder;

    protected Color darkGreen;

    protected DateUtil dateUtil;

    private boolean isMaskVisible;

    private boolean ignoreDatePatternChange;

    private int hours;

    private int minutes;

    private int seconds;

    private int millis;

    private Calendar calendar;

    public JCalendarDateEditor() {
        this(false, null, null, ' ');
    }

    public JCalendarDateEditor(String datePattern, String maskPattern, char placeholder) {
        this(true, datePattern, maskPattern, placeholder);
    }

    public JCalendarDateEditor(boolean showMask, String datePattern, String maskPattern, char placeholder) {
        dateFormatter = (SimpleDateFormat) getDateInstance(DateFormat.MEDIUM);
        dayOfYear = new SimpleDateFormat("DDD");
        yearAndDay = new SimpleDateFormat("yyyyDDD");
        badYearAndDay = new SimpleDateFormat("yyDDD");
        dateFormatter.setLenient(false);
        dayOfYear.setLenient(false);
        yearAndDay.setLenient(false);

        setDateFormatString(datePattern);
        if (datePattern != null) {
            ignoreDatePatternChange = true;
        }

        this.placeholder = placeholder;

        if (maskPattern == null) {
            this.maskPattern = createMaskFromDatePattern(this.datePattern);
        } else {
            this.maskPattern = maskPattern;
        }

        setToolTipText(this.datePattern);
        setMaskVisible(showMask);

        addCaretListener(this);
        addFocusListener(this);
        addActionListener(this);
        darkGreen = new Color(0, 150, 0);

        calendar = Calendar.getInstance();

        dateUtil = new DateUtil();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#getDate()
     */
    @Override
    public Date getDate() {
        try {
            calendar.setTime(dateFormatter.parse(getText()));
            calendar.set(Calendar.HOUR_OF_DAY, hours);
            calendar.set(Calendar.MINUTE, minutes);
            calendar.set(Calendar.SECOND, seconds);
            calendar.set(Calendar.MILLISECOND, millis);
            date = calendar.getTime();
        } catch (ParseException e) {
            date = null;
        }
        return date;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#setDate(java.util.Date)
     */
    @Override
    public void setDate(Date date) {
        setDate(date, true);
    }

    /**
     * Sets the date.
     *
     * @param date the date
     * @param firePropertyChange true, if the date property should be fired.
     */
    protected void setDate(Date date, boolean firePropertyChange) {
        Date oldDate = this.date;
        this.date = date;

        if (date == null) {
            setText("");
        } else {
            calendar.setTime(date);
            hours = calendar.get(Calendar.HOUR_OF_DAY);
            minutes = calendar.get(Calendar.MINUTE);
            seconds = calendar.get(Calendar.SECOND);
            millis = calendar.get(Calendar.MILLISECOND);

            String formattedDate = dateFormatter.format(date);
            try {
                setText(formattedDate);
            } catch (RuntimeException e) {
                logger.debug("could not set text: {}", e);
            }
        }
        if ((date != null) && dateUtil.checkDate(date)) {
            setForeground(Color.BLACK);
        }

        if (firePropertyChange) {
            firePropertyChange("date", oldDate, date);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#setDateFormatString(java.lang.String)
     */
    @Override
    public void setDateFormatString(String dateFormatString) {
        if (!ignoreDatePatternChange) {
            try {
                dateFormatter.applyPattern(dateFormatString);
            } catch (RuntimeException e) {
                dateFormatter = (SimpleDateFormat) getDateInstance(DateFormat.MEDIUM);
                dateFormatter.setLenient(false);
            }
            this.datePattern = dateFormatter.toPattern();
            setToolTipText(this.datePattern);
            setDate(date, false);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#getDateFormatString()
     */
    @Override
    public String getDateFormatString() {
        return datePattern;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#getUiComponent()
     */
    @Override
    public JComponent getUiComponent() {
        return this;
    }

    private Date attemptParsing(String text) {
        Date result = null;
        try {
            if (dayOnly.matcher(text).matches()) {
                String full = LocalDate.now().getYear() + text;
                result = yearAndDay.parse(full);
            } else if (yearDay.matcher(text).matches()) {
                result = yearAndDay.parse(text);
            } else if (badYearDay.matcher(text).matches()) {
                result = badYearAndDay.parse(text);
            } else {
                result = dateFormatter.parse(text);
            }
        } catch (Exception e) {
            logger.trace("failed to parse '{}'", text);
        }
        return result;
    }

    /**
     * After any user input, the value of the textfield is proofed. Depending on
     * being a valid date, the value is colored green or red.
     *
     * @param event Caret event.
     */
    @Override
    public void caretUpdate(CaretEvent event) {
        String text = getText().trim();
        String emptyMask = maskPattern.replace('#', placeholder);

        if (text.isEmpty() || text.equals(emptyMask)) {
            setForeground(Color.BLACK);
            return;
        }

        Date parsed = attemptParsing(this.getText());
        if ((parsed != null) && dateUtil.checkDate(parsed)) {
            this.setForeground(this.darkGreen);
        } else {
            this.setForeground(Color.RED);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
     */
    @Override
    public void focusLost(FocusEvent focusEvent) {
        checkText();
    }

    private void checkText() {
        Date parsedDate = attemptParsing(this.getText());
        if (parsedDate != null) {
            this.setDate(parsedDate, true);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
     */
    @Override
    public void focusGained(FocusEvent e) {
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.Component#setLocale(java.util.Locale)
     */
    @Override
    public void setLocale(Locale locale) {
        if (!locale.equals(getLocale()) || ignoreDatePatternChange) {
            super.setLocale(locale);
            dateFormatter = (SimpleDateFormat) getDateInstance(DateFormat.MEDIUM, locale);
            setToolTipText(dateFormatter.toPattern());
            setDate(date, false);
            doLayout();
        }
    }

    /**
     * Creates a mask from a date pattern. This is a very simple (and
     * incomplete) implementation thet works only with numbers. A date pattern
     * of {@literal "MM/dd/yy"} will result in the mask "##/##/##". Probably
     * you want to override this method if it does not fit your needs.
     *
     * @param datePattern Date pattern.
     * @return the mask
     */
    public String createMaskFromDatePattern(String datePattern) {
        String symbols = "GyMdkHmsSEDFwWahKzZ";
        StringBuilder maskBuffer = new StringBuilder(datePattern.length() * 2);
        for (int i = 0; i < datePattern.length(); i++) {
            char ch = datePattern.charAt(i);
            boolean symbolFound = false;
            for (int n = 0; n < symbols.length(); n++) {
                if (symbols.charAt(n) == ch) {
                    maskBuffer.append('#');
                    symbolFound = true;
                    break;
                }
            }
            if (!symbolFound) {
                maskBuffer.append(ch);
            }
        }
        return maskBuffer.toString();
    }

    /**
     * Returns {@code true}, if the mask is visible.
     *
     * @return {@code true}, if the mask is visible.
     */
    public boolean isMaskVisible() {
        return isMaskVisible;
    }

    /**
     * Sets the mask visible.
     *
     * @param isMaskVisible Whether or not the mask should be visible.
     */
    public void setMaskVisible(boolean isMaskVisible) {
        this.isMaskVisible = isMaskVisible;
        if (isMaskVisible) {
            if (maskFormatter == null) {
                try {
                    String mask = createMaskFromDatePattern(datePattern);
                    maskFormatter = new MaskFormatter(mask);
                    maskFormatter.setPlaceholderCharacter(this.placeholder);
                    maskFormatter.install(this);
                } catch (ParseException e) {
                    logger.debug("parsing error: {}", e);
                }
            }
        }
    }

    /**
     * Returns the preferred size, enough to accommodate longest date someone could enter
     */

    @Override
    public Dimension getPreferredSize() {

        // TJJ May 2018 
        // This didn't seem to work in all cases. So let's set the preferred size
        // to a dimension that will accommodate the longest date someone could enter
        Dimension d = new JTextField("September 28, 1999").getPreferredSize();
        return d;

    }

    /**
     * Validates the typed date and sets it (only if it is valid).
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        checkText();
    }

    /**
     * Enables and disabled the compoment. It also fixes the background bug
     * 4991597 and sets the background explicitely to a
     * TextField.inactiveBackground.
     */
    @Override
    public void setEnabled(boolean b) {
        super.setEnabled(b);
        if (!b) {
            super.setBackground(getColor("TextField.inactiveBackground"));
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#getMaxSelectableDate()
     */
    @Override
    public Date getMaxSelectableDate() {
        return dateUtil.getMaxSelectableDate();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#getMinSelectableDate()
     */
    @Override
    public Date getMinSelectableDate() {
        return dateUtil.getMinSelectableDate();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#setMaxSelectableDate(java.util.Date)
     */
    @Override
    public void setMaxSelectableDate(Date max) {
        dateUtil.setMaxSelectableDate(max);
        checkText();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#setMinSelectableDate(java.util.Date)
     */
    @Override
    public void setMinSelectableDate(Date min) {
        dateUtil.setMinSelectableDate(min);
        checkText();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.toedter.calendar.IDateEditor#setSelectableDateRange(java.util.Date,java.util.Date)
     */
    @Override
    public void setSelectableDateRange(Date min, Date max) {
        dateUtil.setSelectableDateRange(min, max);
        checkText();
    }
}