JNullableTextField.java
001 /*
002  *  Copyright (c) 1995-2011, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009 
010  *  Valentin Tablan, 15 Apr 2011
011  *
012  *  $Id: JNullableTextField.java 17530 2014-03-04 15:57:43Z markagreenwood $
013  */
014 package gate.gui.annedit;
015 
016 import gate.gui.MainFrame;
017 
018 import java.awt.Color;
019 import java.awt.event.ActionEvent;
020 import java.beans.PropertyChangeEvent;
021 import java.beans.PropertyChangeListener;
022 import java.util.Collections;
023 import java.util.HashSet;
024 import java.util.Set;
025 
026 import javax.swing.AbstractAction;
027 import javax.swing.Box;
028 import javax.swing.BoxLayout;
029 import javax.swing.JButton;
030 import javax.swing.JPanel;
031 import javax.swing.JTextField;
032 import javax.swing.event.DocumentEvent;
033 import javax.swing.event.DocumentListener;
034 
035 /**
036  * An encapsulation of {@link JTextField} and a {@link JButton} that allows 
037  * the text value to be set to null by pressing the button. Provides the minimal
038  * API required for the needs of {@link SchemaFeaturesEditor}
039  */
040 public class JNullableTextField extends JPanel {
041   private static final long serialVersionUID = -1530694436281692216L;
042 
043   protected class NullifyTextAction extends AbstractAction {
044     private static final long serialVersionUID = -7807829141939910776L;
045 
046     public NullifyTextAction() {
047       super(null, MainFrame.getIcon("delete"));
048       putValue(SHORT_DESCRIPTION, "Removes this feature completely");
049     }
050 
051     @Override
052     public void actionPerformed(ActionEvent e) {
053       textField.setText(null);
054       text = null;
055       fireRemoveUpdate(null);
056     }
057   }
058   
059   /**
060    * The button used to clear (nullify) the textual value.
061    */
062   protected JButton nullifyButton;
063   
064   /**
065    * The text field used for editing the textual value.
066    */
067   protected JTextField textField;
068   
069   /**
070    * The normal background colour for the text field.
071    */
072   protected Color normalBgColor;
073   
074   /**
075    * The colour used for the text field's background when the value is null.
076    */
077   protected Color nullBgColor = new Color(200250255);
078 
079   /**
080    * My document listeners.
081    */
082   protected Set<DocumentListener> documentListeners;
083   
084   /**
085    * The text value, which can be null
086    */
087   protected String text = null;
088   
089   /**
090    * Creates a new {@link JNullableTextField} widget.
091    */
092   public JNullableTextField() {
093     initGui();
094     initListeners();
095   }
096 
097   /**
098    * Sets the value edited by this component. Will cause an insertUpdate
099    * notification to all {@link DocumentListener}s associated with this
100    * component (see {@link #addDocumentListener(DocumentListener)}.
101    @param text
102    */
103   public void setText(String text) {
104     textField.setText(text);
105     this.text = text;
106     fireInsertUpdate(null);
107   }
108   
109   /**
110    * Gets the value currently being edited. Unlike {@link JTextField}, this 
111    * value may be null (if {@link #setText(String)} was called previously with 
112    * a <code>null</code> value, of the delete button was pressed by the user). 
113    */
114   public String getText() {
115     return text;
116   }
117 
118   /**
119    * Sets the number of columns for the included {@link JTextField}, see 
120    {@link JTextField#setColumns(int)}
121    */
122   public void setColumns(int cols) {
123     textField.setColumns(cols);
124   }
125   
126   protected void initGui() {
127     setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
128     
129     textField = new JTextField();
130     add(textField);
131     add(Box.createHorizontalStrut(2));
132     nullifyButton = new JButton(new NullifyTextAction());
133     add(nullifyButton);
134 
135     normalBgColor = textField.getBackground();
136   }
137   
138   protected void initListeners() {
139     documentListeners = Collections.synchronizedSet(
140             new HashSet<DocumentListener>());
141     
142     final DocumentListener tfDocumentListener = new DocumentListener() {
143       @Override
144       public void removeUpdate(DocumentEvent e) {
145         text = textField.getText();
146         fireRemoveUpdate(e);
147       }
148       
149       @Override
150       public void insertUpdate(DocumentEvent e) {
151         text = textField.getText();
152         fireInsertUpdate(e);
153       }
154       
155       @Override
156       public void changedUpdate(DocumentEvent e) {
157         fireChangedUpdate(e);
158       }
159     };
160     
161     textField.getDocument().addDocumentListener(tfDocumentListener);
162     
163     textField.addPropertyChangeListener("document"new PropertyChangeListener() {
164       @Override
165       public void propertyChange(PropertyChangeEvent evt) {
166         textField.getDocument().addDocumentListener(tfDocumentListener);
167       }
168     });
169     
170     // listen to our own events, and highlight null value
171     addDocumentListener(new DocumentListener() {
172       @Override
173       public void removeUpdate(DocumentEvent e) {
174         valueChanged();
175       }
176       @Override
177       public void insertUpdate(DocumentEvent e) {
178         valueChanged();
179       }
180       
181       @Override
182       public void changedUpdate(DocumentEvent e) { }
183       
184       private void valueChanged() {
185         if(getText() == null) {
186           textField.setBackground(nullBgColor);
187         else {
188           textField.setBackground(normalBgColor);
189         }
190       }
191     });
192     
193   }
194 
195   /**
196    * Registers a new {@link DocumentListener} with this component. The provided
197    * listener will be forwarded all the events generated by the encapsulated 
198    {@link JTextField}. An event will also be generated when the user presses 
199    * the delete button, causing the text value to be nullified.  
200    @param listener
201    */
202   public void addDocumentListener(DocumentListener listener) {
203     documentListeners.add(listener);
204   }
205 
206   /**
207    * Removes a previously registered listener (see 
208    {@link #addDocumentListener(DocumentListener)}).
209    @param listener
210    */
211   public void removeDocumentListener(DocumentListener listener) {
212     documentListeners.remove(listener);
213   }
214   
215   
216   protected void fireChangedUpdate(DocumentEvent e) {
217     for(DocumentListener aListener : documentListeners
218       aListener.changedUpdate(e);
219   }
220   
221   protected void fireInsertUpdate(DocumentEvent e) {
222     for(DocumentListener aListener : documentListeners
223       aListener.insertUpdate(e);
224   }
225   
226   protected void fireRemoveUpdate(DocumentEvent e) {
227     for(DocumentListener aListener : documentListeners
228       aListener.removeUpdate(e);
229   }
230 }