ArduinoPanelMeta.java

/*******************************************************************************
 * jArduino: Arduino C++ Code Generation From Java
 * Copyright 2020 Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.jarduino.gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.border.Border;

import net.sourceforge.jarduino.message.ArduinoAttribute.ArduinoAttrObject;
import net.sourceforge.jarduino.message.ArduinoChar;
import net.sourceforge.jarduino.message.ArduinoMessage;
import net.sourceforge.jarduino.message.ArduinoNamedObject;
import net.sourceforge.jarduino.message.ArduinoNode;
import net.sourceforge.jarduino.message.ArduinoSignal;
import net.sourceforge.jarduino.message.ArduinoSystem;
import net.sourceforge.jarduino.message.ArduinoValues;

/**
 * Configuration display.
 */
public class ArduinoPanelMeta {
    /**
     * Attributes Text.
     */
    private static final String VIEW_ATTR = "Attributes";

    /**
     * Values Text.
     */
    private static final String VIEW_VALUES = "Values";

    /**
     * Connections Text.
     */
    private static final String VIEW_CONNECTS = "Connections";

    /**
     * The panel.
     */
    private final JPanel thePanel;

    /**
     * The Node Select button.
     */
    private final ArduinoScrollButton<ArduinoNode> theNode;

    /**
     * The Message Select button.
     */
    private final ArduinoScrollButton<ArduinoMessage> theMessage;

    /**
     * The Signal Select button.
     */
    private final ArduinoScrollButton<ArduinoSignal> theSignal;

    /**
     * The View button.
     */
    private final JButton theViewButton;

    /**
     * The button Panel map.
     */
    private final Map<ArduinoConfigMode, JPanel> theButtons;

    /**
     * The Select Panel.
     */
    private final JPanel theSelect;

    /**
     * The Filler Panel.
     */
    private final JPanel theFiller;

    /**
     * The View Panel.
     */
    private final JPanel theView;

    /**
     * The Comment label.
     */
    private final JLabel theComment;

    /**
     * The Attributes table.
     */
    private final ArduinoTableAttr theAttrs;

    /**
     * The Values table.
     */
    private final ArduinoTableValues theValues;

    /**
     * The NodeConnect table.
     */
    private final ArduinoTableNodeConnect theNodeConnect;

    /**
     * The MsgConnect table.
     */
    private final ArduinoTableMsgConnect theMsgConnect;

    /**
     * The TablePanel.
     */
    private final JPanel theTablePanel;

    /**
     * The mode.
     */
    private ArduinoConfigMode theMode;

    /**
     * The system.
     */
    private ArduinoSystem theSystem;

    /**
     * The selected object.
     */
    private ArduinoNamedObject theSelected;

    /**
     * Do we show the valuesTable?
     */
    private boolean showValues;

