visolate.Visolate.java Source code

Java tutorial

Introduction

Here is the source code for visolate.Visolate.java

Source

/**
 * "Visolate" -- compute (Voronoi) PCB isolation routing toolpaths
 *
 * Copyright (C) 2004 Marsette A. Vona, III
 *               2012 Markus Hitter <mah@jump-ing.de>
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 **/

package visolate;

import visolate.parser.*;
import visolate.simulator.*;
import visolate.model.*;
import visolate.processor.*;

import java.io.*;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.*;
import java.util.List;
import java.net.*;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.security.*;

import org.apache.commons.cli.CommandLine;

import visolate.processor.GCodeFileWriter;

public class Visolate extends JPanel implements SimulatorUI {

    private static final long serialVersionUID = 1L;

    public static final String DEMO_FILE = "example.grb";
    /**
     * kept here so it is available in the invokeLater Runnable in Main
     */
    public CommandLine commandline;
    public int processstatus;
    public boolean auto_mode;

    public Visolate() {
        this(null);
    }

    public Visolate(File file) {

        processstatus = 0;
        display = new Display(this);
        simulator = new Simulator(this);
        model = new Model(this);
        // TODO: get a toolpathsProcessor here, too, and get rid of myToolpathsProcessor.
        gCodeWriter = new GCodeFileWriter();

        setBackground(Color.WHITE);
        setOpaque(true);

        Dimension d;

        Box processingBox = getProcessingBox();

        Box box = Box.createVerticalBox();

        box.add(getLoadFileBox());
        box.add(display);
        box.add(model);
        box.add(getGCodeOptionsBox());
        box.add(processingBox);

        setLayout(new BorderLayout());
        add(box, "Center");

        //make display take up max available space

        Dimension orig = getPreferredSize();

        d = getLoadFileBox().getPreferredSize();
        getLoadFileBox().setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));

        d = model.getPreferredSize();
        model.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));

        d = processingBox.getPreferredSize();
        processingBox.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));

        display.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
        setPreferredSize(orig);

        if (file != null)
            loadFile(file);
    }

    private Box getLoadFileBox() {
        if (myLoadFileBox == null) {
            myLoadFileBox = Box.createHorizontalBox();

            loadButton = new JButton("Load");
            loadButton.setEnabled(false);
            loadButton.setBackground(Color.WHITE);
            loadButton.setVerticalAlignment(AbstractButton.CENTER);
            loadButton.setHorizontalAlignment(AbstractButton.CENTER);
            Dimension d = loadButton.getPreferredSize();
            loadButton.setMaximumSize(new Dimension(d.width, d.height));
            loadButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    loadFile();
                }
            });
            myLoadFileBox.add(loadButton);

            loadField = new JTextField();
            d = loadField.getPreferredSize();
            loadField.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));
            myLoadFileBox.add(loadField);

            browseButton = new JButton("Browse...");
            browseButton.setBackground(Color.WHITE);
            browseButton.setVerticalAlignment(AbstractButton.CENTER);
            browseButton.setHorizontalAlignment(AbstractButton.CENTER);
            d = browseButton.getPreferredSize();
            browseButton.setMaximumSize(new Dimension(d.width, d.height));
            browseButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    File file = browse();
                    if (file != null)
                        loadFile(file);
                    loadButton.setEnabled(true);
                }
            });
            myLoadFileBox.add(browseButton);

            myLoadFileBox.setBorder(BorderFactory.createTitledBorder("Input File"));
        }
        return myLoadFileBox;
    }

    private Box getGcodeBox() {
        if (myGcodeBox == null) {
            Dimension d;
            myGcodeBox = Box.createHorizontalBox();

            gcodeButton = new JButton("Save G-Code");
            gcodeButton.setEnabled(false);
            gcodeButton.setBackground(Color.WHITE);
            gcodeButton.setVerticalAlignment(AbstractButton.CENTER);
            gcodeButton.setHorizontalAlignment(AbstractButton.CENTER);
            d = gcodeButton.getPreferredSize();
            gcodeButton.setMaximumSize(new Dimension(d.width, d.height));
            gcodeButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    saveGCode();
                }
            });
            myGcodeBox.add(gcodeButton);

            gcodeField = new JTextField();
            gcodeField.setEnabled(false);
            d = gcodeField.getPreferredSize();
            gcodeField.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));
            myGcodeBox.add(gcodeField);

            gcodeBrowseButton = new JButton("Browse...");
            gcodeBrowseButton.setEnabled(false);
            gcodeBrowseButton.setBackground(Color.WHITE);
            gcodeBrowseButton.setVerticalAlignment(AbstractButton.CENTER);
            gcodeBrowseButton.setHorizontalAlignment(AbstractButton.CENTER);
            d = gcodeBrowseButton.getPreferredSize();
            gcodeBrowseButton.setMaximumSize(new Dimension(d.width, d.height));
            gcodeBrowseButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    File file = browse();
                    if (file != null)
                        saveGCode(file);
                }
            });
            myGcodeBox.add(gcodeBrowseButton);

            stopButton = new JButton("Stop");
            stopButton.setBackground(Color.WHITE);
            //    stopButton.setVerticalAlignment(AbstractButton.CENTER);
            //    stopButton.setHorizontalAlignment(AbstractButton.CENTER);
            stopButton.setAlignmentX(0.5f);
            d = stopButton.getPreferredSize();
            stopButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));
            stopButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    stopProcess();
                }
            });
            stopButton.setEnabled(false);

            progressBar = new JProgressBar();
            progressBar.setBackground(Color.WHITE);
        }
        return myGcodeBox;
    }

    private Box getTopologyAndToolpathsBox() {
        Box topologyAndToolpathsBox = Box.createHorizontalBox();
        topologyAndToolpathsBox.add(getTopologyBox());
        topologyAndToolpathsBox.add(Box.createHorizontalGlue());
        topologyAndToolpathsBox.add(getToolpathBox());
        return topologyAndToolpathsBox;
    }

    private Box getToolpathBox() {
        Dimension d;
        Box toolpathsBox = Box.createHorizontalBox();

        toolpathsButton = new JButton("Make Toolpaths");
        toolpathsButton.setBackground(Color.WHITE);
        toolpathsButton.setVerticalAlignment(AbstractButton.CENTER);
        toolpathsButton.setHorizontalAlignment(AbstractButton.CENTER);
        d = toolpathsButton.getPreferredSize();
        toolpathsButton.setMaximumSize(new Dimension(d.width, d.height));
        toolpathsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                computeToolpaths();
            }
        });
        toolpathsBox.add(toolpathsButton);

        //    toolpathsBox.add(Box.createHorizontalStrut(16));

        ButtonGroup modeGroup = new ButtonGroup();

        voronoiButton = new JRadioButton("voronoi");
        modeGroup.add(voronoiButton);
        voronoiButton.setBackground(Color.WHITE);
        // VORONOI_MODE is the default mode.
        voronoiButton.setSelected(true);
        toolpathsBox.add(voronoiButton);

        outlineButton = new JRadioButton("outline");
        modeGroup.add(outlineButton);
        outlineButton.setBackground(Color.WHITE);
        // VORONOI_MODE is the default mode.
        outlineButton.setSelected(false);
        toolpathsBox.add(outlineButton);

        return toolpathsBox;
    }

    private Box getTopologyBox() {
        Dimension d;
        Box topologyBox = Box.createHorizontalBox();

        topologyButton = new JButton("Fix Topology");
        topologyButton.setBackground(Color.WHITE);
        topologyButton.setVerticalAlignment(AbstractButton.CENTER);
        topologyButton.setHorizontalAlignment(AbstractButton.CENTER);
        d = topologyButton.getPreferredSize();
        topologyButton.setMaximumSize(new Dimension(d.width, d.height));
        topologyButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                fixTopology();
            }
        });
        topologyBox.add(topologyButton);

        //    topologyBox.add(Box.createHorizontalStrut(16));

        manualTopology = new JCheckBox("manual");
        manualTopology.setBackground(Color.WHITE);
        manualTopology.setSelected(false);
        topologyBox.add(manualTopology);
        return topologyBox;
    }

    private Box getMosaicBox() {
        Dimension d;
        if (myMosaicBox == null) {
            myMosaicBox = Box.createHorizontalBox();

            mosaicButton = new JButton("Save High-Res");
            mosaicButton.setBackground(Color.WHITE);
            mosaicButton.setVerticalAlignment(AbstractButton.CENTER);
            mosaicButton.setHorizontalAlignment(AbstractButton.CENTER);
            d = mosaicButton.getPreferredSize();
            mosaicButton.setMaximumSize(new Dimension(d.width, d.height));
            mosaicButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    saveMosaic();
                }
            });
            myMosaicBox.add(mosaicButton);

            mosaicField = new JTextField();
            d = mosaicField.getPreferredSize();
            mosaicField.setMaximumSize(new Dimension(Integer.MAX_VALUE, d.height));
            mosaicField.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    saveMosaic();
                }
            });
            //    mosaicField.addFocusListener(new FocusAdapter() {
            //        public void focusLost(FocusEvent e) { saveMosaic(); } });
            myMosaicBox.add(mosaicField);

            mosaicBrowseButton = new JButton("Browse...");
            mosaicBrowseButton.setBackground(Color.WHITE);
            mosaicBrowseButton.setVerticalAlignment(AbstractButton.CENTER);
            mosaicBrowseButton.setHorizontalAlignment(AbstractButton.CENTER);
            d = mosaicBrowseButton.getPreferredSize();
            mosaicBrowseButton.setMaximumSize(new Dimension(d.width, d.height));
            mosaicBrowseButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    File file = browse();
                    if (file != null)
                        saveMosaic(file);
                }
            });
            myMosaicBox.add(mosaicBrowseButton);

            mosaicTilesButton = new JCheckBox("individual tiles");
            mosaicTilesButton.setBackground(Color.WHITE);
            mosaicTilesButton.setSelected(false);
            myMosaicBox.add(mosaicTilesButton);
        }
        return myMosaicBox;
    }

    private Box getProcessingBox() {
        if (myProcessingBox == null) {
            myProcessingBox = Box.createVerticalBox();
            myProcessingBox.setBorder(BorderFactory.createTitledBorder("Processing"));
            myProcessingBox.add(getMosaicBox());
            myProcessingBox.add(getTopologyAndToolpathsBox());
            myProcessingBox.add(getGcodeBox());
            //    processingBox.add(Box.createVerticalStrut(8));
            myProcessingBox.add(stopButton);
            //    processingBox.add(Box.createVerticalStrut(8));
            myProcessingBox.add(progressBar);
        }
        return myProcessingBox;
    }

    private Box getGCodeOptionsBox() {
        if (myGCodeOptionsBox == null) {
            myGCodeOptionsBox = Box.createVerticalBox();
            myGCodeOptionsBox.setBorder(BorderFactory.createTitledBorder("G-Code"));
            JPanel panel = new JPanel();
            panel.setLayout(new GridLayout(3, 4));

            panel.add(new JLabel("Coordinates"));
            panel.add(new JLabel("Z-coordinates"));
            panel.add(new JLabel("left upper coordinates"));
            panel.add(new JLabel("Metric"));
            panel.add(new JLabel("Feedrates"));

            panel.add(getRelativeCoordinatesButton());
            panel.add(getZCuttingHeightPanel());
            panel.add(getInitialXPanel());
            panel.add(getMetricButton());
            panel.add(getMillingSpeedPanel());

            panel.add(getAbsoluteCoordinatesButton());
            panel.add(getZDownMovementPanel());
            panel.add(getInitialYPanel());
            panel.add(getImperialButton());
            panel.add(getPlungeSpeedPanel());

            myGCodeOptionsBox.add(panel);
        }
        return myGCodeOptionsBox;
    }

    private JPanel getInitialXPanel() {
        if (myInitialXPanel == null) {
            myInitialXPanel = new JPanel();
            myInitialXPanel.setLayout(new BorderLayout());
            myInitialXPanel.add(new JLabel("X"), BorderLayout.WEST);
            myInitialXPanel.setToolTipText("Left side is at this coordinate (mm or inch)");
            myInitialXPanel.setEnabled(gCodeWriter.getIsAbsolute());
            final JTextField field = new JTextField(NumberFormat.getInstance().format(gCodeWriter.getXOffset()));
            myInitialXPanel.add(field, BorderLayout.CENTER);
            myInitialXPanel.addPropertyChangeListener("enabled", new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    field.setEnabled(myInitialXPanel.isEnabled());
                }
            });
            field.setEnabled(myInitialXPanel.isEnabled());
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter.setXOffset(NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getXOffset()));
                }
            });
        }
        return myInitialXPanel;
    }

    private JPanel getInitialYPanel() {
        if (myInitialYPanel == null) {
            myInitialYPanel = new JPanel();
            myInitialYPanel.setLayout(new BorderLayout());
            myInitialYPanel.add(new JLabel("Y"), BorderLayout.WEST);
            myInitialYPanel.setToolTipText("Upper side is at this coordinates (mm or inch)");
            myInitialYPanel.setEnabled(gCodeWriter.getIsAbsolute());
            final JTextField field = new JTextField(NumberFormat.getInstance().format(gCodeWriter.getYOffset()));
            myInitialYPanel.add(field, BorderLayout.CENTER);
            myInitialYPanel.addPropertyChangeListener("enabled", new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    field.setEnabled(myInitialYPanel.isEnabled());
                }
            });
            field.setEnabled(myInitialYPanel.isEnabled());
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter.setYOffset(NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getYOffset()));
                }
            });
        }
        return myInitialYPanel;
    }

    private Component getZDownMovementPanel() {
        if (myZDownMovementPanel == null) {
            myZDownMovementPanel = new JPanel();
            myZDownMovementPanel.setLayout(new BorderLayout());
            myZDownMovementPanel.add(new JLabel("travel clearance"), BorderLayout.WEST);
            myZDownMovementPanel.setToolTipText(
                    "When not cutting, lift the cutter to this above origin, in mm or inch. Decimals in native language (point or comma).");
            final JTextField field = new JTextField(NumberFormat.getInstance().format(gCodeWriter.getZClearance()));
            myZDownMovementPanel.add(field, BorderLayout.CENTER);
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter.setZClearance(NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getZClearance()));
                }
            });
        }
        return myZDownMovementPanel;
    }

    private JPanel getMillingSpeedPanel() {
        if (myMillingSpeedPanel == null) {
            myMillingSpeedPanel = new JPanel();
            myMillingSpeedPanel.setLayout(new BorderLayout());
            myMillingSpeedPanel.add(new JLabel("cutting feedrate"), BorderLayout.WEST);
            myMillingSpeedPanel.setToolTipText("Feedrate during cutting in mm or inch per minute.");
            final JTextField field = new JTextField(
                    NumberFormat.getInstance().format(gCodeWriter.getMillingFeedrate()));
            myMillingSpeedPanel.add(field, BorderLayout.CENTER);
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter.setMillingFeedrate(
                                NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getMillingFeedrate()));
                }
            });
        }
        return myMillingSpeedPanel;
    }

    private JPanel getPlungeSpeedPanel() {
        if (myPlungeSpeedPanel == null) {
            myPlungeSpeedPanel = new JPanel();
            myPlungeSpeedPanel.setLayout(new BorderLayout());
            myPlungeSpeedPanel.add(new JLabel("plunge feedrate"), BorderLayout.WEST);
            myPlungeSpeedPanel
                    .setToolTipText("Feedrate when moving vertically into the workpiece in mm or inch per minute.");
            final JTextField field = new JTextField(
                    NumberFormat.getInstance().format(gCodeWriter.getPlungeFeedrate()));
            myPlungeSpeedPanel.add(field, BorderLayout.CENTER);
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter
                                .setPlungeFeedrate(NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getPlungeFeedrate()));
                }
            });
        }
        return myPlungeSpeedPanel;
    }

    private JPanel getZCuttingHeightPanel() {
        if (myZCuttingHeightPanel == null) {
            myZCuttingHeightPanel = new JPanel();
            myZCuttingHeightPanel.setLayout(new BorderLayout());
            myZCuttingHeightPanel.add(new JLabel("cutting height"), BorderLayout.WEST);
            myZCuttingHeightPanel.setToolTipText(
                    "When cutting, the head should have this z-coordinate, in mm or inch. Likely a negative value, decimals in native language (point or comma)");
            myZCuttingHeightPanel.setEnabled(gCodeWriter.getIsAbsolute());
            final JTextField field = new JTextField(
                    NumberFormat.getInstance().format(gCodeWriter.getZCuttingHeight()));
            myZCuttingHeightPanel.add(field, BorderLayout.CENTER);
            myZCuttingHeightPanel.addPropertyChangeListener("enabled", new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    field.setEnabled(myZCuttingHeightPanel.isEnabled());
                }
            });
            field.setEnabled(myZCuttingHeightPanel.isEnabled());
            field.addFocusListener(new FocusAdapter() {
                public void focusLost(FocusEvent evt) {
                    try {
                        gCodeWriter
                                .setZCuttingHeight(NumberFormat.getInstance().parse(field.getText()).doubleValue());
                    } catch (ParseException e) {
                    }
                    field.setText(NumberFormat.getInstance().format(gCodeWriter.getZCuttingHeight()));
                }
            });
        }
        return myZCuttingHeightPanel;
    }

    private JRadioButton getAbsoluteCoordinatesButton() {
        if (myAbsoluteCoordinatesButton == null) {
            myAbsoluteCoordinatesButton = new JRadioButton("absolute");
            myAbsoluteCoordinatesButton.setSelected(gCodeWriter.getIsAbsolute());
            myAbsoluteCoordinatesButton.setToolTipText(
                    "Output absolute G-code coordinates. For the starting point, set X, Y and cutting height.");
            myAbsoluteCoordinatesButton.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    setAbsoluteCoordinates(myAbsoluteCoordinatesButton.isSelected());
                }
            });
        }
        return myAbsoluteCoordinatesButton;
    }

    private JRadioButton getRelativeCoordinatesButton() {
        if (myRelativeCoordinatesButton == null) {
            myRelativeCoordinatesButton = new JRadioButton("relative");
            myRelativeCoordinatesButton.setSelected(!gCodeWriter.getIsAbsolute());
            myRelativeCoordinatesButton
                    .setToolTipText("Output relative G-code coordinates, starting at { 0.0, 0.0, 0.0 }.");
            myRelativeCoordinatesButton.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    setAbsoluteCoordinates(!myRelativeCoordinatesButton.isSelected());
                }
            });
        }
        return myRelativeCoordinatesButton;
    }

    public void setAbsoluteCoordinates(final boolean newValue) {
        getAbsoluteCoordinatesButton().setSelected(newValue);
        getRelativeCoordinatesButton().setSelected(!newValue);
        getZCuttingHeightPanel().setEnabled(newValue);
        getInitialXPanel().setEnabled(newValue);
        getInitialYPanel().setEnabled(newValue);
        gCodeWriter.setIsAbsolute(newValue);
    }

    private JRadioButton getMetricButton() {
        if (myMetricButton == null) {
            myMetricButton = new JRadioButton("metric");
            myMetricButton.setSelected(gCodeWriter.getIsMetric());
            myMetricButton.setToolTipText("Output G-code in mm. Coordinates entered here are in mm, too.");
            myMetricButton.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    if (myMetricButton.isSelected()) {
                        getImperialButton().setSelected(false);
                        gCodeWriter.setIsMetric(true);
                    }
                }
            });
        }
        return myMetricButton;
    }

    private JRadioButton getImperialButton() {
        if (myImperialButton == null) {
            myImperialButton = new JRadioButton("imperial");
            myImperialButton.setSelected(!gCodeWriter.getIsMetric());
            myImperialButton
                    .setToolTipText("Output G-code in inches. Coordinates entered here are in inches, too.");
            myImperialButton.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    if (myImperialButton.isSelected()) {
                        getMetricButton().setSelected(false);
                        gCodeWriter.setIsMetric(false);
                    }
                }
            });
        }
        return myImperialButton;
    }

    public void saveMosaic() {
        saveMosaic(new File(mosaicField.getText().trim()));
    }

    public void saveMosaic(File file) {
        saveMosaic(file, mosaicTilesButton.isSelected());
    }

    public void saveMosaic(File file, boolean tiles) {

        try {

            mosaicField.setText(file.toString());

            if (file.exists()
                    && !(JOptionPane.showConfirmDialog(this, "Overwrite existing mosaic file " + file + "?",
                            "Overwrite?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION))
                return;

            startProcess(new SaveMosaic(this, file, tiles));

        } catch (AccessControlException e1) {
            accessControlError();
        }
    }

    public void fixTopology() {
        startProcess(new TopologyProcessor(this));
    }

    private void computeToolpaths() {

        int mode = -1;

        if (voronoiButton.isSelected())
            mode = ToolpathsProcessor.VORONOI_MODE;
        else
            mode = ToolpathsProcessor.OUTLINE_MODE;

        myToolpathsProcessor = new ToolpathsProcessor(this, mode);

        startProcess(myToolpathsProcessor);
    }

    private File browse() {
        try {
            File input = null;
            String[] zenity = { "zenity", "--file-selection", "--title=Open", "--file-filter=Gerber file | *.gbr",
                    "--file-filter=All | * " }; //FIXME: dirty workaround to get a fully functional and native file dialog on linux
            String filestring;
            try {
                Process p = Runtime.getRuntime().exec(zenity);
                BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
                StringBuffer sb = new StringBuffer();

                sb.append(br.readLine());
                filestring = sb.toString();
                if (filestring.equals("null")) {
                    return null;
                }
                input = new File(filestring);

            } catch (IOException e1) { //fallback if zenity is not installed
                final JFileChooser fc = new JFileChooser();
                fc.addChoosableFileFilter(new FileFilter() {

                    @Override
                    public String getDescription() {
                        return "Gerber file";
                    }

                    @Override
                    public boolean accept(File f) {
                        return f.getName().endsWith(".gbr");
                    }
                });

                int returnVal = -1;

                returnVal = fc.showOpenDialog(fc);
                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    input = fc.getSelectedFile();
                }
            }
            return input;
        } catch (AccessControlException e1) {
            accessControlError();
            return null;
        }
    }

    private void accessControlError() {
        JOptionPane.showMessageDialog(this,
                "Cannot load or save from an applet.  You must "
                        + "either change your browser's security policy or "
                        + "download the application and run it directly.",
                "Access Denied", JOptionPane.ERROR_MESSAGE);
    }

    public void destroy() {
        display.destroy();
    }

    public void loadFile() {
        loadFile(new File(loadField.getText().trim()));
    }

    public void loadDemo() {
        loadURL(getClass().getResource(DEMO_FILE));
        loadField.setText("[built-in demo]");
    }

    public void loadURL(URL url) {
        try {
            load(url.openStream());
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "I/O Error: " + e.getMessage(), "I/O Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    public void loadFile(File file) {

        currentFile = file;
        loadField.setText(file.toString());
        setGcodeFile(file.toString() + ".ngc");

        try {
            load(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            loadDemo();
            JOptionPane.showMessageDialog(this, "File Not Found: " + e.getMessage() + "\nLoaded Demo file instead.",
                    "File Not Found", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void load(InputStream inputStream) {

        stopProcess();

        enableControls(false);
        myToolpathsProcessor = null;

        simulator.reset();

        try {
            Parser parser = new Parser(inputStream);
            parser.setSimulator(simulator);
            parser.Input();
        } catch (visolate.parser.ParseException e) {
            JOptionPane.showMessageDialog(this, "Parse Error: " + e.getMessage(), "Parse Error",
                    JOptionPane.ERROR_MESSAGE);
        }

        model.rebuild();
        enableControls(true);
    }

    public void setGcodeFile(String filename) {
        gcodeField.setText(filename);
    }

    public void saveGCode() {
        saveGCode(new File(gcodeField.getText().trim()));
    }

    public void saveGCode(final File file) {

        gcodeField.setText(file.toString());

        if (myToolpathsProcessor == null) {
            return;
        }

        try {

            if (file.exists() && (!auto_mode)) {
                int yesno = JOptionPane.showConfirmDialog(this, "Overwrite existing G-Code file " + file + "?",
                        "Overwrite?", JOptionPane.YES_NO_OPTION);
                if (yesno != JOptionPane.YES_OPTION) {
                    return;
                }
            }

            try {
                gCodeWriter.open(file);
                myToolpathsProcessor.writeGCode(gCodeWriter);
                gCodeWriter.close();
            } catch (IOException e) {
                JOptionPane.showMessageDialog(this, "I/O Error writing G-Code: " + e.getMessage(), "I/O Error",
                        JOptionPane.ERROR_MESSAGE);
            }

        } catch (AccessControlException e1) {

            accessControlError();

            try {
                myToolpathsProcessor.writeGCode(null);
            } catch (IOException e2) {
                //nope
            }
        }
    }

    public void mouseClicked(double x, double y, int modifiers) {

        SortedSet<Net> clickedNets = new TreeSet<Net>();

        model.getNetsAtPoint(x, y, 1.0 / display.getDPI(), clickedNets);

        if (manualTopology.isSelected()) {
            clearSelection();
            TopologyProcessor.mergeNets(clickedNets);
            return;
        }

        if ((selectedNet != null) && clickedNets.contains(selectedNet)) {

            Iterator<Net> it = (clickedNets.tailSet(selectedNet)).iterator();

            it.next();

            if (it.hasNext()) {
                selectedNet = it.next();
            } else {
                selectedNet = clickedNets.iterator().next();
            }

        } else {

            selectedNet = null;

            if (!clickedNets.isEmpty()) {
                selectedNet = clickedNets.iterator().next();
            }
        }

        Net selectedNetSave = selectedNet;

        if (!((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0))
            clearSelection();

        selectedNet = selectedNetSave;

        if (selectedNet != null) {
            selectedNets.add(selectedNet);
            selectedNet.setHighlighted(true);
        }
    }

    public void clearSelection() {

        for (Iterator<Net> it = selectedNets.iterator(); it.hasNext();) {
            it.next().setHighlighted(false);
        }

        selectedNets.clear();

        selectedNet = null;
    }

    public void keyReleased(KeyEvent e) {

        switch (e.getKeyCode()) {

        case KeyEvent.VK_F:
            startProcess(new FatnessProcessor(this));
            break;

        case KeyEvent.VK_D:
            model.dump();
            break;

        case KeyEvent.VK_I: {

            System.out.println(selectedNets.size() + " selected nets:");

            for (Net net : selectedNets) {
                net.dump();
            }

            break;
        }

        case KeyEvent.VK_DELETE: {

            for (Net net : selectedNets) {

                Set<Net> superNet = net.getSuperNet();

                if (superNet == null) {
                    model.deleteNet(net);
                } else {
                    for (Iterator<Net> jt = superNet.iterator(); jt.hasNext();) {
                        model.deleteNet(jt.next());
                    }
                }
            }

            undoHistory.add(0, new UndoDelete(selectedNets));

            clearSelection();

            break;
        }

        case KeyEvent.VK_U: {

            if (!undoHistory.isEmpty()) {

                Iterator<UndoTask> it = undoHistory.iterator();

                UndoTask undoTask = it.next();
                it.remove();

                undoTask.undo();
            }

            break;
        }
        }
    }

    public boolean askContinue(int line, int seq) {
        return JOptionPane.showConfirmDialog(this,
                "Continue after line " + line + ((seq >= 0) ? ("(sequence number " + seq + ")") : "") + "?",
                "Continue?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
    }

    public Simulator getSimulator() {
        return simulator;
    }

    public Model getModel() {
        return model;
    }

    public Display getDisplay() {
        return display;
    }

    public void enableControls(boolean enable) {

        if (enable == true && loadField.getText() != null && !loadField.getText().endsWith("]")) {
            loadButton.setEnabled(true);
        } else {
            loadButton.setEnabled(false);
        }
        loadField.setEnabled(enable);
        browseButton.setEnabled(enable);

        mosaicButton.setEnabled(enable);
        mosaicField.setEnabled(enable);
        mosaicBrowseButton.setEnabled(enable);
        mosaicTilesButton.setEnabled(enable);

        topologyButton.setEnabled(enable);
        manualTopology.setEnabled(enable);

        toolpathsButton.setEnabled(enable);
        voronoiButton.setEnabled(enable);
        outlineButton.setEnabled(enable);

        if (myToolpathsProcessor != null) {
            gcodeButton.setEnabled(enable);
            gcodeField.setEnabled(enable);
            gcodeBrowseButton.setEnabled(enable);
        }

    }

    public void resetProgressBar(int numSteps) {
        progressBar.setMaximum(numSteps);
        progressBar.setValue(0);
    }

    public void tickProgressBar() {
        progressBar.setValue(progressBar.getValue() + 1);
    }

    public void startProcess(Processor processor) {

        stopProcess();

        this.processor = processor;

        stopButton.setEnabled(true);

        processor.start();
    }

    public void stopProcess() {

        if (processor == null)
            return;

        try {
            processor.stop();
        } catch (InterruptedException e) {
            System.err.println("WARNING: interrupted while stopping process");
        }

        processFinished();
    }

    public void processFinished() {

        progressBar.setValue(0);

        stopButton.setEnabled(false);

        processor = null;

        if (processstatus == 1) { //returning from automated topology fixing
            processstatus = 2;
            computeToolpaths();
        } else if (processstatus == 2) { //returning from automated toolpath creation
            System.out.println("Writing to gcode file: " + gcodeField.getText().trim());
            saveGCode();
            System.out.println("Exiting, all work done");
            System.exit(0);
        }

    }

    public void addFrameTask(Runnable task) {
        if (display != null)
            display.addFrameTask(task);
        else
            task.run();
    }

    private interface UndoTask {
        public void undo();
    }

    private class UndoDelete implements UndoTask {

        private UndoDelete(Collection<Net> nets) {

            for (Net net : nets) {

                Set<Net> superNet = net.getSuperNet();

                if (superNet == null) {
                    this.nets.add(net);
                } else {
                    this.nets.addAll(superNet);
                }
            }
        }

        public void undo() {
            //      System.out.println("undeleting " + nets.size() + " nets");
            for (Net net : nets) {
                model.undeleteNet(net);
            }
        }

        private Collection<Net> nets = new LinkedHashSet<Net>();
    }

    private Simulator simulator = null;
    public Model model = null;
    private Display display = null;
    private Processor processor = null;
    private ToolpathsProcessor myToolpathsProcessor = null;
    private GCodeFileWriter gCodeWriter = null;

    private JButton loadButton;
    private JTextField loadField;
    private JButton browseButton;

    private File currentFile = null;

    private JButton mosaicButton;
    private JTextField mosaicField;
    private JButton mosaicBrowseButton;
    private JCheckBox mosaicTilesButton;

    private JProgressBar progressBar;

    private JButton stopButton;

    private JButton topologyButton;
    private JCheckBox manualTopology;

    private JButton toolpathsButton;
    private JRadioButton voronoiButton;
    private JRadioButton outlineButton;

    private JButton gcodeButton;
    private JTextField gcodeField;
    private JButton gcodeBrowseButton;

    /**
     * The ToolpathsProcessor generates the g-code that we write to a file.
     */

    private Set<Net> selectedNets = new LinkedHashSet<Net>();
    private Net selectedNet = null;

    private List<UndoTask> undoHistory = new LinkedList<UndoTask>();

    private Box myProcessingBox;
    private Box myGCodeOptionsBox;

    private Box myMosaicBox;

    private Box myLoadFileBox;

    private Box myGcodeBox;

    private JRadioButton myRelativeCoordinatesButton;
    private JRadioButton myAbsoluteCoordinatesButton;
    private JRadioButton myImperialButton;
    private JRadioButton myMetricButton;
    private JPanel myZCuttingHeightPanel;
    private JPanel myZDownMovementPanel;
    private JPanel myMillingSpeedPanel;
    private JPanel myPlungeSpeedPanel;
    private JPanel myInitialXPanel;
    private JPanel myInitialYPanel;
}