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 13679 2011-04-15 13:18:56Z valyt $
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     public void actionPerformed(ActionEvent e) {
052       textField.setText(null);
053       text = null;
054       fireRemoveUpdate(null);
055     }
056   }
057   
058   /**
059    * The button used to clear (nullify) the textual value.
060    */
061   protected JButton nullifyButton;
062   
063   /**
064    * The text field used for editing the textual value.
065    */
066   protected JTextField textField;
067   
068   /**
069    * The normal background colour for the text field.
070    */
071   protected Color normalBgColor;
072   
073   /**
074    * The colour used for the text field's background when the value is null.
075    */
076   protected Color nullBgColor = new Color(200250255);
077 
078   /**
079    * My document listeners.
080    */
081   protected Set<DocumentListener> documentListeners;
082   
083   /**
084    * The text value, which can be null
085    */
086   protected String text = null;
087   
088   /**
089    * Creates a new {@link JNullableTextField} widget.
090    */
091   public JNullableTextField() {
092     initGui();
093     initListeners();
094   }
095 
096   /**
097    * Sets the value edited by this component. Will cause an insertUpdate
098    * notification to all {@link DocumentListener}s associated with this
099    * component (see {@link #addDocumentListener(DocumentListener)}.
100    @param text
101    */
102   public void setText(String text) {
103     textField.setText(text);
104     this.text = text;
105     fireInsertUpdate(null);
106   }
107   
108   /**
109    * Gets the value currently being edited. Unlike {@link JTextField}, this 
110    * value may be null (if {@link #setText(String)} was called previously with 
111    * a <code>null</code> value, of the delete button was pressed by the user). 
112    @return
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    @param cols
122    */
123   public void setColumns(int cols) {
124     textField.setColumns(cols);
125   }
126   
127   protected void initGui() {
128     setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
129     
130     textField = new JTextField();
131     add(textField);
132     add(Box.createHorizontalStrut(2));
133     nullifyButton = new JButton(new NullifyTextAction());
134     add(nullifyButton);
135 
136     normalBgColor = textField.getBackground();
137   }
138   
139   protected void initListeners() {
140     documentListeners = Collections.synchronizedSet(
141             new HashSet<DocumentListener>());
142     
143     final DocumentListener tfDocumentListener = new DocumentListener() {
144       public void removeUpdate(DocumentEvent e) {
145         text = textField.getText();
146         fireRemoveUpdate(e);
147       }
148       
149       public void insertUpdate(DocumentEvent e) {
150         text = textField.getText();
151         fireInsertUpdate(e);
152       }
153       
154       public void changedUpdate(DocumentEvent e) {
155         fireChangedUpdate(e);
156       }
157     };
158     
159     textField.getDocument().addDocumentListener(tfDocumentListener);
160     
161     textField.addPropertyChangeListener("document"new PropertyChangeListener() {
162       public void propertyChange(PropertyChangeEvent evt) {
163         textField.getDocument().addDocumentListener(tfDocumentListener);
164       }
165     });
166     
167     // listen to our own events, and highlight null value
168     addDocumentListener(new DocumentListener() {
169       public void removeUpdate(DocumentEvent e) {
170         valueChanged();
171       }
172       public void insertUpdate(DocumentEvent e) {
173         valueChanged();
174       }
175       
176       public void changedUpdate(DocumentEvent e) { }
177       
178       private void valueChanged() {
179         if(getText() == null) {
180           textField.setBackground(nullBgColor);
181         else {
182           textField.setBackground(normalBgColor);
183         }
184       }
185     });
186     
187   }
188 
189   /**
190    * Registers a new {@link DocumentListener} with this component. The provided
191    * listener will be forwarded all the events generated by the encapsulated 
192    {@link JTextField}. An event will also be generated when the user presses 
193    * the delete button, causing the text value to be nullified.  
194    @param listener
195    */
196   public void addDocumentListener(DocumentListener listener) {
197     documentListeners.add(listener);
198   }
199 
200   /**
201    * Removes a previously registered listener (see 
202    {@link #addDocumentListener(DocumentListener)}).
203    @param listener
204    */
205   public void removeDocumentListener(DocumentListener listener) {
206     documentListeners.remove(listener);
207   }
208   
209   
210   protected void fireChangedUpdate(DocumentEvent e) {
211     for(DocumentListener aListener : documentListeners
212       aListener.changedUpdate(e);
213   }
214   
215   protected void fireInsertUpdate(DocumentEvent e) {
216     for(DocumentListener aListener : documentListeners
217       aListener.insertUpdate(e);
218   }
219   
220   protected void fireRemoveUpdate(DocumentEvent e) {
221     for(DocumentListener aListener : documentListeners
222       aListener.removeUpdate(e);
223   }
224 }