/* * Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org> * * This file is part of OpenPnP. * * OpenPnP 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. * * OpenPnP 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 OpenPnP. If not, see * <http://www.gnu.org/licenses/>. * * For more information about OpenPnP visit http://openpnp.org */ package org.openpnp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FileDialog; import java.awt.Frame; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.List; import java.util.prefs.Preferences; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JMenu; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openpnp.ConfigurationListener; import org.openpnp.events.BoardLocationSelectedEvent; import org.openpnp.events.JobLoadedEvent; import org.openpnp.events.PlacementSelectedEvent; import org.openpnp.gui.components.AutoSelectTextTable; import org.openpnp.gui.importer.BoardImporter; import org.openpnp.gui.processes.TwoPlacementBoardLocationProcess; import org.openpnp.gui.support.ActionGroup; import org.openpnp.gui.support.Helpers; import org.openpnp.gui.support.Icons; import org.openpnp.gui.support.MessageBoxes; import org.openpnp.gui.tablemodel.BoardLocationsTableModel; import org.openpnp.model.Board; import org.openpnp.model.Board.Side; import org.openpnp.model.BoardLocation; import org.openpnp.model.BoardPad; import org.openpnp.model.Configuration; import org.openpnp.model.Job; import org.openpnp.model.Location; import org.openpnp.model.Placement; import org.openpnp.spi.Camera; import org.openpnp.spi.HeadMountable; import org.openpnp.spi.JobProcessor; import org.openpnp.spi.JobProcessor.TextStatusListener; import org.openpnp.spi.Machine; import org.openpnp.spi.MachineListener; import org.openpnp.util.FiniteStateMachine; import org.openpnp.util.MovableUtils; import org.openpnp.util.UiUtils; import com.google.common.eventbus.Subscribe; @SuppressWarnings("serial") public class JobPanel extends JPanel { enum State { Stopped, Running, Stepping } enum Message { StartOrPause, Step, Abort, Finished } @SuppressWarnings("unused") final private Configuration configuration; final private MainFrame frame; private static final String PREF_DIVIDER_POSITION = "JobPanel.dividerPosition"; private static final int PREF_DIVIDER_POSITION_DEF = -1; private static final String UNTITLED_JOB_FILENAME = "Untitled.job.xml"; private static final String PREF_RECENT_FILES = "JobPanel.recentFiles"; private static final int PREF_RECENT_FILES_MAX = 10; private BoardLocationsTableModel boardLocationsTableModel; private JTable boardLocationsTable; private JSplitPane splitPane; private ActionGroup jobSaveActionGroup; private ActionGroup boardLocationSelectionActionGroup; private Preferences prefs = Preferences.userNodeForPackage(JobPanel.class); public JMenu mnOpenRecent; private List<File> recentJobs = new ArrayList<>(); private final JobPlacementsPanel jobPlacementsPanel; private final JobPastePanel jobPastePanel; private JTabbedPane tabbedPane; private Job job; private JobProcessor jobProcessor; private FiniteStateMachine<State, Message> fsm = new FiniteStateMachine<>(State.Stopped); public JobPanel(Configuration configuration, MainFrame frame, MachineControlsPanel machineControlsPanel) { this.configuration = configuration; this.frame = frame; fsm.add(State.Stopped, Message.StartOrPause, State.Running, this::jobStart); fsm.add(State.Stopped, Message.Step, State.Stepping, this::jobStart); // No action is needed. The job is running and will exit when the state changes to Stepping. fsm.add(State.Running, Message.StartOrPause, State.Stepping); fsm.add(State.Running, Message.Abort, State.Stopped, this::jobAbort); fsm.add(State.Running, Message.Finished, State.Stopped); fsm.add(State.Stepping, Message.StartOrPause, State.Running, this::jobRun); fsm.add(State.Stepping, Message.Step, State.Stepping, this::jobRun); fsm.add(State.Stepping, Message.Abort, State.Stopped, this::jobAbort); fsm.add(State.Stepping, Message.Finished, State.Stopped); jobSaveActionGroup = new ActionGroup(saveJobAction); jobSaveActionGroup.setEnabled(false); boardLocationSelectionActionGroup = new ActionGroup(removeBoardAction, captureCameraBoardLocationAction, captureToolBoardLocationAction, moveCameraToBoardLocationAction, moveToolToBoardLocationAction, twoPointLocateBoardLocationAction, fiducialCheckAction); boardLocationSelectionActionGroup.setEnabled(false); boardLocationsTableModel = new BoardLocationsTableModel(configuration); // Suppress because adding the type specifiers breaks WindowBuilder. @SuppressWarnings({"unchecked", "rawtypes"}) JComboBox sidesComboBox = new JComboBox(Side.values()); boardLocationsTable = new AutoSelectTextTable(boardLocationsTableModel); boardLocationsTable.setAutoCreateRowSorter(true); boardLocationsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); boardLocationsTable.setDefaultEditor(Side.class, new DefaultCellEditor(sidesComboBox)); boardLocationsTable.getSelectionModel() .addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } BoardLocation boardLocation = getSelectedBoardLocation(); boardLocationSelectionActionGroup.setEnabled(boardLocation != null); jobPlacementsPanel.setBoardLocation(boardLocation); jobPastePanel.setBoardLocation(boardLocation); Configuration.get().getBus().post(new BoardLocationSelectedEvent(boardLocation)); } }); setLayout(new BorderLayout(0, 0)); splitPane = new JSplitPane(); splitPane.setBorder(null); splitPane.setContinuousLayout(true); splitPane .setDividerLocation(prefs.getInt(PREF_DIVIDER_POSITION, PREF_DIVIDER_POSITION_DEF)); splitPane.addPropertyChangeListener("dividerLocation", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { prefs.putInt(PREF_DIVIDER_POSITION, splitPane.getDividerLocation()); } }); JPanel pnlBoards = new JPanel(); pnlBoards.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null), "Boards", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0))); pnlBoards.setLayout(new BorderLayout(0, 0)); JToolBar toolBarBoards = new JToolBar(); toolBarBoards.setFloatable(false); pnlBoards.add(toolBarBoards, BorderLayout.NORTH); JButton btnStartPauseResumeJob = new JButton(startPauseResumeJobAction); btnStartPauseResumeJob.setHideActionText(true); toolBarBoards.add(btnStartPauseResumeJob); JButton btnStepJob = new JButton(stepJobAction); btnStepJob.setHideActionText(true); toolBarBoards.add(btnStepJob); JButton btnStopJob = new JButton(stopJobAction); btnStopJob.setHideActionText(true); toolBarBoards.add(btnStopJob); toolBarBoards.addSeparator(); JButton btnNewBoard = new JButton(newBoardAction); btnNewBoard.setHideActionText(true); toolBarBoards.add(btnNewBoard); JButton btnAddBoard = new JButton(addBoardAction); btnAddBoard.setHideActionText(true); toolBarBoards.add(btnAddBoard); JButton btnRemoveBoard = new JButton(removeBoardAction); btnRemoveBoard.setHideActionText(true); toolBarBoards.add(btnRemoveBoard); toolBarBoards.addSeparator(); JButton btnCaptureCameraBoardLocation = new JButton(captureCameraBoardLocationAction); btnCaptureCameraBoardLocation.setHideActionText(true); toolBarBoards.add(btnCaptureCameraBoardLocation); JButton btnCaptureToolBoardLocation = new JButton(captureToolBoardLocationAction); btnCaptureToolBoardLocation.setHideActionText(true); toolBarBoards.add(btnCaptureToolBoardLocation); JButton btnPositionCameraBoardLocation = new JButton(moveCameraToBoardLocationAction); btnPositionCameraBoardLocation.setHideActionText(true); toolBarBoards.add(btnPositionCameraBoardLocation); JButton btnPositionToolBoardLocation = new JButton(moveToolToBoardLocationAction); btnPositionToolBoardLocation.setHideActionText(true); toolBarBoards.add(btnPositionToolBoardLocation); toolBarBoards.addSeparator(); JButton btnTwoPointBoardLocation = new JButton(twoPointLocateBoardLocationAction); toolBarBoards.add(btnTwoPointBoardLocation); btnTwoPointBoardLocation.setHideActionText(true); JButton btnFiducialCheck = new JButton(fiducialCheckAction); toolBarBoards.add(btnFiducialCheck); btnFiducialCheck.setHideActionText(true); pnlBoards.add(new JScrollPane(boardLocationsTable)); JPanel pnlRight = new JPanel(); pnlRight.setLayout(new BorderLayout(0, 0)); splitPane.setLeftComponent(pnlBoards); splitPane.setRightComponent(pnlRight); tabbedPane = new JTabbedPane(JTabbedPane.TOP); pnlRight.add(tabbedPane, BorderLayout.CENTER); jobPastePanel = new JobPastePanel(this); jobPlacementsPanel = new JobPlacementsPanel(this); add(splitPane); mnOpenRecent = new JMenu("Open Recent Job..."); loadRecentJobs(); Configuration.get().addListener(new ConfigurationListener.Adapter() { public void configurationComplete(Configuration configuration) throws Exception { Machine machine = configuration.getMachine(); machine.addListener(machineListener); if (machine.getPnpJobProcessor() != null) { tabbedPane.addTab("Pick and Place", null, jobPlacementsPanel, null); machine.getPnpJobProcessor().addTextStatusListener(textStatusListener); } if (machine.getPasteDispenseJobProcessor() != null) { tabbedPane.addTab("Solder Paste", null, jobPastePanel, null); machine.getPasteDispenseJobProcessor().addTextStatusListener(textStatusListener); } // Create an empty Job if one is not loaded if (getJob() == null) { setJob(new Job()); } } }); fsm.addPropertyChangeListener((e) -> { updateJobActions(); }); Configuration.get().getBus().register(this); } @Subscribe public void boardLocationSelected(BoardLocationSelectedEvent event) { SwingUtilities.invokeLater(() -> { MainFrame.get().showTab("Job"); selectBoardLocation(event.boardLocation); }); } @Subscribe public void placementSelected(PlacementSelectedEvent event) { SwingUtilities.invokeLater(() -> { MainFrame.get().showTab("Job"); showTab("Pick and Place"); selectBoardLocation(event.boardLocation); jobPlacementsPanel.selectPlacement(event.placement); }); } private void selectBoardLocation(BoardLocation boardLocation) { for (int i = 0; i < boardLocationsTableModel.getRowCount(); i++) { if (boardLocationsTableModel.getBoardLocation(i) == boardLocation) { boardLocationsTable.getSelectionModel().setSelectionInterval(i, i); boardLocationsTable.scrollRectToVisible(new Rectangle(boardLocationsTable.getCellRect(i, 0, true))); break; } } } private void showTab(String title) { int index = tabbedPane.indexOfTab(title); tabbedPane.setSelectedIndex(index); } public Job getJob() { return job; } public void setJob(Job job) { if (this.job != null) { this.job.removePropertyChangeListener("dirty", titlePropertyChangeListener); this.job.removePropertyChangeListener("file", titlePropertyChangeListener); } this.job = job; boardLocationsTableModel.setJob(job); job.addPropertyChangeListener("dirty", titlePropertyChangeListener); job.addPropertyChangeListener("file", titlePropertyChangeListener); updateTitle(); updateJobActions(); Configuration.get().getBus().post(new JobLoadedEvent(job)); } public JobPlacementsPanel getJobPlacementsPanel() { return jobPlacementsPanel; } private void updateRecentJobsMenu() { mnOpenRecent.removeAll(); for (File file : recentJobs) { mnOpenRecent.add(new OpenRecentJobAction(file)); } } private void loadRecentJobs() { recentJobs.clear(); for (int i = 0; i < PREF_RECENT_FILES_MAX; i++) { String path = prefs.get(PREF_RECENT_FILES + "_" + i, null); if (path != null && new File(path).exists()) { File file = new File(path); recentJobs.add(file); } } updateRecentJobsMenu(); } private void saveRecentJobs() { // blow away all the existing values for (int i = 0; i < PREF_RECENT_FILES_MAX; i++) { prefs.remove(PREF_RECENT_FILES + "_" + i); } // update with what we have now for (int i = 0; i < recentJobs.size(); i++) { prefs.put(PREF_RECENT_FILES + "_" + i, recentJobs.get(i).getAbsolutePath()); } updateRecentJobsMenu(); } private void addRecentJob(File file) { while (recentJobs.contains(file)) { recentJobs.remove(file); } // add to top recentJobs.add(0, file); // limit length while (recentJobs.size() > PREF_RECENT_FILES_MAX) { recentJobs.remove(recentJobs.size() - 1); } saveRecentJobs(); } public void refreshSelectedBoardRow() { boardLocationsTableModel.fireTableRowsUpdated(boardLocationsTable.getSelectedRow(), boardLocationsTable.getSelectedRow()); } public BoardLocation getSelectedBoardLocation() { int index = boardLocationsTable.getSelectedRow(); if (index == -1) { return null; } else { index = boardLocationsTable.convertRowIndexToModel(index); return getJob().getBoardLocations().get(index); } } /** * Checks if there are any modifications that need to be saved. Prompts the user if there are. * Returns true if it's okay to exit. * * @return */ public boolean checkForModifications() { if (!checkForBoardModifications()) { return false; } if (!checkForJobModifications()) { return false; } return true; } private boolean checkForJobModifications() { if (getJob().isDirty()) { String name = (job.getFile() == null ? UNTITLED_JOB_FILENAME : job.getFile().getName()); int result = JOptionPane.showConfirmDialog(frame, "Do you want to save your changes to " + name + "?" + "\n" + "If you don't save, your changes will be lost.", "Save " + name + "?", JOptionPane.YES_NO_CANCEL_OPTION); if (result == JOptionPane.YES_OPTION) { return saveJob(); } else if (result == JOptionPane.CANCEL_OPTION) { return false; } } return true; } private boolean checkForBoardModifications() { for (Board board : configuration.getBoards()) { if (board.isDirty()) { int result = JOptionPane.showConfirmDialog(getTopLevelAncestor(), "Do you want to save your changes to " + board.getFile().getName() + "?" + "\n" + "If you don't save, your changes will be lost.", "Save " + board.getFile().getName() + "?", JOptionPane.YES_NO_CANCEL_OPTION); if (result == JOptionPane.YES_OPTION) { try { configuration.saveBoard(board); } catch (Exception e) { MessageBoxes.errorBox(getTopLevelAncestor(), "Board Save Error", e.getMessage()); return false; } } else if (result == JOptionPane.CANCEL_OPTION) { return false; } } } return true; } private boolean saveJob() { if (getJob().getFile() == null) { return saveJobAs(); } else { try { File file = getJob().getFile(); configuration.saveJob(getJob(), file); addRecentJob(file); return true; } catch (Exception e) { MessageBoxes.errorBox(frame, "Job Save Error", e.getMessage()); return false; } } } private boolean saveJobAs() { FileDialog fileDialog = new FileDialog(frame, "Save Job As...", FileDialog.SAVE); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".job.xml"); } }); fileDialog.setVisible(true); try { String filename = fileDialog.getFile(); if (filename == null) { return false; } if (!filename.toLowerCase().endsWith(".job.xml")) { filename = filename + ".job.xml"; } File file = new File(new File(fileDialog.getDirectory()), filename); if (file.exists()) { int ret = JOptionPane.showConfirmDialog(getTopLevelAncestor(), file.getName() + " already exists. Do you want to replace it?", "Replace file?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (ret != JOptionPane.YES_OPTION) { return false; } } configuration.saveJob(getJob(), file); addRecentJob(file); return true; } catch (Exception e) { MessageBoxes.errorBox(frame, "Job Save Error", e.getMessage()); return false; } } /** * Updates the Job controls based on the Job state and the Machine's readiness. */ private void updateJobActions() { if (fsm.getState() == State.Stopped) { startPauseResumeJobAction.setEnabled(true); startPauseResumeJobAction.putValue(AbstractAction.NAME, "Start"); startPauseResumeJobAction.putValue(AbstractAction.SMALL_ICON, Icons.start); startPauseResumeJobAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Start processing the job."); stopJobAction.setEnabled(false); stepJobAction.setEnabled(true); tabbedPane.setEnabled(true); } else if (fsm.getState() == State.Running) { startPauseResumeJobAction.setEnabled(true); startPauseResumeJobAction.putValue(AbstractAction.NAME, "Pause"); startPauseResumeJobAction.putValue(AbstractAction.SMALL_ICON, Icons.pause); startPauseResumeJobAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Pause processing of the job."); stopJobAction.setEnabled(true); stepJobAction.setEnabled(false); tabbedPane.setEnabled(false); } else if (fsm.getState() == State.Stepping) { startPauseResumeJobAction.setEnabled(true); startPauseResumeJobAction.putValue(AbstractAction.NAME, "Resume"); startPauseResumeJobAction.putValue(AbstractAction.SMALL_ICON, Icons.start); startPauseResumeJobAction.putValue(AbstractAction.SHORT_DESCRIPTION, "Resume processing of the job."); stopJobAction.setEnabled(true); stepJobAction.setEnabled(true); tabbedPane.setEnabled(false); } // We allow the above to run first so that all state is represented // correctly even if the machine is disabled. if (!configuration.getMachine().isEnabled()) { startPauseResumeJobAction.setEnabled(false); stopJobAction.setEnabled(false); stepJobAction.setEnabled(false); } } private void updateTitle() { String title = String.format("OpenPnP - %s%s", job.isDirty() ? "*" : "", (job.getFile() == null ? UNTITLED_JOB_FILENAME : job.getFile().getName())); frame.setTitle(title); } public void importBoard(Class<? extends BoardImporter> boardImporterClass) { if (getSelectedBoardLocation() == null) { MessageBoxes.errorBox(getTopLevelAncestor(), "Import Failed", "Please select a board in the Jobs tab to import into."); return; } BoardImporter boardImporter; try { boardImporter = boardImporterClass.newInstance(); } catch (Exception e) { MessageBoxes.errorBox(getTopLevelAncestor(), "Import Failed", e); return; } try { Board importedBoard = boardImporter.importBoard((Frame) getTopLevelAncestor()); if (importedBoard != null) { Board existingBoard = getSelectedBoardLocation().getBoard(); for (Placement placement : importedBoard.getPlacements()) { existingBoard.addPlacement(placement); } for (BoardPad pad : importedBoard.getSolderPastePads()) { // TODO: This is a temporary hack until we redesign the importer // interface to be more intuitive. The Gerber importer tends // to return everything in Inches, so this is a method to // try to get it closer to what the user expects to see. pad.setLocation(pad.getLocation() .convertToUnits(getSelectedBoardLocation().getLocation().getUnits())); existingBoard.addSolderPastePad(pad); } jobPlacementsPanel.setBoardLocation(getSelectedBoardLocation()); jobPastePanel.setBoardLocation(getSelectedBoardLocation()); } } catch (Exception e) { MessageBoxes.errorBox(getTopLevelAncestor(), "Import Failed", e); } } public final Action openJobAction = new AbstractAction("Open Job...") { @Override public void actionPerformed(ActionEvent arg0) { if (!checkForModifications()) { return; } FileDialog fileDialog = new FileDialog(frame); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".job.xml"); } }); fileDialog.setVisible(true); try { if (fileDialog.getFile() == null) { return; } File file = new File(new File(fileDialog.getDirectory()), fileDialog.getFile()); Job job = configuration.loadJob(file); setJob(job); addRecentJob(file); } catch (Exception e) { e.printStackTrace(); MessageBoxes.errorBox(frame, "Job Load Error", e.getMessage()); } } }; public final Action newJobAction = new AbstractAction("New Job") { @Override public void actionPerformed(ActionEvent arg0) { if (!checkForModifications()) { return; } setJob(new Job()); } }; public final Action saveJobAction = new AbstractAction("Save Job") { @Override public void actionPerformed(ActionEvent arg0) { saveJob(); } }; public final Action saveJobAsAction = new AbstractAction("Save Job As...") { @Override public void actionPerformed(ActionEvent arg0) { saveJobAs(); } }; /** * Initialize the job processor and start the run thread. The run thread will run one step and * then either loop if the state is Running or exit if the state is Stepping. * * @throws Exception */ public void jobStart() throws Exception { String title = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()); if (title.equals("Solder Paste")) { jobProcessor = Configuration.get().getMachine().getPasteDispenseJobProcessor(); } else if (title.equals("Pick and Place")) { if((jobProcessor == null || jobProcessor == Configuration.get().getMachine().getPnpJobProcessor()) && (Configuration.get().getMachine().getGlueDispenseJobProcessor()!=null)) { // Run the glue dispense processor first, this will deposit glue ready for any component placements jobProcessor = Configuration.get().getMachine().getGlueDispenseJobProcessor(); } else { jobProcessor = Configuration.get().getMachine().getPnpJobProcessor(); } } else { throw new Error("Programmer error: Unknown tab title."); } jobProcessor.initialize(job); jobRun(); } public void jobRun() { UiUtils.submitUiMachineTask(() -> { // Make sure the FSM has actually transitioned to either Running or Stepping // before continuing so that we don't accidentally exit early. This breaks // the potential race condition where this task may execute before the // calling task (setting the FSM state) finishes. while (fsm.getState() != State.Running && fsm.getState() != State.Stepping); do { if (!jobProcessor.next()) { fsm.send(Message.Finished); } } while (fsm.getState() == State.Running); // if this was the glue dispense run and we've finished, kick off the pick & place if(Configuration.get().getMachine().getGlueDispenseJobProcessor()!=null && jobProcessor==Configuration.get().getMachine().getGlueDispenseJobProcessor()) { fsm.send(Message.StartOrPause); } return null; }, (e) -> { }, (t) -> { List<String> options = new ArrayList<>(); String retryOption = "Try Again"; String skipOption = "Skip"; String pauseOption = "Pause Job"; options.add(retryOption); if (jobProcessor.canSkip()) { options.add(skipOption); } options.add(pauseOption); int result = JOptionPane.showOptionDialog(getTopLevelAncestor(), t.getMessage(), "Job Error", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE, null, options.toArray(), retryOption); String selectedOption = options.get(result); if (selectedOption.equals(retryOption)) { jobRun(); } // Skip else if (selectedOption.equals(skipOption)) { UiUtils.messageBoxOnException(() -> { // Tell the job processor to skip the current placement and then call jobRun() // to start things back up, either running or stepping. jobSkip(); }); } // Pause or cancel dialog else { // We are either Running or Stepping. If Stepping, there is nothing to do. Just // clear the dialog and let the user take control. If Running we need to transition // to Stepping. if (fsm.getState() == State.Running) { try { fsm.send(Message.StartOrPause); } catch (Exception e) { // Since we are checking if we're in the Running state this should not // ever happen. If it does, the Error will let us know. e.printStackTrace(); throw new Error(e); } } } }); } public void jobSkip() { UiUtils.submitUiMachineTask(() -> { jobProcessor.skip(); jobRun(); }); } private void jobAbort() { UiUtils.submitUiMachineTask(() -> { jobProcessor.abort(); }); } public final Action startPauseResumeJobAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.start); putValue(NAME, "Start"); putValue(SHORT_DESCRIPTION, "Start processing the job."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.messageBoxOnException(() -> { fsm.send(Message.StartOrPause); }); } }; public final Action stepJobAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.step); putValue(NAME, "Step"); putValue(SHORT_DESCRIPTION, "Process one step of the job and pause."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.messageBoxOnException(() -> { fsm.send(Message.Step); }); } }; public final Action stopJobAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.stop); putValue(NAME, "Stop"); putValue(SHORT_DESCRIPTION, "Stop processing the job."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.messageBoxOnException(() -> { fsm.send(Message.Abort); }); } }; public final Action newBoardAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.neww); putValue(NAME, "New Board..."); putValue(SHORT_DESCRIPTION, "Create a new board and add it to the job."); } @Override public void actionPerformed(ActionEvent arg0) { FileDialog fileDialog = new FileDialog(frame, "Save New Board As...", FileDialog.SAVE); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".board.xml"); } }); fileDialog.setVisible(true); try { String filename = fileDialog.getFile(); if (filename == null) { return; } if (!filename.toLowerCase().endsWith(".board.xml")) { filename = filename + ".board.xml"; } File file = new File(new File(fileDialog.getDirectory()), filename); Board board = configuration.getBoard(file); BoardLocation boardLocation = new BoardLocation(board); getJob().addBoardLocation(boardLocation); boardLocationsTableModel.fireTableDataChanged(); Helpers.selectLastTableRow(boardLocationsTable); } catch (Exception e) { e.printStackTrace(); MessageBoxes.errorBox(frame, "Unable to create new board", e.getMessage()); } } }; public final Action addBoardAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.add); putValue(NAME, "Add Board..."); putValue(SHORT_DESCRIPTION, "Add an existing board to the job."); } @Override public void actionPerformed(ActionEvent arg0) { FileDialog fileDialog = new FileDialog(frame); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".board.xml"); } }); fileDialog.setVisible(true); try { if (fileDialog.getFile() == null) { return; } File file = new File(new File(fileDialog.getDirectory()), fileDialog.getFile()); Board board = configuration.getBoard(file); BoardLocation boardLocation = new BoardLocation(board); getJob().addBoardLocation(boardLocation); // TODO: Move to a list property listener. boardLocationsTableModel.fireTableDataChanged(); Helpers.selectLastTableRow(boardLocationsTable); } catch (Exception e) { e.printStackTrace(); MessageBoxes.errorBox(frame, "Board load failed", e.getMessage()); } } }; public final Action removeBoardAction = new AbstractAction("Remove Board") { { putValue(SMALL_ICON, Icons.delete); putValue(NAME, "Remove Board"); putValue(SHORT_DESCRIPTION, "Remove the selected board from the job."); } @Override public void actionPerformed(ActionEvent arg0) { int index = boardLocationsTable.getSelectedRow(); if (index != -1) { index = boardLocationsTable.convertRowIndexToModel(index); BoardLocation boardLocation = getJob().getBoardLocations().get(index); getJob().removeBoardLocation(boardLocation); boardLocationsTableModel.fireTableDataChanged(); } } }; public final Action captureCameraBoardLocationAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.captureCamera); putValue(NAME, "Capture Camera Location"); putValue(SHORT_DESCRIPTION, "Set the board's location to the camera's current position."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.messageBoxOnException(() -> { HeadMountable tool = MainFrame.get().getMachineControls().getSelectedTool(); Camera camera = tool.getHead().getDefaultCamera(); double z = getSelectedBoardLocation().getLocation().getZ(); getSelectedBoardLocation() .setLocation(camera.getLocation().derive(null, null, z, null)); boardLocationsTableModel.fireTableRowsUpdated(boardLocationsTable.getSelectedRow(), boardLocationsTable.getSelectedRow()); }); } }; public final Action captureToolBoardLocationAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.captureTool); putValue(NAME, "Capture Tool Location"); putValue(SHORT_DESCRIPTION, "Set the board's location to the tool's current position."); } @Override public void actionPerformed(ActionEvent arg0) { HeadMountable tool = MainFrame.get().getMachineControls().getSelectedTool(); double z = getSelectedBoardLocation().getLocation().getZ(); getSelectedBoardLocation().setLocation(tool.getLocation().derive(null, null, z, null)); boardLocationsTableModel.fireTableRowsUpdated(boardLocationsTable.getSelectedRow(), boardLocationsTable.getSelectedRow()); } }; public final Action moveCameraToBoardLocationAction = new AbstractAction("Move Camera To Board Location") { { putValue(SMALL_ICON, Icons.centerCamera); putValue(NAME, "Move Camera To Board Location"); putValue(SHORT_DESCRIPTION, "Position the camera at the board's location."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.submitUiMachineTask(() -> { HeadMountable tool = MainFrame.get().getMachineControls().getSelectedTool(); Camera camera = tool.getHead().getDefaultCamera(); MainFrame.get().getCameraViews().ensureCameraVisible(camera); Location location = getSelectedBoardLocation().getLocation(); MovableUtils.moveToLocationAtSafeZ(camera, location); }); } }; public final Action moveToolToBoardLocationAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.centerTool); putValue(NAME, "Move Tool To Board Location"); putValue(SHORT_DESCRIPTION, "Position the tool at the board's location."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.submitUiMachineTask(() -> { HeadMountable tool = MainFrame.get().getMachineControls().getSelectedTool(); Location location = getSelectedBoardLocation().getLocation(); MovableUtils.moveToLocationAtSafeZ(tool, location); }); } }; public final Action twoPointLocateBoardLocationAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.twoPointLocate); putValue(NAME, "Two Point Board Location"); putValue(SHORT_DESCRIPTION, "Set the board's location and rotation using two placements."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.messageBoxOnException(() -> { new TwoPlacementBoardLocationProcess(frame, JobPanel.this); }); } }; public final Action fiducialCheckAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.fiducialCheck); putValue(NAME, "Fiducial Check"); putValue(SHORT_DESCRIPTION, "Perform a fiducial check for the board and update it's location and rotation."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.submitUiMachineTask(() -> { Location location = Configuration.get().getMachine().getFiducialLocator() .locateBoard(getSelectedBoardLocation()); getSelectedBoardLocation().setLocation(location); refreshSelectedBoardRow(); HeadMountable tool = MainFrame.get().getMachineControls().getSelectedTool(); Camera camera = tool.getHead().getDefaultCamera(); MainFrame.get().getCameraViews().ensureCameraVisible(camera); MovableUtils.moveToLocationAtSafeZ(camera, location); }); } }; public class OpenRecentJobAction extends AbstractAction { private final File file; public OpenRecentJobAction(File file) { this.file = file; putValue(NAME, file.getName()); } @Override public void actionPerformed(ActionEvent arg0) { if (!checkForModifications()) { return; } try { Job job = configuration.loadJob(file); setJob(job); addRecentJob(file); } catch (Exception e) { e.printStackTrace(); MessageBoxes.errorBox(frame, "Job Load Error", e.getMessage()); } } } private final MachineListener machineListener = new MachineListener.Adapter() { @Override public void machineEnabled(Machine machine) { updateJobActions(); } @Override public void machineDisabled(Machine machine, String reason) { // TODO This fails. When we get this message the machine is already // disabled so we can't perform the abort actions. if (fsm.getState() != State.Stopped) { try { fsm.send(Message.Abort); } catch (Exception e) { e.printStackTrace(); } } updateJobActions(); } }; private final PropertyChangeListener titlePropertyChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { updateTitle(); jobSaveActionGroup.setEnabled(getJob().isDirty()); } }; private final TextStatusListener textStatusListener = text -> { MainFrame.get().setStatus(text); }; }