/*
* 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.Frame;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
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.JTextField;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableRowSorter;
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.PackagesComboBoxModel;
import org.openpnp.gui.support.Wizard;
import org.openpnp.gui.support.WizardContainer;
import org.openpnp.gui.tablemodel.PartsTableModel;
import org.openpnp.model.Configuration;
import org.openpnp.model.Location;
import org.openpnp.model.Part;
import org.openpnp.spi.Feeder;
import org.openpnp.spi.Nozzle;
import org.openpnp.spi.PartAlignment;
import org.openpnp.util.MovableUtils;
import org.openpnp.util.UiUtils;
import org.pmw.tinylog.Logger;
@SuppressWarnings("serial")
public class PartsPanel extends JPanel implements WizardContainer {
private static final String PREF_DIVIDER_POSITION = "PartsPanel.dividerPosition";
private static final int PREF_DIVIDER_POSITION_DEF = -1;
private Preferences prefs = Preferences.userNodeForPackage(PartsPanel.class);
final private Configuration configuration;
final private Frame frame;
private PartsTableModel tableModel;
private TableRowSorter<PartsTableModel> tableSorter;
private JTextField searchTextField;
private JTable table;
private ActionGroup singleSelectionActionGroup;
private ActionGroup multiSelectionActionGroup;
public PartsPanel(Configuration configuration, Frame frame) {
this.configuration = configuration;
this.frame = frame;
singleSelectionActionGroup = new ActionGroup(deletePartAction, pickPartAction);
singleSelectionActionGroup.setEnabled(false);
multiSelectionActionGroup = new ActionGroup(deletePartAction);
multiSelectionActionGroup.setEnabled(false);
setLayout(new BorderLayout(0, 0));
tableModel = new PartsTableModel();
tableSorter = new TableRowSorter<>(tableModel);
JPanel toolbarAndSearch = new JPanel();
add(toolbarAndSearch, BorderLayout.NORTH);
toolbarAndSearch.setLayout(new BorderLayout(0, 0));
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolbarAndSearch.add(toolBar);
JPanel panel_1 = new JPanel();
toolbarAndSearch.add(panel_1, BorderLayout.EAST);
JLabel lblSearch = new JLabel("Search");
panel_1.add(lblSearch);
searchTextField = new JTextField();
searchTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
search();
}
@Override
public void insertUpdate(DocumentEvent e) {
search();
}
@Override
public void changedUpdate(DocumentEvent e) {
search();
}
});
panel_1.add(searchTextField);
searchTextField.setColumns(15);
JComboBox packagesCombo = new JComboBox(new PackagesComboBoxModel());
packagesCombo.setRenderer(new IdentifiableListCellRenderer<org.openpnp.model.Package>());
JSplitPane splitPane = new JSplitPane();
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());
}
});
add(splitPane, BorderLayout.CENTER);
JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
JPanel alignmentPanel = new JPanel();
alignmentPanel.setLayout(new BorderLayout());
tabbedPane.add("Alignment", new JScrollPane(alignmentPanel));
table = new AutoSelectTextTable(tableModel);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setDefaultEditor(org.openpnp.model.Package.class,
new DefaultCellEditor(packagesCombo));
table.setDefaultRenderer(org.openpnp.model.Package.class,
new IdentifiableTableCellRenderer<org.openpnp.model.Package>());
table.setRowSorter(tableSorter);
table.getTableHeader().setDefaultRenderer(new MultisortTableHeaderCellRenderer());
splitPane.setLeftComponent(new JScrollPane(table));
splitPane.setRightComponent(tabbedPane);
JButton btnNewPart = toolBar.add(newPartAction);
btnNewPart.setToolTipText("");
JButton btnDeletePart = toolBar.add(deletePartAction);
btnDeletePart.setToolTipText("");
toolBar.addSeparator();
toolBar.add(pickPartAction);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
List<Part> selections = getSelections();
if (selections.size() > 1) {
singleSelectionActionGroup.setEnabled(false);
multiSelectionActionGroup.setEnabled(true);
}
else {
multiSelectionActionGroup.setEnabled(false);
singleSelectionActionGroup.setEnabled(!selections.isEmpty());
}
alignmentPanel.removeAll();
Part part = getSelection();
if (part != null) {
PartAlignment partAlignment =
Configuration.get().getMachine().getPartAlignment();
Wizard wizard = partAlignment.getPartConfigurationWizard(part);
if (wizard != null) {
wizard.setWizardContainer(PartsPanel.this);
alignmentPanel.add(wizard.getWizardPanel());
}
}
revalidate();
repaint();
}
});
}
private Part getSelection() {
List<Part> selections = getSelections();
if (selections.size() != 1) {
return null;
}
return selections.get(0);
}
private List<Part> getSelections() {
List<Part> selections = new ArrayList<>();
for (int selectedRow : table.getSelectedRows()) {
selectedRow = table.convertRowIndexToModel(selectedRow);
selections.add(tableModel.getPart(selectedRow));
}
return selections;
}
private void search() {
RowFilter<PartsTableModel, Object> rf = null;
// If current expression doesn't parse, don't update.
try {
rf = RowFilter.regexFilter("(?i)" + searchTextField.getText().trim());
}
catch (PatternSyntaxException e) {
Logger.warn("Search failed", e);
return;
}
tableSorter.setRowFilter(rf);
}
public final Action newPartAction = new AbstractAction() {
{
putValue(SMALL_ICON, Icons.add);
putValue(NAME, "New Part...");
putValue(SHORT_DESCRIPTION, "Create a new part, specifying it's ID.");
}
@Override
public void actionPerformed(ActionEvent arg0) {
if (Configuration.get().getPackages().size() == 0) {
MessageBoxes.errorBox(getTopLevelAncestor(), "Error",
"There are currently no packages defined in the system. Please create at least one package before creating a part.");
return;
}
String id;
while ((id = JOptionPane.showInputDialog(frame,
"Please enter an ID for the new part.")) != null) {
if (configuration.getPart(id) != null) {
MessageBoxes.errorBox(frame, "Error", "Part ID " + id + " already exists.");
continue;
}
Part part = new Part(id);
part.setPackage(Configuration.get().getPackages().get(0));
configuration.addPart(part);
tableModel.fireTableDataChanged();
Helpers.selectLastTableRow(table);
break;
}
}
};
public final Action deletePartAction = new AbstractAction() {
{
putValue(SMALL_ICON, Icons.delete);
putValue(NAME, "Delete Part");
putValue(SHORT_DESCRIPTION, "Delete the currently selected part.");
}
@Override
public void actionPerformed(ActionEvent arg0) {
List<Part> selections = getSelections();
List<String> ids = selections.stream().map(Part::getId).collect(Collectors.toList());
String formattedIds;
if (ids.size() <= 3) {
formattedIds = String.join(", ", ids);
}
else {
formattedIds = String.join(", ", ids.subList(0, 3)) + ", and " + (ids.size() - 3) + " others";
}
int ret = JOptionPane.showConfirmDialog(getTopLevelAncestor(),
"Are you sure you want to delete " + formattedIds + "?",
"Delete " + selections.size() + " parts?", JOptionPane.YES_NO_OPTION);
if (ret == JOptionPane.YES_OPTION) {
for (Part part : selections) {
Configuration.get().removePart(part);
}
}
}
};
public final Action pickPartAction = new AbstractAction() {
{
putValue(SMALL_ICON, Icons.load);
putValue(NAME, "Pick Part");
putValue(SHORT_DESCRIPTION, "Pick the selected part from the first available feeder.");
}
@Override
public void actionPerformed(ActionEvent arg0) {
UiUtils.submitUiMachineTask(() -> {
Nozzle nozzle = MainFrame.get().getMachineControls().getSelectedNozzle();
Part part = getSelection();
Feeder feeder = null;
// find a feeder to feed
for (Feeder f : Configuration.get().getMachine().getFeeders()) {
if (f.isEnabled() && f.getPart().equals(part)) {
feeder = f;
}
}
if (feeder == null) {
throw new Exception("No valid feeder found for " + part.getId());
}
// feed the chosen feeder
feeder.feed(nozzle);
// pick the part
Location pickLocation = feeder.getPickLocation();
MovableUtils.moveToLocationAtSafeZ(nozzle, pickLocation);
nozzle.pick(part);
nozzle.moveToSafeZ();
});
}
};
@Override
public void wizardCompleted(Wizard wizard) {}
@Override
public void wizardCancelled(Wizard wizard) {}
}