1   /*
2    *  Copyright (c) 1998-2005, The University of Sheffield.
3    *
4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
5    *  software, licenced under the GNU Library General Public License,
6    *  Version 2, June 1991 (in the distribution as file licence.html,
7    *  and also available at http://gate.ac.uk/gate/licence.html).
8    *
9    *  AnnotatioListView.java
10   *
11   *  Valentin Tablan, May 25, 2004
12   *
13   *  $Id: AnnotationListView.java,v 1.23 2005/11/09 15:24:01 nirajaswani Exp $
14   */
15  
16  package gate.gui.docview;
17  
18  import gate.*;
19  import gate.Annotation;
20  import gate.AnnotationSet;
21  import gate.creole.*;
22  import gate.creole.AnnotationVisualResource;
23  import gate.event.AnnotationEvent;
24  import gate.event.AnnotationListener;
25  import gate.gui.ResizableVisualResource;
26  import gate.swing.XJTable;
27  import gate.util.*;
28  import gate.util.Err;
29  import gate.util.GateRuntimeException;
30  import java.awt.*;
31  import java.awt.Component;
32  import java.awt.GridBagLayout;
33  import java.awt.event.*;
34  import java.util.*;
35  import java.util.List;
36  import java.util.Map;
37  import javax.swing.*;
38  import javax.swing.JScrollPane;
39  import javax.swing.SwingUtilities;
40  import javax.swing.event.*;
41  import javax.swing.event.TableModelEvent;
42  import javax.swing.event.TableModelListener;
43  import javax.swing.table.AbstractTableModel;
44  
45  /**
46   * A tabular view for a list of annotations.
47   * Used as part of the document viewer to display all the annotation currently
48   * highlighted.
49   */
50  public class AnnotationListView extends AbstractDocumentView
51      implements AnnotationListener{
52    public AnnotationListView(){
53      tagList = new ArrayList();
54      annotationHandlerByTag = new HashMap();
55    }
56  
57    /* (non-Javadoc)
58     * @see gate.gui.docview.AbstractDocumentView#initGUI()
59     */
60    protected void initGUI() {
61      editorsCache = new HashMap();
62      tableModel = new AnnotationTableModel();
63      table = new XJTable(tableModel);
64      table.setAutoResizeMode(XJTable.AUTO_RESIZE_OFF);
65      table.setSortable(true);
66      table.setSortedColumn(START_COL);
67      table.setIntercellSpacing(new Dimension(2, 0));
68      scroller = new JScrollPane(table);
69  
70      mainPanel = new JPanel();
71      mainPanel.setLayout(new GridBagLayout());
72      GridBagConstraints constraints = new GridBagConstraints();
73  
74      constraints.gridx = 0;
75      constraints.gridy = 0;
76      constraints.weightx = 1;
77      constraints.weighty = 1;
78      constraints.fill= GridBagConstraints.BOTH;
79      mainPanel.add(scroller, constraints);
80  
81      constraints.gridy = 1;
82      constraints.weightx = 0;
83      constraints.weighty = 0;
84      constraints.fill= GridBagConstraints.NONE;
85      constraints.anchor = GridBagConstraints.WEST;
86      statusLabel = new JLabel();
87      mainPanel.add(statusLabel, constraints);
88  
89      //get a pointer to the text view used to display
90      //the selected annotations
91      Iterator centralViewsIter = owner.getCentralViews().iterator();
92      while(textView == null && centralViewsIter.hasNext()){
93        DocumentView aView = (DocumentView)centralViewsIter.next();
94        if(aView instanceof TextualDocumentView)
95          textView = (TextualDocumentView)aView;
96      }
97  
98      initListeners();
99    }
100 
101   public Component getGUI(){
102     return mainPanel;
103   }
104   protected void initListeners(){
105 //    table.addComponentListener(new ComponentAdapter(){
106 //      public void componentShown(ComponentEvent e){
107 //        //trigger a resize for the columns
108 //        table.adjustSizes();
109 //      }
110 //    });
111 
112     tableModel.addTableModelListener(new TableModelListener(){
113       public void tableChanged(TableModelEvent e){
114         statusLabel.setText(
115                 Integer.toString(tableModel.getRowCount()) +
116                 " Annotations (" +
117                 Integer.toString(table.getSelectedRowCount()) +
118                 " selected)");
119       }
120     });
121 
122 
123     table.getSelectionModel().
124       addListSelectionListener(new ListSelectionListener(){
125         public void valueChanged(ListSelectionEvent e){
126           if(!isActive())return;
127           statusLabel.setText(
128                   Integer.toString(tableModel.getRowCount()) +
129                   " Annotations (" +
130                   Integer.toString(table.getSelectedRowCount()) +
131                   "selected)");
132           //blink the selected annotations
133           textView.removeAllBlinkingHighlights();
134           showHighlights();
135         }
136     });
137 
138     table.addMouseListener(new MouseListener() {
139       public void mouseClicked(final MouseEvent me) {
140         int viewRow = table.rowAtPoint(me.getPoint());
141         final int modelRow = viewRow == -1 ?
142                              viewRow : 
143                              table.rowViewToModel(viewRow);
144         
145         // right click
146         if(javax.swing.SwingUtilities.isRightMouseButton(me)) {
147           JPopupMenu popup = new JPopupMenu();
148           
149           Action deleteAction = new AbstractAction("Delete"){
150             public void actionPerformed(ActionEvent evt){
151               int[] rows = table.getSelectedRows();
152               if(rows == null || rows.length == 0){
153                 //no selection -> use row under cursor
154                 if(modelRow == -1) return;
155                 rows = new int[]{modelRow};
156               }
157 
158               ArrayList handlers = new ArrayList();
159 
160               for(int i = 0; i < rows.length; i++){
161                 Object tag = tagList.get(table.rowViewToModel(rows[i]));
162                 handlers.add(tag);
163               }
164 
165               for(int i=0;i<handlers.size();i++) {
166                 Object tag = handlers.get(i);
167                 AnnotationHandler aHandler = (AnnotationHandler)
168                                              annotationHandlerByTag.get(tag);
169                 aHandler.set.remove(aHandler.ann);
170                 removeAnnotation(tag);
171               }
172             }
173           };
174           popup.add(deleteAction);
175           
176           //add the custom edit actions
177           if(modelRow != -1){
178             AnnotationHandler aHandler = (AnnotationHandler)
179             annotationHandlerByTag.get(tagList.get(modelRow));
180             List editorClasses = Gate.getCreoleRegister().
181               getAnnotationVRs(aHandler.ann.getType());
182             if(editorClasses != null && editorClasses.size() > 0){
183               popup.addSeparator();
184               Iterator editorIter = editorClasses.iterator();
185               while(editorIter.hasNext()){
186                 String editorClass = (String) editorIter.next();
187                 AnnotationVisualResource editor = (AnnotationVisualResource)
188                   editorsCache.get(editorClass);
189                 if(editor == null){
190                   //create the new type of editor
191                   try{
192                     editor = (AnnotationVisualResource)
193                              Factory.createResource(editorClass);
194                     editorsCache.put(editorClass, editor);
195                   }catch(ResourceInstantiationException rie){
196                     rie.printStackTrace(Err.getPrintWriter());
197                   }
198                 }
199                 popup.add(new EditAnnotationAction(aHandler.set, 
200                         aHandler.ann, editor));
201               }
202             }
203           }
204           
205           
206           
207           popup.show(table, me.getX(), me.getY());
208         }
209       }
210       public void mouseReleased(MouseEvent me) { }
211       public void mouseEntered(MouseEvent me) { }
212       public void mouseExited(MouseEvent me) { }
213       public void mousePressed(MouseEvent me) { }
214     });
215     /* End */
216 
217   }
218   /* (non-Javadoc)
219    * @see gate.gui.docview.AbstractDocumentView#registerHooks()
220    */
221   protected void registerHooks() {
222     //this is called on activation
223     showHighlights();
224   }
225 
226   /* (non-Javadoc)
227    * @see gate.gui.docview.AbstractDocumentView#unregisterHooks()
228    */
229   protected void unregisterHooks() {
230     //this is called on de-activation
231     //remove highlights
232     textView.removeAllBlinkingHighlights();
233   }
234 
235   /* (non-Javadoc)
236    * @see gate.gui.docview.DocumentView#getType()
237    */
238   public int getType() {
239     return HORIZONTAL;
240   }
241   protected void guiShown(){
242     tableModel.fireTableDataChanged();
243   }
244 
245   protected void showHighlights(){
246     int[] rows = table.getSelectedRows();
247     for(int i = 0; i < rows.length; i++){
248       Object tag = tagList.get(table.rowViewToModel(rows[i]));
249       AnnotationHandler aHandler = (AnnotationHandler)
250         annotationHandlerByTag.get(tag);
251       textView.addBlinkingHighlight(aHandler.ann);
252       textView.scrollAnnotationToVisible(aHandler.ann);
253     }    
254   }
255 
256   public void addAnnotation(Object tag, Annotation ann, AnnotationSet set){
257     AnnotationHandler aHandler = new AnnotationHandler(set, ann);
258     Object oldValue = annotationHandlerByTag.put(tag, aHandler);
259     if(oldValue == null){
260       //new value
261       tagList.add(tag);
262       int row = tagList.size() -1;
263       if(tableModel != null) tableModel.fireTableRowsInserted(row, row);
264     }else{
265       //update old value
266       int row = tagList.indexOf(tag);
267       if(tableModel != null) tableModel.fireTableRowsUpdated(row,row);
268     }
269     //listen for the new annotation's events
270     ann.addAnnotationListener(this);
271   }
272 
273   public void removeAnnotation(Object tag){
274     int row = tagList.indexOf(tag);
275     if(row >= 0){
276       tagList.remove(row);
277       AnnotationHandler aHandler = (AnnotationHandler)
278           annotationHandlerByTag.remove(tag);
279       if(aHandler != null)aHandler.ann.removeAnnotationListener(this);
280       if(tableModel != null) tableModel.fireTableRowsDeleted(row, row);
281     }
282   }
283 
284   /**
285    * Adds a batch of annotations in one go. The tags and annotations collections
286    * are accessed through their iterators which are expected to return the
287    * corresponding tag for the right annotation.
288    * This method does not assume it was called from the UI Thread.
289    * @param tags a collection of tags
290    * @param annotations a collection of annotations
291    * @param set the annotation set to which all the annotations belong.
292    */
293   public void addAnnotations(Collection tags, Collection annotations,
294           AnnotationSet set){
295     if(tags.size() != annotations.size()) throw new GateRuntimeException(
296             "Invalid invocation - different numbers of annotations and tags!");
297     Iterator tagIter = tags.iterator();
298     Iterator annIter = annotations.iterator();
299     while(tagIter.hasNext()){
300       Object tag = tagIter.next();
301       Annotation ann = (Annotation)annIter.next();
302       AnnotationHandler aHandler = new AnnotationHandler(set, ann);
303       Object oldValue = annotationHandlerByTag.put(tag, aHandler);
304       if(oldValue == null){
305         //new value
306         tagList.add(tag);
307         int row = tagList.size() -1;
308       }else{
309         //update old value
310         int row = tagList.indexOf(tag);
311       }
312       //listen for the new annotation's events
313       ann.addAnnotationListener(this);
314     }
315     SwingUtilities.invokeLater(new Runnable(){
316       public void run(){
317         if(tableModel != null) tableModel.fireTableDataChanged();
318       }
319     });
320   }
321 
322   public void removeAnnotations(Collection tags){
323     Iterator tagIter = tags.iterator();
324     while(tagIter.hasNext()){
325       Object tag = tagIter.next();
326       int row = tagList.indexOf(tag);
327       if(row >= 0){
328         tagList.remove(row);
329         AnnotationHandler aHandler = (AnnotationHandler)
330             annotationHandlerByTag.remove(tag);
331         if(aHandler != null)aHandler.ann.removeAnnotationListener(this);
332       }
333     }
334     SwingUtilities.invokeLater(new Runnable(){
335       public void run(){
336         if(tableModel != null) tableModel.fireTableDataChanged();
337       }
338     });
339   }
340 
341   public void annotationUpdated(AnnotationEvent e){
342     //update all occurrences of this annotation
343     //save selection
344     
345   // if annotations tab has not been set to visible state
346   // table will be null.
347   if(table == null) {
348     return;
349   }
350   
351   int[] selection = table.getSelectedRows();
352     Annotation ann = (Annotation)e.getSource();
353     for(int i = 0; i < tagList.size(); i++){
354       Object tag = tagList.get(i);
355       if(((AnnotationHandler)annotationHandlerByTag.get(tag)).ann == ann){
356         if(tableModel != null)tableModel.fireTableRowsUpdated(i, i);
357       }
358     }
359     //restore selection
360     table.clearSelection();
361     if(selection != null){
362       for(int i = 0; i < selection.length; i++){
363         table.getSelectionModel().addSelectionInterval(selection[i], 
364                 selection[i]);
365       }
366     }
367   }
368 
369   /**
370    * Selects the annotation for the given tag.
371    * @param tag the tag of the annotation to be selected.
372    */
373   public void selectAnnotationForTag(Object tag){
374     int modelPosition = tagList.indexOf(tag);
375     if(modelPosition != -1){
376       int tablePosition = table.rowModelToView(modelPosition);
377       table.getSelectionModel().setSelectionInterval(tablePosition, 
378               tablePosition);
379       table.scrollRectToVisible(table.getCellRect(tablePosition, 0, false));
380     }
381   }
382   
383   class AnnotationTableModel extends AbstractTableModel{
384     public int getRowCount(){
385       return tagList.size();
386     }
387 
388     public int getColumnCount(){
389       return 5;
390     }
391 
392     public String getColumnName(int column){
393       switch(column){
394         case TYPE_COL: return "Type";
395         case SET_COL: return "Set";
396         case START_COL: return "Start";
397         case END_COL: return "End";
398         case FEATURES_COL: return "Features";
399         default: return "?";
400       }
401     }
402 
403     public Class getColumnClass(int column){
404       switch(column){
405         case TYPE_COL: return String.class;
406         case SET_COL: return String.class;
407         case START_COL: return Long.class;
408         case END_COL: return Long.class;
409         case FEATURES_COL: return String.class;
410         default: return String.class;
411       }
412     }
413 
414     public boolean isCellEditable(int rowIndex, int columnIndex){
415       return false;
416     }
417 
418     public Object getValueAt(int row, int column){
419       AnnotationHandler aHandler = (AnnotationHandler)annotationHandlerByTag.
420         get(tagList.get(row));
421       switch(column){
422         case TYPE_COL: return aHandler.ann.getType();
423         case SET_COL: return aHandler.set.getName();
424         case START_COL: return aHandler.ann.getStartNode().getOffset();
425         case END_COL: return aHandler.ann.getEndNode().getOffset();
426         case FEATURES_COL:
427           //sort the features by name
428           FeatureMap features = aHandler.ann.getFeatures();
429           List keyList = new ArrayList(features.keySet());
430           Collections.sort(keyList);
431           StringBuffer strBuf = new StringBuffer("{");
432           Iterator keyIter = keyList.iterator();
433           boolean first = true;
434           while(keyIter.hasNext()){
435             Object key = keyIter.next();
436             Object value = features.get(key);
437             if(first){
438               first = false;
439             }else{
440               strBuf.append(", ");
441             }
442             strBuf.append(key.toString());
443             strBuf.append("=");
444             strBuf.append(value == null ? "[null]" : value.toString());
445           }
446           strBuf.append("}");
447           return strBuf.toString();
448         default: return "?";
449       }
450     }
451 
452   }
453 
454   protected static class AnnotationHandler{
455     public AnnotationHandler(AnnotationSet set, Annotation ann){
456       this.ann = ann;
457       this.set = set;
458     }
459     Annotation ann;
460     AnnotationSet set;
461   }
462 
463   protected class EditAnnotationAction extends AbstractAction{
464     public EditAnnotationAction(AnnotationSet set, Annotation ann, 
465             AnnotationVisualResource editor){
466       this.set = set;
467       this.ann = ann;
468       this.editor = editor;
469       ResourceData rData =(ResourceData)Gate.getCreoleRegister().
470           get(editor.getClass().getName()); 
471       if(rData != null){
472         title = rData.getName();
473         putValue(NAME, "Edit with " + title);
474         putValue(SHORT_DESCRIPTION, rData.getComment());
475       }
476     }
477     
478     public void actionPerformed(ActionEvent evt){
479       JScrollPane scroller = new JScrollPane((Component)editor); 
480       editor.setTarget(set);
481       editor.setAnnotation(ann);
482       JOptionPane optionPane = new JOptionPane(scroller,
483               JOptionPane.QUESTION_MESSAGE, 
484               JOptionPane.OK_CANCEL_OPTION, 
485               null, new String[]{"OK", "Cancel"});
486       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
487       scroller.setMaximumSize(new Dimension((int)(screenSize.width * .75), 
488               (int)(screenSize.height * .75)));
489       JDialog dialog = optionPane.createDialog(AnnotationListView.this.getGUI(),
490               title);
491       dialog.setModal(true);
492       dialog.setResizable(true);
493       dialog.setVisible(true);
494       try{
495         if(optionPane.getValue().equals("OK")) editor.okAction();
496         else editor.cancelAction();
497       }catch(GateException ge){
498         throw new GateRuntimeException(ge);
499       }
500     }
501     
502     String title;
503     Annotation ann;
504     AnnotationSet set;
505     AnnotationVisualResource editor;
506   }
507   
508   protected XJTable table;
509   protected AnnotationTableModel tableModel;
510   protected JScrollPane scroller;
511   protected Map annotationHandlerByTag;
512   protected List tagList;
513   protected JPanel mainPanel;
514   protected JLabel statusLabel;
515   protected TextualDocumentView textView;
516   /**
517    * A map that stores instantiated annotations editors in order to avoid the 
518    * delay of building them at each request;
519    */
520   protected Map editorsCache;
521 
522   private static final int TYPE_COL = 0;
523   private static final int SET_COL = 1;
524   private static final int START_COL = 2;
525   private static final int END_COL = 3;
526   private static final int FEATURES_COL = 4;
527 
528 }
529