protected JComponent createBeepPanel() {
    JPanel beepPanel = new JPanel();

    final JFormattedTextField beepFreq = new JFormattedTextField(Base.getLocalFormat());
    final JFormattedTextField beepDur = new JFormattedTextField(Base.getLocalFormat());
    final JButton beepButton = new JButton("Beep Beep!");

    beepFreq.setColumns(5);
    beepDur.setColumns(5);

    final int EFFECT_DO_IMMEDATELY = 0; // /
    beepButton.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent arg0) {
            Base.logger.severe("running sendBeep");
            machine.runCommand(
                new SendBeep(
                    ((Number) beepFreq.getValue()).intValue(),
                    ((Number) beepDur.getValue()).intValue(),
                    EFFECT_DO_IMMEDATELY));
          }
        });

    beepPanel.add(new JLabel("Frequency"), "split");
    beepPanel.add(beepFreq, "growy");
    beepPanel.add(new JLabel("Duration"), "gap unrel");
    beepPanel.add(beepDur, "growx");
    beepPanel.add(beepButton, "gap unrel");
    return beepPanel;
  }
  /** @param driver Needed for the Replicator-specific options */
  public PreferencesWindow(final MachineInterface machine) {
    super("Preferences");
    setResizable(true);

    Image icon = Base.getImage("images/icon.gif", this);
    setIconImage(icon);

    JTabbedPane prefTabs = new JTabbedPane();

    JPanel basic = new JPanel();

    //		Container content = this.getContentPane();
    Container content = basic;
    content.setLayout(new MigLayout("fill"));

    content.add(new JLabel("MainWindow font size: "), "split");
    fontSizeField = new JFormattedTextField(Base.getLocalFormat());
    fontSizeField.setColumns(4);
    content.add(fontSizeField);
    content.add(new JLabel("  (requires restart of ReplicatorG)"), "wrap");

    boolean checkTempDuringBuild = Base.preferences.getBoolean("build.monitor_temp", true);
    boolean displaySpeedWarning = Base.preferences.getBoolean("build.speed_warning", true);

    addCheckboxForPref(
        content, "Monitor temperature during builds", "build.monitor_temp", checkTempDuringBuild);
    addCheckboxForPref(
        content, "Display Accelerated Speed Warnings", "build.speed_warning", displaySpeedWarning);
    addCheckboxForPref(
        content, "Automatically connect to machine at startup", "replicatorg.autoconnect", true);
    addCheckboxForPref(
        content, "Show experimental machine profiles", "machine.showExperimental", false);
    addCheckboxForPref(
        content,
        "Review GCode for potential toolhead problems before building",
        "build.safetyChecks",
        true);
    addCheckboxForPref(
        content,
        "Break Z motion into separate moves (normally false)",
        "replicatorg.parser.breakzmoves",
        false);
    addCheckboxForPref(
        content, "Show starfield in model preview window", "ui.show_starfield", false);
    addCheckboxForPref(
        content, "Notifications in System tray", "ui.preferSystemTrayNotifications", false);
    addCheckboxForPref(
        content,
        "Automatically regenerate gcode when building from model view.",
        "build.autoGenerateGcode",
        true);
    addCheckboxForPref(
        content, "Use native avrdude for uploading code", "uploader.useNative", false);

    JPanel advanced = new JPanel();
    content = advanced;
    content.setLayout(new MigLayout("fill"));

    JButton modelColorButton;
    modelColorButton = new JButton("Choose model color");
    modelColorButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // Note that this color is also defined in EditingModel.java
            Color modelColor = new Color(Base.preferences.getInt("ui.modelColor", -19635));
            modelColor = JColorChooser.showDialog(null, "Choose Model Color", modelColor);
            if (modelColor == null) return;

            Base.preferences.putInt("ui.modelColor", modelColor.getRGB());
            Base.getEditor().refreshPreviewPanel();
          }
        });
    modelColorButton.setVisible(true);
    content.add(modelColorButton, "split");

    JButton backgroundColorButton;
    backgroundColorButton = new JButton("Choose background color");
    backgroundColorButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // Note that this color is also defined in EditingModel.java
            Color backgroundColor = new Color(Base.preferences.getInt("ui.backgroundColor", 0));
            backgroundColor =
                JColorChooser.showDialog(null, "Choose Background Color", backgroundColor);
            if (backgroundColor == null) return;

            Base.preferences.putInt("ui.backgroundColor", backgroundColor.getRGB());
            Base.getEditor().refreshPreviewPanel();
          }
        });
    backgroundColorButton.setVisible(true);
    content.add(backgroundColorButton, "wrap");

    content.add(new JLabel("Firmware update URL: "), "split");
    firmwareUpdateUrlField = new JTextField(34);
    content.add(firmwareUpdateUrlField, "growx, wrap");

    {
      JLabel arcResolutionLabel = new JLabel("Arc resolution (in mm): ");
      content.add(arcResolutionLabel, "split");
      double value = Base.preferences.getDouble("replicatorg.parser.curve_segment_mm", 1.0);
      JFormattedTextField arcResolutionField = new JFormattedTextField(Base.getLocalFormat());
      arcResolutionField.setValue(new Double(value));
      content.add(arcResolutionField);
      String arcResolutionHelp =
          "<html><small><em>"
              + "The arc resolution is the default segment length that the gcode parser will break arc codes <br>"
              + "like G2 and G3 into.  Drivers that natively handle arcs will ignore this setting."
              + "</em></small></html>";
      arcResolutionField.setToolTipText(arcResolutionHelp);
      arcResolutionLabel.setToolTipText(arcResolutionHelp);
      //			content.add(new JLabel(arcResolutionHelp),"growx,wrap");
      arcResolutionField.setColumns(10);
      arcResolutionField.addPropertyChangeListener(
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
              if (evt.getPropertyName() == "value") {
                try {
                  Double v = (Double) evt.getNewValue();
                  if (v == null) return;
                  Base.preferences.putDouble(
                      "replicatorg.parser.curve_segment_mm", v.doubleValue());
                } catch (ClassCastException cce) {
                  Base.logger.warning(
                      "Unexpected value type: " + evt.getNewValue().getClass().toString());
                }
              }
            }
          });
    }

    {
      JLabel sfTimeoutLabel = new JLabel("Skeinforge timeout: ");
      content.add(sfTimeoutLabel, "split, gap unrelated");
      int value = Base.preferences.getInt("replicatorg.skeinforge.timeout", -1);
      JFormattedTextField sfTimeoutField = new JFormattedTextField(Base.getLocalFormat());
      sfTimeoutField.setValue(new Integer(value));
      content.add(sfTimeoutField, "wrap 10px, growx");
      String sfTimeoutHelp =
          "<html><small><em>"
              + "The Skeinforge timeout is the number of seconds that replicatorg will wait while the<br>"
              + "Skeinforge preferences window is open. If you find that RepG freezes after editing profiles<br>"
              + "you can set this number greater than -1 (-1 means no timeout)."
              + "</em></small></html>";
      sfTimeoutField.setToolTipText(sfTimeoutHelp);
      sfTimeoutLabel.setToolTipText(sfTimeoutHelp);
      //			content.add(new JLabel(sfTimeoutHelp),"growx,wrap");
      sfTimeoutField.setColumns(10);
      sfTimeoutField.addPropertyChangeListener(
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
              if (evt.getPropertyName() == "value") {
                try {
                  Integer v = ((Number) evt.getNewValue()).intValue();
                  if (v == null) return;
                  Base.preferences.putInt("replicatorg.skeinforge.timeout", v.intValue());
                } catch (ClassCastException cce) {
                  Base.logger.warning(
                      "Unexpected value type: " + evt.getNewValue().getClass().toString());
                }
              }
            }
          });
    }

    {
      content.add(new JLabel("Debugging level (default INFO):"), "split");
      content.add(makeDebugLevelDropdown(), "wrap");

      final JCheckBox logCb = new JCheckBox("Log to file");
      logCb.setSelected(Base.preferences.getBoolean("replicatorg.useLogFile", false));
      content.add(logCb, "split");
      logCb.addActionListener(
          new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              Base.preferences.putBoolean("replicatorg.useLogFile", logCb.isSelected());
            }
          });

      final JLabel logPathLabel = new JLabel("Log file name: ");
      content.add(logPathLabel, "split");
      logPathField = new JTextField(34);
      content.add(logPathField, "growx, wrap 10px");
      logPathField.setEnabled(logCb.isSelected());
      logPathLabel.setEnabled(logCb.isSelected());

      logCb.addActionListener(
          new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              JCheckBox box = (JCheckBox) e.getSource();
              logPathField.setEnabled(box.isSelected());
              logPathLabel.setEnabled(box.isSelected());
            }
          });
    }

    {
      final int defaultTemp = 75;
      final String tooltipGeneral =
          "When enabled, starting all builds heats components to this temperature";
      final String tooltipHead = "Set preheat temperature for the specified toolhead";
      final String tooltipPlatform = "Set preheat temperature for the build platfom";

      final JCheckBox preheatCb = new JCheckBox("Preheat builds");
      preheatCb.setToolTipText(tooltipGeneral);
      content.add(preheatCb, "split");

      preheatCb.addActionListener(
          new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
              Base.preferences.putBoolean("build.doPreheat", preheatCb.isSelected());
            }
          });
      preheatCb.setSelected(Base.preferences.getBoolean("build.doPreheat", false));

      final JLabel t0Label = new JLabel("Toolhead Right: ");
      final JLabel t1Label = new JLabel("Toolhead Left: ");
      final JLabel pLabel = new JLabel("Platform: ");

      Integer t0Value = Base.preferences.getInt("build.preheatTool0", defaultTemp);
      Integer t1Value = Base.preferences.getInt("build.preheatTool1", defaultTemp);
      Integer pValue = Base.preferences.getInt("build.preheatPlatform", defaultTemp);

      final JFormattedTextField t0Field = new JFormattedTextField(Base.getLocalFormat());
      final JFormattedTextField t1Field = new JFormattedTextField(Base.getLocalFormat());
      final JFormattedTextField pField = new JFormattedTextField(Base.getLocalFormat());

      t0Field.setToolTipText(tooltipHead);
      t0Label.setToolTipText(tooltipHead);
      t1Field.setToolTipText(tooltipHead);
      t1Label.setToolTipText(tooltipHead);
      pField.setToolTipText(tooltipPlatform);
      pLabel.setToolTipText(tooltipPlatform);

      t0Field.setValue(t0Value);
      t1Field.setValue(t1Value);
      pField.setValue(pValue);

      // let's avoid creating too many Anon. inner Listeners, also is fewer lines (and just as
      // clear)!
      PropertyChangeListener p =
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
              if (evt.getPropertyName() == "value") {
                double target;
                if (evt.getSource() == t0Field) {
                  target = ((Number) t0Field.getValue()).doubleValue();
                  target = confirmTemperature(target, "temperature.acceptedLimit", 200.0);
                  if (target == Double.MIN_VALUE) {
                    t0Field.setValue(Base.preferences.getInt("build.preheatTool0", defaultTemp));
                    return;
                  }
                  Base.preferences.putInt("build.preheatTool0", (int) target);
                } else if (evt.getSource() == t1Field) {
                  target = ((Number) t1Field.getValue()).doubleValue();
                  target = confirmTemperature(target, "temperature.acceptedLimit", 200.0);
                  if (target == Double.MIN_VALUE) {
                    t0Field.setValue(Base.preferences.getInt("build.preheatTool1", defaultTemp));
                    return;
                  }
                  Base.preferences.putInt("build.preheatTool1", (int) target);
                } else if (evt.getSource() == pField) {
                  target = ((Number) pField.getValue()).doubleValue();
                  target = confirmTemperature(target, "temperature.acceptedLimit.bed", 110.0);
                  if (target == Double.MIN_VALUE) {
                    t0Field.setValue(Base.preferences.getInt("build.preheatPlatform", defaultTemp));
                    return;
                  }
                  Base.preferences.putInt("build.preheatPlatform", (int) target);
                }
              }
            }
          };

      t0Field.addPropertyChangeListener(p);
      t1Field.addPropertyChangeListener(p);
      pField.addPropertyChangeListener(p);

      content.add(t0Label, "split, gap 20px");
      content.add(t0Field, "split, growx");
      content.add(t1Label, "split, gap unrelated");
      content.add(t1Field, "split, growx");
      content.add(pLabel, "split, gap unrelated");
      content.add(pField, "split, growx, wrap 10px");
    }

    {
      JButton b = new JButton("Select Python interpreter...");
      content.add(b, "spanx,wrap 10px");
      b.addActionListener(
          new ActionListener() {
            public void actionPerformed(ActionEvent e) {
              SwingPythonSelector sps = new SwingPythonSelector(PreferencesWindow.this);
              String path = sps.selectFreeformPath();
              if (path != null) {
                PythonUtils.setPythonPath(path);
              }
            }
          });
    }

    addInitialFilePrefs(content);

    prefTabs.add(basic, "Basic");
    prefTabs.add(advanced, "Advanced");

    content = getContentPane();
    content.setLayout(new MigLayout());

    content.add(prefTabs, "wrap");

    JButton allPrefs = new JButton("View Preferences Table");
    content.add(allPrefs, "split");
    allPrefs.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            JFrame advancedPrefs = new AdvancedPrefs();
            advancedPrefs.setVisible(true);
          }
        });

    // Also available as a menu item in the main gui.
    JButton delPrefs = new JButton("Reset all preferences");
    content.add(delPrefs);
    delPrefs.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            editor.resetPreferences();
          }
        });

    JButton button;
    button = new JButton("Close");
    button.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            applyFrame();
            dispose();
          }
        });
    content.add(button, "tag ok");

    showCurrentSettings();

    // closing the window is same as hitting cancel button
    addWindowListener(
        new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            dispose();
          }
        });

    ActionListener disposer =
        new ActionListener() {
          public void actionPerformed(ActionEvent actionEvent) {
            dispose();
          }
        };
    Base.registerWindowCloseKeys(getRootPane(), disposer);

    pack();
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    setLocation((screen.width - getWidth()) / 2, (screen.height - getHeight()) / 2);

    // handle window closing commands for ctrl/cmd-W or hitting ESC.

    getContentPane()
        .addKeyListener(
            new KeyAdapter() {
              public void keyPressed(KeyEvent e) {
                KeyStroke wc = MainWindow.WINDOW_CLOSE_KEYSTROKE;
                if ((e.getKeyCode() == KeyEvent.VK_ESCAPE)
                    || (KeyStroke.getKeyStrokeForEvent(e).equals(wc))) {
                  dispose();
                }
              }
            });
  }
