/* * GRBL Firmware Settings. Dynamically load and save all GRBL settings. */ /* Copywrite 2013-2017 Will Winder This file is part of Universal Gcode Sender (UGS). UGS 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 3 of the License, or (at your option) any later version. UGS 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 UGS. If not, see <http://www.gnu.org/licenses/>. */ package com.willwinder.universalgcodesender.uielements; import com.willwinder.universalgcodesender.uielements.components.TableCellListener; import com.willwinder.universalgcodesender.GrblUtils; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.ControllerListener; import com.willwinder.universalgcodesender.listeners.ControllerStatus; import com.willwinder.universalgcodesender.model.BackendAPI; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UGSEvent; import com.willwinder.universalgcodesender.types.GcodeCommand; import java.awt.Frame; import java.awt.event.ActionEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumnModel; /** * * @author wwinder */ public class GrblFirmwareSettingsDialog extends javax.swing.JDialog implements ControllerListener { private static final String ERROR = "Error"; // Controller object to fetch settings from. private final BackendAPI grblController; private final TableCellListener tcl; private int numberOfSettings = 0; //private List<String> commandList; private boolean loadingSettings; private String[] commands = null; private static final int COL_INDEX_SETTING = 0; private static final int COL_INDEX_VALUE = 1; private static final int COL_INDEX_DESCRIPTION = 2; private static final String settingNumRegex = "\\$(\\d*)"; private static final String settingValueRegex = " +\\= +(\\d*\\.?\\d*)"; private static final String commentRegex = "\\(.*\\)"; private final Pattern settingNumPattern; private final Pattern settingValuePattern; private final Pattern commentPattern; // These guys are used to save initial settings and determine when they can // be restored. private boolean initialSingleStepMode; private boolean statusUpdatesEnabled; private boolean savingSettings; /** * Creates new form GrblFirmwareSettingsDialog */ public GrblFirmwareSettingsDialog(Frame parent, boolean modal, BackendAPI backend) throws Exception { super(parent, modal); initComponents(); initLocalization(); setLocationRelativeTo(parent); if (backend == null) { throw new Exception("There is no controller. Are you connected?"); } this.grblController = backend; this.grblController.addControllerListener(this); this.loadingSettings = false; // Compile regular expressions. this.settingNumPattern = Pattern.compile(settingNumRegex); this.settingValuePattern = Pattern.compile(settingValueRegex); this.commentPattern = Pattern.compile(commentRegex); initSettings(); Action action = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { TableCellListener tcl = (TableCellListener)e.getSource(); handleSettingChange(tcl.getRow(), tcl.getColumn(), tcl.getOldValue(), tcl.getNewValue()); } }; tcl = new TableCellListener(this.settingsTable, action); } private void initSettings() throws Exception { this.loadingSettings = true; this.grblController.sendGcodeCommand(GrblUtils.GRBL_VIEW_SETTINGS_COMMAND); } private void checkDoneSavingSettings() { if (this.savingSettings) { // If the controller is done sending (we just received the final OK) // then reset the original user settings. if (!this.grblController.isActive()) { // Reset controller to previous settings. //These should not be re-enabled until all ok arrive this.grblController.getController().setSingleStepMode(initialSingleStepMode); this.grblController.getController().setStatusUpdatesEnabled(statusUpdatesEnabled); this.savingSettings = false; } } } /** * We are interested in settings. */ @Override public void messageForConsole(MessageType type, String msg) { // Initially we are in load-settings mode, looking for results to "$$". if (this.loadingSettings) { if ("ok".equals(msg)) { this.loadingSettings = false; } else if (this.isSettingString(msg)) { this.addSetting(msg); // Increment for each setting. this.numberOfSettings++; } } // Later we can be in save-settings mode. if (this.savingSettings) { this.checkDoneSavingSettings(); } } private boolean isSettingString(String msg) { if (msg.startsWith("$$")) { return false; } else if (msg.startsWith("$")) { return true; } return false; } private void addSetting(String msg) { String setting; String value; String comment; Matcher matcher = this.settingNumPattern.matcher(msg); if (matcher.find()) { setting = matcher.group(); } else { return; } matcher = this.settingValuePattern.matcher(msg); if (matcher.find()) { value = matcher.group(1); } else { return; } matcher = this.commentPattern.matcher(msg); if (matcher.find()) { comment = matcher.group(); } else { return; } addSetting(setting, value, comment); } private void addSetting(String setting, String value, String description) { ((DefaultTableModel)this.settingsTable.getModel()).addRow(new String[]{ setting, value, description}); } private void updateSetting(int row, String setting, String value, String description) { this.settingsTable.getModel().setValueAt(setting, row, COL_INDEX_SETTING); this.settingsTable.getModel().setValueAt(value, row, COL_INDEX_VALUE); this.settingsTable.getModel().setValueAt(description, row, COL_INDEX_DESCRIPTION); } private void handleSettingChange(int row, int column, Object oldValue, Object newValue) { // TODO: Initialize the command array the first time a column is edited. if ((this.commands == null) && (this.numberOfSettings > 0)) { this.commands = new String[numberOfSettings]; } // If the user somehow edited a command (thus creating the array) before // all the commands were loaded this situation arises. if (row > this.commands.length) { String before[] = this.commands; this.commands = new String[numberOfSettings]; System.arraycopy(before, 0, this.commands, 0, before.length); } String setting = this.settingsTable.getModel().getValueAt(row, COL_INDEX_SETTING).toString(); String command = setting + "=" + newValue; this.commands[row] = command; } private void initLocalization() { closeButton.setText(Localization.getString("close")); saveButton.setText(Localization.getString("save")); TableColumnModel tcm = settingsTable.getTableHeader().getColumnModel(); tcm.getColumn(0).setHeaderValue(Localization.getString("setting")); tcm.getColumn(1).setHeaderValue(Localization.getString("value")); tcm.getColumn(2).setHeaderValue(Localization.getString("description")); settingsTable.getTableHeader().repaint(); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { saveButton = new javax.swing.JButton(); closeButton = new javax.swing.JButton(); settingsTableScrollPane = new javax.swing.JScrollPane(); settingsTable = new javax.swing.JTable(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); saveButton.setText("Save"); saveButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { saveButtonActionPerformed(evt); } }); closeButton.setText("Close"); closeButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { closeButtonActionPerformed(evt); } }); settingsTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { }, new String [] { "Setting", "Value", "Description" } ) { private Class[] types = new Class [] { java.lang.String.class, java.lang.String.class, java.lang.String.class }; private boolean[] canEdit = new boolean [] { false, true, false }; public Class getColumnClass(int columnIndex) { return types [columnIndex]; } public boolean isCellEditable(int rowIndex, int columnIndex) { return canEdit [columnIndex]; } }); settingsTable.getTableHeader().setReorderingAllowed(false); settingsTableScrollPane.setViewportView(settingsTable); settingsTable.getColumnModel().getColumn(0).setMinWidth(50); settingsTable.getColumnModel().getColumn(0).setMaxWidth(50); settingsTable.getColumnModel().getColumn(1).setPreferredWidth(85); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(saveButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(closeButton) .addContainerGap()) .addComponent(settingsTableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 510, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(settingsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 245, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(saveButton) .addComponent(closeButton)) .addContainerGap()) ); pack(); }// </editor-fold>//GEN-END:initComponents private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed if (this.savingSettings) { JOptionPane.showMessageDialog(new JFrame(), "Cannot close dialog until settings have finished being saved." + "\nIf we got here by mistake open a bug report on github.", ERROR, JOptionPane.ERROR_MESSAGE); } else { this.setVisible(false); } }//GEN-LAST:event_closeButtonActionPerformed private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveButtonActionPerformed // Make sure current cell has been committed by selecting this. this.settingsTable.editCellAt(-1, -1); // If nothing has been edited, don't do anything. if (this.commands == null) return; // Cannot update firmware if the controller is busy. if (this.grblController.isActive()) { JOptionPane.showMessageDialog(new JFrame(), "Cannot update firmware while it is busy.", ERROR, JOptionPane.ERROR_MESSAGE); return; } initialSingleStepMode = this.grblController.getController().getSingleStepMode(); statusUpdatesEnabled = this.grblController.getController().getStatusUpdatesEnabled(); // Single step mode is required for commands which modify GRBL's EEPROM. this.grblController.getController().setSingleStepMode(true); this.grblController.getController().setStatusUpdatesEnabled(false); this.savingSettings = true; // Search command array for commands and send them. try { String command; for (int i=0; i < this.commands.length; i++) { command = this.commands[i]; if (command != null) { // If GRBL is feeling especially quick, we may need to keep // setting these guys. this.grblController.getController().setSingleStepMode(true); this.grblController.getController().setStatusUpdatesEnabled(false); this.grblController.sendGcodeCommand(command); this.commands[i] = null; } } this.commands = null; } catch (Exception ex) { JOptionPane.showMessageDialog(new JFrame(), "Error from firmware while saving settings: " + ex.getMessage(), ERROR, JOptionPane.ERROR_MESSAGE); } }//GEN-LAST:event_saveButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton closeButton; private javax.swing.JButton saveButton; private javax.swing.JTable settingsTable; private javax.swing.JScrollPane settingsTableScrollPane; // End of variables declaration//GEN-END:variables @Override public void controlStateChange(UGSEvent.ControlState state) { } @Override public void fileStreamComplete(String filename, boolean success) { } @Override public void commandComplete(GcodeCommand command) { } @Override public void commandSkipped(GcodeCommand command) { } @Override public void commandSent(GcodeCommand command) { } @Override public void commandComment(String comment) { } @Override public void probeCoordinates(Position p) { } @Override public void statusStringListener(ControllerStatus status) { } @Override public void postProcessData(int numRows) { } }