diff --git a/app/src/processing/app/AbstractMonitor.java b/app/src/processing/app/AbstractMonitor.java index e8cc0df75db..e33649b8f86 100644 --- a/app/src/processing/app/AbstractMonitor.java +++ b/app/src/processing/app/AbstractMonitor.java @@ -9,13 +9,15 @@ import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.ByteArrayOutputStream; @SuppressWarnings("serial") public abstract class AbstractMonitor extends JFrame implements ActionListener { private boolean closed; - private StringBuffer updateBuffer; + private ByteArrayOutputStream updateBuffer; + private Timer updateTimer; private BoardPort boardPort; @@ -73,13 +75,14 @@ public void actionPerformed(ActionEvent event) { } } - updateBuffer = new StringBuffer(1048576); + updateBuffer = new ByteArrayOutputStream(1048576); updateTimer = new Timer(33, this); // redraw serial monitor at 30 Hz updateTimer.start(); closed = false; } + protected abstract void onCreateWindow(Container mainPane); public void enableWindow(boolean enable) { @@ -127,7 +130,7 @@ protected int[] getPlacement() { return location; } - public abstract void message(final String s); + public abstract void message(final byte[] buf); public boolean requiresAuthorization() { return false; @@ -161,19 +164,19 @@ public void setBoardPort(BoardPort boardPort) { this.boardPort = boardPort; } - public synchronized void addToUpdateBuffer(char buff[], int n) { - updateBuffer.append(buff, 0, n); + public synchronized void addToUpdateBuffer(byte buff[], int n) { + updateBuffer.write(buff, 0, n); } - private synchronized String consumeUpdateBuffer() { - String s = updateBuffer.toString(); - updateBuffer.setLength(0); - return s; + private synchronized byte[] consumeUpdateBuffer() { + byte[] contents = updateBuffer.toByteArray(); + updateBuffer.reset(); + return contents; } public void actionPerformed(ActionEvent e) { - String s = consumeUpdateBuffer(); - if (s.isEmpty()) { + byte[] s = consumeUpdateBuffer(); + if (s.length == 0) { return; } else { message(s); diff --git a/app/src/processing/app/AbstractTextMonitor.java b/app/src/processing/app/AbstractTextMonitor.java index ab8f3080412..b288d46a19d 100644 --- a/app/src/processing/app/AbstractTextMonitor.java +++ b/app/src/processing/app/AbstractTextMonitor.java @@ -8,6 +8,9 @@ import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.geom.AffineTransform; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import javax.swing.Box; import javax.swing.BoxLayout; @@ -35,7 +38,12 @@ public abstract class AbstractTextMonitor extends AbstractMonitor { protected JCheckBox autoscrollBox; protected JComboBox lineEndings; protected JComboBox serialRates; + + protected byte[][] lineEndingSelection; + protected JComboBox txCharsets; + protected JComboBox rxCharsets; + public AbstractTextMonitor(BoardPort boardPort) { super(boardPort); } @@ -83,11 +91,10 @@ protected void onCreateWindow(Container mainPane) { noLineEndingAlert = new JLabel(I18n.format(tr("You've pressed {0} but nothing was sent. Should you select a line ending?"), tr("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); + noLineEndingAlert.setMinimumSize(new Dimension(0, noLineEndingAlert.getPreferredSize().height)); lineEndings = new JComboBox(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")}); + lineEndingSelection = new byte[][] { {}, {'\n'}, {'\r'}, {'\r', '\n'}}; lineEndings.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex()); @@ -105,10 +112,49 @@ public void actionPerformed(ActionEvent event) { } serialRates.setMaximumSize(serialRates.getMinimumSize()); - + + java.util.List list = new java.util.ArrayList(); + + // All JVMs are required to support these standard charsets. I will add them to the top + // the list + + list.add(Charset.forName("US-ASCII")); + list.add(Charset.forName("ISO-8859-1")); + list.add(Charset.forName("UTF-8")); + list.add(Charset.forName("UTF-16")); + list.add(Charset.forName("UTF-16BE")); + list.add(Charset.forName("UTF-16LE")); + + list.addAll(Charset.availableCharsets().values()); + + rxCharsets = new JComboBox(list.toArray(new Charset[list.size()])); + for(java.util.Iterator i = list.iterator(); i.hasNext();) { + if(!i.next().canEncode()) { + i.remove(); + } + } + txCharsets = new JComboBox(list.toArray(new Charset[list.size()])); + + txCharsets.setSelectedItem(Charset.forName("US-ASCII")); // US-ASCII is always available + rxCharsets.setSelectedItem(Charset.defaultCharset()); // assume that the arduino is being tested against the current platform + + // some of these encoding names are a little long, so I'll knock some size off the fonts + Font compressed = txCharsets.getFont().deriveFont(new AffineTransform(.8, 0, 0, 1, 0, 0)); + txCharsets.setFont(compressed); + rxCharsets.setFont(compressed); + txCharsets.setMaximumSize(txCharsets.getMinimumSize()); + rxCharsets.setMaximumSize(rxCharsets.getMinimumSize()); + pane.add(autoscrollBox); pane.add(Box.createHorizontalGlue()); pane.add(noLineEndingAlert); + pane.add(new JLabel("Tx")); + pane.add(Box.createRigidArea(new Dimension(2, 0))); + pane.add(txCharsets); + pane.add(Box.createRigidArea(new Dimension(8, 0))); + pane.add(new JLabel("Rx")); + pane.add(Box.createRigidArea(new Dimension(2, 0))); + pane.add(rxCharsets); pane.add(Box.createRigidArea(new Dimension(8, 0))); pane.add(lineEndings); pane.add(Box.createRigidArea(new Dimension(8, 0))); @@ -137,10 +183,28 @@ public void onSerialRateChange(ActionListener listener) { serialRates.addActionListener(listener); } - public void message(final String s) { + public void onTxCharsetChange(ActionListener listener) { + txCharsets.addActionListener(listener); + } + + public void onRxCharsetChange(ActionListener listener) { + rxCharsets.addActionListener(listener); + } + + public void message(final byte[] msg) { + String s; + + try { + s = new String(msg, ((Charset)rxCharsets.getSelectedItem()).name()); + } catch (UnsupportedEncodingException e) { + s = new String(msg); + } + + final String final_s = s; + SwingUtilities.invokeLater(new Runnable() { public void run() { - textArea.append(s); + textArea.append(final_s); if (autoscrollBox.isSelected()) { textArea.setCaretPosition(textArea.getDocument().getLength()); } diff --git a/app/src/processing/app/NetworkMonitor.java b/app/src/processing/app/NetworkMonitor.java index b7f08026ace..2289397912f 100644 --- a/app/src/processing/app/NetworkMonitor.java +++ b/app/src/processing/app/NetworkMonitor.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import static processing.app.I18n.tr; @@ -120,7 +121,14 @@ public void run() { } @Override - public synchronized void message(String s) { + public synchronized void message(String msg) { + message(msg.getBytes((Charset)rxCharsets.getSelectedItem())); + } + + @Override + public synchronized void message(byte[] msg) { + String s = new String(msg); + if (s.contains("can't connect")) { while (!channel.isClosed()) { try { @@ -148,7 +156,7 @@ public void run() { s = "\n" + tr("Unable to connect: is the sketch using the bridge?"); } } - super.message(s); + super.message(msg); } @Override diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java index bcf39ce9077..cf222504fd7 100644 --- a/app/src/processing/app/SerialMonitor.java +++ b/app/src/processing/app/SerialMonitor.java @@ -24,6 +24,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.nio.charset.Charset; import static processing.app.I18n.tr; @@ -65,23 +66,14 @@ public void actionPerformed(ActionEvent e) { } private void send(String s) { + if (serial != null) { - switch (lineEndings.getSelectedIndex()) { - case 1: - s += "\n"; - break; - case 2: - s += "\r"; - break; - case 3: - s += "\r\n"; - break; - } + serial.write(s.getBytes((Charset)txCharsets.getSelectedItem())); + serial.write(lineEndingSelection[lineEndings.getSelectedIndex()]); if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) { noLineEndingAlert.setForeground(Color.RED); PreferencesData.set("runtime.line.ending.alert.notified", "true"); } - serial.write(s); } } @@ -92,8 +84,8 @@ public void open() throws Exception { serial = new Serial(getBoardPort().getAddress(), serialRate) { @Override - protected void message(char buff[], int n) { - addToUpdateBuffer(buff, n); + protected void message(byte[] bytes, int length) { + addToUpdateBuffer(bytes, length); } }; } diff --git a/app/src/processing/app/SerialPlotter.java b/app/src/processing/app/SerialPlotter.java index decbb9694e3..a2de94b5a02 100644 --- a/app/src/processing/app/SerialPlotter.java +++ b/app/src/processing/app/SerialPlotter.java @@ -218,8 +218,8 @@ private void onSerialRateChange(ActionListener listener) { serialRates.addActionListener(listener); } - public void message(final String s) { - messageBuffer.append(s); + public void message(final byte[] buf) { + messageBuffer.append(new String(buf)); while (true) { int linebreak = messageBuffer.indexOf("\n"); if (linebreak == -1) { @@ -260,7 +260,7 @@ public void open() throws Exception { serial = new Serial(getBoardPort().getAddress(), serialRate) { @Override - protected void message(char buff[], int n) { + protected void message(byte buff[], int n) { addToUpdateBuffer(buff, n); } }; diff --git a/arduino-core/src/processing/app/Serial.java b/arduino-core/src/processing/app/Serial.java index 04803f36381..063625b4fe5 100644 --- a/arduino-core/src/processing/app/Serial.java +++ b/arduino-core/src/processing/app/Serial.java @@ -153,9 +153,7 @@ public synchronized void serialEvent(SerialPortEvent serialEvent) { try { byte[] buf = port.readBytes(serialEvent.getEventValue()); if (buf.length > 0) { - String msg = new String(buf); - char[] chars = msg.toCharArray(); - message(chars, chars.length); + message(buf, buf.length); } } catch (SerialPortException e) { errorMessage("serialEvent", e); @@ -164,8 +162,27 @@ public synchronized void serialEvent(SerialPortEvent serialEvent) { } /** - * This method is intented to be extended to receive messages + * This method is intended to be overridden to receive messages * coming from serial port. + * + * For backward compatibility, the default implementation decodes + * the incoming bytes using the default charset, and calls + * message(char[], int). + */ + protected void message(byte[] bytes, int length) { + String msg = new String(bytes, 0, length); + char chars[] = msg.toCharArray(); + message(chars, chars.length); + } + + /** + * This method is intended to be overridden to receive messages + * coming from serial port. + * + * This method is deprecated. You should override message(byte[], int) + * and handle decoding explicitly. + * + * @deprecated override message(byte[], int) */ protected void message(char[] chars, int length) { // Empty @@ -184,9 +201,9 @@ public void write(int what) { // will also cover char } - private void write(byte bytes[]) { + public void write(byte what[]) { try { - port.writeBytes(bytes); + port.writeBytes(what); } catch (SerialPortException e) { errorMessage("write", e); } @@ -203,10 +220,15 @@ private void write(byte bytes[]) { *

* If you want to move Unicode data, you can first convert the * String to a byte stream in the representation of your choice - * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + * (i.e. UTF8 or two-byte Unicode data), and use write(byte[]). + * + * @deprecated use write(byte[]) */ public void write(String what) { - write(what.getBytes()); + byte[] buf = new byte[what.length()]; + for(int i = 0; i