public class ExtruderOnboardParameters extends JPanel {
  private static final long serialVersionUID = 6353987389397209816L;
  private OnboardParameters target;

  // Float gui objects show at least 2 places, max 8 places for clarity it's a float
  private static final NumberFormat floatFormat = (NumberFormat) Base.getLocalFormat().clone();

  {
    floatFormat.setMaximumFractionDigits(8);
    floatFormat.setMinimumFractionDigits(2);
  }

  private static final NumberFormat mmNumberFormat = (NumberFormat) Base.getLocalFormat().clone();

  {
    floatFormat.setMaximumFractionDigits(0);
    floatFormat.setMinimumFractionDigits(0);
  }

  interface Commitable {
    public void commit();
    // In a sane universe, this would be called "validate".  In a sane universe
    // where Java actually implemented inheritance in a sane and happy manner.
    public boolean isCommitable();
  }

  final int FIELD_WIDTH = 10;

  class ThermistorTablePanel extends JPanel implements Commitable {
    private static final long serialVersionUID = 7765098486598830410L;

    private JFormattedTextField betaField = new JFormattedTextField(floatFormat);
    private JFormattedTextField r0Field = new JFormattedTextField(floatFormat);
    private JFormattedTextField t0Field = new JFormattedTextField(floatFormat);
    // Toolhead or Heated Platform?
    private final int which;

