AnnotationEditor.java
001 /*
002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  AnnotationEditor.java
011  *
012  *  Valentin Tablan, Apr 5, 2004
013  *
014  *  $Id: AnnotationEditor.java 12399 2010-03-25 20:45:27Z thomas_heitz $
015  */
016 
017 package gate.gui.docview;
018 
019 import java.awt.*;
020 import java.awt.event.*;
021 import java.util.*;
022 
023 import javax.swing.*;
024 import javax.swing.Timer;
025 import javax.swing.event.AncestorEvent;
026 import javax.swing.event.AncestorListener;
027 import javax.swing.table.TableCellRenderer;
028 import javax.swing.text.BadLocationException;
029 
030 import gate.*;
031 import gate.creole.*;
032 import gate.event.CreoleEvent;
033 import gate.event.CreoleListener;
034 import gate.gui.FeaturesSchemaEditor;
035 import gate.gui.MainFrame;
036 import gate.gui.annedit.*;
037 import gate.util.*;
038 
039 
040 /**
041  * A generic annotation editor, which uses the known annotation schemas to help
042  * speed up the annotation process (e.g. by pre-populating sets of choices) but
043  * does not enforce the schemas, allowing the user full control.
044  */
045 public class AnnotationEditor extends AbstractVisualResource 
046     implements OwnedAnnotationEditor{
047   
048   private static final long serialVersionUID = 1L;
049 
050   /* (non-Javadoc)
051    * @see gate.creole.AbstractVisualResource#init()
052    */
053   @Override
054   public Resource init() throws ResourceInstantiationException {
055 
056     super.init();
057     initData();
058     initGUI();
059     initListeners();
060     annotationEditorInstance = this;
061     return this;
062   }
063 
064   protected void initData(){
065     schemasByType = new HashMap<String, AnnotationSchema>();
066     java.util.List schemas = Gate.getCreoleRegister().
067         getLrInstances("gate.creole.AnnotationSchema");
068     for(Iterator schIter = schemas.iterator()
069         schIter.hasNext();){
070       AnnotationSchema aSchema = (AnnotationSchema)schIter.next();
071       schemasByType.put(aSchema.getAnnotationName(), aSchema);
072     }
073     
074     CreoleListener creoleListener = new CreoleListener(){
075       public void resourceLoaded(CreoleEvent e){
076         Resource newResource =  e.getResource();
077         if(newResource instanceof AnnotationSchema){
078           AnnotationSchema aSchema = (AnnotationSchema)newResource;
079           schemasByType.put(aSchema.getAnnotationName(), aSchema);
080         }
081       }
082       
083       public void resourceUnloaded(CreoleEvent e){
084         Resource newResource =  e.getResource();
085         if(newResource instanceof AnnotationSchema){
086           AnnotationSchema aSchema = (AnnotationSchema)newResource;
087           if(schemasByType.containsValue(aSchema)){
088             schemasByType.remove(aSchema.getAnnotationName());
089           }
090         }
091       }
092       
093       public void datastoreOpened(CreoleEvent e){
094         
095       }
096       public void datastoreCreated(CreoleEvent e){
097         
098       }
099       public void datastoreClosed(CreoleEvent e){
100         
101       }
102       public void resourceRenamed(Resource resource,
103                               String oldName,
104                               String newName){
105       }  
106     };
107     Gate.getCreoleRegister().addCreoleListener(creoleListener)
108   }
109   
110   protected void initGUI(){
111 
112     popupWindow = new JWindow(SwingUtilities.getWindowAncestor(
113         owner.getTextComponent())) {
114       public void pack() {
115         // increase the feature table size only if not bigger
116         // than the main frame
117         if(isVisible()){
118           int maxHeight = MainFrame.getInstance().getHeight();
119           int otherHeight = getHeight() - featuresScroller.getHeight();
120           maxHeight -= otherHeight;
121           if(featuresScroller.getPreferredSize().height > maxHeight){
122             featuresScroller.setMaximumSize(new Dimension(
123                     featuresScroller.getMaximumSize().width, maxHeight));
124             featuresScroller.setPreferredSize(new Dimension(
125                     featuresScroller.getPreferredSize().width, maxHeight));
126           }
127           
128         }
129         super.pack();
130       }
131       public void setVisible(boolean b) {
132         super.setVisible(b);
133         // when the editor is shown put the focus in the type combo box
134         if (b) { typeCombo.requestFocus()}
135       }
136     };
137 
138     JPanel pane = new JPanel();
139     pane.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
140     pane.setLayout(new GridBagLayout());
141     pane.setBackground(UIManager.getLookAndFeelDefaults().
142             getColor("ToolTip.background"));
143     popupWindow.setContentPane(pane);
144 
145     Insets insets0 = new Insets(0000);
146     GridBagConstraints constraints = new GridBagConstraints();
147     constraints.fill = GridBagConstraints.NONE;
148     constraints.anchor = GridBagConstraints.CENTER;
149     constraints.gridwidth = 1;
150     constraints.gridy = 0;
151     constraints.gridx = GridBagConstraints.RELATIVE;
152     constraints.weightx = 0;
153     constraints.weighty= 0;
154     constraints.insets = insets0;
155 
156     solButton = new JButton();
157     solButton.setContentAreaFilled(false);
158     solButton.setBorderPainted(false);
159     solButton.setMargin(insets0);
160     pane.add(solButton, constraints);
161     
162     sorButton = new JButton();
163     sorButton.setContentAreaFilled(false);
164     sorButton.setBorderPainted(false);
165     sorButton.setMargin(insets0);
166     pane.add(sorButton, constraints);
167     
168     delButton = new JButton();
169     delButton.setContentAreaFilled(false);
170     delButton.setBorderPainted(false);
171     delButton.setMargin(insets0);
172     constraints.insets = new Insets(020020);
173     pane.add(delButton, constraints);
174     constraints.insets = insets0;
175     
176     eolButton = new JButton();
177     eolButton.setContentAreaFilled(false);
178     eolButton.setBorderPainted(false);
179     eolButton.setMargin(insets0);
180     pane.add(eolButton, constraints);
181     
182     eorButton = new JButton();
183     eorButton.setContentAreaFilled(false);
184     eorButton.setBorderPainted(false);
185     eorButton.setMargin(insets0);
186     pane.add(eorButton, constraints);
187     
188     pinnedButton = new JToggleButton(MainFrame.getIcon("pin"));
189     pinnedButton.setSelectedIcon(MainFrame.getIcon("pin-in"));
190     pinnedButton.setSelected(false);
191     pinnedButton.setBorderPainted(false);
192     pinnedButton.setContentAreaFilled(false);
193     constraints.weightx = 1;
194     constraints.insets = new Insets(0000);
195     constraints.anchor = GridBagConstraints.EAST;
196     pane.add(pinnedButton, constraints);
197 
198     dismissButton = new JButton();
199     dismissButton.setBorder(null);
200     constraints.anchor = GridBagConstraints.NORTHEAST;
201     pane.add(dismissButton, constraints);
202     constraints.anchor = GridBagConstraints.CENTER;
203     constraints.insets = insets0;
204 
205     
206     typeCombo = new JComboBox();
207     typeCombo.setEditable(true);
208     typeCombo.setBackground(UIManager.getLookAndFeelDefaults().
209             getColor("ToolTip.background"));
210     constraints.fill = GridBagConstraints.HORIZONTAL;
211     constraints.gridy = 1;
212     constraints.gridwidth = 7;
213     constraints.weightx = 1;
214     constraints.insets = new Insets(3222);
215     pane.add(typeCombo, constraints);
216     
217     featuresEditor = new FeaturesSchemaEditor();
218     featuresEditor.setBackground(UIManager.getLookAndFeelDefaults().
219             getColor("ToolTip.background"));
220     try{
221       featuresEditor.init();
222     }catch(ResourceInstantiationException rie){
223       throw new GateRuntimeException(rie);
224     }
225 
226     constraints.gridy = 2;
227     constraints.weighty = 1;
228     constraints.fill = GridBagConstraints.BOTH;
229     featuresScroller = new JScrollPane(featuresEditor)
230     pane.add(featuresScroller, constraints);
231 
232     // add the search and annotate GUI at the bottom of the annotator editor
233     SearchAndAnnotatePanel searchPanel =
234       new SearchAndAnnotatePanel(pane.getBackground(), this, popupWindow);
235     constraints.insets = new Insets(0000);
236     constraints.fill = GridBagConstraints.BOTH;
237     constraints.anchor = GridBagConstraints.WEST;
238     constraints.gridx = 0;
239     constraints.gridy = GridBagConstraints.RELATIVE;
240     constraints.gridwidth = GridBagConstraints.REMAINDER;
241     constraints.gridheight = GridBagConstraints.REMAINDER;
242     constraints.weightx = 0.0;
243     constraints.weighty = 0.0;
244     pane.add(searchPanel, constraints);
245 
246     popupWindow.pack();
247   }
248 
249   protected void initListeners(){
250     //resize the window when the table changes.
251     featuresEditor.addComponentListener(new ComponentAdapter() {
252       @Override
253       public void componentResized(ComponentEvent e) {
254         //the table has changed size -> resize the window too! 
255         popupWindow.pack();
256       }
257     });
258 
259     KeyAdapter keyAdapter = new KeyAdapter() {
260       public void keyPressed(KeyEvent e) {
261         hideTimer.stop();
262       }
263     };
264 
265     typeCombo.getEditor().getEditorComponent().addKeyListener(keyAdapter);
266 
267     MouseListener windowMouseListener = new MouseAdapter() {
268       public void mouseEntered(MouseEvent evt) {
269         hideTimer.stop();
270       }
271       // allow a JWindow to be dragged with a mouse
272       public void mousePressed(MouseEvent me) {
273         pressed = me;
274       }
275     };
276 
277     MouseMotionListener windowMouseMotionListener = new MouseMotionAdapter() {
278       Point location;
279       // allow a JWindow to be dragged with a mouse
280       public void mouseDragged(MouseEvent me) {
281         location = popupWindow.getLocation(location);
282         int x = location.x - pressed.getX() + me.getX();
283         int y = location.y - pressed.getY() + me.getY();
284         popupWindow.setLocation(x, y);
285         pinnedButton.setSelected(true);
286        }
287     };
288 
289     popupWindow.getRootPane().addMouseListener(windowMouseListener);
290     popupWindow.getRootPane().addMouseMotionListener(windowMouseMotionListener);
291 
292     InputMap inputMap = ((JComponentpopupWindow.getContentPane()).
293       getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
294     actionMap = ((JComponent)popupWindow.getContentPane()).getActionMap();
295 
296     // add the key-action bindings of this Component to the parent window
297     solAction =
298       new StartOffsetLeftAction("", MainFrame.getIcon("extend-left"),
299       "<html><b>Extend start</b><small>" +
300       "<br>LEFT = 1 character" +
301       "<br> + SHIFT = 5 characters, "+
302       "<br> + CTRL + SHIFT = 10 characters</small></html>",
303        KeyEvent.VK_LEFT);
304     solButton.setAction(solAction);
305     inputMap.put(KeyStroke.getKeyStroke("LEFT")"solAction");
306     inputMap.put(KeyStroke.getKeyStroke("shift LEFT")"solAction");
307     inputMap.put(KeyStroke.getKeyStroke("control shift released LEFT"),
308       "solAction");
309     actionMap.put("solAction", solAction);
310 
311    sorAction =
312      new StartOffsetRightAction("", MainFrame.getIcon("extend-right"),
313       "<html><b>Shrink start</b><small>" +
314       "<br>RIGHT = 1 character" +
315       "<br> + SHIFT = 5 characters, "+
316       "<br> + CTRL + SHIFT = 10 characters</small></html>",
317       KeyEvent.VK_RIGHT);
318     sorButton.setAction(sorAction);
319     inputMap.put(KeyStroke.getKeyStroke("RIGHT")"sorAction");
320     inputMap.put(KeyStroke.getKeyStroke("shift RIGHT")"sorAction");
321     inputMap.put(KeyStroke.getKeyStroke("control shift released RIGHT"),
322       "sorAction");
323     actionMap.put("sorAction", sorAction);
324 
325     delAction =
326       new DeleteAnnotationAction("", MainFrame.getIcon("remove-annotation"),
327       "Delete the annotation", KeyEvent.VK_DELETE);
328     delButton.setAction(delAction);
329     inputMap.put(KeyStroke.getKeyStroke("alt DELETE")"delAction");
330     actionMap.put("delAction", delAction);
331 
332     eolAction =
333       new EndOffsetLeftAction("", MainFrame.getIcon("extend-left"),
334       "<html><b>Shrink end</b><small>" +
335       "<br>ALT + LEFT = 1 character" +
336       "<br> + SHIFT = 5 characters, "+
337       "<br> + CTRL + SHIFT = 10 characters</small></html>",
338        KeyEvent.VK_LEFT);
339     eolButton.setAction(eolAction);
340     inputMap.put(KeyStroke.getKeyStroke("alt LEFT")"eolAction");
341     inputMap.put(KeyStroke.getKeyStroke("alt shift LEFT")"eolAction");
342     inputMap.put(KeyStroke.getKeyStroke("control alt shift released LEFT"),
343       "eolAction");
344     actionMap.put("eolAction", eolAction);
345 
346     eorAction =
347       new EndOffsetRightAction("", MainFrame.getIcon("extend-right"),
348       "<html><b>Extend end</b><small>" +
349       "<br>ALT + RIGHT = 1 character" +
350       "<br> + SHIFT = 5 characters, "+
351       "<br> + CTRL + SHIFT = 10 characters</small></html>",
352       KeyEvent.VK_RIGHT);
353     eorButton.setAction(eorAction);
354     inputMap.put(KeyStroke.getKeyStroke("alt RIGHT")"eorAction");
355     inputMap.put(KeyStroke.getKeyStroke("alt shift RIGHT")"eorAction");
356     inputMap.put(KeyStroke.getKeyStroke("control alt shift released RIGHT"),
357       "eorAction");
358     actionMap.put("eorAction", eorAction);
359 
360     pinnedButton.setToolTipText("<html>Press to pin window in place"
361       "&nbsp;&nbsp;<font color=#667799><small>Ctrl-P"
362       "&nbsp;&nbsp;</small></font></html>");
363     inputMap.put(KeyStroke.getKeyStroke("control P")"toggle pin");
364     actionMap.put("toggle pin"new AbstractAction() {
365       public void actionPerformed(ActionEvent e) {
366         pinnedButton.doClick();
367       }
368     });
369 
370     DismissAction dismissAction = new DismissAction("", null,
371       "Close the window", KeyEvent.VK_ESCAPE);
372     dismissButton.setAction(dismissAction);
373     inputMap.put(KeyStroke.getKeyStroke("ESCAPE")"dismissAction");
374     inputMap.put(KeyStroke.getKeyStroke("alt ESCAPE")"dismissAction");
375     actionMap.put("dismissAction", dismissAction);
376 
377     ApplyAction applyAction =
378       new ApplyAction("Apply", null, "", KeyEvent.VK_ENTER);
379     inputMap.put(KeyStroke.getKeyStroke("alt ENTER")"applyAction");
380     actionMap.put("applyAction", applyAction);
381 
382     typeCombo.addActionListener(new ActionListener(){
383       public void actionPerformed(ActionEvent evt){
384         String newType = typeCombo.getSelectedItem().toString();
385         if(ann == null || ann.getType().equals(newType)) return;
386         //annotation editing
387         Integer oldId = ann.getId();
388         Annotation oldAnn = ann;
389         set.remove(ann);
390         try{
391           set.add(oldId, oldAnn.getStartNode().getOffset()
392                   oldAnn.getEndNode().getOffset()
393                   newType, oldAnn.getFeatures());
394           Annotation newAnn = set.get(oldId);
395           //update the selection to the new annotation
396           getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
397           editAnnotation(newAnn, set);
398           owner.annotationChanged(newAnn, set, oldAnn.getType());
399         }catch(InvalidOffsetException ioe){
400           throw new GateRuntimeException(ioe);
401         }
402       }
403     });
404 
405     hideTimer = new Timer(HIDE_DELAY, new ActionListener() {
406       public void actionPerformed(ActionEvent evt) {
407         annotationEditorInstance.setVisible(false);
408       }
409     });
410     hideTimer.setRepeats(false);
411 
412     AncestorListener textAncestorListener = new AncestorListener() {
413       public void ancestorAdded(AncestorEvent event) {
414         if(wasShowing) {
415           annotationEditorInstance.setVisible(true);
416         }
417         wasShowing = false;
418       }
419 
420       public void ancestorRemoved(AncestorEvent event) {
421         if(isShowing()) {
422           wasShowing = true;
423           popupWindow.dispose();
424         }
425       }
426 
427       public void ancestorMoved(AncestorEvent event) {
428       }
429 
430       private boolean wasShowing = false;
431     };
432     owner.getTextComponent().addAncestorListener(textAncestorListener);
433   }
434 
435   /* (non-Javadoc)
436    * @see gate.gui.annedit.AnnotationEditor#isActive()
437    */
438   public boolean isActive() {
439     return popupWindow.isVisible();
440   }
441 
442   public void editAnnotation(Annotation ann, AnnotationSet set){
443     this.ann = ann;
444     this.set = set;
445     if (ann == null) {
446       typeCombo.setModel(new DefaultComboBoxModel());
447       featuresEditor.setSchema(new AnnotationSchema());
448 //      popupWindow.doLayout();
449       popupWindow.validate();
450       return;
451     }
452     //repopulate the types combo
453     String annType = ann.getType();
454     Set<String> types = new HashSet<String>(schemasByType.keySet());
455     types.add(annType);
456     types.addAll(set.getAllTypes());
457     java.util.List<String> typeList = new ArrayList<String>(types);
458     Collections.sort(typeList);
459     typeCombo.setModel(new DefaultComboBoxModel(typeList.toArray()));
460     typeCombo.setSelectedItem(annType);
461    
462     featuresEditor.setSchema(schemasByType.get(annType));
463     featuresEditor.setTargetFeatures(ann.getFeatures());
464 //    popupWindow.doLayout();
465     popupWindow.validate();
466     setEditingEnabled(true);
467     setVisible(true);
468     if (!pinnedButton.isSelected()) {
469       hideTimer.restart();
470     }
471   }
472 
473   public Annotation getAnnotationCurrentlyEdited() {
474     return ann;
475   }
476   
477   /* (non-Javadoc)
478    * @see gate.gui.annedit.AnnotationEditor#editingFinished()
479    */
480   public boolean editingFinished() {
481     //this editor implementation has no special requirements (such as schema 
482     //compliance), so it always returns true.
483     return true;
484   }
485 
486   public boolean isShowing(){
487     return popupWindow.isShowing();
488   }
489   
490   /**
491    * Shows/Hides the UI(s) involved in annotation editing.
492    */
493   @Override
494   public void setVisible(boolean setVisible) {
495     super.setVisible(setVisible);
496     if (setVisible) {
497       placeDialog(ann.getStartNode().getOffset().intValue(),
498         ann.getEndNode().getOffset().intValue());
499 
500     else {
501       popupWindow.setVisible(false);
502       pinnedButton.setSelected(false);
503       SwingUtilities.invokeLater(new Runnable() { public void run() {
504         // when hiding the editor put back the focus in the document
505         owner.getTextComponent().requestFocus();
506       }});
507     }
508   }
509 
510   /**
511    * Finds the best location for the editor dialog for a given span of text.
512    */
513   public void placeDialog(int start, int end){
514     if(popupWindow.isVisible() && pinnedButton.isSelected()){
515       //just resize
516       Point where = popupWindow.getLocation();
517       popupWindow.pack();
518       if(where != null){
519         popupWindow.setLocation(where);
520       }
521     }else{
522       //calculate position
523       try{
524         Rectangle startRect = owner.getTextComponent().modelToView(start);
525         Rectangle endRect = owner.getTextComponent().modelToView(end);
526         Point topLeft = owner.getTextComponent().getLocationOnScreen();
527         int x = topLeft.x + startRect.x;
528         int y = topLeft.y + endRect.y + endRect.height;
529 
530         //make sure the window doesn't start lower 
531         //than the end of the visible rectangle
532         Rectangle visRect = owner.getTextComponent().getVisibleRect();
533         int maxY = topLeft.y + visRect.y + visRect.height;      
534         
535         //make sure window doesn't get off-screen       
536         popupWindow.pack();
537         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
538         boolean revalidate = false;
539         if(popupWindow.getSize().width > screenSize.width){
540           popupWindow.setSize(screenSize.width, popupWindow.getSize().height);
541           revalidate = true;
542         }
543         if(popupWindow.getSize().height > screenSize.height){
544           popupWindow.setSize(popupWindow.getSize().width, screenSize.height);
545           revalidate = true;
546         }
547         
548         if(revalidatepopupWindow.validate();
549         //calculate max X
550         int maxX = screenSize.width - popupWindow.getSize().width;
551         //calculate max Y
552         if(maxY + popupWindow.getSize().height > screenSize.height){
553           maxY = screenSize.height - popupWindow.getSize().height;
554         }
555         
556         //correct position
557         if(y > maxYy = maxY;
558         if(x > maxXx = maxX;
559         popupWindow.setLocation(x, y);
560       }catch(BadLocationException ble){
561         //this should never occur
562         throw new GateRuntimeException(ble);
563       }
564     }
565     if(!popupWindow.isVisible()) popupWindow.setVisible(true);
566   }
567   
568   /**
569    * Changes the span of an existing annotation by creating a new annotation 
570    * with the same ID, type and features but with the new start and end offsets.
571    @param set the annotation set 
572    @param oldAnnotation the annotation to be moved
573    @param newStartOffset the new start offset
574    @param newEndOffset the new end offset
575    */
576   protected void moveAnnotation(AnnotationSet set, Annotation oldAnnotation, 
577           Long newStartOffset, Long newEndOffsetthrows InvalidOffsetException{
578     //Moving is done by deleting the old annotation and creating a new one.
579     //If this was the last one of one type it would mess up the gui which 
580     //"forgets" about this type and then it recreates it (with a different 
581     //colour and not visible.
582     //In order to avoid this problem, we'll create a new temporary annotation.
583     Annotation tempAnn = null;
584     if(set.get(oldAnnotation.getType()).size() == 1){
585       //create a clone of the annotation that will be deleted, to act as a 
586       //placeholder 
587       Integer tempAnnId = set.add(oldAnnotation.getStartNode()
588               oldAnnotation.getStartNode(), oldAnnotation.getType()
589               oldAnnotation.getFeatures());
590       tempAnn = set.get(tempAnnId);
591     }
592 
593     Integer oldID = oldAnnotation.getId();
594     set.remove(oldAnnotation);
595     set.add(oldID, newStartOffset, newEndOffset,
596             oldAnnotation.getType(), oldAnnotation.getFeatures());
597     Annotation newAnn = set.get(oldID);
598     //update the selection to the new annotation
599     getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
600 
601     editAnnotation(newAnn, set);
602     //remove the temporary annotation
603     if(tempAnn != nullset.remove(tempAnn);
604     owner.annotationChanged(newAnn, set, null);
605   }   
606   
607   /**
608    * Base class for actions on annotations.
609    */
610   protected abstract class AnnotationAction extends AbstractAction{
611     public AnnotationAction(String text, Icon icon,
612                             String desc, int mnemonic){
613       super(text, icon);
614       putValue(SHORT_DESCRIPTION, desc);
615       putValue(MNEMONIC_KEY, mnemonic);
616     }
617   }
618 
619   protected class StartOffsetLeftAction extends AnnotationAction{
620     private static final long serialVersionUID = 1L;
621     public StartOffsetLeftAction(String text, Icon icon,
622                                  String desc, int mnemonic) {
623       super(text, icon, desc, mnemonic);
624     }
625     public void actionPerformed(ActionEvent evt){
626       int increment = 1;
627       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
628         //CTRL pressed -> use tokens for advancing
629         increment = SHIFT_INCREMENT;
630         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
631           increment = CTRL_SHIFT_INCREMENT;
632         }
633       }
634       long newValue = ann.getStartNode().getOffset().longValue() - increment;
635       if(newValue < 0newValue = 0;
636       try{
637         moveAnnotation(set, ann, new Long(newValue)
638                 ann.getEndNode().getOffset());
639       }catch(InvalidOffsetException ioe){
640         throw new GateRuntimeException(ioe);
641       }
642     }
643   }
644   
645   protected class StartOffsetRightAction extends AnnotationAction{
646     private static final long serialVersionUID = 1L;
647     public StartOffsetRightAction(String text, Icon icon,
648                                  String desc, int mnemonic) {
649       super(text, icon, desc, mnemonic);
650     }
651     public void actionPerformed(ActionEvent evt){
652       long endOffset = ann.getEndNode().getOffset().longValue()
653       int increment = 1;
654       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
655         //CTRL pressed -> use tokens for advancing
656         increment = SHIFT_INCREMENT;
657         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
658           increment = CTRL_SHIFT_INCREMENT;
659         }
660       }
661       
662       long newValue = ann.getStartNode().getOffset().longValue()  + increment;
663       if(newValue > endOffsetnewValue = endOffset;
664       try{
665         moveAnnotation(set, ann, new Long(newValue)
666                 ann.getEndNode().getOffset());
667       }catch(InvalidOffsetException ioe){
668         throw new GateRuntimeException(ioe);
669       }
670     }
671   }
672 
673   protected class EndOffsetLeftAction extends AnnotationAction{
674     private static final long serialVersionUID = 1L;
675     public EndOffsetLeftAction(String text, Icon icon,
676                                  String desc, int mnemonic) {
677       super(text, icon, desc, mnemonic);
678     }
679     public void actionPerformed(ActionEvent evt){
680       long startOffset = ann.getStartNode().getOffset().longValue()
681       int increment = 1;
682       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
683         //CTRL pressed -> use tokens for advancing
684         increment = SHIFT_INCREMENT;
685         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
686           increment =CTRL_SHIFT_INCREMENT;
687         }
688       }
689       
690       long newValue = ann.getEndNode().getOffset().longValue()  - increment;
691       if(newValue < startOffsetnewValue = startOffset;
692       try{
693         moveAnnotation(set, ann, ann.getStartNode().getOffset()
694                 new Long(newValue));
695       }catch(InvalidOffsetException ioe){
696         throw new GateRuntimeException(ioe);
697       }
698     }
699   }
700   
701   protected class EndOffsetRightAction extends AnnotationAction{
702     private static final long serialVersionUID = 1L;
703     public EndOffsetRightAction(String text, Icon icon,
704                                  String desc, int mnemonic) {
705       super(text, icon, desc, mnemonic);
706     }
707     public void actionPerformed(ActionEvent evt){
708       long maxOffset = owner.getDocument().getContent().size().longValue()
709       int increment = 1;
710       if((evt.getModifiers() & ActionEvent.SHIFT_MASK0){
711         //CTRL pressed -> use tokens for advancing
712         increment = SHIFT_INCREMENT;
713         if((evt.getModifiers() & ActionEvent.CTRL_MASK0){
714           increment = CTRL_SHIFT_INCREMENT;
715         }
716       }
717       long newValue = ann.getEndNode().getOffset().longValue() + increment;
718       if(newValue > maxOffsetnewValue = maxOffset;
719       try{
720         moveAnnotation(set, ann, ann.getStartNode().getOffset(),
721                 new Long(newValue));
722       }catch(InvalidOffsetException ioe){
723         throw new GateRuntimeException(ioe);
724       }
725     }
726   }
727   
728   protected class DeleteAnnotationAction extends AnnotationAction{
729    private static final long serialVersionUID = 1L;
730     public DeleteAnnotationAction(String text, Icon icon,
731                                  String desc, int mnemonic) {
732       super(text, icon, desc, mnemonic);
733     }
734     public void actionPerformed(ActionEvent evt){
735       set.remove(ann);
736 
737       //clear the dialog
738       editAnnotation(null, set);
739 
740       if(!pinnedButton.isSelected()){
741         //if not pinned, hide the dialog.
742         annotationEditorInstance.setVisible(false);
743       else {
744         setEditingEnabled(false);
745       }
746     }
747   }
748   
749   protected class DismissAction extends AnnotationAction{
750     private static final long serialVersionUID = 1L;
751     public DismissAction(String text, Icon icon,
752                        String desc, int mnemonic) {
753       super(text, icon, desc, mnemonic);
754       Icon exitIcon = UIManager.getIcon("InternalFrame.closeIcon");
755       if(exitIcon == nullexitIcon = MainFrame.getIcon("exit");
756       putValue(SMALL_ICON, exitIcon);
757     }
758     public void actionPerformed(ActionEvent evt){
759       annotationEditorInstance.setVisible(false);
760     }
761   }
762   
763   protected class ApplyAction extends AnnotationAction{
764     private static final long serialVersionUID = 1L;
765     public ApplyAction(String text, Icon icon,
766                        String desc, int mnemonic) {
767       super(text, icon, desc, mnemonic);
768     }
769     public void actionPerformed(ActionEvent evt){
770       annotationEditorInstance.setVisible(false);
771     }
772   }
773   
774   /**
775    * The popup window used by the editor.
776    */
777   protected JWindow popupWindow;
778 
779   /**
780    * Toggle button used to pin down the dialog. 
781    */
782   protected JToggleButton pinnedButton;
783   
784   /**
785    * Combobox for annotation type.
786    */
787   protected JComboBox typeCombo;
788   
789   /**
790    * Component for features editing.
791    */
792   protected FeaturesSchemaEditor featuresEditor;
793   
794   protected JScrollPane featuresScroller;
795   
796   
797   protected JButton solButton;
798   protected JButton sorButton;
799   protected JButton delButton;
800   protected JButton eolButton;
801   protected JButton eorButton;
802   protected JButton dismissButton;
803   
804   protected Timer hideTimer;
805   protected MouseEvent pressed;
806 
807   /**
808    * Constant for delay before hiding the popup window (in milliseconds).
809    */
810   protected static final int HIDE_DELAY = 1500;
811   
812   /**
813    * Constant for the number of characters when changing annotation boundary 
814    * with Shift key pressed.
815    */
816   protected static final int SHIFT_INCREMENT = 5;
817 
818   /**
819    * Constant for the number of characters when changing annotation boundary 
820    * with Ctrl+Shift keys pressed.
821    */
822   protected static final int CTRL_SHIFT_INCREMENT = 10;
823   
824   /**
825    * Stores the Annotation schema objects available in the system.
826    * The annotation types are used as keys for the map.
827    */
828   protected Map<String, AnnotationSchema> schemasByType;
829   
830   /**
831    * The controlling object for this editor.
832    */
833   private AnnotationEditorOwner owner;
834 
835   /**
836    * The annotation being edited.
837    */
838   protected Annotation ann;
839   
840   /**
841    * The parent set of the current annotation.
842    */
843   protected AnnotationSet set;
844 
845   /**
846    * Current instance of this class.
847    */
848   protected AnnotationEditor annotationEditorInstance;
849 
850   /**
851    * Action bindings for the popup window.
852    */
853   private ActionMap actionMap;
854 
855   private StartOffsetLeftAction solAction;
856   private StartOffsetRightAction sorAction;
857   private DeleteAnnotationAction delAction;
858   private EndOffsetLeftAction eolAction;
859   private EndOffsetRightAction eorAction;
860 
861   /* (non-Javadoc)
862    * @see gate.gui.annedit.AnnotationEditor#getAnnotationSetCurrentlyEdited()
863    */
864   public AnnotationSet getAnnotationSetCurrentlyEdited() {
865     return set;
866   }
867   
868   /**
869    @return the owner
870    */
871   public AnnotationEditorOwner getOwner() {
872     return owner;
873   }
874 
875   /**
876    @param owner the owner to set
877    */
878   public void setOwner(AnnotationEditorOwner owner) {
879     this.owner = owner;
880   }
881 
882   public void setPinnedMode(boolean pinned) {
883     pinnedButton.setSelected(pinned);
884   }
885 
886   public void setEditingEnabled(boolean isEditingEnabled) {
887     solButton.setEnabled(isEditingEnabled);
888     sorButton.setEnabled(isEditingEnabled);
889     delButton.setEnabled(isEditingEnabled);
890     eolButton.setEnabled(isEditingEnabled);
891     eorButton.setEnabled(isEditingEnabled);
892     typeCombo.setEnabled(isEditingEnabled);
893     // cancel editing, if any
894     if (featuresEditor.isEditing()) {
895       featuresEditor.getColumnModel()
896         .getColumn(featuresEditor.getEditingColumn())
897         .getCellEditor().cancelCellEditing();
898     }
899    // en/disable the featuresEditor table, no easy way unfortunately : |
900     featuresEditor.setEnabled(isEditingEnabled);
901     if (isEditingEnabled) {
902       // avoid the background to be incorrectly reset to the default color
903       Color tableBG = featuresEditor.getBackground();
904       tableBG = new Color(tableBG.getRGB());
905       featuresEditor.setBackground(tableBG);
906     }
907     final boolean isEditingEnabledF = isEditingEnabled;
908     for (int col = 0;
909          col < featuresEditor.getColumnCount(); col++) {
910       final TableCellRenderer previousTcr = featuresEditor
911         .getColumnModel().getColumn(col).getCellRenderer();
912       TableCellRenderer tcr = new TableCellRenderer() {
913       public Component getTableCellRendererComponent(JTable table,
914         Object value, boolean isSelected, boolean hasFocus,
915         int row, int column) {
916         Component c = previousTcr.getTableCellRendererComponent(
917           table, value, isSelected, hasFocus, row, column);
918         c.setEnabled(isEditingEnabledF);
919         return c;
920       }
921     };
922     featuresEditor.getColumnModel().getColumn(col).setCellRenderer(tcr);
923     }
924     // enable/disable the key binding actions
925     if (isEditingEnabled) {
926       actionMap.put("solAction", solAction);
927       actionMap.put("sorAction", sorAction);
928       actionMap.put("delAction", delAction);
929       actionMap.put("eolAction", eolAction);
930       actionMap.put("eorAction", eorAction);
931     else {
932       actionMap.put("solAction"null);
933       actionMap.put("sorAction"null);
934       actionMap.put("delAction"null);
935       actionMap.put("eolAction"null);
936       actionMap.put("eorAction"null);
937     }
938   }
939 
940   /**
941    * Does nothing, as this editor does not support cancelling and rollbacks.
942    */
943   public void cancelAction() throws GateException {
944   }
945 
946   /**
947    * Returns <tt>true</tt> always as this editor is generic and can edit any
948    * annotation type.
949    */
950   public boolean canDisplayAnnotationType(String annotationType) {
951     return true;
952   }
953 
954   /**
955    * Does nothing as this editor works in auto-commit mode (changes are 
956    * implemented immediately).
957    */
958   public void okAction() throws GateException {
959   }
960 
961   /**
962    * Returns <tt>false</tt>, as this editor does not support cancel operations.
963    */
964   public boolean supportsCancel() {
965     return false;
966   }
967 }