ArduinoPanelSignal.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.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.border.Border;

import net.sourceforge.jarduino.message.ArduinoMessage;
import net.sourceforge.jarduino.message.ArduinoSystem;
import net.sourceforge.jarduino.util.ArduinoHex;

/**
 * Message Signals display panel.
 */
public class ArduinoPanelSignal {
    /**
     * Font.
     */
    static final Font FONT = new Font("monospaced", Font.BOLD, 16);

    /**
     * Font.
     */
    static final Font FONTX = new Font("SansSerif", Font.BOLD, 16);

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

    /**
     * The Message table.
     */
    private final ArduinoTableSignal theTable;

    /**
     * The select button.
     */
    private final ArduinoScrollButton<ArduinoMessage> theSelect;

    /**
     * The Hex Data.
     */
    private final JLabel theHex;

    /**
     * The MsgId.
     */
    private final JLabel theMsgId;

    /**
     * The Binary Data.
     */
    private final BinaryData theBinary;

    /**
     * The message data.
     */
    private byte[] theData;

    /**
     * Constructor.
     * @param pFrame the frame
     */
    ArduinoPanelSignal(final JFrame pFrame) {
        /* Create scroll button */
        theSelect = new ArduinoScrollButton<>(pFrame);
        theSelect.setFormatter(ArduinoMessage::getName);
        theSelect.onSelect(this::selectMessage);

        /* Create Panel for messageId */
        theMsgId = new JLabel();
        theMsgId.setFont(FONTX);
        final JLabel myIdPrompt = new JLabel("MessageId:");
        myIdPrompt.setFont(FONTX);
        final JPanel myIdPanel = new JPanel();
        myIdPanel.setLayout(new BoxLayout(myIdPanel, BoxLayout.X_AXIS));
        myIdPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myIdPanel.add(myIdPrompt);
        myIdPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        myIdPanel.add(theMsgId);
        myIdPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));

        /* Create Panel for selection */
        final JPanel mySelectPanel = new JPanel();
        mySelectPanel.setLayout(new BoxLayout(mySelectPanel, BoxLayout.X_AXIS));
        mySelectPanel.add(Box.createHorizontalStrut(ArduinoPanelMain.STRUTSIZE));
        mySelectPanel.add(theSelect.getComponent());
        mySelectPanel.add(Box.createHorizontalGlue());
        mySelectPanel.add(myIdPanel);
        Border myBorder = BorderFactory.createTitledBorder("Select Message");
        mySelectPanel.setBorder(myBorder);

        /* Create the table */
        theTable = new ArduinoTableSignal(this);

        /* Create Panel for Data */
        final JPanel myDataPanel = new JPanel(new GridBagLayout());
        final GridBagConstraints myC = new GridBagConstraints();
        myBorder = BorderFactory.createTitledBorder("Message Data");
        myDataPanel.setBorder(myBorder);

        /* Add hexData */
        myC.gridx = 0;
        myC.gridy = 0;
        myC.anchor = GridBagConstraints.LINE_END;
        myC.weightx = 0.0;
        myC.fill = GridBagConstraints.NONE;
        myDataPanel.add(new JLabel("Hex Data: "), myC);
        myC.gridx = 1;
        myC.gridy = 0;
        myC.anchor = GridBagConstraints.CENTER;
        myC.weightx = 1.0;
        myC.fill = GridBagConstraints.HORIZONTAL;
        theHex = new JLabel();
        theHex.setFont(FONT);
        myDataPanel.add(theHex, myC);

        /* Add binaryData */
        myC.gridx = 0;
        myC.gridy = 1;
        myC.anchor = GridBagConstraints.LINE_END;
        myC.weightx = 0.0;
        myC.fill = GridBagConstraints.NONE;
        myDataPanel.add(new JLabel("Binary Data: "), myC);
        myC.gridx = 1;
        myC.gridy = 1;
        myC.anchor = GridBagConstraints.CENTER;
        myC.weightx = 1.0;
        myC.fill = GridBagConstraints.HORIZONTAL;
        theBinary = new BinaryData();
        myDataPanel.add(theBinary.getComponent(), myC);


        /* Create the message panel */
        final JPanel myMsgPanel = new JPanel(new BorderLayout());
        myMsgPanel.add(new JScrollPane(theTable.getComponent()), BorderLayout.CENTER);
        myBorder = BorderFactory.createTitledBorder("Message Signals");
        myMsgPanel.setBorder(myBorder);

        /* Create the panel */
        thePanel = new JPanel(new BorderLayout());
        thePanel.add(mySelectPanel, BorderLayout.PAGE_START);
        thePanel.add(myMsgPanel, BorderLayout.CENTER);
        thePanel.add(myDataPanel, BorderLayout.PAGE_END);

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

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

    /**
     * Obtain the data.
     * @return the data
     */
    byte[] getData() {
        return theData;
    }

    /**
     * Set the system.
     * @param pSystem the system
     */
    void setSystem(final ArduinoSystem pSystem) {
        /* Build the new message selection list */
        theSelect.removeAll();
        for (ArduinoMessage myMessage : pSystem.getMessages()) {
            theSelect.add(myMessage);
        }
    }

    /**
     * Select the message.
     * @param pMessage the message
     */
    private void selectMessage(final ArduinoMessage pMessage) {
        /* Allocate the data */
        theData = new byte[pMessage.getLength()];

        /* Configure the table */
        theTable.configureTable(pMessage);

        /* Update the hex and binary */
        updateHexAndBinary();
        updateBitColors();
        updateMessageId(pMessage);
    }

    /**
     * Update messageId.
     * @param pMessage the message
     */
    void updateMessageId(final ArduinoMessage pMessage) {
        final int myNum = Integer.parseUnsignedInt(pMessage.getId());
        theMsgId.setText(pMessage.getId() + String.format("  0x%08X", myNum));
    }

    /**
     * Update hex and binary.
     */
    void updateHexAndBinary() {
        updateHexData();
        theBinary.refreshData();
    }

    /**
     * Update Hex Data.
     */
    private void updateHexData() {
        theHex.setText(ArduinoHex.bytesToHexString(theData));
    }

    /**
     * Update BitColors.
     */
    void updateBitColors() {
        /* Reset the colours */
        theBinary.resetColors();

        /* Loop through the rows */
        final int myNumRows = theTable.getNumRows();
        for (int i = 0; i < myNumRows; i++) {
            /* Obtain the colour for the signal */
            final Color myColor = theTable.getColorForRow(i);

            /* If the signal is active */
            if (myColor != null) {
                /* Access the bitMap for the row */
                final byte[] myBitMap = theTable.getBitMapForRow(i);

                /* Update the colours */
                theBinary.updateColor(myColor, myBitMap);
            }
        }
    }

    /**
     * The binaryData panel.
     */
    private class BinaryData {
        /**
         * The Panel.
         */
        private final JPanel thePanel;

        /**
         * The Binary Bit Panels.
         */
        private final BinaryBit[] theBits;

        /**
         * Constructor.
         */
        BinaryData() {
            /* Create the panel */
            thePanel = new JPanel();
            thePanel.setLayout(new BoxLayout(thePanel, BoxLayout.X_AXIS));

            /* Create the bit label array */
            theBits = new BinaryBit[Long.SIZE];

            /* Create the bit panels */
            for (int i = 0; i < Long.SIZE; i++) {
                /* Create the binary bit */
                final BinaryBit myBit = new BinaryBit(i);
                thePanel.add(myBit.getComponent());
                theBits[i] = myBit;

                /* If we are on a byte boundary */
                if ((i + 1) % Byte.SIZE == 0) {
                    final JLabel mySpacer = new JLabel();
                    mySpacer.setText(" ");
                    thePanel.add(mySpacer);
                }
            }
        }

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

        /**
         * Refresh the data.
         */
        void refreshData() {
            /* determine the maxBit */
            final int maxBit = theData.length * Byte.SIZE;

            /* Loop through the bits */
            for (int i = 0; i < Long.SIZE; i++) {
                final boolean visible = i < maxBit;
                final BinaryBit myBit = theBits[i];
                myBit.refreshData(visible);
            }
        }

        /**
         * Reset colors.
         */
        void resetColors() {
            /* determine the maxBit */
            final int maxBit = theData.length * Byte.SIZE;

            /* Loop through the bytes */
            for (int bitNo = 0; bitNo < maxBit; bitNo++) {
                /* Set the colour */
                theBits[bitNo].setColor(thePanel.getBackground());
            }
        }

        /**
         * Update color.
         * @param pColour the colour
         * @param pBitMap the bitMap
         */
        void updateColor(final Color pColour,
                         final byte[] pBitMap) {
            /* determine the maxBit */
            final int maxBit = theData.length * Byte.SIZE;

            /* Loop through the bytes */
            for (int bitNo = 0; bitNo < maxBit; bitNo++) {
                /* Access details */
                final int myByteNo = bitNo / Byte.SIZE;
                final int myShift = Byte.SIZE - (bitNo % Byte.SIZE) - 1;
                final int myMask = 1 << myShift;

                /* If the bit is set */
                if ((pBitMap[myByteNo] & myMask) != 0) {
                    /* Set the colour */
                    theBits[bitNo].setColor(pColour);
                }
            }
        }
    }

    /**
     * The binaryBit panel.
     */
    private class BinaryBit {
        /**
         * The index.
         */
        private final int theIndex;

        /**
         * The Panel.
         */
        private final JLabel theLabel;

        /**
         * Constructor.
         * @param pIndex the index
         */
        BinaryBit(final int pIndex) {
            /* Store parameter */
            theIndex = pIndex;

            /* Create the label */
            theLabel = new JLabel();
            theLabel.setFont(FONT);
            theLabel.setHorizontalAlignment(SwingConstants.CENTER);
            theLabel.setOpaque(true);

            /* Add the mouse adapter */
            theLabel.addMouseListener(new LabelMouse());
        }

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

        /**
         * Set color.
         * @param pColour the colour
         */
        void setColor(final Color pColour) {
            theLabel.setForeground(Color.BLACK);
            theLabel.setBackground(pColour);
        }

        /**
         * Refresh the data.
         * @param pVisible is the bit visible?
         */
        void refreshData(final boolean pVisible) {
            theLabel.setVisible(pVisible);
            if (pVisible) {
                showBitFromData();
            }
        }

        /**
         * Display the bit value.
         */
        void showBitFromData() {
            final int myByteNo = theIndex / Byte.SIZE;
            final int myShift = Byte.SIZE - (theIndex % Byte.SIZE) - 1;
            final int myMask = 1 << myShift;
            theLabel.setText((theData[myByteNo] & myMask) != 0 ? "1" : "0");
        }

        /**
         * Toggle bit value.
         */
        void toggleBitInData() {
            /* Toggle the bit */
            final int myByteNo = theIndex / Byte.SIZE;
            final int myShift = Byte.SIZE - (theIndex % Byte.SIZE) - 1;
            final int myMask = 1 << myShift;
            theData[myByteNo] ^= myMask;
            theLabel.setText((theData[myByteNo] & myMask) != 0 ? "1" : "0");
            refreshOnToggle();
        }

        /**
         * Refresh On Toggle.
         */
        private void refreshOnToggle() {
            theTable.loadValues();
            updateHexData();
            updateBitColors();
        }

        /**
         * LabelMouse.
         */
        private final class LabelMouse
                extends MouseAdapter {
            @Override
            public void mouseClicked(final MouseEvent e) {
                toggleBitInData();
            }
        }
    }
}