Search in sources :

Example 1 with UndoableTableModel

use of CCDD.CcddUndoHandler.UndoableTableModel in project CCDD by nasa.

the class CcddJTableHandler method setFixedCharacteristics.

/**
 ********************************************************************************************
 * Set common table parameters and characteristics. These only need to be set when the table is
 * initially created and do not require to be updated if the table is changed while it is
 * visible
 *
 * @param scrollPane
 *            the scroll pane in which the table resides. Set to null if the table isn't in a
 *            scroll pane
 *
 * @param isRowsAlterable
 *            true if the table's rows are allowed to be changed (e.g., inserted, deleted,
 *            moved) via keyboard commands from the user
 *
 * @param intervalSelection
 *            ListSelectionModel selection mode used by the row and column selection models
 *
 * @param cellSelection
 *            table cell selection mode that determines the cell or cells selected when a cell
 *            has focus: SELECT_BY_ROW to select the entire row occupied the cell with focus;
 *            SELECT_BY_COLUMN to select the entire column containing the cell with focus;
 *            SELECT_BY_CELL to select only the cell with focus
 *
 * @param columnDragAllowed
 *            true if a column can be positioned by selecting the header via the mouse and
 *            dragging the column
 *
 * @param background
 *            table background color (when row not selected)
 *
 * @param selectWithoutFocus
 *            true to ignore if the table has focus when determining the row colors for
 *            selected rows
 *
 * @param allowUndo
 *            true to allow changes to the table to be undone and redone
 *
 * @param cellFont
 *            font to use for displaying the cell contents
 *
 * @param allowSort
 *            true to enable the rows to be sorted by selecting a column header; false to
 *            disable sorting
 *******************************************************************************************
 */
protected void setFixedCharacteristics(final JScrollPane scrollPane, boolean isRowsAlterable, int intervalSelection, TableSelectionMode cellSelection, boolean columnDragAllowed, ModifiableColor background, boolean selectWithoutFocus, boolean allowUndo, Font cellFont, boolean allowSort) {
    // Set the table's scroll pane
    this.scrollPane = scrollPane;
    // Set the table's non-selected background color
    setBackground(background);
    // Set to true to keep cell(s) highlighted when the table loses focus
    this.selectWithoutFocus = selectWithoutFocus;
    // Set to true to allow changes to the table to be undone/redone
    undoHandler.setAllowUndo(allowUndo);
    // Set the cell content font
    this.cellFont = cellFont;
    // Set to true to allow the rows to be sorted by selecting the column header
    this.allowSort = allowSort;
    // Initialize the special row color lists. These lists remain empty if no rows are assigned
    // special colors
    rowColorIndex = new ArrayList<Integer>();
    rowColor = new ArrayList<Color>();
    // Set the selection mode (single, contiguous, or multiple)
    setSelectionMode(intervalSelection);
    // Set row and column selection modes. If both are true then selection is by single cell
    this.cellSelection = cellSelection;
    setRowSelectionAllowed(cellSelection == TableSelectionMode.SELECT_BY_ROW || cellSelection == TableSelectionMode.SELECT_BY_CELL);
    setColumnSelectionAllowed(cellSelection == TableSelectionMode.SELECT_BY_COLUMN || cellSelection == TableSelectionMode.SELECT_BY_CELL);
    // Set if a column can be moved by selecting the header with the mouse and dragging it
    getTableHeader().setReorderingAllowed(columnDragAllowed);
    // Remove the table border
    setBorder(BorderFactory.createEmptyBorder());
    // Set the focus to a cell if the keyboard is used to select it. Needed under some
    // circumstances to make text cursor appear in table cell when editing
    setSurrendersFocusOnKeystroke(true);
    // Set so that the columns aren't automatically resized when the table is resized; this is
    // handled manually below
    setAutoResizeMode(AUTO_RESIZE_OFF);
    // Set the font for the table cells
    setFont(cellFont);
    // Replace the table's header mouse listener with a version that captures double clicks on
    // the column header borders in order to implement automatic resizing
    resizeColumnListener = new ResizeColumnListener();
    // Listen for changes made by the user to the table's cells
    new TableCellListener();
    // Change TAB/SHIFT-TAB behavior so that focus jumps between the tables and the buttons
    setTableKeyboardTraversal();
    // Create the table model. The data and column headers are added later in case these need
    // to be adjusted
    tableModel = undoHandler.new UndoableTableModel() {

        /**
         ************************************************************************************
         * Override the cell clean-up method
         ************************************************************************************
         */
        @Override
        protected Object cleanUpCellValue(Object value, int row, int column) {
            return table.cleanUpCellValue(value, row, column);
        }
    };
    setModel(tableModel);
    // Exit the cell's editor, if active, when the cell loses focus
    putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    // Add a listener for table focus changes
    addFocusListener(new FocusListener() {

        /**
         ************************************************************************************
         * Handle loss of keyboard focus for the table
         ************************************************************************************
         */
        @Override
        public void focusLost(FocusEvent fe) {
            // Force a repaint so that any highlighted rows are unhighlighted when the table
            // loses focus
            repaint();
        }

        /**
         ************************************************************************************
         * Handle gain of keyboard focus for the table
         ************************************************************************************
         */
        @Override
        public void focusGained(FocusEvent fe) {
            // Force a repaint so that all cells are highlighted in a row when the table row
            // gains focus
            repaint();
        }
    });
    // Row height, used to set the viewport size, needs to be determined prior to loading the
    // data, which can alter the height returned; and loading the data must be done prior to
    // setting the viewport size in order for the initial viewport size to be set correctly
    int rowHeight = getRowHeight();
    // Load the table data and format the table cells
    loadAndFormatData();
    // Set the initial number of rows to display in the table
    setPreferredScrollableViewportSize(new Dimension(getPreferredSize().width, initialViewableRows * (rowHeight + getRowMargin() * 2 + 4)));
    // Make the table fill the dialog
    setFillsViewportHeight(true);
    // Set the keys that initiate table cell editing and the keys to be ignored when initiating
    // editing. If the table cells and rows are editable then set the keys for these actions
    setEditorKeys(isRowsAlterable);
    // Add a listener for scroll bar thumb position changes
    scrollPane.getViewport().addChangeListener(new ChangeListener() {

        /**
         ************************************************************************************
         * Handle a scroll bar thumb position change for the table
         ************************************************************************************
         */
        @Override
        public void stateChanged(ChangeEvent ce) {
            // Update the row heights for the visible rows
            tableChanged(null);
        }
    });
}
Also used : Color(java.awt.Color) ModifiableColor(CCDD.CcddClassesComponent.ModifiableColor) Dimension(java.awt.Dimension) FocusEvent(java.awt.event.FocusEvent) Point(java.awt.Point) TableInsertionPoint(CCDD.CcddConstants.TableInsertionPoint) ChangeEvent(javax.swing.event.ChangeEvent) PropertyChangeEvent(java.beans.PropertyChangeEvent) UndoableTableModel(CCDD.CcddUndoHandler.UndoableTableModel) ChangeListener(javax.swing.event.ChangeListener) PropertyChangeListener(java.beans.PropertyChangeListener) FocusListener(java.awt.event.FocusListener)

Example 2 with UndoableTableModel

use of CCDD.CcddUndoHandler.UndoableTableModel in project CCDD by nasa.

the class CcddScriptHandler method getEnvironmentVariableMap.

/**
 ********************************************************************************************
 * Update the environment variable map with any variables specified by the user. Update the
 * association availability based on if the script exists, using the current environment
 * variables to expand its path
 *
 * @param parent
 *            GUI component calling this method
 *
 * @return true if the each key has a corresponding value; false if a value is missing
 ********************************************************************************************
 */
private boolean getEnvironmentVariableMap(Component parent) {
    boolean isValid = true;
    // Get the current system environment variable map
    envVarMap = new HashMap<String, String>(System.getenv());
    // Step through the overrides, if any
    for (String envVarDefn : envVarOverrideFld.getText().split("\\s*,\\s*")) {
        // Check that the definition isn't blank
        if (!envVarDefn.isEmpty()) {
            // Split the override into a key and value
            String[] keyAndValue = CcddUtilities.splitAndRemoveQuotes(envVarDefn.trim(), "\\s*=\\s*", 2, true);
            // Check if the key and value are present
            if (keyAndValue.length == 2) {
                // Add the key and value to the map if the key doesn't already exist; otherwise
                // replace the value for the key
                envVarMap.put(keyAndValue[0].trim().replaceAll("^\\$\\s*", ""), keyAndValue[1].trim());
            } else // Insufficient parameters
            {
                // Inform the user that script association execution can't continue due to an
                // invalid input
                new CcddDialogHandler().showMessageDialog(parent, "<html><b>Environment variable override key '" + keyAndValue[0] + "' has no corresponding value", "Invalid Input", JOptionPane.WARNING_MESSAGE, DialogOption.OK_OPTION);
                isValid = false;
                break;
            }
        }
    }
    // Check if every key has a value
    if (isValid) {
        // Update the environment variable override preferences
        ModifiableOtherSettingInfo.ENV_VAR_OVERRIDE.setValue(envVarOverrideFld.getText().trim(), ccddMain.getProgPrefs());
        // Step through each script association
        for (int row = 0; row < assnsTable.getRowCount(); row++) {
            // Check if the association isn't unavailable due to a missing table
            if (assnsTable.getValueAt(row, AssociationsTableColumnInfo.SCRIPT_FILE.ordinal()) != AvailabilityType.TABLE_MISSING) {
                // Get the reference to the association's script file
                FileEnvVar file = new FileEnvVar(FileEnvVar.expandEnvVars(assnsTable.getValueAt(row, AssociationsTableColumnInfo.SCRIPT_FILE.ordinal()).toString(), envVarMap));
                // Set the availability status based on if the script file exists
                ((UndoableTableModel) assnsTable.getModel()).setValueAt((file.exists() ? AvailabilityType.AVAILABLE : AvailabilityType.SCRIPT_MISSING), row, AssociationsTableColumnInfo.AVAILABLE.ordinal(), false);
            }
        }
        // Force the table to redraw so that a change in an association's availability status
        // is reflected
        ((UndoableTableModel) assnsTable.getModel()).fireTableDataChanged();
        ((UndoableTableModel) assnsTable.getModel()).fireTableStructureChanged();
    }
    return isValid;
}
Also used : UndoableTableModel(CCDD.CcddUndoHandler.UndoableTableModel) FileEnvVar(CCDD.CcddClassesComponent.FileEnvVar)

Example 3 with UndoableTableModel

use of CCDD.CcddUndoHandler.UndoableTableModel in project CCDD by nasa.

the class CcddScriptHandler method getAssociationsPanel.

/**
 ********************************************************************************************
 * Create the panel containing the script associations table
 *
 * @param title
 *            text to display above the script associations table; null or blank if no text is
 *            to be displayed
 *
 * @param allowSelectDisabled
 *            true if disabled associations can be selected; false if not. In the script
 *            manager disabled associations are selectable so that these can be deleted if
 *            desired. Scripts that are selected and disabled are ignored when executing
 *            scripts
 *
 * @param parent
 *            GUI component calling this method
 *
 * @return Reference to the JPanel containing the script associations table
 ********************************************************************************************
 */