    /**
     * Constructor.
     * @param pFrame the frame
     */
    ArduinoPanelMeta(final JFrame pFrame) {
        /* Create the Select buttons */
        theNode = new ArduinoScrollButton<>(pFrame);
        theNode.setFormatter(ArduinoNode::getName);
        theNode.onSelect(this::setSelected);
        theMessage = new ArduinoScrollButton<>(pFrame);
        theMessage.setFormatter(ArduinoMessage::getName);
        theMessage.onSelect(this::setSelected);
        theSignal = new ArduinoScrollButton<>(pFrame);
        theSignal.setFormatter(ArduinoSignal::getName);
        theSignal.onSelect(this::setSelected);

        /* Create the mode button */
        final ArduinoScrollButton<ArduinoConfigMode> myModes = new ArduinoScrollButton<>(pFrame);
        for (ArduinoConfigMode myMode : ArduinoConfigMode.values()) {
            myModes.add(myMode);
        }
        myModes.setSelectedItem(ArduinoConfigMode.SYSTEM);
        myModes.onSelect(this::selectMode);

        /* Create the button map */
        theButtons = new EnumMap<>(ArduinoConfigMode.class);

        /* Create the mode panel */
        final JPanel myModePanel = new JPanel();
        myModePanel.setLayout(new BoxLayout(myModePanel, BoxLayout.X_AXIS));
        myModePanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myModePanel.add(myModes.getComponent());
        myModePanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        Border myBorder = BorderFactory.createTitledBorder("Mode");
        myModePanel.setBorder(myBorder);

        /* Create the item panel */
        theSelect = new JPanel();
        theSelect.setLayout(new BoxLayout(theSelect, BoxLayout.X_AXIS));
        theSelect.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        theSelect.add(buildMsgPanel(ArduinoConfigMode.NODE, theNode));
        theSelect.add(buildMsgPanel(ArduinoConfigMode.MESSAGE, theMessage));
        theSelect.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        theSelect.add(buildMsgPanel(ArduinoConfigMode.SIGNAL, theSignal));
        theSelect.add(Box.createHorizontalGlue());
        myBorder = BorderFactory.createTitledBorder("Selection");
        theSelect.setBorder(myBorder);

        /* Create the view button */
        theViewButton = new JButton(VIEW_ATTR);
        theViewButton.addActionListener(e -> {
            showValues = !showValues;
            theViewButton.setText(getViewButtonText());
            updateView();
        });

        /* Create the view panel */
        theView = new JPanel();
        theView.setLayout(new BoxLayout(theView, BoxLayout.X_AXIS));
        theView.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        theView.add(theViewButton);
        theView.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myBorder = BorderFactory.createTitledBorder("View");
        theView.setBorder(myBorder);

        /* Create the filler panel */
        theFiller = new JPanel();
        theFiller.setLayout(new BoxLayout(theFiller, BoxLayout.X_AXIS));
        theFiller.add(Box.createHorizontalGlue());

        /* Create the selection panel */
        final JPanel mySelectPanel = new JPanel();
        mySelectPanel.setLayout(new BoxLayout(mySelectPanel, BoxLayout.X_AXIS));
        mySelectPanel.add(myModePanel);
        mySelectPanel.add(theSelect);
        mySelectPanel.add(theFiller);
        mySelectPanel.add(theView);

        /* Create the comment field */
        theComment = new JLabel();
        myBorder = BorderFactory.createTitledBorder("Comment");
        theComment.setBorder(myBorder);
        final JPanel myComment = new JPanel(new BorderLayout());
        myComment.add(theComment);

        /* Create the header panel */
        final JPanel myHeader = new JPanel();
        myHeader.setLayout(new BoxLayout(myHeader, BoxLayout.Y_AXIS));
        myHeader.add(mySelectPanel);
        myHeader.add(new JSeparator());
        myHeader.add(myComment);

        /* Create the attributes table */
        theAttrs = new ArduinoTableAttr();
        final JScrollPane myAttrScroll = new JScrollPane(theAttrs.getComponent());
        myBorder = BorderFactory.createTitledBorder(VIEW_ATTR);
        myAttrScroll.setBorder(myBorder);

        /* Create the values table */
        theValues = new ArduinoTableValues();
        final JScrollPane myValueScroll = new JScrollPane(theValues.getComponent());
        myBorder = BorderFactory.createTitledBorder(VIEW_VALUES);
        myValueScroll.setBorder(myBorder);

        /* Create the nodeConnect table */
        theNodeConnect = new ArduinoTableNodeConnect();
        myBorder = BorderFactory.createTitledBorder(VIEW_CONNECTS);
        theNodeConnect.getComponent().setBorder(myBorder);

        /* Create the values table */
        theMsgConnect = new ArduinoTableMsgConnect();
        myBorder = BorderFactory.createTitledBorder(VIEW_CONNECTS);
        theMsgConnect.getComponent().setBorder(myBorder);

        /* Create the table card */
        theTablePanel = new JPanel(new CardLayout());
        theTablePanel.add(myAttrScroll, VIEW_ATTR);
        theTablePanel.add(myValueScroll, VIEW_VALUES);
        theTablePanel.add(theNodeConnect.getComponent(), VIEW_CONNECTS + ArduinoConfigMode.NODE.toString());
        theTablePanel.add(theMsgConnect.getComponent(), VIEW_CONNECTS + ArduinoConfigMode.MESSAGE.toString());

        /* Create the panel */
        thePanel = new JPanel(new BorderLayout());
        thePanel.add(myHeader, BorderLayout.PAGE_START);
        thePanel.add(theTablePanel, BorderLayout.CENTER);

        /* Default to system */
        theMode = ArduinoConfigMode.SYSTEM;
    }

    /**
     * Obtain the component.
     * @return the component
     */
    JComponent getComponent() {
        return thePanel;
    }

