package org.insa.graphics; import java.awt.Color; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.insa.graph.Node; import org.insa.graph.Point; import org.insa.graphics.drawing.DrawingClickListener; import org.insa.graphics.drawing.overlays.MarkerOverlay; public class NodesInputPanel extends JPanel implements DrawingClickListener { /** * */ private static final long serialVersionUID = -1638302070013027690L; private static final Color DEFAULT_MARKER_COLOR = Color.BLUE; public class InputChangedEvent extends ActionEvent { /** * */ private static final long serialVersionUID = 3440024811352247731L; protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled"; protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1; // List of nodes List nodes; public InputChangedEvent(List nodes2) { super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND); this.nodes = nodes2; } List getNodes() { return Collections.unmodifiableList(nodes); } }; // Node inputs and markers. private ArrayList nodeInputs = new ArrayList<>(); private Map markerTrackers = new IdentityHashMap(); // Component that can be enabled/disabled. private ArrayList components = new ArrayList<>(); private int inputToFillIndex; // ActionListener called when all inputs are filled. private ArrayList inputChangeListeners = new ArrayList<>(); // Instance of mainwindow. MainWindow mainWindow; public NodesInputPanel(MainWindow mainWindow) { super(new GridBagLayout()); this.mainWindow = mainWindow; initInputToFill(); } public void addInputChangedListener(ActionListener listener) { inputChangeListeners.add(listener); } @Override public void setEnabled(boolean enabled) { for (JComponent component: components) { component.setEnabled(enabled); } super.setEnabled(enabled); if (enabled) { // Enable: Check if there is an input to fill, otherwize find the next one. if (getInputToFill() == null) { nextInputToFill(); } } else { // Disable, next input to fill = -1. this.inputToFillIndex = -1; } } public void clear() { for (JTextField field: nodeInputs) { field.setText(""); } initInputToFill(); } public void addTextField(String label) { addTextField(label, DEFAULT_MARKER_COLOR); } public void addTextField(String label, Color markerColor) { GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(3, 3, 3, 3); JLabel jLabel = new JLabel(label); jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD)); JTextField textField = new JTextField(); jLabel.setLabelFor(textField); c.gridx = 0; c.gridy = nodeInputs.size(); c.weightx = 0; c.gridwidth = 1; c.fill = GridBagConstraints.HORIZONTAL; add(jLabel, c); c.gridx = 1; c.gridy = nodeInputs.size(); c.weightx = 1; c.gridwidth = 1; c.fill = GridBagConstraints.HORIZONTAL; add(textField, c); JButton clearButton = new JButton("Clear"); c.gridx = 2; c.gridy = nodeInputs.size(); c.weightx = 0; c.gridwidth = 1; c.fill = GridBagConstraints.HORIZONTAL; add(clearButton, c); JButton clickButton = new JButton("Click"); c.gridx = 3; c.gridy = nodeInputs.size(); c.weightx = 0; c.gridwidth = 1; c.fill = GridBagConstraints.HORIZONTAL; add(clickButton, c); // Did not find something easier that this... ? textField.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { insertUpdate(e); } @Override public void removeUpdate(DocumentEvent e) { insertUpdate(e); } @Override public void insertUpdate(DocumentEvent e) { // Draw marker if possible Node curnode = getNodeForInput(textField); MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null); if (curnode != null) { if (tracker == null) { tracker = mainWindow.drawing.drawMarker(curnode.getPoint(), markerColor); markerTrackers.put(textField, tracker); } else { tracker.moveTo(curnode.getPoint()); } tracker.setVisible(true); } else if (tracker != null) { tracker.setVisible(false); } // Create array of nodes List nodes = getNodeForInputs(); // Trigger change event. for (ActionListener lis: inputChangeListeners) { lis.actionPerformed(new InputChangedEvent(nodes)); } } }); clearButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { textField.setText(""); setInputToFill(textField); } }); clickButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setInputToFill(textField); } }); nodeInputs.add(textField); components.add(textField); components.add(clearButton); components.add(clickButton); } /** * @return The node for the given text field, or null if the content of the text * field is invalid. */ protected Node getNodeForInput(JTextField textfield) { try { Node node = this.mainWindow.graph.getNodes().get(Integer.valueOf(textfield.getText().trim())); return node; } catch (IllegalArgumentException | IndexOutOfBoundsException ex) { return null; } } /** * @return List of nodes associated with the input. Some nodes may be null if * their associated input is invalid. */ public List getNodeForInputs() { List nodes = new ArrayList<>(nodeInputs.size()); for (JTextField input: nodeInputs) { nodes.add(getNodeForInput(input)); } return nodes; } /** * Get the next input that should be filled by a click, or null if none should * be filled. * * @return */ protected JTextField getInputToFill() { if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) { return null; } return nodeInputs.get(inputToFillIndex); } /** * Initialize the next input to fill. */ protected void initInputToFill() { inputToFillIndex = 0; } /** * Set the next input to fill to the given text field. * * @param input */ protected void setInputToFill(JTextField input) { inputToFillIndex = nodeInputs.indexOf(input); } /** * Find the next input to fill, if any. */ protected void nextInputToFill() { boolean found = false; for (int i = 1; i < nodeInputs.size() && !found; ++i) { int nextIndex = (i + inputToFillIndex) % nodeInputs.size(); JTextField input = nodeInputs.get(nextIndex); if (input.getText().trim().isEmpty()) { inputToFillIndex = nextIndex; found = true; } } if (!found) { inputToFillIndex = -1; } } @Override public void mouseClicked(Point point) { Node node = this.mainWindow.graph.findClosestNode(point); JTextField input = getInputToFill(); if (input != null) { input.setText(String.valueOf(node.getId())); nextInputToFill(); } } }