LogArea.java
001 /*
002  *  LogArea.java
003  *
004  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Cristian URSU, 26/03/2001
013  *
014  *  $Id: LogArea.java 12006 2009-12-01 17:24:28Z thomas_heitz $
015  *
016  */
017 
018 package gate.gui;
019 
020 import java.awt.Color;
021 import java.awt.Rectangle;
022 import java.awt.event.*;
023 import java.io.*;
024 
025 import javax.swing.*;
026 import javax.swing.text.*;
027 
028 import gate.swing.XJTextPane;
029 import gate.util.Err;
030 import gate.util.Out;
031 
032 /**
033   * This class is used to log all messages from GATE. When an object of this
034   * class is created, it redirects the output of {@link gate.util.Out} &
035   {@link gate.util.Err}.
036   * The output from Err is written with <font color="red">red</font> and the
037   * one from Out is written in <b>black</b>.
038   */
039 public class LogArea extends XJTextPane {
040 
041   /** Field needed in inner classes*/
042   protected LogArea thisLogArea = null;
043 
044   /** The popup menu with various actions*/
045   protected JPopupMenu popup = null;
046 
047   /** Start position from the document. */
048   protected Position startPos;
049   /** End position from the document. */
050   protected Position endPos;
051 
052   /** The original printstream on System.out */
053   protected PrintStream originalOut;
054 
055   /** The original printstream on System.err */
056   protected PrintStream originalErr;
057   /** This fields defines the Select all behaviour*/
058   protected SelectAllAction selectAllAction = null;
059 
060   /** This fields defines the copy  behaviour*/
061   protected CopyAction copyAction = null;
062 
063   /** This fields defines the clear all  behaviour*/
064   protected ClearAllAction clearAllAction = null;
065 
066   /** Constructs a LogArea object and captures the output from Err and Out. The
067     * output from System.out & System.err is not captured.
068     */
069   public LogArea(){
070     thisLogArea = this;
071     this.setEditable(false);
072 
073     LogAreaOutputStream err = new LogAreaOutputStream(true);
074     LogAreaOutputStream out = new LogAreaOutputStream(false);
075 
076     // Redirecting Err
077     try{
078       Err.setPrintWriter(new UTF8PrintWriter(err,true));
079     }catch(UnsupportedEncodingException uee){
080       uee.printStackTrace();
081     }
082     // Redirecting Out
083     try{
084       Out.setPrintWriter(new UTF8PrintWriter(out,true));
085     }catch(UnsupportedEncodingException uee){
086       uee.printStackTrace();
087     }
088 
089     // Redirecting System.out
090     originalOut = System.out;
091     try{
092       System.setOut(new UTF8PrintStream(out, true));
093     }catch(UnsupportedEncodingException uee){
094       uee.printStackTrace();
095     }
096 
097     // Redirecting System.err
098     originalErr = System.err;
099     try{
100       System.setErr(new UTF8PrintStream(err, true));
101     }catch(UnsupportedEncodingException uee){
102       uee.printStackTrace(originalErr);
103     }
104     popup = new JPopupMenu();
105     selectAllAction = new SelectAllAction();
106     copyAction = new CopyAction();
107     clearAllAction = new ClearAllAction();
108     startPos = getDocument().getStartPosition();
109     endPos = getDocument().getEndPosition();
110 
111     popup.add(selectAllAction);
112     popup.add(copyAction);
113     popup.addSeparator();
114     popup.add(clearAllAction);
115     initListeners();
116   }// LogArea
117 
118   /**
119    * Overriddent to fetch new start and end Positions when the document is
120    * changed.
121    */
122   public void setDocument(Document d) {
123     super.setDocument(d);
124     startPos = d.getStartPosition();
125     endPos = d.getEndPosition();
126   }
127 
128   public void setStyledDocument(StyledDocument d) {
129     this.setDocument(d);
130   }
131 
132   /** Init all listeners for this object*/
133   public void initListeners(){
134     super.initListeners();
135     this.addMouseListener(new MouseAdapter(){
136       public void mouseClicked(MouseEvent e){
137         if(SwingUtilities.isRightMouseButton(e)){
138           popup.show(thisLogArea, e.getPoint().x, e.getPoint().y);
139         }//End if
140       }// end mouseClicked()
141     });// End addMouseListener();
142   }
143 
144   /** Returns the original printstream on System.err */
145   public PrintStream getOriginalErr() {
146     return originalErr;
147   }
148 
149   /** Returns the original printstream on System.out */
150   public PrintStream getOriginalOut() {
151     return originalOut;
152   }// initListeners();
153 
154   /** Inner class that defines the behaviour of SelectAll action.*/
155   protected class SelectAllAction extends AbstractAction{
156     public SelectAllAction(){
157       super("Select all");
158     }// SelectAll
159     public void actionPerformed(ActionEvent e){
160       thisLogArea.selectAll();
161     }// actionPerformed();
162   }// End class SelectAllAction
163 
164   /** Inner class that defines the behaviour of copy action.*/
165   protected class CopyAction extends AbstractAction{
166     public CopyAction(){
167       super("Copy");
168     }// CopyAction
169     public void actionPerformed(ActionEvent e){
170       thisLogArea.copy();
171     }// actionPerformed();
172   }// End class CopyAction
173 
174   /**
175    * A runnable that adds a bit of text to the area; needed so we can write
176    * from the Swing thread.
177    */
178   protected class SwingWriter implements Runnable{
179     SwingWriter(String text, Style style){
180       this.text = text;
181       this.style = style;
182     }
183 
184     public void run(){
185       try{
186         if(endPos.getOffset() 1){
187           Rectangle place = modelToView(endPos.getOffset() 1);
188           if(place != nullscrollRectToVisible(place);
189         }
190       catch(BadLocationException e) {
191         // ignore a BLE at this point, just don't bother scrolling
192         originalErr.println("Exception encountered when trying to scroll to "
193             "end of messages pane: " + e);
194       }
195 
196       try {
197         // endPos is always one past the real end position because of the
198         // implicit newline character at the end of any Document
199         getDocument().insertString(endPos.getOffset() 1, text, style);
200       catch(BadLocationException e){
201         // a BLE here is a real problem
202         handleBadLocationException(e, text, style);
203       }// End try
204     }
205     String text;
206     Style style;
207   }
208 
209   /**
210    * Try and recover from a BadLocationException thrown when inserting a string
211    * into the log area.  This method must only be called on the AWT event
212    * handling thread.
213    */
214   private void handleBadLocationException(BadLocationException e,
215       String textToInsert, Style style) {
216     originalErr.println("BadLocationException encountered when writing to "
217         "the log area: " + e);
218     originalErr.println("trying to recover...");
219 
220     Document newDocument = new DefaultStyledDocument();
221     try {
222       StringBuilder sb = new StringBuilder();
223       sb.append("An error occurred when trying to write a message to the log area.  The log\n");
224       sb.append("has been cleared to try and recover from this problem.\n\n");
225       sb.append(textToInsert);
226 
227       newDocument.insertString(0, sb.toString(), style);
228     }
229     catch(BadLocationException e2) {
230       // oh dear, all bets are off now...
231       e2.printStackTrace(originalErr);
232       return;
233     }
234     // replace the log area's document with the new one
235     setDocument(newDocument);
236   }
237 
238   /**
239    * A print writer that uses UTF-8 to convert from char[] to byte[]
240    */
241   public static class UTF8PrintWriter extends PrintWriter{
242     public UTF8PrintWriter(OutputStream out)
243            throws UnsupportedEncodingException{
244       this(out, true);
245     }
246 
247     public UTF8PrintWriter(OutputStream out, boolean autoFlush)
248            throws UnsupportedEncodingException{
249       super(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")),
250             autoFlush);
251     }
252   }
253 
254   /**
255    * A print writer that uses UTF-8 to convert from char[] to byte[]
256    */
257   public static class UTF8PrintStream extends PrintStream{
258     public UTF8PrintStream(OutputStream out)
259            throws UnsupportedEncodingException{
260       this(out, true);
261     }
262 
263     public UTF8PrintStream(OutputStream out, boolean autoFlush)
264            throws UnsupportedEncodingException{
265       super(out, autoFlush);
266     }
267 
268     /**
269      * Overriden so it uses UTF-8 when converting a string to byte[]
270      @param s the string to be printed
271      */
272     public void print(String s) {
273       try{
274         write(s.getBytes("UTF-8"));
275       }catch(UnsupportedEncodingException uee){
276         //support for UTF-8 is guaranteed by the JVM specification
277       }catch(IOException ioe){
278         //print streams don't throw exceptions
279         setError();
280       }
281     }
282 
283     /**
284      * Overriden so it uses UTF-8 when converting a char[] to byte[]
285      @param s the string to be printed
286      */
287     public void print(char s[]) {
288       print(String.valueOf(s));
289     }
290   }
291 
292   /** Inner class that defines the behaviour of clear all action.*/
293   protected class ClearAllAction extends AbstractAction{
294     public ClearAllAction(){
295       super("Clear all");
296     }// ClearAllAction
297     public void actionPerformed(ActionEvent e){
298       try{
299         thisLogArea.getDocument().remove(startPos.getOffset(),endPos.getOffset() - startPos.getOffset() 1);
300       catch (BadLocationException e1){
301         // it's OK to print this exception to the current log area
302         e1.printStackTrace(Err.getPrintWriter());
303       }// End try
304     }// actionPerformed();
305   }// End class ClearAllAction
306 
307   /** Inner class that defines the behaviour of an OutputStream that writes to
308    *  the LogArea.
309    */
310   class LogAreaOutputStream extends OutputStream{
311     /** This field dictates the style on how to write */
312     private boolean isErr = false;
313     /** Char style*/
314     private Style style = null;
315 
316     /** Constructs an Out or Err LogAreaOutputStream*/
317     public LogAreaOutputStream(boolean anIsErr){
318       isErr = anIsErr;
319       if (isErr){
320         style = addStyle("error", getStyle("default"));
321         StyleConstants.setForeground(style, Color.red);
322       }else {
323         style = addStyle("out",getStyle("default"));
324         StyleConstants.setForeground(style, Color.black);
325       }// End if
326     }// LogAreaOutputStream
327 
328     /** Writes an int which must be a the code of a char, into the LogArea,
329      *  using the style specified in constructor. The int is downcast to a byte.
330      */
331     public void write(int charCode){
332       // charCode int must be a char. Let us be sure of that
333       charCode &= 0x000000FF;
334       // Convert the byte to a char before put it into the log area
335       char c = (char)charCode;
336       // Insert it in the log Area
337       SwingUtilities.invokeLater(new SwingWriter(String.valueOf(c), style));
338     }// write(int charCode)
339 
340     /** Writes an array of bytes into the LogArea,
341      *  using the style specified in constructor.
342      */
343     public void write(byte[] data, int offset, int length){
344       // Insert the string to the log area
345       try{
346         SwingUtilities.invokeLater(new SwingWriter(new String(data,offset,
347                                                               length, "UTF-8"),
348                                                    style));
349       }catch(UnsupportedEncodingException uee){
350         // should never happen - all JREs are required to support UTF-8
351         uee.printStackTrace(originalErr);
352       }
353     }// write(byte[] data, int offset, int length)
354   }////End class LogAreaOutputStream
355 }//End class LogArea