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