ArduinoTableFilter.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.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.SwingConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;

import net.sourceforge.jarduino.message.ArduinoMessage;
import net.sourceforge.jarduino.message.ArduinoMsgFilter;
import net.sourceforge.jarduino.message.ArduinoNamedObject;
import net.sourceforge.jarduino.message.ArduinoSystem;
import net.sourceforge.jarduino.util.ArduinoLogManager;
import net.sourceforge.jarduino.util.ArduinoLogger;

/**
 * Arduino Message Table.
 */
public class ArduinoTableFilter {
    /**
     * The LOGGER.
     */
    private static final ArduinoLogger LOGGER = ArduinoLogManager.getLogger(ArduinoTableFilter.class);

    /**
     * The tick icon.
     */
    private static final Icon TICKICON = loadIcon("GreenJellyTick.png");

    /**
     * The cross icon.
     */
    private static final Icon CROSSICON = loadIcon("OrangeJellyCross.png");

    /**
     * Icon Size.
     */
    private static final int ICON_SIZE = 20;

    /**
     * Small width.
     */
    private static final int WIDTH_SMALL = 60;

    /**
     * Medium width.
     */
    private static final int WIDTH_MEDIUM = 80;

    /**
     * Large width.
     */
    private static final int WIDTH_LARGE = 150;

    /**
     * The parent panel.
     */
    private final ArduinoPanelFilter theParent;

    /**
     * The table.
     */
    private final JTable theTable;

    /**
     * The table model.
     */
    private final ArduinoFilterModel theModel;

    /**
     * The message list.
     */
    private final ArduinoMsgFilter theMsgFilter;

    /**
     * The filter.
     */
    private List<ArduinoMessage> theMessages;

    /**
     * Are we using a bespoke filter.
     */
    private boolean isBespoke;

    /**
     * Constructor.
     * @param pParent the parent panel
     */
    ArduinoTableFilter(final ArduinoPanelFilter pParent) {
        /* Store parameters */
        theParent = pParent;

        /* Create the filter */
        theMsgFilter = new ArduinoMsgFilter();

        /* Create the table and model */
        theTable = new JTable();
        theModel = new ArduinoFilterModel();
        theTable.setModel(theModel);
        theTable.addMouseListener(new MouseListener());

        /* Set the row sorter */
        theModel.setSorter();

        /* Configure the columns */
        configureColumns();
    }

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


    /**
     * Obtain the filter.
     * @return the filter
     */
    ArduinoMsgFilter getFilter() {
        return theMsgFilter;
    }

    /**
     * Configure the columns.
     */
    private void configureColumns() {
        /* Create renderers */
        final TableCellRenderer myRightRenderer = new AlignmentRenderer(SwingConstants.RIGHT);
        final TableCellRenderer myCenterRenderer = new AlignmentRenderer(SwingConstants.CENTER);
        final TableCellRenderer myIconRenderer = new IconRenderer();

        /* Access the column model */
        final TableColumnModel myModel = theTable.getColumnModel();

        /* Set the name column */
        TableColumn myColumn = myModel.getColumn(ArduinoFilterModel.COL_NAME);
        myColumn.setPreferredWidth(WIDTH_LARGE);
        myColumn.setCellRenderer(myRightRenderer);

        /* Set the id column */
        myColumn = myModel.getColumn(ArduinoFilterModel.COL_ID);
        myColumn.setPreferredWidth(WIDTH_MEDIUM);
        myColumn.setCellRenderer(myCenterRenderer);

        /* Set the received column */
        myColumn = myModel.getColumn(ArduinoFilterModel.COL_RECEIVED);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setMaxWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myIconRenderer);

