JAL-1632 removed ‘tree’ option and changed dialog layout
[jalview.git] / src / jalview / gui / Console.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.util.MessageManager;
24
25 import java.awt.BorderLayout;
26 import java.awt.Dimension;
27 import java.awt.GraphicsEnvironment;
28 import java.awt.Rectangle;
29 import java.awt.Toolkit;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.awt.event.WindowAdapter;
33 import java.awt.event.WindowEvent;
34 import java.awt.event.WindowListener;
35 import java.io.IOException;
36 import java.io.PipedInputStream;
37 import java.io.PipedOutputStream;
38 import java.io.PrintStream;
39
40 import javax.swing.JButton;
41 import javax.swing.JFrame;
42 import javax.swing.JScrollPane;
43 import javax.swing.JTextArea;
44
45 import org.apache.log4j.SimpleLayout;
46
47 /**
48  * Simple Jalview Java Console. Version 1 - allows viewing of console output
49  * after desktop is created. Acquired with thanks from RJHM's site
50  * http://www.comweb.nl/java/Console/Console.html A simple Java Console for your
51  * application (Swing version) Requires Java 1.1.5 or higher Disclaimer the use
52  * of this source is at your own risk. Permision to use and distribute into your
53  * own applications RJHM van den Bergh , rvdb@comweb.nl
54  */
55
56 public class Console extends WindowAdapter implements WindowListener,
57         ActionListener, Runnable
58 {
59   private JFrame frame;
60
61   private JTextArea textArea;
62
63   /*
64    * unused - tally and limit for lines in console window int lines = 0;
65    * 
66    * int lim = 1000;
67    */
68   int byteslim = 102400, bytescut = 76800; // 100k and 75k cut point.
69
70   private Thread reader, reader2, textAppender;
71
72   private boolean quit;
73
74   private final PrintStream stdout = System.out, stderr = System.err;
75
76   private PipedInputStream pin = new PipedInputStream();
77
78   private PipedInputStream pin2 = new PipedInputStream();
79
80   private StringBuffer displayPipe = new StringBuffer();
81
82   Thread errorThrower; // just for testing (Throws an Exception at this Console
83
84   // are we attached to some parent Desktop
85   Desktop parent = null;
86
87   public Console()
88   {
89     // create all components and add them
90     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
91     frame = initFrame("Java Console", screenSize.width / 2,
92             screenSize.height / 2, -1, -1);
93     initConsole(true);
94   }
95
96   private void initConsole(boolean visible)
97   {
98     initConsole(visible, true);
99   }
100
101   /**
102    * 
103    * @param visible
104    *          - open the window
105    * @param redirect
106    *          - redirect std*
107    */
108   private void initConsole(boolean visible, boolean redirect)
109   {
110     // CutAndPasteTransfer cpt = new CutAndPasteTransfer();
111     // textArea = cpt.getTextArea();
112     textArea = new JTextArea();
113     textArea.setEditable(false);
114     JButton button = new JButton(MessageManager.getString("action.clear"));
115
116     // frame = cpt;
117     frame.getContentPane().setLayout(new BorderLayout());
118     frame.getContentPane().add(new JScrollPane(textArea),
119             BorderLayout.CENTER);
120     frame.getContentPane().add(button, BorderLayout.SOUTH);
121     frame.setVisible(visible);
122     updateConsole = visible;
123     frame.addWindowListener(this);
124     button.addActionListener(this);
125     if (redirect)
126     {
127       redirectStreams();
128     }
129     else
130     {
131       unredirectStreams();
132     }
133     quit = false; // signals the Threads that they should exit
134
135     // Starting two seperate threads to read from the PipedInputStreams
136     //
137     reader = new Thread(this);
138     reader.setDaemon(true);
139     reader.start();
140     //
141     reader2 = new Thread(this);
142     reader2.setDaemon(true);
143     reader2.start();
144     // and a thread to append text to the textarea
145     textAppender = new Thread(this);
146     textAppender.setDaemon(true);
147     textAppender.start();
148   }
149
150   PipedOutputStream pout = null, perr = null;
151
152   public void redirectStreams()
153   {
154     if (pout == null)
155     {
156       try
157       {
158         pout = new PipedOutputStream(this.pin);
159         System.setOut(new PrintStream(pout, true));
160       } catch (java.io.IOException io)
161       {
162         textArea.append("Couldn't redirect STDOUT to this console\n"
163                 + io.getMessage());
164         io.printStackTrace(stderr);
165       } catch (SecurityException se)
166       {
167         textArea.append("Couldn't redirect STDOUT to this console\n"
168                 + se.getMessage());
169         se.printStackTrace(stderr);
170       }
171
172       try
173       {
174         perr = new PipedOutputStream(this.pin2);
175         System.setErr(new PrintStream(perr, true));
176       } catch (java.io.IOException io)
177       {
178         textArea.append("Couldn't redirect STDERR to this console\n"
179                 + io.getMessage());
180         io.printStackTrace(stderr);
181       } catch (SecurityException se)
182       {
183         textArea.append("Couldn't redirect STDERR to this console\n"
184                 + se.getMessage());
185         se.printStackTrace(stderr);
186       }
187     }
188   }
189
190   public void unredirectStreams()
191   {
192     if (pout != null)
193     {
194       try
195       {
196         System.setOut(stdout);
197         pout.flush();
198         pout.close();
199         pin = new PipedInputStream();
200         pout = null;
201       } catch (java.io.IOException io)
202       {
203         textArea.append("Couldn't unredirect STDOUT to this console\n"
204                 + io.getMessage());
205         io.printStackTrace(stderr);
206       } catch (SecurityException se)
207       {
208         textArea.append("Couldn't unredirect STDOUT to this console\n"
209                 + se.getMessage());
210         se.printStackTrace(stderr);
211       }
212
213       try
214       {
215         System.setErr(stderr);
216         perr.flush();
217         perr.close();
218         pin2 = new PipedInputStream();
219         perr = null;
220       } catch (java.io.IOException io)
221       {
222         textArea.append("Couldn't unredirect STDERR to this console\n"
223                 + io.getMessage());
224         io.printStackTrace(stderr);
225       } catch (SecurityException se)
226       {
227         textArea.append("Couldn't unredirect STDERR to this console\n"
228                 + se.getMessage());
229         se.printStackTrace(stderr);
230       }
231     }
232   }
233
234   public void test()
235   {
236     // testing part
237     // you may omit this part for your application
238     //
239
240     System.out.println("Hello World 2");
241     System.out.println("All fonts available to Graphic2D:\n");
242     GraphicsEnvironment ge = GraphicsEnvironment
243             .getLocalGraphicsEnvironment();
244     String[] fontNames = ge.getAvailableFontFamilyNames();
245     for (int n = 0; n < fontNames.length; n++)
246       System.out.println(fontNames[n]);
247     // Testing part: simple an error thrown anywhere in this JVM will be printed
248     // on the Console
249     // We do it with a seperate Thread becasue we don't wan't to break a Thread
250     // used by the Console.
251     System.out.println("\nLets throw an error on this console");
252     errorThrower = new Thread(this);
253     errorThrower.setDaemon(true);
254     errorThrower.start();
255   }
256
257   private JFrame initFrame(String string, int i, int j, int x, int y)
258   {
259     JFrame frame = new JFrame(string);
260     frame.setName(string);
261     if (x == -1)
262       x = (int) (i / 2);
263     if (y == -1)
264       y = (int) (j / 2);
265     frame.setBounds(x, y, i, j);
266     return frame;
267   }
268
269   /**
270    * attach a console to the desktop - the desktop will open it if requested.
271    * 
272    * @param desktop
273    */
274   public Console(Desktop desktop)
275   {
276     this(desktop, true);
277   }
278
279   /**
280    * attach a console to the desktop - the desktop will open it if requested.
281    * 
282    * @param desktop
283    * @param showjconsole
284    *          - if true, then redirect stdout immediately
285    */
286   public Console(Desktop desktop, boolean showjconsole)
287   {
288     parent = desktop;
289     // window name - get x,y,width, height possibly scaled
290     Rectangle bounds = desktop.getLastKnownDimensions("JAVA_CONSOLE_");
291     if (bounds == null)
292     {
293       frame = initFrame("Jalview Java Console", desktop.getWidth() / 2,
294               desktop.getHeight() / 4, desktop.getX(), desktop.getY());
295     }
296     else
297     {
298       frame = initFrame("Jalview Java Console", bounds.width,
299               bounds.height, bounds.x, bounds.y);
300     }
301     // desktop.add(frame);
302     initConsole(false);
303     JalviewAppender jappender = new JalviewAppender();
304     jappender.setLayout(new SimpleLayout());
305     JalviewAppender.setTextArea(textArea);
306     org.apache.log4j.Logger.getRootLogger().addAppender(jappender);
307   }
308
309   public synchronized void stopConsole()
310   {
311     quit = true;
312     this.notifyAll();
313     /*
314      * reader.notify(); reader2.notify(); if (errorThrower!=null)
315      * errorThrower.notify(); // stop all threads if (textAppender!=null)
316      * textAppender.notify();
317      */
318     if (pout != null)
319     {
320       try
321       {
322         reader.join(10);
323         pin.close();
324       } catch (Exception e)
325       {
326       }
327       try
328       {
329         reader2.join(10);
330         pin2.close();
331       } catch (Exception e)
332       {
333       }
334       try
335       {
336         textAppender.join(10);
337       } catch (Exception e)
338       {
339       }
340     }
341     if (!frame.isVisible())
342     {
343       frame.dispose();
344     }
345     // System.exit(0);
346   }
347
348   public synchronized void windowClosed(WindowEvent evt)
349   {
350     frame.setVisible(false);
351     closeConsoleGui();
352   }
353
354   private void closeConsoleGui()
355   {
356     updateConsole = false;
357     if (parent == null)
358     {
359
360       stopConsole();
361     }
362     else
363     {
364       parent.showConsole(false);
365     }
366   }
367
368   public synchronized void windowClosing(WindowEvent evt)
369   {
370     frame.setVisible(false); // default behaviour of JFrame
371     closeConsoleGui();
372
373     // frame.dispose();
374   }
375
376   public synchronized void actionPerformed(ActionEvent evt)
377   {
378     trimBuffer(true);
379     // textArea.setText("");
380   }
381
382   public synchronized void run()
383   {
384     try
385     {
386       while (Thread.currentThread() == reader)
387       {
388         if (pin == null || pin.available() == 0)
389         {
390           try
391           {
392             this.wait(100);
393             if (pin.available() == 0)
394             {
395               trimBuffer(false);
396             }
397           } catch (InterruptedException ie)
398           {
399           }
400         }
401
402         while (pin.available() != 0)
403         {
404           String input = this.readLine(pin);
405           stdout.print(input);
406           long time = System.nanoTime();
407           appendToTextArea(input);
408           // stderr.println("Time taken to stdout append:\t"
409           // + (System.nanoTime() - time) + " ns");
410           // lines++;
411         }
412         if (quit)
413           return;
414       }
415
416       while (Thread.currentThread() == reader2)
417       {
418         if (pin2.available() == 0)
419         {
420           try
421           {
422             this.wait(100);
423             if (pin2.available() == 0)
424             {
425               trimBuffer(false);
426             }
427           } catch (InterruptedException ie)
428           {
429           }
430         }
431         while (pin2.available() != 0)
432         {
433           String input = this.readLine(pin2);
434           stderr.print(input);
435           long time = System.nanoTime();
436           appendToTextArea(input);
437           // stderr.println("Time taken to stderr append:\t"
438           // + (System.nanoTime() - time) + " ns");
439           // lines++;
440         }
441         if (quit)
442           return;
443       }
444       while (Thread.currentThread() == textAppender)
445       {
446         if (updateConsole)
447         {
448           // check string buffer - if greater than console, clear console and
449           // replace with last segment of content, otherwise, append all to
450           // content.
451           long count;
452           while (displayPipe.length() > 0)
453           {
454             count = 0;
455             StringBuffer tmp = new StringBuffer(), replace;
456             synchronized (displayPipe)
457             {
458               replace = displayPipe;
459               displayPipe = tmp;
460             }
461             // simply append whole buffer
462             textArea.append(replace.toString());
463             count += replace.length();
464             if (count > byteslim)
465             {
466               trimBuffer(false);
467             }
468           }
469           if (displayPipe.length() == 0)
470           {
471             try
472             {
473               this.wait(100);
474               if (displayPipe.length() == 0)
475               {
476                 trimBuffer(false);
477               }
478             } catch (InterruptedException e)
479             {
480             }
481             ;
482           }
483         }
484         else
485         {
486           try
487           {
488             this.wait(100);
489           } catch (InterruptedException e)
490           {
491
492           }
493         }
494         if (quit)
495         {
496           return;
497         }
498
499       }
500     } catch (Exception e)
501     {
502       textArea.append("\nConsole reports an Internal error.");
503       textArea.append("The error is: " + e.getMessage());
504       // Need to uncomment this to ensure that line tally is synched.
505       // lines += 2;
506       stderr.println("Console reports an Internal error.\nThe error is: "
507               + e);
508     }
509
510     // just for testing (Throw a Nullpointer after 1 second)
511     if (Thread.currentThread() == errorThrower)
512     {
513       try
514       {
515         this.wait(1000);
516       } catch (InterruptedException ie)
517       {
518       }
519       throw new NullPointerException(
520               MessageManager.getString("exception.application_test_npe"));
521     }
522   }
523
524   private void appendToTextArea(final String input)
525   {
526     if (updateConsole == false)
527     {
528       // do nothing;
529       return;
530     }
531     long time = System.nanoTime();
532     javax.swing.SwingUtilities.invokeLater(new Runnable()
533     {
534       public void run()
535       {
536         displayPipe.append(input); // change to stringBuffer
537         // displayPipe.flush();
538
539       }
540     });
541     // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() -
542     // time)
543     // + " ns");
544   }
545
546   private String header = null;
547
548   private boolean updateConsole = false;
549
550   private synchronized void trimBuffer(boolean clear)
551   {
552     if (header == null && textArea.getLineCount() > 5)
553     {
554       try
555       {
556         header = textArea.getText(0, textArea.getLineStartOffset(5))
557                 + "\nTruncated...\n";
558       } catch (Exception e)
559       {
560         e.printStackTrace();
561       }
562     }
563     // trim the buffer
564     int tlength = textArea.getDocument().getLength();
565     if (header != null)
566     {
567       if (clear || (tlength > byteslim))
568       {
569         try
570         {
571           if (!clear)
572           {
573             long time = System.nanoTime();
574             textArea.replaceRange(header, 0, tlength - bytescut);
575             // stderr.println("Time taken to cut:\t"
576             // + (System.nanoTime() - time) + " ns");
577           }
578           else
579           {
580             textArea.setText(header);
581           }
582         } catch (Exception e)
583         {
584           e.printStackTrace();
585         }
586         // lines = textArea.getLineCount();
587       }
588     }
589
590   }
591
592   public synchronized String readLine(PipedInputStream in)
593           throws IOException
594   {
595     String input = "";
596     int lp = -1;
597     do
598     {
599       int available = in.available();
600       if (available == 0)
601         break;
602       byte b[] = new byte[available];
603       in.read(b);
604       input = input + new String(b, 0, b.length);
605       // counts lines - we don't do this for speed.
606       // while ((lp = input.indexOf("\n", lp + 1)) > -1)
607       // {
608       // lines++;
609       // }
610     } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
611     return input;
612   }
613
614   public static void main(String[] arg)
615   {
616     new Console().test(); // create console with not reference
617
618   }
619
620   public void setVisible(boolean selected)
621   {
622     frame.setVisible(selected);
623     if (selected == true)
624     {
625       redirectStreams();
626       updateConsole = true;
627       frame.toFront();
628     }
629     else
630     {
631       unredirectStreams();
632       updateConsole = false;
633     }
634   }
635
636   public Rectangle getBounds()
637   {
638     if (frame != null)
639     {
640       return frame.getBounds();
641     }
642     return null;
643   }
644
645   /**
646    * set the banner that appears at the top of the console output
647    * 
648    * @param string
649    */
650   public void setHeader(String string)
651   {
652     header = string;
653     if (header.charAt(header.length() - 1) != '\n')
654     {
655       header += "\n";
656     }
657     textArea.insert(header, 0);
658   }
659
660   /**
661    * get the banner
662    * 
663    * @return
664    */
665   public String getHeader()
666   {
667     return header;
668   }
669 }