001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.component;
020
021import icy.util.StringUtil;
022
023import java.awt.event.ActionEvent;
024import java.awt.event.ActionListener;
025import java.awt.event.FocusEvent;
026import java.awt.event.FocusListener;
027import java.awt.event.KeyEvent;
028import java.text.Format;
029import java.util.EventListener;
030
031import javax.swing.JFormattedTextField;
032import javax.swing.event.DocumentEvent;
033import javax.swing.event.DocumentListener;
034
035/**
036 * IcyTextField extends JFormattedTextField and provide easier text change handling.
037 * 
038 * @author Stephane
039 */
040public class IcyTextField extends JFormattedTextField implements DocumentListener, ActionListener, FocusListener
041{
042    /**
043     * 
044     */
045    private static final long serialVersionUID = 4294607311366304781L;
046
047    public interface TextChangeListener extends EventListener
048    {
049        public void textChanged(IcyTextField source, boolean validate);
050    }
051
052    // internals
053    /**
054     * @deprecated Don't use this property.
055     */
056    @Deprecated
057    protected boolean consumeCharKeyPressEvent;
058    protected boolean changed;
059
060    /**
061     * Creates a <code>IcyTextField</code> with no <code>AbstractFormatterFactory</code>. Use
062     * <code>setMask</code> or <code>setFormatterFactory</code> to configure the
063     * <code>JFormattedTextField</code> to edit a particular type of
064     * value.
065     */
066    public IcyTextField()
067    {
068        super();
069
070        init();
071    }
072
073    /**
074     * Creates a <code>IcyTextField</code> with the specified <code>AbstractFormatter</code>. The
075     * <code>AbstractFormatter</code> is placed in an <code>AbstractFormatterFactory</code>.
076     * 
077     * @param formatter
078     *        AbstractFormatter to use for formatting.
079     */
080    public IcyTextField(AbstractFormatter formatter)
081    {
082        super(formatter);
083
084        init();
085    }
086
087    /**
088     * Creates a <code>IcyTextField</code>. <code>format</code> is
089     * wrapped in an appropriate <code>AbstractFormatter</code> which is
090     * then wrapped in an <code>AbstractFormatterFactory</code>.
091     * 
092     * @param format
093     *        Format used to look up an AbstractFormatter
094     */
095    public IcyTextField(Format format)
096    {
097        super(format);
098
099        init();
100    }
101
102    /**
103     * Creates a IcyTextField with the specified value. This will
104     * create an <code>AbstractFormatterFactory</code> based on the
105     * type of <code>value</code>.
106     * 
107     * @param value
108     *        Initial value for the IcyTextField
109     */
110    public IcyTextField(Object value)
111    {
112        super(value);
113
114        init();
115    }
116
117    protected void init()
118    {
119        changed = false;
120        consumeCharKeyPressEvent = false;
121
122        getDocument().addDocumentListener(this);
123        addActionListener(this);
124        addFocusListener(this);
125    }
126
127    protected void internalTextChanged(boolean validate)
128    {
129        // simple text change
130        if (!validate)
131        {
132            // keep mark of text change
133            changed = true;
134            textChanged(false);
135        }
136        else
137        {
138            // previous text change
139            if (changed)
140            {
141                textChanged(true);
142                changed = false;
143            }
144        }
145    }
146
147    protected void textChanged(boolean validate)
148    {
149        fireTextChanged(validate);
150    }
151
152    protected void fireTextChanged(boolean validate)
153    {
154        for (TextChangeListener listener : listenerList.getListeners(TextChangeListener.class))
155            listener.textChanged(this, validate);
156    }
157
158    /**
159     * Force the field to pass to unchanged state (after a {@link #setText(String)} for instance)<br>
160     * so it won't generate further <code>textChanged</code> event.
161     */
162    public void setUnchanged()
163    {
164        changed = false;
165    }
166
167    public void addTextChangeListener(TextChangeListener listener)
168    {
169        listenerList.add(TextChangeListener.class, listener);
170    }
171
172    public void removeTextChangeListener(TextChangeListener listener)
173    {
174        listenerList.remove(TextChangeListener.class, listener);
175    }
176
177    /**
178     * @deprecated Should not be used (keep it to false)
179     */
180    @Deprecated
181    public void setConsumeCharKeyPressEvent(boolean consumeCharKeyPressEvent)
182    {
183        this.consumeCharKeyPressEvent = consumeCharKeyPressEvent;
184    }
185
186    /**
187     * @deprecated Should not be used.
188     */
189    @Deprecated
190    public boolean getConsumeCharKeyPressEvent()
191    {
192        return consumeCharKeyPressEvent;
193    }
194
195    @Override
196    protected void processComponentKeyEvent(KeyEvent e)
197    {
198        super.processComponentKeyEvent(e);
199
200        if (consumeCharKeyPressEvent)
201        {
202            final char c = e.getKeyChar();
203
204            // consume KEY_PRESSED character event
205            if ((e.getID() == KeyEvent.KEY_PRESSED) && Character.isDefined(c) && !Character.isISOControl(c))
206                e.consume();
207        }
208    }
209
210    @Override
211    public void setText(String t)
212    {
213        if (StringUtil.equals(t, getText(), false))
214            return;
215
216        super.setText(t);
217
218        // validate change
219        internalTextChanged(true);
220    }
221
222    @Override
223    public void changedUpdate(DocumentEvent e)
224    {
225        internalTextChanged(false);
226    }
227
228    @Override
229    public void insertUpdate(DocumentEvent e)
230    {
231        internalTextChanged(false);
232    }
233
234    @Override
235    public void removeUpdate(DocumentEvent e)
236    {
237        internalTextChanged(false);
238    }
239
240    @Override
241    public void actionPerformed(ActionEvent e)
242    {
243        internalTextChanged(true);
244    }
245
246    @Override
247    public void focusGained(FocusEvent e)
248    {
249        // nothing to do here
250    }
251
252    @Override
253    public void focusLost(FocusEvent e)
254    {
255        internalTextChanged(true);
256    }
257}