diff --git a/app/src/cc/arduino/ConsoleOutputStream.java b/app/src/cc/arduino/ConsoleOutputStream.java new file mode 100644 index 000000000..b44603984 --- /dev/null +++ b/app/src/cc/arduino/ConsoleOutputStream.java @@ -0,0 +1,132 @@ +/* + * This file is part of Arduino. + * + * Arduino 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 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. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Original version of this file courtesy of Rob Camick + *
+ * https://tips4java.wordpress.com/2008/11/08/message-console/ + *
+ * About page at https://tips4java.wordpress.com/about/ says something + * like MIT + */ + +package cc.arduino; + +import processing.app.EditorConsole; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.SimpleAttributeSet; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +/* + * Class to intercept output from a PrintStream and add it to a Document. + * The output can optionally be redirected to a different PrintStream. + * The text displayed in the Document can be color coded to indicate + * the output source. + */ +public class ConsoleOutputStream extends ByteArrayOutputStream { + + private final SimpleAttributeSet attributes; + private final PrintStream printStream; + private final StringBuilder buffer; + private final Timer timer; + private JScrollPane scrollPane; + private Document document; + + public ConsoleOutputStream(SimpleAttributeSet attributes, PrintStream printStream) { + this.attributes = attributes; + this.printStream = printStream; + this.buffer = new StringBuilder(); + + this.timer = new Timer(100, (e) -> { + if (scrollPane != null) { + synchronized (scrollPane) { + scrollPane.getHorizontalScrollBar().setValue(0); + scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum()); + } + } + }); + timer.setRepeats(false); + } + + public synchronized void setCurrentEditorConsole(EditorConsole console) { + this.scrollPane = console; + this.document = console.getDocument(); + } + + public synchronized void flush() { + String message = toString(); + + if (message.length() == 0) { + return; + } + + handleAppend(message); + + reset(); + } + + private void handleAppend(String message) { + resetBufferIfDocumentEmpty(); + + buffer.append(message); + + clearBuffer(); + } + + private void resetBufferIfDocumentEmpty() { + if (document != null && document.getLength() == 0) { + buffer.setLength(0); + } + } + + private void clearBuffer() { + String line = buffer.toString(); + buffer.setLength(0); + + printStream.print(line); + + if (document != null) { + SwingUtilities.invokeLater(() -> { + try { + String lineWithoutSlashR = line.replace("\r\n", "\n").replace("\r", "\n"); + int offset = document.getLength(); + document.insertString(offset, lineWithoutSlashR, attributes); + } catch (BadLocationException ble) { + //ignore + } + }); + + if (!timer.isRunning()) { + timer.restart(); + } + } + } +} diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index ba95406e3..19782e96a 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -641,7 +641,7 @@ public class Base { } // set the current window to be the console that's getting output - EditorConsoleStream.setCurrent(activeEditor.console); + EditorConsole.setCurrentEditorConsole(activeEditor.console); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index de5688bd0..8871a214a 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -264,7 +264,7 @@ public class Editor extends JFrame implements RunnerListener { status = new EditorStatus(this); consolePanel.add(status, BorderLayout.NORTH); - console = new EditorConsole(this); + console = new EditorConsole(); console.setName("console"); // windows puts an ugly border on this guy console.setBorder(null); diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java index a2116f8b4..b922b1b2d 100644 --- a/app/src/processing/app/EditorConsole.java +++ b/app/src/processing/app/EditorConsole.java @@ -21,236 +21,106 @@ package processing.app; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JScrollPane; -import javax.swing.JTextPane; -import javax.swing.SwingUtilities; -import javax.swing.Timer; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DefaultStyledDocument; -import javax.swing.text.Element; -import javax.swing.text.SimpleAttributeSet; -import javax.swing.text.StyleConstants; - -import processing.app.helpers.OSUtils; +import cc.arduino.ConsoleOutputStream; +import javax.swing.*; +import javax.swing.text.*; +import java.awt.*; +import java.io.PrintStream; /** * Message console that sits below the editing area. - *
- * Debugging this class is tricky... If it's throwing exceptions, - * don't take over System.err, and debug while watching just System.out - * or just write println() or whatever directly to systemOut or systemErr. */ -@SuppressWarnings("serial") public class EditorConsole extends JScrollPane { - Editor editor; - JTextPane consoleTextPane; - BufferedStyledDocument consoleDoc; + private static ConsoleOutputStream out; + private static ConsoleOutputStream err; - SimpleAttributeSet stdStyle; - SimpleAttributeSet errStyle; + public static synchronized void init(SimpleAttributeSet outStyle, PrintStream outStream, SimpleAttributeSet errStyle, PrintStream errStream) { + if (out != null) { + return; + } - // Single static instance shared because there's only one real System.out. - // Within the input handlers, the currentConsole variable will be used to - // echo things to the correct location. - - public EditorConsole(Editor _editor) { - editor = _editor; + out = new ConsoleOutputStream(outStyle, outStream); + System.setOut(new PrintStream(out, true)); - int maxLineCount = PreferencesData.getInteger("console.length"); + err = new ConsoleOutputStream(errStyle, errStream); + System.setErr(new PrintStream(err, true)); + } - consoleDoc = new BufferedStyledDocument(4000, maxLineCount); - consoleTextPane = new JTextPane(consoleDoc); + public static void setCurrentEditorConsole(EditorConsole console) { + out.setCurrentEditorConsole(console); + err.setCurrentEditorConsole(console); + } + + private final DefaultStyledDocument document; + private final JTextPane consoleTextPane; + + public EditorConsole() { + document = new DefaultStyledDocument(); + + consoleTextPane = new JTextPane(document); consoleTextPane.setEditable(false); + DefaultCaret caret = (DefaultCaret) consoleTextPane.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - // necessary? - SimpleAttributeSet leftAlignAttr = new SimpleAttributeSet(); - StyleConstants.setAlignment(leftAlignAttr, StyleConstants.ALIGN_LEFT); - consoleDoc.setParagraphAttributes(0, 0, leftAlignAttr, true); + Color backgroundColour = Theme.getColor("console.color"); + consoleTextPane.setBackground(backgroundColour); - // build styles for different types of console output - Color bgColor = Theme.getColor("console.color"); - Color fgColorOut = Theme.getColor("console.output.color"); - Color fgColorErr = Theme.getColor("console.error.color"); Font consoleFont = Theme.getFont("console.font"); Font editorFont = PreferencesData.getFont("editor.font"); - Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()); + Font actualFont = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()); - stdStyle = new SimpleAttributeSet(); - StyleConstants.setForeground(stdStyle, fgColorOut); - StyleConstants.setBackground(stdStyle, bgColor); - StyleConstants.setFontSize(stdStyle, font.getSize()); - StyleConstants.setFontFamily(stdStyle, font.getFamily()); - StyleConstants.setBold(stdStyle, font.isBold()); - StyleConstants.setItalic(stdStyle, font.isItalic()); + SimpleAttributeSet stdOutStyle = new SimpleAttributeSet(); + StyleConstants.setForeground(stdOutStyle, Theme.getColor("console.output.color")); + StyleConstants.setBackground(stdOutStyle, backgroundColour); + StyleConstants.setFontSize(stdOutStyle, actualFont.getSize()); + StyleConstants.setFontFamily(stdOutStyle, actualFont.getFamily()); + StyleConstants.setBold(stdOutStyle, actualFont.isBold()); + StyleConstants.setItalic(stdOutStyle, actualFont.isItalic()); - errStyle = new SimpleAttributeSet(); - StyleConstants.setForeground(errStyle, fgColorErr); - StyleConstants.setBackground(errStyle, bgColor); - StyleConstants.setFontSize(errStyle, font.getSize()); - StyleConstants.setFontFamily(errStyle, font.getFamily()); - StyleConstants.setBold(errStyle, font.isBold()); - StyleConstants.setItalic(errStyle, font.isItalic()); + consoleTextPane.setParagraphAttributes(stdOutStyle, true); - consoleTextPane.setBackground(bgColor); + SimpleAttributeSet stdErrStyle = new SimpleAttributeSet(); + StyleConstants.setForeground(stdErrStyle, Theme.getColor("console.error.color")); + StyleConstants.setBackground(stdErrStyle, backgroundColour); + StyleConstants.setFontSize(stdErrStyle, actualFont.getSize()); + StyleConstants.setFontFamily(stdErrStyle, actualFont.getFamily()); + StyleConstants.setBold(stdErrStyle, actualFont.isBold()); + StyleConstants.setItalic(stdErrStyle, actualFont.isItalic()); - // add the jtextpane to this scrollpane - setViewportView(consoleTextPane); + JPanel noWrapPanel = new JPanel(new BorderLayout()); + noWrapPanel.add(consoleTextPane); + + setViewportView(noWrapPanel); + getVerticalScrollBar().setUnitIncrement(7); // calculate height of a line of text in pixels // and size window accordingly - FontMetrics metrics = getFontMetrics(font); + FontMetrics metrics = getFontMetrics(actualFont); int height = metrics.getAscent() + metrics.getDescent(); int lines = PreferencesData.getInteger("console.lines"); int sizeFudge = 6; //10; // unclear why this is necessary, but it is setPreferredSize(new Dimension(1024, (height * lines) + sizeFudge)); - setMinimumSize(new Dimension(1024, (height * 4) + sizeFudge)); + setMinimumSize(new Dimension(1024, (height * 5) + sizeFudge)); - EditorConsoleStream.init(); - - // to fix ugliness.. normally macosx java 1.3 puts an - // ugly white border around this object, so turn it off. - if (OSUtils.isMacOS()) { - setBorder(null); - } - - // periodically post buffered messages to the console - // should the interval come from the preferences file? - new Timer(250, new ActionListener() { - public void actionPerformed(ActionEvent evt) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - // only if new text has been added - if (consoleDoc.isChanged()) { - // insert the text that's been added in the meantime - consoleDoc.insertAll(); - // always move to the end of the text as it's added - consoleTextPane.setCaretPosition(consoleDoc.getLength()); - } - } - }); - } - }).start(); + EditorConsole.init(stdOutStyle, System.out, stdErrStyle, System.err); } - - /** - * Append a piece of text to the console. - *
- * Swing components are NOT thread-safe, and since the MessageSiphon - * instantiates new threads, and in those callbacks, they often print - * output to stdout and stderr, which are wrapped by EditorConsoleStream - * and eventually leads to EditorConsole.appendText(), which directly - * updates the Swing text components, causing deadlock. - *
- * Updates are buffered to the console and displayed at regular
- * intervals on Swing's event-dispatching thread. (patch by David Mellis)
- */
- synchronized void appendText(String txt, boolean e) {
- consoleDoc.appendString(txt, e ? errStyle : stdStyle);
- }
-
-
public void clear() {
try {
- consoleDoc.remove(0, consoleDoc.getLength());
+ document.remove(0, document.getLength());
} catch (BadLocationException e) {
// ignore the error otherwise this will cause an infinite loop
// maybe not a good idea in the long run?
}
}
-}
-
-// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
-/**
- * Buffer updates to the console and output them in batches. For info, see:
- * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer and
- * http://javatechniques.com/public/java/docs/gui/jtextpane-speed-part2.html
- * appendString() is called from multiple threads, and insertAll from the
- * swing event thread, so they need to be synchronized
- */
-@SuppressWarnings("serial")
-class BufferedStyledDocument extends DefaultStyledDocument {
- private List