From d27dbc49b420e0a86904e67ec2efbaf5bb67e3fb Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Wed, 7 Oct 2015 22:00:08 -0700 Subject: Update to use ntcore and new Preferences. This significantly upgrades the Preferences GUI to: - Show type information - Allow editing of all data types including arrays - Implement "real" deletion - Support local saves and loads Change-Id: Ic43281de9c1aea52e65d173328a8b10ec4f61259 --- smartdashboard/build.gradle | 64 +-- .../src/edu/wpi/first/smartdashboard/LogToCSV.java | 2 +- .../first/smartdashboard/gui/DashboardMenu.java | 4 +- .../first/smartdashboard/gui/DashboardPanel.java | 12 +- .../gui/elements/ConnectionIndicator.java | 13 +- .../gui/elements/RobotPreferences.java | 511 +++++++++++++++++++-- .../smartdashboard/gui/elements/Scheduler.java | 2 +- .../gui/elements/bindings/AbstractTableWidget.java | 4 +- .../livewindow/elements/LWSubsystem.java | 4 +- .../edu/wpi/first/smartdashboard/robot/Robot.java | 49 +- 10 files changed, 539 insertions(+), 126 deletions(-) diff --git a/smartdashboard/build.gradle b/smartdashboard/build.gradle index c85fee5..b1fcaf6 100644 --- a/smartdashboard/build.gradle +++ b/smartdashboard/build.gradle @@ -1,53 +1,59 @@ apply plugin: 'java' -apply plugin: 'maven-publish' apply plugin: 'application' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'maven-publish' -mainClassName = "edu.wpi.first.smartdashboard.main" - -sourceSets { - main { - java.srcDir "src/" +buildscript { + repositories { jcenter() } + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.1' } } -dependencies { - compile 'edu.wpi.first.wpilib.networktables.java:NetworkTables:0.1.0-SNAPSHOT' - compile 'junit:junit:4.12' - compile 'jcommon:jcommon:0.9.5' - compile 'jfreechart:jfreechart:1.0.0' +if (!hasProperty('repo')) { + ext.repo = 'development' } -repositories { - maven { - url "http://first.wpi.edu/FRC/roborio/maven/" - } - mavenCentral() -} +mainClassName = "edu.wpi.first.smartdashboard.main" publishing { publications { maven(MavenPublication) { - from components.java - - artifact (jar) { - classifier = 'jar' + artifact(shadowJar) { + classifier null } - groupId 'edu.wpi.first.wpilib' artifactId 'SmartDashboard' version '1.0.0-SNAPSHOT' } } - repositories { - maven { - url "file://${System.properties['user.home']}/releases/maven" + repositories.maven { + url = "${System.getProperty('user.home')}/releases/maven/$repo" + } +} + +sourceSets { + main { + java { + srcDirs = ["src"] } } } -jar { - manifest { - attributes 'Main-Class': 'edu.wpi.first.smartdashboard.main' +repositories { + mavenCentral() + maven { + url = "${System.getProperty('user.home')}/releases/maven/$repo" } - from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + mavenLocal() + maven { + url = "http://first.wpi.edu/FRC/roborio/maven/$repo" + } +} + +dependencies { + compile "edu.wpi.first.wpilib.networktables.java:NetworkTables:3.0.0-SNAPSHOT:desktop" + compile 'junit:junit:4.12' + compile 'jcommon:jcommon:0.9.5' + compile 'jfreechart:jfreechart:1.0.0' } diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/LogToCSV.java b/smartdashboard/src/edu/wpi/first/smartdashboard/LogToCSV.java index 9c5e08b..68a103a 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/LogToCSV.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/LogToCSV.java @@ -38,7 +38,7 @@ public class LogToCSV implements ITableListener { m_fw = new FileWriter(path); m_fw.write("Time (ms),Name,Value" + s_lineSeparator); m_fw.flush(); - Robot.getTable().addTableListener(this, true); + Robot.getTable().addTableListenerEx(this, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); } catch (IOException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, "An error occurred when attempting to " + "open the output CSV file for writing. " diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardMenu.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardMenu.java index 28ea979..18a4f01 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardMenu.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardMenu.java @@ -150,7 +150,7 @@ public class DashboardMenu extends JMenuBar { } }); resetLW.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK)); - Robot.getLiveWindow().getSubTable("~STATUS~").addTableListener("LW Enabled", new ITableListener() { + Robot.getLiveWindow().getSubTable("~STATUS~").addTableListenerEx("LW Enabled", new ITableListener() { public void valueChanged(ITable itable, String string, Object o, boolean bln) { final boolean isInLW = Robot.getLiveWindow().getSubTable("~STATUS~").getBoolean("LW Enabled", false); @@ -170,7 +170,7 @@ public class DashboardMenu extends JMenuBar { } }); } - }, true); + }, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); viewMenu.add(resetLW); JMenu addMenu = new JMenu("Add..."); diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardPanel.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardPanel.java index 386a134..948f9c1 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardPanel.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/DashboardPanel.java @@ -69,8 +69,8 @@ public class DashboardPanel extends JPanel { setEditable(editable); - table.addTableListener(listener, true); - table.addSubTableListener(listener); + table.addTableListenerEx(listener, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); + table.addSubTableListener(listener, true); } public ITable getTable() { @@ -179,8 +179,8 @@ public class DashboardPanel extends JPanel { table.removeTableListener(listener); - table.addTableListener(listener, true); - table.addSubTableListener(listener); + table.addTableListenerEx(listener, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); + table.addSubTableListener(listener, true); repaint(); } @@ -514,7 +514,7 @@ public class DashboardPanel extends JPanel { if (!hiddenFields.contains(key)) { if (value instanceof ITable) { final ITable table = (ITable) value; - table.addTableListener("~TYPE~", new ITableListener() { + table.addTableListenerEx("~TYPE~", new ITableListener() { public void valueChanged(final ITable typeSource, final String typeKey, final Object typeValue, final boolean typeIsNew) { table.removeTableListener(this); SwingUtilities.invokeLater(new Runnable() { @@ -523,7 +523,7 @@ public class DashboardPanel extends JPanel { } }); } - }, true); + }, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/ConnectionIndicator.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/ConnectionIndicator.java index da18f20..ccfb89c 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/ConnectionIndicator.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/ConnectionIndicator.java @@ -23,7 +23,6 @@ public class ConnectionIndicator extends StaticWidget implements IRemoteConnecti public final ColorProperty positive = new ColorProperty(this, "Connection Color", Color.GREEN); public final ColorProperty negative = new ColorProperty(this, "No Connection Color", Color.RED); public final MultiProperty display = new MultiProperty(this, "Graphics"); - private boolean firstRun = true; private boolean connected = false; private Runnable repainter = new Runnable() { @@ -91,23 +90,19 @@ public class ConnectionIndicator extends StaticWidget implements IRemoteConnecti @Override public void connected(IRemote remote) { + System.out.println("ConnectionIndicator CONNECTED"); if (!connected) { connected = true; - if (!firstRun) { - SwingUtilities.invokeLater(repainter); - } + SwingUtilities.invokeLater(repainter); } - firstRun = false; } @Override public void disconnected(IRemote remote) { + System.out.println("ConnectionIndicator DISCONNECTED"); if (connected) { connected = false; - if (!firstRun) { - SwingUtilities.invokeLater(repainter); - } + SwingUtilities.invokeLater(repainter); } - firstRun = false; } } diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/RobotPreferences.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/RobotPreferences.java index 9cb35b2..cf640e8 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/RobotPreferences.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/RobotPreferences.java @@ -6,10 +6,13 @@ import java.util.*; import javax.swing.*; import javax.swing.table.*; +import org.jfree.ui.ExtensionFileFilter; import edu.wpi.first.smartdashboard.gui.*; import edu.wpi.first.smartdashboard.properties.*; import edu.wpi.first.smartdashboard.robot.Robot; +import edu.wpi.first.wpilibj.networktables.NetworkTable; +import edu.wpi.first.wpilibj.networktables.PersistentException; import edu.wpi.first.wpilibj.tables.*; /** @@ -18,14 +21,14 @@ import edu.wpi.first.wpilibj.tables.*; */ public class RobotPreferences extends StaticWidget implements ITableListener { - public static final String DELETED_VALUE = "\""; public static final String NAME = "Robot Preferences"; private JTable table; private PreferenceTableModel model; - private Map values; - private JButton save; + private Map values; private JButton add; private JButton remove; + private JButton save; + private JButton load; @Override public void init() { @@ -36,7 +39,7 @@ public class RobotPreferences extends StaticWidget implements ITableListener { NewPreferenceEntryDialog dialog = new NewPreferenceEntryDialog(); dialog.show(remove.getLocationOnScreen()); if (!dialog.isCanceled()) { - model.put(dialog.getKey(), dialog.getValue()); + model.putString(dialog.getKey(), dialog.getValue(), dialog.getValueType()); } } }); @@ -48,7 +51,7 @@ public class RobotPreferences extends StaticWidget implements ITableListener { if (table.isEditing()) { table.getCellEditor().cancelCellEditing(); } - Map.Entry entry = model.getRow(table.getSelectedRow()); + Map.Entry entry = model.getRow(table.getSelectedRow()); if (entry != null) { model.delete(entry.getKey()); } @@ -59,13 +62,50 @@ public class RobotPreferences extends StaticWidget implements ITableListener { save.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Robot.getPreferences().putBoolean(Robot.PREF_SAVE_FIELD, true); + if (table.isEditing()) { + table.getCellEditor().cancelCellEditing(); + } + JFileChooser fc = new JFileChooser("."); + fc.addChoosableFileFilter(new ExtensionFileFilter("INI File", ".ini")); + fc.setApproveButtonText("Save"); + fc.setDialogTitle("Save Preferences As..."); + + fc.setMultiSelectionEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + + if (fc.showOpenDialog(RobotPreferences.this) == JFileChooser.APPROVE_OPTION) { + String filepath = fc.getSelectedFile().getAbsolutePath(); + if (!filepath.endsWith(".ini")) { + filepath = filepath + ".ini"; + } + model.save(filepath); + } } }); - values = new LinkedHashMap(); + load = new JButton("Load"); + load.addActionListener(new ActionListener() { - Robot.getPreferences().addTableListener(this, true); + public void actionPerformed(ActionEvent e) { + if (table.isEditing()) { + table.getCellEditor().cancelCellEditing(); + } + JFileChooser fc = new JFileChooser("."); + fc.addChoosableFileFilter(new ExtensionFileFilter("INI File", ".ini")); + fc.setMultiSelectionEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setApproveButtonText("Load"); + fc.setDialogTitle("Load Preferences"); + + if (fc.showOpenDialog(RobotPreferences.this) == JFileChooser.APPROVE_OPTION) { + model.load(fc.getSelectedFile().getAbsolutePath()); + } + } + }); + + values = new LinkedHashMap(); + + Robot.getPreferences().addTableListenerEx(this, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_DELETE | ITable.NOTIFY_UPDATE); model = new PreferenceTableModel(); @@ -75,14 +115,15 @@ public class RobotPreferences extends StaticWidget implements ITableListener { JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new GridLayout(0, 2)); + buttonPanel.setLayout(new GridLayout(0, 4)); buttonPanel.add(add); buttonPanel.add(remove); + buttonPanel.add(save); + buttonPanel.add(load); JPanel controlPanel = new JPanel(); controlPanel.setLayout(new BorderLayout()); controlPanel.add(buttonPanel, BorderLayout.NORTH); - controlPanel.add(save, BorderLayout.SOUTH); setLayout(new BorderLayout()); JScrollPane tableScrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); @@ -101,16 +142,15 @@ public class RobotPreferences extends StaticWidget implements ITableListener { public void propertyChanged(Property property) { } - @Override - public void valueChanged(ITable source, String key, Object value, boolean isNew) { - if (key.equals(Robot.PREF_SAVE_FIELD)) { - save.setEnabled(!(Boolean) value); + @Override + public void valueChanged(ITable source, String key, Object value, boolean isNew) {} + + @Override + public void valueChangedEx(ITable source, String key, Object value, int flags) { + if ((flags & ITable.NOTIFY_DELETE) != 0) { + values.remove(key); } else { - if (DELETED_VALUE.equals(value.toString())) { - values.remove(key); - } else { - values.put(key, value.toString()); - } + values.put(key, value); } if (model != null) { @@ -118,6 +158,169 @@ public class RobotPreferences extends StaticWidget implements ITableListener { } } + private static final String[] typeNames = new String[] {"Boolean", "Number", "String", "Raw", "Boolean[]", "Number[]", "String[]"}; + + private static String getTypeName(Object value) { + if (value instanceof Boolean) return typeNames[0]; + if (value instanceof Double) return typeNames[1]; + if (value instanceof String) return typeNames[2]; + if (value instanceof byte[]) return typeNames[3]; + if (value instanceof boolean[]) return typeNames[4].substring(0, 8) + ((boolean[])value).length + "]"; + if (value instanceof double[]) return typeNames[5].substring(0, 7) + ((double[])value).length + "]"; + if (value instanceof String[]) return typeNames[6].substring(0, 7) + ((String[])value).length + "]"; + return "ERROR"; + } + + private static int getTypeIndex(String name) { + if (name.equals("Boolean")) return 0; + if (name.equals("Number")) return 1; + if (name.equals("String")) return 2; + if (name.equals("Raw")) return 3; + if (name.startsWith("Boolean[")) return 4; + if (name.startsWith("Number[")) return 5; + if (name.startsWith("String[")) return 6; + return -1; + } + + private static String hex(char ch) { + return Integer.toHexString(ch).toLowerCase(Locale.ENGLISH); + } + + private static void escapeString(StringBuilder out, String str, boolean inArray) { + int sz = str.length(); + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + // handle unicode + /*if (ch > 0xfff) { + out.append("\\u" + hex(ch)); + } else if (ch > 0xff) { + out.append("\\u0" + hex(ch)); + } else if (ch > 0x7f) { + out.append("\\u00" + hex(ch)); + } else*/ if (ch < 32) { + switch (ch) { + case '\b': + out.append("\\b"); + break; + case '\n': + out.append("\\n"); + break; + case '\t': + out.append("\\t"); + break; + case '\f': + out.append("\\f"); + break; + case '\r': + out.append("\\r"); + break; + default: + if (ch > 0xf) { + out.append("\\u00" + hex(ch)); + } else { + out.append("\\u000" + hex(ch)); + } + break; + } + } else { + switch (ch) { + case ',': case ']': + if (inArray) { + out.append('\\'); + } + out.append(ch); + break; + case '\\' : + out.append("\\\\"); + break; + default: + out.append(ch); + break; + } + } + } + } + + private static String escapeString(String str, boolean inArray) { + StringBuilder out = new StringBuilder(); + escapeString(out, str, inArray); + return out.toString(); + } + + private static void unescapeString(StringBuilder out, String str) { + int sz = str.length(); + StringBuilder unicode = new StringBuilder(4); + boolean hadSlash = false; + boolean inUnicode = false; + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + if (inUnicode) { + // if in unicode, then we're reading unicode + // values in somehow + unicode.append(ch); + if (unicode.length() == 4) { + // unicode now contains the four hex digits + // which represents our unicode character + try { + int value = Integer.parseInt(unicode.toString(), 16); + out.append((char) value); + unicode.setLength(0); + inUnicode = false; + hadSlash = false; + } catch (NumberFormatException nfe) { + throw new NumberFormatException("Unable to parse unicode value: " + unicode); + } + } + continue; + } + if (hadSlash) { + // handle an escaped value + hadSlash = false; + switch (ch) { + case 'r': + out.append('\r'); + break; + case 'f': + out.append('\f'); + break; + case 't': + out.append('\t'); + break; + case 'n': + out.append('\n'); + break; + case 'b': + out.append('\b'); + break; + case 'u': + { + // uh-oh, we're in unicode country.... + inUnicode = true; + break; + } + default: + out.append(ch); + break; + } + continue; + } else if (ch == '\\') { + hadSlash = true; + continue; + } + out.append(ch); + } + if (hadSlash) { + // then we're in the weird case of a \ at the end of the + // string, let's output it anyway. + out.append('\\'); + } + } + + private static String unescapeString(String str) { + StringBuilder out = new StringBuilder(); + unescapeString(out, str); + return out.toString(); + } private class PreferenceTableModel extends AbstractTableModel { @@ -126,23 +329,22 @@ public class RobotPreferences extends StaticWidget implements ITableListener { } public int getColumnCount() { - return 2; + return 3; } @Override public String getColumnName(int i) { - if (i == 0) { - return "Key"; - } else if (i == 1) { - return "Value"; - } else { - return "ERROR"; + switch (i) { + case 0: return "Key"; + case 1: return "Value"; + case 2: return "Type"; + default: return "ERROR"; } } - public Map.Entry getRow(int rowIndex) { + public Map.Entry getRow(int rowIndex) { int row = 0; - for (Map.Entry entry : values.entrySet()) { + for (Map.Entry entry : values.entrySet()) { if (row++ == rowIndex) { return entry; } @@ -150,16 +352,29 @@ public class RobotPreferences extends StaticWidget implements ITableListener { return null; } - public boolean put(String key, String value) { - if (validateKey(key) && validateValue(value)) { - Robot.getPreferences().putString(key, value); + public boolean put(String key, Object value) { + if (validateKey(key)) { + Robot.getPreferences().putValue(key, value); + Robot.getPreferences().setPersistent(key); return true; } return false; } + public boolean putString(String key, String value, String type) { + int typeIndex = getTypeIndex(type); + if (typeIndex < 0 || !validateKey(key)) + return false; + Object valueObj = validateValue(value, typeIndex); + if (valueObj == null) + return false; + Robot.getPreferences().putValue(key, valueObj); + Robot.getPreferences().setPersistent(key); + return true; + } + public void delete(String key) { - Robot.getPreferences().putString(key, DELETED_VALUE); + Robot.getPreferences().delete(key); } public boolean validateKey(String key) { @@ -167,28 +382,159 @@ public class RobotPreferences extends StaticWidget implements ITableListener { JOptionPane.showMessageDialog(RobotPreferences.this, "The key cannot be empty", "Bad Key", JOptionPane.ERROR_MESSAGE); return false; } - if (key.contains(" ") || key.contains("=") || key.contains("\t") || key.contains("\r") || key.contains("\n")) { - JOptionPane.showMessageDialog(RobotPreferences.this, "The key cannot containt ' ', '=', tabs or newlines", "Bad Key", JOptionPane.ERROR_MESSAGE); - return false; - } return true; } - public boolean validateValue(String value) { - if (value.contains("\"")) { - JOptionPane.showMessageDialog(RobotPreferences.this, "The value cannot contain '\"'", "Bad Value", JOptionPane.ERROR_MESSAGE); - return false; + public Object validateValue(String value, int typeIndex) { + switch (typeIndex) { + case 0: // Boolean + { + String lower = value.toLowerCase(Locale.ENGLISH); + if (lower.equals("y") || lower.equals("yes") || lower.equals("t") || lower.equals("true") || lower.equals("on") || lower.equals("1")) + return new Boolean(true); + else if (lower.equals("n") || lower.equals("no") || lower.equals("f") || lower.equals("false") || lower.equals("off") || lower.equals("0")) + return new Boolean(false); + else + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid boolean value; expected one of yes, true, 1, no, false, 0", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + case 1: // Number + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid number value", "Bad Value", JOptionPane.ERROR_MESSAGE); + } + break; + case 2: // String + try { + return unescapeString(value); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid string: " + e.getMessage(), "Bad Value", JOptionPane.ERROR_MESSAGE); + } + case 3: // Raw + { + value = value.trim(); + if (value.equals("[]")) + return new byte[0]; + if (!value.startsWith("[")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing [", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + if (!value.endsWith("]")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing ]", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + + String[] arr = value.substring(1, value.length() - 1).split(","); + byte[] barr = new byte[arr.length]; + for (int i=0; i < arr.length; i++) { + try { + barr[i] = Byte.parseByte(arr[i].trim()); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid number value at index " + i + ": '" + arr[i].trim() + "'", "Bad Value", JOptionPane.ERROR_MESSAGE); + return null; + } + } + return barr; + } + case 4: // Boolean[] + { + value = value.trim(); + if (value.equals("[]")) + return new boolean[0]; + if (!value.startsWith("[")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing [", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + if (!value.endsWith("]")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing ]", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + + String[] arr = value.substring(1, value.length() - 1).split(","); + boolean[] barr = new boolean[arr.length]; + for (int i=0; i < arr.length; i++) { + String lower = arr[i].trim().toLowerCase(Locale.ENGLISH); + if (lower.equals("y") || lower.equals("yes") || lower.equals("t") || lower.equals("true") || lower.equals("on") || lower.equals("1")) + barr[i] = true; + else if (lower.equals("n") || lower.equals("no") || lower.equals("f") || lower.equals("false") || lower.equals("off") || lower.equals("0")) + barr[i] = false; + else { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid boolean value at index " + i + ": '" + arr[i].trim() + "'; expected one of yes, true, 1, no, false, 0", "Bad Value", JOptionPane.ERROR_MESSAGE); + return null; + } + } + return barr; + } + case 5: // Number[] + { + value = value.trim(); + if (value.equals("[]")) + return new double[0]; + if (!value.startsWith("[")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing [", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + if (!value.endsWith("]")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing ]", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + + String[] arr = value.substring(1, value.length() - 1).split(","); + double[] darr = new double[arr.length]; + for (int i=0; i < arr.length; i++) { + try { + darr[i] = Double.parseDouble(arr[i].trim()); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid number value at index " + i + ": '" + arr[i].trim() + "'", "Bad Value", JOptionPane.ERROR_MESSAGE); + return null; + } + } + return darr; + } + case 6: // String[] + { + value = value.trim(); + if (value.equals("[]")) + return new String[0]; + if (!value.startsWith("[")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing [", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + if (!value.endsWith("]") || (value.endsWith("\\]") && !value.endsWith("\\\\]"))) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: missing ]", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + if (value.contains("]]]")) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Invalid array: unescaped ]", "Bad Value", JOptionPane.ERROR_MESSAGE); + break; + } + // need to replace with unique string to make "\\" at end of value work + value = value.replaceAll("\\\\\\\\", "]]]"); + // use look-behind assertion to avoid matching "\," + String[] arr = value.substring(1, value.length() - 1).split("(? entry = getRow(rowIndex); + Map.Entry entry = getRow(rowIndex); if (entry != null) { String oldName = entry.getKey(); - String value = entry.getValue(); + Object value = entry.getValue(); if (!oldName.equals(aValue.toString())) { if (!values.containsKey(aValue.toString())) { if(put(aValue.toString(), value)) @@ -198,31 +544,84 @@ public class RobotPreferences extends StaticWidget implements ITableListener { } } } - } else {//Value - Map.Entry entry = getRow(rowIndex); + } else if (columnIndex == 1) {//Value + Map.Entry entry = getRow(rowIndex); if (entry != null) { - put(entry.getKey(), aValue.toString()); + putString(entry.getKey(), aValue.toString(), getTypeName(entry.getValue())); } } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - return rowIndex >= 0; + return rowIndex >= 0 && columnIndex < 2; } public Object getValueAt(int rowIndex, int columnIndex) { - Map.Entry entry = getRow(rowIndex); + Map.Entry entry = getRow(rowIndex); if (entry != null) { - return columnIndex == 0 ? entry.getKey() : entry.getValue(); + switch (columnIndex) { + case 0: + return entry.getKey(); + case 1: + { + Object value = entry.getValue(); + if (value instanceof Boolean) return ((Boolean)value).toString(); + if (value instanceof Double) return ((Double)value).toString(); + if (value instanceof String) return escapeString((String)value, false); + if (value instanceof byte[]) return Arrays.toString((byte[])value); + if (value instanceof boolean[]) return Arrays.toString((boolean[])value); + if (value instanceof double[]) return Arrays.toString((double[])value); + if (value instanceof String[]) { + String[] a = (String[])value; + int imax = a.length - 1; + if (imax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + escapeString(b, a[i], true); + if (i == imax) + return b.append(']').toString(); + b.append(", "); + } + } + break; + } + case 2: + return getTypeName(entry.getValue()); + default: break; + } } return "ERROR"; } + + public void save(String filename) { + try { + NetworkTable.savePersistent(filename); + } catch (PersistentException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Error saving file:" + e.getMessage(), "Save Preferences", JOptionPane.ERROR_MESSAGE); + } + } + + public void load(String filename) { + String[] warnings; + try { + warnings = NetworkTable.loadPersistent(filename); + if (warnings.length > 0) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Warning loading file:" + Arrays.toString(warnings), "Load Preferences", JOptionPane.WARNING_MESSAGE); + } + } catch (PersistentException e) { + JOptionPane.showMessageDialog(RobotPreferences.this, "Error loading file:" + e.getMessage(), "Load Preferences", JOptionPane.ERROR_MESSAGE); + } + } } private class NewPreferenceEntryDialog extends JDialog { private JTextField keyField; + private JComboBox typeComboBox; private JTextField valueField; private JButton addButton; private JButton cancelButton; @@ -243,6 +642,12 @@ public class RobotPreferences extends StaticWidget implements ITableListener { c.gridx = 0; c.gridy = 1; + add(new JLabel("Type: "), c); + c.gridx = 1; + add(typeComboBox = new JComboBox(typeNames), c); + + c.gridx = 0; + c.gridy = 2; add(new JLabel("Value: "), c); c.gridx = 1; add(valueField = new JTextField(10), c); @@ -254,7 +659,7 @@ public class RobotPreferences extends StaticWidget implements ITableListener { public void actionPerformed(ActionEvent e) { if (!values.containsKey(getKey())) { - if (model.validateKey(getKey()) && model.validateValue(getValue())) { + if (model.validateKey(getKey()) && model.validateValue(getValue(), getTypeIndex(getValueType())) != null) { canceled = false; dispose(); } @@ -274,7 +679,7 @@ public class RobotPreferences extends StaticWidget implements ITableListener { }); c.gridx = 0; - c.gridy = 2; + c.gridy = 3; c.gridwidth = 2; add(buttonPanel, c); @@ -294,6 +699,10 @@ public class RobotPreferences extends StaticWidget implements ITableListener { return keyField.getText(); } + public String getValueType() { + return (String)typeComboBox.getSelectedItem(); + } + public String getValue() { return valueField.getText(); } diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/Scheduler.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/Scheduler.java index d61e052..cde41c2 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/Scheduler.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/Scheduler.java @@ -112,7 +112,7 @@ public class Scheduler extends Widget { table.removeTableListener(listener); } table = (ITable) value; - table.addTableListener(listener, true); + table.addTableListenerEx(listener, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); revalidate(); repaint(); diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/bindings/AbstractTableWidget.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/bindings/AbstractTableWidget.java index 99f232a..53fdccb 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/bindings/AbstractTableWidget.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/bindings/AbstractTableWidget.java @@ -41,9 +41,9 @@ public abstract class AbstractTableWidget extends Widget implements ITableListen this.table = table; if(table!=null){ - table.addTableListener(this, true); + table.addTableListenerEx(this, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); if(listenSubtables) - table.addSubTableListener(this); + table.addSubTableListener(this, true); } } } diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/livewindow/elements/LWSubsystem.java b/smartdashboard/src/edu/wpi/first/smartdashboard/livewindow/elements/LWSubsystem.java index a09a60d..a42226c 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/livewindow/elements/LWSubsystem.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/livewindow/elements/LWSubsystem.java @@ -119,7 +119,7 @@ public class LWSubsystem extends AbstractTableWidget { } if (!alreadyHasWidget) { - table.addTableListener("~TYPE~", new ITableListener() { + table.addTableListenerEx("~TYPE~", new ITableListener() { public void valueChanged(final ITable typeSource, final String typeKey, final Object typeValue, final boolean typeIsNew) { table.removeTableListener(this); SwingUtilities.invokeLater(new Runnable() { @@ -128,7 +128,7 @@ public class LWSubsystem extends AbstractTableWidget { } }); } - }, true); + }, ITable.NOTIFY_IMMEDIATE | ITable.NOTIFY_LOCAL | ITable.NOTIFY_NEW | ITable.NOTIFY_UPDATE); } } diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/robot/Robot.java b/smartdashboard/src/edu/wpi/first/smartdashboard/robot/Robot.java index 3d2a6c8..cb7a67b 100644 --- a/smartdashboard/src/edu/wpi/first/smartdashboard/robot/Robot.java +++ b/smartdashboard/src/edu/wpi/first/smartdashboard/robot/Robot.java @@ -3,8 +3,6 @@ package edu.wpi.first.smartdashboard.robot; import java.io.*; import edu.wpi.first.wpilibj.networktables.*; -import edu.wpi.first.wpilibj.networktables2.client.*; -import edu.wpi.first.wpilibj.networktables2.stream.*; import edu.wpi.first.wpilibj.tables.*; /** @@ -17,23 +15,14 @@ public class Robot { public static final String TABLE_NAME = "SmartDashboard"; public static final String LIVE_WINDOW_NAME = "LiveWindow"; public static final String PREFERENCES_NAME = "Preferences"; + public static final String identity = "SmartDashboard"; - private static volatile String _host = null; + private static volatile String _host = ""; private static volatile int _port = NetworkTable.DEFAULT_PORT; private static int _team; private static boolean _usemDNS = true; - private static final IOStreamFactory configurableFactory = new IOStreamFactory() { - @Override - public IOStream createStream() throws IOException { - if(_host==null) - return null; - return new SocketStream(_host, _port); - } - }; - public static final NetworkTableClient client = new NetworkTableClient(configurableFactory); - private static final NetworkTableProvider provider = new NetworkTableProvider(client); static{ - NetworkTable.setTableProvider(provider); + NetworkTable.setClientMode(); } public static void setTeam(int team) { @@ -59,10 +48,16 @@ public class Robot { } else { host = "10." + (_team / 100) + "." + (_team % 100) + ".2"; } - + if (_host.equals(host)) return; _host = host; System.out.println("Host: "+host); - client.close(); + try { + NetworkTable.shutdown(); + } catch (IllegalStateException ex) { + } + NetworkTable.setIPAddress(host); + NetworkTable.setNetworkIdentity(identity); + NetworkTable.initialize(); } public static String getHost() { @@ -70,30 +65,38 @@ public class Robot { } public static void setPort(int port){ + if (_port == port) return; _port = port; - client.close(); + try { + NetworkTable.shutdown(); + } catch (IllegalStateException ex) { + } + NetworkTable.setPort(port); + NetworkTable.initialize(); } public static ITable getTable(String tableName) { - return provider.getRootTable().getSubTable(tableName); + return NetworkTable.getTable(tableName); } public static ITable getTable() { - return provider.getRootTable().getSubTable(TABLE_NAME); + return NetworkTable.getTable(TABLE_NAME); } public static ITable getPreferences() { - return provider.getRootTable().getSubTable(PREFERENCES_NAME); + return NetworkTable.getTable(PREFERENCES_NAME); } public static ITable getLiveWindow() { - return provider.getRootTable().getSubTable(LIVE_WINDOW_NAME); + return NetworkTable.getTable(LIVE_WINDOW_NAME); } public static void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) { - client.addConnectionListener(listener, immediateNotify); + System.out.println("Adding connection listener"); + NetworkTable.getTable(TABLE_NAME).addConnectionListener(listener, immediateNotify); } public static void removeConnectionListener(IRemoteConnectionListener listener) { - client.removeConnectionListener(listener); + System.out.println("Removing connection listener"); + NetworkTable.getTable(TABLE_NAME).removeConnectionListener(listener); } } -- cgit v1.2.3