    //		private final ToolModel tool;
    private final int toolIndex;

    ThermistorTablePanel(int which, String titleText, int toolIndex /*ToolModel tool*/) {
      super(new MigLayout());
      this.which = which;
      // this.tool = tool;
      this.toolIndex = toolIndex;
      setBorder(BorderFactory.createTitledBorder(titleText));
      betaField.setColumns(FIELD_WIDTH);
      r0Field.setColumns(FIELD_WIDTH);
      t0Field.setColumns(FIELD_WIDTH);

      double beta = target.getBeta(which, toolIndex);
      if (beta == -1) beta = 4066;

      betaField.setValue((int) beta);
      add(new JLabel("Beta"));
      add(betaField, "wrap");

      double r0 = target.getR0(which, toolIndex);
      if (r0 == -1) r0 = 100000;

      r0Field.setValue((int) r0);
      add(new JLabel("Thermistor Resistance"));
      add(r0Field, "wrap");

      double t0 = target.getT0(which, toolIndex);
      if (t0 == -1) t0 = 25;

      t0Field.setValue((int) t0);
      add(new JLabel("Base Temperature"));
      add(t0Field, "wrap");
    }

    public void commit() {
      int beta = ((Number) betaField.getValue()).intValue();
      int r0 = ((Number) r0Field.getValue()).intValue();
      int t0 = ((Number) t0Field.getValue()).intValue();
      target.createThermistorTable(which, r0, t0, beta, this.toolIndex);
    }

