/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.gui.component;

import icy.util.StringUtil;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.text.Format;
import java.util.EventListener;

import javax.swing.JFormattedTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * IcyTextField extends JFormattedTextField and provide easier text change handling.
 * 
 * @author Stephane
 */
public class IcyTextField extends JFormattedTextField implements DocumentListener, ActionListener, FocusListener
{
    /**
     * 
     */
    private static final long serialVersionUID = 4294607311366304781L;

    public interface TextChangeListener extends EventListener
    {
        public void textChanged(IcyTextField source, boolean validate);
    }

    // internals
    /**
     * @deprecated Don't use this property.
     */
    @Deprecated
    protected boolean consumeCharKeyPressEvent;
    protected boolean changed;

    /**
     * Creates a <code>IcyTextField</code> with no <code>AbstractFormatterFactory</code>. Use
     * <code>setMask</code> or <code>setFormatterFactory</code> to configure the
     * <code>JFormattedTextField</code> to edit a particular type of
     * value.
     */
    public IcyTextField()
    {
        super();

        init();
    }

    /**
     * Creates a <code>IcyTextField</code> with the specified <code>AbstractFormatter</code>. The
     * <code>AbstractFormatter</code> is placed in an <code>AbstractFormatterFactory</code>.
     * 
     * @param formatter
     *        AbstractFormatter to use for formatting.
     */
    public IcyTextField(AbstractFormatter formatter)
    {
        super(formatter);

        init();
    }

    /**
     * Creates a <code>IcyTextField</code>. <code>format</code> is
     * wrapped in an appropriate <code>AbstractFormatter</code> which is
     * then wrapped in an <code>AbstractFormatterFactory</code>.
     * 
     * @param format
     *        Format used to look up an AbstractFormatter
     */
    public IcyTextField(Format format)
    {
        super(format);

        init();
    }

    /**
     * Creates a IcyTextField with the specified value. This will
     * create an <code>AbstractFormatterFactory</code> based on the
     * type of <code>value</code>.
     * 
     * @param value
     *        Initial value for the IcyTextField
     */
    public IcyTextField(Object value)
    {
        super(value);

        init();
    }

    protected void init()
    {
        changed = false;
        consumeCharKeyPressEvent = false;

        getDocument().addDocumentListener(this);
        addActionListener(this);
        addFocusListener(this);
    }

    protected void internalTextChanged(boolean validate)
    {
        // simple text change
        if (!validate)
        {
            // keep mark of text change
            changed = true;
            textChanged(false);
        }
        else
        {
            // previous text change
            if (changed)
            {
                textChanged(true);
                changed = false;
            }
        }
    }

    protected void textChanged(boolean validate)
    {
        fireTextChanged(validate);
    }

    protected void fireTextChanged(boolean validate)
    {
        for (TextChangeListener listener : listenerList.getListeners(TextChangeListener.class))
            listener.textChanged(this, validate);
    }

    /**
     * Force the field to pass to unchanged state (after a {@link #setText(String)} for instance)<br>
     * so it won't generate further <code>textChanged</code> event.
     */
    public void setUnchanged()
    {
        changed = false;
    }

    public void addTextChangeListener(TextChangeListener listener)
    {
        listenerList.add(TextChangeListener.class, listener);
    }

    public void removeTextChangeListener(TextChangeListener listener)
    {
        listenerList.remove(TextChangeListener.class, listener);
    }

    /**
     * @deprecated Should not be used (keep it to false)
     */
    @Deprecated
    public void setConsumeCharKeyPressEvent(boolean consumeCharKeyPressEvent)
    {
        this.consumeCharKeyPressEvent = consumeCharKeyPressEvent;
    }

    /**
     * @deprecated Should not be used.
     */
    @Deprecated
    public boolean getConsumeCharKeyPressEvent()
    {
        return consumeCharKeyPressEvent;
    }

    @Override
    protected void processComponentKeyEvent(KeyEvent e)
    {
        super.processComponentKeyEvent(e);

        if (consumeCharKeyPressEvent)
        {
            final char c = e.getKeyChar();

            // consume KEY_PRESSED character event
            if ((e.getID() == KeyEvent.KEY_PRESSED) && Character.isDefined(c) && !Character.isISOControl(c))
                e.consume();
        }
    }

    @Override
    public void setText(String t)
    {
        if (StringUtil.equals(t, getText(), false))
            return;

        super.setText(t);

        // validate change
        internalTextChanged(true);
    }

    @Override
    public void changedUpdate(DocumentEvent e)
    {
        internalTextChanged(false);
    }

    @Override
    public void insertUpdate(DocumentEvent e)
    {
        internalTextChanged(false);
    }

    @Override
    public void removeUpdate(DocumentEvent e)
    {
        internalTextChanged(false);
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        internalTextChanged(true);
    }

    @Override
    public void focusGained(FocusEvent e)
    {
        // nothing to do here
    }

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