1   /*
2    * Created on Mar 23, 2004
3    *
4    * To change the template for this generated file go to
5    * Window - Preferences - Java - Code Generation - Code and Comments
6    */
7   package gate.gui.docview;
8   
9   import java.awt.*;
10  import java.awt.Color;
11  import java.awt.Component;
12  import java.awt.event.*;
13  import java.awt.event.ActionEvent;
14  import java.awt.event.ActionListener;
15  import java.util.*;
16  import java.util.List;
17  import java.util.prefs.Preferences;
18  
19  import javax.swing.*;
20  import javax.swing.JScrollPane;
21  import javax.swing.JTable;
22  import javax.swing.border.Border;
23  import javax.swing.event.*;
24  import javax.swing.event.MouseInputListener;
25  import javax.swing.event.PopupMenuListener;
26  import javax.swing.table.*;
27  import javax.swing.table.AbstractTableModel;
28  import javax.swing.table.TableCellRenderer;
29  import javax.swing.text.*;
30  import javax.swing.text.BadLocationException;
31  
32  import gate.*;
33  import gate.Annotation;
34  import gate.AnnotationSet;
35  import gate.event.AnnotationSetEvent;
36  import gate.event.AnnotationSetListener;
37  import gate.event.DocumentEvent;
38  import gate.event.DocumentListener;
39  import gate.gui.MainFrame;
40  import gate.swing.ColorGenerator;
41  import gate.swing.XJTable;
42  import gate.util.GateRuntimeException;
43  import gate.util.InvalidOffsetException;
44  
45  /**
46   * @author valyt
47   *
48   * To change the template for this generated type comment go to
49   * Window - Preferences - Java - Code Generation - Code and Comments
50   */
51  public class AnnotationSetsView extends AbstractDocumentView 
52                              implements DocumentListener,
53                                         AnnotationSetListener{
54  
55    
56    public AnnotationSetsView(){
57      setHandlers = new ArrayList();
58      tableRows = new ArrayList();
59      colourGenerator = new ColorGenerator();
60    }
61    
62  
63    /* (non-Javadoc)
64     * @see gate.gui.docview.DocumentView#getType()
65     */
66    public int getType() {
67      return VERTICAL;
68    }
69    
70    protected void initGUI() {
71      //get a pointer to the textual view used for highlights
72      Iterator centralViewsIter = owner.getCentralViews().iterator();
73      while(textView == null && centralViewsIter.hasNext()){
74        DocumentView aView = (DocumentView)centralViewsIter.next();
75        if(aView instanceof TextualDocumentView) 
76          textView = (TextualDocumentView)aView;
77      }
78      textPane = (JEditorPane)((JScrollPane)textView.getGUI())
79              .getViewport().getView();
80      
81      setHandlers.add(new SetHandler(document.getAnnotations()));
82      List setNames = document.getNamedAnnotationSets() == null ?
83              new ArrayList() :
84              new ArrayList(document.getNamedAnnotationSets().keySet());
85      Collections.sort(setNames);
86      Iterator setsIter = setNames.iterator();
87      while(setsIter.hasNext()){
88        setHandlers.add(new SetHandler(document.
89                getAnnotations((String)setsIter.next())));
90      }
91      tableRows.addAll(setHandlers);
92      mainTable = new XJTable();
93      tableModel = new SetsTableModel();
94      ((XJTable)mainTable).setSortable(false);
95      mainTable.setModel(tableModel);
96  //    mainTable.setRowMargin(0);
97  //    mainTable.getColumnModel().setColumnMargin(0);
98      SetsTableCellRenderer cellRenderer = new SetsTableCellRenderer();
99      mainTable.getColumnModel().getColumn(NAME_COL).setCellRenderer(cellRenderer);
100     mainTable.getColumnModel().getColumn(SELECTED_COL).setCellRenderer(cellRenderer);
101     SetsTableCellEditor cellEditor = new SetsTableCellEditor();
102     mainTable.getColumnModel().getColumn(SELECTED_COL).setCellEditor(cellEditor);
103     mainTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
104     mainTable.setColumnSelectionAllowed(false);
105     mainTable.setRowSelectionAllowed(true);
106     
107     mainTable.setTableHeader(null);
108     mainTable.setShowGrid(false);
109     mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
110     
111     scroller = new JScrollPane(mainTable);
112     scroller.getViewport().setOpaque(true);
113     scroller.getViewport().setBackground(mainTable.getBackground());
114     
115     annotationEditor = new AnnotationEditor(textView, this);
116     
117     mainPanel = new JPanel();
118     mainPanel.setLayout(new GridBagLayout());
119     GridBagConstraints constraints = new GridBagConstraints();
120     
121     constraints.gridy = 0;
122     constraints.gridx = GridBagConstraints.RELATIVE;
123     constraints.gridwidth = 2;
124     constraints.weighty = 1;
125     constraints.weightx = 1;
126     constraints.fill = GridBagConstraints.BOTH;
127     mainPanel.add(scroller, constraints);
128     
129     constraints.gridy = 1;
130     constraints.gridwidth = 1;
131     constraints.weighty = 0;
132     newSetNameTextField = new JTextField();
133     mainPanel.add(newSetNameTextField, constraints);
134     constraints.weightx = 0;
135     newSetAction = new NewAnnotationSetAction();
136     mainPanel.add(new JButton(newSetAction), constraints);
137     initListeners();
138   }
139   
140   public Component getGUI(){
141     return mainPanel;
142   }
143 
144   protected Color getColor(String annotationType){
145     Preferences prefRoot = Preferences.userNodeForPackage(getClass());
146     int rgba = prefRoot.getInt(annotationType, -1);
147     Color colour;
148     if(rgba == -1){
149       //initialise and save
150       float components[] = colourGenerator.getNextColor().getComponents(null);
151       colour = new Color(components[0],
152                          components[1],
153                          components[2],
154                          0.5f);
155       int rgb = colour.getRGB();
156       int alpha = colour.getAlpha();
157       rgba = rgb | (alpha << 24);
158       prefRoot.putInt(annotationType, rgba);
159     }else{
160       colour = new Color(rgba, true);
161     }
162     return colour;
163   }
164   
165   protected void saveColor(String annotationType, Color colour){
166     Preferences prefRoot = Preferences.userNodeForPackage(getClass());
167     int rgb = colour.getRGB();
168     int alpha = colour.getAlpha();
169     int rgba = rgb | (alpha << 24);
170     prefRoot.putInt(annotationType, rgba);
171   }
172   
173   /**
174    * This method will be called whenever the view becomes active. Implementers 
175    * should use this to add hooks (such as mouse listeners) to the other views
176    * as required by their functionality. 
177    */
178   protected void registerHooks(){
179     textPane.addMouseListener(textMouseListener);
180     textPane.addMouseMotionListener(textMouseListener);
181     textPane.addAncestorListener(textAncestorListener);
182   }
183 
184   /**
185    * This method will be called whenever this view becomes inactive. 
186    * Implementers should use it to unregister whatever hooks they registered
187    * in {@link #registerHooks()}.
188    *
189    */
190   protected void unregisterHooks(){
191     textPane.removeMouseListener(textMouseListener);
192     textPane.removeMouseMotionListener(textMouseListener);
193     textPane.removeAncestorListener(textAncestorListener);
194   }
195   
196   
197   protected void initListeners(){
198     document.addDocumentListener(this);
199     mainTable.addComponentListener(new ComponentAdapter(){
200       public void componentResized(ComponentEvent e){
201         //trigger a resize for the columns
202         mainTable.adjustSizes();
203 //        tableModel.fireTableRowsUpdated(0, 0);
204       }
205     });
206     
207     mainTable.addMouseListener(new MouseAdapter(){
208       public void mouseClicked(MouseEvent evt){
209         int row =  mainTable.rowAtPoint(evt.getPoint());
210         int column = mainTable.columnAtPoint(evt.getPoint());
211         if(row >= 0 && column == NAME_COL){
212           Object handler = tableRows.get(row);
213           if(handler instanceof TypeHandler){
214             TypeHandler tHandler = (TypeHandler)handler;
215             if(evt.getClickCount() >= 2){
216               //double click
217               tHandler.changeColourAction.actionPerformed(null);
218             }
219           }
220         }
221       }
222       public void mousePressed(MouseEvent evt){
223         int row =  mainTable.rowAtPoint(evt.getPoint());
224         int column = mainTable.columnAtPoint(evt.getPoint());
225         if(row >= 0 && column == NAME_COL){
226           Object handler = tableRows.get(row);
227           if(handler instanceof TypeHandler){
228             TypeHandler tHandler = (TypeHandler)handler;
229             if(evt.isPopupTrigger()){
230               //show popup
231               JPopupMenu popup = new JPopupMenu();
232               popup.add(tHandler.changeColourAction);
233               popup.show(mainTable, evt.getX(), evt.getY());
234             }
235           }
236         }
237       }
238       
239       public void mouseReleased(MouseEvent evt){
240         int row =  mainTable.rowAtPoint(evt.getPoint());
241         int column = mainTable.columnAtPoint(evt.getPoint());
242         if(row >= 0 && column == NAME_COL){
243           Object handler = tableRows.get(row);
244           if(handler instanceof TypeHandler){
245             TypeHandler tHandler = (TypeHandler)handler;
246             if(evt.isPopupTrigger()){
247               //show popup
248               JPopupMenu popup = new JPopupMenu();
249               popup.add(tHandler.changeColourAction);
250               popup.show(mainTable, evt.getX(), evt.getY());
251             }
252           }
253         }
254       }
255     });
256     
257     
258     mouseStoppedMovingAction = new MouseStoppedMovingAction();
259     mouseMovementTimer = new javax.swing.Timer(MOUSE_MOVEMENT_TIMER_DELAY, 
260             mouseStoppedMovingAction);
261     mouseMovementTimer.setRepeats(false);
262     textMouseListener = new TextMouseListener();
263     textAncestorListener = new AncestorListener(){
264       public void ancestorAdded(AncestorEvent event){
265         if(wasShowing) annotationEditor.show(false);
266         wasShowing = false;
267       }
268       
269       public void ancestorRemoved(AncestorEvent event){
270         if(annotationEditor.isShowing()){
271           wasShowing = true;
272           annotationEditor.hide();
273         }
274       }
275       
276       public void ancestorMoved(AncestorEvent event){
277         
278       }
279       private boolean wasShowing = false; 
280     };
281     
282     mainTable.getInputMap().put(
283             KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteAll");
284     mainTable.getActionMap().put("deleteAll", 
285             new DeleteSelectedAnnotationGroupAction());
286     newSetNameTextField.getInputMap().put(
287             KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "newSet");
288     newSetNameTextField.getActionMap().put("newSet", newSetAction);
289   }
290     
291   
292   /* (non-Javadoc)
293    * @see gate.Resource#cleanup()
294    */
295   public void cleanup() {
296     document.removeDocumentListener(this);
297     super.cleanup();
298   }
299   
300   public void annotationSetAdded(DocumentEvent e) {
301     String newSetName = e.getAnnotationSetName();
302     SetHandler sHandler = new SetHandler(document.getAnnotations(newSetName));
303     //find the right location for the new set
304     //this is a named set and the first one is always the default one
305     int i = 1;
306     for(;
307         i < setHandlers.size() && 
308         ((SetHandler)setHandlers.get(i)).set.
309           getName().compareTo(newSetName) <= 0;
310         i++);
311     setHandlers.add(i, sHandler);
312     //update the tableRows list
313     SetHandler previousHandler = (SetHandler)setHandlers.get(i -1);
314     //find the index for the previous handler - which is guaranteed to exist
315     int j = 0;
316     for(;
317       tableRows.get(j) != previousHandler;
318       j++);
319     if(previousHandler.isExpanded()){
320       j += previousHandler.typeHandlers.size();
321     }
322     j++;
323     tableRows.add(j, sHandler);
324     //update the table view
325     tableModel.fireTableRowsInserted(j, j);
326   }//public void annotationSetAdded(DocumentEvent e) 
327   
328   public void annotationSetRemoved(DocumentEvent e) {
329     String setName = e.getAnnotationSetName();
330     //find the handler and remove it from the list of handlers
331 //    Iterator shIter = setHandlers.iterator();
332     SetHandler sHandler = getSetHandler(setName);
333     if(sHandler != null){
334       //remove highlights if any
335       Iterator typeIter = sHandler.typeHandlers.iterator();
336       while(typeIter.hasNext()){
337         TypeHandler tHandler = (TypeHandler)typeIter.next();
338         tHandler.setSelected(false);
339       }
340       setHandlers.remove(sHandler);
341       //remove the set from the table
342       int row = tableRows.indexOf(sHandler);
343       tableRows.remove(row);
344       int removed = 1;
345       //remove the type rows as well
346       if(sHandler.isExpanded())
347         for(int i = 0; i < sHandler.typeHandlers.size(); i++){ 
348           tableRows.remove(row);
349           removed++;
350         }
351       tableModel.fireTableRowsDeleted(row, row + removed -1);
352       sHandler.cleanup();
353     }
354   }//public void annotationSetRemoved(DocumentEvent e) 
355   
356   /**Called when the content of the document has changed through an edit 
357    * operation.
358    */
359   public void contentEdited(DocumentEvent e){
360     //go through all the type handlers and propagate the event
361     Iterator setIter = setHandlers.iterator();
362     while(setIter.hasNext()){
363       SetHandler sHandler = (SetHandler)setIter.next();
364       Iterator typeIter = sHandler.typeHandlers.iterator();
365       while(typeIter.hasNext()){
366         TypeHandler tHandler = (TypeHandler)typeIter.next();
367         if(tHandler.isSelected()) 
368           tHandler.repairHighlights(e.getEditStart().intValue(), 
369                   e.getEditEnd().intValue());
370       }
371     }
372   }
373   
374   
375   public void annotationAdded(AnnotationSetEvent e) {
376     AnnotationSet set = (AnnotationSet)e.getSource();
377     Annotation ann = e.getAnnotation();
378     TypeHandler tHandler = getTypeHandler(set.getName(), ann.getType());
379     if(tHandler == null){
380       //new type for this set
381       SetHandler sHandler = getSetHandler(set.getName());
382       tHandler = sHandler.newType(ann.getType());
383     }
384     tHandler.annotationAdded(ann);
385   }
386   
387   public void annotationRemoved(AnnotationSetEvent e) {
388     AnnotationSet set = (AnnotationSet)e.getSource();
389     Annotation ann = e.getAnnotation();
390     TypeHandler tHandler = getTypeHandler(set.getName(), ann.getType());
391     tHandler.annotationRemoved(ann);
392   }
393   
394   protected SetHandler getSetHandler(String name){
395     Iterator shIter = setHandlers.iterator();
396     while(shIter.hasNext()){
397       SetHandler sHandler = (SetHandler)shIter.next();
398       if(name == null){
399         if(sHandler.set.getName() == null) return sHandler;
400       }else{
401         if(name.equals(sHandler.set.getName())) return sHandler;
402       }
403     }
404     return null;
405   }
406   
407   protected TypeHandler getTypeHandler(String set, String type){
408     SetHandler sHandler = getSetHandler(set);
409     TypeHandler tHandler = null;
410     Iterator typeIter = sHandler.typeHandlers.iterator();
411     while(tHandler == null && typeIter.hasNext()){
412       TypeHandler aHandler = (TypeHandler)typeIter.next();
413       if(aHandler.name.equals(type)) tHandler = aHandler;
414     }
415     return tHandler;
416   }
417   
418   public void setTypeSelected(final String setName, 
419                               final String typeName, 
420                               final boolean selected){
421     
422     SwingUtilities.invokeLater(new Runnable(){
423       public void run(){
424         TypeHandler tHandler = getTypeHandler(setName, typeName);
425         tHandler.setSelected(selected);
426         int row = tableRows.indexOf(tHandler);
427         tableModel.fireTableRowsUpdated(row, row);
428       }
429     });
430   }
431   
432   /**
433    * Sets the last annotation type created (which will be used as a default
434    * for creating new annotations).
435    * @param annType the type of annotation.
436    */
437   void setLastAnnotationType(String annType){
438     this.lastAnnotationType = annType;
439   }
440   
441   protected class SetsTableModel extends AbstractTableModel{
442     public int getRowCount(){
443       return tableRows.size();
444 //      //we have at least one row per set
445 //      int rows = setHandlers.size();
446 //      //expanded sets add rows
447 //      for(int i =0; i < setHandlers.size(); i++){
448 //        SetHandler sHandler = (SetHandler)setHandlers.get(i);
449 //        rows += sHandler.expanded ? sHandler.set.getAllTypes().size() : 0;
450 //      }
451 //      return rows;
452     }
453     
454     public int getColumnCount(){
455       return 2;
456     }
457     
458     public Object getValueAt(int row, int column){
459       Object value = tableRows.get(row);
460       switch(column){
461         case NAME_COL:
462           return value;
463         case SELECTED_COL:
464           if(value instanceof SetHandler)
465             return new Boolean(((SetHandler)value).isExpanded());
466           if(value instanceof TypeHandler) 
467             return new Boolean(((TypeHandler)value).isSelected());
468         default:
469           return null;
470       }
471 //      
472 //      int currentRow = 0;
473 //      Iterator handlerIter = setHandlers.iterator();
474 //      SetHandler sHandler = (SetHandler)handlerIter.next();
475 //      
476 //      while(currentRow < row){
477 //        if(sHandler.expanded){
478 //          if(sHandler.typeHandlers.size() + currentRow >= row){
479 //            //we want a row in current set
480 //             return sHandler.typeHandlers.get(row - currentRow);
481 //          }else{
482 //            currentRow += sHandler.typeHandlers.size();
483 //            sHandler = (SetHandler)handlerIter.next();
484 //          }
485 //        }else{
486 //          //just go to next handler
487 //          currentRow++;
488 //          sHandler = (SetHandler)handlerIter.next();
489 //        }
490 //        if(currentRow == row) return sHandler;
491 //      }
492 //      if(currentRow == row) return sHandler;
493 //System.out.println("BUG! row: " + row + " col: " + column);      
494 //      return null;
495     }
496     
497     public boolean isCellEditable(int rowIndex, int columnIndex){
498       Object value = tableRows.get(rowIndex);
499       switch(columnIndex){
500         case NAME_COL: return false;
501         case SELECTED_COL:
502           if(value instanceof SetHandler)
503             return ((SetHandler)value).typeHandlers.size() > 0;
504           if(value instanceof TypeHandler) return true; 
505       }
506       return columnIndex == SELECTED_COL;
507     }
508     
509     public void setValueAt(Object aValue, int rowIndex, int columnIndex){
510       Object receiver = tableRows.get(rowIndex);
511       switch(columnIndex){
512         case SELECTED_COL:
513           if(receiver instanceof SetHandler){
514             ((SetHandler)receiver).setExpanded(((Boolean)aValue).booleanValue());
515           }else if(receiver instanceof TypeHandler){
516             ((TypeHandler)receiver).setSelected(((Boolean)aValue).booleanValue());
517           }
518           
519           break;
520         default:
521           break;
522       }
523     }
524   }//public Object getValueAt(int row, int column)
525   
526   protected class SetsTableCellRenderer implements TableCellRenderer{
527     public SetsTableCellRenderer(){
528       typeLabel = new JLabel(){
529         public void repaint(long tm, int x, int y, int width, int height){}
530         public void repaint(Rectangle r){}
531         public void validate(){}
532         public void revalidate(){}
533         protected void firePropertyChange(String propertyName,
534                                           Object oldValue,
535                                           Object newValue){}
536       };
537       typeLabel.setOpaque(true);
538       typeLabel.setBorder(BorderFactory.createCompoundBorder(
539               BorderFactory.createMatteBorder(0, 5, 0, 0,
540                       mainTable.getBackground()),
541               BorderFactory.createEmptyBorder(0, 5, 0, 5)));
542 //      typeLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
543 
544       
545       setLabel = new JLabel(){
546         public void repaint(long tm, int x, int y, int width, int height){}
547         public void repaint(Rectangle r){}
548         public void validate(){}
549         public void revalidate(){}
550         protected void firePropertyChange(String propertyName,
551                                           Object oldValue,
552                                           Object newValue){}
553       };
554       setLabel.setOpaque(true);
555       setLabel.setFont(setLabel.getFont().deriveFont(Font.BOLD));
556       setLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
557       
558 
559       typeChk = new JCheckBox(){
560         public void repaint(long tm, int x, int y, int width, int height){}
561         public void repaint(Rectangle r){}
562         public void validate(){}
563         public void revalidate(){}
564         protected void firePropertyChange(String propertyName,
565                                           Object oldValue,
566                                           Object newValue){}
567       };
568       typeChk.setOpaque(true);
569 //      typeChk.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
570 
571       setChk = new JCheckBox(){
572         public void repaint(long tm, int x, int y, int width, int height){}
573         public void repaint(Rectangle r){}
574         public void validate(){}
575         public void revalidate(){}
576         protected void firePropertyChange(String propertyName,
577                                           Object oldValue,
578                                           Object newValue){}
579       };
580       setChk.setSelectedIcon(MainFrame.getIcon("expanded.gif"));
581       setChk.setIcon(MainFrame.getIcon("closed.gif"));
582       setChk.setMaximumSize(setChk.getMinimumSize());
583       setChk.setOpaque(true);
584       
585       normalBorder = BorderFactory.createLineBorder(
586               mainTable.getBackground(), 2);
587       selectedBorder = BorderFactory.createLineBorder(
588               mainTable.getSelectionBackground(), 2);
589     }
590     
591     public Component getTableCellRendererComponent(JTable table,
592                                                    Object value,
593                                                    boolean isSelected,
594                                                    boolean hasFocus,
595                                                    int row,
596                                                    int column){
597       
598       value = tableRows.get(row);
599       if(value instanceof SetHandler){
600         SetHandler sHandler = (SetHandler)value;
601         switch(column){
602           case NAME_COL:
603             setLabel.setText(sHandler.set.getName());
604             setLabel.setBackground(isSelected ?
605                                    table.getSelectionBackground() :
606                                    table.getBackground());
607             return setLabel;
608           case SELECTED_COL:
609             setChk.setSelected(sHandler.isExpanded());
610             setChk.setEnabled(sHandler.typeHandlers.size() > 0);
611             setChk.setBackground(isSelected ?
612                                  table.getSelectionBackground() :
613                                  table.getBackground());
614             return setChk;
615         }
616       }else if(value instanceof TypeHandler){
617         TypeHandler tHandler = (TypeHandler)value;
618         switch(column){
619           case NAME_COL:
620             typeLabel.setBackground(tHandler.colour);
621             typeLabel.setText(tHandler.name);
622             typeLabel.setBorder(isSelected ? selectedBorder : normalBorder);
623             return typeLabel;
624           case SELECTED_COL:
625             typeChk.setBackground(isSelected ?
626                    table.getSelectionBackground() :
627                    table.getBackground());
628             typeChk.setSelected(tHandler.isSelected());
629             return typeChk;
630         }
631       }
632       typeLabel.setText("?");
633       return typeLabel;
634       //bugcheck!
635     }
636     
637     protected JLabel typeLabel;
638     protected JLabel setLabel;
639     protected JCheckBox setChk;
640     protected JCheckBox typeChk;
641     protected Border selectedBorder;
642     protected Border normalBorder;
643   }
644   
645   protected class SetsTableCellEditor extends AbstractCellEditor
646                                       implements TableCellEditor{
647     public SetsTableCellEditor(){
648       setChk = new JCheckBox();
649       setChk.setSelectedIcon(MainFrame.getIcon("expanded.gif"));
650       setChk.setIcon(MainFrame.getIcon("closed.gif"));
651 //      setChk.setMaximumSize(setChk.getMinimumSize());
652       setChk.setOpaque(true);
653       setChk.addActionListener(new ActionListener(){
654         public void actionPerformed(ActionEvent evt){
655           fireEditingStopped();
656         }
657       });
658       typeChk = new JCheckBox();
659       typeChk.setOpaque(false);
660 //      typeChk.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
661       typeChk.addActionListener(new ActionListener(){
662         public void actionPerformed(ActionEvent evt){
663           fireEditingStopped();
664         }
665       });
666     }
667     
668     public Component getTableCellEditorComponent(JTable table,
669                                                  Object value,
670                                                  boolean isSelected,
671                                                  int row,
672                                                  int column){
673       value = tableRows.get(row);
674       if(value instanceof SetHandler){
675         SetHandler sHandler = (SetHandler)value;
676         switch(column){
677           case NAME_COL: return null;
678           case SELECTED_COL:
679             setChk.setSelected(sHandler.isExpanded());
680             setChk.setEnabled(sHandler.typeHandlers.size() > 0);
681             setChk.setBackground(isSelected ?
682                                  table.getSelectionBackground() :
683                                  table.getBackground());
684             currentChk = setChk;
685             return setChk;
686         }
687       }else if(value instanceof TypeHandler){
688         TypeHandler tHandler = (TypeHandler)value;
689         switch(column){
690           case NAME_COL: return null;
691           case SELECTED_COL:
692 //            typeChk.setBackground(tHandler.colour);
693             typeChk.setSelected(tHandler.isSelected());
694             currentChk = typeChk;
695             return typeChk;
696         }
697       }
698       return null;
699     }
700     
701     public boolean stopCellEditing(){
702       return true;
703     }
704     
705     public Object getCellEditorValue(){
706       return new Boolean(currentChk.isSelected());
707     }
708     
709     public boolean shouldSelectCell(EventObject anEvent){
710       return true;
711     }
712     
713     public boolean isCellEditable(EventObject anEvent){
714       return true;
715     }
716     
717     JCheckBox currentChk;
718     JCheckBox setChk;
719     JCheckBox typeChk;
720   }
721   
722   
723   /**
724    * Stores the data related to an annotation set
725    */
726   protected class SetHandler{
727     SetHandler(AnnotationSet set){
728       this.set = set;
729       typeHandlers = new ArrayList();
730       typeHandlersByType = new HashMap();
731       List typeNames = new ArrayList(set.getAllTypes());
732       Collections.sort(typeNames);
733       Iterator typIter = typeNames.iterator();
734       while(typIter.hasNext()){
735         String name = (String)typIter.next();
736         TypeHandler tHandler = new TypeHandler(this, name); 
737         typeHandlers.add(tHandler);
738         typeHandlersByType.put(name, tHandler);
739       }
740       set.addAnnotationSetListener(AnnotationSetsView.this);
741     }
742     
743     public void cleanup(){
744       set.removeAnnotationSetListener(AnnotationSetsView.this);
745       typeHandlers.clear();
746     }
747     
748     /**
749      * Notifies this set handler that anew type of annotations has been created
750      * @param type the new type of annotations
751      * @return the new TypeHandler created as a result
752      */
753     public TypeHandler newType(String type){
754       //create a new TypeHandler
755       TypeHandler tHandler = new TypeHandler(this, type);
756       //add it to the list at the right position
757       int pos = 0;
758       for(;
759           pos < typeHandlers.size() &&
760           ((TypeHandler)typeHandlers.get(pos)).name.compareTo(type) <= 0;
761           pos++);
762       typeHandlers.add(pos, tHandler);
763       typeHandlersByType.put(type, tHandler);
764       int setRow = tableRows.indexOf(this);
765       if(typeHandlers.size() == 1) 
766         tableModel.fireTableRowsUpdated(setRow, setRow);
767       if(expanded){
768         tableRows.add(setRow + pos + 1, tHandler);
769         tableModel.fireTableRowsInserted(setRow + pos + 1,
770               setRow + pos + 1);
771       }
772       return tHandler;
773     }
774     
775     public void removeType(TypeHandler tHandler){
776       int setRow = tableRows.indexOf(this);
777       int pos = typeHandlers.indexOf(tHandler);
778       typeHandlers.remove(pos);
779       typeHandlersByType.remove(tHandler.name);
780       if(expanded){
781         tableRows.remove(setRow + pos + 1);
782         tableModel.fireTableRowsDeleted(setRow + pos + 1, setRow + pos + 1);
783       }
784       if(typeHandlers.isEmpty()){
785         //the set has no more handlers
786         setExpanded(false);
787         tableModel.fireTableRowsUpdated(setRow, setRow);
788       }
789     }
790     
791     public void removeType(String type){
792       removeType((TypeHandler)typeHandlersByType.get(type));
793     }
794 
795     public TypeHandler getTypeHandler(String type){
796       return (TypeHandler)typeHandlersByType.get(type);
797     }
798     
799     public void setExpanded(boolean expanded){
800       if(this.expanded == expanded) return;
801       this.expanded = expanded;
802       int myPosition = tableRows.indexOf(this);
803       if(expanded){
804         //expand
805         tableRows.addAll(myPosition + 1, typeHandlers);
806         tableModel.fireTableRowsInserted(myPosition + 1, 
807                                          myPosition + 1 + typeHandlers.size());
808       }else{
809         //collapse
810         for(int i = 0; i < typeHandlers.size(); i++){
811           tableRows.remove(myPosition + 1);
812         }
813         tableModel.fireTableRowsDeleted(myPosition + 1, 
814                                         myPosition + 1 + typeHandlers.size());
815       }
816       tableModel.fireTableRowsUpdated(myPosition, myPosition);
817     }
818     
819     public boolean isExpanded(){
820       return expanded;
821     }
822     
823     
824     AnnotationSet set;
825     List typeHandlers;
826     Map typeHandlersByType;
827     private boolean expanded = false;
828   }
829   
830   protected class TypeHandler{
831     TypeHandler (SetHandler setHandler, String name){
832       this.setHandler = setHandler;
833       this.name = name;
834       colour = getColor(name);
835       hghltTagsForAnn = new HashMap();
836       changeColourAction = new ChangeColourAction();
837     }
838     
839     public void setColour(Color colour){
840       if(this.colour.equals(colour)) return;
841       this.colour = colour;
842       saveColor(name, colour);
843       if(isSelected()){
844         //redraw the highlights
845         Runnable runnable = new Runnable(){
846           public void run(){
847             //hide highlights
848             textView.removeHighlights(hghltTagsForAnn.values());
849             hghltTagsForAnn.clear();
850             //show highlights
851             List annots = new ArrayList(setHandler.set.get(name));
852             List tags = textView.addHighlights(annots, setHandler.set, 
853                     TypeHandler.this.colour);
854             for(int i = 0; i < annots.size(); i++){
855               hghltTagsForAnn.put(((Annotation)annots.get(i)).getId(), tags.get(i));
856             }
857           }
858         };
859         Thread thread = new Thread(runnable);
860         thread.setPriority(Thread.MIN_PRIORITY);
861         thread.start();
862       }
863       //update the table display
864       SwingUtilities.invokeLater(new Runnable(){
865         public void run(){
866           int row = tableRows.indexOf(this);
867           if(row >= 0) tableModel.fireTableRowsUpdated(row, row);
868         }
869       });
870     }
871     
872     public void setSelected(boolean selected){
873       if(this.selected == selected) return;
874       this.selected = selected;
875       final List annots = new ArrayList(setHandler.set.get(name));
876       if(selected){
877         //make sure set is expanded
878         setHandler.setExpanded(true);
879         //show highlights
880         hghltTagsForAnn.clear();
881         Iterator annIter = annots.iterator();
882         //we're doing a lot of operations so let's get out of the UI thread
883         Runnable runnable = new Runnable(){
884           public void run(){
885             //do all operations in one go
886             List tags = textView.addHighlights(annots, setHandler.set, colour);
887             for(int i = 0; i < annots.size(); i++){
888               hghltTagsForAnn.put(((Annotation)annots.get(i)).getId(), tags.get(i));
889             }
890           }
891         };
892         Thread thread = new Thread(runnable);
893         thread.setPriority(Thread.MIN_PRIORITY);
894         thread.start();
895       }else{
896         //hide highlights
897         Runnable runnable = new Runnable(){
898           public void run(){
899             //do all operations in one go
900             textView.removeHighlights(hghltTagsForAnn.values());
901             hghltTagsForAnn.clear();
902           }
903         };
904         Thread thread = new Thread(runnable);
905         thread.setPriority(Thread.MIN_PRIORITY);
906         thread.start();
907       }
908       //update the table display
909       int row = tableRows.indexOf(this);
910       tableModel.fireTableRowsUpdated(row, row);
911     }
912     
913     public boolean isSelected(){
914       return selected;
915     }
916     
917     /**
918      * Notifies this type handler that a new annotation was created of the 
919      * right type
920      * @param ann
921      */
922     public void annotationAdded(Annotation ann){
923       //if selected, add new highlight
924       if(selected) hghltTagsForAnn.put(ann.getId(), 
925               textView.addHighlight(ann, setHandler.set, colour));
926     }
927     
928     /**
929      * Notifies this type handler that an annotation has been removed
930      * @param ann the removed annotation
931      */
932     public void annotationRemoved(Annotation ann){
933       if(selected){
934         Object tag = hghltTagsForAnn.remove(ann.getId());
935         textView.removeHighlight(tag);
936       }
937       //if this was the last annotation of this type then the handler is no
938       //longer required
939       Set remainingAnns = setHandler.set.get(name); 
940       if(remainingAnns == null || remainingAnns.isEmpty()){
941         setHandler.removeType(this);
942       }
943     }
944     
945     protected void repairHighlights(int start, int end){
946       //map from tag to annotation
947       List tags = new ArrayList(hghltTagsForAnn.size());
948       List annots = new ArrayList(hghltTagsForAnn.size());
949       Iterator annIter = hghltTagsForAnn.keySet().iterator();
950       while(annIter.hasNext()){
951         Annotation ann = setHandler.set.get((Integer)annIter.next());
952         int annStart = ann.getStartNode().getOffset().intValue();
953         int annEnd = ann.getEndNode().getOffset().intValue();
954         if((annStart <= start && start <= annEnd) ||
955            (start <= annStart && annStart <= end)){
956           if(!hghltTagsForAnn.containsKey(ann.getId())){
957             System.out.println("Error!!!");
958           }
959           tags.add(hghltTagsForAnn.get(ann.getId()));
960           annots.add(ann);
961         }
962       }
963       for(int i = 0; i < tags.size(); i++){
964         Object tag = tags.get(i);
965         Annotation ann = (Annotation)annots.get(i);
966         try{
967           textView.moveHighlight(tag, 
968                   ann.getStartNode().getOffset().intValue(), 
969                   ann.getEndNode().getOffset().intValue());
970         }catch(BadLocationException ble){
971           //this should never happen as the offsets come from an annotation
972         }
973       }
974     }
975     
976     
977     protected class ChangeColourAction extends AbstractAction{
978       public ChangeColourAction(){
979         super("Change colour");
980       }
981       
982       public void actionPerformed(ActionEvent evt){
983         Color col = JColorChooser.showDialog(mainTable, 
984                 "Select colour for \"" + name + "\"",
985                 colour);
986         if(col != null){
987           Color colAlpha = new Color(col.getRed(), col.getGreen(),
988                   col.getBlue(), 128);
989           setColour(colAlpha);
990         }
991       }
992     }
993     
994     ChangeColourAction changeColourAction;
995     boolean selected;
996     //Map from annotation ID (which is imuttable) to tag
997     Map hghltTagsForAnn;
998     String name;
999     SetHandler setHandler;
1000    Color colour;
1001  }
1002  
1003  protected static class AnnotationHandler{
1004    public AnnotationHandler(AnnotationSet set, Annotation ann){
1005      this.ann = ann;
1006      this.set = set;
1007    }
1008    Annotation ann;
1009    AnnotationSet set;
1010  }
1011  
1012  /**
1013   * A mouse listener used for events in the text view. 
1014   */
1015  protected class TextMouseListener implements MouseInputListener{    
1016    public void mouseDragged(MouseEvent e){
1017      mouseStoppedMovingAction.setTextLocation(textPane.viewToModel(e.getPoint()));
1018      mouseMovementTimer.restart();
1019    }
1020    
1021    public void mouseMoved(MouseEvent e){
1022      //this triggers select annotation leading to edit annotation or new 
1023      //annotation actions
1024      mouseStoppedMovingAction.setTextLocation(textPane.viewToModel(e.getPoint()));
1025      mouseMovementTimer.restart();
1026    }
1027    
1028    public void mouseClicked(MouseEvent e){
1029      //this is required so we can trigger new annotation when selecting text 
1030      //by double/triple clicking
1031      mouseStoppedMovingAction.setTextLocation(textPane.viewToModel(e.getPoint()));
1032      mouseMovementTimer.restart();
1033    }
1034    
1035    public void mousePressed(MouseEvent e){
1036      
1037    }
1038    public void mouseReleased(MouseEvent e){
1039      
1040    }
1041    
1042    public void mouseEntered(MouseEvent e){
1043      
1044    }
1045    
1046    public void mouseExited(MouseEvent e){
1047      mouseMovementTimer.stop();
1048    }
1049  }//protected class TextMouseListener implements MouseInputListener
1050  
1051    
1052  protected class NewAnnotationSetAction extends AbstractAction{
1053    public NewAnnotationSetAction(){
1054      super("New");
1055      putValue(SHORT_DESCRIPTION, "Creates a new annotation set");
1056    }
1057    
1058    public void actionPerformed(ActionEvent evt){
1059      String name = newSetNameTextField.getText();
1060      newSetNameTextField.setText("");
1061      if(name != null && name.length() > 0){
1062        document.getAnnotations(name);
1063      }
1064    }
1065  }
1066
1067  protected class NewAnnotationAction extends AbstractAction{
1068    public void actionPerformed(ActionEvent evt){
1069      int start = textPane.getSelectionStart();
1070      int end = textPane.getSelectionEnd();
1071      if(start != end){
1072        textPane.setSelectionStart(start);
1073        textPane.setSelectionEnd(start);
1074        //create a new annotation
1075        //find the selected set
1076        int row = mainTable.getSelectedRow();
1077        //select the default annotation set if none selected
1078        if(row < 0) row = 0;
1079        //find the set handler
1080        while(!(tableRows.get(row) instanceof SetHandler)) row --;
1081        AnnotationSet set = ((SetHandler)tableRows.get(row)).set;
1082        try{
1083          Integer annId =  set.add(new Long(start), new Long(end), 
1084                  lastAnnotationType, Factory.newFeatureMap());
1085          Annotation ann = set.get(annId);
1086          //make sure new annotaion is visible
1087          setTypeSelected(set.getName(), ann.getType(), true);
1088          //show the editor
1089          annotationEditor.setAnnotation(ann, set);
1090          annotationEditor.show(true);
1091        }catch(InvalidOffsetException ioe){
1092          //this should never happen
1093          throw new GateRuntimeException(ioe);
1094        }
1095      }
1096    }
1097  }
1098  
1099  /**
1100   * Used to select an annotation for editing.
1101   *
1102   */
1103  protected class MouseStoppedMovingAction extends AbstractAction{
1104    
1105    public void actionPerformed(ActionEvent evt){
1106      //first check for selection hovering
1107      //if inside selection, add new annotation.
1108      if(textPane.getSelectionStart() <= textLocation &&
1109         textPane.getSelectionEnd() >= textLocation){
1110        new NewAnnotationAction().actionPerformed(evt);
1111      }else{
1112        //now check for annotations at location
1113        List annotsAtPoint = new ArrayList();
1114        Iterator shIter = setHandlers.iterator();
1115        while(shIter.hasNext()){
1116          SetHandler sHandler = (SetHandler)shIter.next();
1117          Iterator annIter = sHandler.set.get(new Long(textLocation),
1118                                              new Long(textLocation)).iterator();
1119          while(annIter.hasNext()){
1120            Annotation ann = (Annotation)annIter.next();
1121            if(sHandler.getTypeHandler(ann.getType()).isSelected()){
1122              annotsAtPoint.add(new AnnotationHandler(sHandler.set, ann));
1123            }
1124          }
1125        }
1126        if(annotsAtPoint.size() > 0){
1127          if(annotsAtPoint.size() > 1){
1128            JPopupMenu popup = new JPopupMenu();
1129            Iterator annIter = annotsAtPoint.iterator();
1130            while(annIter.hasNext()){
1131              AnnotationHandler aHandler = (AnnotationHandler)annIter.next();
1132              popup.add(new HighlightMenuItem(
1133                      new EditAnnotationAction(aHandler),
1134                      aHandler.ann.getStartNode().getOffset().intValue(),
1135                      aHandler.ann.getEndNode().getOffset().intValue(),
1136                      popup));
1137            }
1138            try{
1139              Rectangle rect =  textPane.modelToView(textLocation);
1140              popup.show(textPane, rect.x + 10, rect.y);
1141            }catch(BadLocationException ble){
1142              throw new GateRuntimeException(ble);
1143            }
1144          }else{
1145            //only one annotation: start the editing directly
1146            new EditAnnotationAction((AnnotationHandler)annotsAtPoint.get(0)).
1147              actionPerformed(null);
1148          }
1149        }
1150      }
1151    }
1152    
1153    public void setTextLocation(int textLocation){
1154      this.textLocation = textLocation;
1155    }
1156    int textLocation;
1157  }//protected class SelectAnnotationAction extends AbstractAction{
1158  
1159  
1160  /**
1161   * The popup menu items used to select annotations
1162   * Apart from the normal {@link javax.swing.JMenuItem} behaviour, this menu
1163   * item also highlights the annotation which it would select if pressed.
1164   */
1165  protected class HighlightMenuItem extends JMenuItem {
1166    public HighlightMenuItem(Action action, int startOffset, int endOffset, 
1167            JPopupMenu popup) {
1168      super(action);
1169      this.start = startOffset;
1170      this.end = endOffset;
1171      this.addMouseListener(new MouseAdapter() {
1172        public void mouseEntered(MouseEvent e) {
1173          showHighlight();
1174        }
1175
1176        public void mouseExited(MouseEvent e) {
1177          removeHighlight();
1178        }
1179      });
1180      popup.addPopupMenuListener(new PopupMenuListener(){
1181        public void popupMenuWillBecomeVisible(PopupMenuEvent e){
1182          
1183        }
1184        public void popupMenuCanceled(PopupMenuEvent e){
1185          removeHighlight();
1186        }
1187        public void popupMenuWillBecomeInvisible(PopupMenuEvent e){
1188          removeHighlight();
1189        }
1190        
1191        
1192      });
1193    }
1194    
1195    protected void showHighlight(){
1196      try {
1197        highlight = textPane.getHighlighter().addHighlight(start, end,
1198                                        DefaultHighlighter.DefaultPainter);
1199      }catch(BadLocationException ble){
1200        throw new GateRuntimeException(ble.toString());
1201      }
1202
1203    }
1204    
1205    protected void removeHighlight(){
1206      if(highlight != null){
1207        textPane.getHighlighter().removeHighlight(highlight);
1208        highlight = null;
1209      }
1210      
1211    }
1212
1213    int start;
1214    int end;
1215    Action action;
1216    Object highlight;
1217  }
1218  
1219  
1220  
1221  protected class EditAnnotationAction extends AbstractAction{
1222    public EditAnnotationAction(AnnotationHandler aHandler){
1223      super(aHandler.ann.getType() + " [" + 
1224              (aHandler.set.getName() == null ? "  " : 
1225                aHandler.set.getName()) +
1226              "]");
1227      putValue(SHORT_DESCRIPTION, aHandler.ann.getFeatures().toString());
1228      this.aHandler = aHandler;
1229    }
1230    
1231    public void actionPerformed(ActionEvent evt){
1232      annotationEditor.setAnnotation(aHandler.ann, aHandler.set);
1233      annotationEditor.show(true);
1234    }
1235    
1236    AnnotationHandler aHandler;
1237  }
1238  
1239  protected class DeleteSelectedAnnotationGroupAction extends AbstractAction{
1240    public DeleteSelectedAnnotationGroupAction(){
1241    }
1242    public void actionPerformed(ActionEvent evt){
1243      int row = mainTable.getSelectedRow();
1244      if(row >= 0){
1245        Object handler = tableRows.get(row);
1246        if(handler instanceof TypeHandler){
1247          TypeHandler tHandler = (TypeHandler)handler;
1248          AnnotationSet set = tHandler.setHandler.set;
1249          AnnotationSet toDeleteAS = set.get(tHandler.name);
1250          if(toDeleteAS != null){
1251            List toDelete = new ArrayList(toDeleteAS);
1252            set.removeAll(toDelete);
1253          }
1254        }else if(handler instanceof SetHandler){
1255          SetHandler sHandler = (SetHandler)handler;
1256          if(sHandler.set == document.getAnnotations()){
1257            //the default annotation set - clear
1258            sHandler.set.clear();
1259          }else{
1260            document.removeAnnotationSet(sHandler.set.getName());
1261          }
1262        }
1263      }
1264    }
1265  }  
1266  
1267  List setHandlers;
1268  List tableRows; 
1269  XJTable mainTable;
1270  SetsTableModel tableModel;
1271  JScrollPane scroller;
1272  JPanel mainPanel;
1273  JTextField newSetNameTextField;
1274  
1275  TextualDocumentView textView;
1276  JEditorPane textPane;
1277  AnnotationEditor annotationEditor;
1278  NewAnnotationSetAction newSetAction;
1279  
1280  /**
1281   * The listener for mouse and mouse motion events in the text view.
1282   */
1283  protected TextMouseListener textMouseListener;
1284  
1285  protected javax.swing.Timer mouseMovementTimer;
1286  private static final int MOUSE_MOVEMENT_TIMER_DELAY = 500;
1287  protected AncestorListener textAncestorListener; 
1288  protected MouseStoppedMovingAction mouseStoppedMovingAction;
1289  
1290  protected String lastAnnotationType = "_New_";
1291  
1292  protected ColorGenerator colourGenerator;
1293  private static final int NAME_COL = 1;
1294  private static final int SELECTED_COL = 0;
1295  
1296}
1297