        /* Set the sent column */
        myColumn = myModel.getColumn(ArduinoFilterModel.COL_SENT);
        myColumn.setPreferredWidth(WIDTH_SMALL);
        myColumn.setMaxWidth(WIDTH_SMALL);
        myColumn.setCellRenderer(myIconRenderer);
    }

    /**
     * Configure the table.
     *
     * @param pSystem the system
     */
    void configureForSystem(final ArduinoSystem pSystem) {
        /* store the details */
        theMessages = pSystem.getMessages();

        /* Configure for the system node */
        configureForNode(pSystem);
    }

    /**
     * Configure the table.
     *
     * @param pNode the node
     */
    void configureForNode(final ArduinoNamedObject pNode) {
        /* Update the filter */
        theMsgFilter.resetSelection(pNode);

        /* Reset bespoke flag */
        isBespoke = false;

        /* update the table model */
        theModel.fireTableDataChanged();
        theParent.updateFilter();
    }

    /**
     * Is the filter bespoke?
     * @return true/false
     */
    boolean isBespoke() {
        return isBespoke;
    }

    /**
     * Load icon.
     * @param pName the name of the icon
     * @return the icon
     */
    private static Icon loadIcon(final String pName) {
        /* Load the icon */
        try {
            final Image myImage = ImageIO.read(ArduinoTableFilter.class.getResourceAsStream("icons/" + pName));
            final Image myNewImage = myImage.getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH);
            return new ImageIcon(myNewImage);
        } catch (IOException e) {
            LOGGER.error("Failed to load icon", e);
            return null;
        }
    }

    /**
     * Table Model.
     */
    private class ArduinoFilterModel
            extends AbstractTableModel {
        /**
         * Serial Id.
         */
        private static final long serialVersionUID = 240324436537632666L;

        /**
         * The name column.
         */
        private static final int COL_NAME = 0;

        /**
         * The id column.
         */
        private static final int COL_ID = COL_NAME + 1;

        /**
         * The sent column.
         */
        private static final int COL_SENT = COL_ID + 1;

        /**
         * The received column.
         */
        private static final int COL_RECEIVED = COL_SENT + 1;

        /**
         * Constructor.
         */
        ArduinoFilterModel() {
        }

        @Override
        public int getRowCount() {
            return theMessages != null ? theMessages.size() : 0;
        }

        @Override
        public String getColumnName(final int pColIndex) {
            switch (pColIndex) {
                case COL_NAME:
                    return "Name";
                case COL_ID:
                    return "Id";
                case COL_SENT:
                    return "Sent";
                case COL_RECEIVED:
                    return "Received";
                default:
                    return null;
            }
        }

        @Override
        public int getColumnCount() {
            return COL_RECEIVED + 1;
        }

        @Override
        public boolean isCellEditable(final int pRowIndex,
                                      final int pColIndex) {
            return false;
        }

        @Override
        public Object getValueAt(final int pRowIndex,
                                 final int pColIndex) {
            switch (pColIndex) {
                case COL_NAME:
                    return theMessages.get(pRowIndex).getName();
                case COL_ID:
                    return getId(pRowIndex);
                case COL_SENT:
                    return theMsgFilter.isBuilt(getId(pRowIndex)) ? TICKICON : CROSSICON;
                case COL_RECEIVED:
                    return theMsgFilter.isParsed(getId(pRowIndex)) ? TICKICON : CROSSICON;
                default:
                    return null;
            }
        }

        /**
         * Obtain the id for a row.
         * @param pRowIndex the rowIndex
         * @return the id.
         */
        private String getId(final int pRowIndex) {
            return theMessages.get(pRowIndex).getId();
        }

        /**
         * Set row sorter.
         */
        void setSorter() {
            /* Create a sorter */
            final TableRowSorter<ArduinoFilterModel> mySorter = new TableRowSorter<>(this);

            /* Create the standard sort order */
            final List<SortKey> mySortKeys = new ArrayList<>();
            mySortKeys.add(new SortKey(COL_SENT, SortOrder.ASCENDING));
            mySortKeys.add(new SortKey(COL_RECEIVED, SortOrder.ASCENDING));
            mySorter.setSortKeys(mySortKeys);

            /* Sort on update and set sorter */
            mySorter.setSortsOnUpdates(true);
            theTable.setRowSorter(mySorter);
        }
    }

    /**
     * Simple Alignment Renderer.
     */
    private static class AlignmentRenderer extends DefaultTableCellRenderer {
        /**
         * Constructor.
         *
         * @param pAlignment the alignment
         */
        AlignmentRenderer(final int pAlignment) {
            setHorizontalAlignment(pAlignment);
        }

        @Override
        public JComponent getTableCellRendererComponent(final JTable pTable,
                                                        final Object pValue,
                                                        final boolean pSelected,
                                                        final boolean hasFocus,
                                                        final int pRow,
                                                        final int pCol) {
            /* Obtain the string representation */
            final String myValue = pValue == null
                             ? null
                             : pValue.toString();

            /* Set the value */
            setText(myValue);

            /* return this as the component */
            return this;
        }
    }

    /**
     * Simple Icon Renderer.
     */
    private static class IconRenderer extends DefaultTableCellRenderer {
        /**
         * Constructor.
         */
        IconRenderer() {
            setHorizontalAlignment(SwingConstants.CENTER);
        }

        @Override
        public JComponent getTableCellRendererComponent(final JTable pTable,
                                                        final Object pValue,
                                                        final boolean pSelected,
                                                        final boolean hasFocus,
                                                        final int pRow,
                                                        final int pCol) {
            /* Access the icon */
            final Icon myIcon = pValue instanceof Icon
                                   ? (Icon) pValue
                                   : null;

            /* Set the icon */
            setIcon(myIcon);

            /* return this as the component */
            return this;
        }
    }

    /**
     * Mouse Adapter class.
     * <p>
     * Required to handle button clicked, dragged, and released in different place
     */
    private class MouseListener
            extends MouseAdapter {
        @Override
        public void mouseClicked(final MouseEvent e) {
            /* Determine the row and column that the click has occurred in */
            final int myRow = theTable.convertRowIndexToModel(theTable.rowAtPoint(e.getPoint()));
            final int myCol = theTable.convertColumnIndexToModel(theTable.columnAtPoint(e.getPoint()));

            /* If it was in the table and is a column of interest */
            if (myRow != -1
                    && (myCol == ArduinoFilterModel.COL_RECEIVED || myCol == ArduinoFilterModel.COL_SENT)) {
                /* Process the click */
                final ArduinoMessage myMessage = theMessages.get(myRow);
                processClick(myMessage, myCol);
                theModel.fireTableCellUpdated(myRow, myCol);
                theParent.updateFilter();
            }
        }

        /**
         * Process click.
         * @param pMessage the message
         * @param pColumn the column index
         */
        void processClick(final ArduinoMessage pMessage,
                          final int pColumn) {
            final int myFlag = pColumn == ArduinoFilterModel.COL_RECEIVED
                               ? ArduinoMsgFilter.PARSEMSG
                               : ArduinoMsgFilter.BUILDMSG;
            theMsgFilter.toggleFlag(pMessage.getId(), myFlag);
            isBespoke = true;
        }
    }
}