package ui.view.inspector; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.text.NumberFormatter; import classes.AbstractCanvasObject; import classes.GroupNode; import classes.HolonElement; import classes.HolonElement.Priority; import classes.HolonObject; import net.miginfocom.swing.MigLayout; import ui.controller.Control; import ui.view.component.TrippleCheckBox; import ui.view.component.TrippleCheckBox.State; import utility.ImageImport; import utility.events.Action; import utility.events.SimpleDocumentListener; public class InspectorTable extends JPanel { private List elementRows = new ArrayList(); private Control control; private final int columnHeight = 20; //UI private final TrippleCheckBox selectAllCheckBox = new TrippleCheckBox(); private final JButton addButton = new JButton(); private final JButton duplicateButton = new JButton(); private final JButton deleteButton = new JButton(); private final JPanel buttonPanel = new JPanel(); private final static NumberFormatter doubleFormatter = generateNumberFormatter(); //Colors private final static Color selectedColor = new Color(126, 186, 255); private final static Color borderColor = new Color(171, 173, 179); //Events public Action> OnElementSelectionChanged = new Action<>(); public InspectorTable(Control control) { control.OnSelectionChanged.addListener(() -> update_ui()); this.control = control; init(); generateHeader(); } private void init() { MigLayout layout = new MigLayout("insets 0,gap 0,wrap 7", // Layout Constraints "[][fill, grow][][fill, grow][fill, grow][][]", // Column constraints "[25!][20:20:20]"); // Row constraints this.setLayout(layout); initSelectAllCheckBox(); initButtonPanel(); } private static NumberFormatter generateNumberFormatter() { NumberFormat doubleFormat = NumberFormat.getNumberInstance(Locale.US); doubleFormat.setMinimumFractionDigits(1); doubleFormat.setMaximumFractionDigits(10); doubleFormat.setRoundingMode(RoundingMode.HALF_UP); doubleFormat.setGroupingUsed(false); NumberFormatter doubleFormatter = new NumberFormatter(doubleFormat); doubleFormatter.setCommitsOnValidEdit(true); doubleFormatter.setValueClass(Double.class); return doubleFormatter; } private void generateHeader() { this.add(selectAllCheckBox); this.add(generateHeaderColumnTitle("Object")); this.add(generateHeaderColumnTitle("Id ")); this.add(generateHeaderColumnTitle("Device")); this.add(generateHeaderColumnTitle("Energy")); this.add(generateHeaderColumnTitle("Priority")); this.add(generateHeaderColumnTitle("Activ")); } private void initSelectAllCheckBox() { selectAllCheckBox.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); // Pixel Perfect alignment selectAllCheckBox.setBorder(BorderFactory.createEmptyBorder(2, 3, 0, 0)); selectAllCheckBox.addActionListener(clicked -> { switch (selectAllCheckBox.getSelectionState()) { case mid_state_selection: case selected: { for (ElementRow row : elementRows) { row.setSelected(false); } duplicateButton.setEnabled(false); deleteButton.setEnabled(false); } break; case unselected: if(!elementRows.isEmpty()) { for (ElementRow row : elementRows) { row.setSelected(true); } duplicateButton.setEnabled(true); deleteButton.setEnabled(true); } default: break; } updateElementSelection(); }); } private JLabel generateHeaderColumnTitle(String string) { JLabel columnTitleLabel = new JLabel(string); columnTitleLabel.setToolTipText(string); columnTitleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); return columnTitleLabel; } private void initButtonPanel() { buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); buttonPanel.add(Box.createRigidArea(new Dimension(2, 0))); addButton.setIcon(new ImageIcon(ImageImport.loadImage("Images/plus.png", 16, 16))); addButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); addButton.addActionListener(clicked -> { Optional last = control.getModel().getSelectedObjects().stream() .filter(obj -> obj instanceof HolonObject).reduce((prev, next) -> next) .map(obj -> (HolonObject) obj); last.ifPresent(obj -> { obj.addElement(new HolonElement(obj, "Element", 0.0f)); control.calculateStateAndVisualForCurrentTimeStep(); update_ui(); }); }); buttonPanel.add(addButton); duplicateButton.setIcon(new ImageIcon(ImageImport.loadImage("Images/duplicate.png", 16, 16))); duplicateButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); duplicateButton.addActionListener(clicked -> { for (ElementRow row : elementRows) { if(row.isSelected()) { row.element.parentObject.addElement(new HolonElement(row.element)); } } control.calculateStateAndVisualForCurrentTimeStep(); update_ui(); }); buttonPanel.add(duplicateButton); deleteButton.setIcon(new ImageIcon(ImageImport.loadImage("Images/minus.png", 16, 16))); deleteButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); deleteButton.addActionListener(clicked -> { for (ElementRow row : elementRows) { if(row.isSelected()) { row.element.parentObject.removeElement(row.element); } } control.calculateStateAndVisualForCurrentTimeStep(); update_ui(); }); buttonPanel.add(deleteButton); } private void update_ui() { Stream elements = extractElements(control.getModel().getSelectedObjects()); // TODO Pooling this.removeAll(); elementRows.clear(); generateHeader(); elements.forEach(element -> elementRows.add(new ElementRow(element))); this.add(buttonPanel, "span"); boolean isAtLeastOneHolonObjectSelected = control.getModel().getSelectedObjects().stream().anyMatch(object -> object instanceof HolonObject); this.addButton.setEnabled(isAtLeastOneHolonObjectSelected); duplicateButton.setEnabled(false); deleteButton.setEnabled(false); selectAllCheckBox.setSelectionState(State.unselected); revalidate(); repaint(); this.OnElementSelectionChanged.broadcast(new HashSet()); } private void updateElementSelection() { Set eleSet = elementRows.stream().filter(ele -> ele.isSelected()).map(row -> row.element).collect(Collectors.toSet()); this.OnElementSelectionChanged.broadcast(eleSet); } private void updateButtonAppearance() { long count = elementRows.stream().filter(ele -> ele.isSelected()).count(); if (count == elementRows.size()) { selectAllCheckBox.setSelectionState(State.selected); } else if (count == 0) { selectAllCheckBox.setSelectionState(State.unselected); } else { selectAllCheckBox.setSelectionState(State.mid_state_selection); } duplicateButton.setEnabled(count != 0); deleteButton.setEnabled(count != 0); } // Extract elements from a list of AbstractCanvasObjects static Stream extractElements(Collection toInspect) { Stream recursiveLayer = toInspect.stream().filter(object ->object instanceof GroupNode).flatMap(obj -> extractElements(((GroupNode)obj).getNodes())); Stream thisLayer = toInspect.stream().filter(obj -> obj instanceof HolonObject).flatMap(obj -> { HolonObject ho = (HolonObject) obj; return ho.getElements().stream(); }); return Stream.concat(thisLayer, recursiveLayer); } private class ElementRow { HolonElement element; private JCheckBox selectionBox; Container[] cellsInRow = new Container[7]; ElementRow(HolonElement element) { this.element = element; this.createEditFields(); addContainerToInspector(); } private void addContainerToInspector() { for (Container cell : cellsInRow) { InspectorTable.this.add(cell); } } public void setSelected(boolean value) { selectionBox.setSelected(value); // Color row for (Container cell : cellsInRow) { cell.setBackground(selectionBox.isSelected() ? selectedColor : Color.white); } } public boolean isSelected() { return selectionBox.isSelected(); } private void createEditFields() { // Selected JPanel selectedColumnPanel = new JPanel(new BorderLayout()); selectedColumnPanel.setBackground(Color.white); selectedColumnPanel.setBorder(BorderFactory.createLineBorder(borderColor)); selectionBox = new JCheckBox(); selectionBox.addActionListener(clicked -> { setSelected(selectionBox.isSelected()); updateButtonAppearance(); updateElementSelection(); }); selectedColumnPanel.setMinimumSize(new Dimension(columnHeight, columnHeight)); selectedColumnPanel.setPreferredSize(new Dimension(columnHeight, columnHeight)); selectedColumnPanel.setMaximumSize(new Dimension(columnHeight, columnHeight)); selectionBox.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0)); selectionBox.setOpaque(false); selectedColumnPanel.add(selectionBox, BorderLayout.CENTER); cellsInRow[0] = selectedColumnPanel; // ObjectName and ID JTextField objectNameTextField = new JTextField(this.element.parentObject.getName()); objectNameTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> { this.element.parentObject.setName(objectNameTextField.getText()); }); objectNameTextField.addActionListener(ae -> update_ui()); cellsInRow[1] = objectNameTextField; JTextField idObjectTextField = new JTextField(Integer.toString(this.element.parentObject.getId())); idObjectTextField.setMinimumSize(idObjectTextField.getPreferredSize()); idObjectTextField.setBackground(Color.white); idObjectTextField.setEditable(false); idObjectTextField.setEnabled(false); cellsInRow[2] = idObjectTextField; // Name JTextField elementNameTextField = new JTextField(this.element.getEleName()); elementNameTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> { this.element.setEleName(elementNameTextField.getText()); }); elementNameTextField.setBackground(Color.white); cellsInRow[3] = elementNameTextField; // Energy JFormattedTextField energyTextField = new JFormattedTextField(doubleFormatter); energyTextField.setInputVerifier(getInputVerifier()); energyTextField.setValue(this.element.getEnergy()); energyTextField.setBackground(Color.white); energyTextField.addPropertyChangeListener(actionEvent -> { try { float energy = Float.parseFloat(energyTextField.getText()); if (this.element.getEnergy() != energy) { this.element.setEnergyPerElement(energy); control.calculateStateAndVisualForCurrentTimeStep(); } } catch (NumberFormatException e) { // Dont Update } }); cellsInRow[4] = energyTextField; // Priority JComboBox comboBox = new JComboBox(Priority.values()); comboBox.setSelectedItem(this.element.getPriority()); comboBox.setBackground(Color.white); comboBox.setEditable(false); comboBox.addActionListener(ae -> { this.element.setPriority((Priority) comboBox.getSelectedItem()); }); cellsInRow[5] = comboBox; JPanel checkBoxWrapperPanel = new JPanel(new BorderLayout()); checkBoxWrapperPanel.setBorder(BorderFactory.createLineBorder(borderColor)); checkBoxWrapperPanel.setBackground(Color.white); checkBoxWrapperPanel.setMinimumSize(new Dimension(columnHeight, columnHeight)); checkBoxWrapperPanel.setMaximumSize(new Dimension(columnHeight, columnHeight)); // Active JCheckBox checkbox = new JCheckBox(); checkbox.setSelected(this.element.isActive()); checkbox.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0)); checkbox.setOpaque(false); checkbox.addActionListener(actionEvent -> { this.element.setActive(checkbox.isSelected()); control.calculateStateAndVisualForCurrentTimeStep(); }); checkBoxWrapperPanel.add(checkbox, BorderLayout.CENTER); cellsInRow[6] = checkBoxWrapperPanel; } } }