mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-01 12:24:14 +01:00
Merge pull request #2177 from henningpohl/master
Adding plotting functionality to the editor
This commit is contained in:
commit
09b29b3d40
@ -3,6 +3,7 @@ package processing.app;
|
||||
import static processing.app.I18n._;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Rectangle;
|
||||
@ -15,15 +16,10 @@ import java.awt.event.WindowEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
@ -37,19 +33,11 @@ import processing.app.legacy.PApplet;
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
|
||||
protected final JLabel noLineEndingAlert;
|
||||
protected TextAreaFIFO textArea;
|
||||
protected JScrollPane scrollPane;
|
||||
protected JTextField textField;
|
||||
protected JButton sendButton;
|
||||
protected JCheckBox autoscrollBox;
|
||||
protected JComboBox lineEndings;
|
||||
protected JComboBox serialRates;
|
||||
private boolean monitorEnabled;
|
||||
private boolean closed;
|
||||
|
||||
private Timer updateTimer;
|
||||
private StringBuffer updateBuffer;
|
||||
private Timer updateTimer;
|
||||
|
||||
private BoardPort boardPort;
|
||||
|
||||
@ -82,84 +70,10 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
}
|
||||
}));
|
||||
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
Font consoleFont = Theme.getFont("console.font");
|
||||
Font editorFont = PreferencesData.getFont("editor.font");
|
||||
Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize());
|
||||
onCreateWindow(getContentPane());
|
||||
|
||||
textArea = new TextAreaFIFO(8000000);
|
||||
textArea.setRows(16);
|
||||
textArea.setColumns(40);
|
||||
textArea.setEditable(false);
|
||||
textArea.setFont(font);
|
||||
|
||||
// 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);
|
||||
|
||||
scrollPane = new JScrollPane(textArea);
|
||||
|
||||
getContentPane().add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
JPanel upperPane = new JPanel();
|
||||
upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
|
||||
upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
|
||||
|
||||
textField = new JTextField(40);
|
||||
sendButton = new JButton(_("Send"));
|
||||
|
||||
upperPane.add(textField);
|
||||
upperPane.add(Box.createRigidArea(new Dimension(4, 0)));
|
||||
upperPane.add(sendButton);
|
||||
|
||||
getContentPane().add(upperPane, BorderLayout.NORTH);
|
||||
|
||||
final JPanel pane = new JPanel();
|
||||
pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
|
||||
pane.setBorder(new EmptyBorder(4, 4, 4, 4));
|
||||
|
||||
autoscrollBox = new JCheckBox(_("Autoscroll"), true);
|
||||
|
||||
noLineEndingAlert = new JLabel(I18n.format(_("You've pressed {0} but nothing was sent. Should you select a line ending?"), _("Send")));
|
||||
noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
|
||||
noLineEndingAlert.setForeground(pane.getBackground());
|
||||
Dimension minimumSize = new Dimension(noLineEndingAlert.getMinimumSize());
|
||||
minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
|
||||
noLineEndingAlert.setMinimumSize(minimumSize);
|
||||
|
||||
lineEndings = new JComboBox(new String[]{_("No line ending"), _("Newline"), _("Carriage return"), _("Both NL & CR")});
|
||||
lineEndings.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
|
||||
noLineEndingAlert.setForeground(pane.getBackground());
|
||||
}
|
||||
});
|
||||
if (PreferencesData.get("serial.line_ending") != null) {
|
||||
lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
|
||||
}
|
||||
lineEndings.setMaximumSize(lineEndings.getMinimumSize());
|
||||
|
||||
String[] serialRateStrings = {"300", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "74880", "115200", "230400", "250000"};
|
||||
|
||||
serialRates = new JComboBox();
|
||||
for (String rate : serialRateStrings) {
|
||||
serialRates.addItem(rate + " " + _("baud"));
|
||||
}
|
||||
|
||||
serialRates.setMaximumSize(serialRates.getMinimumSize());
|
||||
|
||||
pane.add(autoscrollBox);
|
||||
pane.add(Box.createHorizontalGlue());
|
||||
pane.add(noLineEndingAlert);
|
||||
pane.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
pane.add(lineEndings);
|
||||
pane.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
pane.add(serialRates);
|
||||
|
||||
this.setMinimumSize(new Dimension(pane.getMinimumSize().width, this.getPreferredSize().height));
|
||||
|
||||
getContentPane().add(pane, BorderLayout.SOUTH);
|
||||
this.setMinimumSize(new Dimension(getContentPane().getMinimumSize().width, this.getPreferredSize().height));
|
||||
|
||||
pack();
|
||||
|
||||
@ -186,18 +100,15 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
closed = false;
|
||||
}
|
||||
|
||||
public void enableWindow(boolean enable) {
|
||||
textArea.setEnabled(enable);
|
||||
scrollPane.setEnabled(enable);
|
||||
textField.setEnabled(enable);
|
||||
sendButton.setEnabled(enable);
|
||||
autoscrollBox.setEnabled(enable);
|
||||
lineEndings.setEnabled(enable);
|
||||
serialRates.setEnabled(enable);
|
||||
protected abstract void onCreateWindow(Container mainPane);
|
||||
|
||||
public void enableWindow(boolean enable) {
|
||||
onEnableWindow(enable);
|
||||
monitorEnabled = enable;
|
||||
}
|
||||
|
||||
protected abstract void onEnableWindow(boolean enable);
|
||||
|
||||
// Puts the window in suspend state, closing the serial port
|
||||
// to allow other entity (the programmer) to use it
|
||||
public void suspend() throws Exception {
|
||||
@ -220,15 +131,6 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
open();
|
||||
}
|
||||
|
||||
public void onSerialRateChange(ActionListener listener) {
|
||||
serialRates.addActionListener(listener);
|
||||
}
|
||||
|
||||
public void onSendCommand(ActionListener listener) {
|
||||
textField.addActionListener(listener);
|
||||
sendButton.addActionListener(listener);
|
||||
}
|
||||
|
||||
protected void setPlacement(int[] location) {
|
||||
setBounds(location[0], location[1], location[2], location[3]);
|
||||
}
|
||||
@ -246,16 +148,7 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void message(final String s) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
textArea.append(s);
|
||||
if (autoscrollBox.isSelected()) {
|
||||
textArea.setCaretPosition(textArea.getDocument().getLength());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
public abstract void message(final String s);
|
||||
|
||||
public boolean requiresAuthorization() {
|
||||
return false;
|
||||
@ -298,18 +191,10 @@ public abstract class AbstractMonitor extends JFrame implements ActionListener {
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String s = consumeUpdateBuffer();
|
||||
|
||||
if (s.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//System.out.println("gui append " + s.length());
|
||||
if (autoscrollBox.isSelected()) {
|
||||
textArea.appendTrim(s);
|
||||
textArea.setCaretPosition(textArea.getDocument().getLength());
|
||||
} else {
|
||||
textArea.appendNoTrim(s);
|
||||
message(s);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
165
app/src/processing/app/AbstractTextMonitor.java
Normal file
165
app/src/processing/app/AbstractTextMonitor.java
Normal file
@ -0,0 +1,165 @@
|
||||
package processing.app;
|
||||
|
||||
import static processing.app.I18n._;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.text.DefaultCaret;
|
||||
|
||||
import cc.arduino.packages.BoardPort;
|
||||
import processing.app.debug.TextAreaFIFO;
|
||||
import processing.app.legacy.PApplet;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AbstractTextMonitor extends AbstractMonitor {
|
||||
|
||||
protected JLabel noLineEndingAlert;
|
||||
protected TextAreaFIFO textArea;
|
||||
protected JScrollPane scrollPane;
|
||||
protected JTextField textField;
|
||||
protected JButton sendButton;
|
||||
protected JCheckBox autoscrollBox;
|
||||
protected JComboBox lineEndings;
|
||||
protected JComboBox serialRates;
|
||||
|
||||
public AbstractTextMonitor(BoardPort boardPort) {
|
||||
super(boardPort);
|
||||
}
|
||||
|
||||
protected void onCreateWindow(Container mainPane) {
|
||||
Font consoleFont = Theme.getFont("console.font");
|
||||
Font editorFont = PreferencesData.getFont("editor.font");
|
||||
Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize());
|
||||
|
||||
mainPane.setLayout(new BorderLayout());
|
||||
|
||||
textArea = new TextAreaFIFO(8000000);
|
||||
textArea.setRows(16);
|
||||
textArea.setColumns(40);
|
||||
textArea.setEditable(false);
|
||||
textArea.setFont(font);
|
||||
|
||||
// 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);
|
||||
|
||||
scrollPane = new JScrollPane(textArea);
|
||||
|
||||
mainPane.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
JPanel upperPane = new JPanel();
|
||||
upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
|
||||
upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
|
||||
|
||||
textField = new JTextField(40);
|
||||
sendButton = new JButton(_("Send"));
|
||||
|
||||
upperPane.add(textField);
|
||||
upperPane.add(Box.createRigidArea(new Dimension(4, 0)));
|
||||
upperPane.add(sendButton);
|
||||
|
||||
mainPane.add(upperPane, BorderLayout.NORTH);
|
||||
|
||||
final JPanel pane = new JPanel();
|
||||
pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
|
||||
pane.setBorder(new EmptyBorder(4, 4, 4, 4));
|
||||
|
||||
autoscrollBox = new JCheckBox(_("Autoscroll"), true);
|
||||
|
||||
noLineEndingAlert = new JLabel(I18n.format(_("You've pressed {0} but nothing was sent. Should you select a line ending?"), _("Send")));
|
||||
noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
|
||||
noLineEndingAlert.setForeground(pane.getBackground());
|
||||
Dimension minimumSize = new Dimension(noLineEndingAlert.getMinimumSize());
|
||||
minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
|
||||
noLineEndingAlert.setMinimumSize(minimumSize);
|
||||
|
||||
lineEndings = new JComboBox(new String[]{_("No line ending"), _("Newline"), _("Carriage return"), _("Both NL & CR")});
|
||||
lineEndings.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
|
||||
noLineEndingAlert.setForeground(pane.getBackground());
|
||||
}
|
||||
});
|
||||
if (PreferencesData.get("serial.line_ending") != null) {
|
||||
lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
|
||||
}
|
||||
lineEndings.setMaximumSize(lineEndings.getMinimumSize());
|
||||
|
||||
String[] serialRateStrings = {
|
||||
"300", "1200", "2400", "4800", "9600",
|
||||
"19200", "38400", "57600", "115200", "230400", "250000"
|
||||
};
|
||||
|
||||
serialRates = new JComboBox();
|
||||
for (String rate : serialRateStrings) {
|
||||
serialRates.addItem(rate + " " + _("baud"));
|
||||
}
|
||||
|
||||
serialRates.setMaximumSize(serialRates.getMinimumSize());
|
||||
|
||||
pane.add(autoscrollBox);
|
||||
pane.add(Box.createHorizontalGlue());
|
||||
pane.add(noLineEndingAlert);
|
||||
pane.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
pane.add(lineEndings);
|
||||
pane.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
pane.add(serialRates);
|
||||
|
||||
mainPane.add(pane, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
protected void onEnableWindow(boolean enable)
|
||||
{
|
||||
textArea.setEnabled(enable);
|
||||
scrollPane.setEnabled(enable);
|
||||
textField.setEnabled(enable);
|
||||
sendButton.setEnabled(enable);
|
||||
autoscrollBox.setEnabled(enable);
|
||||
lineEndings.setEnabled(enable);
|
||||
serialRates.setEnabled(enable);
|
||||
}
|
||||
|
||||
public void onSendCommand(ActionListener listener) {
|
||||
textField.addActionListener(listener);
|
||||
sendButton.addActionListener(listener);
|
||||
}
|
||||
|
||||
public void onSerialRateChange(ActionListener listener) {
|
||||
serialRates.addActionListener(listener);
|
||||
}
|
||||
|
||||
public void message(final String s) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
textArea.append(s);
|
||||
if (autoscrollBox.isSelected()) {
|
||||
textArea.setCaretPosition(textArea.getDocument().getLength());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -138,6 +138,7 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
private static JMenu portMenu;
|
||||
|
||||
static volatile AbstractMonitor serialMonitor;
|
||||
static AbstractMonitor serialPlotter;
|
||||
|
||||
final EditorHeader header;
|
||||
EditorStatus status;
|
||||
@ -787,6 +788,14 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
item.addActionListener(e -> handleSerial());
|
||||
toolsMenu.add(item);
|
||||
|
||||
item = newJMenuItemShift(_("Serial Plotter"), 'L');
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handlePlotter();
|
||||
}
|
||||
});
|
||||
toolsMenu.add(item);
|
||||
|
||||
addTools(toolsMenu, BaseNoGui.getToolsFolder());
|
||||
File sketchbookTools = new File(BaseNoGui.getSketchbookFolder(), "tools");
|
||||
addTools(toolsMenu, sketchbookTools);
|
||||
@ -1113,6 +1122,7 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
}
|
||||
if (selection != null) selection.setState(true);
|
||||
//System.out.println(item.getLabel());
|
||||
|
||||
BaseNoGui.selectSerialPort(name);
|
||||
if (serialMonitor != null) {
|
||||
try {
|
||||
@ -1123,6 +1133,16 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
}
|
||||
}
|
||||
|
||||
if (serialPlotter != null) {
|
||||
try {
|
||||
serialPlotter.close();
|
||||
serialPlotter.setVisible(false);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
onBoardOrPortChange();
|
||||
base.onBoardOrPortChange();
|
||||
|
||||
//System.out.println("set to " + get("serial.port"));
|
||||
@ -2389,6 +2409,9 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
if (serialMonitor != null) {
|
||||
serialMonitor.suspend();
|
||||
}
|
||||
if (serialPlotter != null) {
|
||||
serialPlotter.suspend();
|
||||
}
|
||||
|
||||
uploading = true;
|
||||
|
||||
@ -2420,6 +2443,7 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
toolbar.deactivate(EditorToolbar.EXPORT);
|
||||
|
||||
resumeOrCloseSerialMonitor();
|
||||
resumeOrCloseSerialPlotter();
|
||||
base.onBoardOrPortChange();
|
||||
}
|
||||
}
|
||||
@ -2439,6 +2463,8 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (serialMonitor != null)
|
||||
serialMonitor.resume(boardPort);
|
||||
if (boardPort == null) {
|
||||
serialMonitor.close();
|
||||
handleSerial();
|
||||
@ -2451,6 +2477,25 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeOrCloseSerialPlotter() {
|
||||
// Return the serial plotter window to its initial state
|
||||
if (serialPlotter != null) {
|
||||
BoardPort boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port"));
|
||||
try {
|
||||
if (serialPlotter != null)
|
||||
serialPlotter.resume(boardPort);
|
||||
if (boardPort == null) {
|
||||
serialPlotter.close();
|
||||
handlePlotter();
|
||||
} else {
|
||||
serialPlotter.resume(boardPort);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
statusError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DAM: in Arduino, this is upload (with verbose output)
|
||||
class DefaultExportAppHandler implements Runnable {
|
||||
public void run() {
|
||||
@ -2459,6 +2504,9 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
if (serialMonitor != null) {
|
||||
serialMonitor.suspend();
|
||||
}
|
||||
if (serialPlotter != null) {
|
||||
serialPlotter.suspend();
|
||||
}
|
||||
|
||||
uploading = true;
|
||||
|
||||
@ -2490,12 +2538,23 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
toolbar.deactivate(EditorToolbar.EXPORT);
|
||||
|
||||
resumeOrCloseSerialMonitor();
|
||||
resumeOrCloseSerialPlotter();
|
||||
|
||||
base.onBoardOrPortChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleSerial() {
|
||||
if(serialPlotter != null) {
|
||||
if(serialPlotter.isClosed()) {
|
||||
serialPlotter = null;
|
||||
} else {
|
||||
statusError(I18n.format("Serial monitor not available while plotter is open"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (serialMonitor != null) {
|
||||
// The serial monitor already exists
|
||||
|
||||
@ -2583,6 +2642,96 @@ public class Editor extends JFrame implements RunnerListener {
|
||||
|
||||
}
|
||||
|
||||
public void handlePlotter() {
|
||||
if(serialMonitor != null) {
|
||||
if(serialMonitor.isClosed()) {
|
||||
serialMonitor = null;
|
||||
} else {
|
||||
statusError(I18n.format("Plotter not available while serial monitor is open"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (serialPlotter != null) {
|
||||
// The serial plotter already exists
|
||||
|
||||
if (serialPlotter.isClosed()) {
|
||||
// If it's closed, clear the refrence to the existing
|
||||
// plotter and create a new one
|
||||
serialPlotter = null;
|
||||
}
|
||||
else {
|
||||
// If it's not closed, give it the focus
|
||||
try {
|
||||
serialPlotter.toFront();
|
||||
serialPlotter.requestFocus();
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BoardPort port = Base.getDiscoveryManager().find(PreferencesData.get("serial.port"));
|
||||
|
||||
if (port == null) {
|
||||
statusError(I18n.format("Board at {0} is not available", PreferencesData.get("serial.port")));
|
||||
return;
|
||||
}
|
||||
|
||||
serialPlotter = new SerialPlotter(port);
|
||||
serialPlotter.setIconImage(getIconImage());
|
||||
|
||||
// If currently uploading, disable the plotter (it will be later
|
||||
// enabled when done uploading)
|
||||
if (uploading) {
|
||||
try {
|
||||
serialPlotter.suspend();
|
||||
} catch (Exception e) {
|
||||
statusError(e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
do {
|
||||
if (serialPlotter.requiresAuthorization() && !PreferencesData.has(serialPlotter.getAuthorizationKey())) {
|
||||
PasswordAuthorizationDialog dialog = new PasswordAuthorizationDialog(this, _("Type board password to access its console"));
|
||||
dialog.setLocationRelativeTo(this);
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.isCancelled()) {
|
||||
statusNotice(_("Unable to open serial plotter"));
|
||||
return;
|
||||
}
|
||||
|
||||
PreferencesData.set(serialPlotter.getAuthorizationKey(), dialog.getPassword());
|
||||
}
|
||||
|
||||
try {
|
||||
serialPlotter.open();
|
||||
serialPlotter.setVisible(true);
|
||||
success = true;
|
||||
} catch (ConnectException e) {
|
||||
statusError(_("Unable to connect: is the sketch using the bridge?"));
|
||||
} catch (JSchException e) {
|
||||
statusError(_("Unable to connect: wrong password?"));
|
||||
} catch (SerialException e) {
|
||||
String errorMessage = e.getMessage();
|
||||
if (e.getCause() != null && e.getCause() instanceof SerialPortException) {
|
||||
errorMessage += " (" + ((SerialPortException) e.getCause()).getExceptionType() + ")";
|
||||
}
|
||||
statusError(errorMessage);
|
||||
} catch (Exception e) {
|
||||
statusError(e);
|
||||
} finally {
|
||||
if (serialPlotter.requiresAuthorization() && !success) {
|
||||
PreferencesData.remove(serialPlotter.getAuthorizationKey());
|
||||
}
|
||||
}
|
||||
|
||||
} while (serialPlotter.requiresAuthorization() && !success);
|
||||
|
||||
}
|
||||
|
||||
private void handleBurnBootloader() {
|
||||
console.clear();
|
||||
|
@ -22,7 +22,7 @@ import java.io.OutputStream;
|
||||
import static processing.app.I18n._;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class NetworkMonitor extends AbstractMonitor implements MessageConsumer {
|
||||
public class NetworkMonitor extends AbstractTextMonitor implements MessageConsumer {
|
||||
|
||||
private static final int MAX_CONNECTION_ATTEMPTS = 5;
|
||||
|
||||
|
@ -28,7 +28,7 @@ import java.awt.event.ActionListener;
|
||||
import static processing.app.I18n._;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SerialMonitor extends AbstractMonitor {
|
||||
public class SerialMonitor extends AbstractTextMonitor {
|
||||
|
||||
private Serial serial;
|
||||
private int serialRate;
|
||||
|
242
app/src/processing/app/SerialPlotter.java
Normal file
242
app/src/processing/app/SerialPlotter.java
Normal file
@ -0,0 +1,242 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
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 cc.arduino.packages.BoardPort;
|
||||
import processing.app.legacy.PApplet;
|
||||
|
||||
import processing.app.debug.MessageConsumer;
|
||||
import processing.app.helpers.*;
|
||||
import static processing.app.I18n._;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.text.*;
|
||||
|
||||
public class SerialPlotter extends AbstractMonitor {
|
||||
private StringBuffer messageBuffer;
|
||||
private CircularBuffer buffer;
|
||||
private GraphPanel graphPanel;
|
||||
private JComboBox serialRates;
|
||||
|
||||
private Serial serial;
|
||||
private int serialRate;
|
||||
|
||||
private class GraphPanel extends JPanel {
|
||||
private double minY, maxY, rangeY;
|
||||
private Rectangle bounds;
|
||||
private int xOffset;
|
||||
private Font font;
|
||||
private Color graphColor;
|
||||
|
||||
public GraphPanel() {
|
||||
font = Theme.getFont("console.font");
|
||||
graphColor = Theme.getColor("header.bgcolor");
|
||||
xOffset = 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g1) {
|
||||
Graphics2D g = (Graphics2D)g1;
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setFont(font);
|
||||
super.paintComponent(g);
|
||||
|
||||
bounds = g.getClipBounds();
|
||||
setBackground(Color.WHITE);
|
||||
if(buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
minY = buffer.min();
|
||||
maxY = buffer.max();
|
||||
Ticks ticks = new Ticks(minY, maxY, 3);
|
||||
minY = Math.min(minY, ticks.getTick(0));
|
||||
maxY = Math.max(maxY, ticks.getTick(ticks.getTickCount() - 1));
|
||||
rangeY = maxY - minY;
|
||||
minY -= 0.05 * rangeY;
|
||||
maxY += 0.05 * rangeY;
|
||||
rangeY = maxY - minY;
|
||||
|
||||
g.setStroke(new BasicStroke(1.0f));
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
for(int i = 0; i < ticks.getTickCount(); ++i) {
|
||||
double tick = ticks.getTick(i);
|
||||
Rectangle2D fRect = fm.getStringBounds(String.valueOf(tick), g);
|
||||
xOffset = Math.max(xOffset, (int)fRect.getWidth() + 15);
|
||||
|
||||
// draw tick
|
||||
g.drawLine(xOffset - 5, (int)transformY(tick), xOffset + 2, (int)transformY(tick));
|
||||
// draw tick label
|
||||
g.drawString(String.valueOf(tick), xOffset - (int)fRect.getWidth() - 10, transformY(tick) - (float)fRect.getHeight() * 0.5f + fm.getAscent());
|
||||
}
|
||||
|
||||
g.drawLine(bounds.x + xOffset, bounds.y + 5, bounds.x + xOffset, bounds.y + bounds.height - 10);
|
||||
|
||||
g.setTransform(AffineTransform.getTranslateInstance(xOffset, 0));
|
||||
float xstep = (float)(bounds.width - xOffset) / (float)buffer.capacity();
|
||||
|
||||
g.setColor(graphColor);
|
||||
g.setStroke(new BasicStroke(0.75f));
|
||||
|
||||
for(int i = 0; i < buffer.size() - 1; ++i) {
|
||||
g.drawLine(
|
||||
(int)(i * xstep), (int)transformY(buffer.get(i)),
|
||||
(int)((i + 1) * xstep), (int)transformY(buffer.get(i + 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return new Dimension(200, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(500, 250);
|
||||
}
|
||||
|
||||
private float transformY(double rawY) {
|
||||
return (float)(5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY));
|
||||
}
|
||||
}
|
||||
|
||||
public SerialPlotter(BoardPort port) {
|
||||
super(port);
|
||||
|
||||
serialRate = PreferencesData.getInteger("serial.debug_rate");
|
||||
serialRates.setSelectedItem(serialRate + " " + _("baud"));
|
||||
onSerialRateChange(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
String wholeString = (String) serialRates.getSelectedItem();
|
||||
String rateString = wholeString.substring(0, wholeString.indexOf(' '));
|
||||
serialRate = Integer.parseInt(rateString);
|
||||
PreferencesData.set("serial.debug_rate", rateString);
|
||||
try {
|
||||
close();
|
||||
Thread.sleep(100); // Wait for serial port to properly close
|
||||
open();
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
messageBuffer = new StringBuffer();
|
||||
}
|
||||
|
||||
protected void onCreateWindow(Container mainPane) {
|
||||
mainPane.setLayout(new BorderLayout());
|
||||
|
||||
Font consoleFont = Theme.getFont("console.font");
|
||||
Font editorFont = PreferencesData.getFont("editor.font");
|
||||
Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize());
|
||||
|
||||
buffer = new CircularBuffer(500);
|
||||
graphPanel = new GraphPanel();
|
||||
|
||||
mainPane.add(graphPanel, BorderLayout.CENTER);
|
||||
|
||||
JPanel pane = new JPanel();
|
||||
pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
|
||||
pane.setBorder(new EmptyBorder(4, 4, 4, 4));
|
||||
|
||||
String[] serialRateStrings = {
|
||||
"300","1200","2400","4800","9600","14400",
|
||||
"19200","28800","38400","57600","115200"
|
||||
};
|
||||
|
||||
serialRates = new JComboBox();
|
||||
for (int i = 0; i < serialRateStrings.length; i++)
|
||||
serialRates.addItem(serialRateStrings[i] + " " + _("baud"));
|
||||
|
||||
serialRates.setMaximumSize(serialRates.getMinimumSize());
|
||||
|
||||
pane.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
pane.add(serialRates);
|
||||
|
||||
mainPane.add(pane, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
protected void onEnableWindow(boolean enable)
|
||||
{
|
||||
serialRates.setEnabled(enable);
|
||||
}
|
||||
|
||||
public void onSerialRateChange(ActionListener listener) {
|
||||
serialRates.addActionListener(listener);
|
||||
}
|
||||
|
||||
public void message(final String s) {
|
||||
messageBuffer.append(s);
|
||||
while(true) {
|
||||
int linebreak = messageBuffer.indexOf("\n");
|
||||
if(linebreak == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
String line = messageBuffer.substring(0, linebreak);
|
||||
line = line.trim();
|
||||
messageBuffer.delete(0, linebreak + 1);
|
||||
|
||||
try {
|
||||
double value = Double.valueOf(line);
|
||||
buffer.add(value);
|
||||
} catch(NumberFormatException e) {
|
||||
continue; // ignore lines that can't be cast to a number
|
||||
}
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
SerialPlotter.this.repaint();
|
||||
}});
|
||||
}
|
||||
|
||||
public void open() throws Exception {
|
||||
super.open();
|
||||
|
||||
if (serial != null) return;
|
||||
|
||||
serial = new Serial(getBoardPort().getAddress(), serialRate) {
|
||||
@Override
|
||||
protected void message(char buff[], int n) {
|
||||
addToUpdateBuffer(buff, n);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
if (serial != null) {
|
||||
super.close();
|
||||
int[] location = getPlacement();
|
||||
String locationStr = PApplet.join(PApplet.str(location), ",");
|
||||
PreferencesData.set("last.serial.location", locationStr);
|
||||
serial.dispose();
|
||||
serial = null;
|
||||
}
|
||||
}
|
||||
}
|
81
app/src/processing/app/helpers/CircularBuffer.java
Normal file
81
app/src/processing/app/helpers/CircularBuffer.java
Normal file
@ -0,0 +1,81 @@
|
||||
package processing.app.helpers;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class CircularBuffer {
|
||||
private double[] elements;
|
||||
private int start = -1;
|
||||
private int end = -1;
|
||||
private int capacity;
|
||||
|
||||
public void add(double num) {
|
||||
end = (end + 1) % capacity;
|
||||
elements[end] = num;
|
||||
if(start == end || start == -1) {
|
||||
start = (start + 1) % capacity;
|
||||
}
|
||||
}
|
||||
|
||||
public double get(int index) {
|
||||
if(index >= capacity) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if(index >= size()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
return elements[(start + index) % capacity];
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return start == -1 && end == -1;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
start = end = -1;
|
||||
}
|
||||
|
||||
public CircularBuffer(int capacity) {
|
||||
this.capacity = capacity;
|
||||
elements = new double[capacity];
|
||||
}
|
||||
|
||||
public double min() {
|
||||
if(size() == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
double out = get(0);
|
||||
for(int i = 1; i < size(); ++i) {
|
||||
out = Math.min(out, get(i));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public double max() {
|
||||
if(size() == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
double out = get(0);
|
||||
for(int i = 1; i < size(); ++i) {
|
||||
out = Math.max(out, get(i));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
if(end == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (end - start + capacity) % capacity + 1;
|
||||
}
|
||||
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
}
|
46
app/src/processing/app/helpers/Ticks.java
Normal file
46
app/src/processing/app/helpers/Ticks.java
Normal file
@ -0,0 +1,46 @@
|
||||
package processing.app.helpers;
|
||||
|
||||
public class Ticks {
|
||||
private double tickMin;
|
||||
private double tickMax;
|
||||
private double tickStep;
|
||||
private int tickCount;
|
||||
|
||||
private double[] ticks;
|
||||
|
||||
public Ticks(double min, double max, int tickCount) {
|
||||
double range = max - min;
|
||||
double exp = Math.floor(Math.log10(range / (tickCount - 1)));
|
||||
double scale = Math.pow(10, exp);
|
||||
|
||||
double rawTickStep = (range / (tickCount - 1)) / scale;
|
||||
for(double potentialStep : new double[] {1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0}) {
|
||||
if(potentialStep < rawTickStep) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tickStep = potentialStep * scale;
|
||||
tickMin = tickStep * Math.floor(min / tickStep);
|
||||
tickMax = tickMin + tickStep * (tickCount - 1);
|
||||
if(tickMax >= max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tickCount -= (int)Math.floor((tickMax - max) / tickStep);
|
||||
this.tickCount = tickCount;
|
||||
|
||||
ticks = new double[tickCount];
|
||||
for(int i = 0; i < tickCount; ++i) {
|
||||
ticks[i] = tickMin + i * tickStep;
|
||||
}
|
||||
}
|
||||
|
||||
public double getTick(int i) {
|
||||
return ticks[i];
|
||||
}
|
||||
|
||||
public int getTickCount() {
|
||||
return tickCount;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user