@SuppressWarnings("serial")
protected JPanel getAssociationsPanel(String title, final boolean allowSelectDisabled, final Component parent) {
    // Set the initial layout manager characteristics
    GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(ModifiableSpacingInfo.LABEL_VERTICAL_SPACING.getSpacing(), 0, 0, 0), 0, 0);
    // Define the panel to contain the table
    JPanel assnsPnl = new JPanel(new GridBagLayout());
    // Check if a table title is provided
    if (title != null && !title.isEmpty()) {
        // Create the script associations label
        JLabel assnsLbl = new JLabel(title);
        assnsLbl.setFont(ModifiableFontInfo.LABEL_BOLD.getFont());
        assnsLbl.setForeground(ModifiableColorInfo.SPECIAL_LABEL_TEXT.getColor());
        assnsPnl.add(assnsLbl, gbc);
        gbc.gridy++;
    }
    // Create the table to display the search results
    assnsTable = new CcddJTableHandler() {

        /**
         ************************************************************************************
         * Allow multiple line display in all columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnMultiLine(int column) {
            return true;
        }

        /**
         ************************************************************************************
         * Allow HTML-formatted text in the specified column(s)
         ************************************************************************************
         */
        @Override
        protected boolean isColumnHTML(int column) {
            return column == AssociationsTableColumnInfo.MEMBERS.ordinal();
        }

        /**
         ************************************************************************************
         * Hide the the specified columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnHidden(int column) {
            return column == AssociationsTableColumnInfo.AVAILABLE.ordinal();
        }

        /**
         ************************************************************************************
         * Allow editing the description in the script manager's associations table
         ************************************************************************************
         */
        @Override
        public boolean isCellEditable(int row, int column) {
            return (column == convertColumnIndexToModel(AssociationsTableColumnInfo.NAME.ordinal()) || column == convertColumnIndexToModel(AssociationsTableColumnInfo.DESCRIPTION.ordinal())) && allowSelectDisabled;
        }

        /**
         ************************************************************************************
         * Validate changes to the editable cells
         *
         * @param tableData
         *            list containing the table data row arrays
         *
         * @param row
         *            table model row number
         *
         * @param column
         *            table model column number
         *
         * @param oldValue
         *            original cell contents
         *
         * @param newValue
         *            new cell contents
         *
         * @param showMessage
         *            true to display the invalid input dialog, if applicable
         *
         * @param isMultiple
         *            true if this is one of multiple cells to be entered and checked; false if
         *            only a single input is being entered
         *
         * @return Always returns false
         ***********************************************************************************
         */
        @Override
        protected Boolean validateCellContent(List<Object[]> tableData, int row, int column, Object oldValue, Object newValue, Boolean showMessage, boolean isMultiple) {
            // Reset the flag that indicates the last edited cell's content is invalid
            setLastCellValid(true);
            // Create a string version of the new value
            String newValueS = newValue.toString();
            try {
                // Check if the value isn't blank
                if (!newValueS.isEmpty()) {
                    // blank
                    if (column == AssociationsTableColumnInfo.NAME.ordinal()) {
                        // type
                        if (!newValueS.matches(InputDataType.ALPHANUMERIC.getInputMatch())) {
                            throw new CCDDException("Illegal character(s) in association name");
                        }
                        // avoid creating a duplicate
                        for (int otherRow = 0; otherRow < getRowCount(); otherRow++) {
                            // association name matches the one being added (case insensitive)
                            if (otherRow != row && newValueS.equalsIgnoreCase(tableData.get(otherRow)[column].toString())) {
                                throw new CCDDException("Association name already in use");
                            }
                        }
                    }
                }
            } catch (CCDDException ce) {
                // Set the flag that indicates the last edited cell's content is invalid
                setLastCellValid(false);
                // Check if the input error dialog should be displayed
                if (showMessage) {
                    // Inform the user that the input value is invalid
                    new CcddDialogHandler().showMessageDialog(parent, "<html><b>" + ce.getMessage(), "Invalid Input", JOptionPane.WARNING_MESSAGE, DialogOption.OK_OPTION);
                }
                // Restore the cell contents to its original value and pop the edit from the
                // stack
                tableData.get(row)[column] = oldValue;
                getUndoManager().undoRemoveEdit();
            }
            return false;
        }

        /**
         ************************************************************************************
         * Load the script associations data into the table and format the table cells
         ************************************************************************************
         */
        @Override
        protected void loadAndFormatData() {
            // Place the data into the table model along with the column names, set up the
            // editors and renderers for the table cells, set up the table grid lines, and
            // calculate the minimum width required to display the table information
            int totalWidth = setUpdatableCharacteristics(getScriptAssociationData(allowSelectDisabled, parent), AssociationsTableColumnInfo.getColumnNames(), null, AssociationsTableColumnInfo.getToolTips(), true, true, true);
            // Check if the script manager or executive is active
            if (scriptDialog != null) {
                // Set the script manager or executive width to the associations table width
                scriptDialog.setTableWidth(totalWidth + LAF_SCROLL_BAR_WIDTH + ModifiableSpacingInfo.LABEL_HORIZONTAL_SPACING.getSpacing() * 2);
            }
        }

        /**
         ************************************************************************************
         * Alter the association table cell color or contents
         *
         * @param component
         *            reference to the table cell renderer component
         *
         * @param value
         *            cell value
         *
         * @param isSelected
         *            true if the cell is to be rendered with the selection highlighted
         *
         * @param int
         *            row cell row, view coordinates
         *
         * @param column
         *            cell column, view coordinates
         ************************************************************************************
         */
        @Override
        protected void doSpecialRendering(Component component, String text, boolean isSelected, int row, int column) {
            // Check if the association on the specified row is flagged as unavailable
            if (!isAssociationAvailable(convertRowIndexToModel(row))) {
                // Set the text color for this row to indicate it's not available
                ((JTextComponent) component).setForeground(Color.GRAY);
                // Check if selection of disabled associations isn't allowed
                if (!allowSelectDisabled) {
                    // Set the background color to indicate the row isn't selectable
                    ((JTextComponent) component).setBackground(ModifiableColorInfo.TABLE_BACK.getColor());
                }
            }
            // displayed
            if (column == convertColumnIndexToView(AssociationsTableColumnInfo.SCRIPT_FILE.ordinal()) && hideScriptFilePath.isSelected()) {
                // Remove the path, leaving only the script file name
                ((JTextComponent) component).setText(((JTextComponent) component).getText().replaceFirst(".*" + Pattern.quote(File.separator), ""));
            }
        }

        /**
         ************************************************************************************
         * Override the method that sets the row sorter so that special sorting can be
         * performed on the script file column
         ************************************************************************************
         */
        @Override
        protected void setTableSortable() {
            super.setTableSortable();
            // Get a reference to the sorter
            @SuppressWarnings("unchecked") TableRowSorter<UndoableTableModel> sorter = (TableRowSorter<UndoableTableModel>) getRowSorter();
            // rows in the table
            if (sorter != null) {
                // Add a sort comparator for the script file column
                sorter.setComparator(AssociationsTableColumnInfo.SCRIPT_FILE.ordinal(), new Comparator<String>() {

                    /**
                     ************************************************************************
                     * Override the comparison when sorting the script file column to ignore
                     * the script file paths if these are currently hidden
                     ************************************************************************
                     */
                    @Override
                    public int compare(String filePath1, String filePath2) {
                        return (hideScriptFilePath.isSelected() ? filePath1.replaceFirst(".*" + Pattern.quote(File.separator), "") : filePath1).compareTo(hideScriptFilePath.isSelected() ? filePath2.replaceFirst(".*" + Pattern.quote(File.separator), "") : filePath2);
                    }
                });
            }
        }

        /**
         ************************************************************************************
         * Handle a change to the table's content
         ************************************************************************************
         */
        @Override
        protected void processTableContentChange() {
            // associations manager dialog is open)
            if (scriptDialog != null && scriptDialog instanceof CcddScriptManagerDialog) {
                // Update the script associations manager change indicator
                ((CcddScriptManagerDialog) scriptDialog).updateChangeIndicator();
            }
        }
    };
    // Set the list selection model in order to detect table rows that aren't allowed to be
    // selected
    assnsTable.setSelectionModel(new DefaultListSelectionModel() {

        /**
         ************************************************************************************
         * Check if the script association table item is selected, ignoring associations that
         * are flagged as unavailable
         ************************************************************************************
         */
        @Override
        public boolean isSelectedIndex(int row) {
            return allowSelectDisabled || isAssociationAvailable(assnsTable.convertRowIndexToModel(row)) ? super.isSelectedIndex(row) : false;
        }
    });
    // Place the table into a scroll pane
    JScrollPane scrollPane = new JScrollPane(assnsTable);
    // Set up the search results table parameters
    assnsTable.setFixedCharacteristics(scrollPane, false, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, TableSelectionMode.SELECT_BY_ROW, true, ModifiableColorInfo.TABLE_BACK.getColor(), true, true, ModifiableFontInfo.OTHER_TABLE_CELL.getFont(), true);
    // Define the panel to contain the table and add it to the dialog
    JPanel assnsTblPnl = new JPanel();
    assnsTblPnl.setLayout(new BoxLayout(assnsTblPnl, BoxLayout.X_AXIS));
    assnsTblPnl.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
    assnsTblPnl.add(scrollPane);
    gbc.weighty = 1.0;
    assnsPnl.add(assnsTblPnl, gbc);
    // Create the check box for hiding/showing the file paths in the associations table script
    // file column
    hideScriptFilePath = new JCheckBox("Hide script file path", ccddMain.getProgPrefs().getBoolean(HIDE_SCRIPT_PATH, false));
    hideScriptFilePath.setFont(ModifiableFontInfo.LABEL_BOLD.getFont());
    hideScriptFilePath.setBorder(BorderFactory.createEmptyBorder());
    hideScriptFilePath.setToolTipText(CcddUtilities.wrapText("Remove the file paths from the script file column", ModifiableSizeInfo.MAX_TOOL_TIP_LENGTH.getSize()));
    // Add a listener for check box selection changes
    hideScriptFilePath.addActionListener(new ActionListener() {

        /**
         ************************************************************************************
         * Handle a change in the hide script file path check box state
         ************************************************************************************
         */
        @Override
        public void actionPerformed(ActionEvent ae) {
            assnsTable.repaint();
            ccddMain.getProgPrefs().putBoolean(HIDE_SCRIPT_PATH, hideScriptFilePath.isSelected());
        }
    });
    gbc.weighty = 0.0;
    gbc.gridy++;
    assnsPnl.add(hideScriptFilePath, gbc);
    // Create a panel to contain the environment variable override label and field
    JPanel envVarOverridePnl = new JPanel(new GridBagLayout());
    JLabel envVarOverrideLbl = new JLabel("Environment variable override");
    envVarOverrideLbl.setFont(ModifiableFontInfo.LABEL_BOLD.getFont());
    gbc.insets.right = ModifiableSpacingInfo.LABEL_HORIZONTAL_SPACING.getSpacing();
    gbc.weightx = 0.0;
    gbc.gridy++;
    envVarOverridePnl.add(envVarOverrideLbl, gbc);
    envVarOverrideFld = new JTextField(ModifiableOtherSettingInfo.ENV_VAR_OVERRIDE.getValue());
    envVarOverrideFld.setFont(ModifiableFontInfo.INPUT_TEXT.getFont());
    envVarOverrideFld.setEditable(true);
    envVarOverrideFld.setForeground(ModifiableColorInfo.INPUT_TEXT.getColor());
    envVarOverrideFld.setBackground(ModifiableColorInfo.INPUT_BACK.getColor());
    envVarOverrideFld.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.LIGHT_GRAY, Color.GRAY), BorderFactory.createEmptyBorder(ModifiableSpacingInfo.INPUT_FIELD_PADDING.getSpacing(), ModifiableSpacingInfo.INPUT_FIELD_PADDING.getSpacing(), ModifiableSpacingInfo.INPUT_FIELD_PADDING.getSpacing(), ModifiableSpacingInfo.INPUT_FIELD_PADDING.getSpacing())));
    // Add a listener for focus changes on the environment variable override field
    envVarOverrideFld.addFocusListener(new FocusAdapter() {

        /**
         ************************************************************************************
         * Handle a loss of focus
         ************************************************************************************
         */
        @Override
        public void focusLost(FocusEvent fe) {
            // Update the environment variable map and association availability
            getEnvironmentVariableMap(parent);
        }
    });
    gbc.insets.right = 0;
    gbc.weightx = 1.0;
    gbc.gridx++;
    envVarOverridePnl.add(envVarOverrideFld, gbc);
    gbc.gridx = 0;
    assnsPnl.add(envVarOverridePnl, gbc);
    // Initialize the environment variable map
    getEnvironmentVariableMap(parent);
    return assnsPnl;
}
Also used : JPanel(javax.swing.JPanel) FocusAdapter(java.awt.event.FocusAdapter) GridBagConstraints(java.awt.GridBagConstraints) Insets(java.awt.Insets) CCDDException(CCDD.CcddClassesDataTable.CCDDException) GridBagLayout(java.awt.GridBagLayout) ActionEvent(java.awt.event.ActionEvent) BoxLayout(javax.swing.BoxLayout) DefaultListSelectionModel(javax.swing.DefaultListSelectionModel) JTextField(javax.swing.JTextField) FocusEvent(java.awt.event.FocusEvent) Comparator(java.util.Comparator) Component(java.awt.Component) JTextComponent(javax.swing.text.JTextComponent) TableRowSorter(javax.swing.table.TableRowSorter) JScrollPane(javax.swing.JScrollPane) JLabel(javax.swing.JLabel) JCheckBox(javax.swing.JCheckBox) ActionListener(java.awt.event.ActionListener) UndoableTableModel(CCDD.CcddUndoHandler.UndoableTableModel)

Example 4 with UndoableTableModel

use of CCDD.CcddUndoHandler.UndoableTableModel in project CCDD by nasa.

the class CcddTableEditorHandler method initialize.

/**
 ********************************************************************************************
 * Create the table editor
 ********************************************************************************************
 */
