package org.openpnp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; 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.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.LineBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import org.openpnp.events.BoardLocationSelectedEvent; import org.openpnp.events.PlacementSelectedEvent; import org.openpnp.gui.components.AutoSelectTextTable; import org.openpnp.gui.support.ActionGroup; import org.openpnp.gui.support.Helpers; import org.openpnp.gui.support.Icons; import org.openpnp.gui.support.IdentifiableListCellRenderer; import org.openpnp.gui.support.IdentifiableTableCellRenderer; import org.openpnp.gui.support.MessageBoxes; import org.openpnp.gui.support.PartsComboBoxModel; import org.openpnp.gui.tablemodel.PlacementsTableModel; import org.openpnp.gui.tablemodel.PlacementsTableModel.Status; import org.openpnp.model.Board; import org.openpnp.model.Board.Side; import org.openpnp.model.BoardLocation; import org.openpnp.model.Configuration; import org.openpnp.model.Location; import org.openpnp.model.Part; import org.openpnp.model.Placement; import org.openpnp.model.Placement.Type; import org.openpnp.spi.Camera; import org.openpnp.spi.HeadMountable; import org.openpnp.spi.Nozzle; import org.openpnp.util.MovableUtils; import org.openpnp.util.UiUtils; import org.openpnp.util.Utils2D; import com.google.common.eventbus.Subscribe; public class JobPlacementsPanel extends JPanel { private JTable table; private PlacementsTableModel tableModel; private ActionGroup boardLocationSelectionActionGroup; private ActionGroup singleSelectionActionGroup; private ActionGroup multiSelectionActionGroup; private ActionGroup captureAndPositionActionGroup; private BoardLocation boardLocation; private static Color typeColorIgnore = new Color(252, 255, 157); private static Color typeColorFiducial = new Color(157, 188, 255); private static Color typeColorPlace = new Color(157, 255, 168); private static Color statusColorWarning = new Color(252, 255, 157); private static Color statusColorReady = new Color(157, 255, 168); private static Color statusColorError = new Color(255, 157, 157); public JobPlacementsPanel(JobPanel jobPanel) { Configuration configuration = Configuration.get(); boardLocationSelectionActionGroup = new ActionGroup(newAction); boardLocationSelectionActionGroup.setEnabled(false); singleSelectionActionGroup = new ActionGroup(removeAction, editPlacementFeederAction, setTypeAction); singleSelectionActionGroup.setEnabled(false); multiSelectionActionGroup = new ActionGroup(removeAction, setTypeAction); multiSelectionActionGroup.setEnabled(false); captureAndPositionActionGroup = new ActionGroup(captureCameraPlacementLocation, captureToolPlacementLocation, moveCameraToPlacementLocation, moveToolToPlacementLocation); captureAndPositionActionGroup.setEnabled(false); JComboBox<PartsComboBoxModel> partsComboBox = new JComboBox(new PartsComboBoxModel()); partsComboBox.setRenderer(new IdentifiableListCellRenderer<Part>()); JComboBox<Side> sidesComboBox = new JComboBox(Side.values()); JComboBox<Type> typesComboBox = new JComboBox(Type.values()); setLayout(new BorderLayout(0, 0)); JToolBar toolBarPlacements = new JToolBar(); add(toolBarPlacements, BorderLayout.NORTH); toolBarPlacements.setFloatable(false); JButton btnNewPlacement = new JButton(newAction); btnNewPlacement.setHideActionText(true); toolBarPlacements.add(btnNewPlacement); JButton btnRemovePlacement = new JButton(removeAction); btnRemovePlacement.setHideActionText(true); toolBarPlacements.add(btnRemovePlacement); toolBarPlacements.addSeparator(); JButton btnCaptureCameraPlacementLocation = new JButton(captureCameraPlacementLocation); btnCaptureCameraPlacementLocation.setHideActionText(true); toolBarPlacements.add(btnCaptureCameraPlacementLocation); JButton btnCaptureToolPlacementLocation = new JButton(captureToolPlacementLocation); btnCaptureToolPlacementLocation.setHideActionText(true); toolBarPlacements.add(btnCaptureToolPlacementLocation); JButton btnPositionCameraPositionLocation = new JButton(moveCameraToPlacementLocation); btnPositionCameraPositionLocation.setHideActionText(true); toolBarPlacements.add(btnPositionCameraPositionLocation); JButton btnPositionToolPositionLocation = new JButton(moveToolToPlacementLocation); btnPositionToolPositionLocation.setHideActionText(true); toolBarPlacements.add(btnPositionToolPositionLocation); toolBarPlacements.addSeparator(); JButton btnEditFeeder = new JButton(editPlacementFeederAction); btnEditFeeder.setHideActionText(true); toolBarPlacements.add(btnEditFeeder); tableModel = new PlacementsTableModel(configuration); table = new AutoSelectTextTable(tableModel); table.setAutoCreateRowSorter(true); table.getTableHeader().setDefaultRenderer(new MultisortTableHeaderCellRenderer()); table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); table.setDefaultEditor(Side.class, new DefaultCellEditor(sidesComboBox)); table.setDefaultEditor(Part.class, new DefaultCellEditor(partsComboBox)); table.setDefaultEditor(Type.class, new DefaultCellEditor(typesComboBox)); table.setDefaultRenderer(Part.class, new IdentifiableTableCellRenderer<Part>()); table.setDefaultRenderer(PlacementsTableModel.Status.class, new StatusRenderer()); table.setDefaultRenderer(Placement.Type.class, new TypeRenderer()); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } if (getSelections().size() > 1) { // multi select singleSelectionActionGroup.setEnabled(false); captureAndPositionActionGroup.setEnabled(false); multiSelectionActionGroup.setEnabled(true); } else { // single select, or no select multiSelectionActionGroup.setEnabled(false); singleSelectionActionGroup.setEnabled(getSelection() != null); captureAndPositionActionGroup.setEnabled(getSelection() != null && getSelection().getSide() == boardLocation.getSide()); Configuration.get().getBus().post(new PlacementSelectedEvent(getSelection(), boardLocation)); } } }); table.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent mouseEvent) { if (mouseEvent.getClickCount() != 2) { return; } int row = table.rowAtPoint(new Point(mouseEvent.getX(), mouseEvent.getY())); int col = table.columnAtPoint(new Point(mouseEvent.getX(), mouseEvent.getY())); if (tableModel.getColumnClass(col) == Status.class) { Status status = (Status) tableModel.getValueAt(row, col); // TODO: This is some sample code for handling the user // wishing to do something with the status. Not using it // right now but leaving it here for the future. System.out.println(status); } } }); table.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { if (e.getKeyChar() == ' ') { Placement placement = getSelection(); placement.setType(placement.getType() == Type.Place ? Type.Ignore : Type.Place); tableModel.fireTableRowsUpdated(table.getSelectedRow(), table.getSelectedRow()); } else { super.keyTyped(e); } } }); JPopupMenu popupMenu = new JPopupMenu(); JMenu setTypeMenu = new JMenu(setTypeAction); for (Placement.Type type : Placement.Type.values()) { setTypeMenu.add(new SetTypeAction(type)); } popupMenu.add(setTypeMenu); JMenu setSideMenu = new JMenu(setSideAction); for (Board.Side side : Board.Side.values()) { setSideMenu.add(new SetSideAction(side)); } popupMenu.add(setSideMenu); table.setComponentPopupMenu(popupMenu); JScrollPane scrollPane = new JScrollPane(table); add(scrollPane, BorderLayout.CENTER); } public void selectPlacement(Placement placement) { for (int i = 0; i < tableModel.getRowCount(); i++) { if (tableModel.getPlacement(i) == placement) { table.getSelectionModel().setSelectionInterval(i, i); table.scrollRectToVisible(new Rectangle(table.getCellRect(i, 0, true))); break; } } } public void setBoardLocation(BoardLocation boardLocation) { this.boardLocation = boardLocation; if (boardLocation == null) { tableModel.setBoard(null); boardLocationSelectionActionGroup.setEnabled(false); } else { tableModel.setBoard(boardLocation.getBoard()); boardLocationSelectionActionGroup.setEnabled(true); } } public Placement getSelection() { List<Placement> selectedPlacements = getSelections(); if (selectedPlacements.isEmpty()) { return null; } return selectedPlacements.get(0); } public List<Placement> getSelections() { ArrayList<Placement> placements = new ArrayList<>(); if (boardLocation == null) { return placements; } int[] selectedRows = table.getSelectedRows(); for (int selectedRow : selectedRows) { selectedRow = table.convertRowIndexToModel(selectedRow); placements.add(boardLocation.getBoard().getPlacements().get(selectedRow)); } return placements; } public final Action newAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.add); putValue(NAME, "New Placement"); putValue(SHORT_DESCRIPTION, "Create a new placement and add it to the board."); } @Override public void actionPerformed(ActionEvent arg0) { if (Configuration.get().getParts().size() == 0) { MessageBoxes.errorBox(getTopLevelAncestor(), "Error", "There are currently no parts defined in the system. Please create at least one part before creating a placement."); return; } String id = JOptionPane.showInputDialog(getTopLevelAncestor(), "Please enter an ID for the new placement."); if (id == null) { return; } // TODO: Make sure it's unique. Placement placement = new Placement(id); placement.setPart(Configuration.get().getParts().get(0)); placement.setLocation(new Location(Configuration.get().getSystemUnits())); boardLocation.getBoard().addPlacement(placement); tableModel.fireTableDataChanged(); Helpers.selectLastTableRow(table); } }; public final Action removeAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.delete); putValue(NAME, "Remove Placement(s)"); putValue(SHORT_DESCRIPTION, "Remove the currently selected placement(s)."); } @Override public void actionPerformed(ActionEvent arg0) { for (Placement placement : getSelections()) { boardLocation.getBoard().removePlacement(placement); } tableModel.fireTableDataChanged(); } }; public final Action moveCameraToPlacementLocation = new AbstractAction() { { putValue(SMALL_ICON, Icons.centerCamera); putValue(NAME, "Move Camera To Placement Location"); putValue(SHORT_DESCRIPTION, "Position the camera at the placement's location."); } @Override public void actionPerformed(ActionEvent arg0) { UiUtils.submitUiMachineTask(() -> { Location location = Utils2D.calculateBoardPlacementLocation(boardLocation, getSelection().getLocation()); Camera camera = MainFrame.get().getMachineControls().getSelectedTool().getHead() .getDefaultCamera(); MovableUtils.moveToLocationAtSafeZ(camera, location); }); } }; public final Action moveToolToPlacementLocation = new AbstractAction() { { putValue(SMALL_ICON, Icons.centerTool); putValue(NAME, "Move Tool To Placement Location"); putValue(SHORT_DESCRIPTION, "Position the tool at the placement's location."); } @Override public void actionPerformed(ActionEvent arg0) { Location location = Utils2D.calculateBoardPlacementLocation(boardLocation, getSelection().getLocation()); Nozzle nozzle = MainFrame.get().getMachineControls().getSelectedNozzle(); UiUtils.submitUiMachineTask(() -> { MovableUtils.moveToLocationAtSafeZ(nozzle, location); }); } }; public final Action captureCameraPlacementLocation = new AbstractAction() { { putValue(SMALL_ICON, Icons.captureCamera); putValue(NAME, "Capture Camera Placement Location"); putValue(SHORT_DESCRIPTION, "Set the placement'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(); Location placementLocation = Utils2D.calculateBoardPlacementLocationInverse( boardLocation, camera.getLocation()); getSelection().setLocation(placementLocation); table.repaint(); }); } }; public final Action captureToolPlacementLocation = new AbstractAction() { { putValue(SMALL_ICON, Icons.captureTool); putValue(NAME, "Capture Tool Placement Location"); putValue(SHORT_DESCRIPTION, "Set the placement's location to the tool's current position."); } @Override public void actionPerformed(ActionEvent arg0) { Nozzle nozzle = MainFrame.get().getMachineControls().getSelectedNozzle(); Location placementLocation = Utils2D .calculateBoardPlacementLocationInverse(boardLocation, nozzle.getLocation()); getSelection().setLocation(placementLocation); table.repaint(); } }; public final Action editPlacementFeederAction = new AbstractAction() { { putValue(SMALL_ICON, Icons.editFeeder); putValue(NAME, "Edit Placement Feeder"); putValue(SHORT_DESCRIPTION, "Edit the placement's associated feeder definition."); } @Override public void actionPerformed(ActionEvent arg0) { Placement placement = getSelection(); MainFrame.get().getFeedersTab().showFeederForPart(placement.getPart()); } }; public final Action setTypeAction = new AbstractAction() { { putValue(NAME, "Set Type"); putValue(SHORT_DESCRIPTION, "Set placement type(s) to..."); } @Override public void actionPerformed(ActionEvent arg0) {} }; class SetTypeAction extends AbstractAction { final Placement.Type type; public SetTypeAction(Placement.Type type) { this.type = type; putValue(NAME, type.toString()); putValue(SHORT_DESCRIPTION, "Set placement type(s) to " + type.toString()); } @Override public void actionPerformed(ActionEvent arg0) { for (Placement placement : getSelections()) { placement.setType(type); } } }; public final Action setSideAction = new AbstractAction() { { putValue(NAME, "Set Side"); putValue(SHORT_DESCRIPTION, "Set placement side(s) to..."); } @Override public void actionPerformed(ActionEvent arg0) {} }; class SetSideAction extends AbstractAction { final Board.Side side; public SetSideAction(Board.Side side) { this.side = side; putValue(NAME, side.toString()); putValue(SHORT_DESCRIPTION, "Set placement side(s) to " + side.toString()); } @Override public void actionPerformed(ActionEvent arg0) { for (Placement placement : getSelections()) { placement.setSide(side); } } }; static class TypeRenderer extends DefaultTableCellRenderer { public void setValue(Object value) { Type type = (Type) value; setText(type.name()); if (type == Type.Fiducial) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(typeColorFiducial); } else if (type == Type.Ignore) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(typeColorIgnore); } else if (type == Type.Place) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(typeColorPlace); } } } static class StatusRenderer extends DefaultTableCellRenderer { public void setValue(Object value) { Status status = (Status) value; if (status == Status.Ready) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(statusColorReady); setText("Ready"); } else if (status == Status.MissingFeeder) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(statusColorError); setText("Missing Feeder"); } else if (status == Status.ZeroPartHeight) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(statusColorWarning); setText("Part Height"); } else if (status == Status.MissingPart) { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(statusColorError); setText("Missing Part"); } else { setBorder(new LineBorder(getBackground())); setForeground(Color.black); setBackground(statusColorError); setText(status.toString()); } } } }