    public boolean isCommitable() {
      return true;
    }
  }

  Vector<Commitable> commitList = new Vector<Commitable>();

  private boolean commit() {
    for (Commitable c : commitList) {
      if (!c.isCommitable()) {
        return false;
      }
    }

    for (Commitable c : commitList) {
      c.commit();
    }
    JOptionPane.showMessageDialog(
        this,
        "Changes will not take effect until the extruder board is reset.  You can \n"
            + "do this by turning your machine off and then on, or by disconnecting and \n"
            + "reconnecting the extruder cable.  Make sure you don't still have a USB2TTL \n"
            + "cable attached to the extruder controller, as the cable will keep the board \n"
            + "from resetting.",
        "Extruder controller reminder",
        JOptionPane.INFORMATION_MESSAGE);
    return true;
  }

  private class BackoffPanel extends JPanel implements Commitable {
    private static final long serialVersionUID = 6593800743174557032L;

    private JFormattedTextField stopMsField = new JFormattedTextField(mmNumberFormat);
    private JFormattedTextField reverseMsField = new JFormattedTextField(mmNumberFormat);
    private JFormattedTextField forwardMsField = new JFormattedTextField(mmNumberFormat);
    private JFormattedTextField triggerMsField = new JFormattedTextField(mmNumberFormat);
    // private final ToolModel tool;
    private int toolIndex;

