001 /*
002 * Copyright (c) 1998-2005, The University of Sheffield.
003 *
004 * This file is part of GATE (see http://gate.ac.uk/), and is free software,
005 * licenced under the GNU Library General Public License, Version 2, June 1991
006 * (in the distribution as file licence.html, and also available at
007 * http://gate.ac.uk/gate/licence.html).
008 *
009 * FeaturesSchemaEditor.java
010 *
011 * Valentin Tablan, May 18, 2004
012 *
013 * $Id: FeaturesSchemaEditor.java 12283 2010-02-18 16:40:15Z valyt $
014 */
015 package gate.gui;
016
017 import java.awt.*;
018 import java.awt.event.ActionEvent;
019 import java.awt.event.ActionListener;
020 import java.beans.BeanInfo;
021 import java.beans.Introspector;
022 import java.util.*;
023 import java.util.List;
024 import javax.swing.*;
025 import javax.swing.table.AbstractTableModel;
026 import javax.swing.table.TableCellRenderer;
027 import gate.*;
028 import gate.creole.*;
029 import gate.creole.metadata.*;
030 import gate.event.FeatureMapListener;
031 import gate.swing.XJTable;
032 import gate.util.*;
033
034 /**
035 */
036 @CreoleResource(name = "Features", guiType = GuiType.SMALL,
037 resourceDisplayed = "gate.util.FeatureBearer")
038 public class FeaturesSchemaEditor extends XJTable
039 implements ResizableVisualResource, FeatureMapListener{
040 public FeaturesSchemaEditor(){
041 // setBackground(UIManager.getDefaults().getColor("Table.background"));
042 instance = this;
043 }
044
045 public void setTargetFeatures(FeatureMap features){
046 if(targetFeatures != null) targetFeatures.removeFeatureMapListener(this);
047 this.targetFeatures = features;
048 populate();
049 if(targetFeatures != null) targetFeatures.addFeatureMapListener(this);
050 }
051
052
053 public void cleanup() {
054 if(targetFeatures != null){
055 targetFeatures.removeFeatureMapListener(this);
056 targetFeatures = null;
057 }
058 target = null;
059 schema = null;
060 }
061
062 /* (non-Javadoc)
063 * @see gate.VisualResource#setTarget(java.lang.Object)
064 */
065 public void setTarget(Object target){
066 this.target = (FeatureBearer)target;
067 setTargetFeatures(this.target.getFeatures());
068 }
069
070 public void setSchema(AnnotationSchema schema){
071 this.schema = schema;
072 featuresModel.fireTableRowsUpdated(0, featureList.size() - 1);
073 }
074
075 /* (non-Javadoc)
076 * @see gate.event.FeatureMapListener#featureMapUpdated()
077 * Called each time targetFeatures is changed.
078 */
079 public void featureMapUpdated(){
080 SwingUtilities.invokeLater(new Runnable(){
081 public void run(){
082 populate();
083 }
084 });
085 }
086
087
088 /** Initialise this resource, and return it. */
089 public Resource init() throws ResourceInstantiationException {
090 featureList = new ArrayList<Feature>();
091 emptyFeature = new Feature("", null);
092 featureList.add(emptyFeature);
093 initGUI();
094 return this;
095 }//init()
096
097 protected void initGUI(){
098 featuresModel = new FeaturesTableModel();
099 setModel(featuresModel);
100 setTableHeader(null);
101 setSortable(false);
102 setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
103 setShowGrid(false);
104 setBackground(getBackground());
105 setIntercellSpacing(new Dimension(2,2));
106 setTabSkipUneditableCell(true);
107 setEditCellAsSoonAsFocus(true);
108 featureEditorRenderer = new FeatureEditorRenderer();
109 getColumnModel().getColumn(ICON_COL).
110 setCellRenderer(featureEditorRenderer);
111 getColumnModel().getColumn(NAME_COL).
112 setCellRenderer(featureEditorRenderer);
113 getColumnModel().getColumn(NAME_COL).
114 setCellEditor(featureEditorRenderer);
115 getColumnModel().getColumn(VALUE_COL).
116 setCellRenderer(featureEditorRenderer);
117 getColumnModel().getColumn(VALUE_COL).
118 setCellEditor(featureEditorRenderer);
119 getColumnModel().getColumn(DELETE_COL).
120 setCellRenderer(featureEditorRenderer);
121 getColumnModel().getColumn(DELETE_COL).
122 setCellEditor(featureEditorRenderer);
123
124 //the background colour seems to change somewhere when using the GTK+
125 //look and feel on Linux, so we copy the value now and set it
126 Color tableBG = getBackground();
127 //make a copy of the value (as the reference gets changed somewhere)
128 tableBG = new Color(tableBG.getRGB());
129 setBackground(tableBG);
130
131 // allow Tab key to select the next cell in the table
132 setSurrendersFocusOnKeystroke(true);
133 setFocusCycleRoot(true);
134
135 // remove (shift) control tab as traversal keys
136 Set<AWTKeyStroke> keySet = new HashSet<AWTKeyStroke>(
137 getFocusTraversalKeys(
138 KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
139 keySet.remove(KeyStroke.getKeyStroke("control TAB"));
140 setFocusTraversalKeys(
141 KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keySet);
142 keySet = new HashSet<AWTKeyStroke>(
143 getFocusTraversalKeys(
144 KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
145 keySet.remove(KeyStroke.getKeyStroke("shift control TAB"));
146 setFocusTraversalKeys(
147 KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keySet);
148
149 // add (shift) control tab to go the container of this component
150 keySet.clear();
151 keySet.add(KeyStroke.getKeyStroke("control TAB"));
152 setFocusTraversalKeys(
153 KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, keySet);
154 keySet.clear();
155 keySet.add(KeyStroke.getKeyStroke("shift control TAB"));
156 setFocusTraversalKeys(
157 KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS, keySet);
158 }
159
160 /**
161 * Called internally whenever the data represented changes.
162 * Get feature names from targetFeatures and schema then sort them
163 * and add them to featureList.
164 * Fire a table data changed event for the feature table whith featureList
165 * used as data model.
166 */
167 protected void populate(){
168 featureList.clear();
169 //get all the existing features
170 Set fNames = new HashSet();
171
172 if(targetFeatures != null){
173 //add all the schema features
174 fNames.addAll(targetFeatures.keySet());
175 if(schema != null && schema.getFeatureSchemaSet() != null){
176 for(FeatureSchema featureSchema : schema.getFeatureSchemaSet()) {
177 // if(featureSchema.isRequired())
178 fNames.add(featureSchema.getFeatureName());
179 }
180 }
181 List featureNames = new ArrayList(fNames);
182 Collections.sort(featureNames);
183 for(Object featureName : featureNames) {
184 String name = (String) featureName;
185 Object value = targetFeatures.get(name);
186 featureList.add(new Feature(name, value));
187 }
188 }
189 if (!featureList.contains(emptyFeature)) {
190 featureList.add(emptyFeature);
191 }
192 featuresModel.fireTableDataChanged();
193 // mainTable.setSize(mainTable.getPreferredScrollableViewportSize());
194 }
195
196 FeatureMap targetFeatures;
197 FeatureBearer target;
198 Feature emptyFeature;
199 AnnotationSchema schema;
200 FeaturesTableModel featuresModel;
201 List<Feature> featureList;
202 FeatureEditorRenderer featureEditorRenderer;
203 FeaturesSchemaEditor instance;
204
205 private static final int COLUMNS = 4;
206 private static final int ICON_COL = 0;
207 private static final int NAME_COL = 1;
208 private static final int VALUE_COL = 2;
209 private static final int DELETE_COL = 3;
210
211 private static final Color REQUIRED_WRONG = Color.RED;
212 private static final Color OPTIONAL_WRONG = Color.ORANGE;
213
214 protected class Feature{
215 String name;
216 Object value;
217
218 public Feature(String name, Object value){
219 this.name = name;
220 this.value = value;
221 }
222 boolean isSchemaFeature(){
223 return schema != null && schema.getFeatureSchema(name) != null;
224 }
225 boolean isCorrect(){
226 if(schema == null) return true;
227 FeatureSchema fSchema = schema.getFeatureSchema(name);
228 return fSchema == null || fSchema.getPermittedValues() == null||
229 fSchema.getPermittedValues().contains(value);
230 }
231 boolean isRequired(){
232 if(schema == null) return false;
233 FeatureSchema fSchema = schema.getFeatureSchema(name);
234 return fSchema != null && fSchema.isRequired();
235 }
236 Object getDefaultValue(){
237 if(schema == null) return null;
238 FeatureSchema fSchema = schema.getFeatureSchema(name);
239 return fSchema == null ? null : fSchema.getFeatureValue();
240 }
241 }
242
243
244 protected class FeaturesTableModel extends AbstractTableModel{
245 public int getRowCount(){
246 return featureList.size();
247 }
248
249 public int getColumnCount(){
250 return COLUMNS;
251 }
252
253 public Object getValueAt(int row, int column){
254 Feature feature = featureList.get(row);
255 switch(column){
256 case NAME_COL:
257 return feature.name;
258 case VALUE_COL:
259 return feature.value;
260 default:
261 return null;
262 }
263 }
264
265 public boolean isCellEditable(int rowIndex, int columnIndex){
266 return columnIndex == VALUE_COL || columnIndex == NAME_COL ||
267 columnIndex == DELETE_COL;
268 }
269
270 public void setValueAt(Object aValue, int rowIndex, int columnIndex){
271 Feature feature = featureList.get(rowIndex);
272 if (feature == null) { return; }
273 if(targetFeatures == null){
274 targetFeatures = Factory.newFeatureMap();
275 target.setFeatures(targetFeatures);
276 setTargetFeatures(targetFeatures);
277 }
278 switch(columnIndex){
279 case VALUE_COL:
280 if (feature.value != null
281 && feature.value.equals(aValue)) { return; }
282 feature.value = aValue;
283 if(feature.name != null && feature.name.length() > 0){
284 targetFeatures.removeFeatureMapListener(instance);
285 targetFeatures.put(feature.name, aValue);
286 targetFeatures.addFeatureMapListener(instance);
287 SwingUtilities.invokeLater(new Runnable() {
288 public void run() {
289 // edit the last row that is empty
290 FeaturesSchemaEditor.this.editCellAt(FeaturesSchemaEditor.this.getRowCount() - 1, NAME_COL);
291 }
292 });
293 }
294 break;
295 case NAME_COL:
296 if (feature.name.equals(aValue)) {
297 return;
298 }
299 targetFeatures.remove(feature.name);
300 feature.name = (String)aValue;
301 targetFeatures.put(feature.name, feature.value);
302 if(feature == emptyFeature) emptyFeature = new Feature("", null);
303 populate();
304 int newRow;
305 for (newRow = 0; newRow < FeaturesSchemaEditor.this.getRowCount(); newRow++) {
306 if (FeaturesSchemaEditor.this.getValueAt(newRow, NAME_COL).equals(feature.name)) {
307 break; // find the previously selected row in the new table
308 }
309 }
310 final int newRowF = newRow;
311 SwingUtilities.invokeLater(new Runnable() {
312 public void run() {
313 // edit the cell containing the value associated with this name
314 FeaturesSchemaEditor.this.editCellAt(newRowF, VALUE_COL);
315 }
316 });
317 break;
318 case DELETE_COL:
319 //nothing
320 break;
321 default:
322 throw new GateRuntimeException("Non editable cell!");
323 }
324
325 }
326
327 public String getColumnName(int column){
328 switch(column){
329 case NAME_COL:
330 return "Name";
331 case VALUE_COL:
332 return "Value";
333 case DELETE_COL:
334 return "";
335 default:
336 return null;
337 }
338 }
339 }
340
341
342 protected class FeatureEditorRenderer extends DefaultCellEditor
343 implements TableCellRenderer {
344 public FeatureEditorRenderer(){
345 super(new JComboBox());
346 defaultComparator = new ObjectComparator();
347 editorCombo = (JComboBox)editorComponent;
348 editorCombo.setModel(new DefaultComboBoxModel());
349 editorCombo.setBackground(FeaturesSchemaEditor.this.getBackground());
350 editorCombo.setEditable(true);
351 editorCombo.addActionListener(new ActionListener(){
352 public void actionPerformed(ActionEvent evt){
353 stopCellEditing();
354 }
355 });
356
357 rendererCombo = new JComboBox(){
358 @Override
359 public Dimension getMaximumSize() {
360 return getPreferredSize();
361 }
362 @Override
363 public Dimension getMinimumSize() {
364 return getPreferredSize();
365 }
366 };
367 rendererCombo.setModel(new DefaultComboBoxModel());
368 rendererCombo.setBackground(FeaturesSchemaEditor.this.getBackground());
369 rendererCombo.setEditable(true);
370 rendererCombo.setOpaque(false);
371
372
373 requiredIconLabel = new JLabel(){
374 public void repaint(long tm, int x, int y, int width, int height){}
375 public void repaint(Rectangle r){}
376 public void validate(){}
377 public void revalidate(){}
378 protected void firePropertyChange(String propertyName,
379 Object oldValue,
380 Object newValue){}
381 @Override
382 public Dimension getMaximumSize() {
383 return getPreferredSize();
384 }
385 @Override
386 public Dimension getMinimumSize() {
387 return getPreferredSize();
388 }
389 };
390 requiredIconLabel.setIcon(MainFrame.getIcon("r"));
391 requiredIconLabel.setOpaque(false);
392 requiredIconLabel.setToolTipText("Required feature");
393
394 optionalIconLabel = new JLabel(){
395 public void repaint(long tm, int x, int y, int width, int height){}
396 public void repaint(Rectangle r){}
397 public void validate(){}
398 public void revalidate(){}
399 protected void firePropertyChange(String propertyName,
400 Object oldValue,
401 Object newValue){}
402 @Override
403 public Dimension getMaximumSize() {
404 return getPreferredSize();
405 }
406 @Override
407 public Dimension getMinimumSize() {
408 return getPreferredSize();
409 }
410 };
411 optionalIconLabel.setIcon(MainFrame.getIcon("o"));
412 optionalIconLabel.setOpaque(false);
413 optionalIconLabel.setToolTipText("Optional feature");
414
415 nonSchemaIconLabel = new JLabel(MainFrame.getIcon("c")){
416 public void repaint(long tm, int x, int y, int width, int height){}
417 public void repaint(Rectangle r){}
418 public void validate(){}
419 public void revalidate(){}
420 protected void firePropertyChange(String propertyName,
421 Object oldValue,
422 Object newValue){}
423 @Override
424 public Dimension getMaximumSize() {
425 return getPreferredSize();
426 }
427 @Override
428 public Dimension getMinimumSize() {
429 return getPreferredSize();
430 }
431 };
432 nonSchemaIconLabel.setToolTipText("Custom feature");
433 nonSchemaIconLabel.setOpaque(false);
434
435 deleteButton = new JButton(MainFrame.getIcon("delete"));
436 deleteButton.setMargin(new Insets(0,0,0,0));
437 // deleteButton.setBorderPainted(false);
438 // deleteButton.setContentAreaFilled(false);
439 // deleteButton.setOpaque(false);
440 deleteButton.setToolTipText("Delete");
441 deleteButton.addActionListener(new ActionListener(){
442 public void actionPerformed(ActionEvent evt){
443 int row = FeaturesSchemaEditor.this.getEditingRow();
444 if(row < 0) return;
445 Feature feature = featureList.get(row);
446 if(feature == emptyFeature){
447 feature.value = null;
448 }else{
449 featureList.remove(row);
450 targetFeatures.remove(feature.name);
451 featuresModel.fireTableRowsDeleted(row, row);
452 // mainTable.setSize(mainTable.getPreferredScrollableViewportSize());
453 }
454 }
455 });
456 }
457
458 public Component getTableCellRendererComponent(JTable table, Object value,
459 boolean isSelected, boolean hasFocus, int row, int column){
460 Feature feature = featureList.get(row);
461 switch(column){
462 case ICON_COL:
463 return feature.isSchemaFeature() ?
464 (feature.isRequired() ?
465 requiredIconLabel :
466 optionalIconLabel) :
467 nonSchemaIconLabel;
468 case NAME_COL:
469
470 prepareCombo(rendererCombo, row, column);
471 rendererCombo.getPreferredSize();
472 // Dimension dim = rendererCombo.getPreferredSize();
473 // rendererCombo.setPreferredSize(new Dimension(dim.width + 5, dim.height));
474 return rendererCombo;
475 case VALUE_COL:
476 prepareCombo(rendererCombo, row, column);
477 return rendererCombo;
478 case DELETE_COL: return deleteButton;
479 default: return null;
480 }
481 }
482
483 public Component getTableCellEditorComponent(JTable table, Object value,
484 boolean isSelected, int row, int column){
485 switch(column){
486 case NAME_COL:
487 prepareCombo(editorCombo, row, column);
488 return editorCombo;
489 case VALUE_COL:
490 prepareCombo(editorCombo, row, column);
491 return editorCombo;
492 case DELETE_COL: return deleteButton;
493 default: return null;
494 }
495
496 }
497
498 protected void prepareCombo(JComboBox combo, int row, int column){
499 Feature feature = featureList.get(row);
500 DefaultComboBoxModel comboModel = (DefaultComboBoxModel)combo.getModel();
501 comboModel.removeAllElements();
502 switch(column){
503 case NAME_COL:
504 List fNames = new ArrayList();
505 if(schema != null && schema.getFeatureSchemaSet() != null){
506 Iterator fSchemaIter = schema.getFeatureSchemaSet().iterator();
507 while(fSchemaIter.hasNext())
508 fNames.add(((FeatureSchema)fSchemaIter.next()).getFeatureName());
509 }
510 if(!fNames.contains(feature.name))fNames.add(feature.name);
511 Collections.sort(fNames);
512 for(Iterator nameIter = fNames.iterator();
513 nameIter.hasNext();
514 comboModel.addElement(nameIter.next()));
515 combo.getEditor().getEditorComponent().setBackground(FeaturesSchemaEditor.this.getBackground());
516 combo.setSelectedItem(feature.name);
517 break;
518 case VALUE_COL:
519 List fValues = new ArrayList();
520 if(feature.isSchemaFeature()){
521 Set permValues = schema.getFeatureSchema(feature.name).
522 getPermittedValues();
523 if(permValues != null) fValues.addAll(permValues);
524 }
525 if(!fValues.contains(feature.value)) fValues.add(feature.value);
526 Collections.sort(fValues, defaultComparator);
527 for(Iterator valIter = fValues.iterator();
528 valIter.hasNext();
529 comboModel.addElement(valIter.next()));
530 combo.getEditor().getEditorComponent().setBackground(feature.isCorrect() ?
531 FeaturesSchemaEditor.this.getBackground() :
532 (feature.isRequired() ? REQUIRED_WRONG : OPTIONAL_WRONG));
533 combo.setSelectedItem(feature.value);
534 break;
535 default: ;
536 }
537
538 }
539
540 JLabel requiredIconLabel;
541 JLabel optionalIconLabel;
542 JLabel nonSchemaIconLabel;
543 JComboBox editorCombo;
544 JComboBox rendererCombo;
545 JButton deleteButton;
546 ObjectComparator defaultComparator;
547 }
548
549 /*
550 * Resource implementation
551 */
552 /** Accessor for features. */
553 public FeatureMap getFeatures(){
554 return features;
555 }//getFeatures()
556
557 /** Mutator for features*/
558 public void setFeatures(FeatureMap features){
559 this.features = features;
560 }// setFeatures()
561
562
563 /**
564 * Used by the main GUI to tell this VR what handle created it. The VRs can
565 * use this information e.g. to add items to the popup for the resource.
566 */
567 public void setHandle(Handle handle){
568 this.handle = handle;
569 }
570
571 //Parameters utility methods
572 /**
573 * Gets the value of a parameter of this resource.
574 * @param paramaterName the name of the parameter
575 * @return the current value of the parameter
576 */
577 public Object getParameterValue(String paramaterName)
578 throws ResourceInstantiationException{
579 return AbstractResource.getParameterValue(this, paramaterName);
580 }
581
582 /**
583 * Sets the value for a specified parameter.
584 *
585 * @param paramaterName the name for the parameteer
586 * @param parameterValue the value the parameter will receive
587 */
588 public void setParameterValue(String paramaterName, Object parameterValue)
589 throws ResourceInstantiationException{
590 // get the beaninfo for the resource bean, excluding data about Object
591 BeanInfo resBeanInf = null;
592 try {
593 resBeanInf = Introspector.getBeanInfo(this.getClass(), Object.class);
594 } catch(Exception e) {
595 throw new ResourceInstantiationException(
596 "Couldn't get bean info for resource " + this.getClass().getName()
597 + Strings.getNl() + "Introspector exception was: " + e
598 );
599 }
600 AbstractResource.setParameterValue(this, resBeanInf, paramaterName, parameterValue);
601 }
602
603 /**
604 * Sets the values for more parameters in one step.
605 *
606 * @param parameters a feature map that has paramete names as keys and
607 * parameter values as values.
608 */
609 public void setParameterValues(FeatureMap parameters)
610 throws ResourceInstantiationException{
611 AbstractResource.setParameterValues(this, parameters);
612 }
613
614 // Properties for the resource
615 protected FeatureMap features;
616
617 /**
618 * The handle for this visual resource
619 */
620 protected Handle handle;
621
622
623 }
|