JAL-3253 code tidies and tweaks to Desktop
[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
57         implements WindowListener, 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   private int MIN_WIDTH = 300;
88
89   private int MIN_HEIGHT = 250;
90
91   public Console()
92   {
93     // create all components and add them
94     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
95     frame = initFrame("Java Console", screenSize.width / 2,
96             screenSize.height / 2, -1, -1);
97     initConsole(true);
98   }
99
100   private void initConsole(boolean visible)
101   {
102     initConsole(visible, true);
103   }
104
105   /**
106    * 
107    * @param visible
108    *          - open the window
109    * @param redirect
110    *          - redirect std*
111    */
112   private void initConsole(boolean visible, boolean redirect)
113   {
114     // CutAndPasteTransfer cpt = new CutAndPasteTransfer();
115     // textArea = cpt.getTextArea();
116     textArea = new JTextArea();
117     textArea.setEditable(false);
118     JButton button = new JButton(MessageManager.getString("action.clear"));
119
120     // frame = cpt;
121     frame.getContentPane().setLayout(new BorderLayout());
122     frame.getContentPane().add(new JScrollPane(textArea),
123             BorderLayout.CENTER);
124     frame.getContentPane().add(button, BorderLayout.SOUTH);
125     frame.setVisible(visible);
126     updateConsole = visible;
127     frame.addWindowListener(this);
128     button.addActionListener(this);
129     if (redirect)
130     {
131       redirectStreams();
132     }
133     else
134     {
135       unredirectStreams();
136     }
137     quit = false; // signals the Threads that they should exit
138
139     // Starting two separate threads to read from the PipedInputStreams
140     //
141     reader = new Thread(this);
142     reader.setDaemon(true);
143     reader.start();
144     //
145     reader2 = new Thread(this);
146     reader2.setDaemon(true);
147     reader2.start();
148     // and a thread to append text to the textarea
149     textAppender = new Thread(this);
150     textAppender.setDaemon(true);
151     textAppender.start();
152   }
153
154   PipedOutputStream pout = null, perr = null;
155
156   public void redirectStreams()
157   {
158     if (pout == null)
159     {
160       try
161       {
162         pout = new PipedOutputStream(this.pin);
163         System.setOut(new PrintStream(pout, true));
164       } catch (java.io.IOException io)
165       {
166         textArea.append("Couldn't redirect STDOUT to this console\n"
167                 + io.getMessage());
168         io.printStackTrace(stderr);
169       } catch (SecurityException se)
170       {
171         textArea.append("Couldn't redirect STDOUT to this console\n"
172                 + se.getMessage());
173         se.printStackTrace(stderr);
174       }
175
176       try
177       {
178         perr = new PipedOutputStream(this.pin2);
179         System.setErr(new PrintStream(perr, true));
180       } catch (java.io.IOException io)
181       {
182         textArea.append("Couldn't redirect STDERR to this console\n"
183                 + io.getMessage());
184         io.printStackTrace(stderr);
185       } catch (SecurityException se)
186       {
187         textArea.append("Couldn't redirect STDERR to this console\n"
188                 + se.getMessage());
189         se.printStackTrace(stderr);
190       }
191     }
192   }
193
194   public void unredirectStreams()
195   {
196     if (pout != null)
197     {
198       try
199       {
200         System.setOut(stdout);
201         pout.flush();
202         pout.close();
203         pin = new PipedInputStream();
204         pout = null;
205       } catch (java.io.IOException io)
206       {
207         textArea.append("Couldn't unredirect STDOUT to this console\n"
208                 + io.getMessage());
209         io.printStackTrace(stderr);
210       } catch (SecurityException se)
211       {
212         textArea.append("Couldn't unredirect STDOUT to this console\n"
213                 + se.getMessage());
214         se.printStackTrace(stderr);
215       }
216
217       try
218       {
219         System.setErr(stderr);
220         perr.flush();
221         perr.close();
222         pin2 = new PipedInputStream();
223         perr = null;
224       } catch (java.io.IOException io)
225       {
226         textArea.append("Couldn't unredirect STDERR to this console\n"
227                 + io.getMessage());
228         io.printStackTrace(stderr);
229       } catch (SecurityException se)
230       {
231         textArea.append("Couldn't unredirect STDERR to this console\n"
232                 + se.getMessage());
233         se.printStackTrace(stderr);
234       }
235     }
236   }
237
238   public void test()
239   {
240     // testing part
241     // you may omit this part for your application
242     //
243
244     System.out.println("Hello World 2");
245     System.out.println("All fonts available to Graphic2D:\n");
246     GraphicsEnvironment ge = GraphicsEnvironment
247             .getLocalGraphicsEnvironment();
248     String[] fontNames = ge.getAvailableFontFamilyNames();
249     for (int n = 0; n < fontNames.length; n++)
250     {
251       System.out.println(fontNames[n]);
252     }
253     // Testing part: simple an error thrown anywhere in this JVM will be printed
254     // on the Console
255     // We do it with a seperate Thread becasue we don't wan't to break a Thread
256     // used by the Console.
257     System.out.println("\nLets throw an error on this console");
258     errorThrower = new Thread(this);
259     errorThrower.setDaemon(true);
260     errorThrower.start();
261   }
262
263   private JFrame initFrame(String string, int i, int j, int x, int y)
264   {
265     JFrame frame = new JFrame(string);
266     frame.setName(string);
267     if (x == -1)
268     {
269       x = i / 2;
270     }
271     if (y == -1)
272     {
273       y = j / 2;
274     }
275     frame.setBounds(x, y, i, j);
276     return frame;
277   }
278
279   /**
280    * attach a console to the desktop - the desktop will open it if requested.
281    * 
282    * @param desktop
283    */
284   public Console(Desktop desktop)
285   {
286     this(desktop, true);
287   }
288
289   /**
290    * attach a console to the desktop - the desktop will open it if requested.
291    * 
292    * @param desktop
293    * @param showjconsole
294    *          - if true, then redirect stdout immediately
295    */
296   public Console(Desktop desktop, boolean showjconsole)
297   {
298     parent = desktop;
299     // window name - get x,y,width, height possibly scaled
300     Rectangle bounds = desktop.getLastKnownDimensions("JAVA_CONSOLE_");
301     if (bounds == null)
302     {
303       frame = initFrame("Jalview Java Console", desktop.getWidth() / 2,
304               desktop.getHeight() / 4, desktop.getX(), desktop.getY());
305     }
306     else
307     {
308       frame = initFrame("Jalview Java Console", bounds.width, bounds.height,
309               bounds.x, bounds.y);
310     }
311     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
312     // desktop.add(frame);
313     initConsole(false);
314     JalviewAppender jappender = new JalviewAppender();
315     jappender.setLayout(new SimpleLayout());
316     // BH 2019 JalviewAppender.setTextArea(textArea);
317     jappender.setTextArea(textArea);
318     org.apache.log4j.Logger.getRootLogger().addAppender(jappender);
319   }
320
321   public synchronized void stopConsole()
322   {
323     quit = true;
324     this.notifyAll();
325     /*
326      * reader.notify(); reader2.notify(); if (errorThrower!=null)
327      * errorThrower.notify(); // stop all threads if (textAppender!=null)
328      * textAppender.notify();
329      */
330     if (pout != null)
331     {
332       try
333       {
334         reader.join(10);
335         pin.close();
336       } catch (Exception e)
337       {
338       }
339       try
340       {
341         reader2.join(10);
342         pin2.close();
343       } catch (Exception e)
344       {
345       }
346       try
347       {
348         textAppender.join(10);
349       } catch (Exception e)
350       {
351       }
352     }
353     if (!frame.isVisible())
354     {
355       frame.dispose();
356     }
357     // System.exit(0);
358   }
359
360   @Override
361   public synchronized void windowClosed(WindowEvent evt)
362   {
363     frame.setVisible(false);
364     closeConsoleGui();
365   }
366
367   private void closeConsoleGui()
368   {
369     updateConsole = false;
370     if (parent == null)
371     {
372
373       stopConsole();
374     }
375     else
376     {
377       parent.showConsole(false);
378     }
379   }
380
381   @Override
382   public synchronized void windowClosing(WindowEvent evt)
383   {
384     frame.setVisible(false); // default behaviour of JFrame
385     closeConsoleGui();
386
387     // frame.dispose();
388   }
389
390   @Override
391   public synchronized void actionPerformed(ActionEvent evt)
392   {
393     trimBuffer(true);
394     // textArea.setText("");
395   }
396
397   @Override
398   public synchronized void run()
399   {
400     try
401     {
402       while (Thread.currentThread() == reader)
403       {
404         if (pin == null || pin.available() == 0)
405         {
406           try
407           {
408             this.wait(100);
409             if (pin.available() == 0)
410             {
411               trimBuffer(false);
412             }
413           } catch (InterruptedException ie)
414           {
415           }
416         }
417
418         while (pin.available() != 0)
419         {
420           String input = this.readLine(pin);
421           stdout.print(input);
422           long time = System.nanoTime();
423           appendToTextArea(input);
424           // stderr.println("Time taken to stdout append:\t"
425           // + (System.nanoTime() - time) + " ns");
426           // lines++;
427         }
428         if (quit)
429         {
430           return;
431         }
432       }
433
434       while (Thread.currentThread() == reader2)
435       {
436         if (pin2.available() == 0)
437         {
438           try
439           {
440             this.wait(100);
441             if (pin2.available() == 0)
442             {
443               trimBuffer(false);
444             }
445           } catch (InterruptedException ie)
446           {
447           }
448         }
449         while (pin2.available() != 0)
450         {
451           String input = this.readLine(pin2);
452           stderr.print(input);
453           long time = System.nanoTime();
454           appendToTextArea(input);
455           // stderr.println("Time taken to stderr append:\t"
456           // + (System.nanoTime() - time) + " ns");
457           // lines++;
458         }
459         if (quit)
460         {
461           return;
462         }
463       }
464       while (Thread.currentThread() == textAppender)
465       {
466         if (updateConsole)
467         {
468           // check string buffer - if greater than console, clear console and
469           // replace with last segment of content, otherwise, append all to
470           // content.
471           long count;
472           while (displayPipe.length() > 0)
473           {
474             count = 0;
475             StringBuffer tmp = new StringBuffer(), replace;
476             synchronized (displayPipe)
477             {
478               replace = displayPipe;
479               displayPipe = tmp;
480             }
481             // simply append whole buffer
482             textArea.append(replace.toString());
483             count += replace.length();
484             if (count > byteslim)
485             {
486               trimBuffer(false);
487             }
488           }
489           if (displayPipe.length() == 0)
490           {
491             try
492             {
493               this.wait(100);
494               if (displayPipe.length() == 0)
495               {
496                 trimBuffer(false);
497               }
498             } catch (InterruptedException e)
499             {
500             }
501           }
502         }
503         else
504         {
505           try
506           {
507             this.wait(100);
508           } catch (InterruptedException e)
509           {
510
511           }
512         }
513         if (quit)
514         {
515           return;
516         }
517
518       }
519     } catch (Exception e)
520     {
521       textArea.append("\nConsole reports an Internal error.");
522       textArea.append("The error is: " + e.getMessage());
523       // Need to uncomment this to ensure that line tally is synched.
524       // lines += 2;
525       stderr.println(
526               "Console reports an Internal error.\nThe error is: " + e);
527     }
528
529     // just for testing (Throw a Nullpointer after 1 second)
530     if (Thread.currentThread() == errorThrower)
531     {
532       try
533       {
534         this.wait(1000);
535       } catch (InterruptedException ie)
536       {
537       }
538       throw new NullPointerException(
539               MessageManager.getString("exception.application_test_npe"));
540     }
541   }
542
543   private void appendToTextArea(final String input)
544   {
545     if (updateConsole == false)
546     {
547       // do nothing;
548       return;
549     }
550     long time = System.nanoTime();
551     javax.swing.SwingUtilities.invokeLater(new Runnable()
552     {
553       @Override
554       public void run()
555       {
556         displayPipe.append(input); // change to stringBuffer
557         // displayPipe.flush();
558
559       }
560     });
561     // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() -
562     // time)
563     // + " ns");
564   }
565
566   private String header = null;
567
568   private boolean updateConsole = false;
569
570   private synchronized void trimBuffer(boolean clear)
571   {
572     if (header == null && textArea.getLineCount() > 5)
573     {
574       try
575       {
576         header = textArea.getText(0, textArea.getLineStartOffset(5))
577                 + "\nTruncated...\n";
578       } catch (Exception e)
579       {
580         e.printStackTrace();
581       }
582     }
583     // trim the buffer
584     int tlength = textArea.getDocument().getLength();
585     if (header != null)
586     {
587       if (clear || (tlength > byteslim))
588       {
589         try
590         {
591           if (!clear)
592           {
593             long time = System.nanoTime();
594             textArea.replaceRange(header, 0, tlength - bytescut);
595             // stderr.println("Time taken to cut:\t"
596             // + (System.nanoTime() - time) + " ns");
597           }
598           else
599           {
600             textArea.setText(header);
601           }
602         } catch (Exception e)
603         {
604           e.printStackTrace();
605         }
606         // lines = textArea.getLineCount();
607       }
608     }
609
610   }
611
612   public synchronized String readLine(PipedInputStream in)
613           throws IOException
614   {
615     String input = "";
616     int lp = -1;
617     do
618     {
619       int available = in.available();
620       if (available == 0)
621       {
622         break;
623       }
624       byte b[] = new byte[available];
625       in.read(b);
626       input = input + new String(b, 0, b.length);
627       // counts lines - we don't do this for speed.
628       // while ((lp = input.indexOf("\n", lp + 1)) > -1)
629       // {
630       // lines++;
631       // }
632     } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
633     return input;
634   }
635
636   /**
637    * @j2sIgnore
638    * @param arg
639    */
640   public static void main(String[] arg)
641   {
642     new Console().test(); // create console with not reference
643
644   }
645
646   public void setVisible(boolean selected)
647   {
648     frame.setVisible(selected);
649     if (selected == true)
650     {
651       redirectStreams();
652       updateConsole = true;
653       frame.toFront();
654     }
655     else
656     {
657       unredirectStreams();
658       updateConsole = false;
659     }
660   }
661
662   public Rectangle getBounds()
663   {
664     if (frame != null)
665     {
666       return frame.getBounds();
667     }
668     return null;
669   }
670
671   /**
672    * set the banner that appears at the top of the console output
673    * 
674    * @param string
675    */
676   public void setHeader(String string)
677   {
678     header = string;
679     if (header.charAt(header.length() - 1) != '\n')
680     {
681       header += "\n";
682     }
683     textArea.insert(header, 0);
684   }
685
686   /**
687    * get the banner
688    * 
689    * @return
690    */
691   public String getHeader()
692   {
693     return header;
694   }
695 }