/*- * Copyright (C) 2008 Erik Larsson * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.catacombae.hfsexplorer.io; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.GapContent; import javax.swing.text.PlainDocument; import org.catacombae.util.Util; /** * An implementation of OutputStream that writes all output to a JTextArea, decoded with the * standard platform encoding, or a user supplied one. * * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a> */ public class JTextAreaOutputStream extends OutputStream { /** * Maximum size of the document. 1000 filled lines 80 characters wide => 80000*2 bytes = * 160000 bytes (not considering possible UTF-16 surrogate pairs). */ private static final int MAX_LENGTH = /*10;//*/80*1000; private final PrintStream stdErr; private final JTextArea textArea; private final JScrollPane textAreaScroller; private final Object syncObject; private final String encoding; private final GapContent content; private boolean updateRequested = false; private PlainDocument document; /** * Creates a new JTextAreaOutputStream which writes to <code>textArea</code> and synchronizes * all writes using <code>synchronized(textArea) { ... }</code> statements. * * @param textArea the text area to write to (non-null). */ public JTextAreaOutputStream(PrintStream stdErr, JTextArea textArea) { this(stdErr, textArea, textArea); } /** * Creates a new JTextAreaOutputStream which writes to <code>textArea</code> and synchronizes * all writes using <code>synchronized(syncObject) { ... }</code> statements. * * @param textArea the text area to write to (non-null). * @param syncObject the object to synchronize on (non-null). */ public JTextAreaOutputStream(PrintStream stdErr, JTextArea textArea, Object syncObject) { this(stdErr, textArea, syncObject, null); } /** * Creates a new JTextAreaOutputStream which writes to <code>textArea</code> and synchronizes * all writes using <code>synchronized(syncObject) { ... }</code> statements. * * @param textArea the text area to write to (non-null). * @param syncObject the object to synchronize on (non-null). * @param encoding the encoding to use when decoding the stream data into Unicode characters. */ public JTextAreaOutputStream(PrintStream stdErr, JTextArea textArea, Object syncObject, String encoding) { this(stdErr, textArea, null, syncObject, encoding); } /** * Creates a new JTextAreaOutputStream which writes to <code>textArea</code> and synchronizes * all writes using <code>synchronized(syncObject) { ... }</code> statements. In addition, when * updating the JTextArea, this constructor makes JTextAreaOutputStream adjust its JScrollPane, * <code>textAreaScroller</code>, accordingly so that the view always follows the latest written * text. * * @param stdErr a reliable System.err stream where this stream can write error messages. * @param textArea the text area to write to (non-null). * @param textAreaScroller the scroll pane to adjust when updating <code>textArea</code>. * @param syncObject the object to synchronize on (non-null). * @param encoding the encoding to use when decoding the stream data into Unicode characters. */ public JTextAreaOutputStream(PrintStream stdErr, JTextArea textArea, JScrollPane textAreaScroller, Object syncObject, String encoding) { if(stdErr == null) throw new IllegalArgumentException("stdErr == null"); if(textArea == null) throw new IllegalArgumentException("textArea == null"); if(syncObject == null) throw new IllegalArgumentException("syncObject == null"); this.stdErr = stdErr; this.textArea = textArea; this.textAreaScroller = textAreaScroller; this.syncObject = syncObject; this.encoding = encoding; this.content = new GapContent(); this.document = new PlainDocument(content); textArea.setDocument(document); } /** * {@inheritDoc} */ @Override public void write(int b) throws IOException { this.write(new byte[]{(byte) b}, 0, 1); } /** * {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { this.write(b, 0, b.length); } //private StringBuilder curBuilder = new StringBuilder(); /** * {@inheritDoc} */ @Override public void write(byte[] b, int off, int len) throws IOException { synchronized(syncObject) { try { String s; if(encoding == null) s = new String(b, off, len); else s = new String(b, off, len, encoding); try { //textArea.append(s); //curBuilder.append(s); if(s.length() > MAX_LENGTH) { //stdErr.print("adjusting s.length() from " + s.length()); s = s.substring(s.length()-MAX_LENGTH); //stdErr.println(" to " + s.length()); } int overrun = (document.getLength()-2+s.length()) - MAX_LENGTH; //stdErr.println("overrun=" + overrun); //stdErr.println("document.getLength()=" + document.getLength()); //stdErr.println("s.length()=" + s.length()); //stdErr.println("MAX_LENGTH=" + MAX_LENGTH); if(overrun > 0) { //content.remove(0, overrun); //stdErr.println("Removing " + overrun + " bytes at the start."); document.remove(0, overrun); } //content.insertString(content.length() - 1, s); //stdErr.println("insertString(" + (document.getLength()) + ", \"" + s + "\", null);"); document.insertString(document.getLength(), s, null); } catch(BadLocationException ex) { throw new RuntimeException("Exception while updating content", ex); } if(textAreaScroller != null && !updateRequested) { updateRequested = true; SwingUtilities.invokeLater(new Runnable() { /* @Override */ public void run() { synchronized(syncObject) { //textArea.append(curBuilder.toString()); //curBuilder.setLength(0); updateRequested = false; JScrollBar sb = textAreaScroller.getVerticalScrollBar(); sb.setValue(sb.getMaximum() - sb.getVisibleAmount()); } //textArea.append(s); //textArea.append(" [Update!] "); } }); } } catch(Exception e) { StringBuilder sb = new StringBuilder(); Util.buildStackTrace(e, Integer.MAX_VALUE, sb); stdErr.println(sb.toString()); //GUIUtil.displayExceptionDialog(e, 100, null); } } } }