@Override
  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == imageView) {
      selectedView = imageView.getSelectedIndex();
      owner.viewUpdated();
    } else if (e.getSource() == showCorners) {
      bShowCorners = showCorners.isSelected();
      owner.viewUpdated();
    } else if (e.getSource() == showLines) {
      bShowLines = showLines.isSelected();
      owner.viewUpdated();
    } else if (e.getSource() == showContour) {
      bShowContour = showContour.isSelected();
      owner.viewUpdated();
    } else if (e.getSource() == refineChoice) {
      refineType = PolygonRefineType.values()[refineChoice.getSelectedIndex()];

      updateRefineSettings();
      owner.configUpdate();
    } else if (e.getSource() == setConvex) {
      config.convex = setConvex.isSelected();
      owner.configUpdate();
    } else if (e.getSource() == setBorder) {
      config.convex = setBorder.isSelected();
      owner.configUpdate();
    }
  }
  public DetectPolygonControlPanel(DetectBlackPolygonApp owner) {
    this.owner = owner;

    imageView = new JComboBox();
    imageView.addItem("Input");
    imageView.addItem("Binary");
    imageView.addItem("Black");
    imageView.addActionListener(this);
    imageView.setMaximumSize(imageView.getPreferredSize());

    selectZoom = new JSpinner(new SpinnerNumberModel(1, 0.1, 50, 1));
    selectZoom.addChangeListener(this);
    selectZoom.setMaximumSize(selectZoom.getPreferredSize());

    showCorners = new JCheckBox("Corners");
    showCorners.addActionListener(this);
    showCorners.setSelected(bShowCorners);
    showLines = new JCheckBox("Lines");
    showLines.setSelected(bShowLines);
    showLines.addActionListener(this);
    showContour = new JCheckBox("Contour");
    showContour.addActionListener(this);
    showContour.setSelected(bShowContour);

    threshold = new ThresholdControlPanel(owner);

    refineChoice = new JComboBox();
    for (PolygonRefineType n : PolygonRefineType.values()) {
      refineChoice.addItem(n.name());
    }
    refineChoice.setSelectedIndex(refineType.ordinal());
    refineChoice.addActionListener(this);
    refineChoice.setMaximumSize(refineChoice.getPreferredSize());

    spinnerMinContourSize =
        new JSpinner(new SpinnerNumberModel(config.minContourImageWidthFraction, 0.0, 0.2, 0.01));
    configureSpinnerFloat(spinnerMinContourSize);
    spinnerMinSides = new JSpinner(new SpinnerNumberModel(minSides, 3, 20, 1));
    spinnerMinSides.setMaximumSize(spinnerMinSides.getPreferredSize());
    spinnerMinSides.addChangeListener(this);
    spinnerMaxSides = new JSpinner(new SpinnerNumberModel(maxSides, 3, 20, 1));
    spinnerMaxSides.setMaximumSize(spinnerMaxSides.getPreferredSize());
    spinnerMaxSides.addChangeListener(this);

    spinnerMinEdge =
        new JSpinner(new SpinnerNumberModel(config.minimumEdgeIntensity, 0.0, 255.0, 1.0));
    spinnerMinEdge.setMaximumSize(spinnerMinEdge.getPreferredSize());
    spinnerMinEdge.addChangeListener(this);
    spinnerContourSplit =
        new JSpinner(new SpinnerNumberModel(config.contour2Poly_splitFraction, 0.0, 1.0, 0.01));
    configureSpinnerFloat(spinnerContourSplit);
    spinnerContourMinSplit =
        new JSpinner(
            new SpinnerNumberModel(config.contour2Poly_minimumSideFraction, 0.0, 1.0, 0.001));
    configureSpinnerFloat(spinnerContourMinSplit);
    spinnerContourSplit.addChangeListener(this);
    spinnerContourIterations =
        new JSpinner(new SpinnerNumberModel(config.contour2Poly_iterations, 1, 200, 1));
    spinnerContourIterations.setMaximumSize(spinnerContourIterations.getPreferredSize());
    spinnerContourIterations.addChangeListener(this);
    spinnerSplitPenalty =
        new JSpinner(new SpinnerNumberModel(config.splitPenalty, 0.0, 100.0, 1.0));
    configureSpinnerFloat(spinnerSplitPenalty);

    setConvex = new JCheckBox("Convex");
    setConvex.addActionListener(this);
    setConvex.setSelected(config.convex);
    setBorder = new JCheckBox("Image Border");
    setBorder.addActionListener(this);
    setBorder.setSelected(config.canTouchBorder);

    spinnerLineSamples = new JSpinner(new SpinnerNumberModel(configLine.lineSamples, 5, 100, 1));
    spinnerLineSamples.setMaximumSize(spinnerLineSamples.getPreferredSize());
    spinnerLineSamples.addChangeListener(this);
    spinnerCornerOffset = new JSpinner(new SpinnerNumberModel(configLine.cornerOffset, 0, 10, 1));
    spinnerCornerOffset.setMaximumSize(spinnerCornerOffset.getPreferredSize());
    spinnerCornerOffset.addChangeListener(this);
    spinnerSampleRadius = new JSpinner(new SpinnerNumberModel(configLine.sampleRadius, 0, 10, 1));
    spinnerSampleRadius.setMaximumSize(spinnerCornerOffset.getPreferredSize());
    spinnerSampleRadius.addChangeListener(this);
    spinnerRefineMaxIterations =
        new JSpinner(new SpinnerNumberModel(configLine.maxIterations, 0, 200, 1));
    spinnerRefineMaxIterations.setMaximumSize(spinnerRefineMaxIterations.getPreferredSize());
    spinnerRefineMaxIterations.addChangeListener(this);
    spinnerConvergeTol =
        new JSpinner(new SpinnerNumberModel(configLine.convergeTolPixels, 0.0, 2.0, 0.005));
    configureSpinnerFloat(spinnerConvergeTol);
    spinnerMaxCornerChange =
        new JSpinner(new SpinnerNumberModel(configLine.maxCornerChangePixel, 0.0, 50.0, 1.0));
    configureSpinnerFloat(spinnerMaxCornerChange);

    addLabeled(imageView, "View: ", this);
    addLabeled(selectZoom, "Zoom", this);
    addAlignLeft(showCorners, this);
    addAlignLeft(showLines, this);
    addAlignLeft(showContour, this);
    add(threshold);
    addLabeled(spinnerMinContourSize, "Min Contour Size: ", this);
    addLabeled(spinnerMinSides, "Minimum Sides: ", this);
    addLabeled(spinnerMaxSides, "Maximum Sides: ", this);
    addLabeled(spinnerMinEdge, "Edge Intensity: ", this);
    addAlignLeft(setConvex, this);
    addAlignLeft(setBorder, this);
    addCenterLabel("Contour", this);
    addLabeled(spinnerContourSplit, "Split Fraction: ", this);
    addLabeled(spinnerContourMinSplit, "Min Split: ", this);
    addLabeled(spinnerContourIterations, "Max Iterations: ", this);
    addLabeled(spinnerSplitPenalty, "Split Penalty: ", this);
    addCenterLabel("Refinement", this);
    addLabeled(refineChoice, "Refine: ", this);
    addLabeled(spinnerLineSamples, "Line Samples: ", this);
    addLabeled(spinnerCornerOffset, "Corner Offset: ", this);
    addLabeled(spinnerSampleRadius, "Sample Radius: ", this);
    addLabeled(spinnerRefineMaxIterations, "Iterations: ", this);
    addLabeled(spinnerConvergeTol, "Converge Tol Pixels: ", this);
    addLabeled(spinnerMaxCornerChange, "Max Corner Change: ", this);
    addVerticalGlue(this);
  }
  @Override
  public void stateChanged(ChangeEvent e) {
    PolygonRefineType refine = PolygonRefineType.values()[refineChoice.getSelectedIndex()];

    if (e.getSource() == spinnerMinEdge) {
      config.minimumEdgeIntensity = ((Number) spinnerMinEdge.getValue()).doubleValue();
    } else if (e.getSource() == spinnerMinSides) {
      minSides = ((Number) spinnerMinSides.getValue()).intValue();
      if (minSides > maxSides) {
        maxSides = minSides;
        spinnerMaxSides.setValue(minSides);
      }
      updateSidesInConfig();
    } else if (e.getSource() == spinnerMaxSides) {
      maxSides = ((Number) spinnerMaxSides.getValue()).intValue();
      if (maxSides < minSides) {
        minSides = maxSides;
        spinnerMinSides.setValue(minSides);
      }
      updateSidesInConfig();
    } else if (e.getSource() == selectZoom) {
      zoom = ((Number) selectZoom.getValue()).doubleValue();
      owner.viewUpdated();
      return;
    } else if (e.getSource() == spinnerMinContourSize) {
      config.minContourImageWidthFraction =
          ((Number) spinnerMinContourSize.getValue()).doubleValue();
    } else if (e.getSource() == spinnerContourSplit) {
      config.contour2Poly_splitFraction = ((Number) spinnerContourSplit.getValue()).doubleValue();
    } else if (e.getSource() == spinnerContourMinSplit) {
      config.contour2Poly_minimumSideFraction =
          ((Number) spinnerContourMinSplit.getValue()).doubleValue();
    } else if (e.getSource() == spinnerContourIterations) {
      config.contour2Poly_iterations = ((Number) spinnerContourIterations.getValue()).intValue();
    } else if (e.getSource() == spinnerSplitPenalty) {
      config.splitPenalty = ((Number) spinnerSplitPenalty.getValue()).doubleValue();
    } else if (e.getSource() == spinnerLineSamples) {
      if (refine == PolygonRefineType.LINE) {
        configLine.lineSamples = ((Number) spinnerLineSamples.getValue()).intValue();
      } else {
        configCorner.lineSamples = ((Number) spinnerLineSamples.getValue()).intValue();
      }
    } else if (e.getSource() == spinnerCornerOffset) {
      if (refine == PolygonRefineType.LINE) {
        configLine.cornerOffset = ((Number) spinnerCornerOffset.getValue()).intValue();
      } else {
        configCorner.cornerOffset = ((Number) spinnerCornerOffset.getValue()).intValue();
      }
    } else if (e.getSource() == spinnerSampleRadius) {
      if (refine == PolygonRefineType.LINE) {
        configLine.sampleRadius = ((Number) spinnerSampleRadius.getValue()).intValue();
      } else {
        configCorner.sampleRadius = ((Number) spinnerSampleRadius.getValue()).intValue();
      }
    } else if (e.getSource() == spinnerRefineMaxIterations) {
      if (refine == PolygonRefineType.LINE) {
        configLine.maxIterations = ((Number) spinnerRefineMaxIterations.getValue()).intValue();
      } else {
        configCorner.maxIterations = ((Number) spinnerRefineMaxIterations.getValue()).intValue();
      }
    } else if (e.getSource() == spinnerConvergeTol) {
      if (refine == PolygonRefineType.LINE) {
        configLine.convergeTolPixels = ((Number) spinnerConvergeTol.getValue()).doubleValue();
      } else {
        configCorner.convergeTolPixels = ((Number) spinnerConvergeTol.getValue()).doubleValue();
      }
    } else if (e.getSource() == spinnerMaxCornerChange) {
      if (refine == PolygonRefineType.LINE) {
        configLine.maxCornerChangePixel =
            ((Number) spinnerMaxCornerChange.getValue()).doubleValue();
      } else {
        configCorner.maxCornerChangePixel =
            ((Number) spinnerMaxCornerChange.getValue()).doubleValue();
      }
    }
    owner.configUpdate();
  }