AnnotationEditor.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, Apr 5, 2004
0013  
0014  * $Id: AnnotationEditor.java 17901 2014-04-24 12:59:58Z markagreenwood $
0015  */
0016 package gate.gui.docview;
0017 
0018 import gate.Annotation;
0019 import gate.AnnotationSet;
0020 import gate.Gate;
0021 import gate.LanguageResource;
0022 import gate.Resource;
0023 import gate.creole.AbstractVisualResource;
0024 import gate.creole.AnnotationSchema;
0025 import gate.creole.ResourceInstantiationException;
0026 import gate.event.CreoleEvent;
0027 import gate.event.CreoleListener;
0028 import gate.gui.FeaturesSchemaEditor;
0029 import gate.gui.MainFrame;
0030 import gate.gui.annedit.AnnotationDataImpl;
0031 import gate.gui.annedit.AnnotationEditorOwner;
0032 import gate.gui.annedit.OwnedAnnotationEditor;
0033 import gate.gui.annedit.SearchAndAnnotatePanel;
0034 import gate.util.GateException;
0035 import gate.util.GateRuntimeException;
0036 import gate.util.InvalidOffsetException;
0037 
0038 import java.awt.Color;
0039 import java.awt.Component;
0040 import java.awt.ComponentOrientation;
0041 import java.awt.Dimension;
0042 import java.awt.GridBagConstraints;
0043 import java.awt.GridBagLayout;
0044 import java.awt.Insets;
0045 import java.awt.Point;
0046 import java.awt.Rectangle;
0047 import java.awt.Toolkit;
0048 import java.awt.event.ActionEvent;
0049 import java.awt.event.ActionListener;
0050 import java.awt.event.ComponentAdapter;
0051 import java.awt.event.ComponentEvent;
0052 import java.awt.event.KeyAdapter;
0053 import java.awt.event.KeyEvent;
0054 import java.awt.event.MouseAdapter;
0055 import java.awt.event.MouseEvent;
0056 import java.awt.event.MouseListener;
0057 import java.awt.event.MouseMotionAdapter;
0058 import java.awt.event.MouseMotionListener;
0059 import java.util.ArrayList;
0060 import java.util.Collections;
0061 import java.util.HashMap;
0062 import java.util.HashSet;
0063 import java.util.Iterator;
0064 import java.util.Map;
0065 import java.util.Set;
0066 
0067 import javax.swing.AbstractAction;
0068 import javax.swing.Action;
0069 import javax.swing.ActionMap;
0070 import javax.swing.BorderFactory;
0071 import javax.swing.DefaultComboBoxModel;
0072 import javax.swing.Icon;
0073 import javax.swing.InputMap;
0074 import javax.swing.JButton;
0075 import javax.swing.JComboBox;
0076 import javax.swing.JComponent;
0077 import javax.swing.JPanel;
0078 import javax.swing.JScrollPane;
0079 import javax.swing.JTable;
0080 import javax.swing.JToggleButton;
0081 import javax.swing.JWindow;
0082 import javax.swing.KeyStroke;
0083 import javax.swing.SwingUtilities;
0084 import javax.swing.Timer;
0085 import javax.swing.UIManager;
0086 import javax.swing.event.AncestorEvent;
0087 import javax.swing.event.AncestorListener;
0088 import javax.swing.table.TableCellRenderer;
0089 import javax.swing.text.BadLocationException;
0090 
0091 /**
0092  * A generic annotation editor, which uses the known annotation schemas to help
0093  * speed up the annotation process (e.g. by pre-populating sets of choices) but
0094  * does not enforce the schemas, allowing the user full control.
0095  */
0096 @SuppressWarnings("serial")
0097 public class AnnotationEditor extends AbstractVisualResource implements
0098                                                             OwnedAnnotationEditor {
0099   /*
0100    * (non-Javadoc)
0101    
0102    * @see gate.creole.AbstractVisualResource#init()
0103    */
0104   @Override
0105   public Resource init() throws ResourceInstantiationException {
0106     super.init();
0107     initData();
0108     initGUI();
0109     initListeners();
0110     annotationEditorInstance = this;
0111     return this;
0112   }
0113 
0114   protected void initData() {
0115     schemasByType = new HashMap<String, AnnotationSchema>();
0116     java.util.List<LanguageResource> schemas =
0117         Gate.getCreoleRegister().getLrInstances("gate.creole.AnnotationSchema");
0118     for(Iterator<LanguageResource> schIter = schemas.iterator(); schIter.hasNext();) {
0119       AnnotationSchema aSchema = (AnnotationSchema)schIter.next();
0120       schemasByType.put(aSchema.getAnnotationName(), aSchema);
0121     }
0122     CreoleListener creoleListener = new CreoleListener() {
0123       @Override
0124       public void resourceLoaded(CreoleEvent e) {
0125         Resource newResource = e.getResource();
0126         if(newResource instanceof AnnotationSchema) {
0127           AnnotationSchema aSchema = (AnnotationSchema)newResource;
0128           schemasByType.put(aSchema.getAnnotationName(), aSchema);
0129         }
0130       }
0131 
0132       @Override
0133       public void resourceUnloaded(CreoleEvent e) {
0134         Resource newResource = e.getResource();
0135         if(newResource instanceof AnnotationSchema) {
0136           AnnotationSchema aSchema = (AnnotationSchema)newResource;
0137           if(schemasByType.containsValue(aSchema)) {
0138             schemasByType.remove(aSchema.getAnnotationName());
0139           }
0140         }
0141       }
0142 
0143       @Override
0144       public void datastoreOpened(CreoleEvent e) {
0145       }
0146 
0147       @Override
0148       public void datastoreCreated(CreoleEvent e) {
0149       }
0150 
0151       @Override
0152       public void datastoreClosed(CreoleEvent e) {
0153       }
0154 
0155       @Override
0156       public void resourceRenamed(Resource resource, String oldName,
0157           String newName) {
0158       }
0159     };
0160     Gate.getCreoleRegister().addCreoleListener(creoleListener);
0161   }
0162 
0163   protected void initGUI() {
0164     popupWindow =
0165         new JWindow(SwingUtilities.getWindowAncestor(owner.getTextComponent())) {
0166           @Override
0167           public void pack() {
0168             // increase the feature table size only if not bigger
0169             // than the main frame
0170             if(isVisible()) {
0171               int maxHeight = MainFrame.getInstance().getHeight();
0172               int otherHeight = getHeight() - featuresScroller.getHeight();
0173               maxHeight -= otherHeight;
0174               if(featuresScroller.getPreferredSize().height > maxHeight) {
0175                 featuresScroller.setMaximumSize(new Dimension(featuresScroller
0176                     .getMaximumSize().width, maxHeight));
0177                 featuresScroller.setPreferredSize(new Dimension(
0178                     featuresScroller.getPreferredSize().width, maxHeight));
0179               }
0180             }
0181             super.pack();
0182           }
0183 
0184           @Override
0185           public void setVisible(boolean b) {
0186             super.setVisible(b);
0187             // when the editor is shown put the focus in the type combo box
0188             if(b) {
0189               typeCombo.requestFocus();
0190             }
0191           }
0192         };
0193     JPanel pane = new JPanel();
0194     pane.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
0195     pane.setLayout(new GridBagLayout());
0196     pane.setBackground(UIManager.getLookAndFeelDefaults().getColor(
0197         "ToolTip.background"));
0198     popupWindow.setContentPane(pane);
0199     Insets insets0 = new Insets(0000);
0200     GridBagConstraints constraints = new GridBagConstraints();
0201     constraints.fill = GridBagConstraints.NONE;
0202     constraints.anchor = GridBagConstraints.CENTER;
0203     constraints.gridwidth = 1;
0204     constraints.gridy = 0;
0205     constraints.gridx = GridBagConstraints.RELATIVE;
0206     constraints.weightx = 0;
0207     constraints.weighty = 0;
0208     constraints.insets = insets0;
0209     solButton = new JButton();
0210     solButton.setContentAreaFilled(false);
0211     solButton.setBorderPainted(false);
0212     solButton.setMargin(insets0);
0213     pane.add(solButton, constraints);
0214     sorButton = new JButton();
0215     sorButton.setContentAreaFilled(false);
0216     sorButton.setBorderPainted(false);
0217     sorButton.setMargin(insets0);
0218     pane.add(sorButton, constraints);
0219     delButton = new JButton();
0220     delButton.setContentAreaFilled(false);
0221     delButton.setBorderPainted(false);
0222     delButton.setMargin(insets0);
0223     constraints.insets = new Insets(020020);
0224     pane.add(delButton, constraints);
0225     constraints.insets = insets0;
0226     eolButton = new JButton();
0227     eolButton.setContentAreaFilled(false);
0228     eolButton.setBorderPainted(false);
0229     eolButton.setMargin(insets0);
0230     pane.add(eolButton, constraints);
0231     eorButton = new JButton();
0232     eorButton.setContentAreaFilled(false);
0233     eorButton.setBorderPainted(false);
0234     eorButton.setMargin(insets0);
0235     pane.add(eorButton, constraints);
0236     pinnedButton = new JToggleButton(MainFrame.getIcon("pin"));
0237     pinnedButton.setSelectedIcon(MainFrame.getIcon("pin-in"));
0238     pinnedButton.setSelected(false);
0239     pinnedButton.setBorderPainted(false);
0240     pinnedButton.setContentAreaFilled(false);
0241     constraints.weightx = 1;
0242     constraints.insets = new Insets(0000);
0243     constraints.anchor = GridBagConstraints.EAST;
0244     pane.add(pinnedButton, constraints);
0245     dismissButton = new JButton();
0246     dismissButton.setBorder(null);
0247     constraints.anchor = GridBagConstraints.NORTHEAST;
0248     pane.add(dismissButton, constraints);
0249     constraints.anchor = GridBagConstraints.CENTER;
0250     constraints.insets = insets0;
0251     typeCombo = new JComboBox<String>();
0252     typeCombo.setEditable(true);
0253     typeCombo.setBackground(UIManager.getLookAndFeelDefaults().getColor(
0254         "ToolTip.background"));
0255     constraints.fill = GridBagConstraints.HORIZONTAL;
0256     constraints.gridy = 1;
0257     constraints.gridwidth = 7;
0258     constraints.weightx = 1;
0259     constraints.insets = new Insets(3222);
0260     pane.add(typeCombo, constraints);
0261     featuresEditor = new FeaturesSchemaEditor();
0262     featuresEditor.setBackground(UIManager.getLookAndFeelDefaults().getColor(
0263         "ToolTip.background"));
0264     try {
0265       featuresEditor.init();
0266     catch(ResourceInstantiationException rie) {
0267       throw new GateRuntimeException(rie);
0268     }
0269     constraints.gridy = 2;
0270     constraints.weighty = 1;
0271     constraints.fill = GridBagConstraints.BOTH;
0272     featuresScroller = new JScrollPane(featuresEditor);
0273     pane.add(featuresScroller, constraints);
0274     // add the search and annotate GUI at the bottom of the annotator editor
0275     SearchAndAnnotatePanel searchPanel =
0276         new SearchAndAnnotatePanel(pane.getBackground(), this, popupWindow);
0277     constraints.insets = new Insets(0000);
0278     constraints.fill = GridBagConstraints.BOTH;
0279     constraints.anchor = GridBagConstraints.WEST;
0280     constraints.gridx = 0;
0281     constraints.gridy = GridBagConstraints.RELATIVE;
0282     constraints.gridwidth = GridBagConstraints.REMAINDER;
0283     constraints.gridheight = GridBagConstraints.REMAINDER;
0284     constraints.weightx = 0.0;
0285     constraints.weighty = 0.0;
0286     pane.add(searchPanel, constraints);
0287     popupWindow.pack();
0288   }
0289 
0290   protected void initListeners() {
0291     // resize the window when the table changes.
0292     featuresEditor.addComponentListener(new ComponentAdapter() {
0293       @Override
0294       public void componentResized(ComponentEvent e) {
0295         // the table has changed size -> resize the window too!
0296         popupWindow.pack();
0297       }
0298     });
0299     KeyAdapter keyAdapter = new KeyAdapter() {
0300       @Override
0301       public void keyPressed(KeyEvent e) {
0302         hideTimer.stop();
0303       }
0304     };
0305     typeCombo.getEditor().getEditorComponent().addKeyListener(keyAdapter);
0306     MouseListener windowMouseListener = new MouseAdapter() {
0307       @Override
0308       public void mouseEntered(MouseEvent evt) {
0309         hideTimer.stop();
0310       }
0311 
0312       // allow a JWindow to be dragged with a mouse
0313       @Override
0314       public void mousePressed(MouseEvent me) {
0315         pressed = me;
0316       }
0317     };
0318     MouseMotionListener windowMouseMotionListener = new MouseMotionAdapter() {
0319       Point location;
0320 
0321       // allow a JWindow to be dragged with a mouse
0322       @Override
0323       public void mouseDragged(MouseEvent me) {
0324         location = popupWindow.getLocation(location);
0325         int x = location.x - pressed.getX() + me.getX();
0326         int y = location.y - pressed.getY() + me.getY();
0327         popupWindow.setLocation(x, y);
0328         pinnedButton.setSelected(true);
0329       }
0330     };
0331     popupWindow.getRootPane().addMouseListener(windowMouseListener);
0332     popupWindow.getRootPane().addMouseMotionListener(windowMouseMotionListener);
0333     InputMap inputMap =
0334         ((JComponent)popupWindow.getContentPane())
0335             .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0336     actionMap = ((JComponent)popupWindow.getContentPane()).getActionMap();
0337     // add the key-action bindings of this Component to the parent window
0338     solAction =
0339         new StartOffsetLeftAction("", MainFrame.getIcon("extend-left"),
0340             SOL_DESC, KeyEvent.VK_LEFT);
0341     solButton.setAction(solAction);
0342     setShortCuts(inputMap, SOL_KEY_STROKES, "solAction");
0343     actionMap.put("solAction", solAction);
0344     sorAction =
0345         new StartOffsetRightAction("", MainFrame.getIcon("extend-right"),
0346             SOR_DESC, KeyEvent.VK_RIGHT);
0347     sorButton.setAction(sorAction);
0348     setShortCuts(inputMap, SOR_KEY_STROKES, "sorAction");
0349     actionMap.put("sorAction", sorAction);
0350     delAction =
0351         new DeleteAnnotationAction("", MainFrame.getIcon("remove-annotation"),
0352             "Delete the annotation", KeyEvent.VK_DELETE);
0353     delButton.setAction(delAction);
0354     inputMap.put(KeyStroke.getKeyStroke("alt DELETE")"delAction");
0355     actionMap.put("delAction", delAction);
0356     eolAction =
0357         new EndOffsetLeftAction("", MainFrame.getIcon("extend-left"), EOL_DESC,
0358             KeyEvent.VK_LEFT);
0359     eolButton.setAction(eolAction);
0360     setShortCuts(inputMap, EOL_KEY_STROKES, "eolAction");
0361     actionMap.put("eolAction", eolAction);
0362     eorAction =
0363         new EndOffsetRightAction("", MainFrame.getIcon("extend-right"),
0364             EOR_DESC, KeyEvent.VK_RIGHT);
0365     eorButton.setAction(eorAction);
0366     setShortCuts(inputMap, EOR_KEY_STROKES, "eorAction");
0367     actionMap.put("eorAction", eorAction);
0368     pinnedButton.setToolTipText("<html>Press to pin window in place"
0369         "&nbsp;&nbsp;<font color=#667799><small>Ctrl-P"
0370         "&nbsp;&nbsp;</small></font></html>");
0371     inputMap.put(KeyStroke.getKeyStroke("control P")"toggle pin");
0372     actionMap.put("toggle pin"new AbstractAction() {
0373       @Override
0374       public void actionPerformed(ActionEvent e) {
0375         pinnedButton.doClick();
0376       }
0377     });
0378     DismissAction dismissAction =
0379         new DismissAction("", null, "Close the window", KeyEvent.VK_ESCAPE);
0380     dismissButton.setAction(dismissAction);
0381     inputMap.put(KeyStroke.getKeyStroke("ESCAPE")"dismissAction");
0382     inputMap.put(KeyStroke.getKeyStroke("alt ESCAPE")"dismissAction");
0383     actionMap.put("dismissAction", dismissAction);
0384     ApplyAction applyAction =
0385         new ApplyAction("Apply", null, "", KeyEvent.VK_ENTER);
0386     inputMap.put(KeyStroke.getKeyStroke("alt ENTER")"applyAction");
0387     actionMap.put("applyAction", applyAction);
0388     typeCombo.addActionListener(new ActionListener() {
0389       @Override
0390       public void actionPerformed(ActionEvent evt) {
0391         String newType = typeCombo.getSelectedItem().toString();
0392         if(ann == null || ann.getType().equals(newType)) return;
0393         // annotation editing
0394         Integer oldId = ann.getId();
0395         Annotation oldAnn = ann;
0396         set.remove(ann);
0397         try {
0398           set.add(oldId, oldAnn.getStartNode().getOffset(), oldAnn.getEndNode()
0399               .getOffset(), newType, oldAnn.getFeatures());
0400           Annotation newAnn = set.get(oldId);
0401           // update the selection to the new annotation
0402           getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
0403           editAnnotation(newAnn, set);
0404           owner.annotationChanged(newAnn, set, oldAnn.getType());
0405         catch(InvalidOffsetException ioe) {
0406           throw new GateRuntimeException(ioe);
0407         }
0408       }
0409     });
0410     hideTimer = new Timer(HIDE_DELAY, new ActionListener() {
0411       @Override
0412       public void actionPerformed(ActionEvent evt) {
0413         annotationEditorInstance.setVisible(false);
0414       }
0415     });
0416     hideTimer.setRepeats(false);
0417     AncestorListener textAncestorListener = new AncestorListener() {
0418       @Override
0419       public void ancestorAdded(AncestorEvent event) {
0420         if(wasShowing) {
0421           annotationEditorInstance.setVisible(true);
0422         }
0423         wasShowing = false;
0424       }
0425 
0426       @Override
0427       public void ancestorRemoved(AncestorEvent event) {
0428         if(isShowing()) {
0429           wasShowing = true;
0430           popupWindow.dispose();
0431         }
0432       }
0433 
0434       @Override
0435       public void ancestorMoved(AncestorEvent event) {
0436       }
0437 
0438       private boolean wasShowing = false;
0439     };
0440     owner.getTextComponent().addAncestorListener(textAncestorListener);
0441   }
0442 
0443   /*
0444    * (non-Javadoc)
0445    
0446    * @see gate.gui.annedit.AnnotationEditor#isActive()
0447    */
0448   @Override
0449   public boolean isActive() {
0450     return popupWindow.isVisible();
0451   }
0452 
0453   @Override
0454   public void editAnnotation(Annotation ann, AnnotationSet set) {
0455     this.ann = ann;
0456     this.set = set;
0457     if(ann == null) {
0458       typeCombo.setModel(new DefaultComboBoxModel<String>());
0459       featuresEditor.setSchema(new AnnotationSchema());
0460       // popupWindow.doLayout();
0461       popupWindow.validate();
0462       return;
0463     }
0464     // repopulate the types combo
0465     String annType = ann.getType();
0466     Set<String> types = new HashSet<String>(schemasByType.keySet());
0467     types.add(annType);
0468     types.addAll(set.getAllTypes());
0469     java.util.List<String> typeList = new ArrayList<String>(types);
0470     Collections.sort(typeList);
0471     typeCombo.setModel(new DefaultComboBoxModel<String>(typeList.toArray(new String[typeList.size()])));
0472     typeCombo.setSelectedItem(annType);
0473     featuresEditor.setSchema(schemasByType.get(annType));
0474     featuresEditor.setTargetFeatures(ann.getFeatures());
0475     setEditingEnabled(true);
0476     popupWindow.pack();
0477     setVisible(true);
0478     if(!pinnedButton.isSelected()) {
0479       hideTimer.restart();
0480     }
0481   }
0482 
0483   @Override
0484   public Annotation getAnnotationCurrentlyEdited() {
0485     return ann;
0486   }
0487 
0488   /*
0489    * (non-Javadoc)
0490    
0491    * @see gate.gui.annedit.AnnotationEditor#editingFinished()
0492    */
0493   @Override
0494   public boolean editingFinished() {
0495     // this editor implementation has no special requirements (such as schema
0496     // compliance), so it always returns true.
0497     return true;
0498   }
0499 
0500   @Override
0501   public boolean isShowing() {
0502     return popupWindow.isShowing();
0503   }
0504 
0505   /**
0506    * Shows/Hides the UI(s) involved in annotation editing.
0507    */
0508   @Override
0509   public void setVisible(boolean setVisible) {
0510     super.setVisible(setVisible);
0511     if(setVisible) {
0512       placeDialog(ann.getStartNode().getOffset().intValue(), ann.getEndNode()
0513           .getOffset().intValue());
0514     else {
0515       popupWindow.setVisible(false);
0516       pinnedButton.setSelected(false);
0517       SwingUtilities.invokeLater(new Runnable() {
0518         @Override
0519         public void run() {
0520           // when hiding the editor put back the focus in the document
0521           owner.getTextComponent().requestFocus();
0522         }
0523       });
0524     }
0525   }
0526 
0527   /**
0528    * Finds the best location for the editor dialog for a given span of text.
0529    */
0530   @Override
0531   public void placeDialog(int start, int end) {
0532     if(popupWindow.isVisible() && pinnedButton.isSelected()) {
0533       // just resize
0534       Point where = popupWindow.getLocation();
0535       popupWindow.pack();
0536       if(where != null) {
0537         popupWindow.setLocation(where);
0538       }
0539     else {
0540       // calculate position
0541       try {
0542         Rectangle startRect = owner.getTextComponent().modelToView(start);
0543         Rectangle endRect = owner.getTextComponent().modelToView(end);
0544         Point topLeft = owner.getTextComponent().getLocationOnScreen();
0545         int x = topLeft.x + startRect.x;
0546         int y = topLeft.y + endRect.y + endRect.height;
0547         // make sure the window doesn't start lower
0548         // than the end of the visible rectangle
0549         Rectangle visRect = owner.getTextComponent().getVisibleRect();
0550         int maxY = topLeft.y + visRect.y + visRect.height;
0551         // make sure window doesn't get off-screen
0552         popupWindow.pack();
0553         // responding to changed orientation
0554         if(currentOrientation == ComponentOrientation.RIGHT_TO_LEFT) {
0555           x = x - popupWindow.getSize().width;
0556           if(x < 0x = 0;
0557         }
0558         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
0559         boolean revalidate = false;
0560         if(popupWindow.getSize().width > screenSize.width) {
0561           popupWindow.setSize(screenSize.width, popupWindow.getSize().height);
0562           revalidate = true;
0563         }
0564         if(popupWindow.getSize().height > screenSize.height) {
0565           popupWindow.setSize(popupWindow.getSize().width, screenSize.height);
0566           revalidate = true;
0567         }
0568         if(revalidatepopupWindow.validate();
0569         // calculate max X
0570         int maxX = screenSize.width - popupWindow.getSize().width;
0571         // calculate max Y
0572         if(maxY + popupWindow.getSize().height > screenSize.height) {
0573           maxY = screenSize.height - popupWindow.getSize().height;
0574         }
0575         // correct position
0576         if(y > maxYy = maxY;
0577         if(x > maxXx = maxX;
0578         popupWindow.setLocation(x, y);
0579       catch(BadLocationException ble) {
0580         // this should never occur
0581         throw new GateRuntimeException(ble);
0582       }
0583     }
0584     if(!popupWindow.isVisible()) popupWindow.setVisible(true);
0585   }
0586 
0587   /**
0588    * Changes the span of an existing annotation by creating a new annotation
0589    * with the same ID, type and features but with the new start and end offsets.
0590    
0591    @param set
0592    *          the annotation set
0593    @param oldAnnotation
0594    *          the annotation to be moved
0595    @param newStartOffset
0596    *          the new start offset
0597    @param newEndOffset
0598    *          the new end offset
0599    */
0600   protected void moveAnnotation(AnnotationSet set, Annotation oldAnnotation,
0601       Long newStartOffset, Long newEndOffsetthrows InvalidOffsetException {
0602     // Moving is done by deleting the old annotation and creating a new one.
0603     // If this was the last one of one type it would mess up the gui which
0604     // "forgets" about this type and then it recreates it (with a different
0605     // colour and not visible.
0606     // In order to avoid this problem, we'll create a new temporary annotation.
0607     Annotation tempAnn = null;
0608     if(set.get(oldAnnotation.getType()).size() == 1) {
0609       // create a clone of the annotation that will be deleted, to act as a
0610       // placeholder
0611       Integer tempAnnId =
0612           set.add(oldAnnotation.getStartNode(), oldAnnotation.getStartNode(),
0613               oldAnnotation.getType(), oldAnnotation.getFeatures());
0614       tempAnn = set.get(tempAnnId);
0615     }
0616     Integer oldID = oldAnnotation.getId();
0617     set.remove(oldAnnotation);
0618     set.add(oldID, newStartOffset, newEndOffset, oldAnnotation.getType(),
0619         oldAnnotation.getFeatures());
0620     Annotation newAnn = set.get(oldID);
0621     // update the selection to the new annotation
0622     getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
0623     editAnnotation(newAnn, set);
0624     // remove the temporary annotation
0625     if(tempAnn != nullset.remove(tempAnn);
0626     owner.annotationChanged(newAnn, set, null);
0627   }
0628 
0629   /**
0630    * Base class for actions on annotations.
0631    */
0632   protected abstract class AnnotationAction extends AbstractAction {
0633     public AnnotationAction(String text, Icon icon, String desc, int mnemonic) {
0634       super(text, icon);
0635       putValue(SHORT_DESCRIPTION, desc);
0636       putValue(MNEMONIC_KEY, mnemonic);
0637     }
0638   }
0639 
0640   protected class StartOffsetLeftAction extends AnnotationAction {
0641     private static final long serialVersionUID = 1L;
0642 
0643     public StartOffsetLeftAction(String text, Icon icon, String desc,
0644         int mnemonic) {
0645       super(text, icon, desc, mnemonic);
0646     }
0647 
0648     @Override
0649     public void actionPerformed(ActionEvent evt) {
0650       int increment = 1;
0651       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0) {
0652         // CTRL pressed -> use tokens for advancing
0653         increment = SHIFT_INCREMENT;
0654         if((evt.getModifiers() & ActionEvent.CTRL_MASK0) {
0655           increment = CTRL_SHIFT_INCREMENT;
0656         }
0657       }
0658       long newValue = ann.getStartNode().getOffset().longValue() - increment;
0659       if(newValue < 0newValue = 0;
0660       try {
0661         moveAnnotation(set, ann, new Long(newValue), ann.getEndNode()
0662             .getOffset());
0663       catch(InvalidOffsetException ioe) {
0664         throw new GateRuntimeException(ioe);
0665       }
0666     }
0667   }
0668 
0669   protected class StartOffsetRightAction extends AnnotationAction {
0670     private static final long serialVersionUID = 1L;
0671 
0672     public StartOffsetRightAction(String text, Icon icon, String desc,
0673         int mnemonic) {
0674       super(text, icon, desc, mnemonic);
0675     }
0676 
0677     @Override
0678     public void actionPerformed(ActionEvent evt) {
0679       long endOffset = ann.getEndNode().getOffset().longValue();
0680       int increment = 1;
0681       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0) {
0682         // CTRL pressed -> use tokens for advancing
0683         increment = SHIFT_INCREMENT;
0684         if((evt.getModifiers() & ActionEvent.CTRL_MASK0) {
0685           increment = CTRL_SHIFT_INCREMENT;
0686         }
0687       }
0688       long newValue = ann.getStartNode().getOffset().longValue() + increment;
0689       if(newValue > endOffsetnewValue = endOffset;
0690       try {
0691         moveAnnotation(set, ann, new Long(newValue), ann.getEndNode()
0692             .getOffset());
0693       catch(InvalidOffsetException ioe) {
0694         throw new GateRuntimeException(ioe);
0695       }
0696     }
0697   }
0698 
0699   protected class EndOffsetLeftAction extends AnnotationAction {
0700     private static final long serialVersionUID = 1L;
0701 
0702     public EndOffsetLeftAction(String text, Icon icon, String desc, int mnemonic) {
0703       super(text, icon, desc, mnemonic);
0704     }
0705 
0706     @Override
0707     public void actionPerformed(ActionEvent evt) {
0708       long startOffset = ann.getStartNode().getOffset().longValue();
0709       int increment = 1;
0710       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0) {
0711         // CTRL pressed -> use tokens for advancing
0712         increment = SHIFT_INCREMENT;
0713         if((evt.getModifiers() & ActionEvent.CTRL_MASK0) {
0714           increment = CTRL_SHIFT_INCREMENT;
0715         }
0716       }
0717       long newValue = ann.getEndNode().getOffset().longValue() - increment;
0718       if(newValue < startOffsetnewValue = startOffset;
0719       try {
0720         moveAnnotation(set, ann, ann.getStartNode().getOffset()new Long(
0721             newValue));
0722       catch(InvalidOffsetException ioe) {
0723         throw new GateRuntimeException(ioe);
0724       }
0725     }
0726   }
0727 
0728   protected class EndOffsetRightAction extends AnnotationAction {
0729     private static final long serialVersionUID = 1L;
0730 
0731     public EndOffsetRightAction(String text, Icon icon, String desc,
0732         int mnemonic) {
0733       super(text, icon, desc, mnemonic);
0734     }
0735 
0736     @Override
0737     public void actionPerformed(ActionEvent evt) {
0738       long maxOffset = owner.getDocument().getContent().size().longValue();
0739       int increment = 1;
0740       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0) {
0741         // CTRL pressed -> use tokens for advancing
0742         increment = SHIFT_INCREMENT;
0743         if((evt.getModifiers() & ActionEvent.CTRL_MASK0) {
0744           increment = CTRL_SHIFT_INCREMENT;
0745         }
0746       }
0747       long newValue = ann.getEndNode().getOffset().longValue() + increment;
0748       if(newValue > maxOffsetnewValue = maxOffset;
0749       try {
0750         moveAnnotation(set, ann, ann.getStartNode().getOffset()new Long(
0751             newValue));
0752       catch(InvalidOffsetException ioe) {
0753         throw new GateRuntimeException(ioe);
0754       }
0755     }
0756   }
0757 
0758   protected class DeleteAnnotationAction extends AnnotationAction {
0759     private static final long serialVersionUID = 1L;
0760 
0761     public DeleteAnnotationAction(String text, Icon icon, String desc,
0762         int mnemonic) {
0763       super(text, icon, desc, mnemonic);
0764     }
0765 
0766     @Override
0767     public void actionPerformed(ActionEvent evt) {
0768       set.remove(ann);
0769       // clear the dialog
0770       editAnnotation(null, set);
0771       if(!pinnedButton.isSelected()) {
0772         // if not pinned, hide the dialog.
0773         annotationEditorInstance.setVisible(false);
0774       else {
0775         setEditingEnabled(false);
0776       }
0777     }
0778   }
0779 
0780   protected class DismissAction extends AnnotationAction {
0781     private static final long serialVersionUID = 1L;
0782 
0783     public DismissAction(String text, Icon icon, String desc, int mnemonic) {
0784       super(text, icon, desc, mnemonic);
0785       Icon exitIcon = UIManager.getIcon("InternalFrame.closeIcon");
0786       if(exitIcon == nullexitIcon = MainFrame.getIcon("exit");
0787       putValue(SMALL_ICON, exitIcon);
0788     }
0789 
0790     @Override
0791     public void actionPerformed(ActionEvent evt) {
0792       annotationEditorInstance.setVisible(false);
0793     }
0794   }
0795 
0796   protected class ApplyAction extends AnnotationAction {
0797     private static final long serialVersionUID = 1L;
0798 
0799     public ApplyAction(String text, Icon icon, String desc, int mnemonic) {
0800       super(text, icon, desc, mnemonic);
0801     }
0802 
0803     @Override
0804     public void actionPerformed(ActionEvent evt) {
0805       annotationEditorInstance.setVisible(false);
0806     }
0807   }
0808 
0809   /**
0810    * The popup window used by the editor.
0811    */
0812   protected JWindow popupWindow;
0813 
0814   /**
0815    * Toggle button used to pin down the dialog.
0816    */
0817   protected JToggleButton pinnedButton;
0818 
0819   /**
0820    * Combobox for annotation type.
0821    */
0822   protected JComboBox<String> typeCombo;
0823 
0824   /**
0825    * Component for features editing.
0826    */
0827   protected FeaturesSchemaEditor featuresEditor;
0828 
0829   protected JScrollPane featuresScroller;
0830 
0831   protected JButton solButton;
0832 
0833   protected JButton sorButton;
0834 
0835   protected JButton delButton;
0836 
0837   protected JButton eolButton;
0838 
0839   protected JButton eorButton;
0840 
0841   protected JButton dismissButton;
0842 
0843   protected Timer hideTimer;
0844 
0845   protected MouseEvent pressed;
0846 
0847   /**
0848    * Constant for delay before hiding the popup window (in milliseconds).
0849    */
0850   protected static final int HIDE_DELAY = 3000;
0851 
0852   /**
0853    * Constant for the number of characters when changing annotation boundary
0854    * with Shift key pressed.
0855    */
0856   protected static final int SHIFT_INCREMENT = 5;
0857 
0858   /**
0859    * Constant for the number of characters when changing annotation boundary
0860    * with Ctrl+Shift keys pressed.
0861    */
0862   protected static final int CTRL_SHIFT_INCREMENT = 10;
0863 
0864   /**
0865    * Stores the Annotation schema objects available in the system. The
0866    * annotation types are used as keys for the map.
0867    */
0868   protected Map<String, AnnotationSchema> schemasByType;
0869 
0870   /**
0871    * The controlling object for this editor.
0872    */
0873   private AnnotationEditorOwner owner;
0874 
0875   /**
0876    * The annotation being edited.
0877    */
0878   protected Annotation ann;
0879 
0880   /**
0881    * The parent set of the current annotation.
0882    */
0883   protected AnnotationSet set;
0884 
0885   /**
0886    * Current instance of this class.
0887    */
0888   protected AnnotationEditor annotationEditorInstance;
0889 
0890   /**
0891    * Action bindings for the popup window.
0892    */
0893   private ActionMap actionMap;
0894 
0895   private StartOffsetLeftAction solAction;
0896 
0897   private StartOffsetRightAction sorAction;
0898 
0899   private DeleteAnnotationAction delAction;
0900 
0901   private EndOffsetLeftAction eolAction;
0902 
0903   private EndOffsetRightAction eorAction;
0904 
0905   /*
0906    * (non-Javadoc)
0907    
0908    * @see gate.gui.annedit.AnnotationEditor#getAnnotationSetCurrentlyEdited()
0909    */
0910   @Override
0911   public AnnotationSet getAnnotationSetCurrentlyEdited() {
0912     return set;
0913   }
0914 
0915   /**
0916    @return the owner
0917    */
0918   @Override
0919   public AnnotationEditorOwner getOwner() {
0920     return owner;
0921   }
0922 
0923   /**
0924    @param owner
0925    *          the owner to set
0926    */
0927   @Override
0928   public void setOwner(AnnotationEditorOwner owner) {
0929     this.owner = owner;
0930   }
0931 
0932   @Override
0933   public void setPinnedMode(boolean pinned) {
0934     pinnedButton.setSelected(pinned);
0935   }
0936 
0937   @Override
0938   public void setEditingEnabled(boolean isEditingEnabled) {
0939     solButton.setEnabled(isEditingEnabled);
0940     sorButton.setEnabled(isEditingEnabled);
0941     delButton.setEnabled(isEditingEnabled);
0942     eolButton.setEnabled(isEditingEnabled);
0943     eorButton.setEnabled(isEditingEnabled);
0944     typeCombo.setEnabled(isEditingEnabled);
0945     // cancel editing, if any
0946     if(featuresEditor.isEditing()) {
0947       featuresEditor.getColumnModel()
0948           .getColumn(featuresEditor.getEditingColumn()).getCellEditor()
0949           .cancelCellEditing();
0950     }
0951     // en/disable the featuresEditor table, no easy way unfortunately : |
0952     featuresEditor.setEnabled(isEditingEnabled);
0953     if(isEditingEnabled) {
0954       // avoid the background to be incorrectly reset to the default color
0955       Color tableBG = featuresEditor.getBackground();
0956       tableBG = new Color(tableBG.getRGB());
0957       featuresEditor.setBackground(tableBG);
0958     }
0959     final boolean isEditingEnabledF = isEditingEnabled;
0960     for(int col = 0; col < featuresEditor.getColumnCount(); col++) {
0961       final TableCellRenderer previousTcr =
0962           featuresEditor.getColumnModel().getColumn(col).getCellRenderer();
0963       TableCellRenderer tcr = new TableCellRenderer() {
0964         @Override
0965         public Component getTableCellRendererComponent(JTable table,
0966             Object value, boolean isSelected, boolean hasFocus, int row,
0967             int column) {
0968           Component c =
0969               previousTcr.getTableCellRendererComponent(table, value,
0970                   isSelected, hasFocus, row, column);
0971           c.setEnabled(isEditingEnabledF);
0972           return c;
0973         }
0974       };
0975       featuresEditor.getColumnModel().getColumn(col).setCellRenderer(tcr);
0976     }
0977     // enable/disable the key binding actions
0978     if(isEditingEnabled) {
0979       actionMap.put("solAction", solAction);
0980       actionMap.put("sorAction", sorAction);
0981       actionMap.put("delAction", delAction);
0982       actionMap.put("eolAction", eolAction);
0983       actionMap.put("eorAction", eorAction);
0984     else {
0985       actionMap.put("solAction"null);
0986       actionMap.put("sorAction"null);
0987       actionMap.put("delAction"null);
0988       actionMap.put("eolAction"null);
0989       actionMap.put("eorAction"null);
0990     }
0991     // change the orientation
0992     changeOrientation(currentOrientation);
0993   }
0994 
0995   /**
0996    * Does nothing, as this editor does not support cancelling and rollbacks.
0997    */
0998   @Override
0999   public void cancelAction() throws GateException {
1000   }
1001 
1002   /**
1003    * Returns <tt>true</tt> always as this editor is generic and can edit any
1004    * annotation type.
1005    */
1006   @Override
1007   public boolean canDisplayAnnotationType(String annotationType) {
1008     return true;
1009   }
1010 
1011   /**
1012    * Does nothing as this editor works in auto-commit mode (changes are
1013    * implemented immediately).
1014    */
1015   @Override
1016   public void okAction() throws GateException {
1017   }
1018 
1019   /**
1020    * Returns <tt>false</tt>, as this editor does not support cancel operations.
1021    */
1022   @Override
1023   public boolean supportsCancel() {
1024     return false;
1025   }
1026 
1027   @Override
1028   public void changeOrientation(ComponentOrientation orientation) {
1029     if(orientation == nullreturn;
1030     // remember the current orientation
1031     this.currentOrientation = orientation;
1032     // input map
1033     InputMap inputMap =
1034         ((JComponent)popupWindow.getContentPane())
1035             .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1036     Action solAction = actionMap.get("solAction");
1037     Action sorAction = actionMap.get("sorAction");
1038     Action eolAction = actionMap.get("eolAction");
1039     Action eorAction = actionMap.get("eorAction");
1040     if(orientation == ComponentOrientation.RIGHT_TO_LEFT) {
1041       // in right to left orientation
1042       // extending start offset is equal to extending end offset
1043       solButton.setAction(eorAction);
1044       solButton.setToolTipText(EOR_DESC);
1045       setShortCuts(inputMap, SOL_KEY_STROKES, "eorAction");
1046       solButton.setIcon(MainFrame.getIcon("extend-left"));
1047       // shrinking start offset is equal to shrinking end offset
1048       sorButton.setAction(eolAction);
1049       sorButton.setToolTipText(EOL_DESC);
1050       setShortCuts(inputMap, SOR_KEY_STROKES, "eolAction");
1051       sorButton.setIcon(MainFrame.getIcon("extend-right"));
1052       // shrinking end offset is equal to shrinking start offset
1053       eolButton.setAction(sorAction);
1054       eolButton.setToolTipText(SOR_DESC);
1055       setShortCuts(inputMap, EOL_KEY_STROKES, "sorAction");
1056       eolButton.setIcon(MainFrame.getIcon("extend-left"));
1057       // extending end offset is extending start offset
1058       eorButton.setAction(solAction);
1059       eorButton.setToolTipText(SOL_DESC);
1060       setShortCuts(inputMap, EOR_KEY_STROKES, "solAction");
1061       eorButton.setIcon(MainFrame.getIcon("extend-right"));
1062     else {
1063       solButton.setAction(solAction);
1064       solButton.setToolTipText(SOL_DESC);
1065       setShortCuts(inputMap, SOL_KEY_STROKES, "solAction");
1066       solButton.setIcon(MainFrame.getIcon("extend-left"));
1067       sorButton.setAction(sorAction);
1068       sorButton.setToolTipText(SOR_DESC);
1069       setShortCuts(inputMap, SOR_KEY_STROKES, "sorAction");
1070       sorButton.setIcon(MainFrame.getIcon("extend-right"));
1071       eolButton.setAction(eolAction);
1072       eolButton.setToolTipText(EOL_DESC);
1073       setShortCuts(inputMap, EOL_KEY_STROKES, "eolAction");
1074       eolButton.setIcon(MainFrame.getIcon("extend-left"));
1075       eorButton.setAction(eorAction);
1076       eorButton.setToolTipText(EOR_DESC);
1077       setShortCuts(inputMap, EOR_KEY_STROKES, "eorAction");
1078       eorButton.setIcon(MainFrame.getIcon("extend-right"));
1079     }
1080   }
1081 
1082   private void setShortCuts(InputMap inputMap, String[] keyStrokes,
1083       String action) {
1084     for(String aKeyStroke : keyStrokes) {
1085       inputMap.put(KeyStroke.getKeyStroke(aKeyStroke), action);
1086     }
1087   }
1088 
1089   /**
1090    * current orientation set by the user
1091    */
1092   private ComponentOrientation currentOrientation = null;
1093 
1094   /* various tool tips for the changing offsets buttons */
1095   private final String SOL_DESC = "<html><b>Extend start</b><small>"
1096       "<br>LEFT = 1 character" "<br> + SHIFT = 5 characters, "
1097       "<br> + CTRL + SHIFT = 10 characters</small></html>";
1098 
1099   private final String SOR_DESC = "<html><b>Shrink start</b><small>"
1100       "<br>RIGHT = 1 character" "<br> + SHIFT = 5 characters, "
1101       "<br> + CTRL + SHIFT = 10 characters</small></html>";
1102 
1103   private final String EOL_DESC = "<html><b>Shrink end</b><small>"
1104       "<br>ALT + LEFT = 1 character" "<br> + SHIFT = 5 characters, "
1105       "<br> + CTRL + SHIFT = 10 characters</small></html>";
1106 
1107   private final String EOR_DESC = "<html><b>Extend end</b><small>"
1108       "<br>ALT + RIGHT = 1 character" "<br> + SHIFT = 5 characters, "
1109       "<br> + CTRL + SHIFT = 10 characters</small></html>";
1110 
1111   /* various short cuts we define */
1112   private final String[] SOL_KEY_STROKES = new String[]{"LEFT""shift LEFT",
1113       "control shift released LEFT"};
1114 
1115   private final String[] SOR_KEY_STROKES = new String[]{"RIGHT""shift RIGHT",
1116       "control shift released RIGHT"};
1117 
1118   private final String[] EOL_KEY_STROKES = new String[]{"LEFT""alt LEFT",
1119       "control alt released LEFT"};
1120 
1121   private final String[] EOR_KEY_STROKES = new String[]{"RIGHT""alt RIGHT",
1122       "control alt released RIGHT"};
1123 }