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