    /**
     * Select Modes.
     * @param pMode the mode
     */
    private void selectMode(final ArduinoConfigMode pMode) {
        /* If this is the existing mode, just return */
        if (theMode == pMode) {
            return;
        }

        /* Set visibility */
        setVisibility(pMode);

        /* Switch on mode */
        theMode = pMode;
        switch (theMode) {
            case NODE:
                setSelected(theNode.getSelectedItem());
                break;
            case MESSAGE:
                setSelected(theMessage.getSelectedItem());
                break;
            case SIGNAL:
                /* Careful if signal is selected before any message */
                final ArduinoSignal mySignal = theSignal.getSelectedItem();
                setSelected(mySignal == null ? theMessage.getSelectedItem() : mySignal);
                break;
            case SYSTEM:
            default:
                setSelected(theSystem);
                break;
        }

        /* Update the view button */
        theViewButton.setText(getViewButtonText());
    }

    /**
     * Set selected Object.
     * @param pObject the selected object
     */
    private void setSelected(final ArduinoNamedObject pObject) {
        /* Ignore if the object is irrelevant */
        if (!theMode.checkObject(pObject)
             || Objects.equals(theSelected, pObject)) {
            return;
        }

        /* If we have a message when we are in signal Mode */
        if (pObject instanceof ArduinoMessage) {
            /* Ignore if the object is the owner of the selected */
            final ArduinoMessage myMessage = (ArduinoMessage) pObject;
            if (theMode == ArduinoConfigMode.SIGNAL
                    && theSelected instanceof ArduinoSignal
                    && pObject.equals(((ArduinoSignal) theSelected).getOwner())) {
                return;
            }

            /* Populate the signal list */
            theSignal.removeAll();
            for (ArduinoSignal mySignal : myMessage.getAllSignals()) {
                /* Add to the list */
                theSignal.add(mySignal);
            }

            /* Return if we are in signal mode */
            if (theMode == ArduinoConfigMode.SIGNAL) {
                return;
            }
        }

        /* Set the details for the object */
        setComment(pObject);
        theAttrs.configureTable(theSystem, theMode, (ArduinoAttrObject) pObject);
        theValues.getComponent().setVisible(false);
        theView.setVisible(false);
        if (pObject instanceof ArduinoSignal) {
            final ArduinoValues myValues = ((ArduinoSignal) pObject).getValues();
            if (myValues != null) {
                theValues.configureTable(myValues);
                theValues.getComponent().setVisible(true);
                theView.setVisible(true);
            }
        }
        if (pObject instanceof ArduinoNode) {
            theNodeConnect.configureTable((ArduinoNode) pObject);
            theView.setVisible(true);
        }
        if (pObject instanceof ArduinoMessage) {
            theMsgConnect.configureTable((ArduinoMessage) pObject);
            theView.setVisible(true);
        }
        theSelected = pObject;
        updateView();

        /* Set Dimensions */
        thePanel.setPreferredSize(new Dimension(ArduinoPanelMain.WIDTH, ArduinoPanelMain.HEIGHT));
    }

    /**
     * Set comment.
     * @param pObject the selected object
     */
    private void setComment(final ArduinoNamedObject pObject) {
        /* Access the comment for the object */
        String myComment = theSystem.getCommentForObject(pObject);

        /* Clean the comment */
        myComment = cleanseLabelText(myComment);

        /* Set text and visibility */
        theComment.setText(myComment);
        theComment.setVisible(myComment != null && !showValues);
    }

    /**
     * Clean label text to handle embedded new line.
     * @param pText the text
     * @return the cleansed text
     */
    static String cleanseLabelText(final String pText) {
        /* Access the text */
        String myText = pText;

        /* If the text includes a linefeed */
        if (myText != null
                && myText.indexOf(ArduinoChar.LF) != -1) {
            /* Convert to html */
            myText = "<html>"
                    + myText.replace("&", "&amp;")
                    .replace(">", "&gt;")
                    .replace("<", "&lt;")
                    .replace(Character.toString(ArduinoChar.LF), "<br>");
        }

        /* Return the text */
        return myText;
    }

