Java tutorial
package lejos.pc.charting; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.SystemColor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ConcurrentLinkedQueue; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.MenuElement; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.text.BadLocationException; import lejos.util.EndianTools; import org.jfree.chart.JFreeChart; import javax.swing.JToggleButton; import javax.swing.ImageIcon; /** The main GUI window for NXT Charting Logger. * @author Kirk P. Thompson */ class LogChartFrame extends JFrame { private final String THISCLASS; private static final int MAXDOMAIN_DATAPOINT_LIMIT = 50000; private static final int MAXDOMAIN_TIME_LIMIT = 30000; private static final int MINDOMAIN_LIMIT = 10; private final static float DOMLIMIT_POW = 2.4f; private JButton jButtonConnect = new JButton(); private JPanel UIPanel = new JPanel(); private JPanel connectionPanel = new JPanel(); private JLabel jLabel1logfilename = new JLabel(); private JTextField jTextFieldNXTName = new JTextField(); private JTextArea jTextAreaStatus = new JTextArea(); private JLabel jLabel5 = new JLabel(); private JTextField logFileTextField = new JTextField(); private JScrollPane statusScrollPane = new JScrollPane(); private JScrollPane dataLogScrollPane = new JScrollPane(); // make sure all non-GUI vars are below the ones added by Jdev private CustomChartPanel customChartPanel = new CustomChartPanel(); private JFreeChart loggingJFreeChart = customChartPanel.getLoggingChartPanel().getChart(); private final LoggerComms connectionManager; private boolean isNXTConnected = false; private File theLogFile; private JMenuBar menuBar = new JMenuBar(); private JMenu menu; private JMenuItem menuItem; private JTabbedPane jTabbedPane1 = new JTabbedPane(); private JTextArea dataLogTextArea = new JTextArea(); private SelfLogger loggerHook = new SelfLogger(); private JTextArea FQPathTextArea = new JTextArea(); private JButton selectFolderButton = new JButton(); private ConcurrentLinkedQueue<String> logDataQueue = new ConcurrentLinkedQueue<String>(); private JPanel chartOptionsPanel = new JPanel(); private JPanel chartDomLimitsPanel = new JPanel(); private JSlider domainDisplayLimitSlider = new JSlider(); private int domainLimitSliderValue = MAXDOMAIN_DATAPOINT_LIMIT; private JRadioButton useTimeRadioButton = new JRadioButton(); private JRadioButton useDataPointsRadioButton = new JRadioButton(); private JCheckBox datasetLimitEnableCheckBox = new JCheckBox(); private JLabel domainLimitLabel = new JLabel(); private GridLayout gridLayout1 = new GridLayout(); private JLabel jLabel1 = new JLabel(); private JLabel jLabel2 = new JLabel(); private JLabel jLabel3 = new JLabel(); private JLabel jLabel4 = new JLabel(); private JLabel jLabel6 = new JLabel(); private JTextField chartTitleTextField = new JTextField(); private JTextField axis1LabelTextField = new JTextField(); private JTextField axis2LabelTextField = new JTextField(); private JTextField axis3LabelTextField = new JTextField(); private JTextField axis4LabelTextField = new JTextField(); private GridBagLayout gridBagLayout1 = new GridBagLayout(); private JCheckBox showCommentsCheckBox = new JCheckBox(); private Timer updateLogTextAreaTimer; private JCheckBox scrollDomainCheckBox = new JCheckBox(); private ExtensionGUIManager eGuiManager = new ExtensionGUIManager(jTabbedPane1); private TunneledMessageManager tmm = new TunneledMessageManager(eGuiManager); private LoggerProtocolManager lpm; private JToggleButton tglbtnpauseplay; /** Default constructor */ public LogChartFrame() { try { jbInit(); } catch (Exception e) { e.printStackTrace(); } System.out.println("Hooking into System.out.."); System.setOut(new redirector(System.out)); System.out.println("creating connectionManager instance"); this.connectionManager = new LoggerComms(); String[] thisClass = this.getClass().getName().split("[\\s\\.]"); THISCLASS = thisClass[thisClass.length - 1]; // manage Range axis label fields state for (int i = 0; i < 4; i++) manageAxisLabel(i); } /** invoked by ChartingLogger on window close to clean up and close connection */ void closeCurrentConnection() { SwingUtilities.invokeLater(new Runnable() { public void run() { jButtonConnect.setText("Connect"); if (connectionManager != null) connectionManager.closeConnection(); isNXTConnected = false; } }); } private void scrollDomainCheckBox_actionPerformed(ActionEvent e) { customChartPanel.getLoggingChartPanel().setDomainScrolling(scrollDomainCheckBox.isSelected()); } /**This class is used to provide listener callbacks from DataLogger. */ private class SelfLogger implements LoggerListener { private long lastUpdate = 0; public void logCommentReceived(int timestamp, String comment) { String theComment = String.format("%1$-1d\t%2$s\n", timestamp, comment); LogChartFrame.this.logDataQueue.add(theComment); customChartPanel.addCommentMarker(timestamp, comment); } /** used to track series defs */ private class SeriesDef { String name; boolean chartable; int axisID; } private SeriesDef[] seriesDefs; private SeriesDef[] parseSeriesDef(String[] logFields) { SeriesDef[] sd = new SeriesDef[logFields.length]; String[] seriesDef; // parse the column defs into a struct for (int i = 0; i < logFields.length; i++) { seriesDef = logFields[i].split("!"); sd[i] = new SeriesDef(); sd[i].name = seriesDef[0]; if (seriesDef.length > 1) { sd[i].chartable = seriesDef[1].equalsIgnoreCase("y"); sd[i].axisID = Integer.valueOf(seriesDef[2]); } else { // coldef structure not correct, use defaults sd[i].chartable = true; sd[i].axisID = 1; } // ensure domain val is always chartable if (i == 0) sd[i].chartable = true; } return sd; } /**Parse chartable datapoints into a double array. Uses previous parsed seriesDefs[] to determine chartable * * @param logDataItems The datatype representational "structs" from the logger * @return array of representative <code>double</code> */ private double[] parseDataPoints(DataItem[] logDataItems) { double[] seriesTempvalues = new double[logDataItems.length]; int chartableCount = 0; for (int i = 0; i < logDataItems.length; i++) { if (seriesDefs[i].chartable) { switch (logDataItems[i].datatype) { case DataItem.DT_BOOLEAN: case DataItem.DT_BYTE: case DataItem.DT_SHORT: case DataItem.DT_INTEGER: seriesTempvalues[chartableCount] = ((Integer) logDataItems[i].value).doubleValue(); break; case DataItem.DT_LONG: seriesTempvalues[chartableCount] = ((Long) logDataItems[i].value).doubleValue(); break; case DataItem.DT_FLOAT: seriesTempvalues[chartableCount] = ((Float) logDataItems[i].value).doubleValue(); break; case DataItem.DT_DOUBLE: seriesTempvalues[chartableCount] = ((Double) logDataItems[i].value).doubleValue(); break; case DataItem.DT_STRING: chartableCount--; default: System.out.println("Bad datatype!" + logDataItems[i].datatype); } chartableCount++; } } double[] seriesTempvalues2 = new double[chartableCount]; System.arraycopy(seriesTempvalues, 0, seriesTempvalues2, 0, chartableCount); return seriesTempvalues2; } public void logLineAvailable(DataItem[] logDataItems) { // tell the chart it has some data customChartPanel.addDataPoints(parseDataPoints(logDataItems)); // queue text line for log textarea LogChartFrame.this.logDataQueue.add(LoggerProtocolManager.parseLogData(logDataItems)); if (this.lastUpdate == 0) this.lastUpdate = System.currentTimeMillis() - 1; // variable textarea update timer delay based on update rate int period = (int) (System.currentTimeMillis() - lastUpdate); period = (int) (2155.1 * Math.exp(-.0024 * period)); if (period < 200) period = 200; LogChartFrame.this.updateLogTextAreaTimer.setDelay(period); lastUpdate = System.currentTimeMillis(); } public void dataInputStreamEOF() { closeCurrentConnection(); // allows user to use interactive stuff without chart glitch System.out.println("Finalizing chart"); loggingJFreeChart.setNotify(true); customChartPanel.getLoggingChartPanel().setChartDirty(); tmm.dataInputStreamEOF(); } // * The string format/structure of each string field passed by NXTDataLogger is:<br> // * <code>[name]![y or n to indicate if charted]![axis ID 1-4]</code> // * <br>i.e. <pre>"MySeries!y!1"</pre> public void logFieldNamesChanged(String[] logFields) { System.out.println("client:logFieldNamesChanged"); StringBuilder chartLabels = new StringBuilder(); StringBuilder sb = new StringBuilder(); // parse the array into a struct this.seriesDefs = parseSeriesDef(logFields); // Build headers/labels/series names for txt log and chartable for chart for (int i = 0; i < this.seriesDefs.length; i++) { sb.append(this.seriesDefs[i].name); if (i < this.seriesDefs.length - 1) sb.append("\t"); if (this.seriesDefs[i].chartable) { chartLabels.append(this.seriesDefs[i].name); chartLabels.append(":"); chartLabels.append(this.seriesDefs[i].axisID); chartLabels.append("!"); } } sb.append("\n"); try { // clear the data log text area dataLogTextArea.getDocument().remove(0, dataLogTextArea.getDocument().getLength()); } catch (BadLocationException e) { // TODO what to do here? I'm pretty sure the try(...) code should never throw this... } LogChartFrame.this.logDataQueue.add(sb.toString()); // set the chartable series headers/labels customChartPanel.setSeries(chartLabels.toString().split("!")); if (theLogFile != null) { if (theLogFile.isFile()) { setChartTitle(getCanonicalName(theLogFile)); } else { setChartTitle("Run " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ").format(new Date())); } } // manange Range axis label fields state for (int i = 0; i < 4; i++) manageAxisLabel(i); } public void tunneledMessageReceived(byte[] message) { tmm.processMessage(message); } } private void manageAxisLabel(int AxisIndex) { JLabel tempLabel; JTextField tempTextField; switch (AxisIndex) { case 0: tempLabel = jLabel2; tempTextField = axis1LabelTextField; break; case 1: tempLabel = jLabel3; tempTextField = axis2LabelTextField; break; case 2: tempLabel = jLabel4; tempTextField = axis3LabelTextField; break; case 3: tempLabel = jLabel6; tempTextField = axis4LabelTextField; break; default: return; } boolean axisExists = loggingJFreeChart.getXYPlot().getRangeAxis(AxisIndex) != null; tempTextField.setEnabled(axisExists); tempLabel.setEnabled(axisExists); if (!axisExists) return; String chartAxisLabel = loggingJFreeChart.getXYPlot().getRangeAxis(AxisIndex).getLabel(); String customLabel = tempTextField.getText(); if (customLabel.equals("")) { tempTextField.setText(chartAxisLabel); } else { loggingJFreeChart.getXYPlot().getRangeAxis(AxisIndex).setLabel(customLabel); } } /** enables "forking" of STDOUT */ private class redirector extends PrintStream { public redirector(OutputStream out) { super(out); } @Override public void println(String x) { print(x + "\n"); } @Override public void write(int b) { } @Override public void print(String x) { super.print(x); toStatus(x); } private synchronized void toStatus(String x) { final String fx = x; SwingUtilities.invokeLater(new Runnable() { public void run() { try { jTextAreaStatus.getDocument().insertString(jTextAreaStatus.getDocument().getLength(), fx, null); } catch (BadLocationException e) { // TODO Do we really need to output this? System.out.print("BadLocationException:" + e.toString() + "\n"); } } }); } } private void setChartTitle(String title) { loggingJFreeChart.setTitle(title); loggingJFreeChart.setNotify(true); if (chartTitleTextField.getText().equals(title)) return; chartTitleTextField.setText(title); } private class MenuEventListener implements MenuListener { private JMenuItem getMenuItem(String menuFind, String menuItemFind) { MenuElement[] menus = menuBar.getSubElements(); JMenu menu = null; for (int i = 0; i < menus.length; i++) { menu = (JMenu) menus[i]; if (!menu.getActionCommand().equals(menuFind)) continue; MenuElement[] menuItems = menu.getPopupMenu().getSubElements(); JMenuItem menuItem = null; for (int j = 0; j < menuItems.length; j++) { menuItem = (JMenuItem) menuItems[j]; if (menuItem.getActionCommand().equalsIgnoreCase(menuItemFind)) return menuItem; } } return null; } public void menuSelected(MenuEvent e) { JMenu menu = (JMenu) e.getSource(); if (menu.getActionCommand().equals("VIEW_MENU")) { JMenuItem tempMenuItem = getMenuItem(menu.getActionCommand(), "Chart in New Window"); if (tempMenuItem == null) { return; } if (customChartPanel.getLoggingChartPanel().isEmptyChart()) { tempMenuItem.setEnabled(false); } else { tempMenuItem.setEnabled(true); } } } public void menuDeselected(MenuEvent e) { } public void menuCanceled(MenuEvent e) { } } // to handle menu events private class MenuActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equalsIgnoreCase("about")) new LicenseDialog(LogChartFrame.this).setVisible(true); if (e.getActionCommand().equalsIgnoreCase("generate sample data")) { LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new Thread(new Runnable() { public void run() { populateSampleData(); } }).start(); } if (e.getActionCommand().equalsIgnoreCase("chart controls")) new UsageHelpDialog(LogChartFrame.this).setVisible(true); if (e.getActionCommand().equalsIgnoreCase("copy chart image")) { LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { customChartPanel.copyChart(); } catch (OutOfMemoryError e2) { JOptionPane.showMessageDialog(LogChartFrame.this, "Not enough memory to copy chart image!", "Houston, we have a problem...", JOptionPane.ERROR_MESSAGE); } LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } if (e.getActionCommand().equalsIgnoreCase("Copy Data Log")) { LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); int curPos = dataLogTextArea.getCaretPosition(); try { dataLogTextArea.selectAll(); dataLogTextArea.copy(); } catch (OutOfMemoryError e2) { JOptionPane.showMessageDialog(LogChartFrame.this, "Not enough memory to copy log data!", "Houston, we have a problem...", JOptionPane.ERROR_MESSAGE); } catch (Exception e2) { JOptionPane.showMessageDialog(LogChartFrame.this, "Problem copying log data! " + e2.toString(), "Houston, we have a problem...", JOptionPane.ERROR_MESSAGE); } dataLogTextArea.setCaretPosition(curPos); LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } if (e.getActionCommand().equalsIgnoreCase("Expand Chart")) { JMenuItem mi = (JMenuItem) e.getSource(); mi.setText("Restore Chart"); mi.setMnemonic(KeyEvent.VK_R); UIPanel.setVisible(false); jTabbedPane1.setVisible(false); } if (e.getActionCommand().equalsIgnoreCase("Restore Chart")) { JMenuItem mi = (JMenuItem) e.getSource(); mi.setText("Expand Chart"); mi.setMnemonic(KeyEvent.VK_X); UIPanel.setVisible(true); jTabbedPane1.setVisible(true); } if (e.getActionCommand().equalsIgnoreCase("Chart in New Window")) { LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { customChartPanel.getLoggingChartPanel().spawnChartCopy(); } catch (OutOfMemoryError e2) { JOptionPane.showMessageDialog(LogChartFrame.this, "Not enough memory to create chart!", "Houston, we have a problem...", JOptionPane.ERROR_MESSAGE); } LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } // to allow keystroke-by-keystroke updating of Chart Title and range axis labels private class titleLabelChangeNotifier implements DocumentListener { public void insertUpdate(DocumentEvent e) { setText(e); } public void removeUpdate(DocumentEvent e) { setText(e); } public void changedUpdate(DocumentEvent e) { setText(e); } private void setText(DocumentEvent e) { if (e.getDocument() == chartTitleTextField.getDocument()) { setChartTitle(chartTitleTextField.getText()); } else if (e.getDocument() == axis1LabelTextField.getDocument()) { setAxisLabel(0, axis1LabelTextField.getText()); } else if (e.getDocument() == axis2LabelTextField.getDocument()) { setAxisLabel(1, axis2LabelTextField.getText()); } else if (e.getDocument() == axis3LabelTextField.getDocument()) { setAxisLabel(2, axis3LabelTextField.getText()); } else if (e.getDocument() == axis4LabelTextField.getDocument()) { setAxisLabel(3, axis4LabelTextField.getText()); } } private void setAxisLabel(int rangeAxisID, String title) { try { loggingJFreeChart.getXYPlot().getRangeAxis(rangeAxisID).setLabel(title); loggingJFreeChart.setNotify(true); } catch (NullPointerException e) { ; //ignore } } } /** All the setup of components, etc. What's scary is Swing is a "lightweight" GUI framework... * @throws Exception */ private void jbInit() throws Exception { this.setJMenuBar(menuBar); this.setSize(new Dimension(819, 613)); this.setMinimumSize(new Dimension(819, 613)); this.setTitle("NXT Charting Logger"); this.setEnabled(true); // enforce minimum window size this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { JFrame theFrame = (JFrame) e.getSource(); Dimension d1 = theFrame.getMinimumSize(); Dimension d2 = theFrame.getSize(); boolean enforce = false; if (theFrame.getWidth() < d1.getWidth()) { d2.setSize(d1.getWidth(), d2.getHeight()); enforce = true; } if (theFrame.getHeight() < d1.getHeight()) { d2.setSize(d2.getWidth(), d1.getHeight()); enforce = true; } if (enforce) theFrame.setSize(d2); } }); this.getContentPane().setLayout(gridBagLayout1); MenuActionListener menuItemActionListener = new MenuActionListener(); MenuEventListener menuListener = new MenuEventListener(); menu = new JMenu("Edit"); menu.setMnemonic(KeyEvent.VK_E); menuBar.add(menu); menuItem = new JMenuItem("Copy Chart Image", KeyEvent.VK_I); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menuItem = new JMenuItem("Copy Data Log", KeyEvent.VK_D); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menu = new JMenu("View"); menu.setMnemonic(KeyEvent.VK_V); menu.setActionCommand("VIEW_MENU"); menu.addMenuListener(menuListener); menuBar.add(menu); menuItem = new JMenuItem("Expand Chart", KeyEvent.VK_F); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menuItem = new JMenuItem("Chart in New Window", KeyEvent.VK_N); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menu = new JMenu("Help"); menu.setMnemonic(KeyEvent.VK_H); menuBar.add(menu); menuItem = new JMenuItem("Chart controls", KeyEvent.VK_C); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menuItem = new JMenuItem("Generate sample data", KeyEvent.VK_G); menuItem.addActionListener(menuItemActionListener); menu.add(menuItem); menuItem = new JMenuItem("About", KeyEvent.VK_A); menuItem.addActionListener(menuItemActionListener); jTabbedPane1.setPreferredSize(new Dimension(621, 199)); jTabbedPane1.setMinimumSize(new Dimension(621, 199)); menu.add(menuItem); jButtonConnect.setText("Connect"); jButtonConnect.setBounds(new Rectangle(25, 65, 115, 25)); jButtonConnect.setToolTipText("Connect/disconnect toggle"); jButtonConnect.setMnemonic('C'); jButtonConnect.setSelected(true); jButtonConnect.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jButtonConnect_actionPerformed(e); } }); UIPanel.setSize(new Dimension(820, 200)); UIPanel.setLayout(null); UIPanel.setPreferredSize(new Dimension(300, 200)); UIPanel.setMinimumSize(new Dimension(300, 200)); UIPanel.setBounds(new Rectangle(0, 350, 820, 200)); UIPanel.setMaximumSize(new Dimension(300, 32767)); connectionPanel.setBounds(new Rectangle(10, 10, 175, 100)); connectionPanel.setBorder(BorderFactory.createTitledBorder("Connection")); connectionPanel.setLayout(null); connectionPanel.setFont(new Font("Tahoma", 0, 11)); jLabel1logfilename.setText("Log File:"); jLabel1logfilename.setBounds(new Rectangle(10, 125, 165, 20)); jLabel1logfilename.setHorizontalTextPosition(SwingConstants.RIGHT); jLabel1logfilename.setHorizontalAlignment(SwingConstants.LEFT); jLabel1logfilename.setToolTipText("Specify the name of your log file here"); jTextFieldNXTName.setBounds(new Rectangle(5, 40, 165, 20)); jTextFieldNXTName.setToolTipText( "The name or Address of the NXT. Leave empty and the first one found will be used."); jTextFieldNXTName.requestFocus(); jTextAreaStatus.setLineWrap(true); jTextAreaStatus.setFont(new Font("Tahoma", 0, 11)); jTextAreaStatus.setWrapStyleWord(true); jTextAreaStatus.setBackground(SystemColor.window); dataLogTextArea.setLineWrap(false); dataLogTextArea.setFont(new Font("Tahoma", 0, 11)); dataLogTextArea.setBackground(SystemColor.window); FQPathTextArea.setBounds(new Rectangle(5, 170, 185, 40)); FQPathTextArea.setLineWrap(true); FQPathTextArea.setText(getCanonicalName(new File(".", ""))); FQPathTextArea.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); FQPathTextArea.setRows(2); FQPathTextArea.setFont(new Font("Tahoma", 0, 9)); FQPathTextArea.setOpaque(false); FQPathTextArea.setEditable(false); selectFolderButton.setText("Folder..."); selectFolderButton.setBounds(new Rectangle(120, 125, 70, 20)); selectFolderButton.setMargin(new Insets(1, 1, 1, 1)); selectFolderButton.setFocusable(false); selectFolderButton.setMnemonic('F'); selectFolderButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { selectFolderButton_actionPerformed(e); } }); // domain display limits GUI chartOptionsPanel.setLayout(null); chartDomLimitsPanel.setBounds(new Rectangle(5, 35, 180, 135)); chartDomLimitsPanel.setLayout(gridLayout1); chartDomLimitsPanel.setBorder(BorderFactory.createTitledBorder("Domain Display Limiting")); domainDisplayLimitSlider.setEnabled(false); domainDisplayLimitSlider.setMaximum(MAXDOMAIN_DATAPOINT_LIMIT); domainDisplayLimitSlider.setMinimum(MINDOMAIN_LIMIT); domainDisplayLimitSlider.setValue(MAXDOMAIN_DATAPOINT_LIMIT); domainDisplayLimitSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { domainDisplayLimitSlider_stateChanged(e); } }); useTimeRadioButton.setText("By Time"); useTimeRadioButton.setEnabled(false); useTimeRadioButton.setMnemonic('I'); useTimeRadioButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { domainDisplayLimitRadioButton_actionPerformed(e); } }); useDataPointsRadioButton.setText("By Data Points"); ButtonGroup bg1 = new ButtonGroup(); bg1.add(useTimeRadioButton); bg1.add(useDataPointsRadioButton); useDataPointsRadioButton.setSelected(true); useDataPointsRadioButton.setEnabled(false); useDataPointsRadioButton.setMnemonic('P'); useDataPointsRadioButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { domainDisplayLimitRadioButton_actionPerformed(e); } }); datasetLimitEnableCheckBox.setText("Enable"); datasetLimitEnableCheckBox.setRolloverEnabled(true); datasetLimitEnableCheckBox.setMnemonic('A'); datasetLimitEnableCheckBox.setToolTipText("Enable Domain Clipping"); datasetLimitEnableCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { datasetLimitEnableCheckBox_actionPerformed(e); } }); domainLimitLabel.setText(String.format("%1$,d datapoints", MAXDOMAIN_DATAPOINT_LIMIT).toString()); domainLimitLabel.setEnabled(false); gridLayout1.setRows(5); gridLayout1.setColumns(1); jLabel1.setText("Chart Title:"); jLabel1.setBounds(new Rectangle(200, 10, 85, 20)); jLabel1.setPreferredSize(new Dimension(115, 14)); jLabel2.setText("Range Axis 1 Label:"); jLabel2.setBounds(new Rectangle(200, 35, 115, 20)); jLabel2.setSize(new Dimension(115, 20)); jLabel3.setText("Range Axis 2 Label:"); jLabel3.setBounds(new Rectangle(200, 60, 115, 20)); jLabel3.setSize(new Dimension(115, 20)); jLabel4.setText("Range Axis 3 Label:"); jLabel4.setBounds(new Rectangle(200, 85, 115, 20)); jLabel4.setSize(new Dimension(115, 20)); jLabel6.setText("Range Axis 4 Label:"); jLabel6.setBounds(new Rectangle(200, 110, 115, 20)); jLabel6.setSize(new Dimension(115, 20)); titleLabelChangeNotifier notifier = new titleLabelChangeNotifier(); chartTitleTextField.setBounds(new Rectangle(315, 10, 290, 20)); chartTitleTextField.getDocument().addDocumentListener(notifier); axis1LabelTextField.setBounds(new Rectangle(315, 35, 290, 20)); axis1LabelTextField.getDocument().addDocumentListener(notifier); axis2LabelTextField.setBounds(new Rectangle(315, 60, 290, 20)); axis2LabelTextField.getDocument().addDocumentListener(notifier); axis3LabelTextField.setBounds(new Rectangle(315, 85, 290, 20)); axis3LabelTextField.getDocument().addDocumentListener(notifier); axis4LabelTextField.setBounds(new Rectangle(315, 110, 290, 20)); showCommentsCheckBox.setText("Show Comment Markers"); showCommentsCheckBox.setBounds(new Rectangle(200, 140, 185, 25)); showCommentsCheckBox.setToolTipText("Show/Hide any comment markers on the chart"); showCommentsCheckBox.setRolloverEnabled(true); showCommentsCheckBox.setSelected(true); showCommentsCheckBox.setMnemonic('M'); showCommentsCheckBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { customChartPanel.setCommentsVisible(e.getStateChange() == ItemEvent.SELECTED); } }); scrollDomainCheckBox.setText("Scroll Domain"); scrollDomainCheckBox.setBounds(new Rectangle(10, 5, 175, 20)); scrollDomainCheckBox.setSize(new Dimension(175, 25)); scrollDomainCheckBox.setSelected(true); scrollDomainCheckBox.setMnemonic('O'); scrollDomainCheckBox.setToolTipText("Checked to scroll domain as new data is received"); scrollDomainCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { scrollDomainCheckBox_actionPerformed(e); } }); axis4LabelTextField.getDocument().addDocumentListener(notifier); logFileTextField.setBounds(new Rectangle(10, 145, 180, 20)); logFileTextField.setText("NXTData.txt"); logFileTextField.setPreferredSize(new Dimension(180, 20)); logFileTextField.setToolTipText("File name. Leave empty to not log to file."); statusScrollPane.setOpaque(false); dataLogScrollPane.setOpaque(false); customChartPanel.setMinimumSize(new Dimension(400, 300)); customChartPanel.setPreferredSize(new Dimension(812, 400)); jLabel5.setText("NXT Name/Address:"); jLabel5.setBounds(new Rectangle(5, 20, 160, 20)); jLabel5.setToolTipText(jTextFieldNXTName.getToolTipText()); jLabel5.setHorizontalTextPosition(SwingConstants.RIGHT); jLabel5.setHorizontalAlignment(SwingConstants.LEFT); connectionPanel.add(jTextFieldNXTName, null); connectionPanel.add(jButtonConnect, null); connectionPanel.add(jLabel5, null); dataLogScrollPane.setViewportView(dataLogTextArea); jTabbedPane1.addTab("Data Log", dataLogScrollPane); statusScrollPane.setViewportView(jTextAreaStatus); jTabbedPane1.addTab("Status", statusScrollPane); jTabbedPane1.addTab("Chart", chartOptionsPanel); chartDomLimitsPanel.add(datasetLimitEnableCheckBox, null); chartDomLimitsPanel.add(useDataPointsRadioButton, null); chartDomLimitsPanel.add(useTimeRadioButton, null); chartDomLimitsPanel.add(domainDisplayLimitSlider, null); chartDomLimitsPanel.add(domainLimitLabel, null); chartOptionsPanel.add(scrollDomainCheckBox, null); chartOptionsPanel.add(showCommentsCheckBox, null); chartOptionsPanel.add(axis4LabelTextField, null); chartOptionsPanel.add(axis3LabelTextField, null); chartOptionsPanel.add(axis2LabelTextField, null); chartOptionsPanel.add(axis1LabelTextField, null); chartOptionsPanel.add(chartTitleTextField, null); chartOptionsPanel.add(jLabel6, null); chartOptionsPanel.add(jLabel4, null); chartOptionsPanel.add(jLabel3, null); chartOptionsPanel.add(jLabel2, null); chartOptionsPanel.add(jLabel1, null); chartOptionsPanel.add(chartDomLimitsPanel, null); tglbtnpauseplay = new JToggleButton(""); tglbtnpauseplay.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (lpm == null) return; boolean doPause = false; if (e.getStateChange() == ItemEvent.SELECTED) { doPause = true; } lpm.setReaderPaused(doPause); } }); // tglbtnpauseplay.addChangeListener(new ChangeListener() { // public void stateChanged(ChangeEvent e) { // System.out.println(e.toString()); // //lpm.setReaderPaused(doPause) // } // }); tglbtnpauseplay .setSelectedIcon(new ImageIcon(LogChartFrame.class.getResource("/lejos/pc/charting/play.png"))); tglbtnpauseplay.setIcon(new ImageIcon(LogChartFrame.class.getResource("/lejos/pc/charting/pause.png"))); tglbtnpauseplay.setBounds(571, 135, 30, 30); chartOptionsPanel.add(tglbtnpauseplay); jTabbedPane1.setToolTipTextAt(0, "The tab-delimited log of the data sent from the NXT"); jTabbedPane1.setToolTipTextAt(1, "Status output"); jTabbedPane1.setToolTipTextAt(2, "Chart options"); jTabbedPane1.setMnemonicAt(0, KeyEvent.VK_D); jTabbedPane1.setMnemonicAt(1, KeyEvent.VK_S); jTabbedPane1.setMnemonicAt(2, KeyEvent.VK_T); this.getContentPane().add(customChartPanel, new GridBagConstraints(0, 0, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); this.getContentPane().add(UIPanel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), -107, 0)); this.getContentPane().add(jTabbedPane1, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); UIPanel.add(connectionPanel, null); UIPanel.add(selectFolderButton, null); UIPanel.add(logFileTextField, null); UIPanel.add(jLabel1logfilename, null); UIPanel.add(FQPathTextArea, null); ActionListener taskPerformer = new ActionListener() { public void actionPerformed(ActionEvent evt) { String theData = null; for (;;) { theData = LogChartFrame.this.logDataQueue.poll(); if (theData == null) break; try { dataLogTextArea.getDocument().insertString(dataLogTextArea.getDocument().getLength(), theData, null); } catch (BadLocationException e) { System.out.print( "BadLocationException in datalog textarea updater thread:" + e.toString() + "\n"); } } } }; this.updateLogTextAreaTimer = new Timer(1000, taskPerformer); this.updateLogTextAreaTimer.start(); } /** Attempt to start a connection using a thread so the GUI stays responsive. * @return <code>false</code> if the file action is user-canceled (if file exists, user is * chicken-tested) or connection to NXT fails. */ private boolean makeConnection() { final int fileAction = getFileAppend(); // sets theLogFile if (fileAction == 2) return false; // return if closed or cancel // get the NXT to connect to and try to connect isNXTConnected = this.connectionManager.connect(jTextFieldNXTName.getText()); if (isNXTConnected) { jTextFieldNXTName.setText(this.connectionManager.getConnectedNXTName()); tmm.setDataOutputStream(new DataOutputStream(this.connectionManager.getOutputStream())); new Thread(new Runnable() { private DataLogger dataLogger = null; // Start the logging run with the specifed file public void run() { try { lpm = new LoggerProtocolManager(connectionManager.getInputStream(), connectionManager.getOutputStream()); lpm.addLoggerListener(loggerHook); } catch (IOException e) { System.out.println(THISCLASS + " IOException in makeConnection():" + e); return; } dataLogger = new DataLogger(lpm, theLogFile, fileAction == 1); // if the log file field is empty, the PC logger will handle it. we need to make sure the title is appropo // start the logger try { tglbtnpauseplay.setSelected(false); lpm.startListen(); // will block until logging session ends } catch (IOException e) { System.out.println(THISCLASS + " IOException in makeConnection():" + e); } // remove the ref so we can gc() dataLogger = null; System.gc(); } }).start(); } return isNXTConnected; } private void populateSampleData() { float value = 0, value2 = 0; int x = 0; DataItem[] di = { new DataItem(), new DataItem(), new DataItem() }; di[0].datatype = DataItem.DT_INTEGER; di[1].datatype = DataItem.DT_FLOAT; di[2].datatype = DataItem.DT_FLOAT; loggerHook.logFieldNamesChanged(new String[] { "System_ms!n!1", "Sine!Y!1", "Random!y!2" }); for (int i = 0; i < 10000; i++) { if (i % 100 == 0) value2 = (float) (Math.random() * 5000 - 2500); if (i % 10 == 0) { di[0].value = new Integer(x); di[1].value = new Float(Math.sin(value)); di[2].value = new Float(value2); loggerHook.logLineAvailable(di); if (i == 4000) loggerHook.logCommentReceived(x, "Event 1: This is an example of what NXTDataLogger.writeComment() does."); if (i == 6510) loggerHook.logCommentReceived(x, "Event 2: This illustrates that multiple comment markers can be added to the chart."); if (i == 9000) loggerHook.logCommentReceived(x, "Look! Did you notice that the domain value is displayed in the tooltip? "); x += 10; value += .1f; } } setChartTitle("Sample Multiple Range Axis Dataset"); loggerHook.dataInputStreamEOF(); System.out.println("Sample dataset generation complete"); this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); // // emulate a CMD_INIT_HANDLER command from the NXT lejos.util.LogMessageManager // byte[] buf = {0,2,-1,-1,1}; // EndianTools.encodeShortBE(1, buf, 2); // tmm.tunneledMessageReceived(buf); // // // emulate a CMD_DELIVER_PACKET command from the NXT lejos.util.LogMessageManager // buf = new byte[10]; // buf[0]= 3; // CMD_DELIVER_PACKET // buf[1]= 2; // TYPE_ROBOT_DRIVE // EndianTools.encodeShortBE(6, buf, 2); // Packet size = 6: 2 bytes , 1 float // buf[4]= 1; // Handler uniqID // buf[5]= 0; // handler command: Set Forward // EndianTools.encodeIntBE(Float.floatToIntBits(500.6675f), buf, 6); // System.out.println("emulate: intbits=" + Float.floatToIntBits(500f)); // tmm.tunneledMessageReceived(buf); } private String getCanonicalName(File file) { String FQPFilename; try { FQPFilename = file.getCanonicalPath(); } catch (IOException e) { FQPFilename = ""; } return FQPFilename; } /** if file exists, ask user to append or overwrite. sets this.theLogFile. * @return 0 to replace, 1 to append, 2 to cancel */ private int getFileAppend() { JFrame frame = new JFrame("DialogDemo"); int n = 0; Object[] options = { "Replace it", "Add to it", "Cancel" }; // if the log file field is empty, the PC logger will handle it. we need to make sure the title is appropo this.theLogFile = new File(FQPathTextArea.getText(), logFileTextField.getText()); if (theLogFile.isFile()) { n = JOptionPane.showOptionDialog(frame, "File \"" + getCanonicalName(theLogFile) + "\" exists.\nWhat do you want to do?", "I have a Question...", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, //do not use a custom Icon options, //the titles of buttons options[0]); //default is "Replace it" } frame = null; if (n == JOptionPane.CLOSED_OPTION) n = 2; return n; } /** Attempt to start a connection using a thread so the GUI stays responsive. Manage connect button state * @param e */ private void jButtonConnect_actionPerformed(ActionEvent e) { final ActionEvent ee = e; Runnable doAction = new Runnable() { public void run() { if (jButtonConnect.getText().equals("Connect")) { if (!isNXTConnected) { jButtonConnect.setText("Connecting.."); jButtonConnect.setEnabled(false); LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (makeConnection()) { jButtonConnect.setText("Disconnect"); } else { jButtonConnect.setText("Connect"); } LogChartFrame.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); jButtonConnect.setEnabled(true); } System.out.println(ee.getActionCommand().toString()); } else { closeCurrentConnection(); } } }; new Thread(doAction).start(); } private void selectFolderButton_actionPerformed(ActionEvent e) { this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); JFileChooser jfc = new JFileChooser(new File(FQPathTextArea.getText(), "")); jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); jfc.setApproveButtonText("Select"); jfc.setDialogTitle("Select Directory"); jfc.setDialogType(JFileChooser.OPEN_DIALOG); this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); int returnVal = jfc.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { FQPathTextArea.setText(getCanonicalName(jfc.getSelectedFile())); jfc.setCurrentDirectory(jfc.getSelectedFile()); System.out.println("folder set to \"" + getCanonicalName(jfc.getSelectedFile()) + "\""); } } private void datasetLimitEnableCheckBox_actionPerformed(ActionEvent e) { useDataPointsRadioButton.setEnabled(datasetLimitEnableCheckBox.isSelected()); useTimeRadioButton.setEnabled(datasetLimitEnableCheckBox.isSelected()); domainDisplayLimitSlider.setEnabled(datasetLimitEnableCheckBox.isSelected()); domainLimitLabel.setEnabled(datasetLimitEnableCheckBox.isSelected()); if (datasetLimitEnableCheckBox.isSelected()) { customChartPanel.getLoggingChartPanel(); int mode = LoggingChart.DAL_TIME; if (useDataPointsRadioButton.isSelected()) { customChartPanel.getLoggingChartPanel(); mode = LoggingChart.DAL_COUNT; } customChartPanel.getLoggingChartPanel().setDomainLimiting(mode, this.domainLimitSliderValue); } else { customChartPanel.getLoggingChartPanel(); customChartPanel.getLoggingChartPanel().setDomainLimiting(LoggingChart.DAL_UNLIMITED, 0); } } void fileExit_ActionPerformed(ActionEvent e) { customChartPanel = null; System.gc(); System.exit(0); } private void domainDisplayLimitSlider_stateChanged(ChangeEvent e) { JSlider workingSlider = (JSlider) e.getSource(); String unit = "ms"; customChartPanel.getLoggingChartPanel(); int mode = LoggingChart.DAL_TIME; int maxSliderPerMode = MAXDOMAIN_TIME_LIMIT; if (useDataPointsRadioButton.isSelected()) { unit = "datapoints"; customChartPanel.getLoggingChartPanel(); mode = LoggingChart.DAL_COUNT; maxSliderPerMode = MAXDOMAIN_DATAPOINT_LIMIT; } this.domainLimitSliderValue = workingSlider.getValue(); int working = maxSliderPerMode; if (this.domainLimitSliderValue != maxSliderPerMode) { working = (int) (Math.pow((float) this.domainLimitSliderValue / maxSliderPerMode, DOMLIMIT_POW) * this.domainLimitSliderValue) + MINDOMAIN_LIMIT; } domainLimitLabel.setText(String.format("%1$,d %2$s", working, unit)); if (workingSlider.getValueIsAdjusting()) return; customChartPanel.getLoggingChartPanel().setDomainLimiting(mode, working); } private void domainDisplayLimitRadioButton_actionPerformed(ActionEvent e) { if (e.getSource() == useTimeRadioButton) { domainDisplayLimitSlider.setMaximum(MAXDOMAIN_TIME_LIMIT); domainDisplayLimitSlider.setMinimum(MINDOMAIN_LIMIT); domainDisplayLimitSlider.setValue(MAXDOMAIN_TIME_LIMIT); } else if (e.getSource() == useDataPointsRadioButton) { domainDisplayLimitSlider.setMaximum(MAXDOMAIN_DATAPOINT_LIMIT); domainDisplayLimitSlider.setMinimum(MINDOMAIN_LIMIT); domainDisplayLimitSlider.setValue(MAXDOMAIN_DATAPOINT_LIMIT); } } }