JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[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 java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.GraphicsEnvironment;
27 import java.awt.GridBagConstraints;
28 import java.awt.GridBagLayout;
29 import java.awt.Rectangle;
30 import java.awt.Toolkit;
31 import java.awt.datatransfer.Clipboard;
32 import java.awt.datatransfer.StringSelection;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.MouseAdapter;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.WindowAdapter;
38 import java.awt.event.WindowEvent;
39 import java.awt.event.WindowListener;
40 import java.io.IOException;
41 import java.io.PipedInputStream;
42 import java.io.PipedOutputStream;
43 import java.io.PrintStream;
44
45 import javax.swing.BorderFactory;
46 import javax.swing.JButton;
47 import javax.swing.JComboBox;
48 import javax.swing.JFrame;
49 import javax.swing.JLabel;
50 import javax.swing.JPanel;
51 import javax.swing.JScrollPane;
52 import javax.swing.JTextArea;
53 import javax.swing.border.Border;
54 import javax.swing.text.DefaultCaret;
55
56 import jalview.bin.Cache;
57 import jalview.log.JLoggerI.LogLevel;
58 import jalview.log.JLoggerLog4j;
59 import jalview.log.JalviewAppender;
60 import jalview.util.ChannelProperties;
61 import jalview.util.MessageManager;
62 import jalview.util.Platform;
63
64 /**
65  * Simple Jalview Java Console. Version 1 - allows viewing of console output
66  * after desktop is created. Acquired with thanks from RJHM's site
67  * http://www.comweb.nl/java/Console/Console.html A simple Java Console for your
68  * application (Swing version) Requires Java 1.1.5 or higher Disclaimer the use
69  * of this source is at your own risk. Permision to use and distribute into your
70  * own applications RJHM van den Bergh , rvdb@comweb.nl
71  */
72
73 public class Console extends WindowAdapter
74         implements WindowListener, ActionListener, Runnable
75 {
76   private JFrame frame;
77
78   private JTextArea textArea;
79
80   /*
81    * unused - tally and limit for lines in console window int lines = 0;
82    * 
83    * int lim = 1000;
84    */
85   int byteslim = 102400, bytescut = 76800; // 100k and 75k cut point.
86
87   private Thread reader, reader2, textAppender;
88
89   private boolean quit;
90
91   private final PrintStream stdout = System.out, stderr = System.err;
92
93   private PipedInputStream pin = new PipedInputStream();
94
95   private PipedInputStream pin2 = new PipedInputStream();
96
97   private StringBuffer displayPipe = new StringBuffer();
98
99   Thread errorThrower; // just for testing (Throws an Exception at this Console
100
101   // are we attached to some parent Desktop
102   Desktop parent = null;
103
104   private int MIN_WIDTH = 300;
105
106   private int MIN_HEIGHT = 250;
107
108   private JComboBox<LogLevel> logLevelCombo = new JComboBox<LogLevel>();
109
110   protected LogLevel startingLogLevel = LogLevel.INFO;
111
112   public Console()
113   {
114     // create all components and add them
115     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
116     frame = initFrame("Java Console", screenSize.width / 2,
117             screenSize.height / 2, -1, -1);
118     initConsole(true);
119   }
120
121   private void initConsole(boolean visible)
122   {
123     initConsole(visible, true);
124   }
125
126   /**
127    * 
128    * @param visible
129    *          - open the window
130    * @param redirect
131    *          - redirect std*
132    */
133   private void initConsole(boolean visible, boolean redirect)
134   {
135     // CutAndPasteTransfer cpt = new CutAndPasteTransfer();
136     // textArea = cpt.getTextArea();
137     textArea = new JTextArea();
138     textArea.setEditable(false);
139     // autoscroll
140     DefaultCaret caret = (DefaultCaret) textArea.getCaret();
141     caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
142     // toggle autoscroll by clicking on the text area
143     Border pausedBorder = BorderFactory.createMatteBorder(2, 2, 2, 2,
144             textArea.getForeground());
145     Border noBorder = BorderFactory.createEmptyBorder(2, 2, 2, 2);
146     JScrollPane scrollPane = new JScrollPane(textArea);
147     scrollPane.setBorder(noBorder);
148     textArea.addMouseListener(new MouseAdapter()
149     {
150       public void mouseClicked(MouseEvent e)
151       {
152         if (e.getButton() == MouseEvent.BUTTON1)
153         {
154           if (caret.getUpdatePolicy() == DefaultCaret.ALWAYS_UPDATE)
155           {
156             caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
157             scrollPane.setBorder(pausedBorder);
158           }
159           else
160           {
161             caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
162             textArea.setCaretPosition(textArea.getDocument().getLength());
163             scrollPane.setBorder(noBorder);
164           }
165         }
166       }
167     });
168
169     JButton clearButton = new JButton(
170             MessageManager.getString("action.clear"));
171     JButton copyToClipboardButton = new JButton(
172             MessageManager.getString("label.copy_to_clipboard"));
173     copyToClipboardButton.addActionListener(new ActionListener()
174     {
175       public void actionPerformed(ActionEvent e)
176       {
177         copyConsoleTextToClipboard();
178       }
179     });
180     copyToClipboardButton.addMouseListener(new MouseAdapter()
181     {
182       private Color bg = textArea.getBackground();
183
184       private Color fg = textArea.getForeground();
185
186       public void mousePressed(MouseEvent e)
187       {
188         textArea.setBackground(textArea.getSelectionColor());
189         textArea.setForeground(textArea.getSelectedTextColor());
190       }
191
192       public void mouseReleased(MouseEvent e)
193       {
194         textArea.setBackground(bg);
195         textArea.setForeground(fg);
196       }
197
198     });
199     copyToClipboardButton.setToolTipText(
200             MessageManager.getString("label.copy_to_clipboard_tooltip"));
201
202     JLabel logLevelLabel = new JLabel(
203             MessageManager.getString("label.log_level") + ":");
204
205     // logLevelCombo.addItem(LogLevel.ALL);
206     logLevelCombo.addItem(LogLevel.TRACE);
207     logLevelCombo.addItem(LogLevel.DEBUG);
208     logLevelCombo.addItem(LogLevel.INFO);
209     logLevelCombo.addItem(LogLevel.WARN);
210     // logLevelCombo.addItem(LogLevel.ERROR);
211     // logLevelCombo.addItem(LogLevel.FATAL);
212     // logLevelCombo.addItem(LogLevel.ERROR);
213     // logLevelCombo.addItem(LogLevel.OFF);
214     // set startingLogLevel
215     startingLogLevel = Cache.log == null ? LogLevel.INFO
216             : Cache.log.getLevel();
217     setChosenLogLevelCombo();
218     logLevelCombo.addActionListener(new ActionListener()
219     {
220       public void actionPerformed(ActionEvent e)
221       {
222         if (Cache.log != null)
223         {
224           Cache.log.setLevel((LogLevel) logLevelCombo.getSelectedItem());
225         }
226       }
227
228     });
229
230     // frame = cpt;
231     frame.getContentPane().setLayout(new BorderLayout());
232     frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
233     JPanel southPanel = new JPanel();
234     southPanel.setLayout(new GridBagLayout());
235
236     JPanel logLevelPanel = new JPanel();
237     logLevelPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
238     logLevelPanel.add(logLevelLabel);
239     logLevelPanel.add(logLevelCombo);
240     String logLevelTooltip = MessageManager.formatMessage(
241             "label.log_level_tooltip", startingLogLevel.toString());
242     logLevelLabel.setToolTipText(logLevelTooltip);
243     logLevelCombo.setToolTipText(logLevelTooltip);
244
245     GridBagConstraints gbc = new GridBagConstraints();
246     gbc.gridx = 0;
247     gbc.gridy = 0;
248     gbc.gridwidth = 1;
249     gbc.gridheight = 1;
250     gbc.weightx = 0.1;
251     southPanel.add(logLevelPanel, gbc);
252
253     gbc.gridx++;
254     gbc.weightx = 0.8;
255     gbc.fill = GridBagConstraints.HORIZONTAL;
256     southPanel.add(clearButton, gbc);
257
258     gbc.gridx++;
259     gbc.weightx = 0.1;
260     gbc.fill = GridBagConstraints.NONE;
261     southPanel.add(copyToClipboardButton, gbc);
262
263     southPanel.setVisible(true);
264     frame.getContentPane().add(southPanel, BorderLayout.SOUTH);
265     frame.setVisible(visible);
266     updateConsole = visible;
267     frame.addWindowListener(this);
268     clearButton.addActionListener(this);
269
270     if (redirect)
271     {
272       redirectStreams();
273     }
274     else
275     {
276       unredirectStreams();
277     }
278     quit = false; // signals the Threads that they should exit
279
280     // Starting two seperate threads to read from the PipedInputStreams
281     //
282     reader = new Thread(this);
283     reader.setDaemon(true);
284     reader.start();
285     //
286     reader2 = new Thread(this);
287     reader2.setDaemon(true);
288     reader2.start();
289     // and a thread to append text to the textarea
290     textAppender = new Thread(this);
291     textAppender.setDaemon(true);
292     textAppender.start();
293
294     // set icons
295     frame.setIconImages(ChannelProperties.getIconList());
296   }
297
298   private void setChosenLogLevelCombo()
299   {
300     setChosenLogLevelCombo(startingLogLevel);
301   }
302
303   private void setChosenLogLevelCombo(LogLevel setLogLevel)
304   {
305     logLevelCombo.setSelectedItem(setLogLevel);
306     if (!logLevelCombo.getSelectedItem().equals(setLogLevel))
307     {
308       // setLogLevel not (yet) in list
309       if (setLogLevel != null && setLogLevel instanceof LogLevel)
310       {
311         // add new item to list (might be set via .jalview_properties)
312         boolean added = false;
313         for (int i = 0; i < logLevelCombo.getItemCount(); i++)
314         {
315           LogLevel l = (LogLevel) logLevelCombo.getItemAt(i);
316           if (l.compareTo(setLogLevel) >= 0)
317           {
318             logLevelCombo.insertItemAt(setLogLevel, i);
319             added = true;
320             break;
321           }
322         }
323         if (!added) // lower priority than others or some confusion -- add to
324                     // end of list
325         {
326           logLevelCombo.addItem(setLogLevel);
327         }
328         logLevelCombo.setSelectedItem(setLogLevel);
329       }
330       else
331       {
332         logLevelCombo.setSelectedItem(LogLevel.INFO);
333       }
334     }
335   }
336
337   private void copyConsoleTextToClipboard()
338   {
339     String consoleText = textArea.getText();
340     StringSelection consoleTextSelection = new StringSelection(consoleText);
341     Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
342     cb.setContents(consoleTextSelection, null);
343   }
344
345   PipedOutputStream pout = null, perr = null;
346
347   public void redirectStreams()
348   {
349     if (pout == null)
350     {
351       try
352       {
353         pout = new PipedOutputStream(this.pin);
354         System.setOut(new PrintStream(pout, true));
355       } catch (java.io.IOException io)
356       {
357         textArea.append("Couldn't redirect STDOUT to this console\n"
358                 + io.getMessage());
359         io.printStackTrace(stderr);
360       } catch (SecurityException se)
361       {
362         textArea.append("Couldn't redirect STDOUT to this console\n"
363                 + se.getMessage());
364         se.printStackTrace(stderr);
365       }
366
367       try
368       {
369         perr = new PipedOutputStream(this.pin2);
370         System.setErr(new PrintStream(perr, true));
371       } catch (java.io.IOException io)
372       {
373         textArea.append("Couldn't redirect STDERR to this console\n"
374                 + io.getMessage());
375         io.printStackTrace(stderr);
376       } catch (SecurityException se)
377       {
378         textArea.append("Couldn't redirect STDERR to this console\n"
379                 + se.getMessage());
380         se.printStackTrace(stderr);
381       }
382     }
383   }
384
385   public void unredirectStreams()
386   {
387     if (pout != null)
388     {
389       try
390       {
391         System.setOut(stdout);
392         pout.flush();
393         pout.close();
394         pin = new PipedInputStream();
395         pout = null;
396       } catch (java.io.IOException io)
397       {
398         textArea.append("Couldn't unredirect STDOUT to this console\n"
399                 + io.getMessage());
400         io.printStackTrace(stderr);
401       } catch (SecurityException se)
402       {
403         textArea.append("Couldn't unredirect STDOUT to this console\n"
404                 + se.getMessage());
405         se.printStackTrace(stderr);
406       }
407
408       try
409       {
410         System.setErr(stderr);
411         perr.flush();
412         perr.close();
413         pin2 = new PipedInputStream();
414         perr = null;
415       } catch (java.io.IOException io)
416       {
417         textArea.append("Couldn't unredirect STDERR to this console\n"
418                 + io.getMessage());
419         io.printStackTrace(stderr);
420       } catch (SecurityException se)
421       {
422         textArea.append("Couldn't unredirect STDERR to this console\n"
423                 + se.getMessage());
424         se.printStackTrace(stderr);
425       }
426     }
427   }
428
429   public void test()
430   {
431     // testing part
432     // you may omit this part for your application
433     //
434
435     System.out.println("Hello World 2");
436     System.out.println("All fonts available to Graphic2D:\n");
437     GraphicsEnvironment ge = GraphicsEnvironment
438             .getLocalGraphicsEnvironment();
439     String[] fontNames = ge.getAvailableFontFamilyNames();
440     for (int n = 0; n < fontNames.length; n++)
441     {
442       System.out.println(fontNames[n]);
443     }
444     // Testing part: simple an error thrown anywhere in this JVM will be printed
445     // on the Console
446     // We do it with a seperate Thread becasue we don't wan't to break a Thread
447     // used by the Console.
448     System.out.println("\nLets throw an error on this console");
449     errorThrower = new Thread(this);
450     errorThrower.setDaemon(true);
451     errorThrower.start();
452   }
453
454   private JFrame initFrame(String string, int i, int j, int x, int y)
455   {
456     JFrame frame = new JFrame(string);
457     frame.setName(string);
458     if (x == -1)
459     {
460       x = i / 2;
461     }
462     if (y == -1)
463     {
464       y = j / 2;
465     }
466     frame.setBounds(x, y, i, j);
467     return frame;
468   }
469
470   /**
471    * attach a console to the desktop - the desktop will open it if requested.
472    * 
473    * @param desktop
474    */
475   public Console(Desktop desktop)
476   {
477     this(desktop, true);
478   }
479
480   /**
481    * attach a console to the desktop - the desktop will open it if requested.
482    * 
483    * @param desktop
484    * @param showjconsole
485    *          - if true, then redirect stdout immediately
486    */
487   public Console(Desktop desktop, boolean showjconsole)
488   {
489     parent = desktop;
490     // window name - get x,y,width, height possibly scaled
491     Rectangle bounds = desktop.getLastKnownDimensions("JAVA_CONSOLE_");
492     if (bounds == null)
493     {
494       frame = initFrame(
495               ChannelProperties.getProperty("app_name") + " Java Console",
496               desktop.getWidth() / 2, desktop.getHeight() / 4,
497               desktop.getX(), desktop.getY());
498     }
499     else
500     {
501       frame = initFrame(
502               ChannelProperties.getProperty("app_name") + " Java Console",
503               bounds.width, bounds.height, bounds.x, bounds.y);
504     }
505     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
506     // desktop.add(frame);
507     initConsole(false);
508     LogLevel level = (LogLevel) logLevelCombo.getSelectedItem();
509     if (!Platform.isJS())
510     {
511       JalviewAppender jappender = new JalviewAppender(level);
512       JalviewAppender.setTextArea(textArea);
513       jappender.start();
514       if (Cache.log != null && Cache.log instanceof JLoggerLog4j)
515       {
516         JLoggerLog4j.addAppender(Cache.log, jappender);
517       }
518     }
519   }
520
521   public synchronized void stopConsole()
522   {
523     quit = true;
524     this.notifyAll();
525     /*
526      * reader.notify(); reader2.notify(); if (errorThrower!=null)
527      * errorThrower.notify(); // stop all threads if (textAppender!=null)
528      * textAppender.notify();
529      */
530     if (pout != null)
531     {
532       try
533       {
534         reader.join(10);
535         pin.close();
536       } catch (Exception e)
537       {
538       }
539       try
540       {
541         reader2.join(10);
542         pin2.close();
543       } catch (Exception e)
544       {
545       }
546       try
547       {
548         textAppender.join(10);
549       } catch (Exception e)
550       {
551       }
552     }
553     if (!frame.isVisible())
554     {
555       frame.dispose();
556     }
557     // System.exit(0);
558   }
559
560   @Override
561   public synchronized void windowClosed(WindowEvent evt)
562   {
563     frame.setVisible(false);
564     closeConsoleGui();
565   }
566
567   private void closeConsoleGui()
568   {
569     updateConsole = false;
570     if (parent == null)
571     {
572
573       stopConsole();
574     }
575     else
576     {
577       parent.showConsole(false);
578     }
579   }
580
581   @Override
582   public synchronized void windowClosing(WindowEvent evt)
583   {
584     frame.setVisible(false); // default behaviour of JFrame
585     closeConsoleGui();
586
587     // frame.dispose();
588   }
589
590   @Override
591   public synchronized void actionPerformed(ActionEvent evt)
592   {
593     trimBuffer(true);
594     // textArea.setText("");
595   }
596
597   @Override
598   public synchronized void run()
599   {
600     try
601     {
602       while (Thread.currentThread() == reader)
603       {
604         if (pin == null || pin.available() == 0)
605         {
606           try
607           {
608             this.wait(100);
609             if (pin.available() == 0)
610             {
611               trimBuffer(false);
612             }
613           } catch (InterruptedException ie)
614           {
615           }
616         }
617
618         while (pin.available() != 0)
619         {
620           String input = this.readLine(pin);
621           stdout.print(input);
622           long time = System.nanoTime();
623           appendToTextArea(input);
624           // stderr.println("Time taken to stdout append:\t"
625           // + (System.nanoTime() - time) + " ns");
626           // lines++;
627         }
628         if (quit)
629         {
630           return;
631         }
632       }
633
634       while (Thread.currentThread() == reader2)
635       {
636         if (pin2.available() == 0)
637         {
638           try
639           {
640             this.wait(100);
641             if (pin2.available() == 0)
642             {
643               trimBuffer(false);
644             }
645           } catch (InterruptedException ie)
646           {
647           }
648         }
649         while (pin2.available() != 0)
650         {
651           String input = this.readLine(pin2);
652           stderr.print(input);
653           long time = System.nanoTime();
654           appendToTextArea(input);
655           // stderr.println("Time taken to stderr append:\t"
656           // + (System.nanoTime() - time) + " ns");
657           // lines++;
658         }
659         if (quit)
660         {
661           return;
662         }
663       }
664       while (Thread.currentThread() == textAppender)
665       {
666         if (updateConsole)
667         {
668           // check string buffer - if greater than console, clear console and
669           // replace with last segment of content, otherwise, append all to
670           // content.
671           long count;
672           while (displayPipe.length() > 0)
673           {
674             count = 0;
675             StringBuffer tmp = new StringBuffer(), replace;
676             synchronized (displayPipe)
677             {
678               replace = displayPipe;
679               displayPipe = tmp;
680             }
681             // simply append whole buffer
682             textArea.append(replace.toString());
683             count += replace.length();
684             if (count > byteslim)
685             {
686               trimBuffer(false);
687             }
688           }
689           if (displayPipe.length() == 0)
690           {
691             try
692             {
693               this.wait(100);
694               if (displayPipe.length() == 0)
695               {
696                 trimBuffer(false);
697               }
698             } catch (InterruptedException e)
699             {
700             }
701           }
702         }
703         else
704         {
705           try
706           {
707             this.wait(100);
708           } catch (InterruptedException e)
709           {
710
711           }
712         }
713         if (quit)
714         {
715           return;
716         }
717
718       }
719     } catch (Exception e)
720     {
721       textArea.append("\nConsole reports an Internal error.");
722       textArea.append("The error is: " + e.getMessage());
723       // Need to uncomment this to ensure that line tally is synched.
724       // lines += 2;
725       stderr.println(
726               "Console reports an Internal error.\nThe error is: " + e);
727     }
728
729     // just for testing (Throw a Nullpointer after 1 second)
730     if (Thread.currentThread() == errorThrower)
731     {
732       try
733       {
734         this.wait(1000);
735       } catch (InterruptedException ie)
736       {
737       }
738       throw new NullPointerException(
739               MessageManager.getString("exception.application_test_npe"));
740     }
741   }
742
743   private void appendToTextArea(final String input)
744   {
745     if (updateConsole == false)
746     {
747       // do nothing;
748       return;
749     }
750     long time = System.nanoTime();
751     javax.swing.SwingUtilities.invokeLater(new Runnable()
752     {
753       @Override
754       public void run()
755       {
756         displayPipe.append(input); // change to stringBuffer
757         // displayPipe.flush();
758
759       }
760     });
761     // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() -
762     // time)
763     // + " ns");
764   }
765
766   private String header = null;
767
768   private boolean updateConsole = false;
769
770   private synchronized void trimBuffer(boolean clear)
771   {
772     if (header == null && textArea.getLineCount() > 5)
773     {
774       try
775       {
776         header = textArea.getText(0, textArea.getLineStartOffset(5))
777                 + "\nTruncated...\n";
778       } catch (Exception e)
779       {
780         e.printStackTrace();
781       }
782     }
783     // trim the buffer
784     int tlength = textArea.getDocument().getLength();
785     if (header != null)
786     {
787       if (clear || (tlength > byteslim))
788       {
789         try
790         {
791           if (!clear)
792           {
793             long time = System.nanoTime();
794             textArea.replaceRange(header, 0, tlength - bytescut);
795             // stderr.println("Time taken to cut:\t"
796             // + (System.nanoTime() - time) + " ns");
797           }
798           else
799           {
800             textArea.setText(header);
801           }
802         } catch (Exception e)
803         {
804           e.printStackTrace();
805         }
806         // lines = textArea.getLineCount();
807       }
808     }
809
810   }
811
812   public synchronized String readLine(PipedInputStream in)
813           throws IOException
814   {
815     String input = "";
816     int lp = -1;
817     do
818     {
819       int available = in.available();
820       if (available == 0)
821       {
822         break;
823       }
824       byte b[] = new byte[available];
825       in.read(b);
826       input = input + new String(b, 0, b.length);
827       // counts lines - we don't do this for speed.
828       // while ((lp = input.indexOf("\n", lp + 1)) > -1)
829       // {
830       // lines++;
831       // }
832     } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
833     return input;
834   }
835
836   /**
837    * @j2sIgnore
838    * @param arg
839    */
840   public static void main(String[] arg)
841   {
842     new Console().test(); // create console with not reference
843
844   }
845
846   public void setVisible(boolean selected)
847   {
848     frame.setVisible(selected);
849     if (selected == true)
850     {
851       setChosenLogLevelCombo();
852       redirectStreams();
853       updateConsole = true;
854       frame.toFront();
855     }
856     else
857     {
858       // reset log level to what it was before
859       if (Cache.log != null)
860       {
861         Cache.log.setLevel(startingLogLevel);
862       }
863
864       unredirectStreams();
865       updateConsole = false;
866     }
867   }
868
869   public Rectangle getBounds()
870   {
871     if (frame != null)
872     {
873       return frame.getBounds();
874     }
875     return null;
876   }
877
878   /**
879    * set the banner that appears at the top of the console output
880    * 
881    * @param string
882    */
883   public void setHeader(String string)
884   {
885     header = string;
886     if (header.charAt(header.length() - 1) != '\n')
887     {
888       header += "\n";
889     }
890     textArea.insert(header, 0);
891   }
892
893   /**
894    * get the banner
895    * 
896    * @return
897    */
898   public String getHeader()
899   {
900     return header;
901   }
902 }