mirror of
https://github.com/arduino/Arduino.git
synced 2024-11-29 10:24:12 +01:00
419 lines
13 KiB
Java
419 lines
13 KiB
Java
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-05 Ben Fry and Casey Reas
|
|
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
|
|
|
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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
package processing.app;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.io.*;
|
|
import javax.swing.*;
|
|
import javax.swing.text.*;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Message console that sits below the editing area.
|
|
* <P>
|
|
* 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.
|
|
*/
|
|
public class EditorConsole extends JScrollPane {
|
|
Editor editor;
|
|
|
|
JTextPane consoleTextPane;
|
|
BufferedStyledDocument consoleDoc;
|
|
|
|
MutableAttributeSet stdStyle;
|
|
MutableAttributeSet errStyle;
|
|
|
|
boolean cerror;
|
|
|
|
//int maxCharCount;
|
|
int maxLineCount;
|
|
|
|
static File errFile;
|
|
static File outFile;
|
|
static File tempFolder;
|
|
|
|
static PrintStream systemOut;
|
|
static PrintStream systemErr;
|
|
|
|
static PrintStream consoleOut;
|
|
static PrintStream consoleErr;
|
|
|
|
static OutputStream stdoutFile;
|
|
static OutputStream stderrFile;
|
|
|
|
|
|
public EditorConsole(Editor editor) {
|
|
this.editor = editor;
|
|
|
|
maxLineCount = Preferences.getInteger("console.length");
|
|
|
|
consoleDoc = new BufferedStyledDocument(10000, maxLineCount);
|
|
consoleTextPane = new JTextPane(consoleDoc);
|
|
consoleTextPane.setEditable(false);
|
|
|
|
// necessary?
|
|
MutableAttributeSet standard = new SimpleAttributeSet();
|
|
StyleConstants.setAlignment(standard, StyleConstants.ALIGN_LEFT);
|
|
consoleDoc.setParagraphAttributes(0, 0, standard, true);
|
|
|
|
// build styles for different types of console output
|
|
Color bgColor = Preferences.getColor("console.color");
|
|
Color fgColorOut = Preferences.getColor("console.output.color");
|
|
Color fgColorErr = Preferences.getColor("console.error.color");
|
|
Font font = Preferences.getFont("console.font");
|
|
|
|
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());
|
|
|
|
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.setBackground(bgColor);
|
|
|
|
// add the jtextpane to this scrollpane
|
|
this.setViewportView(consoleTextPane);
|
|
|
|
// calculate height of a line of text in pixels
|
|
// and size window accordingly
|
|
FontMetrics metrics = this.getFontMetrics(font);
|
|
int height = metrics.getAscent() + metrics.getDescent();
|
|
int lines = Preferences.getInteger("console.lines"); //, 4);
|
|
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));
|
|
|
|
if (systemOut == null) {
|
|
systemOut = System.out;
|
|
systemErr = System.err;
|
|
|
|
tempFolder = Base.createTempFolder("console");
|
|
try {
|
|
String outFileName = Preferences.get("console.output.file");
|
|
if (outFileName != null) {
|
|
outFile = new File(tempFolder, outFileName);
|
|
stdoutFile = new FileOutputStream(outFile);
|
|
//outFile.deleteOnExit();
|
|
}
|
|
|
|
String errFileName = Preferences.get("console.error.file");
|
|
if (errFileName != null) {
|
|
errFile = new File(tempFolder, errFileName);
|
|
stderrFile = new FileOutputStream(errFile);
|
|
//errFile.deleteOnExit();
|
|
}
|
|
} catch (IOException e) {
|
|
Base.showWarning("Console Error",
|
|
"A problem occurred while trying to open the\n" +
|
|
"files used to store the console output.", e);
|
|
}
|
|
|
|
consoleOut =
|
|
new PrintStream(new EditorConsoleStream(this, false, stdoutFile));
|
|
consoleErr =
|
|
new PrintStream(new EditorConsoleStream(this, true, stderrFile));
|
|
|
|
if (Preferences.getBoolean("console")) {
|
|
try {
|
|
System.setOut(consoleOut);
|
|
System.setErr(consoleErr);
|
|
} catch (Exception e) {
|
|
e.printStackTrace(systemOut);
|
|
}
|
|
}
|
|
}
|
|
|
|
// to fix ugliness.. normally macosx java 1.3 puts an
|
|
// ugly white border around this object, so turn it off.
|
|
if (Base.isMacOS()) {
|
|
setBorder(null);
|
|
}
|
|
|
|
// periodically post buffered messages to the console
|
|
// should the interval come from the preferences file?
|
|
new javax.swing.Timer(250, new ActionListener() {
|
|
public void actionPerformed(ActionEvent evt) {
|
|
// only if new text has been added
|
|
if (consoleDoc.hasAppendage) {
|
|
// 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();
|
|
}
|
|
|
|
|
|
/**
|
|
* Close the streams so that the temporary files can be deleted.
|
|
* <p/>
|
|
* File.deleteOnExit() cannot be used because the stdout and stderr
|
|
* files are inside a folder, and have to be deleted before the
|
|
* folder itself is deleted, which can't be guaranteed when using
|
|
* the deleteOnExit() method.
|
|
*/
|
|
public void handleQuit() {
|
|
// replace original streams to remove references to console's streams
|
|
System.setOut(systemOut);
|
|
System.setErr(systemErr);
|
|
|
|
// close the PrintStream
|
|
consoleOut.close();
|
|
consoleErr.close();
|
|
|
|
// also have to close the original FileOutputStream
|
|
// otherwise it won't be shut down completely
|
|
try {
|
|
stdoutFile.close();
|
|
stderrFile.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace(systemOut);
|
|
}
|
|
|
|
outFile.delete();
|
|
errFile.delete();
|
|
tempFolder.delete();
|
|
}
|
|
|
|
|
|
public void write(byte b[], int offset, int length, boolean err) {
|
|
if (err != cerror) {
|
|
// advance the line because switching between err/out streams
|
|
// potentially, could check whether we're already on a new line
|
|
message("", cerror, true);
|
|
}
|
|
|
|
// we could do some cross platform CR/LF mangling here before outputting
|
|
|
|
// add text to output document
|
|
message(new String(b, offset, length), err, false);
|
|
// set last error state
|
|
cerror = err;
|
|
}
|
|
|
|
|
|
// added sync for 0091.. not sure if it helps or hinders
|
|
synchronized public void message(String what, boolean err, boolean advance) {
|
|
if (err) {
|
|
systemErr.print(what);
|
|
} else {
|
|
systemOut.print(what);
|
|
}
|
|
|
|
if (advance) {
|
|
appendText("\n", err);
|
|
if (err) {
|
|
systemErr.println();
|
|
} else {
|
|
systemOut.println();
|
|
}
|
|
}
|
|
|
|
// to console display
|
|
appendText(what, err);
|
|
// moved down here since something is punting
|
|
}
|
|
|
|
|
|
/**
|
|
* append a piece of text to the console.
|
|
* <P>
|
|
* 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.
|
|
* <P>
|
|
* Updates are buffered to the console and displayed at regular
|
|
* intervals on Swing's event-dispatching thread. (patch by David Mellis)
|
|
*/
|
|
synchronized private void appendText(String txt, boolean e) {
|
|
consoleDoc.appendString(txt, e ? errStyle : stdStyle);
|
|
}
|
|
|
|
public void clear() {
|
|
try {
|
|
consoleDoc.remove(0, consoleDoc.getLength());
|
|
} catch (BadLocationException e) {
|
|
// ignore the error otherwise this will cause an infinite loop
|
|
// maybe not a good idea in the long run?
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class EditorConsoleStream extends OutputStream {
|
|
EditorConsole parent;
|
|
boolean err; // whether stderr or stdout
|
|
byte single[] = new byte[1];
|
|
OutputStream echo;
|
|
|
|
public EditorConsoleStream(EditorConsole parent,
|
|
boolean err, OutputStream echo) {
|
|
this.parent = parent;
|
|
this.err = err;
|
|
this.echo = echo;
|
|
}
|
|
|
|
public void close() { }
|
|
|
|
public void flush() { }
|
|
|
|
public void write(byte b[]) { // appears never to be used
|
|
parent.write(b, 0, b.length, err);
|
|
if (echo != null) {
|
|
try {
|
|
echo.write(b); //, 0, b.length);
|
|
echo.flush();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
echo = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void write(byte b[], int offset, int length) {
|
|
parent.write(b, offset, length, err);
|
|
if (echo != null) {
|
|
try {
|
|
echo.write(b, offset, length);
|
|
echo.flush();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
echo = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void write(int b) {
|
|
single[0] = (byte)b;
|
|
parent.write(single, 0, 1, err);
|
|
if (echo != null) {
|
|
try {
|
|
echo.write(b);
|
|
echo.flush();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
echo = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
class BufferedStyledDocument extends DefaultStyledDocument {
|
|
ArrayList elements = new ArrayList();
|
|
int maxLineLength, maxLineCount;
|
|
int currentLineLength = 0;
|
|
boolean needLineBreak = false;
|
|
boolean hasAppendage = false;
|
|
|
|
public BufferedStyledDocument(int maxLineLength, int maxLineCount) {
|
|
this.maxLineLength = maxLineLength;
|
|
this.maxLineCount = maxLineCount;
|
|
}
|
|
|
|
/** buffer a string for insertion at the end of the DefaultStyledDocument */
|
|
public synchronized void appendString(String str, AttributeSet a) {
|
|
// do this so that it's only updated when needed (otherwise console
|
|
// updates every 250 ms when an app isn't even running.. see bug 180)
|
|
hasAppendage = true;
|
|
|
|
// process each line of the string
|
|
while (str.length() > 0) {
|
|
// newlines within an element have (almost) no effect, so we need to
|
|
// replace them with proper paragraph breaks (start and end tags)
|
|
if (needLineBreak || currentLineLength > maxLineLength) {
|
|
elements.add(new ElementSpec(a, ElementSpec.EndTagType));
|
|
elements.add(new ElementSpec(a, ElementSpec.StartTagType));
|
|
currentLineLength = 0;
|
|
}
|
|
|
|
if (str.indexOf('\n') == -1) {
|
|
elements.add(new ElementSpec(a, ElementSpec.ContentType,
|
|
str.toCharArray(), 0, str.length()));
|
|
currentLineLength += str.length();
|
|
needLineBreak = false;
|
|
str = str.substring(str.length()); // eat the string
|
|
} else {
|
|
elements.add(new ElementSpec(a, ElementSpec.ContentType,
|
|
str.toCharArray(), 0, str.indexOf('\n') + 1));
|
|
needLineBreak = true;
|
|
str = str.substring(str.indexOf('\n') + 1); // eat the line
|
|
}
|
|
}
|
|
}
|
|
|
|
/** insert the buffered strings */
|
|
public synchronized void insertAll() {
|
|
ElementSpec[] elementArray = new ElementSpec[elements.size()];
|
|
elements.toArray(elementArray);
|
|
|
|
try {
|
|
// check how many lines have been used so far
|
|
// if too many, shave off a few lines from the beginning
|
|
Element element = super.getDefaultRootElement();
|
|
int lineCount = element.getElementCount();
|
|
int overage = lineCount - maxLineCount;
|
|
if (overage > 0) {
|
|
// if 1200 lines, and 1000 lines is max,
|
|
// find the position of the end of the 200th line
|
|
//systemOut.println("overage is " + overage);
|
|
Element lineElement = element.getElement(overage);
|
|
if (lineElement == null) return; // do nuthin
|
|
|
|
int endOffset = lineElement.getEndOffset();
|
|
// remove to the end of the 200th line
|
|
super.remove(0, endOffset);
|
|
}
|
|
super.insert(super.getLength(), elementArray);
|
|
|
|
} catch (BadLocationException e) {
|
|
// ignore the error otherwise this will cause an infinite loop
|
|
// maybe not a good idea in the long run?
|
|
}
|
|
elements.clear();
|
|
hasAppendage = false;
|
|
}
|
|
}
|