123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- package ui.view.inspector;
- import java.awt.BorderLayout;
- import java.awt.Color;
- import java.awt.Container;
- import java.awt.Dimension;
- import java.awt.event.ActionEvent;
- import java.awt.event.InputEvent;
- import java.awt.event.KeyEvent;
- import java.math.RoundingMode;
- import java.text.NumberFormat;
- import java.util.Collection;
- import java.util.Comparator;
- 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 java.util.ArrayList;
- import javax.swing.AbstractAction;
- 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.KeyStroke;
- import javax.swing.SwingConstants;
- 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.Pool;
- import utility.events.Action;
- import utility.events.SimpleDocumentListener;
- public class InspectorTable extends JPanel {
- private Pool<ElementRow> rowPool = new Pool<ElementRow>() {
- @Override
- public ElementRow create() {
- return new ElementRow();
- }
- };
- private final int maxDisplayedRowsNumber = 100;
- private int actualPage = 0;
- private int maxPageNumberForThisSelection = 0;
- 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 JButton pageIncreaseButton = new JButton();
- private final JButton pageDecreaseButton = new JButton();
- private final JLabel pageInformationLabel = new JLabel();
- private final JPanel pageSelectionPanel = new JPanel();
- private ArrayList<SortButton<ElementRow>> headerButtonList = new ArrayList<>();
- private final static NumberFormatter doubleFormatter = generateNumberFormatter();
- //sorting
- private Comparator<ElementRow> actual_comp = (ElementRow a, ElementRow b) -> Float.compare(a.element.getEnergy(), b.element.getEnergy());
-
- // Colors
- private final static Color selectedColor = new Color(126, 186, 255);
- private final static Color borderColor = new Color(171, 173, 179);
- // Events
- public Action<Set<HolonElement>> OnElementSelectionChanged = new Action<>();
- private Thread populateRowsThread;
- private boolean abortThread = false;
- public InspectorTable(Control control) {
- control.OnSelectionChanged.addListener(() -> updateInspectorUi());
- this.control = control;
- init();
- addHeader();
- }
- private void init() {
- MigLayout layout = new MigLayout("insets 0,gap 0,wrap 7", // Layout Constraints
- "[][fill, grow][fill][fill, grow][fill, grow][][fill]", // Column constraints
- "[25!][20:20:20]"); // Row constraints
- this.setLayout(layout);
- initSelectAllCheckBox();
- initButtons();
- initKeyControls();
- initHeaderButtons();
- }
- private void initKeyControls() {
- this.getInputMap(WHEN_IN_FOCUSED_WINDOW)
- .put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), "PageRight");
- this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK),
- "PageLeft");
- this.getActionMap().put("PageRight", new AbstractAction() {
- @Override
- public void actionPerformed(ActionEvent e) {
- performPageAction(PageAction.Increase);
- }
- });
- this.getActionMap().put("PageLeft", new AbstractAction() {
- @Override
- public void actionPerformed(ActionEvent e) {
- performPageAction(PageAction.Decrease);
- }
- });
- }
- private enum PageAction {
- Increase, Decrease
- };
- private void performPageAction(PageAction action) {
- int newPageNumber;
- switch (action) {
- case Decrease:
- newPageNumber = Math.max(actualPage - 1, 0);
- break;
- case Increase:
- default:
- newPageNumber = Math.min(actualPage + 1, maxPageNumberForThisSelection);
- break;
- }
- if (newPageNumber != actualPage) {
- actualPage = newPageNumber;
- updateTableUi();
- updatePageButtonAppearance();
- }
- }
- private void updatePageButtonAppearance() {
- this.pageDecreaseButton.setEnabled(actualPage != 0);
- this.pageIncreaseButton.setEnabled(actualPage != maxPageNumberForThisSelection);
- }
- 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 initHeaderButtons() {
- Comparator<ElementRow> objectComp = (ElementRow a, ElementRow b) -> a.element.parentObject.getName().compareTo(b.element.parentObject.getName());
- Comparator<ElementRow> idComp = (ElementRow a, ElementRow b) -> Integer.compare(a.element.parentObject.getId(), b.element.parentObject.getId());
- Comparator<ElementRow> deviceComp = (ElementRow a, ElementRow b) -> a.element.getName().compareTo(b.element.getName());
- Comparator<ElementRow> energyComp = (ElementRow a, ElementRow b) -> Float.compare(a.element.getEnergy(), b.element.getEnergy());
- Comparator<ElementRow> priorityComp = (ElementRow a, ElementRow b) -> a.element.getPriority().compareTo(b.element.getPriority());
- Comparator<ElementRow> activeComp = (ElementRow a, ElementRow b) -> Boolean.compare(a.element.isActive(), b.element.isActive());
- ;
-
- headerButtonList.add(new SortButton<ElementRow>("Object", objectComp));
- headerButtonList.add(new SortButton<ElementRow>("Id", idComp));
- headerButtonList.add(new SortButton<ElementRow>("Device", deviceComp));
- headerButtonList.add(new SortButton<ElementRow>("Energy", energyComp));
- headerButtonList.add(new SortButton<ElementRow>("Priority", priorityComp));
- headerButtonList.add(new SortButton<ElementRow>("Activ", activeComp));
- }
-
-
- private void addHeader() {
- this.add(selectAllCheckBox);
- for(SortButton<ElementRow> button : headerButtonList) {
- this.add(button);
- }
- }
- 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: {
- rowPool.getBorrowedStream().forEach(row -> row.setSelected(false));
- duplicateButton.setEnabled(false);
- deleteButton.setEnabled(false);
- }
- break;
- case unselected:
- // TODO maybe select only current page
- if (rowPool.getBorrowedCount() != 0) {
- rowPool.getBorrowedStream().forEach(row -> row.setSelected(true));
- duplicateButton.setEnabled(true);
- deleteButton.setEnabled(true);
- }
- default:
- break;
- }
- updateElementSelection();
- });
- }
-
- private void initButtons() {
- 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<HolonObject> 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();
- updateInspectorUi();
- });
- });
- 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 -> {
- rowPool.getBorrowedStream().forEach(row -> {
- if (row.isSelected()) {
- row.element.parentObject.addElement(new HolonElement(row.element));
- }
- });
- control.calculateStateAndVisualForCurrentTimeStep();
- updateInspectorUi();
- });
- 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 -> {
- rowPool.getBorrowedStream().forEach(row -> {
- if (row.isSelected()) {
- row.element.parentObject.removeElement(row.element);
- }
- });
- control.calculateStateAndVisualForCurrentTimeStep();
- updateInspectorUi();
- });
- buttonPanel.add(deleteButton);
- pageIncreaseButton.setIcon(new ImageIcon(ImageImport.loadImage("Images/page_increase.png", 16, 16)));
- ;
- pageIncreaseButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
- pageIncreaseButton.addActionListener(clicked -> this.performPageAction(PageAction.Increase));
- pageDecreaseButton.setIcon(new ImageIcon(ImageImport.loadImage("Images/page_decrease.png", 16, 16)));
- pageDecreaseButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
- pageDecreaseButton.addActionListener(clicked -> this.performPageAction(PageAction.Decrease));
- pageInformationLabel.setForeground(Color.gray);
- pageSelectionPanel.setLayout(new BoxLayout(pageSelectionPanel, BoxLayout.LINE_AXIS));
- pageSelectionPanel.add(Box.createRigidArea(new Dimension(2, 0)));
- pageSelectionPanel.add(this.pageInformationLabel);
- pageSelectionPanel.add(Box.createHorizontalGlue());
- pageSelectionPanel.add(this.pageDecreaseButton);
- pageSelectionPanel.add(this.pageIncreaseButton);
- }
- private void assignElementsToRowPool(Set<AbstractCanvasObject> selection) {
- List<HolonElement> elementList = extractElements(selection).toList();
- rowPool.clear();
- for (HolonElement element : elementList) {
- ElementRow row = rowPool.get();
- row.setElement(element);
- }
- actualPage = 0;
- this.maxPageNumberForThisSelection = elementList.size() / this.maxDisplayedRowsNumber;
- updatePageButtonAppearance();
- }
- private void updateTableUi() {
- // Maybe abort current thread and join them
- if (populateRowsThread != null) {
- try {
- abortThread = true;
- populateRowsThread.join();
- populateRowsThread.join();
- abortThread = false;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- populateRowsThread = new Thread(() -> {
- int numberOfRows = rowPool.getBorrowedCount();
- this.removeAll();
- addHeader();
- rowPool.getBorrowedStream().sorted(actual_comp).skip(actualPage * maxDisplayedRowsNumber).limit(maxDisplayedRowsNumber)
- .takeWhile(row -> !abortThread).forEach(row -> {
- row.addContainerToInspector();
- });
- if (numberOfRows > maxDisplayedRowsNumber) {
- int lastDisplayedElementNumber = Math.min(numberOfRows, (actualPage + 1) * maxDisplayedRowsNumber);
- pageInformationLabel.setText(String.format("%d - %d from %d", 1 + actualPage * maxDisplayedRowsNumber,
- lastDisplayedElementNumber, numberOfRows));
- this.add(pageSelectionPanel, "span, grow");
- }
- 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<HolonElement>());
- });
- populateRowsThread.start();
- }
- private void updateInspectorUi() {
- // clone for concurrency
- Set<AbstractCanvasObject> selection = new HashSet<>(control.getModel().getSelectedObjects());
- assignElementsToRowPool(selection);
- updateTableUi();
- }
- private void updateElementSelection() {
- Set<HolonElement> eleSet = rowPool.getBorrowedStream().filter(ele -> ele.isSelected()).map(row -> row.element)
- .collect(Collectors.toSet());
- this.OnElementSelectionChanged.broadcast(eleSet);
- }
- private void updateButtonAppearance() {
- long count = rowPool.getBorrowedStream().filter(ele -> ele.isSelected()).count();
- if (count == rowPool.getBorrowedCount()) {
- 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<HolonElement> extractElements(Collection<AbstractCanvasObject> toInspect) {
- Stream<HolonElement> recursiveLayer = toInspect.stream().filter(object -> object instanceof GroupNode)
- .flatMap(obj -> extractElements(((GroupNode) obj).getNodes()));
- Stream<HolonElement> 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 {
- private HolonElement element = null;
- private Container[] cellsInRow = new Container[7];
- // TextBoxes
- private JTextField elementNameTextField;
- private JCheckBox selectionBox;
- private JTextField idObjectTextField;
- private JFormattedTextField energyTextField;
- private JComboBox<Priority> comboBox;
- private JCheckBox activeCheckBox;
- private JTextField objectNameTextField;
- public ElementRow() {
- this.createEditFields();
- }
- public 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();
- }
- public void setElement(HolonElement element) {
- this.element = element;
- setSelected(false);
- objectNameTextField.setText(this.element.parentObject.getName());
- idObjectTextField.setText(Integer.toString(this.element.parentObject.getId()));
- elementNameTextField.setText(this.element.getName());
- energyTextField.setValue(this.element.getEnergy());
- comboBox.setSelectedItem(this.element.getPriority());
- activeCheckBox.setSelected(this.element.isActive());
- }
- 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
- objectNameTextField = new JTextField();
- objectNameTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
- this.element.parentObject.setName(objectNameTextField.getText());
- });
- objectNameTextField.addActionListener(ae -> updateInspectorUi());
- cellsInRow[1] = objectNameTextField;
- idObjectTextField = new JTextField();
- idObjectTextField.setMinimumSize(idObjectTextField.getPreferredSize());
- idObjectTextField.setBackground(Color.white);
- idObjectTextField.setEditable(false);
- idObjectTextField.setEnabled(false);
- cellsInRow[2] = idObjectTextField;
- // Name
- elementNameTextField = new JTextField();
- elementNameTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
- this.element.setEleName(elementNameTextField.getText());
- });
- elementNameTextField.setBackground(Color.white);
- cellsInRow[3] = elementNameTextField;
- // Energy
- energyTextField = new JFormattedTextField(doubleFormatter);
- energyTextField.setInputVerifier(getInputVerifier());
- 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
- comboBox = new JComboBox<Priority>(Priority.values());
- 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(Integer.MAX_VALUE, columnHeight));
- // Active
- activeCheckBox = new JCheckBox();
- activeCheckBox.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
- activeCheckBox.setOpaque(false);
- activeCheckBox.addActionListener(actionEvent -> {
- this.element.setActive(activeCheckBox.isSelected());
- control.calculateStateAndVisualForCurrentTimeStep();
- });
- checkBoxWrapperPanel.add(activeCheckBox, BorderLayout.CENTER);
- cellsInRow[6] = checkBoxWrapperPanel;
- }
- }
- private enum SortState {
- None, Descending, Ascending
- };
- private class SortButton<T> extends JButton {
- private SortState state = SortState.None;
- private Comparator<T> comp;
- public SortButton(String text, Comparator<T> comp) {
- super(text);
- this.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
- this.setContentAreaFilled(false);
- this.setBorderPainted(false);
- this.setFocusPainted(false);
- this.setHorizontalAlignment(SwingConstants.LEFT);
- this.comp = comp;
- this.addActionListener(onClick -> changeStateOnClick());
- }
- @SuppressWarnings("unchecked")
- private void changeStateOnClick() {
- setState((this.state == SortState.Ascending) ? SortState.Descending : SortState.Ascending);
- headerButtonList.stream().filter(button -> (button !=this)).forEach(button -> button.setState(SortState.None));
- actual_comp = (Comparator<ElementRow>) getComp();
- updateInspectorUi();
- }
- public void setState(SortState state) {
- this.state = state;
- String text = this.getText();
- // remove order symbols from text
- text = text.replaceAll("\u25bc|\u25b2", "");
- // update text
- switch (state) {
- case Descending:
- this.setText(text + "\u25bc");
- break;
- case Ascending:
- this.setText(text + "\u25b2");
- break;
- case None:
- default:
- this.setText(text);
- break;
- }
- }
- public Comparator<T> getComp(){
- switch(state){
- case Descending:
- return comp.reversed();
- case Ascending:
- case None:
- default:
- return comp;
- }
- }
- }
- }
|