    /**
     * Create a labelled button panel.
     * @param pMode the mode
     * @param pButton the button
     * @return the panel
     */
    private JPanel buildMsgPanel(final ArduinoConfigMode pMode,
                                 final ArduinoScrollButton<?> pButton) {
        /* Create the panel */
        final JPanel myPanel = new JPanel();
        myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.X_AXIS));
        myPanel.add(new JLabel(pMode.toString() + ArduinoChar.COLON));
        myPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myPanel.add(pButton.getComponent());

        /* Store into map and return */
        theButtons.put(pMode, myPanel);
        return myPanel;
    }

    /**
     * Set visibility.
     * @param pMode the mode
     */
    private void setVisibility(final ArduinoConfigMode pMode) {
        /* Determine visibility */
        final boolean bNodeVisible = pMode == ArduinoConfigMode.NODE;
        final boolean bSignalVisible = pMode == ArduinoConfigMode.SIGNAL;
        final boolean bMessageVisible = pMode == ArduinoConfigMode.MESSAGE || bSignalVisible;
        final boolean bSelectVisible = pMode != ArduinoConfigMode.SYSTEM;

        /* Update the button panels */
        theButtons.get(ArduinoConfigMode.NODE).setVisible(bNodeVisible);
        theButtons.get(ArduinoConfigMode.MESSAGE).setVisible(bMessageVisible);
        theButtons.get(ArduinoConfigMode.SIGNAL).setVisible(bSignalVisible);

        /* Update selection/filler */
        theSelect.setVisible(bSelectVisible);
        theFiller.setVisible(!bSelectVisible);
    }

    /**
     * Update the view.
     */
    private void updateView() {
        /* Determine the card to show */
        String myCard = VIEW_ATTR;
        if (showValues) {
            if (theSelected instanceof ArduinoNode) {
                myCard = VIEW_CONNECTS + ArduinoConfigMode.NODE.toString();
            } else if (theSelected instanceof ArduinoMessage) {
                myCard = VIEW_CONNECTS + ArduinoConfigMode.MESSAGE.toString();
            } else if (theSelected instanceof ArduinoSignal
                    && ((ArduinoSignal) theSelected).getValues() != null) {
                myCard = VIEW_VALUES;
            }
        }

        /* Determine if we are showing connections */
        final CardLayout myLayout = (CardLayout) theTablePanel.getLayout();
        myLayout.show(theTablePanel, myCard);
    }

    /**
     * Determine the viewButton Text.
     * @return the text
     */
    private String getViewButtonText() {
        /* Determine the card to show */
        if (showValues) {
            switch (theMode) {
                case NODE:
                case MESSAGE:
                    return VIEW_CONNECTS;
                case SIGNAL:
                    return VIEW_VALUES;
                default:
                    break;
            }
        }
        return VIEW_ATTR;
    }

    /**
     * Set the system.
     * @param pSystem the system
     */
    void setSystem(final ArduinoSystem pSystem) {
        /* Store system */
        theSystem = pSystem;

        /* clear the selection lists */
        theSelected = null;
        theNode.removeAll();
        theMessage.removeAll();
        theSignal.removeAll();

        /* Loop through the nodes */
        for (ArduinoNode myNode : theSystem.getNodes()) {
            /* Add to the list (unless it is the nullNode) */
            if (!myNode.getName().equals(ArduinoNode.NULL_NODE)) {
                theNode.add(myNode);
            }

            /* Loop through the messages */
            for (ArduinoMessage myMessage : myNode.getMessages()) {
                /* Add to the list */
                theMessage.add(myMessage);
            }
        }

        /* Reset the mode */
        final ArduinoConfigMode myMode = theMode;
        theMode = null;
        selectMode(myMode);
    }

    /**
     * The modes.
     */
    enum ArduinoConfigMode {
        /**
         * System.
         */
        SYSTEM("System"),

        /**
         * Node.
         */
        NODE("Node"),

        /**
         * Message.
         */
        MESSAGE("Message"),

        /**
         * Signal.
         */
        SIGNAL("Signal");

        /**
         * The name.
         */
        private final String theName;

        /**
         * Constructor.
         * @param pName the Name
         */
        ArduinoConfigMode(final String pName) {
            theName = pName;
        }

        /**
         * Check that object is of the correct type.
         * @param pObject the object
         * @return true/false
         */
        boolean checkObject(final ArduinoNamedObject pObject) {
            switch (this) {
                case NODE:
                    return pObject instanceof ArduinoNode;
                case MESSAGE:
                    return pObject instanceof ArduinoMessage;
                case SIGNAL:
                    return pObject instanceof ArduinoMessage
                            || pObject instanceof ArduinoSignal;
                case SYSTEM:
                    return pObject instanceof ArduinoSystem;
                default:
                    return false;
            }
        }

        @Override
        public String toString() {
            return theName;
        }
    }
}