1   /*
2    * Copyright (c) 1998-2004, 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.15 2004/06/29 16:17:59 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       editorCombo = (JComboBox)editorComponent;
284       editorCombo.setModel(new DefaultComboBoxModel());
285       editorCombo.setBackground(mainTable.getBackground());
286       editorCombo.setEditable(true);
287       editorCombo.addActionListener(new ActionListener(){
288         public void actionPerformed(ActionEvent evt){
289           stopCellEditing();
290         }
291       });
292       
293       rendererCombo = new JComboBox();
294       rendererCombo.setModel(new DefaultComboBoxModel());
295       rendererCombo.setBackground(mainTable.getBackground());
296       rendererCombo.setEditable(true);
297       rendererCombo.setOpaque(false);
298 
299       
300       requiredIconLabel = new JLabel(){
301         public void repaint(long tm, int x, int y, int width, int height){}
302         public void repaint(Rectangle r){}
303         public void validate(){}
304         public void revalidate(){}
305         protected void firePropertyChange(String propertyName,
306                                           Object oldValue,
307                                           Object newValue){}
308         
309       };
310       requiredIconLabel.setIcon(MainFrame.getIcon("r.gif"));
311       requiredIconLabel.setOpaque(false);
312       requiredIconLabel.setToolTipText("Required feature");
313       
314       optionalIconLabel = new JLabel(){
315         public void repaint(long tm, int x, int y, int width, int height){}
316         public void repaint(Rectangle r){}
317         public void validate(){}
318         public void revalidate(){}
319         protected void firePropertyChange(String propertyName,
320                                           Object oldValue,
321                                           Object newValue){}
322         
323       };
324       optionalIconLabel.setIcon(MainFrame.getIcon("o.gif"));
325       optionalIconLabel.setOpaque(false);
326       optionalIconLabel.setToolTipText("Optional feature");
327 
328       nonSchemaIconLabel = new JLabel(MainFrame.getIcon("c.gif")){
329         public void repaint(long tm, int x, int y, int width, int height){}
330         public void repaint(Rectangle r){}
331         public void validate(){}
332         public void revalidate(){}
333         protected void firePropertyChange(String propertyName,
334                                           Object oldValue,
335                                           Object newValue){}
336         
337       };
338       nonSchemaIconLabel.setToolTipText("Custom feature");
339       nonSchemaIconLabel.setOpaque(false);
340       
341       deleteButton = new JButton(MainFrame.getIcon("delete.gif"));
342       deleteButton.setMargin(new Insets(0,0,0,0));
343       deleteButton.setBorderPainted(false);
344       deleteButton.setContentAreaFilled(false);
345       deleteButton.setOpaque(false);
346       deleteButton.setToolTipText("Delete");
347       deleteButton.addActionListener(new ActionListener(){
348         public void actionPerformed(ActionEvent evt){
349           int row = mainTable.getEditingRow();
350           if(row < 0) return;
351           Feature feature = (Feature)featureList.get(row);
352           if(feature == emptyFeature){
353             feature.value = null;
354             featuresModel.fireTableRowsUpdated(row, row);
355           }else{
356             featureList.remove(row);
357             targetFeatures.remove(feature.name);
358             populate();
359           }
360         }
361       });
362     }    
363     
364     public Component getTableCellRendererComponent(JTable table, Object value,
365                                  boolean isSelected, boolean hasFocus, int row, int column){
366       Feature feature = (Feature)featureList.get(row);
367       switch(column){
368         case ICON_COL: 
369           return feature.isSchemaFeature() ? 
370                  (feature.isRequired() ? 
371                          requiredIconLabel : 
372                          optionalIconLabel) :
373                          nonSchemaIconLabel;  
374         case NAME_COL:
375           rendererCombo.setPreferredSize(null);
376           prepareCombo(rendererCombo, row, column);
377           Dimension dim = rendererCombo.getPreferredSize();
378 //          rendererCombo.setPreferredSize(new Dimension(dim.width + 5, dim.height));
379           return rendererCombo;
380         case VALUE_COL:
381           prepareCombo(rendererCombo, row, column);
382           return rendererCombo;
383         case DELETE_COL: return deleteButton;  
384         default: return null;
385       }
386     }
387   
388     public Component getTableCellEditorComponent(JTable table,  Object value, 
389             boolean isSelected, int row, int column){
390       switch(column){
391         case NAME_COL:
392           prepareCombo(editorCombo, row, column);
393           return editorCombo;
394         case VALUE_COL:
395           prepareCombo(editorCombo, row, column);
396           return editorCombo;
397         case DELETE_COL: return deleteButton;  
398         default: return null;
399       }
400 
401     }
402   
403     protected void prepareCombo(JComboBox combo, int row, int column){
404       Feature feature = (Feature)featureList.get(row);
405       DefaultComboBoxModel comboModel = (DefaultComboBoxModel)combo.getModel(); 
406       comboModel.removeAllElements();
407       switch(column){
408         case NAME_COL:
409           List fNames = new ArrayList();
410           if(schema != null && schema.getFeatureSchemaSet() != null){
411             Iterator fSchemaIter = schema.getFeatureSchemaSet().iterator();
412             while(fSchemaIter.hasNext())
413               fNames.add(((FeatureSchema)fSchemaIter.next()).getFeatureName());
414           }
415           if(!fNames.contains(feature.name))fNames.add(feature.name);
416           Collections.sort(fNames);
417           for(Iterator nameIter = fNames.iterator(); 
418               nameIter.hasNext(); 
419               comboModel.addElement(nameIter.next()));
420           combo.getEditor().getEditorComponent().setBackground(FeaturesSchemaEditor.this.getBackground());
421           combo.setSelectedItem(feature.name);
422           break;
423         case VALUE_COL:
424           List fValues = new ArrayList();
425           if(feature.isSchemaFeature()){
426             Set permValues = schema.getFeatureSchema(feature.name).
427               getPermissibleValues();
428             if(permValues != null) fValues.addAll(permValues);
429           }
430           if(!fValues.contains(feature.value)) fValues.add(feature.value);
431           for(Iterator valIter = fValues.iterator(); 
432               valIter.hasNext(); 
433               comboModel.addElement(valIter.next()));
434           combo.getEditor().getEditorComponent().setBackground(feature.isCorrect() ?
435                   FeaturesSchemaEditor.this.getBackground() :
436                   (feature.isRequired() ? REQUIRED_WRONG : OPTIONAL_WRONG));
437           combo.setSelectedItem(feature.value);
438           break;
439         default: ;
440       }
441       
442     }
443 
444     JLabel requiredIconLabel;
445     JLabel optionalIconLabel;
446     JLabel nonSchemaIconLabel;
447     JComboBox editorCombo;
448     JComboBox rendererCombo;
449     JButton deleteButton;
450   }
451 }