1   /*
2    *  Copyright (c) 1998-2004, 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.20 2004/10/06 16:23:55 valyt 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           statusLabel.setText(
127                   Integer.toString(tableModel.getRowCount()) +
128                   " Annotations (" +
129                   Integer.toString(table.getSelectedRowCount()) +
130                   "selected)");
131           //blink the selected annotations
132           textView.removeAllBlinkingHighlights();
133           int[] rows = table.getSelectedRows();
134           for(int i = 0; i < rows.length; i++){
135             Object tag = tagList.get(table.rowViewToModel(rows[i]));
136             AnnotationHandler aHandler = (AnnotationHandler)
137               annotationHandlerByTag.get(tag);
138             textView.addBlinkingHighlight(aHandler.ann);
139             textView.scrollAnnotationToVisible(aHandler.ann);
140           }
141         }
142     });
143 
144     table.addMouseListener(new MouseListener() {
145       public void mouseClicked(final MouseEvent me) {
146         int viewRow = table.rowAtPoint(me.getPoint());
147         final int modelRow = viewRow == -1 ?
148                              viewRow : 
149                              table.rowViewToModel(viewRow);
150         
151         // right click
152         if(javax.swing.SwingUtilities.isRightMouseButton(me)) {
153           JPopupMenu popup = new JPopupMenu();
154           
155           Action deleteAction = new AbstractAction("Delete"){
156             public void actionPerformed(ActionEvent evt){
157               int[] rows = table.getSelectedRows();
158               if(rows == null || rows.length == 0){
159                 //no selection -> use row under cursor
160                 if(modelRow == -1) return;
161                 rows = new int[]{modelRow};
162               }
163 
164               ArrayList handlers = new ArrayList();
165 
166               for(int i = 0; i < rows.length; i++){
167                 Object tag = tagList.get(table.rowViewToModel(rows[i]));
168                 handlers.add(tag);
169               }
170 
171               for(int i=0;i<handlers.size();i++) {
172                 Object tag = handlers.get(i);
173                 AnnotationHandler aHandler = (AnnotationHandler)
174                                              annotationHandlerByTag.get(tag);
175                 aHandler.set.remove(aHandler.ann);
176                 removeAnnotation(tag);
177               }
178             }
179           };
180           popup.add(deleteAction);
181           
182           //add the custom edit actions
183           if(modelRow != -1){
184             AnnotationHandler aHandler = (AnnotationHandler)
185             annotationHandlerByTag.get(tagList.get(modelRow));
186             List editorClasses = Gate.getCreoleRegister().
187               getAnnotationVRs(aHandler.ann.getType());
188             if(editorClasses != null && editorClasses.size() > 0){
189               popup.addSeparator();
190               Iterator editorIter = editorClasses.iterator();
191               while(editorIter.hasNext()){
192                 String editorClass = (String) editorIter.next();
193                 AnnotationVisualResource editor = (AnnotationVisualResource)
194                   editorsCache.get(editorClass);
195                 if(editor == null){
196                   //create the new type of editor
197                   try{
198                     editor = (AnnotationVisualResource)
199                              Factory.createResource(editorClass);
200                     editorsCache.put(editorClass, editor);
201                   }catch(ResourceInstantiationException rie){
202                     rie.printStackTrace(Err.getPrintWriter());
203                   }
204                 }
205                 popup.add(new EditAnnotationAction(aHandler.set, 
206                         aHandler.ann, editor));
207               }
208             }
209           }
210           
211           
212           
213           popup.show(table, me.getX(), me.getY());
214         }
215       }
216       public void mouseReleased(MouseEvent me) { }
217       public void mouseEntered(MouseEvent me) { }
218       public void mouseExited(MouseEvent me) { }
219       public void mousePressed(MouseEvent me) { }
220     });
221     /* End */
222 
223   }
224   /* (non-Javadoc)
225    * @see gate.gui.docview.AbstractDocumentView#registerHooks()
226    */
227   protected void registerHooks() {
228     //this view is a slave only view so it has no hooks to register
229   }
230 
231   /* (non-Javadoc)
232    * @see gate.gui.docview.AbstractDocumentView#unregisterHooks()
233    */
234   protected void unregisterHooks() {
235     //this view is a slave only view so it has no hooks to register
236   }
237 
238   /* (non-Javadoc)
239    * @see gate.gui.docview.DocumentView#getType()
240    */
241   public int getType() {
242     return HORIZONTAL;
243   }
244   protected void guiShown(){
245     tableModel.fireTableDataChanged();
246   }
247 
248 
249   public void addAnnotation(Object tag, Annotation ann, AnnotationSet set){
250     AnnotationHandler aHandler = new AnnotationHandler(set, ann);
251     Object oldValue = annotationHandlerByTag.put(tag, aHandler);
252     if(oldValue == null){
253       //new value
254       tagList.add(tag);
255       int row = tagList.size() -1;
256       if(tableModel != null) tableModel.fireTableRowsInserted(row, row);
257     }else{
258       //update old value
259       int row = tagList.indexOf(tag);
260       if(tableModel != null) tableModel.fireTableRowsUpdated(row,row);
261     }
262     //listen for the new annotation's events
263     ann.addAnnotationListener(this);
264   }
265 
266   public void removeAnnotation(Object tag){
267     int row = tagList.indexOf(tag);
268     if(row >= 0){
269       tagList.remove(row);
270       AnnotationHandler aHandler = (AnnotationHandler)
271           annotationHandlerByTag.remove(tag);
272       if(aHandler != null)aHandler.ann.removeAnnotationListener(this);
273       if(tableModel != null) tableModel.fireTableRowsDeleted(row, row);
274     }
275   }
276 
277   /**
278    * Adds a batch of annotations in one go. The tags and annotations collections
279    * are accessed through their iterators which are expected to return the
280    * corresponding tag for the right annotation.
281    * This method does not assume it was called from the UI Thread.
282    * @param tags a collection of tags
283    * @param annotations a collection of annotations
284    * @param set the annotation set to which all the annotations belong.
285    */
286   public void addAnnotations(Collection tags, Collection annotations,
287           AnnotationSet set){
288     if(tags.size() != annotations.size()) throw new GateRuntimeException(
289             "Invalid invocation - different numbers of annotations and tags!");
290     Iterator tagIter = tags.iterator();
291     Iterator annIter = annotations.iterator();
292     while(tagIter.hasNext()){
293       Object tag = tagIter.next();
294       Annotation ann = (Annotation)annIter.next();
295       AnnotationHandler aHandler = new AnnotationHandler(set, ann);
296       Object oldValue = annotationHandlerByTag.put(tag, aHandler);
297       if(oldValue == null){
298         //new value
299         tagList.add(tag);
300         int row = tagList.size() -1;
301       }else{
302         //update old value
303         int row = tagList.indexOf(tag);
304       }
305       //listen for the new annotation's events
306       ann.addAnnotationListener(this);
307     }
308     SwingUtilities.invokeLater(new Runnable(){
309       public void run(){
310         if(tableModel != null) tableModel.fireTableDataChanged();
311       }
312     });
313   }
314 
315   public void removeAnnotations(Collection tags){
316     Iterator tagIter = tags.iterator();
317     while(tagIter.hasNext()){
318       Object tag = tagIter.next();
319       int row = tagList.indexOf(tag);
320       if(row >= 0){
321         tagList.remove(row);
322         AnnotationHandler aHandler = (AnnotationHandler)
323             annotationHandlerByTag.remove(tag);
324         if(aHandler != null)aHandler.ann.removeAnnotationListener(this);
325       }
326     }
327     SwingUtilities.invokeLater(new Runnable(){
328       public void run(){
329         if(tableModel != null) tableModel.fireTableDataChanged();
330       }
331     });
332   }
333 
334   public void annotationUpdated(AnnotationEvent e){
335     //update all occurrences of this annotation
336     Annotation ann = (Annotation)e.getSource();
337     for(int i = 0; i < tagList.size(); i++){
338       Object tag = tagList.get(i);
339       if(((AnnotationHandler)annotationHandlerByTag.get(tag)).ann == ann){
340         if(tableModel != null)tableModel.fireTableRowsUpdated(i, i);
341       }
342     }
343   }
344 
345   class AnnotationTableModel extends AbstractTableModel{
346     public int getRowCount(){
347       return tagList.size();
348     }
349 
350     public int getColumnCount(){
351       return 5;
352     }
353 
354     public String getColumnName(int column){
355       switch(column){
356         case TYPE_COL: return "Type";
357         case SET_COL: return "Set";
358         case START_COL: return "Start";
359         case END_COL: return "End";
360         case FEATURES_COL: return "Features";
361         default: return "?";
362       }
363     }
364 
365     public Class getColumnClass(int column){
366       switch(column){
367         case TYPE_COL: return String.class;
368         case SET_COL: return String.class;
369         case START_COL: return Long.class;
370         case END_COL: return Long.class;
371         case FEATURES_COL: return String.class;
372         default: return String.class;
373       }
374     }
375 
376     public boolean isCellEditable(int rowIndex, int columnIndex){
377       return false;
378     }
379 
380     public Object getValueAt(int row, int column){
381       AnnotationHandler aHandler = (AnnotationHandler)annotationHandlerByTag.
382         get(tagList.get(row));
383       switch(column){
384         case TYPE_COL: return aHandler.ann.getType();
385         case SET_COL: return aHandler.set.getName();
386         case START_COL: return aHandler.ann.getStartNode().getOffset();
387         case END_COL: return aHandler.ann.getEndNode().getOffset();
388         case FEATURES_COL:
389           //sort the features by name
390           FeatureMap features = aHandler.ann.getFeatures();
391           List keyList = new ArrayList(features.keySet());
392           Collections.sort(keyList);
393           StringBuffer strBuf = new StringBuffer("{");
394           Iterator keyIter = keyList.iterator();
395           boolean first = true;
396           while(keyIter.hasNext()){
397             Object key = keyIter.next();
398             Object value = features.get(key);
399             if(first){
400               first = false;
401             }else{
402               strBuf.append(", ");
403             }
404             strBuf.append(key.toString());
405             strBuf.append("=");
406             strBuf.append(value == null ? "[null]" : value.toString());
407           }
408           strBuf.append("}");
409           return strBuf.toString();
410         default: return "?";
411       }
412     }
413 
414   }
415 
416   protected static class AnnotationHandler{
417     public AnnotationHandler(AnnotationSet set, Annotation ann){
418       this.ann = ann;
419       this.set = set;
420     }
421     Annotation ann;
422     AnnotationSet set;
423   }
424 
425   protected class EditAnnotationAction extends AbstractAction{
426     public EditAnnotationAction(AnnotationSet set, Annotation ann, 
427             AnnotationVisualResource editor){
428       this.set = set;
429       this.ann = ann;
430       this.editor = editor;
431       ResourceData rData =(ResourceData)Gate.getCreoleRegister().
432           get(editor.getClass().getName()); 
433       if(rData != null){
434         title = rData.getName();
435         putValue(NAME, "Edit with " + title);
436         putValue(SHORT_DESCRIPTION, rData.getComment());
437       }
438     }
439     
440     public void actionPerformed(ActionEvent evt){
441       JScrollPane scroller = new JScrollPane((Component)editor); 
442       editor.setTarget(set);
443       editor.setAnnotation(ann);
444       JOptionPane optionPane = new JOptionPane(scroller,
445               JOptionPane.QUESTION_MESSAGE, 
446               JOptionPane.OK_CANCEL_OPTION, 
447               null, new String[]{"OK", "Cancel"});
448       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
449       scroller.setMaximumSize(new Dimension((int)(screenSize.width * .75), 
450               (int)(screenSize.height * .75)));
451       JDialog dialog = optionPane.createDialog(AnnotationListView.this.getGUI(),
452               title);
453       dialog.setModal(true);
454       dialog.setResizable(true);
455       dialog.setVisible(true);
456       try{
457         if(optionPane.getValue().equals("OK")) editor.okAction();
458         else editor.cancelAction();
459       }catch(GateException ge){
460         throw new GateRuntimeException(ge);
461       }
462     }
463     
464     String title;
465     Annotation ann;
466     AnnotationSet set;
467     AnnotationVisualResource editor;
468   }
469   
470   protected XJTable table;
471   protected AnnotationTableModel tableModel;
472   protected JScrollPane scroller;
473   protected Map annotationHandlerByTag;
474   protected List tagList;
475   protected JPanel mainPanel;
476   protected JLabel statusLabel;
477   protected TextualDocumentView textView;
478   /**
479    * A map that stores instantiated annotations editors in order to avoid the 
480    * delay of building them at each request;
481    */
482   protected Map editorsCache;
483 
484   private static final int TYPE_COL = 0;
485   private static final int SET_COL = 1;
486   private static final int START_COL = 2;
487   private static final int END_COL = 3;
488   private static final int FEATURES_COL = 4;
489 
490 }
491