    BackoffPanel(int toolIndex /*ToolModel tool*/) {
      this.toolIndex = toolIndex;

      setLayout(new MigLayout());
      setBorder(BorderFactory.createTitledBorder("Reversal parameters"));
      stopMsField.setColumns(FIELD_WIDTH);
      reverseMsField.setColumns(FIELD_WIDTH);
      forwardMsField.setColumns(FIELD_WIDTH);
      triggerMsField.setColumns(FIELD_WIDTH);

      add(new JLabel("Time to pause (ms)"));
      add(stopMsField, "wrap");
      add(new JLabel("Time to reverse (ms)"));
      add(reverseMsField, "wrap");
      add(new JLabel("Time to advance (ms)"));
      add(forwardMsField, "wrap");
      add(new JLabel("Min. extrusion time before reversal (ms)"));
      add(triggerMsField, "wrap");
      OnboardParameters.BackoffParameters bp = target.getBackoffParameters(toolIndex);
      stopMsField.setValue(bp.stopMs);
      reverseMsField.setValue(bp.reverseMs);
      forwardMsField.setValue(bp.forwardMs);
      triggerMsField.setValue(bp.triggerMs);
    }

    public void commit() {
      OnboardParameters.BackoffParameters bp = new OnboardParameters.BackoffParameters();
      bp.forwardMs = ((Number) forwardMsField.getValue()).intValue();
      bp.reverseMs = ((Number) reverseMsField.getValue()).intValue();
      bp.stopMs = ((Number) stopMsField.getValue()).intValue();
      bp.triggerMs = ((Number) triggerMsField.getValue()).intValue();
      target.setBackoffParameters(bp, toolIndex);
    }

