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:
parent
58eeaafde1
commit
2bcbaf7a68
@ -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));
|
||||
}
|
||||
|
||||
|
160
app/src/processing/app/HTMLTextAreaFIFO.java
Normal file
160
app/src/processing/app/HTMLTextAreaFIFO.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user