private void initialize() {
    // Set the table type definition
    setTypeDefinition();
    // Get the model column indices for columns with special input types
    getSpecialColumnIndices();
    // Create a copy of the table information
    setCommittedInformation(tableInfo);
    // Get the array size and index column indices and create a row filter to show/hide the
    // array member rows if an array size column exists
    setUpArraySizeColumn();
    // Define the table editor JTable
    table = new CcddJTableHandler(ModifiableSizeInfo.INIT_VIEWABLE_DATA_TABLE_ROWS.getSize()) {

        /**
         ************************************************************************************
         * Highlight any macros or special flags in the table cells
         *
         * @param component
         *            reference to the table cell renderer component
         *
         * @param text
         *            cell text
         *
         * @param isSelected
         *            true if the cell is to be rendered with the selection highlighted
         *
         * @param int
         *            row cell row, view coordinates
         *
         * @param column
         *            cell column, view coordinates
         ************************************************************************************
         */
        @Override
        protected void doSpecialRendering(Component component, String text, boolean isSelected, int row, int column) {
            // Highlight any macro names in the table cell. Adjust the highlight color to
            // account for the cell selection highlighting so that the macro is easily readable
            macroHandler.highlightMacro(component, text, isSelected ? ModifiableColorInfo.INPUT_TEXT.getColor() : ModifiableColorInfo.TEXT_HIGHLIGHT.getColor());
            // Highlight 'sizeof(data type)' instances
            CcddDataTypeHandler.highlightSizeof(component, text, isSelected ? ModifiableColorInfo.INPUT_TEXT.getColor() : ModifiableColorInfo.TEXT_HIGHLIGHT.getColor());
            // Highlight the flag that indicates the custom value for this cell is to be
            // removed and the prototype's value used instead. Create a highlighter painter
            DefaultHighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(isSelected ? ModifiableColorInfo.INPUT_TEXT.getColor() : Color.MAGENTA);
            // Create the match pattern
            Pattern pattern = Pattern.compile("^" + Pattern.quote(REPLACE_INDICATOR));
            // Create the pattern matcher from the pattern
            Matcher matcher = pattern.matcher(text);
            // Check if there is a match in the cell value
            if (matcher.find()) {
                try {
                    // Highlight the matching text. Adjust the highlight color to account for
                    // the cell selection highlighting so that the search text is easily
                    // readable
                    ((JTextComponent) component).getHighlighter().addHighlight(matcher.start(), matcher.end(), painter);
                } catch (BadLocationException ble) {
                // Ignore highlighting failure
                }
            }
        }

        /**
         ************************************************************************************
         * Get the tool tip text for a table cell, showing any macro name replaced with its
         * corresponding macro value
         ************************************************************************************
         */
        @Override
        public String getToolTipText(MouseEvent me) {
            String toolTipText = null;
            // Get the row and column of the cell over which the mouse pointer is hovering
            Point point = me.getPoint();
            int row = rowAtPoint(point);
            int column = columnAtPoint(point);
            // Check if a cell is beneath the mouse pointer
            if (row != -1 && column != -1) {
                // Expand any macros in the cell text and display this as the cell's tool tip
                // text
                toolTipText = macroHandler.getMacroToolTipText(getValueAt(row, column).toString());
            }
            return toolTipText;
        }

        /**
         ************************************************************************************
         * Return true if the table data, column order, description, or a data field changes.
         * If the table isn't open in and editor (as when a macro is changed) then the table
         * description and data fields are not applicable
         ************************************************************************************
         */
        @Override
        protected boolean isTableChanged(Object[][] previousData, List<Integer> ignoreColumns) {
            boolean isFieldChanged = false;
            // and/or value change)
            if (editorDialog != null) {
                // Update the field information with the current text field values
                updateCurrentFieldValues(tableInfo.getFieldHandler().getFieldInformation());
                // Set the flag if the number of fields, field attributes, or field contents
                // have changed
                isFieldChanged = CcddFieldHandler.isFieldChanged(tableInfo.getFieldHandler().getFieldInformation(), committedInfo.getFieldHandler().getFieldInformation(), false);
            }
            return super.isTableChanged(previousData, ignoreColumns) || isFieldChanged || !getColumnOrder().equals(committedInfo.getColumnOrder()) || (editorDialog != null && !committedInfo.getDescription().equals(getDescription()));
        }

        /**
         ************************************************************************************
         * Allow multiple line display in all columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnMultiLine(int column) {
            return true;
        }

        /**
         ************************************************************************************
         * Hide the the specified columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnHidden(int column) {
            return column == primaryKeyIndex || column == rowIndex;
        }

        /**
         ************************************************************************************
         * Override isCellEditable to determine which cells can be edited
         ************************************************************************************
         */
        @Override
        public boolean isCellEditable(int row, int column) {
            // Initialize the flag to the table edit flag (based on the table edit flag this
            // enables normal editing or disables editing any cell)
            boolean isEditable = isEditEnabled;
            // one row
            if (isEditable && (isDisplayable() || editorDialog == null) && tableModel != null && tableModel.getRowCount() != 0) {
                // Convert the view row and column indices to model coordinates
                int modelRow = convertRowIndexToModel(row);
                int modelColumn = convertColumnIndexToModel(column);
                // Check if the cell is editable
                isEditable = isDataAlterable(((List<?>) tableModel.getDataVector().elementAt(modelRow)).toArray(new String[0]), modelRow, modelColumn);
            }
            return isEditable;
        }

        /**
         ************************************************************************************
         * Override isDataAlterable to determine which table data values can be changed
         *
         * @param rowData
         *            array containing the table row data
         *
         * @param row
         *            table row index in model coordinates
         *
         * @param column
         *            table column index in model coordinates
         *
         * @return true if the data value can be changed
         ************************************************************************************
         */
        @Override
        protected boolean isDataAlterable(Object[] rowData, int row, int column) {
            boolean isAlterable = true;
            // Check if the table data has at least one row
            if (rowData != null && rowData.length != 0) {
                // Copy the row of table data. This prevents the macro expansions for array
                // size and bit length below from changing the cell contents
                Object[] rowCopy = Arrays.copyOf(rowData, rowData.length);
                // Check if the array size column is present in this table
                if (arraySizeIndex != -1) {
                    // Expand any macros in the array size column
                    rowCopy[arraySizeIndex] = newMacroHandler.getMacroExpansion(rowCopy[arraySizeIndex].toString());
                }
                // Check if the array size column is present in this table
                if (bitLengthIndex != -1) {
                    // Expand any macros in the array size column
                    rowCopy[bitLengthIndex] = newMacroHandler.getMacroExpansion(rowCopy[bitLengthIndex].toString());
                }
                // Flag that is true if the row represents an array definition
                boolean isArrayDefinition = arraySizeIndex != -1 && variableNameIndex != -1 && !rowCopy[arraySizeIndex].toString().isEmpty() && !ArrayVariable.isArrayMember(rowCopy[variableNameIndex]);
                // Check if the cell is non-alterable based on the following criteria:
                if (// this is not a prototype table
                ((column == variableNameIndex || column == dataTypeIndex || column == arraySizeIndex || column == bitLengthIndex) && !tableInfo.isPrototype()) || // displays an array member
                ((column == variableNameIndex || column == dataTypeIndex || column == arraySizeIndex) && ArrayVariable.isArrayMember(rowCopy[variableNameIndex])) || // isn't valid for structures
                (dataTypeIndex != -1 && !dataTypeHandler.isPrimitive(rowCopy[dataTypeIndex].toString()) && !typeDefn.isStructureAllowed()[column]) || // This data type is a pointer and the column isn't valid for pointers
                (dataTypeIndex != -1 && dataTypeHandler.isPointer(rowCopy[dataTypeIndex].toString()) && !typeDefn.isPointerAllowed()[column]) || // This is the enumeration or rate cell in a row displaying an array definition
                ((isArrayDefinition && enumerationIndex.contains(column) && rateIndex.contains(column))) || // type is not an integer (signed or unsigned)
                (column == bitLengthIndex && ((arraySizeIndex != -1 && !rowCopy[arraySizeIndex].toString().isEmpty()) || (dataTypeIndex != -1 && !dataTypeHandler.isInteger(rowCopy[dataTypeIndex].toString())))) || // length is present
                (column == arraySizeIndex && ((variableNameIndex != -1 && rowCopy[variableNameIndex].toString().isEmpty()) || (bitLengthIndex != -1 && !rowCopy[bitLengthIndex].toString().isEmpty()))) || // This is a rate cell, and a data type exists that is not a primitive
                (rateIndex.contains(column) && dataTypeIndex != -1 && !rowCopy[dataTypeIndex].toString().isEmpty() && !dataTypeHandler.isPrimitive(rowCopy[dataTypeIndex].toString())) || // first array member
                (variableNameIndex != -1 && dataTypeIndex != -1 && dataTypeHandler.isString(rowCopy[dataTypeIndex].toString()) && ArrayVariable.isArrayMember(rowCopy[variableNameIndex]) && !rowCopy[variableNameIndex].toString().endsWith("[0]")) || // the variable path
                (isArrayDefinition && (typeDefn.getInputTypes()[column].equals(InputDataType.MESSAGE_ID) || column == variablePathIndex)) || // variables in a non-root prototype structure
                (column == variablePathIndex && rowCopy[variablePathIndex].toString().isEmpty())) {
                    // Set the flag to prevent altering the data value
                    isAlterable = false;
                } else // Check the column groupings
                {
                    // Step through each column grouping
                    for (AssociatedColumns colGrp : associatedColumns) {
                        // Check if the cell is non-alterable based on the following criteria:
                        if ((// isn't an integer type (signed or unsigned)
                        column == colGrp.getEnumeration() && !dataTypeHandler.isInteger(rowCopy[colGrp.getDataType()].toString())) || // defined
                        ((column == colGrp.getMinimum() || column == colGrp.getMaximum()) && rowCopy[colGrp.getDataType()].toString().isEmpty())) {
                            // Set the flag to prevent altering the data value and stop
                            // searching
                            isAlterable = false;
                            break;
                        }
                    }
                    // Check if no command argument pairing reset the flag
                    if (isAlterable) {
                        // Step through each non-command argument minimum/maximum pairing
                        for (MinMaxPair minMax : minMaxPair) {
                            // is missing, empty, or isn't a primitive type
                            if (dataTypeIndex != -1 && (rowCopy[dataTypeIndex].toString().isEmpty() || !dataTypeHandler.isPrimitive(rowCopy[dataTypeIndex].toString())) && (column == minMax.getMinimum() || column == minMax.getMaximum())) {
                                // Set the flag to prevent altering the data value and stop
                                // searching
                                isAlterable = false;
                                break;
                            }
                        }
                    }
                }
            }
            return isAlterable;
        }

        /**
         ************************************************************************************
         * Override getCellEditor so that for a data type column cell in a row where the
         * enumeration cell isn't empty the combo box editor that displays only integer data
         * types (signed and unsigned) is returned; for all other cells return the normal cell
         * editor
         *
         * @param row
         *            table view row number
         *
         * @param column
         *            table view column number
         *
         * @return The cell editor for the specified row and column
         ************************************************************************************
         */
        @Override
        public TableCellEditor getCellEditor(int row, int column) {
            // Get the editor for this cell
            TableCellEditor cellEditor = super.getCellEditor(row, column);
            // Convert the row and column indices to the model coordinates
            int modelRow = convertRowIndexToModel(row);
            int modelColumn = convertColumnIndexToModel(column);
            // column and the bit length cell is not empty
            if (modelColumn == dataTypeIndex && bitLengthIndex != -1 && !getExpandedValueAt(modelRow, bitLengthIndex).isEmpty()) {
                // Select the combo box cell editor that displays only integer data types
                // (signed and unsigned)
                cellEditor = enumDataTypeCellEditor;
            } else // Check if this is a data type and enumeration pairing or a command argument
            // column grouping
            {
                // Step through each column grouping
                for (AssociatedColumns colGrp : associatedColumns) {
                    // type column, and that the associated enumeration cell isn't blank
                    if (modelColumn == colGrp.getDataType() && colGrp.getEnumeration() != -1 && !getExpandedValueAt(modelRow, colGrp.getEnumeration()).isEmpty()) {
                        // Select the combo box cell editor that displays only integer data
                        // types (signed and unsigned) and stop searching
                        cellEditor = enumDataTypeCellEditor;
                        break;
                    }
                }
            }
            return cellEditor;
        }

        /**
         ************************************************************************************
         * Validate changes to the editable cells; e.g., verify cell content and, if found
         * invalid, revert to the original value. Update array members if needed
         *
         * @param tableData
         *            list containing the table data row arrays
         *
         * @param row
         *            table model row index
         *
         * @param column
         *            table model column index
         *
         * @param oldValue
         *            original cell contents
         *
         * @param newValue
         *            new cell contents
         *
         * @param showMessage
         *            true to display the invalid input dialog, if applicable
         *
         * @param isMultiple
         *            true if this is one of multiple cells to be entered and checked; false if
         *            only a single input is being entered
         *
         * @return true to indicate that subsequent errors should be displayed; false if
         *         subsequent errors should not be displayed; null if the operation should be
         *         canceled
         ************************************************************************************
         */
        @Override
        protected Boolean validateCellContent(List<Object[]> tableData, int row, int column, Object oldValue, Object newValue, Boolean showMessage, boolean isMultiple) {
            // Reset the flag that indicates the last edited cell's content is invalid
            setLastCellValid(true);
            try {
                // Set the parameters that govern recalculating packed variables to begin with
                // the first row in the table and to use the first variable in the pack to set
                // the rates for other variables in the same pack
                int startRow = 0;
                boolean useRowRate = false;
                // Create a string version of the new value, replacing any macro in the text
                // with its corresponding value
                String newValueS = newMacroHandler.getMacroExpansion(newValue.toString(), validDataTypes);
                // to this structure's prototype or the prototype of one of its children
                if (variableHandler.isInvalidReference()) {
                    throw new CCDDException("Invalid input value in column '</b>" + typeDefn.getColumnNamesUser()[column] + "<b>'; data type invalid or unknown in sizeof() call");
                }
                // Check if the cell is flagged for replacement by the prototype value
                if (newValueS.startsWith(REPLACE_INDICATOR)) {
                    // Remove the flag so that the updated value is stored as a custom value
                    newValueS = newValueS.replaceFirst("^" + REPLACE_INDICATOR, "");
                }
                // Check that the new value isn't blank
                if (!newValueS.isEmpty()) {
                    // Check if the values in this column must not be duplicated
                    if (typeDefn.isRowValueUnique()[column]) {
                        // Step through each row in the table
                        for (int otherRow = 0; otherRow < tableData.size(); otherRow++) {
                            // matches the one being added (case insensitive)
                            if (otherRow != row && newValueS.equalsIgnoreCase(getExpandedValueAt(tableData, otherRow, column))) {
                                throw new CCDDException("Invalid input value for column '</b>" + typeDefn.getColumnNamesUser()[column] + "<b>'; value must be unique");
                            }
                        }
                    }
                    // Step through each column grouping
                    for (AssociatedColumns colGrp : associatedColumns) {
                        // Check if this is a name column
                        if (column == colGrp.getName()) {
                            // Step through the column groupings
                            for (AssociatedColumns otherColGrp : associatedColumns) {
                                // name matches the name of another command argument
                                if (!colGrp.equals(otherColGrp) && newValueS.equals(getExpandedValueAt(tableData, row, otherColGrp.getName()))) {
                                    throw new CCDDException("Invalid input value for column '</b>" + typeDefn.getColumnNamesUser()[column] + "<b>'; command argument names must be unique for a command");
                                }
                            }
                        } else // Check if this is the minimum or maximum value columns
                        if (column == colGrp.getMinimum() || column == colGrp.getMaximum()) {
                            // Verify that the minimum/maximum value is valid for the
                            // argument's data type, and stop searching
                            validateMinMaxContent(tableData, row, column, newValueS, colGrp.getDataType(), colGrp.getMinimum(), colGrp.getMaximum());
                            break;
                        }
                    }
                    // Step through each minimum/maximum pairing
                    for (MinMaxPair minMax : minMaxPair) {
                        // Check if this is the minimum or maximum value columns
                        if (column == minMax.getMinimum() || column == minMax.getMaximum()) {
                            // Verify that the minimum/maximum value is valid for the
                            // argument's data type, and stop searching
                            validateMinMaxContent(tableData, row, column, newValueS, dataTypeIndex, minMax.getMinimum(), minMax.getMaximum());
                            break;
                        }
                    }
                    // Check if the value doesn't match the expected input type
                    if (!newValueS.matches(typeDefn.getInputTypes()[column].getInputMatch())) {
                        throw new CCDDException("Invalid characters in column '</b>" + typeDefn.getColumnNamesUser()[column] + "<b>'; characters consistent with input type '" + typeDefn.getInputTypes()[column].getInputName() + "' expected");
                    }
                    // Check if this is a message ID name column
                    if (msgIDNameIndex.contains(column)) {
                        // The message ID, which is included with the ID name in the combo box
                        // list, doesn't appear when the item is selected from the list, so
                        // remove the ID
                        newValueS = newValueS.replaceFirst(" \\(.*", "");
                    }
                }
                // Flag that indicates that the new cell value contains a macro and/or a
                // sizeof() call
                boolean hasMacroSizeof = CcddMacroHandler.hasMacro(newValue.toString()) || CcddVariableSizeAndConversionHandler.hasSizeof(newValue.toString());
                // prevents the macro reference from being lost
                if (!hasMacroSizeof) {
                    // Store the new value in the table data array after formatting the cell
                    // value per its input type. This is needed primarily to clean up numeric
                    // formatting
                    newValueS = typeDefn.getInputTypes()[column].formatInput(newValueS);
                    newValue = newValueS;
                    tableData.get(row)[column] = newValueS;
                }
                // Replace any macro in the original text with its corresponding value
                String oldValueS = macroHandler.getMacroExpansion(oldValue.toString());
                // original text with its corresponding value
                if (!newValueS.equals(oldValueS)) {
                    String variableName = null;
                    String dataType = null;
                    String arraySize = null;
                    String bitLength = null;
                    // Check if the variable name column exists
                    if (variableNameIndex != -1) {
                        // Get the variable name for the current row, expanding macros in the
                        // name (if present)
                        variableName = getExpandedValueAt(tableData, row, variableNameIndex);
                    }
                    // Check if the data type column exists
                    if (dataTypeIndex != -1) {
                        // Get the data type for the current row
                        dataType = tableData.get(row)[dataTypeIndex].toString();
                    }
                    // Check if the array size column exists
                    if (arraySizeIndex != -1) {
                        // Get the array size for the current row, expanding macros in the
                        // value (if present)
                        arraySize = getExpandedValueAt(tableData, row, arraySizeIndex);
                    }
                    // Check if the bit length column exists
                    if (bitLengthIndex != -1) {
                        // Get the bit length for the current row, expanding macros in the
                        // value (if present)
                        bitLength = getExpandedValueAt(tableData, row, bitLengthIndex);
                    }
                    // Check if the variable name or data type has been changed
                    if (column == variableNameIndex || column == dataTypeIndex) {
                        // if a data type is pasted into the cell
                        if (invalidDataTypes != null && invalidDataTypes.contains(dataType)) {
                            throw new CCDDException("Data type '</b>" + dataType + "<b>' invalid; structure cannot reference itself or an ancestor");
                        }
                        // Check if the variable is an array
                        if (arraySize != null) {
                            // and the bit length exceeds the size of the data type in bits
                            if (column == dataTypeIndex && !newValueS.isEmpty() && bitLength != null && !bitLength.isEmpty() && Integer.valueOf(bitLength) > newDataTypeHandler.getSizeInBits(dataType)) {
                                throw new CCDDException("Bit length exceeds the size of the data type");
                            }
                            // Get the array index values from the array size column and update
                            // array members if this is an array definition
                            int[] arrayDims = ArrayVariable.getArrayIndexFromSize(arraySize);
                            adjustArrayMember(tableData, arrayDims, arrayDims, row, column);
                        }
                        // manually set
                        if (variablePathIndex != -1 && tableData.get(row)[variablePathIndex].toString().equals(getVariablePath((column == variableNameIndex ? oldValueS : variableName), (column == dataTypeIndex ? oldValueS : dataType), true))) {
                            // Update the variable path with the new variable name and/or data
                            // type
                            tableData.get(row)[variablePathIndex] = getVariablePath(variableName, dataType, true);
                        }
                    } else // Check if this is the array size column
                    if (column == arraySizeIndex) {
                        // Get the original and updated array index values
                        int[] arraySizeOld = ArrayVariable.getArrayIndexFromSize(oldValueS);
                        int[] arraySizeNew = ArrayVariable.getArrayIndexFromSize(newValueS);
                        // Set the flag that indicates the array index values changed based on
                        // the number of index values changing
                        boolean isDifferent = arraySizeOld.length != arraySizeNew.length;
                        // individual array index values must be compared
                        if (!isDifferent) {
                            // Step through each index value
                            for (int index = 0; index < arraySizeOld.length; index++) {
                                // Check if the original and updated values differ
                                if (arraySizeOld[index] != arraySizeNew[index]) {
                                    // Set the flag to indicate an index value changed and stop
                                    // searching
                                    isDifferent = true;
                                    break;
                                }
                            }
                        }
                        // Check if the original and updated values differ
                        if (isDifferent) {
                            // Add or remove array members to match the new array size
                            adjustArrayMember(tableData, arraySizeOld, arraySizeNew, row, column);
                        }
                    } else // Check if this is the rate column and the row is an array definition
                    if (rateIndex.contains(column) && arraySize != null && variableName != null && !arraySize.isEmpty() && !ArrayVariable.isArrayMember(variableName)) {
                        // Get the array index value(s)
                        int[] arrayDims = ArrayVariable.getArrayIndexFromSize(arraySize);
                        // Update the array members with the new rate
                        adjustArrayMember(tableData, arrayDims, arrayDims, row, column);
                    } else // Check if this is the rate column and the variable has a bit length value
                    if (rateIndex.contains(column) && bitLength != null && dataType != null && !bitLength.isEmpty()) {
                        // Adjust the rates of any other bit-wise variables that are packed
                        // together with this variable, using this row's rate
                        startRow = row;
                        useRowRate = true;
                    } else // Check if this is the bit length column
                    if (column == bitLengthIndex) {
                        // data type
                        if (bitLength != null && !bitLength.isEmpty() && dataType != null && Integer.valueOf(bitLength) > newDataTypeHandler.getSizeInBits(dataType)) {
                            throw new CCDDException("Bit length exceeds the size of the data type");
                        }
                        // Adjust the rates of any other bit-wise variables that are packed
                        // together with this variable, using the first packed variable's rate
                        startRow = row;
                    } else // Check if this is the variable path column
                    if (column == variablePathIndex && variableName != null && !variableName.isEmpty() && dataType != null && !dataType.isEmpty()) {
                        // entered
                        if (!newValueS.isEmpty()) {
                            // another structure table
                            if (variableHandler.isVariablePathInUse(tableInfo.getTablePath() + "," + dataType + "." + variableName, newValueS)) {
                                throw new CCDDException("Variable path already in use in another structure");
                            }
                        } else // The cell has been blanked
                        {
                            // Build the variable path from the variable name and data type
                            tableData.get(row)[variablePathIndex] = getVariablePath(variableName, dataType, false);
                        }
                    } else // array
                    if (variableName != null && dataType != null && arraySize != null && column != variableNameIndex && column != dataTypeIndex && column != arraySizeIndex && column != variablePathIndex && !arraySize.isEmpty() && (!ArrayVariable.isArrayMember(variableName) || newDataTypeHandler.isString(dataType))) {
                        // Propagate the value to all members of this array/string
                        propagateArrayValues(tableData, row, column);
                    }
                    // Clear the contents of any cells that are no longer valid in this row
                    clearInvalidCells(tableData.get(row), row);
                    // Adjust the rates of the bit-wise variables that are packed together,
                    // beginning at the indicated row
                    setAllPackedVariableRates(tableData, startRow, useRowRate);
                    // Check if the new value contains any macros
                    if (hasMacroSizeof) {
                        // Store the new value, with the macro(s) restored, into the table data
                        // array
                        tableData.get(row)[column] = newValue;
                    }
                } else // vice versa)
                if (!newValue.equals(oldValue)) {
                    // Store the new value in the table data array
                    tableData.get(row)[column] = newValue;
                    // Check if the column is the array size
                    if (column == arraySizeIndex) {
                        // Propagate the value to all members of this array/string
                        propagateArrayValues(tableData, row, column);
                    }
                } else // The cell value didn't change
                {
                    // Pop the edit from the stack
                    table.getUndoManager().undoRemoveEdit();
                }
            } catch (CCDDException ce) {
                // Set the flag that indicates the last edited cell's content is invalid
                setLastCellValid(false);
                // Check if the error message dialog should be displayed
                if (showMessage) {
                    // Check if this is a single cell insert
                    if (!isMultiple) {
                        // Inform the user that the input value is invalid
                        new CcddDialogHandler().showMessageDialog(editorDialog, "<html><b>" + ce.getMessage(), "Invalid Input", JOptionPane.WARNING_MESSAGE, DialogOption.OK_OPTION);
                    } else // This is one of multiple cells being inserted
                    {
                        // Inform the user that the input value is invalid
                        CcddDialogHandler validityDlg = new CcddDialogHandler();
                        int buttonSelected = validityDlg.showIgnoreCancelDialog(editorDialog, "<html><b>" + ce.getMessage(), "Invalid Input", "Ignore this invalid input", "Ignore this and any remaining invalid inputs for this table", "Cease inputting values");
                        // Check if the Ignore All button was pressed
                        if (buttonSelected == IGNORE_BUTTON) {
                            // Set the flag to ignore subsequent input errors
                            showMessage = false;
                        } else // Check if the Cancel button was pressed
                        if (buttonSelected == CANCEL_BUTTON) {
                            // Set the flag to cancel updating the cells
                            showMessage = null;
                        }
                    }
                }
                // Restore the cell contents to its original value and pop the edit from the
                // stack
                tableData.get(row)[column] = oldValue;
                table.getUndoManager().undoRemoveEdit();
            }
            return showMessage;
        }

        /**
         ************************************************************************************
         * Clear the contents of cells in the specified row that are no longer valid due to the
         * contents of other cells
         *
         * @param tableData
         *            list containing the table data row arrays
         *
         * @param row
         *            table model row index
         ************************************************************************************
         */
        private void clearInvalidCells(Object[] rowData, int row) {
            // Step through each visible column
            for (int column = 0; column < getColumnCount(); column++) {
                // Get the column index in model coordinates
                int modelColumn = convertColumnIndexToModel(column);
                if (// rate, or variable path column, and that the cell is not alterable
                (modelColumn != variableNameIndex && modelColumn != dataTypeIndex && modelColumn != arraySizeIndex && modelColumn != bitLengthIndex && modelColumn != variablePathIndex && !rateIndex.contains(modelColumn) && !isDataAlterable(rowData, row, modelColumn)) || // (i.e., it's a structure), and structures are not allowed for this column
                (dataTypeIndex != -1 && !newDataTypeHandler.isPrimitive(rowData[dataTypeIndex].toString()) && !typeDefn.isStructureAllowed()[modelColumn]) || // pointers are not allowed for this column
                (dataTypeIndex != -1 && newDataTypeHandler.isPointer(rowData[dataTypeIndex].toString()) && !typeDefn.isPointerAllowed()[modelColumn]) || // data type isn't a primitive
                (dataTypeIndex != -1 && modelColumn == bitLengthIndex && !newDataTypeHandler.isPrimitive(rowData[dataTypeIndex].toString()))) {
                    // Clear the contents of the cell
                    rowData[modelColumn] = "";
                }
            }
        }

        /**
         ************************************************************************************
         * Load the database values into the table and format the table cells
         ************************************************************************************
         */
        @Override
        protected void loadAndFormatData() {
            // Place the data into the table model along with the column names, set up the
            // editors and renderers for the table cells, set up the table grid lines, and
            // calculate the minimum width required to display the table information
            int totalWidth = setUpdatableCharacteristics(committedInfo.getData(), typeDefn.getColumnNamesUser(), committedInfo.getColumnOrder(), toolTips, true, true, true);
            // editor dialog
            if (editorDialog != null) {
                // Get the minimum width needed to display all columns, but no wider than the
                // display
                int width = Math.min(totalWidth + LAF_SCROLL_BAR_WIDTH, GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth());
                // Check if the editor's width is less than the minimum
                if (editorDialog.getTableWidth() < width) {
                    // Set the initial and preferred editor size
                    editorDialog.setTableWidth(width);
                    editorDialog.setPreferredSize(new Dimension(width, editorDialog.getPreferredSize().height));
                }
            }
            // Create the drop-down combo box for the column with the name 'data type' that
            // displays the available data types, including primitive types and the names of
            // tables that represent structures, and add a mouse listener to handle mouse click
            // events. Set up any command argument data type, argument name, enumeration,
            // minimum, and maximum groupings
            setUpDataTypeColumns(null, null);
            // Set up any minimum and maximum pairings (excluding those associated with command
            // argument groupings)
            setUpMinMaxColumns();
            // Create drop-down combo boxes that display the available sample rates for the
            // "Rate" column
            setUpSampleRateColumn();
            // Create drop-down combo boxes that display the available message ID names and
            // values
            setUpMsgNamesAndIDsColumn(null);
            // Create the mouse listener for the data type column
            createDataTypeColumnMouseListener();
        }

        /**
         ************************************************************************************
         * Override prepareRenderer to allow adjusting the background colors of table cells
         ************************************************************************************
         */
        @Override
        public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
            JComponent comp = (JComponent) super.prepareRenderer(renderer, row, column);
            // highlight colors override the invalid highlight color
            if (comp.getBackground() != ModifiableColorInfo.FOCUS_BACK.getColor() && comp.getBackground() != ModifiableColorInfo.SELECTED_BACK.getColor()) {
                // contain an item in the list
                if (!isCellValueFound(row, column)) {
                    // Change the cell's background color
                    comp.setBackground(ModifiableColorInfo.REQUIRED_BACK.getColor());
                } else // Check if this cell is protected from changes
                if (!isCellEditable(row, column)) {
                    // Change the cell's text and background colors
                    comp.setForeground(ModifiableColorInfo.PROTECTED_TEXT.getColor());
                    comp.setBackground(ModifiableColorInfo.PROTECTED_BACK.getColor());
                } else // variable
                if (variableNameIndex != -1 && getExpandedValueAt(table.convertRowIndexToModel(row), variableNameIndex).toString().matches(PAD_VARIABLE + "[0-9]+(?:\\[[0-9]+\\])?$")) {
                    // Change the cell's background color
                    comp.setBackground(ModifiableColorInfo.PADDING_BACK.getColor());
                }
            }
            return comp;
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method to handle right double click events on the
         * array size cells in order to show/hide the array index column and array member rows,
         * and to handle sorting of columns based on their input type
         ************************************************************************************
         */
        @Override
        protected void setTableSortable() {
            super.setTableSortable();
            // Get the table's row sorter
            TableRowSorter<?> sorter = (TableRowSorter<?>) getRowSorter();
            // Create a runnable object to be executed
            SwingUtilities.invokeLater(new Runnable() {

                /**
                 ****************************************************************************
                 * Execute after all pending Swing events are finished. This allows the number
                 * of viewable columns to catch up with the column model when a column is
                 * removed
                 ****************************************************************************
                 */
                @Override
                public void run() {
                    // Issue a table change event so that the new row is displayed properly
                    // when the array view is collapsed. Can't use tableModel here since it
                    // isn't set when the first call to this method is made
                    ((UndoableTableModel) table.getModel()).fireTableStructureChanged();
                }
            });
            // Check if the table has a sorter (i.e., has at least one row)
            if (sorter != null) {
                // filter
                if (sorter.getRowFilter() == null && rowFilter != null) {
                    // Apply the row filter that shows/hides the array members
                    sorter.setRowFilter(rowFilter);
                }
                // Step through each table column
                for (int column = 0; column < table.getModel().getColumnCount(); column++) {
                    // Get the input type format for this column
                    final InputTypeFormat inputFormat = typeDefn.getInputTypes()[column].getInputFormat();
                    // Add a column sort comparator
                    sorter.setComparator(column, new Comparator<String>() {

                        /**
                         ********************************************************************
                         * Override the comparison when sorting columns to account for the
                         * column's input type format. Note that macros aren't expanded when
                         * sorting. Though expansion provides an accurate sort, visually it's
                         * confusing since the macro values aren't readily apparent. For
                         * columns with a numeric input type that contain macros then initial
                         * numeric portion (if any) if used when sorting
                         ********************************************************************
                         */
                        @Override
                        public int compare(String cell1, String cell2) {
                            Integer result = 0;
                            // Check if either cell is empty
                            if (cell1.isEmpty() || cell2.isEmpty()) {
                                // Compare as text (alphabetically)
                                result = cell1.compareTo(cell2);
                            } else // Neither cell is empty
                            {
                                // type
                                switch(inputFormat) {
                                    case TEXT:
                                    case DATA_TYPE:
                                    case ENUMERATION:
                                    case PAGE_FORMAT:
                                    case VARIABLE_PATH:
                                    case BOOLEAN:
                                        // Compare as text (alphabetically)
                                        result = cell1.compareTo(cell2);
                                        break;
                                    default:
                                        // number
                                        if (cell1.matches(InputDataType.INTEGER.getInputMatch() + ".*") && cell2.matches(InputDataType.INTEGER.getInputMatch() + ".*")) {
                                            switch(inputFormat) {
                                                case INTEGER:
                                                    // Compare the two cell values as integers
                                                    result = Integer.compare(Integer.valueOf(cell1.replaceAll("(" + InputDataType.INTEGER.getInputMatch() + ").*", "$1")), Integer.valueOf(cell2.replaceAll("(" + InputDataType.INTEGER.getInputMatch() + ").*", "$1")));
                                                    break;
                                                case HEXADECIMAL:
                                                    // Compare the two cell values as integers
                                                    result = Integer.compare(Integer.decode(cell1.replaceAll("(" + InputDataType.HEXADECIMAL.getInputMatch() + ").*", "$1")), Integer.decode(cell2.replaceAll("(" + InputDataType.HEXADECIMAL.getInputMatch() + ").*", "$1")));
                                                    break;
                                                case FLOAT:
                                                case MINIMUM:
                                                case MAXIMUM:
                                                    // Compare the two cell values as floating
                                                    // points
                                                    result = Double.compare(Double.valueOf(cell1.replaceAll("(" + InputDataType.FLOAT.getInputMatch() + ").*", "$1")), Double.valueOf(cell2.replaceAll("(" + InputDataType.FLOAT.getInputMatch() + ").*", "$1")));
                                                    break;
                                                case RATE:
                                                    // Calculate the value of the cells'
                                                    // expressions, then compare the results as
                                                    // floating point values
                                                    result = Double.compare(CcddMathExpressionHandler.evaluateExpression(cell1), CcddMathExpressionHandler.evaluateExpression(cell2));
                                                    break;
                                                case ARRAY:
                                                    // Array sizes are in the format #<,#<...>.
                                                    // Each cell's array dimensions are first
                                                    // separated, then the first dimension is
                                                    // compared between the two cells, then the
                                                    // second, and so on until a mismatch is
                                                    // found; the sort is performed based on
                                                    // the mismatch (e.g., '1, 2' follows '1'
                                                    // when sorted in ascending order)
                                                    String[] dim1 = cell1.split("\\s*,\\s*");
                                                    String[] dim2 = cell2.split("\\s*,\\s*");
                                                    // same dimensions than the second
                                                    if (dim1.length == dim2.length) {
                                                        // long as there's no mismatch
                                                        for (int index = 0; index < dim1.length && result == 0; index++) {
                                                            // sizeof() call)
                                                            if (dim1[index].matches(InputDataType.INTEGER.getInputMatch()) && dim2[index].matches(InputDataType.INTEGER.getInputMatch())) {
                                                                // Compare the two array
                                                                // dimensions
                                                                result = Integer.compare(Integer.valueOf(dim1[index]), Integer.valueOf(dim2[index]));
                                                            } else // One or both dimension values
                                                            // isn't a number
                                                            {
                                                                // Compare as text
                                                                // (alphabetically)
                                                                result = dim1[index].compareTo(dim2[index]);
                                                            }
                                                        }
                                                    } else // fewer dimensions than the second
                                                    if (dim1.length < dim2.length) {
                                                        // long as there's no mismatch
                                                        for (int index = 0; index < dim1.length && result == 0; index++) {
                                                            // sizeof() call)
                                                            if (dim1[index].matches(InputDataType.INTEGER.getInputMatch()) && dim2[index].matches(InputDataType.INTEGER.getInputMatch())) {
                                                                // Compare the two array
                                                                // dimensions
                                                                result = Integer.compare(Integer.valueOf(dim1[index]), Integer.valueOf(dim2[index]));
                                                            } else // One or both dimension values
                                                            // isn't a number
                                                            {
                                                                // Compare as text
                                                                // (alphabetically)
                                                                result = dim1[index].compareTo(dim2[index]);
                                                            }
                                                        }
                                                        // dimension values are identical
                                                        if (result == 0) {
                                                            // Set the result to indicate the
                                                            // first cell comes before the
                                                            // second since the second has more
                                                            // dimensions
                                                            result = -1;
                                                        }
                                                    } else // The first array size has the more
                                                    // dimensions than the second
                                                    {
                                                        // long as there's no mismatch
                                                        for (int index = 0; index < dim2.length && result == 0; index++) {
                                                            // sizeof() call)
                                                            if (dim1[index].matches(InputDataType.INTEGER.getInputMatch()) && dim2[index].matches(InputDataType.INTEGER.getInputMatch())) {
                                                                // Compare the two array
                                                                // dimensions
                                                                result = Integer.compare(Integer.valueOf(dim1[index]), Integer.valueOf(dim2[index]));
                                                            } else // One or both dimension values
                                                            // isn't a number
                                                            {
                                                                // Compare as text
                                                                // (alphabetically)
                                                                result = dim1[index].compareTo(dim2[index]);
                                                            }
                                                        }
                                                        // dimension values are identical
                                                        if (result == 0) {
                                                            // Set the result to indicate the
                                                            // first cell comes after the
                                                            // second since the second has
                                                            // fewer dimensions
                                                            result = 1;
                                                        }
                                                    }
                                                    break;
                                                default:
                                                    break;
                                            }
                                        } else // One or both cells doesn't begin with a number (this
                                        // is the case if the cell begins with a macro or
                                        // sizeof() call)
                                        {
                                            // Compare as text (alphabetically)
                                            result = cell1.compareTo(cell2);
                                        }
                                }
                            }
                            return result;
                        }
                    });
                }
            }
        }

        /**
         ************************************************************************************
         * Move the selected row(s) in the specified direction if possible. Account for if the
         * selection or target is an array definition or member
         *
         * @param rowDelta
         *            row move direction (-1 for up, +1 for down)
         ************************************************************************************
         */
        private void adjustAndMoveSelectedRows(int rowDelta) {
            int modelStartRow;
            int modelEndRow;
            boolean isCanMove = false;
            // Set the selected start and end rows
            MoveCellSelection selected = new MoveCellSelection();
            // Set the selected start and end rows (model coordinates), and the direction to
            // move
            modelStartRow = selected.getStartRow();
            modelEndRow = selected.getEndRow();
            // Check if the selected row(s) can be moved in the desired direction
            if ((rowDelta < 0 && modelStartRow > 0) || (rowDelta > 0 && modelEndRow < tableModel.getRowCount() - 1)) {
                // Check if the table can display arrays
                if (isCanHaveArrays()) {
                    // While the start row references an array member
                    while (ArrayVariable.isArrayMember(getExpandedValueAt(modelStartRow, variableNameIndex))) {
                        // Decrement the start index to get to the array definition row
                        modelStartRow--;
                    }
                    // Check if the selected ending row references an array definition
                    if (!getExpandedValueAt(modelEndRow, arraySizeIndex).isEmpty() && !ArrayVariable.isArrayMember(getExpandedValueAt(modelEndRow, variableNameIndex))) {
                        // Increment the end row so that the members will be included below
                        modelEndRow++;
                    }
                    boolean isIncludeMember = false;
                    // model hasn't been reached
                    while (modelEndRow < tableModel.getRowCount() && ArrayVariable.isArrayMember(getExpandedValueAt(modelEndRow, variableNameIndex))) {
                        // Increment the end index to get to the end of the array
                        modelEndRow++;
                        isIncludeMember = true;
                    }
                    // Check if the ending row was adjusted to include an array member
                    if (isIncludeMember) {
                        // Decrement the row index since the row selection is inclusive
                        modelEndRow--;
                    }
                    // Check if the selected row(s) can be moved in the desired direction
                    if ((rowDelta < 0 && modelStartRow > 0) || (rowDelta > 0 && modelEndRow < tableModel.getRowCount() - 1)) {
                        // Get the array size column value for the target row
                        String arraySize = getExpandedValueAt((rowDelta < 0 ? modelStartRow : modelEndRow) + rowDelta, arraySizeIndex);
                        // Check if the array size is present on this row
                        if (!arraySize.isEmpty()) {
                            // Get the total number of array members
                            int totalSize = ArrayVariable.getNumMembersFromArraySize(arraySize);
                            // Adjust the number of rows to move based on the number of array
                            // members
                            rowDelta += totalSize * rowDelta;
                        }
                        // Set the flag to indicate the selected row(s) can be moved
                        isCanMove = true;
                    }
                } else // The table can't have an array
                {
                    // Set the flag to indicate the selected row(s) can be moved
                    isCanMove = true;
                }
                // Calculate the row that the selected row(s) will be moved to
                int modelToRow = modelStartRow + rowDelta;
                // Check if the selected row(s) can be moved
                if (isCanMove) {
                    // Move the row(s) in the specified direction and update the cell selection
                    performRowMove(modelStartRow, modelEndRow, modelToRow, selected, rowDelta);
                }
            }
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for moving the selected row(s) up one row in
         * order to prevent moving a row within an array definition and its member rows;
         * instead skip past the array
         ************************************************************************************
         */
        @Override
        protected void moveRowUp() {
            // Move the selected row(s) up if possible
            adjustAndMoveSelectedRows(-1);
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for moving the selected row(s) down one row in
         * order to prevent moving a row within an array definition and its member rows;
         * instead skip past the array
         ************************************************************************************
         */
        @Override
        protected void moveRowDown() {
            // Move the selected row(s) down if possible
            adjustAndMoveSelectedRows(1);
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for moving the selected row(s) so that
         * adjustments can be made to the rates for any packed variables
         *
         * @param startRow
         *            selected starting row, in model coordinates
         *
         * @param endRow
         *            selected ending row, in model coordinates
         *
         * @param toRow
         *            target row to move the selected row(s) to, in model coordinates
         *
         * @param selected
         *            cell selection class
         *
         * @param rowDelta
         *            row move direction and magnitude
         ************************************************************************************
         */
        @Override
        protected void performRowMove(int startRow, int endRow, int toRow, MoveCellSelection selected, int rowDelta) {
            // Move the row(s)
            super.performRowMove(startRow, endRow, toRow, selected, rowDelta);
            // Check if this is a parent structure table
            if (tableInfo.isRootStructure()) {
                // Load the table data into a list
                List<Object[]> tableData = getTableDataList(false);
                // Adjust the rate for any packed variables, beginning with the lowest affected
                // row index
                setAllPackedVariableRates(tableData, Math.min(startRow, toRow), false);
                // Check if a rate value changed
                if (isRateChange) {
                    // Load the array of data into the table
                    loadDataArrayIntoTable(tableData.toArray(new Object[0][0]), true);
                }
            }
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for putting data into a new row inserted below
         * the specified row in order to adjust the insertion index based on the presence of
         * array members
         *
         * @param targetRow
         *            index of the row in model coordinates below which to insert the new row
         *
         * @param data
         *            data to place in the inserted row
         *
         * @return The new row's index, in model coordinates, adjusted as needed to account for
         *         array member visibility
         ************************************************************************************
         */
        @Override
        protected int insertRowData(int targetRow, Object[] data) {
            // Check if table has rows, and has variable name and array size columns
            if (targetRow != -1 && isCanHaveArrays()) {
                // Get the array size value
                String arraySize = getExpandedValueAt(targetRow, arraySizeIndex);
                // (i.e., this is the array definition row)
                if (!arraySize.isEmpty() && !ArrayVariable.isArrayMember(getExpandedValueAt(targetRow, variableNameIndex))) {
                    // Adjust the row index past the array definition and member rows
                    targetRow += ArrayVariable.getNumMembersFromArraySize(arraySize);
                } else // Check if the array members are set to be displayed
                if (isShowArrayMembers) {
                    boolean isIndex = false;
                    // While the selection row is on an array member
                    while (targetRow < tableModel.getRowCount() && ArrayVariable.isArrayMember(getExpandedValueAt(targetRow, variableNameIndex))) {
                        // Skip the array member row
                        targetRow++;
                        isIndex = true;
                    }
                    // Check if an array member was skipped
                    if (isIndex) {
                        // Decrement the row index
                        targetRow--;
                    }
                }
            }
            // Insert the supplied data below the selected row
            return super.insertRowData(targetRow, data);
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for removing a row from the table. Array
         * member rows are ignored unless the array definition row is also deleted; for this
         * case the entire array is removed
         *
         * @param tableData
         *            list containing the table data row arrays
         *
         * @param modelRow
         *            row to remove (model coordinates)
         *
         * @return The index of the row prior to the last deleted row's index
         ************************************************************************************
         */
        @Override
        protected int removeRow(List<Object[]> tableData, int modelRow) {
            boolean isArray = false;
            // Check if the table has array size and variable name columns
            if (isCanHaveArrays()) {
                // Extract the array size cell value
                String arraySize = getExpandedValueAt(modelRow, arraySizeIndex);
                // Check if an array size is present
                if (!arraySize.isEmpty()) {
                    // Set the flag indicating that an array row is being removed
                    isArray = true;
                    // Perform while this row is an array member
                    while (ArrayVariable.isArrayMember(tableData.get(modelRow)[variableNameIndex])) {
                        // Move the row index up
                        modelRow--;
                    }
                    // Get the row index of the last array member
                    int arrayRow = modelRow + ArrayVariable.getNumMembersFromArraySize(arraySize);
                    // Step through each member of the array
                    while (arrayRow >= modelRow) {
                        // Delete the row
                        tableData.remove(modelRow);
                        // Go to the next array member row to remove
                        arrayRow--;
                    }
                }
            }
            // Check if the row does not represent an array definition or member
            if (!isArray) {
                // Delete the row
                super.removeRow(tableData, modelRow);
            }
            // Adjust the rate for any packed variables, beginning with this row
            setAllPackedVariableRates(tableData, convertRowIndexToView(modelRow), false);
            return modelRow - 1;
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for getting the special replacement character
         * when deleting the contents of a cell. Get the corresponding cell value from the
         * table's prototype
         *
         * @param row
         *            cell row index in model coordinates
         *
         * @param column
         *            cell column index in model coordinates
         *
         * @return The corresponding cell value from the tables' prototype
         ************************************************************************************
         */
        @Override
        protected String getSpecialReplacement(int row, int column) {
            return dbTable.queryTableCellValue(tableInfo.getPrototypeName(), committedInfo.getData()[row][primaryKeyIndex], typeDefn.getColumnNamesDatabase()[column], editorDialog);
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method for deleting a cell. Set the special character
         * flag to false if the table is a prototype - prototypes can't have an entry in the
         * custom values table so no special handling is needed for this case
         *
         * @param isReplaceSpecial
         *            false to replace the cell value with a blank; true to replace the cell
         *            contents with the prototype's corresponding cell value
         ************************************************************************************
         */
        @Override
        protected void deleteCell(boolean isReplaceSpecial) {
            super.deleteCell(isReplaceSpecial && !tableInfo.isPrototype());
        }

        /**
         ************************************************************************************
         * Adjust the starting row index to the next row during a paste (insert) operation. If
         * the insertion point falls within an array, skip to the row immediately following the
         * array's members
         *
         * @param startRow
         *            starting row index in view coordinates
         *
         * @return Starting row index, in model coordinates, at which to insert a new row
         ************************************************************************************
         */
        protected int adjustPasteStartRow(int startRow) {
            // Check if the starting row index references a valid row
            if (startRow >= 0 && startRow < getRowCount()) {
                // Convert the row index to model coordinates and adjust the starting row index
                // to the next row
                startRow = convertRowIndexToModel(startRow) + 1;
            } else // The starting index is not a valid row
            {
                // Set the starting row index to the end of the table
                startRow = tableModel.getRowCount();
            }
            // variable size and array name columns, and an array size is present
            if (startRow < tableModel.getRowCount() && isCanHaveArrays() && !getExpandedValueAt(startRow, arraySizeIndex).isEmpty()) {
                // been reached
                while (startRow < tableModel.getRowCount() && ArrayVariable.isArrayMember(getExpandedValueAt(startRow, variableNameIndex))) {
                    // Adjust the row index to the next row
                    startRow++;
                }
            }
            return startRow;
        }

        /**
         ************************************************************************************
         * Determine if a row insertion is required during a paste operation. Array member rows
         * are inserted automatically when an array is defined, so if an array member is being
         * inserted no row needs to be inserted by the paste operation
         *
         * @param index
         *            current index into the cell data array
         *
         * @param cellData
         *            array containing the cell data being inserted
         *
         * @param startColumn
         *            data insertion starting column index
         *
         * @param endColumn
         *            data insertion ending column index
         *
         * @return true if a row should be inserted; false otherwise
         ************************************************************************************
         */
        protected boolean isInsertRowRequired(int index, Object[] cellData, int startColumn, int endColumn) {
            boolean isNotArrayMember = true;
            // Get the variable name column index in view coordinates
            int variableNameIndexView = convertColumnIndexToView(variableNameIndex);
            // Step through the row of data being inserted
            for (int column = startColumn; column <= endColumn; column++, index++) {
                // Check if the column index matches the variable name column
                if (column == variableNameIndexView) {
                    // Check if the variable name cell has a value and is an array member
                    if (cellData[index] == null || ArrayVariable.isArrayMember(cellData[index])) {
                        // Set the flag to indicate a new row doesn't need to be inserted
                        isNotArrayMember = false;
                    }
                    // Stop searching
                    break;
                }
            }
            return isNotArrayMember;
        }

        /**
         ************************************************************************************
         * Override the paste method so that hidden rows (array members) are displayed prior to
         * pasting in new data
         ************************************************************************************
         */
        @Override
        protected boolean pasteData(Object[] cellData, int numColumns, boolean isInsert, boolean isAddIfNeeded, boolean startFirstColumn, boolean combineAsSingleEdit) {
            Boolean showMessage = true;
            // Check if the pasted data should be combined into a single edit operation
            if (combineAsSingleEdit) {
                // End any active edit sequence, then disable auto-ending so that the paste
                // operation can be handled as a single edit for undo/redo purposes
                getUndoManager().endEditSequence();
                getUndoHandler().setAutoEndEditSequence(false);
            }
            // Get the table data array
            List<Object[]> tableData = getTableDataList(false);
            // Calculate the number of rows to be pasted in
            int numRows = cellData.length / numColumns;
            // Initialize the starting row to the first row, which is the default if no row is
            // selected
            int startRow = 0;
            // Check if no row is selected
            if (getSelectedRow() == -1) {
                // Clear the column selection. The column selection can remain in effect after
                // an undo action that clears the row selection. It needs to be cleared if
                // invalid so that the starting column index is correctly calculated below
                getColumnModel().getSelectionModel().clearSelection();
            } else // A row is selected
            {
                // Determine the starting row for pasting the data based on the selected row
                startRow = convertRowIndexToModel(getSelectedRow()) + getSelectedRowCount() - 1;
            }
            // Determine the starting column and ending column for pasting the data. If no
            // column is selected then default to the first column. Data pasted outside of the
            // column range is ignored
            int startColumn = startFirstColumn ? 0 : Math.max(Math.max(getSelectedColumn(), 0), getSelectedColumn() + getSelectedColumnCount() - 1);
            int endColumn = startColumn + numColumns - 1;
            int endColumnSelect = Math.min(endColumn, getColumnCount() - 1);
            // members are hidden
            if (isCanHaveArrays() && !isShowArrayMembers) {
                // Show the array members. All rows must be visible in order for the pasted
                // data to be inserted correctly. The model and view row coordinates are the
                // same after expanding the array members. Note that this clears the row and
                // column selection
                showHideArrayMembers();
            }
            // Check if the data is to be inserted versus overwriting existing cells
            if (isInsert) {
                // Adjust the starting row index to the one after the selected row, and account
                // for hidden rows, if applicable
                startRow = adjustPasteStartRow(startRow);
            } else // contains no rows
            if (startRow == -1) {
                // Set the start row to the first row
                startRow = 0;
            }
            // Determine the ending row for pasting the data
            int endRow = startRow + numRows;
            // Clear the cell selection
            clearSelection();
            // Counters for the number of array member rows added (due to pasting in an array
            // definition) and the number of rows ignored (due to the first pasted row(s) being
            // an array member)
            int arrayRowsAdded = 0;
            int totalAddedRows = 0;
            int skippedRows = 0;
            boolean isIgnoreRow = false;
            // Step through each new row
            for (int index = 0, row = startRow; row < endRow && showMessage != null; row++) {
                boolean skipRow = false;
                // Calculate the row in the table data where the values are to be pasted. This
                // must be adjusted to account for pasting in arrays
                int adjustedRow = row + totalAddedRows - skippedRows;
                // member
                for (int column = startColumn; column <= endColumn && column < getColumnCount() && showMessage != null; column++) {
                    // Get the index into the cell data for this column
                    int tempIndex = index + column - startColumn;
                    // Check if rows were removed due to an array size reduction or removal
                    if (arrayRowsAdded < 0) {
                        // Set the flag indicating that this row of data is to be skipped and
                        // increment the skipped row counter
                        skipRow = true;
                        skippedRows++;
                        // Adjust the row counters so that all array member rows are skipped
                        arrayRowsAdded++;
                        totalAddedRows++;
                    } else // and that the value is an array member
                    if (variableNameIndex == convertColumnIndexToModel(column) && tempIndex < cellData.length && cellData[tempIndex] != null && ArrayVariable.isArrayMember(cellData[tempIndex])) {
                        // the array members
                        if (arrayRowsAdded > 0) {
                            // Move the row index back so that the array member data is pasted
                            // in the proper row
                            adjustedRow -= arrayRowsAdded;
                            arrayRowsAdded--;
                            totalAddedRows--;
                        } else // No rows were added for this array member
                        {
                            // member has no definition
                            if (row == startRow) {
                                // Set the flag indicating that array member rows are ignored
                                isIgnoreRow = true;
                            }
                            // Set the flag indicating that this row of data is to be skipped
                            // and increment the skipped row counter
                            skipRow = true;
                            skippedRows++;
                            // Update the cell data index so that this row is skipped
                            index += numColumns;
                        }
                        break;
                    }
                }
                // Check that this row is not to be ignored
                if (!skipRow) {
                    // Check if inserting is in effect and the cell value is null
                    if (isInsert && index < cellData.length && cellData[index] == null) {
                        // Replace the null with a blank
                        cellData[index] = "";
                    }
                    // Check if a row needs to be inserted to contain the cell data
                    if ((isInsert || (isAddIfNeeded && adjustedRow == tableData.size())) && isInsertRowRequired(index, cellData, startColumn, endColumn)) {
                        // Insert a row at the selection point
                        tableData.add(adjustedRow, getEmptyRow());
                    }
                    // Store the index into the array of data to be pasted
                    int indexSave = index;
                    // are pasted, then the cells that are not empty are pasted
                    for (int pass = 1; pass <= 2; pass++) {
                        // Check if this is the second pass through the row's columns
                        if (pass == 2) {
                            // Reset the index into the array of data to be pasted so that the
                            // non-blank cells can be processed
                            index = indexSave;
                        }
                        // Step through the columns, beginning at the one with the focus
                        for (int column = startColumn; column <= endColumn && showMessage != null; column++) {
                            // outside the bounds or protected then discard the value
                            if (column < getColumnCount()) {
                                // Convert the column coordinate from view to model
                                int columnModel = convertColumnIndexToModel(column);
                                // Get the value to be pasted into the cell, cleaning up the
                                // value if needed. If the number of cells to be filled exceeds
                                // the stored values then insert a blank. A null paste value
                                // indicates that the current cell's value won't be overwritten
                                Object newValue = index < cellData.length ? (cellData[index] != null ? cleanUpCellValue(cellData[index], adjustedRow, columnModel) : (isInsert ? "" : null)) : "";
                                // alterable
                                if (newValue != null && ((pass == 1 && newValue.toString().isEmpty()) || (pass == 2 && !newValue.toString().isEmpty())) && isDataAlterable(tableData.get(adjustedRow), adjustedRow, columnModel)) {
                                    // Get the original cell value
                                    Object oldValue = tableData.get(adjustedRow)[columnModel];
                                    // being inserted, that the value isn't blank
                                    if (!oldValue.equals(newValue) && !(isInsert && newValue.toString().isEmpty())) {
                                        // Insert the value into the cell
                                        tableData.get(adjustedRow)[columnModel] = newValue;
                                        // Get the number of rows in the table prior to
                                        // inserting the new value
                                        int previousRows = tableData.size();
                                        // Validate the new cell contents
                                        showMessage = validateCellContent(tableData, adjustedRow, columnModel, oldValue, newValue, showMessage, cellData.length > 1);
                                        // following an invalid input
                                        if (showMessage == null) {
                                            // Stop pasting data
                                            continue;
                                        }
                                        // Get the number of rows added due to pasting in the
                                        // new value. This is non-zero if an array definition
                                        // is pasted in or if an existing array's size is
                                        // altered
                                        int deltaRows = tableData.size() - previousRows;
                                        // Check if the row count changed
                                        if (deltaRows > 0) {
                                            // Store the number of added/deleted rows and
                                            // update the total number of added/deleted rows
                                            arrayRowsAdded = deltaRows;
                                            totalAddedRows += arrayRowsAdded;
                                        }
                                    }
                                }
                            }
                            // Increment the index to the next value to paste
                            index++;
                        }
                    }
                }
            }
            // Check if the user hasn't selected the Cancel button following an invalid input
            if (showMessage != null) {
                // Load the array of data into the table
                loadDataArrayIntoTable(tableData.toArray(new Object[0][0]), true);
                // Check if automatic edit sequence ending is in effect
                if (getUndoHandler().isAutoEndEditSequence()) {
                    // Flag the end of the editing sequence for undo/redo purposes
                    getUndoManager().endEditSequence();
                }
                // Check if there are rows left to be selected
                if (endRow - 1 - skippedRows > 0) {
                    // Select all of the rows into which the data was pasted
                    setRowSelectionInterval(startRow, endRow - 1 - skippedRows);
                }
                // Select all of the columns into which the data was pasted
                setColumnSelectionInterval(startColumn, endColumnSelect);
                // Select the pasted cells and force the table to be redrawn so that the
                // changes are displayed
                setSelectedCells(startRow, endRow - 1, startColumn, endColumnSelect);
                repaint();
                // Check if any rows were ignored
                if (isIgnoreRow) {
                    // Inform the user how many rows were skipped
                    new CcddDialogHandler().showMessageDialog(editorDialog, "<html><b>" + skippedRows + " array member row(s) ignored due " + "to missing array definition(s)", "Rows Ignored", JOptionPane.WARNING_MESSAGE, DialogOption.OK_OPTION);
                }
            }
            // Set the flag that indicates the last edited cell's content is valid (if an
            // invalid input set the flag to false then it can prevent closing the editor)
            setLastCellValid(true);
            // Check if the pasted data should be combined into a single edit operation
            if (combineAsSingleEdit) {
                // Re-enable auto-ending of the edit sequence and end the sequence. The pasted
                // data can be removed with a single undo if desired
                getUndoHandler().setAutoEndEditSequence(true);
                getUndoManager().endEditSequence();
            }
            return showMessage == null;
        }

        /**
         ************************************************************************************
         * Override the method for cleaning-up of the cell value. The default is to remove any
         * leading and trailing white space characters. This method skips removal of white
         * space characters for cells having input types that allow it
         *
         * @param value
         *            new cell value
         *
         * @param row
         *            table row, model coordinates
         *
         * @param column
         *            table column, model coordinates
         *
         * @return Cell value following clean-up
         ************************************************************************************
         */
        @Override
        protected Object cleanUpCellValue(Object value, int row, int column) {
            // string (i.e., it isn't boolean, etc.)
            if (!table.isEditing() && value instanceof String) {
                // Get the input type for this column
                InputDataType inputType = typeDefn.getInputTypes()[column];
                // space characters
                if (inputType != InputDataType.TEXT_WHT_SPC && inputType != InputDataType.TEXT_MULTI_WHT_SPC) {
                    // Perform the default clean-up (remove leading and trailing white space
                    // characters)
                    value = super.cleanUpCellValue(value, row, column);
                }
            }
            return value;
        }

        /**
         ************************************************************************************
         * Handle a change to the table's content
         ************************************************************************************
         */
        @Override
        protected void processTableContentChange() {
            // and/or value change)
            if (editorDialog != null) {
                // Update the change indicator for the table
                editorDialog.updateChangeIndicator(CcddTableEditorHandler.this);
            }
        }
    };
    // Place the table into a scroll pane
    JScrollPane scrollPane = new JScrollPane(table);
    // Disable storage of edit operations during table creation
    table.getUndoHandler().setAllowUndo(false);
    // Set common table parameters and characteristics
    table.setFixedCharacteristics(scrollPane, tableInfo.isPrototype(), ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, TableSelectionMode.SELECT_BY_CELL, true, ModifiableColorInfo.TABLE_BACK.getColor(), true, true, ModifiableFontInfo.DATA_TABLE_CELL.getFont(), true);
    // Get a reference to the table model to shorten later calls
    tableModel = (UndoableTableModel) table.getModel();
    // Re-enable storage of edit operations
    table.getUndoHandler().setAllowUndo(true);
    // Set the reference to the editor's data field handler in the undo handler so that data
    // field value changes can be undone/redone correctly
    table.getUndoHandler().setFieldHandler(tableInfo.getFieldHandler());
    // Set the undo/redo manager and handler for the description and data field values
    setEditPanelUndo(table.getUndoManager(), table.getUndoHandler());
    // Set the mouse listener to expand and collapse arrays
    setArrayExpansionListener();
    // Get the variable path separators and create the variable path column content, if present
    updateVariablePaths();
    // change)
    if (editorDialog != null) {
        // Create the input field panel to contain the table editor
        createDescAndDataFieldPanel(editorDialog, scrollPane, tableInfo.getProtoVariableName(), tableInfo.getDescription(), tableInfo.getFieldHandler());
        // Set the dialog name so that this dialog can be recognized as being open by the table
        // selection dialog, and the JTable name so that table change events can be identified
        // with this table
        setTableName();
        // Store the current data field information in the event an undo/redo operation occurs
        storeCurrentFieldInformation();
    }
}
Also used : CCDDException(CCDD.CcddClassesDataTable.CCDDException) Matcher(java.util.regex.Matcher) DefaultHighlighter(javax.swing.text.DefaultHighlighter) JTextComponent(javax.swing.text.JTextComponent) InputTypeFormat(CCDD.CcddConstants.InputTypeFormat) Comparator(java.util.Comparator) List(java.util.List) ArrayList(java.util.ArrayList) TableCellEditor(javax.swing.table.TableCellEditor) Component(java.awt.Component) JComponent(javax.swing.JComponent) JTextComponent(javax.swing.text.JTextComponent) DefaultHighlightPainter(javax.swing.text.DefaultHighlighter.DefaultHighlightPainter) TableRowSorter(javax.swing.table.TableRowSorter) InputDataType(CCDD.CcddConstants.InputDataType) JScrollPane(javax.swing.JScrollPane) Pattern(java.util.regex.Pattern) TableCellRenderer(javax.swing.table.TableCellRenderer) MouseEvent(java.awt.event.MouseEvent) JComponent(javax.swing.JComponent) Point(java.awt.Point) Dimension(java.awt.Dimension) Point(java.awt.Point) DefaultHighlightPainter(javax.swing.text.DefaultHighlighter.DefaultHighlightPainter) AssociatedColumns(CCDD.CcddClassesDataTable.AssociatedColumns) UndoableTableModel(CCDD.CcddUndoHandler.UndoableTableModel) MinMaxPair(CCDD.CcddClassesDataTable.MinMaxPair) BadLocationException(javax.swing.text.BadLocationException)

Example 5 with UndoableTableModel

use of CCDD.CcddUndoHandler.UndoableTableModel in project CCDD by nasa.

the class CcddTableTypeEditorHandler method initialize.

/**
 ********************************************************************************************
 * Create the table type editor
 ********************************************************************************************
 */
private void initialize() {
    // Define the table type editor JTable
    table = new CcddJTableHandler() {

        /**
         ************************************************************************************
         * Return true if the type data, description, or data field changes
         ************************************************************************************
         */
        @Override
        protected boolean isTableChanged(Object[][] data) {
            // Update the field information with the current text field values
            updateCurrentFieldValues(fieldHandler.getFieldInformation());
            // Set the flag if the number of fields, field attributes, or field contents have
            // changed
            boolean isFieldChanged = CcddFieldHandler.isFieldChanged(fieldHandler.getFieldInformation(), committedInfo.getFieldInformation(), true);
            return isFieldChanged || !committedDescription.equals(getDescription()) || super.isTableChanged(data);
        }

        /**
         ************************************************************************************
         * Allow resizing of the specified columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnResizable(int column) {
            return column == TableTypeEditorColumnInfo.NAME.ordinal() || column == TableTypeEditorColumnInfo.DESCRIPTION.ordinal() || column == TableTypeEditorColumnInfo.INPUT_TYPE.ordinal();
        }

        /**
         ************************************************************************************
         * Allow multiple line display in the specified columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnMultiLine(int column) {
            return column == TableTypeEditorColumnInfo.NAME.ordinal() || column == TableTypeEditorColumnInfo.DESCRIPTION.ordinal() || column == TableTypeEditorColumnInfo.INPUT_TYPE.ordinal();
        }

        /**
         ************************************************************************************
         * Hide the the specified columns
         ************************************************************************************
         */
        @Override
        protected boolean isColumnHidden(int column) {
            return column == TableTypeEditorColumnInfo.INDEX.ordinal() || (typeOfTable != TableTypeIndicator.IS_STRUCTURE && (column == TableTypeEditorColumnInfo.STRUCTURE_ALLOWED.ordinal() || column == TableTypeEditorColumnInfo.POINTER_ALLOWED.ordinal()));
        }

        /**
         ************************************************************************************
         * Display the specified column(s) as check boxes
         ************************************************************************************
         */
        @Override
        protected boolean isColumnBoolean(int column) {
            return column == TableTypeEditorColumnInfo.UNIQUE.ordinal() || column == TableTypeEditorColumnInfo.REQUIRED.ordinal() || column == TableTypeEditorColumnInfo.STRUCTURE_ALLOWED.ordinal() || column == TableTypeEditorColumnInfo.POINTER_ALLOWED.ordinal();
        }

        /**
         ************************************************************************************
         * Override isCellEditable so that all columns can be edited
         ************************************************************************************
         */
        @Override
        public boolean isCellEditable(int row, int column) {
            boolean isEditable = true;
            // the table model exists, and if the table has at least one row
            if (isDisplayable() && getModel() != null && getModel().getRowCount() != 0) {
                // Create storage for the row of table data
                Object[] rowData = new Object[getModel().getColumnCount()];
                // Convert the view row and column indices to model coordinates
                int modelRow = convertRowIndexToModel(row);
                int modelColumn = convertColumnIndexToModel(column);
                // Step through each column in the row
                for (int index = 0; index < rowData.length; index++) {
                    // Store the column value into the row data array
                    rowData[index] = getModel().getValueAt(modelRow, index);
                }
                // Check if the cell is editable
                isEditable = isDataAlterable(rowData, modelRow, modelColumn);
            }
            return isEditable;
        }

        /**
         ************************************************************************************
         * Override isDataAlterable to determine which table data values can be changed
         *
         * @param rowData
         *            array containing the table row data
         *
         * @param row
         *            table row index in model coordinates
         *
         * @param column
         *            table column index in model coordinates
         *
         * @return true if the data value can be changed
         ************************************************************************************
         */
        @Override
        protected boolean isDataAlterable(Object[] rowData, int row, int column) {
            // Set the flag to false if the column is one that doesn't allow changing the
            // structure or pointer allowed property
            boolean isAllowed = !(rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.VARIABLE.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.PRIM_AND_STRUCT.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.ARRAY_INDEX.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.BIT_LENGTH.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.ENUMERATION.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.RATE.getInputName()) || rowData[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].equals(InputDataType.VARIABLE_PATH.getInputName()));
            // Allow editing if:
            return // This is the column name or description column
            column == TableTypeEditorColumnInfo.NAME.ordinal() || column == TableTypeEditorColumnInfo.DESCRIPTION.ordinal() || typeDefinition == null || // allows the structure allowed property to be changed
            ((column != TableTypeEditorColumnInfo.STRUCTURE_ALLOWED.ordinal() || isAllowed) && // type is one that allows the pointer allowed property to be changed
            (column != TableTypeEditorColumnInfo.POINTER_ALLOWED.ordinal() || isAllowed));
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method to prevent deleting the contents of the cell
         * at the specified row and column
         *
         * @param row
         *            table row index in view coordinates
         *
         * @param column
         *            table column index in view coordinates
         *
         * @return false if the cell contains a combo box; true otherwise
         ************************************************************************************
         */
        @Override
        protected boolean isCellBlankable(int row, int column) {
            return convertColumnIndexToModel(column) != TableTypeEditorColumnInfo.INPUT_TYPE.ordinal();
        }

        /**
         ************************************************************************************
         * Validate changes to the editable cells
         *
         * @param tableData
         *            list containing the table data row arrays
         *
         * @param row
         *            table model row index
         *
         * @param column
         *            table model column index
         *
         * @param oldValue
         *            original cell contents
         *
         * @param newValue
         *            new cell contents
         *
         * @param showMessage
         *            true to display the invalid input dialog, if applicable
         *
         * @param isMultiple
         *            true if this is one of multiple cells to be entered and checked; false if
         *            only a single input is being entered
         *
         * @return Always returns false
         ***********************************************************************************
         */
        @Override
        protected Boolean validateCellContent(List<Object[]> tableData, int row, int column, Object oldValue, Object newValue, Boolean showMessage, boolean isMultiple) {
            // Reset the flag that indicates the last edited cell's content is invalid
            setLastCellValid(true);
            // Create a string version of the new value
            String newValueS = newValue.toString();
            try {
                // Check if the column name has been changed and if the name isn't blank
                if (column == TableTypeEditorColumnInfo.NAME.ordinal() && !newValueS.isEmpty()) {
                    // Check if the column name matches a default name (case insensitive)
                    if (newValueS.equalsIgnoreCase(DefaultColumn.PRIMARY_KEY.getDbName()) || newValueS.equalsIgnoreCase(DefaultColumn.ROW_INDEX.getDbName())) {
                        throw new CCDDException("Column name '" + newValueS + "' already in use (hidden)");
                    }
                    // Set the flag to true if the table type represents a structure
                    boolean isStructure = typeDefinition.isStructure();
                    // Get the database form of the column name
                    String dbName = DefaultColumn.convertVisibleToDatabase(newValueS, InputDataType.getInputTypeByName(tableData.get(row)[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].toString()), isStructure);
                    // creating a duplicate
                    for (int otherRow = 0; otherRow < getRowCount(); otherRow++) {
                        // Check if this row isn't the one being edited,
                        if (otherRow != row) {
                            // insensitive)
                            if (newValueS.equalsIgnoreCase(tableData.get(otherRow)[column].toString())) {
                                throw new CCDDException("Column name '" + newValueS + "' already in use");
                            }
                            // the database form of the one being added
                            if (dbName.equalsIgnoreCase(DefaultColumn.convertVisibleToDatabase(tableData.get(otherRow)[TableTypeEditorColumnInfo.NAME.ordinal()].toString(), InputDataType.getInputTypeByName(tableData.get(otherRow)[TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()].toString()), isStructure))) {
                                throw new CCDDException("Column name '" + newValueS + "' already in use (database)");
                            }
                        }
                    }
                } else // value
                if ((column == TableTypeEditorColumnInfo.UNIQUE.ordinal() || column == TableTypeEditorColumnInfo.REQUIRED.ordinal()) && !newValue.equals(true) && !newValue.equals(false)) {
                    throw new CCDDException("Column '" + TableTypeEditorColumnInfo.getColumnNames()[column] + "' expects a boolean value");
                } else // Check if this is the input type column
                if (column == TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()) {
                    // Check if the input type is disabled
                    if (newValueS.startsWith(DISABLED_TEXT_COLOR)) {
                        throw new CCDDException();
                    }
                    // Check if the input type is invalid
                    if (InputDataType.getInputTypeByName(newValueS) == null) {
                        throw new CCDDException("Unknown input type '" + newValueS + "'");
                    }
                    // Get the table type (structure, command, or other) based on the column
                    // definition input types
                    TableTypeIndicator typeOfTableNew = getTypeOfTable();
                    // structure or command
                    if (typeOfTableNew != typeOfTable && typeOfTableNew != TableTypeIndicator.IS_OTHER) {
                        // Get the invalid input types, if any
                        String msg = getInvalidInputTypes(typeOfTableNew);
                        // the table type definition
                        if (!msg.isEmpty()) {
                            throw new CCDDException(msg);
                        }
                    }
                }
            } catch (CCDDException ce) {
                // Set the flag that indicates the last edited cell's content is invalid
                setLastCellValid(false);
                // Check if the input error dialog should be displayed
                if (showMessage && !ce.getMessage().isEmpty()) {
                    // Inform the user that the input value is invalid
                    new CcddDialogHandler().showMessageDialog(editorDialog, "<html><b>" + ce.getMessage(), "Invalid Input", JOptionPane.WARNING_MESSAGE, DialogOption.OK_OPTION);
                }
                // Restore the cell contents to its original value
                tableData.get(row)[column] = oldValue;
                table.getUndoManager().undoRemoveEdit();
            }
            return showMessage;
        }

        /**
         ************************************************************************************
         * Load the table type definition values into the table and format the table cells
         ************************************************************************************
         */
        @Override
        protected void loadAndFormatData() {
            // Place the data into the table model along with the column names, set up the
            // editors and renderers for the table cells, set up the table grid lines, and
            // calculate the minimum width required to display the table information
            int totalWidth = setUpdatableCharacteristics(committedData, TableTypeEditorColumnInfo.getColumnNames(), null, TableTypeEditorColumnInfo.getToolTips(), true, true, true);
            // Get the minimum width needed to display all columns, but no wider than the
            // display
            int width = Math.min(totalWidth + LAF_SCROLL_BAR_WIDTH, GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth());
            // Check if this is the widest editor table in this tabbed editor dialog
            if (editorDialog.getTableWidth() < width) {
                // Set the initial and preferred editor size
                editorDialog.setTableWidth(width);
                editorDialog.setPreferredSize(new Dimension(width, editorDialog.getPreferredSize().height));
            }
        }

        /**
         ************************************************************************************
         * Override prepareRenderer to allow adjusting the background colors of table cells
         ************************************************************************************
         */
        @Override
        public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
            JComponent comp = (JComponent) super.prepareRenderer(renderer, row, column);
            // highlight colors override the invalid highlight color
            if (comp.getBackground() != ModifiableColorInfo.FOCUS_BACK.getColor() && comp.getBackground() != ModifiableColorInfo.SELECTED_BACK.getColor()) {
                boolean found = true;
                String value = table.getValueAt(row, column).toString();
                // Check if the cell is required and is empty
                if (TableTypeEditorColumnInfo.values()[table.convertColumnIndexToModel(column)].isRequired() && value.isEmpty()) {
                    // Set the flag indicating that the cell value is invalid
                    found = false;
                } else // Check if this is the input type column
                if (column == inputTypeIndex && comboBox != null) {
                    found = false;
                    // Step through each combo box item
                    for (int index = 0; index < comboBox.getItemCount() && !found; index++) {
                        // in case this item is displayed as disabled
                        if (CcddUtilities.removeHTMLTags(comboBox.getItemAt(index)).equals(value)) {
                            // Set the flag indicating that the cell value is valid and stop
                            // searching
                            found = true;
                            break;
                        }
                    }
                }
                // Check if the cell value is invalid
                if (!found) {
                    // Change the cell's background color
                    comp.setBackground(ModifiableColorInfo.REQUIRED_BACK.getColor());
                } else // Check if this cell is protected from changes
                if (!isCellEditable(row, column)) {
                    // Change the cell's text and background colors
                    comp.setForeground(ModifiableColorInfo.PROTECTED_TEXT.getColor());
                    comp.setBackground(ModifiableColorInfo.PROTECTED_BACK.getColor());
                } else // table type as a structure or command table
                if (DefaultColumn.isTypeRequiredColumn((typeOfTable == TableTypeIndicator.IS_STRUCTURE ? TYPE_STRUCTURE : (typeOfTable == TableTypeIndicator.IS_COMMAND ? TYPE_COMMAND : TYPE_OTHER)), InputDataType.getInputTypeByName(table.getValueAt(row, inputTypeIndex).toString()))) {
                    // Change the cell's background color
                    comp.setBackground(ModifiableColorInfo.TYPE_REQUIRED_BACK.getColor());
                }
            }
            return comp;
        }

        /**
         ************************************************************************************
         * Override the CcddJTableHandler method to produce an array containing empty values
         * for a new row in this table
         *
         * @return Array containing blank cell values for a new row
         ************************************************************************************
         */
        @Override
        protected Object[] getEmptyRow() {
            return TableTypeEditorColumnInfo.getEmptyRow();
        }

        /**
         ************************************************************************************
         * Handle a change to the table's content
         ************************************************************************************
         */
        @Override
        protected void processTableContentChange() {
            // Check if there are no duplicated input types that are defined as unique
            if (!isBadType) {
                // Get the table type based on the column definition input types
                TableTypeIndicator typeOfTableNew = getTypeOfTable();
                // Check if the table type changed to/from representing a structure
                if (typeOfTableNew != typeOfTable) {
                    // Store the new table type
                    typeOfTable = typeOfTableNew;
                    // Show/hide the structure table type specific editor columns
                    table.updateHiddenColumns();
                    // Update the input type combo box item list, enabling and/or disabling
                    // items based on those currently in use
                    comboBox.setModel(new DefaultComboBoxModel<String>(getInputTypeNames()));
                }
                // Check if the table type represents a structure
                if (typeOfTableNew == TableTypeIndicator.IS_STRUCTURE) {
                    // Step through each row (column definition) in the table
                    for (int row = 0; row < table.getModel().getRowCount(); row++) {
                        // Get the reference to the table model to shorten subsequent calls
                        UndoableTableModel tableModel = (UndoableTableModel) table.getModel();
                        // Get the column definition's input type
                        String inputType = tableModel.getValueAt(row, TableTypeEditorColumnInfo.INPUT_TYPE.ordinal()).toString();
                        // size, bit length, enumeration, or variable path
                        if (inputType.equals(InputDataType.VARIABLE.getInputName()) || inputType.equals(InputDataType.PRIM_AND_STRUCT.getInputName()) || inputType.equals(InputDataType.ARRAY_INDEX.getInputName()) || inputType.equals(InputDataType.BIT_LENGTH.getInputName()) || inputType.equals(InputDataType.ENUMERATION.getInputName()) || inputType.equals(InputDataType.VARIABLE_PATH.getInputName())) {
                            // Select the structure and pointer allowed check boxes since these
                            // columns always are valid for structure and pointer data types
                            tableModel.setValueAt(true, row, TableTypeEditorColumnInfo.STRUCTURE_ALLOWED.ordinal(), false);
                            tableModel.setValueAt(true, row, TableTypeEditorColumnInfo.POINTER_ALLOWED.ordinal(), false);
                        } else // Check if this is the rate column
                        if (inputType.equals(InputDataType.RATE.getInputName())) {
                            // Deselect the structure allowed and set the pointer allowed check
                            // boxes since a
                            // rate column isn't valid for a structure data type but is valid
                            // for a pointer data type
                            tableModel.setValueAt(false, row, TableTypeEditorColumnInfo.STRUCTURE_ALLOWED.ordinal(), false);
                            tableModel.setValueAt(true, row, TableTypeEditorColumnInfo.POINTER_ALLOWED.ordinal(), false);
                        }
                    }
                }
                // Update the change indicator for the table
                editorDialog.updateChangeIndicator(CcddTableTypeEditorHandler.this);
            }
            // Reset the bad input type flag so that subsequent table type changes are
            // processed
            isBadType = false;
        }
    };
    // Place the table into a scroll pane
    JScrollPane scrollPane = new JScrollPane(table);
    // Disable storage of edit operations during table creation
    table.getUndoHandler().setAllowUndo(false);
    // Set common table parameters and characteristics
    table.setFixedCharacteristics(scrollPane, true, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, TableSelectionMode.SELECT_BY_CELL, false, ModifiableColorInfo.TABLE_BACK.getColor(), true, true, ModifiableFontInfo.DATA_TABLE_CELL.getFont(), true);
    // Re-enable storage of edit operations
    table.getUndoHandler().setAllowUndo(true);
    // Create a drop-down combo box to display the available table type input data types
    setUpInputTypeColumn();
    // Set the reference to the editor's data field handler in the undo handler so that data
    // field value changes can be undone/redone correctly
    table.getUndoHandler().setFieldHandler(fieldHandler);
    // Set the undo/redo manager and handler for the description and data field values
    setEditPanelUndo(table.getUndoManager(), table.getUndoHandler());
    // Create the input field panel to contain the type editor
    createDescAndDataFieldPanel(editorDialog, scrollPane, tableTypeName, committedDescription, fieldHandler);
    // Set the JTable name so that table change events can be identified with this table
    setTableTypeName(tableTypeName);
}
Also used : JScrollPane(javax.swing.JScrollPane) TableCellRenderer(javax.swing.table.TableCellRenderer) CCDDException(CCDD.CcddClassesDataTable.CCDDException) JComponent(javax.swing.JComponent) Dimension(java.awt.Dimension) DefaultComboBoxModel(javax.swing.DefaultComboBoxModel) UndoableTableModel(CCDD.CcddUndoHandler.UndoableTableModel) JComponent(javax.swing.JComponent) Component(java.awt.Component)

Aggregations

UndoableTableModel (CCDD.CcddUndoHandler.UndoableTableModel)5 CCDDException (CCDD.CcddClassesDataTable.CCDDException)3 Component (java.awt.Component)3 Dimension (java.awt.Dimension)3 JScrollPane (javax.swing.JScrollPane)3 Point (java.awt.Point)2 FocusEvent (java.awt.event.FocusEvent)2 Comparator (java.util.Comparator)2 JComponent (javax.swing.JComponent)2 TableCellRenderer (javax.swing.table.TableCellRenderer)2 TableRowSorter (javax.swing.table.TableRowSorter)2 JTextComponent (javax.swing.text.JTextComponent)2 FileEnvVar (CCDD.CcddClassesComponent.FileEnvVar)1 ModifiableColor (CCDD.CcddClassesComponent.ModifiableColor)1 AssociatedColumns (CCDD.CcddClassesDataTable.AssociatedColumns)1 MinMaxPair (CCDD.CcddClassesDataTable.MinMaxPair)1 InputDataType (CCDD.CcddConstants.InputDataType)1 InputTypeFormat (CCDD.CcddConstants.InputTypeFormat)1 TableInsertionPoint (CCDD.CcddConstants.TableInsertionPoint)1 Color (java.awt.Color)1