1
0
mirror of https://github.com/arduino/Arduino.git synced 2025-02-26 20:54:22 +01:00

Add clickable HTML view of Serial Monitor

The HTML view only activates if:
- the output is steady
- the "frame" contains a link
- the length of the entire content is < 1KB

No performance penalty compared to normal view (in standard conditions)
This commit is contained in:
Martino Facchin 2017-01-09 12:06:37 +01:00
parent 58eeaafde1
commit 2bcbaf7a68
3 changed files with 264 additions and 0 deletions

View File

@ -32,6 +32,9 @@ import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Document;
import cc.arduino.packages.BoardPort;
@ -40,7 +43,9 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
protected JLabel noLineEndingAlert;
protected TextAreaFIFO textArea;
protected HTMLTextAreaFIFO htmlTextArea;
protected JScrollPane scrollPane;
protected JScrollPane htmlScrollPane;
protected JTextField textField;
protected JButton sendButton;
protected JButton clearButton;
@ -48,6 +53,10 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
protected JCheckBox addTimeStampBox;
protected JComboBox<String> lineEndings;
protected JComboBox<String> serialRates;
protected Container mainPane;
private long lastMessage;
private javax.swing.Timer updateTimer;
private boolean htmlView = true;
public AbstractTextMonitor(BoardPort boardPort) {
super(boardPort);
@ -69,6 +78,7 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
@Override
protected void onCreateWindow(Container mainPane) {
this.mainPane = mainPane;
mainPane.setLayout(new BorderLayout());
textArea = new TextAreaFIFO(8_000_000);
@ -76,14 +86,89 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
textArea.setColumns(40);
textArea.setEditable(false);
htmlTextArea = new HTMLTextAreaFIFO(8000000);
htmlTextArea.setEditable(false);
htmlTextArea.setOpaque(false);
// don't automatically update the caret. that way we can manually decide
// whether or not to do so based on the autoscroll checkbox.
((DefaultCaret) textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
((DefaultCaret) htmlTextArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
Document doc = textArea.getDocument();
if (doc instanceof AbstractDocument)
{
UndoableEditListener[] undoListeners =
( (AbstractDocument) doc).getUndoableEditListeners();
if (undoListeners.length > 0)
{
for (UndoableEditListener undoListener : undoListeners)
{
doc.removeUndoableEditListener(undoListener);
}
}
}
doc = htmlTextArea.getDocument();
if (doc instanceof AbstractDocument)
{
UndoableEditListener[] undoListeners =
( (AbstractDocument) doc).getUndoableEditListeners();
if (undoListeners.length > 0)
{
for (UndoableEditListener undoListener : undoListeners)
{
doc.removeUndoableEditListener(undoListener);
}
}
}
scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
htmlScrollPane = new JScrollPane(htmlTextArea);
htmlScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
ActionListener checkIfSteady = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (System.currentTimeMillis() - lastMessage > 200) {
if (htmlView == false && textArea.getLength() < 1000) {
htmlTextArea.setText("");
boolean res = htmlTextArea.append(textArea.getText());
if (res) {
htmlView = true;
mainPane.remove(scrollPane);
if (textArea.getCaretPosition() > htmlTextArea.getDocument().getLength()) {
htmlTextArea.setCaretPosition(htmlTextArea.getDocument().getLength());
} else {
htmlTextArea.setCaretPosition(textArea.getCaretPosition());
}
mainPane.add(htmlScrollPane, BorderLayout.CENTER);
scrollPane.setVisible(false);
mainPane.validate();
mainPane.repaint();
}
}
} else {
if (htmlView == true) {
htmlView = false;
mainPane.remove(htmlScrollPane);
mainPane.add(scrollPane, BorderLayout.CENTER);
scrollPane.setVisible(true);
mainPane.validate();
mainPane.repaint();
}
}
}
};
updateTimer = new javax.swing.Timer(33, checkIfSteady);
mainPane.add(scrollPane, BorderLayout.CENTER);
htmlTextArea.setVisible(true);
htmlScrollPane.setVisible(true);
JPanel upperPane = new JPanel();
upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
@ -168,6 +253,8 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
applyPreferences();
mainPane.add(pane, BorderLayout.SOUTH);
updateTimer.start();
}
@Override
@ -190,9 +277,21 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
textArea.setBackground(new Color(238, 238, 238));
}
textArea.invalidate();
clearButton.setEnabled(enable);
htmlTextArea.setEnabled(enable);
scrollPane.setEnabled(enable);
htmlScrollPane.setEnabled(enable);
textField.setEnabled(enable);
sendButton.setEnabled(enable);
autoscrollBox.setEnabled(enable);
addTimeStampBox.setEnabled(enable);
lineEndings.setEnabled(enable);
serialRates.setEnabled(enable);
if (enable == false) {
htmlTextArea.setText("");
}
}
public void onSendCommand(ActionListener listener) {
@ -210,6 +309,7 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
@Override
public void message(String msg) {
lastMessage = System.currentTimeMillis();
SwingUtilities.invokeLater(() -> updateTextArea(msg));
}

View File

@ -0,0 +1,160 @@
/*
Copyright (c) 2014 Paul Stoffregen <paul@pjrc.com>
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
*/
// adapted from https://community.oracle.com/thread/1479784
package processing.app;
import java.io.IOException;
import java.net.URL;
import java.awt.Desktop;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.JEditorPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTMLEditorKit;
import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
public class HTMLTextAreaFIFO extends JTextPane implements DocumentListener {
private int maxChars;
private int trimMaxChars;
private int updateCount; // limit how often we trim the document
private boolean doTrim;
private final HTMLEditorKit kit;
public HTMLTextAreaFIFO(int max) {
maxChars = max;
trimMaxChars = max / 2;
updateCount = 0;
doTrim = true;
setContentType("text/html");
getDocument().addDocumentListener(this);
setText("");
kit = new HTMLEditorKit();
this.addHyperlinkListener(new UpdatableBoardsLibsFakeURLsHandler(Base.INSTANCE));
}
public void insertUpdate(DocumentEvent e) {
}
public void removeUpdate(DocumentEvent e) {
}
public void changedUpdate(DocumentEvent e) {
}
public void trimDocument() {
int len = 0;
len = getDocument().getLength();
if (len > trimMaxChars) {
int n = len - trimMaxChars;
//System.out.println("trimDocument: remove " + n + " chars");
try {
getDocument().remove(0, n);
} catch (BadLocationException ble) {
}
}
}
private static List<String> extractUrls(String input) {
List<String> result = new ArrayList<String>();
Pattern pattern = Pattern.compile(
"(http|ftp|https)://([^\\s]+)");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
result.add(matcher.group());
}
return result;
}
static public final String WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))";
public boolean append(String s) {
boolean htmlFound = false;
try {
HTMLDocument doc = (HTMLDocument) getDocument();
String strings[] = s.split(String.format(WITH_DELIMITER, "\\r?\\n"));
for (int l = 0; l < strings.length; l++) {
String str = strings[l];
List<String> urls = extractUrls(str);
if (urls.size() > 0) {
for (int i = 0; i < urls.size(); i++) {
if (!((urls.get(i)).contains("</a>"))) {
str = str.replace(urls.get(i), "<a href='" + urls.get(i) + "'>" + urls.get(i) + "</a>");
}
}
kit.insertHTML(doc, doc.getLength(), str, 0, 0, null);
htmlFound = true;
} else {
doc.insertString(doc.getLength(), str, null);
}
}
} catch(BadLocationException exc) {
exc.printStackTrace();
} catch(IOException exc) {
exc.printStackTrace();
}
if (++updateCount > 150 && doTrim) {
updateCount = 0;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
trimDocument();
}
});
}
return htmlFound;
}
public void appendNoTrim(String s) {
int free = maxChars - getDocument().getLength();
if (free <= 0)
return;
if (s.length() > free)
append(s.substring(0, free));
else
append(s);
doTrim = false;
}
public void appendTrim(String str) {
append(str);
doTrim = true;
}
}

View File

@ -72,6 +72,10 @@ public class TextAreaFIFO extends JTextArea implements DocumentListener {
}
}
public int getLength() {
return getDocument().getLength();
}
public void appendNoTrim(String s) {
int free = maxChars - getDocument().getLength();
if (free <= 0)