    public boolean isCommitable() {
      return true;
    }
  }

  private class ExtraFeaturesPanel extends JPanel implements Commitable {
    private JCheckBox swapMotors;
    private JComboBox extCh, hbpCh, abpCh;
    private OnboardParameters.ExtraFeatures ef;

    // private final ToolModel tool;
    private int toolIndex;

    ExtraFeaturesPanel(int toolIndex /*ToolModel tool*/) {
      // this.tool = tool;
      this.toolIndex = toolIndex;
      setLayout(new MigLayout());
      ef = target.getExtraFeatures(toolIndex);
      swapMotors =
          new JCheckBox("Use 2A/2B to drive DC motor instead of 1A/1B", ef.swapMotorController);
      add(swapMotors, "span 3,growx,wrap");
      Vector<String> choices = new Vector<String>();
      choices.add("Channel A");
      choices.add("Channel B");
      choices.add("Channel C");
      extCh = new JComboBox(choices);
      extCh.setSelectedIndex(ef.heaterChannel);
      add(new JLabel("Extruder heater uses:"));
      add(extCh);
      add(new JLabel("(default ch. B)"), "wrap");
      hbpCh = new JComboBox(choices);
      hbpCh.setSelectedIndex(ef.hbpChannel);
      add(new JLabel("Platform heater uses:"));
      add(hbpCh);
      add(new JLabel("(default ch. A)"), "wrap");
      abpCh = new JComboBox(choices);
      abpCh.setSelectedIndex(ef.abpChannel);
      add(new JLabel("ABP motor uses:"));
      add(abpCh);
      add(new JLabel("(default ch. C)"), "wrap");
    }

    public void commit() {
      ef.swapMotorController = swapMotors.isSelected();
      ef.heaterChannel = extCh.getSelectedIndex();
      ef.hbpChannel = hbpCh.getSelectedIndex();
      ef.abpChannel = abpCh.getSelectedIndex();
      target.setExtraFeatures(ef, toolIndex);
    }

    public boolean isCommitable() {
      int a = extCh.getSelectedIndex();
      int b = hbpCh.getSelectedIndex();
      int c = abpCh.getSelectedIndex();
      if (a == b || b == c || a == c) {
        JOptionPane.showMessageDialog(
            this,
            "Two or more features are using the same mosfet channel!",
            "Channel conflict",
            JOptionPane.ERROR_MESSAGE);
        return false;
      }
      return true;
    }
  }

  private class PIDPanel extends JPanel implements Commitable {
    private NumberFormat eightPlaces = (NumberFormat) floatFormat.clone();

    {
      eightPlaces.setMaximumFractionDigits(8);
    }

    private JFormattedTextField pField = new JFormattedTextField(floatFormat);
    private JFormattedTextField iField = new JFormattedTextField(eightPlaces);
    private JFormattedTextField dField = new JFormattedTextField(floatFormat);
    private final int which;
    // private final ToolModel tool;
    private int toolIndex;

    PIDPanel(int which, String name, int toolIndex) {
      this.which = which;
      this.toolIndex = toolIndex;
      setLayout(new MigLayout());
      setBorder(BorderFactory.createTitledBorder(name));
      pField.setColumns(FIELD_WIDTH);
      iField.setColumns(FIELD_WIDTH);
      dField.setColumns(FIELD_WIDTH);

      add(new JLabel("P parameter"));
      add(pField, "wrap");
      add(new JLabel("I parameter"));
      add(iField, "wrap");
      add(new JLabel("D parameter"));
      add(dField, "wrap");
      OnboardParameters.PIDParameters pp = target.getPIDParameters(which, toolIndex);
      pField.setValue(pp.p);
      iField.setValue(pp.i);
      dField.setValue(pp.d);
    }

