Search in sources :

Example 1 with MinMaxPair

use of CCDD.CcddClassesDataTable.MinMaxPair 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 2 with MinMaxPair

use of CCDD.CcddClassesDataTable.MinMaxPair in project CCDD by nasa.

the class CcddTableEditorHandler method setUpMinMaxColumns.

/**
 ********************************************************************************************
 * Set up the minimum/maximum value groups and associate these columns with the data type
 * column. Only those minimum and maximum columns not already associated with a command
 * argument group are included in these pairings
 ********************************************************************************************
 */
private void setUpMinMaxColumns() {
    // Create a list to contain minimum/maximum column associations
    minMaxPair = new ArrayList<MinMaxPair>();
    // Initialize the minimum and maximum columns and the index of the data type/enumeration
    // column already paired (if any)
    int minIndex = 0;
    int maxIndex = 0;
    // Step through each column defined for this table's type
    for (int index = 0; index < typeDefn.getColumnCountDatabase(); index++) {
        // Initialize the minimum and maximum column indices
        int minColumn = -1;
        int maxColumn = -1;
        // Step through the remaining columns to find the next minimum input type column
        for (; minIndex < table.getModel().getColumnCount(); minIndex++) {
            // Check that this is a minimum column
            if (typeDefn.getInputTypes()[minIndex] == InputDataType.MINIMUM) {
                // Save the minimum column index, increment the index for matching up with the
                // next pairing, and stop searching
                minColumn = minIndex;
                minIndex++;
                break;
            }
        }
        // Step through the remaining columns to find the next maximum input type column
        for (; maxIndex < table.getModel().getColumnCount(); maxIndex++) {
            // Check that this is a maximum column
            if (typeDefn.getInputTypes()[maxIndex] == InputDataType.MAXIMUM) {
                // Save the maximum column index, increment the index for matching up with the
                // next pairing, and stop searching
                maxColumn = maxIndex;
                maxIndex++;
                break;
            }
        }
        // Step through each command argument column grouping
        for (AssociatedColumns cmdArg : associatedColumns) {
            // column index
            if (cmdArg.getMinimum() == minColumn) {
                // Reset the minimum column index so that it isn't reused
                minColumn = -1;
            }
            // column index
            if (cmdArg.getMaximum() == maxColumn) {
                // Reset the maximum column index so that it isn't reused
                maxColumn = -1;
            }
        }
        // command argument
        if (minColumn != -1 || maxColumn != -1) {
            // Add the new minimum/maximum column pairing to the list
            minMaxPair.add(new MinMaxPair(minColumn, maxColumn));
        }
    }
}
Also used : AssociatedColumns(CCDD.CcddClassesDataTable.AssociatedColumns) MinMaxPair(CCDD.CcddClassesDataTable.MinMaxPair) Point(java.awt.Point)

Aggregations

AssociatedColumns (CCDD.CcddClassesDataTable.AssociatedColumns)2 MinMaxPair (CCDD.CcddClassesDataTable.MinMaxPair)2 Point (java.awt.Point)2 CCDDException (CCDD.CcddClassesDataTable.CCDDException)1 InputDataType (CCDD.CcddConstants.InputDataType)1 InputTypeFormat (CCDD.CcddConstants.InputTypeFormat)1 UndoableTableModel (CCDD.CcddUndoHandler.UndoableTableModel)1 Component (java.awt.Component)1 Dimension (java.awt.Dimension)1 MouseEvent (java.awt.event.MouseEvent)1 ArrayList (java.util.ArrayList)1 Comparator (java.util.Comparator)1 List (java.util.List)1 Matcher (java.util.regex.Matcher)1 Pattern (java.util.regex.Pattern)1 JComponent (javax.swing.JComponent)1 JScrollPane (javax.swing.JScrollPane)1 TableCellEditor (javax.swing.table.TableCellEditor)1 TableCellRenderer (javax.swing.table.TableCellRenderer)1 TableRowSorter (javax.swing.table.TableRowSorter)1