1   /*
2    * Copyright (c) 1998-2005, The University of Sheffield.
3    * 
4    * This file is part of GATE (see http://gate.ac.uk/), and is free software,
5    * licenced under the GNU Library General Public License, Version 2, June 1991
6    * (in the distribution as file licence.html, and also available at
7    * http://gate.ac.uk/gate/licence.html).
8    * 
9    * FeaturesSchemaEditor.java
10   * 
11   * Valentin Tablan, May 18, 2004
12   * 
13   * $Id: FeaturesSchemaEditor.java,v 1.19 2006/02/03 13:42:26 valyt Exp $
14   */
15  package gate.gui;
16  
17  import java.awt.*;
18  import java.awt.Component;
19  import java.awt.Rectangle;
20  import java.awt.event.ActionEvent;
21  import java.awt.event.ActionListener;
22  import java.util.*;
23  import java.util.ArrayList;
24  import java.util.List;
25  import javax.swing.*;
26  import javax.swing.JLabel;
27  import javax.swing.JTable;
28  import javax.swing.table.AbstractTableModel;
29  import javax.swing.table.TableCellRenderer;
30  import gate.*;
31  import gate.FeatureMap;
32  import gate.Resource;
33  import gate.creole.*;
34  import gate.creole.AnnotationSchema;
35  import gate.creole.FeatureSchema;
36  import gate.event.FeatureMapListener;
37  import gate.swing.XJTable;
38  import gate.util.*;
39  import gate.util.GateRuntimeException;
40  
41  /**
42   */
43  public class FeaturesSchemaEditor extends AbstractVisualResource
44          implements ResizableVisualResource, FeatureMapListener{
45    public FeaturesSchemaEditor(){
46      setBackground(UIManager.getDefaults().getColor("Table.background"));
47    }
48    
49    public void setTargetFeatures(FeatureMap features){
50      if(features != null) features.removeFeatureMapListener(this);
51      this.targetFeatures = features;
52      populate();
53      if(features != null) features.addFeatureMapListener(this);
54    }
55    
56    
57    /* (non-Javadoc)
58     * @see gate.VisualResource#setTarget(java.lang.Object)
59     */
60    public void setTarget(Object target){
61      this.target = (FeatureBearer)target;
62      setTargetFeatures(this.target.getFeatures());
63    }
64    
65    public void setSchema(AnnotationSchema schema){
66      this.schema = schema;
67      featuresModel.fireTableRowsUpdated(0, featureList.size() - 1);
68    }
69      
70    public XJTable getTable(){
71      return mainTable;
72    }
73  
74    /* (non-Javadoc)
75     * @see gate.event.FeatureMapListener#featureMapUpdated()
76     */
77    public void featureMapUpdated(){
78      populate();
79    }
80    
81    
82    /** Initialise this resource, and return it. */
83    public Resource init() throws ResourceInstantiationException {
84      featureList = new ArrayList();
85      emptyFeature = new Feature("", null);
86      featureList.add(emptyFeature);
87      initGUI();
88      return this;
89    }//init()
90    
91    protected void initGUI(){
92      featuresModel = new FeaturesTableModel();
93      mainTable = new XJTable();
94      mainTable.setModel(featuresModel);
95      mainTable.setTableHeader(null);
96      mainTable.setSortable(false);
97      mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
98      mainTable.setShowVerticalLines(false);    
99      mainTable.setBackground(getBackground());
100     mainTable.setIntercellSpacing(new Dimension(2,2));
101     featureEditorRenderer = new FeatureEditorRenderer();
102     mainTable.getColumnModel().getColumn(ICON_COL).
103         setCellRenderer(featureEditorRenderer);
104     mainTable.getColumnModel().getColumn(NAME_COL).
105         setCellRenderer(featureEditorRenderer);
106     mainTable.getColumnModel().getColumn(NAME_COL).
107         setCellEditor(featureEditorRenderer);
108     mainTable.getColumnModel().getColumn(VALUE_COL).
109         setCellRenderer(featureEditorRenderer);
110     mainTable.getColumnModel().getColumn(VALUE_COL).
111         setCellEditor(featureEditorRenderer);
112     mainTable.getColumnModel().getColumn(DELETE_COL).
113         setCellRenderer(featureEditorRenderer);
114     mainTable.getColumnModel().getColumn(DELETE_COL).
115       setCellEditor(featureEditorRenderer);
116     scroller = new JScrollPane(mainTable);
117     scroller.setBackground(getBackground());
118     scroller.getViewport().setBackground(getBackground());
119     setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
120     add(scroller);
121   }
122   
123   /**
124    * Called internally whenever the data represented changes.
125    *  
126    */
127   protected void populate(){
128     featureList.clear();
129     //get all the exisitng features
130     Set fNames = new HashSet();
131     
132     if(targetFeatures != null){
133       //add all the schema features
134       fNames.addAll(targetFeatures.keySet());
135       if(schema != null && schema.getFeatureSchemaSet() != null){
136         Iterator fSchemaIter = schema.getFeatureSchemaSet().iterator();
137         while(fSchemaIter.hasNext()){
138           FeatureSchema fSchema = (FeatureSchema)fSchemaIter.next();
139   //        if(fSchema.isRequired()) 
140             fNames.add(fSchema.getFeatureName());
141         }
142       }
143       List featureNames = new ArrayList(fNames);
144       Collections.sort(featureNames);
145       Iterator namIter = featureNames.iterator();
146       while(namIter.hasNext()){
147         String name = (String)namIter.next();
148         Object value = targetFeatures.get(name);
149         featureList.add(new Feature(name, value));
150       }
151     }
152     featureList.add(emptyFeature);
153     featuresModel.fireTableDataChanged();
154 //    mainTable.setSize(mainTable.getPreferredScrollableViewportSize());
155     mainTable.setSize(mainTable.getPreferredScrollableViewportSize());
156   }
157 
158   FeatureMap targetFeatures;
159   FeatureBearer target;
160   Feature emptyFeature;
161   AnnotationSchema schema;
162   FeaturesTableModel featuresModel;
163   List featureList;
164   FeatureEditorRenderer featureEditorRenderer;
165   XJTable mainTable;
166   JScrollPane scroller;
167   
168   private static final int COLUMNS = 4;
169   private static final int ICON_COL = 0;
170   private static final int NAME_COL = 1;
171   private static final int VALUE_COL = 2;
172   private static final int DELETE_COL = 3;
173   
174   private static final Color REQUIRED_WRONG = Color.RED;
175   private static final Color OPTIONAL_WRONG = Color.ORANGE;
176 
177   protected class Feature{
178     String name;
179     Object value;
180 
181     public Feature(String name, Object value){
182       this.name = name;
183       this.value = value;
184     }
185     boolean isSchemaFeature(){
186       return schema != null && schema.getFeatureSchema(name) != null;
187     }
188     boolean isCorrect(){
189       if(schema == null) return true;
190       FeatureSchema fSchema = schema.getFeatureSchema(name);
191       return fSchema == null || fSchema.getPermissibleValues() == null||
192              fSchema.getPermissibleValues().contains(value);
193     }
194     boolean isRequired(){
195       if(schema == null) return false;
196       FeatureSchema fSchema = schema.getFeatureSchema(name);
197       return fSchema != null && fSchema.isRequired();
198     }
199     Object getDefaultValue(){
200       if(schema == null) return null;
201       FeatureSchema fSchema = schema.getFeatureSchema(name);
202       return fSchema == null ? null : fSchema.getFeatureValue();
203     }
204   }
205   
206   
207   protected class FeaturesTableModel extends AbstractTableModel{
208     public int getRowCount(){
209       return featureList.size();
210     }
211     
212     public int getColumnCount(){
213       return COLUMNS;
214     }
215     
216     public Object getValueAt(int row, int column){
217       Feature feature = (Feature)featureList.get(row);
218       switch(column){
219         case NAME_COL:
220           return feature.name;
221         case VALUE_COL:
222           return feature.value;
223         default:
224           return null;
225       }
226     }
227     
228     public boolean isCellEditable(int rowIndex, int columnIndex){
229       return columnIndex == VALUE_COL || columnIndex == NAME_COL || 
230              columnIndex == DELETE_COL;
231     }
232     
233     public void setValueAt(Object aValue, int rowIndex,  int columnIndex){
234       Feature feature = (Feature)featureList.get(rowIndex);
235       if(targetFeatures == null){
236         targetFeatures = Factory.newFeatureMap();
237         target.setFeatures(targetFeatures);
238         setTargetFeatures(targetFeatures);
239       }
240       switch(columnIndex){
241         case VALUE_COL:
242           feature.value = aValue;
243           if(feature.name != null && feature.name.length() > 0){
244             targetFeatures.put(feature.name, aValue);
245             fireTableRowsUpdated(rowIndex, rowIndex);
246             mainTable.setSize(mainTable.getPreferredScrollableViewportSize());
247           }
248           break;
249         case NAME_COL:
250           targetFeatures.remove(feature.name);
251           feature.name = (String)aValue;
252           targetFeatures.put(feature.name, feature.value);
253           if(feature == emptyFeature) emptyFeature = new Feature("", null);
254           populate();
255           break;
256         case DELETE_COL:
257           //nothing
258           break;
259         default:
260           throw new GateRuntimeException("Non editable cell!");
261       }
262       
263     }
264     
265     public String getColumnName(int column){
266       switch(column){
267         case NAME_COL:
268           return "Name";
269         case VALUE_COL:
270           return "Value";
271         case DELETE_COL:
272           return "";
273         default:
274           return null;
275       }
276     }
277   }
278   
279   
280   protected class FeatureEditorRenderer extends DefaultCellEditor implements TableCellRenderer{
281     public FeatureEditorRenderer(){
282       super(new JComboBox());
283       defaultComparator = new ObjectComparator();
284       editorCombo = (JComboBox)editorComponent;
285       editorCombo.setModel(new DefaultComboBoxModel());
286       editorCombo.setBackground(mainTable.getBackground());
287       editorCombo.setEditable(true);
288       editorCombo.addActionListener(new ActionListener(){
289         public void actionPerformed(ActionEvent evt){
290           stopCellEditing();
291         }
292       });
293       
294       rendererCombo = new JComboBox();
295       rendererCombo.setModel(new DefaultComboBoxModel());
296       rendererCombo.setBackground(mainTable.getBackground());
297       rendererCombo.setEditable(true);
298       rendererCombo.setOpaque(false);
299 
300       
301       requiredIconLabel = new JLabel(){
302         public void repaint(long tm, int x, int y, int width, int height){}
303         public void repaint(Rectangle r){}
304         public void validate(){}
305         public void revalidate(){}
306         protected void firePropertyChange(String propertyName,
307                                           Object oldValue,
308                                           Object newValue){}
309         
310       };
311       requiredIconLabel.setIcon(MainFrame.getIcon("r.gif"));
312       requiredIconLabel.setOpaque(false);
313       requiredIconLabel.setToolTipText("Required feature");
314       
315       optionalIconLabel = new JLabel(){
316         public void repaint(long tm, int x, int y, int width, int height){}
317         public void repaint(Rectangle r){}
318         public void validate(){}
319         public void revalidate(){}
320         protected void firePropertyChange(String propertyName,
321                                           Object oldValue,
322                                           Object newValue){}
323         
324       };
325       optionalIconLabel.setIcon(MainFrame.getIcon("o.gif"));
326       optionalIconLabel.setOpaque(false);
327       optionalIconLabel.setToolTipText("Optional feature");
328 
329       nonSchemaIconLabel = new JLabel(MainFrame.getIcon("c.gif")){
330         public void repaint(long tm, int x, int y, int width, int height){}
331         public void repaint(Rectangle r){}
332         public void validate(){}
333         public void revalidate(){}
334         protected void firePropertyChange(String propertyName,
335                                           Object oldValue,
336                                           Object newValue){}
337         
338       };
339       nonSchemaIconLabel.setToolTipText("Custom feature");
340       nonSchemaIconLabel.setOpaque(false);
341       
342       deleteButton = new JButton(MainFrame.getIcon("delete.gif"));
343       deleteButton.setMargin(new Insets(0,0,0,0));
344       deleteButton.setBorderPainted(false);
345       deleteButton.setContentAreaFilled(false);
346       deleteButton.setOpaque(false);
347       deleteButton.setToolTipText("Delete");
348       deleteButton.addActionListener(new ActionListener(){
349         public void actionPerformed(ActionEvent evt){
350           int row = mainTable.getEditingRow();
351           if(row < 0) return;
352           Feature feature = (Feature)featureList.get(row);
353           if(feature == emptyFeature){
354             feature.value = null;
355             featuresModel.fireTableRowsUpdated(row, row);
356           }else{
357             featureList.remove(row);
358             targetFeatures.remove(feature.name);
359             populate();
360           }
361         }
362       });
363     }    
364     
365     public Component getTableCellRendererComponent(JTable table, Object value,
366                                  boolean isSelected, boolean hasFocus, int row, int column){
367       Feature feature = (Feature)featureList.get(row);
368       switch(column){
369         case ICON_COL: 
370           return feature.isSchemaFeature() ? 
371                  (feature.isRequired() ? 
372                          requiredIconLabel : 
373                          optionalIconLabel) :
374                          nonSchemaIconLabel;  
375         case NAME_COL:
376           rendererCombo.setPreferredSize(null);
377           prepareCombo(rendererCombo, row, column);
378           Dimension dim = rendererCombo.getPreferredSize();
379 //          rendererCombo.setPreferredSize(new Dimension(dim.width + 5, dim.height));
380           return rendererCombo;
381         case VALUE_COL:
382           prepareCombo(rendererCombo, row, column);
383           return rendererCombo;
384         case DELETE_COL: return deleteButton;  
385         default: return null;
386       }
387     }
388   
389     public Component getTableCellEditorComponent(JTable table,  Object value, 
390             boolean isSelected, int row, int column){
391       switch(column){
392         case NAME_COL:
393           prepareCombo(editorCombo, row, column);
394           return editorCombo;
395         case VALUE_COL:
396           prepareCombo(editorCombo, row, column);
397           return editorCombo;
398         case DELETE_COL: return deleteButton;  
399         default: return null;
400       }
401 
402     }
403   
404     protected void prepareCombo(JComboBox combo, int row, int column){
405       Feature feature = (Feature)featureList.get(row);
406       DefaultComboBoxModel comboModel = (DefaultComboBoxModel)combo.getModel(); 
407       comboModel.removeAllElements();
408       switch(column){
409         case NAME_COL:
410           List fNames = new ArrayList();
411           if(schema != null && schema.getFeatureSchemaSet() != null){
412             Iterator fSchemaIter = schema.getFeatureSchemaSet().iterator();
413             while(fSchemaIter.hasNext())
414               fNames.add(((FeatureSchema)fSchemaIter.next()).getFeatureName());
415           }
416           if(!fNames.contains(feature.name))fNames.add(feature.name);
417           Collections.sort(fNames);
418           for(Iterator nameIter = fNames.iterator(); 
419               nameIter.hasNext(); 
420               comboModel.addElement(nameIter.next()));
421           combo.getEditor().getEditorComponent().setBackground(FeaturesSchemaEditor.this.getBackground());
422           combo.setSelectedItem(feature.name);
423           break;
424         case VALUE_COL:
425           List fValues = new ArrayList();
426           if(feature.isSchemaFeature()){
427             Set permValues = schema.getFeatureSchema(feature.name).
428               getPermissibleValues();
429             if(permValues != null) fValues.addAll(permValues);
430           }
431           if(!fValues.contains(feature.value)) fValues.add(feature.value);
432           Collections.sort(fValues, defaultComparator);
433           for(Iterator valIter = fValues.iterator(); 
434               valIter.hasNext(); 
435               comboModel.addElement(valIter.next()));
436           combo.getEditor().getEditorComponent().setBackground(feature.isCorrect() ?
437                   FeaturesSchemaEditor.this.getBackground() :
438                   (feature.isRequired() ? REQUIRED_WRONG : OPTIONAL_WRONG));
439           combo.setSelectedItem(feature.value);
440           break;
441         default: ;
442       }
443       
444     }
445 
446     JLabel requiredIconLabel;
447     JLabel optionalIconLabel;
448     JLabel nonSchemaIconLabel;
449     JComboBox editorCombo;
450     JComboBox rendererCombo;
451     JButton deleteButton;
452     ObjectComparator defaultComparator;
453   }
454 }