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    *  Valentin Tablan, 22 March 2004
10   *
11   *  $Id: TextualDocumentView.java,v 1.21 2004/10/14 09:49:59 valyt Exp $
12   */
13  package gate.gui.docview;
14  
15  import java.awt.*;
16  import java.awt.Component;
17  import java.awt.Point;
18  import java.awt.event.*;
19  import java.awt.event.ComponentAdapter;
20  import java.awt.event.ComponentEvent;
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.util.*;
24  import java.util.List;
25  
26  import javax.swing.*;
27  import javax.swing.Timer;
28  import javax.swing.text.*;
29  import javax.swing.text.Highlighter;
30  
31  
32  import gate.Annotation;
33  import gate.AnnotationSet;
34  import gate.corpora.DocumentContentImpl;
35  import gate.util.*;
36  import gate.util.GateRuntimeException;
37  import gate.util.RawEditorKit;
38  
39  
40  /**
41   * This class provides a central view for a textual document.
42   */
43  
44  public class TextualDocumentView extends AbstractDocumentView {
45  
46    public TextualDocumentView(){
47      blinkingTagsForAnnotations = new HashMap();
48    }
49    
50    public Object addHighlight(Annotation ann, AnnotationSet set, Color colour){
51      Highlighter highlighter = textView.getHighlighter();
52      try{
53        Object tag = highlighter.addHighlight(
54                ann.getStartNode().getOffset().intValue(),
55                ann.getEndNode().getOffset().intValue(),
56                new DefaultHighlighter.DefaultHighlightPainter(colour));
57        annotationListView.addAnnotation(tag, ann, set);
58        return tag;
59      }catch(BadLocationException ble){
60        //the offsets should always be OK as they come from an annotation
61        throw new GateRuntimeException(ble.toString());
62      }
63    }
64  
65    
66    public void scrollAnnotationToVisible(Annotation ann){
67      //if at least part of the blinking section is visible then we
68      //need to do no scrolling
69      //this is required for long annotations that span more than a 
70      //screen
71      Rectangle visibleView = scroller.getViewport().getViewRect();
72      int viewStart = textView.viewToModel(visibleView.getLocation());
73      Point endPoint = new Point(visibleView.getLocation());
74      endPoint.translate(visibleView.width, visibleView.height);
75      int viewEnd = textView.viewToModel(endPoint);
76      int annStart = ann.getStartNode().getOffset().intValue();
77      int annEnd = ann.getEndNode().getOffset().intValue();
78      if(annEnd < viewStart || viewEnd < annStart){
79        try{
80          textView.scrollRectToVisible(textView.modelToView(annStart));
81        }catch(BadLocationException ble){
82          //this should never happen
83          throw new GateRuntimeException(ble);
84        }
85      }
86    }
87    
88    public void removeHighlight(Object tag){
89      Highlighter highlighter = textView.getHighlighter();
90      highlighter.removeHighlight(tag);
91      annotationListView.removeAnnotation(tag);
92    }
93  
94    /**
95     * Gives access to the highliter's change highlight operation. Can be used to 
96     * change the offset of an existing highlight.
97     * @param tag the tag for the highlight
98     * @param newStart new start offset.
99     * @param newEnd new end offset.
100    * @throws BadLocationException
101    */
102   public void moveHighlight(Object tag, int newStart, int newEnd) 
103     throws BadLocationException{
104     textView.getHighlighter().changeHighlight(tag, newStart, newEnd);
105   }
106   
107   /**
108    * Ads several highlights in one go. 
109    * This method should <b>not</b> be called from within the UI thread.
110    * @param annotations the collection of annotations for which highlights 
111    * are to be added.
112    * @param set the annotation set all the annotations belong to.
113    * @param colour the colour for the highlights.
114    * @return the list of tags for the added highlights. The order of the 
115    * elements corresponds to the order defined by the iterator of the 
116    * collection of annotations provided. 
117    */
118   public List addHighlights(Collection annotations, 
119           AnnotationSet set, Color colour){
120     //hide the text pane to speed up rendering.
121     SwingUtilities.invokeLater(new Runnable(){
122       public void run(){
123         textView.setVisible(false);
124         scroller.getViewport().setView(new JLabel("Updating"));
125       }
126     });
127     //wait for the textual view to be hidden
128     while(textView.isVisible()) 
129       try{
130         Thread.sleep(30);
131       }catch(InterruptedException ie){
132         //ignore
133       }
134     
135     Highlighter highlighter = textView.getHighlighter();
136     
137     Iterator annIter = annotations.iterator();
138     List tagsList = new ArrayList(annotations.size());
139     while(annIter.hasNext()){
140       Annotation ann = (Annotation)annIter.next();
141       try{
142         Object tag = highlighter.addHighlight(
143                 ann.getStartNode().getOffset().intValue(),
144                 ann.getEndNode().getOffset().intValue(),
145                 new DefaultHighlighter.DefaultHighlightPainter(colour));
146         tagsList.add(tag);
147       }catch(BadLocationException ble){
148         //the offsets should always be OK as they come from an annotation
149         throw new GateRuntimeException(ble.toString());
150       }
151     }
152     annotationListView.addAnnotations(tagsList, annotations, set);
153     SwingUtilities.invokeLater(new Runnable(){
154       public void run(){
155         scroller.getViewport().setView(textView);
156         textView.setVisible(true);
157       }
158     });
159     return tagsList;
160   }
161   
162   /**
163    * Removes several highlights in one go. 
164    * This method should <b>not</b> be called from within the UI thread.
165    * @param tags the tags for the highlights to be removed
166    */
167   public void removeHighlights(Collection tags){
168     //hide the text pane to speed up rendering.
169     SwingUtilities.invokeLater(new Runnable(){
170       public void run(){
171         textView.setVisible(false);
172         scroller.getViewport().setView(new JLabel("Updating"));
173       }
174     });
175     //wait for the textual view to be hidden.
176     while(textView.isVisible()) 
177       try{
178         Thread.sleep(30);
179       }catch(InterruptedException ie){
180         //ignore
181       }
182     
183     Highlighter highlighter = textView.getHighlighter();
184     
185     Iterator tagIter = tags.iterator();
186     while(tagIter.hasNext()){
187       highlighter.removeHighlight(tagIter.next());
188     }
189     annotationListView.removeAnnotations(tags);
190     SwingUtilities.invokeLater(new Runnable(){
191       public void run(){
192         scroller.getViewport().setView(textView);
193         textView.setVisible(true);
194       }
195     });
196   }
197 
198   
199   public void addBlinkingHighlight(Annotation ann){
200     synchronized(blinkingTagsForAnnotations){
201       blinkingTagsForAnnotations.put(ann.getId(), new AnnotationTag(ann, null));
202     }
203   }
204   
205   public void removeBlinkingHighlight(Annotation ann){
206     synchronized(blinkingTagsForAnnotations){
207       AnnotationTag annTag = (AnnotationTag)blinkingTagsForAnnotations.
208           remove(ann.getId()); 
209       if(annTag != null && annTag.getTag() != null)
210           textView.getHighlighter().removeHighlight(annTag.getTag());
211     }
212   }
213   
214   
215   public void removeAllBlinkingHighlights(){
216     synchronized(blinkingTagsForAnnotations){
217       Iterator annIdIter = new ArrayList(blinkingTagsForAnnotations.keySet()).
218         iterator();
219       while(annIdIter.hasNext()){
220         AnnotationTag annTag = (AnnotationTag)
221             blinkingTagsForAnnotations.remove(annIdIter.next());
222         Annotation ann = annTag.getAnnotation();
223         Object tag = annTag.getTag();
224         if(tag != null){
225           Highlighter highlighter = textView.getHighlighter();
226           highlighter.removeHighlight(tag);
227         }
228       }
229     }
230   }
231   
232   
233   public int getType() {
234     return CENTRAL;
235   }
236 
237   
238   
239   /* (non-Javadoc)
240    * @see gate.gui.docview.AbstractDocumentView#initGUI()
241    */
242   protected void initGUI() {
243     textView = new JEditorPane();
244     textView.setContentType("text/plain");
245     textView.setEditorKit(new RawEditorKit());
246     textView.setAutoscrolls(false);
247     scroller = new JScrollPane(textView);
248 
249     textView.setText(document.getContent().toString());
250     textView.getDocument().addDocumentListener(new SwingDocumentListener());
251     scroller.getViewport().setViewPosition(new Point(0, 0));
252     
253     //get a pointer to the annotation list view used to display
254     //the highlighted annotations 
255     Iterator horizViewsIter = owner.getHorizontalViews().iterator();
256     while(annotationListView == null && horizViewsIter.hasNext()){
257       DocumentView aView = (DocumentView)horizViewsIter.next();
258       if(aView instanceof AnnotationListView) 
259         annotationListView = (AnnotationListView)aView;
260     }
261     blinker = new Timer(BLINK_DELAY, new BlinkAction());
262     blinker.setRepeats(true);
263     blinker.start();
264     initListeners();
265   }
266   
267   public Component getGUI(){
268     return scroller;
269   }
270   
271   protected void initListeners(){
272     textView.addComponentListener(new ComponentAdapter(){
273       public void componentResized(ComponentEvent e){
274         try{
275           scroller.getViewport().setViewPosition(
276                   textView.modelToView(0).getLocation());
277           scroller.paintImmediately(textView.getBounds());
278         }catch(BadLocationException ble){
279           //ignore
280         }
281       }
282     });
283   }
284   protected void unregisterHooks(){}
285   protected void registerHooks(){}
286   
287   
288   /**
289    * Blinks the blinking highlights if any.
290    */
291   protected class BlinkAction extends AbstractAction{
292     public void actionPerformed(ActionEvent evt){
293       //this needs to either add or remove the highlights
294       synchronized(blinkingTagsForAnnotations){
295         //get out as quickly as possible if nothing to do
296         if(blinkingTagsForAnnotations.isEmpty()) return;
297         Iterator annIdIter = new ArrayList(blinkingTagsForAnnotations.keySet()).
298           iterator();
299         Highlighter highlighter = textView.getHighlighter();
300         if(highlightsShown){
301           //hide current highlights
302           while(annIdIter.hasNext()){
303             AnnotationTag annTag = (AnnotationTag)
304               blinkingTagsForAnnotations.get(annIdIter.next());
305             Annotation ann = annTag.getAnnotation();
306             Object tag = annTag.getTag();
307             if(tag != null) highlighter.removeHighlight(tag);
308             annTag.setTag(null);
309           }
310           highlightsShown = false;
311         }else{
312           //show highlights
313           while(annIdIter.hasNext()){
314             AnnotationTag annTag = (AnnotationTag)
315               blinkingTagsForAnnotations.get(annIdIter.next());
316             Annotation ann = annTag.getAnnotation();
317             try{
318               Object tag = highlighter.addHighlight(
319                       ann.getStartNode().getOffset().intValue(),
320                       ann.getEndNode().getOffset().intValue(),
321                       new DefaultHighlighter.DefaultHighlightPainter(
322                               textView.getSelectionColor()));
323               annTag.setTag(tag);
324 //              scrollAnnotationToVisible(ann);
325             }catch(BadLocationException ble){
326               //this should never happen
327               throw new GateRuntimeException(ble);
328             }
329           }
330           highlightsShown = true;
331         }
332       }
333     }
334     protected boolean highlightsShown = false;
335   }
336     
337   class SwingDocumentListener implements javax.swing.event.DocumentListener{
338     public void insertUpdate(final javax.swing.event.DocumentEvent e) {
339       //propagate the edit to the document
340       try{
341         document.edit(new Long(e.getOffset()), new Long(e.getOffset()),
342                       new DocumentContentImpl(
343                         e.getDocument().getText(e.getOffset(), e.getLength())));
344       }catch(BadLocationException ble){
345         ble.printStackTrace(Err.getPrintWriter());
346       }catch(InvalidOffsetException ioe){
347         ioe.printStackTrace(Err.getPrintWriter());
348       }
349       //update the offsets in the list
350       Component listView = annotationListView.getGUI();
351       if(listView != null) listView.repaint();
352     }
353 
354     public void removeUpdate(final javax.swing.event.DocumentEvent e) {
355       //propagate the edit to the document
356       try{
357         document.edit(new Long(e.getOffset()),
358                       new Long(e.getOffset() + e.getLength()),
359                       new DocumentContentImpl(""));
360       }catch(InvalidOffsetException ioe){
361         ioe.printStackTrace(Err.getPrintWriter());
362       }
363       //update the offsets in the list
364       Component listView = annotationListView.getGUI();
365       if(listView != null) listView.repaint();
366     }
367 
368     public void changedUpdate(javax.swing.event.DocumentEvent e) {
369       //some attributes changed: we don't care about that
370     }
371   }//class SwingDocumentListener implements javax.swing.event.DocumentListener
372 
373   /**
374    * A structure that holds an annotation and its tag.
375    */
376   class AnnotationTag{
377     
378     /**
379      * @param tag The tag to set.
380      */
381     public void setTag(Object tag){
382       this.tag = tag;
383     }
384     public AnnotationTag(Annotation annotation, Object tag){
385       this.annotation = annotation;
386       this.tag = tag;
387     }
388     
389     /**
390      * @return Returns the annotation.
391      */
392     public Annotation getAnnotation(){
393       return annotation;
394     }
395     /**
396      * @return Returns the tag.
397      */
398     public Object getTag(){
399       return tag;
400     }
401     Annotation annotation;
402     Object tag;
403   }
404   
405   protected JScrollPane scroller;
406   protected AnnotationListView annotationListView;
407 
408   /**
409    * The annotations used for blinking highlights and their tags. A map from 
410    * {@link Annotation} ID to tag(i.e. {@link Object}).
411    */
412   protected Map blinkingTagsForAnnotations;
413   
414   protected Timer blinker;
415   
416   protected JEditorPane textView;
417   
418   /**
419    * The delay used by the blinker.
420    */
421   protected final static int BLINK_DELAY = 400;
422 }
423