SchemaAnnotationEditor.java
0001 /*
0002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
0003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004  *
0005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0006  *  software, licenced under the GNU Library General Public License,
0007  *  Version 2, June 1991 (in the distribution as file licence.html,
0008  *  and also available at http://gate.ac.uk/gate/licence.html).
0009  *
0010  *  AnnotationEditor.java
0011  *
0012  *  Valentin Tablan, Sep 10, 2007
0013  *
0014  *  $Id: SchemaAnnotationEditor.java 13631 2011-04-06 11:41:56Z ian_roberts $
0015  */
0016 
0017 
0018 package gate.gui.annedit;
0019 
0020 import java.awt.*;
0021 import java.awt.event.*;
0022 import java.util.*;
0023 import java.util.List;
0024 
0025 import javax.swing.*;
0026 import javax.swing.border.Border;
0027 import javax.swing.event.*;
0028 import javax.swing.text.BadLocationException;
0029 import javax.swing.text.JTextComponent;
0030 
0031 import gate.*;
0032 import gate.creole.*;
0033 import gate.event.*;
0034 import gate.gui.MainFrame;
0035 import gate.swing.JChoice;
0036 import gate.util.*;
0037 
0038 public class SchemaAnnotationEditor extends AbstractVisualResource 
0039     implements OwnedAnnotationEditor{
0040 
0041   private static final long serialVersionUID = 1L;
0042 
0043   public void editAnnotation(Annotation ann, AnnotationSet set) {
0044     //the external components we listen to (the text and the list view) can
0045     //change outside of our control, so we need to update the values frequently
0046     updateListeners();
0047     
0048     this.annotation = ann;
0049     this.annSet = set;
0050     //update the selection in the list view
0051     //this is necessary because sometimes the call to eidtAnnotaiton is 
0052     //internally received from the search and annotate function.
0053     //update the editor display
0054     String annType = annotation == null null : annotation.getType();
0055     //update the border for the types choice
0056     if(annType == null){
0057       //no annotation -> ok
0058       if(typesChoice.getBorder() != typeDefaultBorder)
0059         typesChoice.setBorder(typeDefaultBorder);
0060     }else{
0061       if(schemasByType.containsKey(annType)){
0062         //accepted type
0063         if(typesChoice.getBorder() != typeDefaultBorder)
0064           typesChoice.setBorder(typeDefaultBorder);
0065       }else{
0066         //wrong type
0067         if(typesChoice.getBorder() != typeHighlightBorder)
0068           typesChoice.setBorder(typeHighlightBorder);
0069         
0070       }
0071     }
0072     //update the features editor
0073     SchemaFeaturesEditor newFeaturesEditor = featureEditorsByType.get(annType);
0074     //if new type, we need to change the features editor and selected type 
0075     //button
0076     if(newFeaturesEditor != featuresEditor){
0077       typesChoice.setSelectedItem(annType);
0078       if(featuresEditor != null){
0079         featuresBox.remove(featuresEditor);
0080         featuresEditor.editFeatureMap(null);
0081       }
0082       featuresEditor = newFeaturesEditor;
0083       if(featuresEditor != null){
0084         featuresBox.add(featuresEditor);
0085       }
0086     }
0087     if(featuresEditor != null){
0088       FeatureMap features = ann.getFeatures();
0089       if(features == null){
0090         features = Factory.newFeatureMap();
0091         ann.setFeatures(features);
0092       }
0093       featuresEditor.editFeatureMap(features);
0094     }
0095     // enable editing if there is an annotation, disable if not
0096     setEditingEnabled(annType != null);
0097     if(dialog != null){
0098       if(annotation != null){
0099         placeDialog(annotation.getStartNode().getOffset().intValue(),
0100                     annotation.getEndNode().getOffset().intValue());
0101       }else{
0102         //this should only occur when the dialog is pinned, so offsets are 
0103         //irrelevant
0104         placeDialog(0,0);        
0105       }
0106     }
0107   }
0108 
0109   /**
0110    * This editor implementation is designed to enforce schema compliance. This
0111    * method will return <tt>false</tt> if the current annotation type does not
0112    * have a schema or if the features of the current annotation do not comply
0113    * with the schema. 
0114    @see gate.gui.annedit.OwnedAnnotationEditor#editingFinished()
0115    */
0116   public boolean editingFinished() {
0117     if(annotation == nullreturn true;
0118     //if the dialog is hidden, we've missed the train and we can't force 
0119     //compliance for the old annotation any more. Just give up and
0120     //allow further editing
0121     if(!dialog.isVisible()) return true;
0122     
0123     if(!schemasByType.containsKey(annotation.getType())) return false;
0124     
0125     //we need to check that:
0126     //1) all required features have values
0127     //2) all features known by schema that have values, comply with the schema
0128     if(annotation == nullreturn true;
0129     AnnotationSchema aSchema = schemasByType.get(annotation.getType());
0130     if(aSchema.getFeatureSchemaSet() == null ||
0131        aSchema.getFeatureSchemaSet().isEmpty()){
0132       //known type but no schema restrictions -> OK
0133       return true;
0134     }
0135     FeatureMap annotationFeatures = annotation.getFeatures();
0136     Map<String, FeatureSchema> featureSchemaByName = 
0137       new HashMap<String, FeatureSchema>()
0138     //store all the feature schemas, and check the required ones
0139     for(FeatureSchema aFeatureSchema : aSchema.getFeatureSchemaSet()){
0140       featureSchemaByName.put(aFeatureSchema.getFeatureName(), aFeatureSchema);
0141       Object featureValue = annotationFeatures == null null :
0142         annotationFeatures.get(aFeatureSchema.getFeatureName());
0143       if(aFeatureSchema.isRequired() && featureValue == nullreturn false;
0144     }
0145     //check all the actual values for compliance
0146     for(Object featureName : annotationFeatures.keySet()){
0147       Object featureValue = annotationFeatures.get(featureName);
0148       FeatureSchema fSchema = featureSchemaByName.get(featureName);
0149       if(fSchema != null){
0150         //this is a schema feature
0151         if(fSchema.getFeatureValueClass().equals(Boolean.class||
0152            fSchema.getFeatureValueClass().equals(Integer.class||
0153            fSchema.getFeatureValueClass().equals(Short.class||
0154            fSchema.getFeatureValueClass().equals(Byte.class||
0155            fSchema.getFeatureValueClass().equals(Float.class||
0156            fSchema.getFeatureValueClass().equals(Double.class)){
0157           //just check the right type
0158           if(!fSchema.getFeatureValueClass().isAssignableFrom(
0159                  featureValue.getClass())){
0160            //invalid value type
0161            return false;
0162           }
0163         }else if(fSchema.getFeatureValueClass().equals(String.class)){
0164           if(fSchema.getPermittedValues() != null &&
0165             !fSchema.getPermittedValues().contains(featureValue)){
0166             //invalid value
0167             return false;
0168           }
0169         }
0170       }
0171     }
0172     return true;
0173   }
0174 
0175   /**
0176    * Does nothing, as this editor does not support cancelling and rollbacks.
0177    */
0178   public void cancelAction() throws GateException {
0179   }
0180 
0181   /**
0182    * Returns <tt>true</tt> always as this editor is generic and can edit any
0183    * annotation type.
0184    */
0185   public boolean canDisplayAnnotationType(String annotationType) {
0186     return true;
0187   }
0188 
0189   /**
0190    * Does nothing as this editor works in auto-commit mode (changes are 
0191    * implemented immediately).
0192    */
0193   public void okAction() throws GateException {
0194   }
0195 
0196   /**
0197    * Returns <tt>false</tt>, as this editor does not support cancel operations.
0198    */
0199   public boolean supportsCancel() {
0200     return false;
0201   }
0202 
0203   /* (non-Javadoc)
0204    * @see gate.gui.annedit.AnnotationEditor#isActive()
0205    */
0206   public boolean isActive() {
0207     return dialog.isVisible();
0208   }
0209 
0210   /**
0211    * Finds the best location for the editor dialog for a given span of text
0212    */
0213   public void placeDialog(int start, int end){
0214     if(pinnedButton.isSelected()){
0215       //just resize
0216       Point where = null;
0217       if(dialog.isVisible()){
0218 //        where = dialog.getLocationOnScreen();
0219         where = dialog.getLocation();
0220       }
0221       
0222       dialog.pack();
0223       if(where != null){
0224         dialogLocation.move(where.x, where.y);
0225         dialog.setLocation(dialogLocation);
0226       }
0227     }else{
0228       //calculate position
0229       try{
0230         Rectangle startRect = owner.getTextComponent().modelToView(start);
0231         Rectangle endRect = owner.getTextComponent().modelToView(end);
0232         Point topLeft = owner.getTextComponent().getLocationOnScreen();
0233         int x = topLeft.x + startRect.x;
0234         int y = topLeft.y + endRect.y + endRect.height;
0235 
0236         //make sure the window doesn't start lower 
0237         //than the end of the visible rectangle
0238         Rectangle visRect = owner.getTextComponent().getVisibleRect();
0239         int maxY = topLeft.y + visRect.y + visRect.height;      
0240         
0241         //make sure window doesn't get off-screen       
0242         dialog.pack();
0243 //        dialog.validate();
0244         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
0245         boolean revalidate = false;
0246         if(dialog.getSize().width > screenSize.width){
0247           dialog.setSize(screenSize.width, dialog.getSize().height);
0248           revalidate = true;
0249         }
0250         if(dialog.getSize().height > screenSize.height){
0251           dialog.setSize(dialog.getSize().width, screenSize.height);
0252           revalidate = true;
0253         }
0254         
0255         if(revalidatedialog.validate();
0256         //calculate max X
0257         int maxX = screenSize.width - dialog.getSize().width;
0258         //calculate max Y
0259         if(maxY + dialog.getSize().height > screenSize.height){
0260           maxY = screenSize.height - dialog.getSize().height;
0261         }
0262         
0263         //correct position
0264         if(y > maxYy = maxY;
0265         if(x > maxXx = maxX;
0266         dialogLocation.move(x, y);
0267         dialog.setLocation(dialogLocation);
0268       }catch(BadLocationException ble){
0269         //this should never occur
0270         throw new GateRuntimeException(ble);
0271       }
0272     }
0273     if(!dialog.isVisible()) dialog.setVisible(true);
0274   }
0275   
0276   protected static final int HIDE_DELAY = 1500;
0277   protected static final int SHIFT_INCREMENT = 5;
0278   protected static final int CTRL_SHIFT_INCREMENT = 10;
0279 
0280   
0281   /**
0282    * The annotation currently being edited.
0283    */
0284   protected Annotation annotation;
0285   
0286   /**
0287    * The annotation set containing the currently edited annotation. 
0288    */
0289   protected AnnotationSet annSet;
0290   
0291   /**
0292    * The controlling object for this editor.
0293    */
0294   private AnnotationEditorOwner owner;
0295   
0296   /**
0297    * The text component (obtained from the owner) that this editor listens to.
0298    */
0299   private JTextComponent textComponent;
0300   
0301   /**
0302    * JChoice used for selecting the annotation type.
0303    */
0304   protected JChoice typesChoice;
0305   
0306   /**
0307    * The default border for the types choice
0308    */
0309   protected Border typeDefaultBorder;
0310   
0311   /**
0312    * The highlight border for the types choice
0313    */
0314   protected Border typeHighlightBorder;
0315   
0316   /**
0317    * The dialog used to show this annotation editor.
0318    */
0319   protected JDialog dialog;
0320   
0321   protected CreoleListener creoleListener;
0322   
0323   /**
0324    * Listener used to hide the editing window when the text is hidden.
0325    */
0326   protected AncestorListener textAncestorListener;
0327   
0328   
0329   /**
0330    * Stores the Annotation schema objects available in the system.
0331    * The annotation types are used as keys for the map.
0332    */
0333   protected Map<String, AnnotationSchema> schemasByType;
0334   
0335   /**
0336    * Caches the features editor for each annotation type.
0337    */
0338   protected Map<String, SchemaFeaturesEditor> featureEditorsByType;
0339   
0340   /**
0341    * The box used to host the features editor pane.
0342    */
0343   protected Box featuresBox;
0344   
0345   /**
0346    * Toggle button used to pin down the dialog. 
0347    */
0348   protected JToggleButton pinnedButton;
0349   
0350   /**
0351    * The current features editor, one of the ones stored in 
0352    {@link #featureEditorsByType}.
0353    */
0354   protected SchemaFeaturesEditor featuresEditor = null;
0355 
0356   protected MouseEvent pressed;
0357 
0358 
0359   public SchemaAnnotationEditor(){
0360     initData();
0361   }
0362   
0363   
0364   /* (non-Javadoc)
0365    * @see gate.creole.AbstractVisualResource#init()
0366    */
0367   @Override
0368   public Resource init() throws ResourceInstantiationException {
0369     super.init();
0370     initGui();
0371     initListeners();
0372     return this;
0373   }
0374 
0375   protected void updateListeners(){
0376     if(owner != null){
0377       //we have a new owner
0378       //if the components that we listen to have changed, we need to update the 
0379       //listeners
0380       if(textComponent != getOwner().getTextComponent()){
0381         //remove old listener
0382         if(textComponent != null){
0383           textComponent.removeAncestorListener(textAncestorListener);
0384         }
0385         this.textComponent = owner.getTextComponent();
0386         //register new listener
0387        if(textComponent != null){
0388          textComponent.addAncestorListener(textAncestorListener);
0389        }
0390       }
0391     }else{
0392       //no new owner -> just remove old listeners
0393       if(textComponent != null){
0394         textComponent.removeAncestorListener(textAncestorListener);
0395       }
0396     }
0397   }
0398   
0399   
0400   protected void initData(){
0401     schemasByType = new TreeMap<String, AnnotationSchema>();
0402     for(LanguageResource aSchema : Gate.getCreoleRegister().
0403         getLrInstances("gate.creole.AnnotationSchema")){
0404       schemasByType.put(((AnnotationSchema)aSchema).getAnnotationName()
0405               (AnnotationSchema)aSchema);
0406     }
0407     creoleListener = new CreoleListener(){
0408       public void resourceLoaded(CreoleEvent e){
0409         Resource newResource =  e.getResource();
0410         if(newResource instanceof AnnotationSchema){
0411           AnnotationSchema aSchema = (AnnotationSchema)newResource;
0412           schemasByType.put(aSchema.getAnnotationName(), aSchema);
0413         }
0414       }
0415       
0416       public void resourceUnloaded(CreoleEvent e){
0417         Resource newResource =  e.getResource();
0418         if(newResource instanceof AnnotationSchema){
0419           AnnotationSchema aSchema = (AnnotationSchema)newResource;
0420           if(schemasByType.containsValue(aSchema)){
0421             schemasByType.remove(aSchema.getAnnotationName());
0422           }
0423         }
0424       }
0425       
0426       public void datastoreOpened(CreoleEvent e){
0427         
0428       }
0429       public void datastoreCreated(CreoleEvent e){
0430         
0431       }
0432       public void datastoreClosed(CreoleEvent e){
0433         
0434       }
0435       public void resourceRenamed(Resource resource,
0436                               String oldName,
0437                               String newName){
0438       }  
0439     };
0440     Gate.getCreoleRegister().addCreoleListener(creoleListener)
0441     
0442     textAncestorListener = new AncestorListener(){
0443       /**
0444        * A flag used to mark the fact that the dialog is active and was hidden 
0445        * by this listener.
0446        */
0447       private boolean dialogActive = false;
0448       
0449       public void ancestorAdded(AncestorEvent event) {
0450         if(dialogActive){
0451           if(annotation != null){
0452             placeDialog(annotation.getStartNode().getOffset().intValue(),
0453                     annotation.getEndNode().getOffset().intValue());
0454           }
0455           dialogActive = false;
0456         }
0457       }
0458       public void ancestorMoved(AncestorEvent event) {
0459         if(dialog.isVisible() && annotation != null){
0460           placeDialog(annotation.getStartNode().getOffset().intValue(),
0461                   annotation.getEndNode().getOffset().intValue());
0462         }
0463       }
0464       
0465       public void ancestorRemoved(AncestorEvent event) {
0466         if(dialog.isVisible()){
0467           dialogActive = true;
0468           dialog.setVisible(false);
0469         }
0470       }
0471     };
0472 
0473   }  
0474   
0475   public void cleanup(){
0476     Gate.getCreoleRegister().removeCreoleListener(creoleListener);
0477   }
0478   
0479   protected void initGui(){
0480 
0481     //make the dialog
0482     Window parentWindow =
0483       SwingUtilities.windowForComponent(owner.getTextComponent());
0484     if(parentWindow != null){
0485       dialog = parentWindow instanceof Frame ?
0486                 new JDialog((Frame)parentWindow, 
0487                         "Annotation Editor Dialog"false:
0488                 new JDialog((Dialog)parentWindow, 
0489                         "Annotation Editor Dialog"false);
0490       dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
0491       MainFrame.getGuiRoots().add(dialog);
0492       
0493     }
0494 
0495     setLayout(new BorderLayout());
0496 
0497     //build the toolbar
0498     JPanel tBar = new JPanel();
0499     tBar.setLayout(new GridBagLayout());
0500     GridBagConstraints constraints = new GridBagConstraints();
0501     constraints.gridx = GridBagConstraints.RELATIVE;
0502     constraints.gridy = 0;
0503     constraints.weightx = 0;
0504     
0505     solButton = new IconOnlyButton(null);
0506     solButton.setIcon(MainFrame.getIcon("bounds-sol"));
0507     solButton.setPressedIcon(MainFrame.getIcon("bounds-sol-pressed"));
0508     tBar.add(solButton, constraints);
0509     JLabel aLabel = new JLabel(MainFrame.getIcon("bounds-left"));
0510     aLabel.setBorder(null);
0511     tBar.add(aLabel, constraints);
0512     sorButton = new IconOnlyButton(null);
0513     sorButton.setIcon(MainFrame.getIcon("bounds-sor"));
0514     sorButton.setPressedIcon(MainFrame.getIcon("bounds-sor-pressed"));
0515     tBar.add(sorButton, constraints);
0516     aLabel = new JLabel(MainFrame.getIcon("bounds-span"));
0517     aLabel.setBorder(null);
0518     tBar.add(aLabel, constraints);
0519     eolButton = new IconOnlyButton(null);
0520     eolButton.setIcon(MainFrame.getIcon("bounds-eol"));
0521     eolButton.setPressedIcon(MainFrame.getIcon("bounds-eol-pressed"));
0522     tBar.add(eolButton, constraints);
0523     aLabel = new JLabel(MainFrame.getIcon("bounds-right"));
0524     aLabel.setBorder(null);
0525     tBar.add(aLabel, constraints);
0526     eorButton = new IconOnlyButton(null);
0527     eorButton.setIcon(MainFrame.getIcon("bounds-eor"));
0528     eorButton.setPressedIcon(MainFrame.getIcon("bounds-eor-pressed"));
0529     tBar.add(eorButton, constraints);
0530     
0531     tBar.add(Box.createHorizontalStrut(15), constraints);
0532     tBar.add(delButton = new SmallButton(null), constraints);
0533     constraints.weightx = 1;
0534     tBar.add(Box.createHorizontalGlue(), constraints);
0535     
0536     constraints.weightx = 0;
0537     pinnedButton = new JToggleButton(MainFrame.getIcon("pin"));
0538     pinnedButton.setSelectedIcon(MainFrame.getIcon("pin-in"));
0539     pinnedButton.setSelected(false);
0540     pinnedButton.setToolTipText("Press to pin window in place.");
0541     pinnedButton.setMargin(new Insets(0202));
0542     pinnedButton.setBorderPainted(false);
0543     pinnedButton.setContentAreaFilled(false);
0544     tBar.add(pinnedButton);
0545 
0546     add(tBar, BorderLayout.NORTH);
0547     
0548     //build the main pane
0549     mainPane = new JPanel();
0550     mainPane.setLayout(new BorderLayout());
0551     
0552     featureEditorsByType = new HashMap<String, SchemaFeaturesEditor>();
0553     //for each schema we need to create a type button and a features editor
0554     for(String annType : schemasByType.keySet()){
0555       AnnotationSchema annSchema = schemasByType.get(annType);
0556       SchemaFeaturesEditor aFeaturesEditor = new SchemaFeaturesEditor(annSchema);
0557       featureEditorsByType.put(annType, aFeaturesEditor);
0558     }
0559     List<String> typeList = new ArrayList<String>(schemasByType.keySet());
0560     Collections.sort(typeList);
0561     String[] typesArray = new String[typeList.size()];
0562     typeList.toArray(typesArray);
0563     typesChoice = new JChoice(typesArray);
0564     typesChoice.setDefaultButtonMargin(new Insets(0202));
0565     typesChoice.setMaximumFastChoices(20);
0566     typesChoice.setMaximumWidth(300);
0567     String aTitle = "Type ";
0568     
0569     Border titleBorder = BorderFactory.createTitledBorder(aTitle);
0570     typeDefaultBorder = BorderFactory.createCompoundBorder(titleBorder, 
0571             BorderFactory.createEmptyBorder(2222));
0572     typeHighlightBorder = BorderFactory.createCompoundBorder(titleBorder, 
0573             BorderFactory.createLineBorder(Color.RED, 2));
0574     
0575     typesChoice.setBorder(typeDefaultBorder);
0576     aLabel = new JLabel(aTitle);
0577     typesChoice.setMinimumSize(
0578             new Dimension(aLabel.getPreferredSize().width, 0));
0579     mainPane.add(typesChoice, BorderLayout.NORTH);
0580 
0581     //add the features box
0582     featuresBox = Box.createVerticalBox();
0583     aTitle = "Features "
0584     featuresBox.setBorder(BorderFactory.createTitledBorder(aTitle));
0585     aLabel = new JLabel(aTitle);
0586     mainPane.add(featuresBox, BorderLayout.SOUTH);
0587 
0588     add(mainPane, BorderLayout.CENTER);
0589 
0590     // add the search and annotate GUI at the bottom of the annotator editor
0591     SearchAndAnnotatePanel searchPanel =
0592       new SearchAndAnnotatePanel(mainPane.getBackground(), this, dialog);
0593 
0594     add(searchPanel, BorderLayout.SOUTH);
0595             
0596     dialog.add(this);
0597     dialog.pack();
0598   }
0599 
0600   protected void initListeners(){
0601     typesChoice.addActionListener(new ActionListener(){
0602       public void actionPerformed(ActionEvent e) {
0603         String newType;
0604         if(typesChoice.getSelectedItem() == null){
0605           newType = "";
0606         }else{
0607           newType = typesChoice.getSelectedItem().toString();
0608         }
0609         if(annotation != null && annSet != null && 
0610                 !annotation.getType().equals(newType)){
0611           //annotation type change
0612           Integer oldId = annotation.getId();
0613           Annotation oldAnn = annotation;
0614           annSet.remove(oldAnn);
0615           try{
0616             annSet.add(oldId, oldAnn.getStartNode().getOffset()
0617                     oldAnn.getEndNode().getOffset()
0618                     newType, oldAnn.getFeatures());
0619             Annotation newAnn = annSet.get(oldId)
0620             //update the selection to the new annotation
0621             getOwner().selectAnnotation(new AnnotationDataImpl(annSet, newAnn));            
0622             editAnnotation(newAnn, annSet);
0623             owner.annotationChanged(newAnn, annSet, oldAnn.getType());
0624           }catch(InvalidOffsetException ioe){
0625             //this should never happen 
0626             throw new LuckyException(ioe);
0627           }
0628         }
0629       }
0630     });
0631 
0632     dialog.addWindowListener(new WindowAdapter(){
0633       public void windowClosing(WindowEvent e) {
0634         if(editingFinished()){
0635           //we can close
0636           dialog.setVisible(false);
0637           if(pinnedButton.isSelected())pinnedButton.setSelected(false);
0638         }else{
0639           //let's be really snotty
0640           getToolkit().beep();
0641         }
0642       }
0643     });
0644 
0645     dialog.getRootPane().addMouseListener(new MouseAdapter() {
0646       // allow dialog to be dragged with a mouse
0647       public void mousePressed(MouseEvent me) {
0648         pressed = me;
0649       }
0650     });
0651 
0652     dialog.getRootPane().addMouseMotionListener(new MouseMotionAdapter() {
0653       Point location;
0654       // allow a dialog to be dragged with a mouse
0655       public void mouseDragged(MouseEvent me) {
0656         location = dialog.getLocation(location);
0657         int x = location.x - pressed.getX() + me.getX();
0658         int y = location.y - pressed.getY() + me.getY();
0659         dialog.setLocation(x, y);
0660         pinnedButton.setSelected(true);
0661        }
0662     });
0663     
0664     dialog.addComponentListener(new ComponentAdapter(){
0665       /* (non-Javadoc)
0666        * @see java.awt.event.ComponentAdapter#componentMoved(java.awt.event.ComponentEvent)
0667        */
0668       @Override
0669       public void componentMoved(ComponentEvent e) {
0670         Point newLocation =  dialog.getLocation();
0671         if(!newLocation.equals(dialogLocation)){
0672           pinnedButton.setSelected(true);
0673         }
0674       }
0675     });
0676 
0677     InputMap inputMap = ((JComponent)dialog.getContentPane()).
0678         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0679     actionMap = ((JComponent)dialog.getContentPane()).getActionMap();
0680     // add the key-action bindings of this Component to the parent window
0681 
0682     solAction =
0683       new StartOffsetLeftAction("", MainFrame.getIcon("extend-left"),
0684       "<html><b>Extend start</b><small>" +
0685       "<br>LEFT = 1 character" +
0686       "<br> + SHIFT = 5 characters, "+
0687       "<br> + CTRL + SHIFT = 10 characters</small></html>",
0688        KeyEvent.VK_LEFT);
0689     solButton.setAction(solAction);
0690     inputMap.put(KeyStroke.getKeyStroke("LEFT")"solAction");
0691     inputMap.put(KeyStroke.getKeyStroke("shift LEFT")"solAction");
0692     inputMap.put(KeyStroke.getKeyStroke("control shift released LEFT")"solAction");
0693     actionMap.put("solAction", solAction);
0694 
0695    sorAction =
0696      new StartOffsetRightAction("", MainFrame.getIcon("extend-right"),
0697       "<html><b>Shrink start</b><small>" +
0698       "<br>RIGHT = 1 character" +
0699       "<br> + SHIFT = 5 characters, "+
0700       "<br> + CTRL + SHIFT = 10 characters</small></html>",
0701       KeyEvent.VK_RIGHT);
0702     sorButton.setAction(sorAction);
0703     inputMap.put(KeyStroke.getKeyStroke("RIGHT")"sorAction");
0704     inputMap.put(KeyStroke.getKeyStroke("shift RIGHT")"sorAction");
0705     inputMap.put(KeyStroke.getKeyStroke("control shift released RIGHT")"sorAction");
0706     actionMap.put("sorAction", sorAction);
0707 
0708     delAction =
0709       new DeleteAnnotationAction("", MainFrame.getIcon("remove-annotation"),
0710       "Delete the annotation", KeyEvent.VK_DELETE);
0711     delButton.setAction(delAction);
0712     inputMap.put(KeyStroke.getKeyStroke("alt DELETE")"delAction");
0713     actionMap.put("delAction", delAction);
0714 
0715     eolAction =
0716       new EndOffsetLeftAction("", MainFrame.getIcon("extend-left"),
0717       "<html><b>Shrink end</b><small>" +
0718       "<br>ALT + LEFT = 1 character" +
0719       "<br> + SHIFT = 5 characters, "+
0720       "<br> + CTRL + SHIFT = 10 characters</small></html>",
0721        KeyEvent.VK_LEFT);
0722     eolButton.setAction(eolAction);
0723     inputMap.put(KeyStroke.getKeyStroke("alt LEFT")"eolAction");
0724     inputMap.put(KeyStroke.getKeyStroke("alt shift LEFT")"eolAction");
0725     inputMap.put(KeyStroke.getKeyStroke("control alt shift released LEFT")"eolAction");
0726     actionMap.put("eolAction", eolAction);
0727 
0728     eorAction =
0729       new EndOffsetRightAction("", MainFrame.getIcon("extend-right"),
0730       "<html><b>Extend end</b><small>" +
0731       "<br>ALT + RIGHT = 1 character" +
0732       "<br> + SHIFT = 5 characters, "+
0733       "<br> + CTRL + SHIFT = 10 characters</small></html>",
0734       KeyEvent.VK_RIGHT);
0735     eorButton.setAction(eorAction);
0736     inputMap.put(KeyStroke.getKeyStroke("alt RIGHT")"eorAction");
0737     inputMap.put(KeyStroke.getKeyStroke("alt shift RIGHT")"eorAction");
0738     inputMap.put(KeyStroke.getKeyStroke("control alt shift released RIGHT")"eorAction");
0739     actionMap.put("eorAction", eorAction);
0740 
0741     Action dismissAction = new AbstractAction() {
0742       private static final long serialVersionUID = 1L;
0743       public void actionPerformed(ActionEvent evt){
0744         dialog.setVisible(false);
0745       }
0746     };
0747     inputMap.put(KeyStroke.getKeyStroke("ESCAPE")"dismissAction");
0748     actionMap.put("dismissAction", dismissAction);
0749 
0750   }
0751 
0752   /**
0753    * Stores the currently set dialog location (which is used to identify cases
0754    * when the dialog was moved by hand, which causes the dialog to be pinned).
0755    */
0756   private Point dialogLocation = new Point(00);
0757   
0758   
0759   /**
0760    @param args
0761    */
0762   public static void main(String[] args) {
0763     try {
0764       Gate.init();
0765       
0766       
0767       JFrame aFrame = new JFrame("New Annotation Editor");
0768       aFrame.setSize800600);
0769       aFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
0770       
0771       JDialog annDialog = new JDialog(aFrame, "Annotation Editor Dialog"false);
0772       annDialog.setFocusableWindowState(false);
0773 //      annDialog.setResizable(false);
0774 //      annDialog.setUndecorated(true);
0775       
0776       SchemaAnnotationEditor pane = new SchemaAnnotationEditor();
0777       annDialog.add(pane);
0778       annDialog.pack();
0779       
0780 //      JToolBar tBar = new JToolBar("Annotation Editor", JToolBar.HORIZONTAL);
0781 //      tBar.setLayout(new BorderLayout());
0782 //      tBar.setMinimumSize(tBar.getPreferredSize());
0783 //      tBar.add(pane);
0784 //      aFrame.getContentPane().add(tBar, BorderLayout.NORTH);
0785       
0786       StringBuffer strBuf = new StringBuffer();
0787       for(int i = 0; i < 100; i++){
0788         strBuf.append("The quick brown fox jumped over the lazy dog.\n");
0789       }
0790       JTextArea aTextPane = new JTextArea(strBuf.toString());
0791       JScrollPane scroller = new JScrollPane(aTextPane);
0792       aFrame.getContentPane().add(scroller, BorderLayout.CENTER);
0793       
0794 //    Box aBox = Box.createVerticalBox();
0795 //    aFrame.getContentPane().add(aBox);
0796 //    
0797 //    FeatureEditor aFeatEditor = new FeatureEditor("F-nominal-small",
0798 //            FeatureType.nominal, "val1");
0799 //    aFeatEditor.setValues(new String[]{"val1", "val2", "val3"});
0800 //    aBox.add(aFeatEditor.getGui());
0801 //    
0802 //    aFeatEditor = new FeatureEditor("F-nominal-large",
0803 //            FeatureType.nominal, "val1");
0804 //    aFeatEditor.setValues(new String[]{"val1", "val2", "val3", "val4", "val5", 
0805 //            "val6", "val7", "val8", "val9"});
0806 //    aBox.add(aFeatEditor.getGui());
0807 //    
0808 //    aFeatEditor = new FeatureEditor("F-boolean-true",
0809 //            FeatureType.bool, "true");
0810 //    aBox.add(aFeatEditor.getGui());    
0811 //    
0812 //    aFeatEditor = new FeatureEditor("F-boolean-false",
0813 //            FeatureType.bool, "false");
0814 //    aBox.add(aFeatEditor.getGui());
0815       
0816       aFrame.setVisible(true);
0817 System.out.println("Window up");
0818       annDialog.setVisible(true);
0819       System.out.println("Dialog up");      
0820       
0821     }catch(HeadlessException e) {
0822       e.printStackTrace();
0823     }
0824     catch(GateException e) {
0825       e.printStackTrace();
0826     }
0827   }
0828 
0829   /**
0830    * Base class for actions on annotations.
0831    */
0832   protected abstract class AnnotationAction extends AbstractAction{
0833     public AnnotationAction(String text, Icon icon,
0834                             String desc, int mnemonic){
0835       super(text, icon);
0836       putValue(SHORT_DESCRIPTION, desc);
0837       putValue(MNEMONIC_KEY, mnemonic);
0838     }
0839   }
0840 
0841   protected class StartOffsetLeftAction extends AnnotationAction{
0842     private static final long serialVersionUID = 1L;
0843     public StartOffsetLeftAction(String text, Icon icon,
0844                                  String desc, int mnemonic) {
0845       super(text, icon, desc, mnemonic);
0846     }
0847     public void actionPerformed(ActionEvent evt){
0848       int increment = 1;
0849       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
0850         //CTRL pressed -> use tokens for advancing
0851         increment = SHIFT_INCREMENT;
0852         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
0853           increment = CTRL_SHIFT_INCREMENT;
0854         }
0855       }
0856       long newValue = annotation.getStartNode().getOffset().longValue() - increment;
0857       if(newValue < 0newValue = 0;
0858       try{
0859         moveAnnotation(annSet, annotation, new Long(newValue)
0860                 annotation.getEndNode().getOffset());
0861       }catch(InvalidOffsetException ioe){
0862         throw new GateRuntimeException(ioe);
0863       }
0864     }
0865   }
0866   
0867   protected class StartOffsetRightAction extends AnnotationAction{
0868     private static final long serialVersionUID = 1L;
0869     public StartOffsetRightAction(String text, Icon icon,
0870                                  String desc, int mnemonic) {
0871       super(text, icon, desc, mnemonic);
0872     }
0873     public void actionPerformed(ActionEvent evt){
0874       long endOffset = annotation.getEndNode().getOffset().longValue()
0875       int increment = 1;
0876       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
0877         //CTRL pressed -> use tokens for advancing
0878         increment = SHIFT_INCREMENT;
0879         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
0880           increment = CTRL_SHIFT_INCREMENT;
0881         }
0882       }
0883       
0884       long newValue = annotation.getStartNode().getOffset().longValue()  + increment;
0885       if(newValue > endOffsetnewValue = endOffset;
0886       try{
0887         moveAnnotation(annSet, annotation, new Long(newValue)
0888                 annotation.getEndNode().getOffset());
0889       }catch(InvalidOffsetException ioe){
0890         throw new GateRuntimeException(ioe);
0891       }
0892     }
0893   }
0894 
0895   protected class EndOffsetLeftAction extends AnnotationAction{
0896     private static final long serialVersionUID = 1L;
0897     public EndOffsetLeftAction(String text, Icon icon,
0898                                  String desc, int mnemonic) {
0899       super(text, icon, desc, mnemonic);
0900     }
0901     public void actionPerformed(ActionEvent evt){
0902       long startOffset = annotation.getStartNode().getOffset().longValue()
0903       int increment = 1;
0904       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
0905         //CTRL pressed -> use tokens for advancing
0906         increment = SHIFT_INCREMENT;
0907         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
0908           increment =CTRL_SHIFT_INCREMENT;
0909         }
0910       }
0911       
0912       long newValue = annotation.getEndNode().getOffset().longValue()  - increment;
0913       if(newValue < startOffsetnewValue = startOffset;
0914       try{
0915         moveAnnotation(annSet, annotation, annotation.getStartNode().getOffset()
0916                 new Long(newValue));
0917       }catch(InvalidOffsetException ioe){
0918         throw new GateRuntimeException(ioe);
0919       }
0920     }
0921   }
0922   
0923   protected class EndOffsetRightAction extends AnnotationAction{
0924     private static final long serialVersionUID = 1L;
0925     public EndOffsetRightAction(String text, Icon icon,
0926                                  String desc, int mnemonic) {
0927       super(text, icon, desc, mnemonic);
0928     }
0929     public void actionPerformed(ActionEvent evt){
0930       long maxOffset = owner.getDocument().
0931           getContent().size().longValue() -1
0932       int increment = 1;
0933       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
0934         //CTRL pressed -> use tokens for advancing
0935         increment = SHIFT_INCREMENT;
0936         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
0937           increment = CTRL_SHIFT_INCREMENT;
0938         }
0939       }
0940       long newValue = annotation.getEndNode().getOffset().longValue() + increment;
0941       if(newValue > maxOffsetnewValue = maxOffset;
0942       try{
0943         moveAnnotation(annSet, annotation, annotation.getStartNode().getOffset(),
0944                 new Long(newValue));
0945       }catch(InvalidOffsetException ioe){
0946         throw new GateRuntimeException(ioe);
0947       }
0948     }
0949   }
0950 
0951   protected class DeleteAnnotationAction extends AnnotationAction{
0952     private static final long serialVersionUID = 1L;
0953     public DeleteAnnotationAction(String text, Icon icon,
0954                                  String desc, int mnemonic) {
0955       super(text, icon, desc, mnemonic);
0956     }
0957     public void actionPerformed(ActionEvent evt){
0958       annSet.remove(annotation);
0959 
0960       //clear the dialog
0961       editAnnotation(null, annSet);
0962 
0963       if(!pinnedButton.isSelected()){
0964         //if not pinned, hide the dialog.
0965         dialog.setVisible(false);
0966       else {
0967         setEditingEnabled(false);
0968       }
0969     }
0970   }
0971 
0972   /**
0973    * Changes the span of an existing annotation by creating a new annotation 
0974    * with the same ID, type and features but with the new start and end offsets.
0975    @param set the annotation set 
0976    @param oldAnnotation the annotation to be moved
0977    @param newStartOffset the new start offset
0978    @param newEndOffset the new end offset
0979    */
0980   protected void moveAnnotation(AnnotationSet set, Annotation oldAnnotation, 
0981           Long newStartOffset, Long newEndOffsetthrows InvalidOffsetException{
0982     //Moving is done by deleting the old annotation and creating a new one.
0983     //If this was the last one of one type it would mess up the gui which 
0984     //"forgets" about this type and then it recreates it (with a different 
0985     //colour and not visible.
0986     //In order to avoid this problem, we'll create a new temporary annotation.
0987     Annotation tempAnn = null;
0988     if(set.get(oldAnnotation.getType()).size() == 1){
0989       //create a clone of the annotation that will be deleted, to act as a 
0990       //placeholder 
0991       Integer tempAnnId = set.add(oldAnnotation.getStartNode()
0992               oldAnnotation.getStartNode(), oldAnnotation.getType()
0993               oldAnnotation.getFeatures());
0994       tempAnn = set.get(tempAnnId);
0995     }
0996 
0997     Integer oldID = oldAnnotation.getId();
0998     set.remove(oldAnnotation);
0999     set.add(oldID, newStartOffset, newEndOffset,
1000             oldAnnotation.getType(), oldAnnotation.getFeatures());
1001     Annotation newAnn = set.get(oldID);
1002     //update the selection to the new annotation
1003     getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
1004     editAnnotation(newAnn, set);
1005     //remove the temporary annotation
1006     if(tempAnn != nullset.remove(tempAnn);
1007     owner.annotationChanged(newAnn, set, null);
1008   }  
1009 
1010   /**
1011    * A JButton with content are not filled and border not painted (in order to
1012    * save screen real estate)
1013    */  
1014   protected class SmallButton extends JButton{
1015 
1016    private static final long serialVersionUID = 1L;
1017 
1018     public SmallButton(Action a) {
1019       super(a);
1020 //      setBorder(null);
1021       setMargin(new Insets(0202));
1022 //      setBorderPainted(false);
1023 //      setContentAreaFilled(false);
1024     }
1025   }
1026   
1027   protected class IconOnlyButton extends JButton{
1028 
1029     private static final long serialVersionUID = 1L;
1030 
1031     public IconOnlyButton(Action a) {
1032       super(a);
1033       setMargin(new Insets(0000));
1034       setBorder(null);
1035       setBorderPainted(false);
1036       setContentAreaFilled(false);
1037     }
1038   }
1039   
1040   protected IconOnlyButton solButton;
1041   protected IconOnlyButton sorButton;
1042   protected SmallButton delButton;
1043   protected IconOnlyButton eolButton;
1044   protected IconOnlyButton eorButton;
1045   protected JPanel mainPane;
1046 
1047   /**
1048    * Action bindings for the popup window.
1049    */
1050   ActionMap actionMap;
1051 
1052   private StartOffsetLeftAction solAction;
1053   private StartOffsetRightAction sorAction;
1054   private DeleteAnnotationAction delAction;
1055   private EndOffsetLeftAction eolAction;
1056   private EndOffsetRightAction eorAction;
1057 
1058   /**
1059    @return the owner
1060    */
1061   public AnnotationEditorOwner getOwner() {
1062     return owner;
1063   }
1064 
1065   /**
1066    @param owner the owner to set
1067    */
1068   public void setOwner(AnnotationEditorOwner owner) {
1069     //if the owner is new, register existing listeners to new owner elements
1070     if(this.owner != owner){  
1071       this.owner = owner;
1072       updateListeners();
1073     }
1074   }
1075 
1076   public AnnotationSet getAnnotationSetCurrentlyEdited() {
1077     return annSet;
1078   }
1079 
1080   public Annotation getAnnotationCurrentlyEdited() {
1081     return annotation;
1082   }
1083 
1084   public void setPinnedMode(boolean pinned) {
1085     pinnedButton.setSelected(pinned);
1086   }
1087 
1088   public void setEditingEnabled(boolean isEditingEnabled) {
1089     solButton.setEnabled(isEditingEnabled);
1090     sorButton.setEnabled(isEditingEnabled);
1091     delButton.setEnabled(isEditingEnabled);
1092     eolButton.setEnabled(isEditingEnabled);
1093     eorButton.setEnabled(isEditingEnabled);
1094     for (Component c : typesChoice.getComponents()) {
1095       c.setEnabled(isEditingEnabled);
1096     }
1097     // en/disable the components in the featuresBox
1098     Vector<Component> components = new Vector<Component>();
1099     Collections.addAll(components, featuresBox.getComponents());
1100     while (!components.isEmpty()) {
1101       Component component = components.remove(0);
1102       if (component instanceof JToggleButton
1103        || component instanceof JTextField) {
1104         component.setEnabled(isEditingEnabled);
1105       else if (component instanceof Container) {
1106         Collections.addAll(components,
1107           ((Container)component).getComponents());
1108       }
1109     }
1110     // enable/disable the key binding actions
1111     if (isEditingEnabled) {
1112       actionMap.put("solAction", solAction);
1113       actionMap.put("sorAction", sorAction);
1114       actionMap.put("delAction", delAction);
1115       actionMap.put("eolAction", eolAction);
1116       actionMap.put("eorAction", eorAction);
1117     else {
1118       actionMap.put("solAction"null);
1119       actionMap.put("sorAction"null);
1120       actionMap.put("delAction"null);
1121       actionMap.put("eolAction"null);
1122       actionMap.put("eorAction"null);
1123     }
1124   }
1125 
1126 }