    public void commit() {
      OnboardParameters.PIDParameters pp = new OnboardParameters.PIDParameters();
      pp.p = ((Number) pField.getValue()).floatValue();
      pp.i = ((Number) iField.getValue()).floatValue();
      pp.d = ((Number) dField.getValue()).floatValue();
      target.setPIDParameters(which, pp, toolIndex);
    }

    public boolean isCommitable() {
      return true;
    }
  }

  class RegulatedCoolingFan extends JPanel implements Commitable {
    private static final long serialVersionUID = 7765098486598830410L;
    private JCheckBox coolingFanEnabled;

    private JFormattedTextField coolingFanSetpoint = new JFormattedTextField(floatFormat);

    // private final ToolModel tool;
    private final int toolIndex;

    RegulatedCoolingFan(/*ToolModel tool*/ int toolIndex) {
      super(new MigLayout());

      // this.tool = tool;
      this.toolIndex = toolIndex;
      coolingFanEnabled =
          new JCheckBox(
              "Enable regulated cooling fan (stepper extruders only)",
              target.getCoolingFanEnabled(toolIndex));
      add(coolingFanEnabled, "growx,wrap");

      coolingFanSetpoint.setColumns(FIELD_WIDTH);

      coolingFanSetpoint.setValue((int) target.getCoolingFanSetpoint(toolIndex));
      add(new JLabel("Setpoint (C)"));
      add(coolingFanSetpoint, "wrap");
    }

    public void commit() {
      boolean enabled = coolingFanEnabled.isSelected();
      int setpoint = ((Number) coolingFanSetpoint.getValue()).intValue();
      target.setCoolingFanParameters(enabled, setpoint, toolIndex);
    }

    public boolean isCommitable() {
      return true;
    }
  }

  public ExtruderOnboardParameters(OnboardParameters target, ToolModel tool, JFrame parent) {
    this.target = target;
    int toolIndex = tool.getIndex();

    Version v = new Version(0, 0);
    if (target instanceof Sanguino3GDriver) {
      v = ((Sanguino3GDriver) target).getToolVersion();
    }

    setLayout(new MigLayout());

    ThermistorTablePanel ttp;

    if (tool.hasExtruderThermistor()) {
      ttp = new ThermistorTablePanel(OnboardParameters.EXTRUDER, "Extruder thermistor", toolIndex);
      this.add(ttp);
      commitList.add(ttp);
    }

    if (tool.hasAutomatedPlatform()) {
      ttp =
          new ThermistorTablePanel(
              OnboardParameters.BUILD_PLATFORM, "Heated build platform thermistor", toolIndex);
      this.add(ttp, "wrap");
      commitList.add(ttp);
    }

    if (tool.hasExtruderThermocouple()) {
      PIDPanel pidPanel =
          new PIDPanel(OnboardParameters.EXTRUDER, "Extruder PID parameters", toolIndex);
      this.add(pidPanel, "growx");
      commitList.add(pidPanel);
    }

    if (v.atLeast(new Version(2, 4))) {
      PIDPanel pp =
          new PIDPanel(OnboardParameters.BUILD_PLATFORM, "Heated build platform", toolIndex);
      this.add(pp, "growx,wrap");
      commitList.add(pp);
    }

    if (v.atLeast(new Version(2, 9))) {
      RegulatedCoolingFan rcf = new RegulatedCoolingFan(toolIndex);
      this.add(rcf, "span 2,growx,wrap");
      commitList.add(rcf);
    }

    String machineType = target.getMachineType();
    if (!(machineType.equals("MightyBoard")
        || machineType.equals("The Replicator")
        || machineType.equals("MightyBoard(unverified)"))) {
      JButton commitButton = new JButton("Commit Changes");
      commitButton.addActionListener(
          new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
              if (ExtruderOnboardParameters.this.commit()) {}
            }
          });
      add(commitButton, "newline, span 2");
    }
  }
}