001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset.query;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.time.LocalTime;
007import java.time.ZoneId;
008import java.time.ZonedDateTime;
009import java.time.format.DateTimeFormatter;
010import java.time.format.DateTimeParseException;
011import java.time.format.FormatStyle;
012
013import javax.swing.text.JTextComponent;
014
015import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
016import org.openstreetmap.josm.tools.Logging;
017
018/**
019 * Validates time values entered as text in a {@link JTextComponent}. Validates the input
020 * on the fly and gives feedback about whether the time value is valid or not.
021 *
022 * Time values can be entered in one of four standard formats defined for the current locale.
023 * @since 11326 (extracted from AdvancedChangesetQueryPanel)
024 */
025public class TimeValidator extends AbstractTextComponentValidator {
026
027    /**
028     * Constructs a new {@code TimeValidator} for the given text component.
029     * @param tc text component
030     */
031    public TimeValidator(JTextComponent tc) {
032        super(tc);
033    }
034
035    /**
036     * Decorates the given text component.
037     * @param tc text component to decorate
038     * @return new time validator attached to {@code tc}
039     */
040    public static TimeValidator decorate(JTextComponent tc) {
041        return new TimeValidator(tc);
042    }
043
044    @Override
045    public boolean isValid() {
046        if (getComponent().getText().trim().isEmpty())
047            return true;
048        return getTime() != null;
049    }
050
051    /**
052     * Returns the standard tooltip text as HTML.
053     * @return the standard tooltip text as HTML
054     */
055    public String getStandardTooltipTextAsHtml() {
056        return "<html>" + getStandardTooltipText() + "</html>";
057    }
058
059    /**
060     * Returns the standard tooltip text.
061     * @return the standard tooltip text
062     */
063    public String getStandardTooltipText() {
064        final ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
065        return tr(
066                "Please enter a valid time in the usual format for your locale.<br>"
067                + "Example: {0}<br>"
068                + "Example: {1}<br>"
069                + "Example: {2}<br>"
070                + "Example: {3}<br>",
071                DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(now),
072                DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).format(now),
073                DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(now),
074                DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).format(now)
075        );
076    }
077
078    @Override
079    public void validate() {
080        if (!isValid()) {
081            String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>";
082            feedbackInvalid(msg);
083        } else {
084            String msg = "<html>" + getStandardTooltipText() + "</html>";
085            feedbackValid(msg);
086        }
087    }
088
089    /**
090     * Returns the time.
091     * @return the time
092     */
093    public LocalTime getTime() {
094        if (getComponent().getText().trim().isEmpty())
095            return LocalTime.MIDNIGHT;
096
097        for (final FormatStyle format: FormatStyle.values()) {
098            DateTimeFormatter df = DateTimeFormatter.ofLocalizedTime(format);
099            try {
100                return LocalTime.parse(getComponent().getText(), df);
101            } catch (DateTimeParseException e) {
102                // Try next format
103                Logging.trace(e);
104            }
105        }
106        return LocalTime.MIDNIGHT;
107    }
108}