X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FConsole.java;h=b456025194dd4e2ba7ee796fe1c31bcb368271b3;hb=5f14ca65f127ffbe8ca3c043ac46730fe5a00423;hp=39b0ab35159eca93380331978b65aa69bb1e8809;hpb=d0d3897c2bbba80ed870da1ad5d70eadfec8c5f9;p=jalview.git diff --git a/src/jalview/gui/Console.java b/src/jalview/gui/Console.java index 39b0ab3..b456025 100644 --- a/src/jalview/gui/Console.java +++ b/src/jalview/gui/Console.java @@ -1,62 +1,109 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4.1) - * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of Jalview. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.gui; -import java.io.*; -import java.awt.*; -import java.awt.event.*; -import javax.swing.*; - +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintStream; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +import org.apache.log4j.Level; import org.apache.log4j.SimpleLayout; +import jalview.bin.Cache; +import jalview.util.MessageManager; + /** - * Simple Jalview Java Console. Version 1 - allows viewing of console output after desktop is created. - * Acquired with thanks from RJHM's site http://www.comweb.nl/java/Console/Console.html - * A simple Java Console for your application (Swing version) - * Requires Java 1.1.5 or higher - * Disclaimer the use of this source is at your own risk. - * Permision to use and distribute into your own applications - * RJHM van den Bergh , rvdb@comweb.nl + * Simple Jalview Java Console. Version 1 - allows viewing of console output + * after desktop is created. Acquired with thanks from RJHM's site + * http://www.comweb.nl/java/Console/Console.html A simple Java Console for your + * application (Swing version) Requires Java 1.1.5 or higher Disclaimer the use + * of this source is at your own risk. Permision to use and distribute into your + * own applications RJHM van den Bergh , rvdb@comweb.nl */ -public class Console extends WindowAdapter implements WindowListener, - ActionListener, Runnable +public class Console extends WindowAdapter + implements WindowListener, ActionListener, Runnable { private JFrame frame; private JTextArea textArea; - private Thread reader; + /* + * unused - tally and limit for lines in console window int lines = 0; + * + * int lim = 1000; + */ + int byteslim = 102400, bytescut = 76800; // 100k and 75k cut point. - private Thread reader2; + private Thread reader, reader2, textAppender; private boolean quit; - private final PrintStream stdout = System.out; - private final PrintStream stderr = System.err; - private final PipedInputStream pin = new PipedInputStream(); - private final PipedInputStream pin2 = new PipedInputStream(); + private final PrintStream stdout = System.out, stderr = System.err; + + private PipedInputStream pin = new PipedInputStream(); + private PipedInputStream pin2 = new PipedInputStream(); + + private StringBuffer displayPipe = new StringBuffer(); Thread errorThrower; // just for testing (Throws an Exception at this Console + // are we attached to some parent Desktop Desktop parent = null; + + private int MIN_WIDTH = 300; + + private int MIN_HEIGHT = 250; + + private JComboBox logLevelCombo = new JComboBox(); + + protected Level startingLogLevel = Level.INFO; + public Console() { // create all components and add them @@ -68,50 +115,131 @@ public class Console extends WindowAdapter implements WindowListener, private void initConsole(boolean visible) { + initConsole(visible, true); + } + + /** + * + * @param visible + * - open the window + * @param redirect + * - redirect std* + */ + private void initConsole(boolean visible, boolean redirect) + { // CutAndPasteTransfer cpt = new CutAndPasteTransfer(); - //textArea = cpt.getTextArea(); + // textArea = cpt.getTextArea(); textArea = new JTextArea(); textArea.setEditable(false); - JButton button = new JButton("clear"); + JButton clearButton = new JButton( + MessageManager.getString("action.clear")); + JButton copyToClipboardButton = new JButton( + MessageManager.getString("label.copy_to_clipboard")); + copyToClipboardButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + copyConsoleTextToClipboard(); + } + }); + copyToClipboardButton.addMouseListener(new MouseAdapter() + { + private Color bg = textArea.getBackground(); + + private Color fg = textArea.getForeground(); + + public void mousePressed(MouseEvent e) + { + textArea.setBackground(textArea.getSelectionColor()); + textArea.setForeground(textArea.getSelectedTextColor()); + } + + public void mouseReleased(MouseEvent e) + { + textArea.setBackground(bg); + textArea.setForeground(fg); + } + + }); + copyToClipboardButton.setToolTipText( + MessageManager.getString("label.copy_to_clipboard_tooltip")); + + JLabel logLevelLabel = new JLabel( + MessageManager.getString("label.log_level") + ":"); + + // logLevelCombo.addItem(Level.ALL); + logLevelCombo.addItem(Level.TRACE); + logLevelCombo.addItem(Level.DEBUG); + logLevelCombo.addItem(Level.INFO); + logLevelCombo.addItem(Level.WARN); + // logLevelCombo.addItem(Level.ERROR); + // logLevelCombo.addItem(Level.FATAL); + // logLevelCombo.addItem(Level.OFF); + // set startingLogLevel + startingLogLevel = Cache.log == null ? Level.INFO + : Cache.log.getLevel(); + setChosenLogLevelCombo(); + logLevelCombo.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + if (Cache.log != null) + { + Cache.log.setLevel((Level) logLevelCombo.getSelectedItem()); + } + } + + }); // frame = cpt; frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(new JScrollPane(textArea), BorderLayout.CENTER); - frame.getContentPane().add(button, BorderLayout.SOUTH); + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridBagLayout()); + + JPanel logLevelPanel = new JPanel(); + logLevelPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); + logLevelPanel.add(logLevelLabel); + logLevelPanel.add(logLevelCombo); + String logLevelTooltip = MessageManager.formatMessage( + "label.log_level_tooltip", startingLogLevel.toString()); + logLevelLabel.setToolTipText(logLevelTooltip); + logLevelCombo.setToolTipText(logLevelTooltip); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 0.1; + southPanel.add(logLevelPanel, gbc); + + gbc.gridx++; + gbc.weightx = 0.8; + gbc.fill = GridBagConstraints.HORIZONTAL; + southPanel.add(clearButton, gbc); + + gbc.gridx++; + gbc.weightx = 0.1; + gbc.fill = GridBagConstraints.NONE; + southPanel.add(copyToClipboardButton, gbc); + + southPanel.setVisible(true); + frame.getContentPane().add(southPanel, BorderLayout.SOUTH); frame.setVisible(visible); - + updateConsole = visible; frame.addWindowListener(this); - button.addActionListener(this); + clearButton.addActionListener(this); - try - { - PipedOutputStream pout = new PipedOutputStream(this.pin); - System.setOut(new PrintStream(pout, true)); - } catch (java.io.IOException io) + if (redirect) { - textArea.append("Couldn't redirect STDOUT to this console\n" - + io.getMessage()); - } catch (SecurityException se) - { - textArea.append("Couldn't redirect STDOUT to this console\n" - + se.getMessage()); + redirectStreams(); } - - try - { - PipedOutputStream pout2 = new PipedOutputStream(this.pin2); - System.setErr(new PrintStream(pout2, true)); - } catch (java.io.IOException io) + else { - textArea.append("Couldn't redirect STDERR to this console\n" - + io.getMessage()); - } catch (SecurityException se) - { - textArea.append("Couldn't redirect STDERR to this console\n" - + se.getMessage()); + unredirectStreams(); } - quit = false; // signals the Threads that they should exit // Starting two seperate threads to read from the PipedInputStreams @@ -123,19 +251,158 @@ public class Console extends WindowAdapter implements WindowListener, reader2 = new Thread(this); reader2.setDaemon(true); reader2.start(); + // and a thread to append text to the textarea + textAppender = new Thread(this); + textAppender.setDaemon(true); + textAppender.start(); } - public void test() { + + private void setChosenLogLevelCombo() + { + setChosenLogLevelCombo(startingLogLevel); + } + + private void setChosenLogLevelCombo(Level setLogLevel) + { + logLevelCombo.setSelectedItem(setLogLevel); + if (!logLevelCombo.getSelectedItem().equals(setLogLevel)) + { + // setLogLevel not (yet) in list + if (setLogLevel != null && setLogLevel instanceof Level) + { + // add new item to list (might be set via .jalview_properties) + boolean added = false; + for (int i = 0; i < logLevelCombo.getItemCount(); i++) + { + Level l = (Level) logLevelCombo.getItemAt(i); + if (l.isGreaterOrEqual(setLogLevel)) + { + logLevelCombo.insertItemAt(setLogLevel, i); + added = true; + break; + } + } + if (!added) // lower priority than others or some confusion -- add to + // end of list + { + logLevelCombo.addItem(setLogLevel); + } + logLevelCombo.setSelectedItem(setLogLevel); + } + else + { + logLevelCombo.setSelectedItem(Level.INFO); + } + } + } + + private void copyConsoleTextToClipboard() + { + String consoleText = textArea.getText(); + StringSelection consoleTextSelection = new StringSelection(consoleText); + Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); + cb.setContents(consoleTextSelection, null); + } + + PipedOutputStream pout = null, perr = null; + + public void redirectStreams() + { + if (pout == null) + { + try + { + pout = new PipedOutputStream(this.pin); + System.setOut(new PrintStream(pout, true)); + } catch (java.io.IOException io) + { + textArea.append("Couldn't redirect STDOUT to this console\n" + + io.getMessage()); + io.printStackTrace(stderr); + } catch (SecurityException se) + { + textArea.append("Couldn't redirect STDOUT to this console\n" + + se.getMessage()); + se.printStackTrace(stderr); + } + + try + { + perr = new PipedOutputStream(this.pin2); + System.setErr(new PrintStream(perr, true)); + } catch (java.io.IOException io) + { + textArea.append("Couldn't redirect STDERR to this console\n" + + io.getMessage()); + io.printStackTrace(stderr); + } catch (SecurityException se) + { + textArea.append("Couldn't redirect STDERR to this console\n" + + se.getMessage()); + se.printStackTrace(stderr); + } + } + } + + public void unredirectStreams() + { + if (pout != null) + { + try + { + System.setOut(stdout); + pout.flush(); + pout.close(); + pin = new PipedInputStream(); + pout = null; + } catch (java.io.IOException io) + { + textArea.append("Couldn't unredirect STDOUT to this console\n" + + io.getMessage()); + io.printStackTrace(stderr); + } catch (SecurityException se) + { + textArea.append("Couldn't unredirect STDOUT to this console\n" + + se.getMessage()); + se.printStackTrace(stderr); + } + + try + { + System.setErr(stderr); + perr.flush(); + perr.close(); + pin2 = new PipedInputStream(); + perr = null; + } catch (java.io.IOException io) + { + textArea.append("Couldn't unredirect STDERR to this console\n" + + io.getMessage()); + io.printStackTrace(stderr); + } catch (SecurityException se) + { + textArea.append("Couldn't unredirect STDERR to this console\n" + + se.getMessage()); + se.printStackTrace(stderr); + } + } + } + + public void test() + { // testing part // you may omit this part for your application - // - + // + System.out.println("Hello World 2"); System.out.println("All fonts available to Graphic2D:\n"); GraphicsEnvironment ge = GraphicsEnvironment .getLocalGraphicsEnvironment(); String[] fontNames = ge.getAvailableFontFamilyNames(); for (int n = 0; n < fontNames.length; n++) + { System.out.println(fontNames[n]); + } // Testing part: simple an error thrown anywhere in this JVM will be printed // on the Console // We do it with a seperate Thread becasue we don't wan't to break a Thread @@ -150,10 +417,14 @@ public class Console extends WindowAdapter implements WindowListener, { JFrame frame = new JFrame(string); frame.setName(string); - if (x==-1) - x = (int) (i / 2); - if (y==-1) - y = (int) (j / 2); + if (x == -1) + { + x = i / 2; + } + if (y == -1) + { + y = j / 2; + } frame.setBounds(x, y, i, j); return frame; } @@ -165,16 +436,32 @@ public class Console extends WindowAdapter implements WindowListener, */ public Console(Desktop desktop) { + this(desktop, true); + } + + /** + * attach a console to the desktop - the desktop will open it if requested. + * + * @param desktop + * @param showjconsole + * - if true, then redirect stdout immediately + */ + public Console(Desktop desktop, boolean showjconsole) + { parent = desktop; // window name - get x,y,width, height possibly scaled Rectangle bounds = desktop.getLastKnownDimensions("JAVA_CONSOLE_"); - if (bounds==null) + if (bounds == null) { - frame = initFrame("Jalview Java Console", desktop.getWidth() / 2, - desktop.getHeight() / 4,desktop.getX(),desktop.getY()); - } else { - frame = initFrame("Jalview Java Console", bounds.width, bounds.height, bounds.x, bounds.y); + frame = initFrame("Jalview Java Console", desktop.getWidth() / 2, + desktop.getHeight() / 4, desktop.getX(), desktop.getY()); } + else + { + frame = initFrame("Jalview Java Console", bounds.width, bounds.height, + bounds.x, bounds.y); + } + frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); // desktop.add(frame); initConsole(false); JalviewAppender jappender = new JalviewAppender(); @@ -186,93 +473,209 @@ public class Console extends WindowAdapter implements WindowListener, public synchronized void stopConsole() { quit = true; - this.notifyAll(); // stop all threads - try - { - reader.join(10); - pin.close(); - } catch (Exception e) + this.notifyAll(); + /* + * reader.notify(); reader2.notify(); if (errorThrower!=null) + * errorThrower.notify(); // stop all threads if (textAppender!=null) + * textAppender.notify(); + */ + if (pout != null) { + try + { + reader.join(10); + pin.close(); + } catch (Exception e) + { + } + try + { + reader2.join(10); + pin2.close(); + } catch (Exception e) + { + } + try + { + textAppender.join(10); + } catch (Exception e) + { + } } - try - { - reader2.join(10); - pin2.close(); - } catch (Exception e) + if (!frame.isVisible()) { + frame.dispose(); } -// System.exit(0); + // System.exit(0); } + @Override public synchronized void windowClosed(WindowEvent evt) { frame.setVisible(false); + closeConsoleGui(); + } + + private void closeConsoleGui() + { + updateConsole = false; if (parent == null) { stopConsole(); - } else { + } + else + { parent.showConsole(false); } } + @Override public synchronized void windowClosing(WindowEvent evt) { frame.setVisible(false); // default behaviour of JFrame -// frame.dispose(); + closeConsoleGui(); + + // frame.dispose(); } + @Override public synchronized void actionPerformed(ActionEvent evt) { - textArea.setText(""); + trimBuffer(true); + // textArea.setText(""); } - int lines=0,lim=1000; + + @Override public synchronized void run() { try { while (Thread.currentThread() == reader) { - try - { - this.wait(100); - } catch (InterruptedException ie) + if (pin == null || pin.available() == 0) { + try + { + this.wait(100); + if (pin.available() == 0) + { + trimBuffer(false); + } + } catch (InterruptedException ie) + { + } } - if (pin.available() != 0) + + while (pin.available() != 0) { String input = this.readLine(pin); stdout.print(input); - textArea.append(input); - lines++; + long time = System.nanoTime(); + appendToTextArea(input); + // stderr.println("Time taken to stdout append:\t" + // + (System.nanoTime() - time) + " ns"); + // lines++; } if (quit) + { return; + } } while (Thread.currentThread() == reader2) { - try - { - this.wait(100); - } catch (InterruptedException ie) + if (pin2.available() == 0) { + try + { + this.wait(100); + if (pin2.available() == 0) + { + trimBuffer(false); + } + } catch (InterruptedException ie) + { + } } - if (pin2.available() != 0) + while (pin2.available() != 0) { String input = this.readLine(pin2); stderr.print(input); - textArea.append(input); - lines++; + long time = System.nanoTime(); + appendToTextArea(input); + // stderr.println("Time taken to stderr append:\t" + // + (System.nanoTime() - time) + " ns"); + // lines++; } if (quit) + { return; + } + } + while (Thread.currentThread() == textAppender) + { + if (updateConsole) + { + // check string buffer - if greater than console, clear console and + // replace with last segment of content, otherwise, append all to + // content. + long count; + while (displayPipe.length() > 0) + { + count = 0; + StringBuffer tmp = new StringBuffer(), replace; + synchronized (displayPipe) + { + replace = displayPipe; + displayPipe = tmp; + } + // simply append whole buffer + textArea.append(replace.toString()); + count += replace.length(); + if (count > byteslim) + { + trimBuffer(false); + } + } + if (displayPipe.length() == 0) + { + try + { + this.wait(100); + if (displayPipe.length() == 0) + { + trimBuffer(false); + } + } catch (InterruptedException e) + { + } + } + } + else + { + try + { + this.wait(100); + } catch (InterruptedException e) + { + + } + } + if (quit) + { + return; + } + } } catch (Exception e) { textArea.append("\nConsole reports an Internal error."); - textArea.append("The error is: " + e); - lines+=2; + textArea.append("The error is: " + e.getMessage()); + // Need to uncomment this to ensure that line tally is synched. + // lines += 2; + stderr.println( + "Console reports an Internal error.\nThe error is: " + e); } // just for testing (Throw a Nullpointer after 1 second) @@ -285,19 +688,75 @@ public class Console extends WindowAdapter implements WindowListener, { } throw new NullPointerException( - "Application test: throwing an NullPointerException It should arrive at the console"); + MessageManager.getString("exception.application_test_npe")); + } + } + + private void appendToTextArea(final String input) + { + if (updateConsole == false) + { + // do nothing; + return; + } + long time = System.nanoTime(); + javax.swing.SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + displayPipe.append(input); // change to stringBuffer + // displayPipe.flush(); + + } + }); + // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() - + // time) + // + " ns"); + } + + private String header = null; + + private boolean updateConsole = false; + + private synchronized void trimBuffer(boolean clear) + { + if (header == null && textArea.getLineCount() > 5) + { + try + { + header = textArea.getText(0, textArea.getLineStartOffset(5)) + + "\nTruncated...\n"; + } catch (Exception e) + { + e.printStackTrace(); + } } // trim the buffer - if (lines>lim) + int tlength = textArea.getDocument().getLength(); + if (header != null) { - try { - String header = textArea.getText(0,textArea.getLineEndOffset(5))+"\n..Truncated..\n"; // keep first 5 lines for startup info - int truncate = textArea.getLineEndOffset(lim-7-lines); - textArea.setText(header+textArea.getText(truncate,textArea.getText().length()-truncate)); } catch (Exception e) + if (clear || (tlength > byteslim)) + { + try + { + if (!clear) + { + long time = System.nanoTime(); + textArea.replaceRange(header, 0, tlength - bytescut); + // stderr.println("Time taken to cut:\t" + // + (System.nanoTime() - time) + " ns"); + } + else + { + textArea.setText(header); + } + } catch (Exception e) { e.printStackTrace(); } - lines = textArea.getLineCount(); + // lines = textArea.getLineCount(); + } } } @@ -306,22 +765,34 @@ public class Console extends WindowAdapter implements WindowListener, throws IOException { String input = ""; + int lp = -1; do { int available = in.available(); if (available == 0) + { break; + } byte b[] = new byte[available]; in.read(b); input = input + new String(b, 0, b.length); + // counts lines - we don't do this for speed. + // while ((lp = input.indexOf("\n", lp + 1)) > -1) + // { + // lines++; + // } } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit); return input; } + /** + * @j2sIgnore + * @param arg + */ public static void main(String[] arg) { new Console().test(); // create console with not reference - + } public void setVisible(boolean selected) @@ -329,16 +800,55 @@ public class Console extends WindowAdapter implements WindowListener, frame.setVisible(selected); if (selected == true) { + setChosenLogLevelCombo(); + redirectStreams(); + updateConsole = true; frame.toFront(); - } + } + else + { + // reset log level to what it was before + if (Cache.log != null) + { + Cache.log.setLevel(startingLogLevel); + } + + unredirectStreams(); + updateConsole = false; + } } public Rectangle getBounds() { - if (frame!=null) + if (frame != null) { return frame.getBounds(); } return null; } + + /** + * set the banner that appears at the top of the console output + * + * @param string + */ + public void setHeader(String string) + { + header = string; + if (header.charAt(header.length() - 1) != '\n') + { + header += "\n"; + } + textArea.insert(header, 0); + } + + /** + * get the banner + * + * @return + */ + public String getHeader() + { + return header; + } }