/*
 * OprnTableModel.java
 *
 * Created on July 20, 2004, 2:18 PM
 *
 * This is the model for delivering data to the main operations table view.
 * This handles loading the operations_defn.xml file and the
 * operations_lang.xml file.
 *
 * The operations_defn.xml file contains the following information that is stored:
 *    <paramname> - name of the parameter
 *    <paramtype> - type of the parameter, float, int, etc
 *    <paramunit> - default units for the value
 *
 * The operations_lang.xml file contains the following information that is stored:
 *    <paramprompt> - long description of parameter, what is displayed as a column heading
 *    <paramaltunit> - an alternate unit (English) that the parameter can be displayed in
 *    <factor> - what to multiple the value by to display the value in alternate units
 *    <paramchoice> - list of values and text for the choice lists that a parameter may have
 *
 * Jim Frankenberger
 * USDA-ARS, West Lafayette IN
 * jrf@purdue.edu
 *
 */
package ex1;

import de.schlichtherle.truezip.file.TFile;
import javax.swing.JOptionPane;
import java.util.*;

/**
 * This the model for delivering data to the main operations table view. This handles loading the
 * operations_defn.xml file and the operations_lang.xml file.
 *
 * The operations_defn.xml file contains the following information that is stored:
 * <paramname> - name of the parameter
 * <paramtype> - type of the parameter, float, int, etc
 * <paramunit> - default units for the value
 *
 * @author jrf
 *
 */
public class OprnTableModel extends WepsTableModel {

    private static final long serialVersionUID = 1L;

    private final HashMap<Integer, int[]> rowMapInfo;
    private final HashMap<Integer, int[]> subRowInfo;
    
    private final Hashtable<String, InputLimits.TableStatus> archival;
    
    private final Set<String> detailSet;

    private boolean shortNames;

    /**
     * Create the table model for the main part of operations
     *
     * @param cfgDir directory where mcrew config files are located
     * @param dbMainDir directory where data files are located
     */
    public OprnTableModel(String cfgDir, String dbMainDir) {
        super(cfgDir, dbMainDir);
        rowMapInfo = new HashMap<Integer, int[]>();
        subRowInfo = new HashMap<Integer, int[]>();
        parms = new DefnFileParser(cfgDir + "/operation_defn_db.xml", false);
        new LangFileParser(parms, cfgDir + "/operation_lang_db.xml", false);
        pData = new DataFileParser(parms, cfgDir, false);
        shortNames = false;
        archival = new Hashtable<String, InputLimits.TableStatus>();
        detailSet = archival.keySet();
    }
    
    /**
     * Create the table model for the main part of operations
     *
     * @param cfgDir directory where mcrew config files are located
     * @param dbMainDir directory where data files are located
     * @param initData flag for UPGM subclass to handle data itself.
     */
    public OprnTableModel(String cfgDir, String dbMainDir, boolean initData) 
    {
        super(cfgDir, dbMainDir);
        rowMapInfo = new HashMap<Integer, int[]>();
        subRowInfo = new HashMap<Integer, int[]>();
        if(initData)
        {
            parms = new DefnFileParser(cfgDir + "/operation_defn_db.xml", false);
            new LangFileParser(parms, cfgDir + "/operation_lang_db.xml", false);
            pData = new DataFileParser(parms, cfgDir, false);
        }
        shortNames = false;
        archival = new Hashtable<String, InputLimits.TableStatus>();
        detailSet = archival.keySet();
    }

    /**
     * This returns the number of columns in the main operations panel view. The number of columns
     * is the maximum of all loaded file process counts plus the NUM,NAME,DIRECTORY columns and one
     * blank column.
     *
     * @return number of columns in main operations tale
     */
    @Override
    public int getColumnCount() {
        // for each file name get the number of processes that are enclosed
        int count;
        int maxProcs = 0;
        for (WepsDBFile f : xmlFiles) {
            count = f.getActionCount();
            if (count > maxProcs) {
                maxProcs = count;
            }
        }
        return maxProcs + 3;   // add Num,Name,path
    }

    /**
     * Number of rows in the main operations table which is the same as the number of files loaded.
     *
     * @return number of rows in main operations table
     */
    @Override
    public int getRowCount() {
        return xmlFiles.size();
    }

    /**
     * Return the number of rows this view/process has. This may be different between the table tabs
     * if a file has a process that appears more than once.
     *
     * @param tab index of sub/detail/process table to get row count for
     * @return number of rows in this process table
     */
    public int getRowCount(int tab) {

        int count = 0;
        int code = parms.getOpParmCode(tab);
        char ty = parms.getOpParmCodeType(tab);
        for (WepsDBFile wf : xmlFiles) {
            count += wf.getRowsSpanned(code, ty);
            //count += pData.getRowsSpanned(i,code,ty);
        }
        return count;
    }

    /**
     * Get if any changes can be made.
     *
     * @return
     */
    public boolean getEdit() {
        return allowEdits;
    }

    /**
     * This function causes all row mapping to be updated for all detail tables. The row maps
     * contain links between a detail row info and the operation it is realted to. For example row
     * 12 in a detail might link to the operation on row 10 of the main table.
     */
    public void computeRowMaps() {
        rowMapInfo.clear();
        subRowInfo.clear();

        for (int k = 0; k < getActionNameCount(); k++) {
            DefnFileParser p = getParamDef();
            int cols = p.getOpParmCount(k);
            if (cols > 0) {
                computeRowInfo(k);
            }
        }
    }

    /**
     * This function updates the row mapping info for one detail table.
     *
     * @param tab
     */
    public void computeRowInfo(int tab) {

        int span = 0;
        int physRow = 0;
        int code = parms.getOpParmCode(tab);
        char ty = parms.getOpParmCodeType(tab);
        int count = getRowCount(tab);

        int rowMap[] = new int[count];
        int subMap[] = new int[count];

        for (int i = 0; i < xmlFiles.size(); i++) {
            WepsDBFile wf = xmlFiles.get(i);

            span = wf.getRowsSpanned(code, ty);
            if (span > 0) {
                for (int k = 0; k < span; k++) {
                    subMap[physRow] = k;
                    rowMap[physRow++] = i;
                }
            }
        }
        rowMapInfo.put(tab, rowMap);
        subRowInfo.put(tab, subMap);
    }

    /**
     * Get the name of the column of the main operations screen, very generic.
     *
     * @param col the column to get the name for
     * @return the name used for the column
     */
    @Override
    public String getColumnName(int col) {
        //return parms.getParmName(col);
        if (col == 0) {
            return "Num";
        }
        if (col == 1) {
            return "Name";
        }
        if (col == 2) {
            return "Subdirectory";
        } else if (shortNames) {
            return String.valueOf(col - 2);
        } else {
            return "Process " + String.valueOf(col - 2);
        }
    }

    /**
     * Returns the name of a process at a particular position.
     *
     * @param row the row to get process name for
     * @param col the column to get process name for
     * @return the string representing the value
     */
    @Override
    public Object getValueAt(int row, int col) {
        if (col == 0) {
            return Integer.toString(row + 1);
        } else if (col == 1) {
            return getFileName(row);
        } else if (col == 2) {
            return getFilePathOnly(row);
        } else {
            // This gets the main process sequence for each row
            WepsDBFile w = xmlFiles.get(row);
            if (shortNames) {
                return w.getShortActionName(col - 3);
            } else {
                return w.getActionName(col - 3);
            }
        }

    }

    /**
     * This gets the value of a cell for a detailed operations table - a specific tab and then the
     * row+column within the tab.
     *
     * @param tab the specific detail table to get value from
     * @param row the row to get value from
     * @param col the column to get value from
     * @return
     */
    public Object getValueAt(int tab, int row, int col) {
        Integer t = tab;
        int rows[] = rowMapInfo.get(t);
        int subs[] = subRowInfo.get(t);
        int row2 = rows[row];
        int sub2 = subs[row];
        if (col == 0) {
            if (sub2 == 0) {
                return Integer.toString(row2 + 1);
            } else {
                return "";
            }

        } else if (col == 1) {
            if (sub2 == 0) {
                WepsDBFile w = xmlFiles.get(row2);
                return w.getFileName();
            } else {
                return "";
            }
        } else {
            if (col == 2) {
                if (sub2 == 0) {
                    WepsDBFile w = xmlFiles.get(row2);
                    return w.getPathOnly();
                } else {
                    return "";
                }
            } else {
                // This gets the main process sequence for each row
                WepsDBFile wf = xmlFiles.get(row2);
                return wf.getValueAt(tab, col, sub2, altUnits);
                //return parms.getOpVal(tab,row2,col,sub2,altUnits);
            }
        }

    }

    /**
     * Save the value of a cell for a detailed operations table.
     *
     * @param o value to store (a string)
     * @param tab detail table from value came from
     * @param row row in detail table value came from
     * @param col column in detail table value came from
     */
    public void setValueAt(Object o, int tab, int row, int col) {

        if (col <= 2) {
            return;
        }

        Integer t = tab;
        int rows[] = rowMapInfo.get(t);
        int subs[] = subRowInfo.get(t);
        int row2 = rows[row];
        int sub2 = subs[row];

        String pstr = (String) o;
        if (pstr != null) {
            WepsDBFile wf = xmlFiles.get(row2);
            wf.setValueAt(tab, col, sub2, pstr, altUnits);
            //parms.setOpVal(tab,row2,col,sub2,pstr,true,altUnits);
        }

    }

    /**
     * This gets the row in the main operation table that a specific row in a detail table
     * corresponds to.
     *
     * @param tab detail table index
     * @param row row in detail table
     * @return row in main operations table that has operation
     */
    @Override
    public int getLogicalRow(int tab, int row) {
        Integer t = tab;
        int row2 = -1;

        int rows[] = rowMapInfo.get(t);

        row2 = rows[row];

        return row2;
    }

    /**
     * This gets the sub-index for a process in a detail table. If an operation has Several
     * processes of the same type they are ordered as 0,1,2,etc based on the row of the detail table
     * this returns the sub-index of the row (what index within a group of the same processes this
     * row corresponds to)
     *
     * @param tab detail table index
     * @param row row in detail table
     * @return what index within a group of the same processes this row corresponds to
     */
    public int getLogicalSub(int tab, int row) {
        Integer t = tab;
        int subs[] = subRowInfo.get(t);
        int sub2 = subs[row];
        return sub2;
    }

    /**
     * This gets the column type in the main operations table.
     *
     * @param col column to get type for
     * @return type of data in this column
     */
    @Override
    public ParamDef.ColumnType getColumnType(int col) {
//        return parms.getType(col);          
        if (col > 2) {
            return ParamDef.ColumnType.CHOICE;
        } else {
            return ParamDef.ColumnType.FLOAT;
        }
    }
    public ParamDef.ColumnType getColumnTypeValidate( int col, String value)
    {
        return parms.getOprnType(col, value);
    }

    /**
     * Class of data in column
     *
     * @param c column to inspect
     * @return type of data (all are Strings)
     */
    @Override
    public Class<?> getColumnClass(int c) {
        return String.class;
    }

    /**
     * Check if a cell can be changed.
     *
     * @param row row to check
     * @param col column to check
     * @return
     */
    @Override
    public boolean isCellEditable(int row, int col) {
        return false;
    }

    /**
     * Get number of choice entries for a cell
     *
     * @return number of process types.
     */
    public int getChoiceCount() {
        return getActionNameCount();
    }

    /**
     * Get the name of a specific choice.
     *
     * @param ch index of choice array to return
     * @return choice at index as a string
     */
    public String getChoice(int ch) {
        return getFullActionName(ch);
    }

    /**
     * This adds an XML data file to the table model
     *
     * @param file name of the operations file to add
     */
    @Override
    public void addXmlFile(String file) {
        TFile wf = new TFile(file);
        if (wf.exists() == true) {
            WepsDBFile w = new WepsDBFile(file, false, parms, dbDir);
            pData.addXmlFile(w);
            w.fixupOperation();
            xmlFiles.add(w);
        } else {
            JOptionPane.showMessageDialog(null, "Can not find file: " + file);
        }
    }

    /**
     * After sorting update the file list order by reseting the array.
     *
     * @param li new list of ordered file info objects
     */
    public void setXmlFiles(ArrayList<WepsDBFile> li) {
        xmlFiles = li;
    }

    /**
     * Get the number of different process names
     *
     * @return the number of process names
     */
    public int getActionNameCount() {
        return parms.getActionNameCount();
    }

    /**
     * Get the name of a specific action
     *
     * @param ind what index to get the name for
     * @return the process name at that index
     */
    public String getActionName(int ind) {
        return parms.getActionName(ind);
    }

    /**
     * Get the full name of a specific action. This has the process number on the fron
     *
     * @param ind what index to get the name for
     * @return the full process name at that index
     */
    public String getFullActionName(int ind) {
        return parms.getFullActionName(ind);
    }

    /**
     * Check if the file on a specific row has been modified
     *
     * @param row row to check for a modified file
     * @return true if the file on this row was modified.
     */
    @Override
    public boolean isModified(int row) {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
                return wf.isModified();
            }
        }

        return false;
    }

    /**
     * This calls a save function for all files that have been modified.
     */
    @Override
    public String saveAll() {
        boolean rc = true;
        String notSaved = null;
        String saved = null;
        int numSaved = 0;

        for (WepsDBFile wf : xmlFiles) {
            if (wf != null) {
                if (wf.isModified()) {
                    if (wf.saveFile(mcrewCfgDir) == false) {
                        rc = false;
                        if (notSaved == null) {
                            notSaved = wf.getFileName();
                        } else {
                            notSaved = notSaved + "\n" + wf.getFileName();
                        }
                    } else {
                        numSaved++;
                        if (saved == null) {
                            saved = wf.getFileName();
                        } else {
                            saved = saved + "\n" + wf.getFileName();
                        }
                    }
                }
            }
        }

        if (rc == true) {
            if (numSaved > 0) {
                return Integer.toString(numSaved) + " files saved.";
            } else {
                return "No files were modified, none saved.";
            }
        } else {
            String msg = "Some files were not saved.\n\n";
            if (saved != null) {
                msg = msg + "Operations Saved:\n" + saved + "\n\n";
            }
            msg = msg + "Operations Not Saved:\n" + notSaved;

            return msg;
        }
    }

    /**
     * This gets the file name (short version for the operations record on a specified row.
     *
     * @param row row to get filename from
     * @return filename (short) corresponding to this row.
     */
    @Override
    public String getFileName(int row) {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
                return wf.getFileName();
            } else {
                return "???";
            }
        } else {
            return "???";
        }
    }

    /**
     * Get the path only part of the file for a specific row This is used to fill in the directory
     * portion of the table
     *
     * @param row row to get file path from
     * @return path part of file relative to main db directory
     */
    public String getFilePathOnly(int row) {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
                return wf.getPathOnly();
            } else {
                return "???";
            }
        } else {
            return "???";
        }
    }

    /**
     * This adds an empty operations file to the table.
     *
     * @param name name of the new file.
     */
    public void addBlankOperation(String name) {

        WepsDBFile wf = new WepsDBFile(name, false, parms, dbDir);
        xmlFiles.add(wf);
        wf.createClone(mcrewCfgDir, xmlFiles.get(0), true);
    }

    /**
     * This copies an existing operation and gives it a new name.
     *
     * @param name name of new operation
     * @param row base row that source is found on
     */
    @Override
    public void copy(String name, int row) {
        WepsDBFile wf = new WepsDBFile(name, false, parms, dbDir);

        wf.createClone(mcrewCfgDir, xmlFiles.get(row), false);
        pData.addXmlFile(wf);
        wf.fixupOperation();
        xmlFiles.add(wf);
    }



    /**
     * Delete process from a specific row.
     *
     * @param row row number that delete will occur on
     * @param procNum index of process within row that will be deleted.
     * @return true if the process could be deleted.
     */
    public boolean deleteProcess(int row, int procNum) {
        boolean rc = false;
        if(procNum == 0)
        {
            JOptionPane.showMessageDialog(null, "All Operations must have exactly"
                    + " one Operation level process.  Cannot delete.", "Operation "
                    + "Process", JOptionPane.INFORMATION_MESSAGE);
            return false;
        }

        WepsDBFile wf = xmlFiles.get(row);
        if (wf != null) {
            return wf.deleteProcess(procNum);
        }
        return rc;
    }

    /**
     * Changes the process type within a row to new process
     *
     * @param row row that process belongs to
     * @param procNum index of process that is to be changed
     * @param newProc index of new process in processes array
     * @return
     */
    public boolean changeProcess(int row, int procNum, int newProc) {
        boolean rc = false;
        OpAction a = parms.getAction(newProc);
        if((procNum == 0) ^ a.code == 'O')
        {
            JOptionPane.showMessageDialog(null, "All Operations must have exactly"
                    + " one Operation level process.  Cannot change.", "Operation "
                    + "Process", JOptionPane.INFORMATION_MESSAGE);
            return false;
        }

        WepsDBFile wf = xmlFiles.get(row);
        if (wf != null) {
            ActionValue av = findSameAction(a);

            return wf.changeProcess(procNum, newProc, av);
        }
        return rc;
    }

    /**
     * Searches all files for one that has the same type of process.
     *
     * @param a action/process value to search for
     * @return an action structure with parameters set to reasonable values
     */
    private ActionValue findSameAction(OpAction a) {

        for (WepsDBFile wf : xmlFiles) {
            ActionValue av = wf.find(a);
            if (av != null) {
                return av;
            }
        }

        return null;
    }

    /**
     * Insert a new process int the operation.
     *
     * @param row row that process will be added to
     * @param procNum index of process to be changed
     * @param newProc type of new process to be added
     * @param before true if new process is before existing, or false if after
     * @return true if new process could be added
     */
    public boolean insertProcess(int row, int procNum, int newProc, boolean before) {
        boolean rc = false;
        OpAction a = parms.getAction(newProc);
        if(((procNum == 0) || before) && a.code == 'O')
        {
            JOptionPane.showMessageDialog(null, "All Operations must have exactly"
                    + " one Operation level process.  Cannot change.", "Operation "
                    + "Process", JOptionPane.INFORMATION_MESSAGE);
            return false;
        }

        WepsDBFile wf = xmlFiles.get(row);
        if (wf != null) {
            ActionValue av = findSameAction(a);
            return wf.insertProcess(procNum, newProc, before, av);
        }
        return rc;
    }

    /**
     * Insert a new process int the operation.
     *
     * @param row row that process will be added to
     * @param procNum index of process to be changed
     * @param newProc type of new process to be added
     * @param before true if new process is before existing, or false if after
     * @return true if new process could be added
     */
    public boolean insertFirstProcess(int row, int procNum, int newProc, boolean before) {
        boolean rc = false;
        OpAction a = parms.getAction(newProc);
        WepsDBFile wf = xmlFiles.get(row);
        if(wf.getActionCount() > 0)
        {
            JOptionPane.showMessageDialog(null, "All Operations must have exactly"
                    + " one Operation level process.  Cannot change.", "Operation "
                    + "Process", JOptionPane.INFORMATION_MESSAGE);
            return false;
        }
        if (wf != null) {
            ActionValue av = findSameAction(a);
            return wf.insertProcess(procNum, newProc, before, av);
        }
        return rc;
    }

    /**
     * Get the file structure associated with a specific row.
     *
     * @param row row to get file info for
     * @return structure holding all info about the file
     */
    public WepsDBFile getFile(int row) {
        if (row < xmlFiles.size()) {
            return xmlFiles.get(row);
        } else {
            return null;
        }
    }

    /**
     * This will shift/move an existing process in the process order
     *
     * @param row row that process belongs to
     * @param col column of process to shift
     * @param isRight true if data should be shofted right, false for left
     */
    public void moveProcess(int row, int col, boolean isRight) {
        WepsDBFile wf = xmlFiles.get(row);
        if(((col == 0) && isRight) || ((col == 1) && !isRight))
        {
            JOptionPane.showMessageDialog(null, "All Operations must have exactly"
                    + " one Operation level process.  Cannot move.", "Operation "
                    + "Process", JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        if (wf != null) {
            if (isRight) {
                wf.moveRight(col);
            } else {
                wf.moveLeft(col);
            }
        }
    }

    /**
     * Checks if the processes for a specific row are out-of-order
     *
     * @param row row to check process order
     * @return true if process order has an error, false if ok
     */
    @Override
    public boolean nameMismatched(int row) {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
                return wf.isNameMismatched();
            }
        }
        return false;
    }
    
    @Override
    public boolean outOfOrder(int row)
    {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
               return wf.isOutOfOrder();
            }
        }
        return false;
    }

    /**
     * This function checks the process order for one file.
     *
     * @param row row of table to check
     * @param display true if any errors are to be displayed
     * @return true if order incorrect, false if ok
     */
    public boolean checkOneOperation(int row, boolean display) {
        if (row < xmlFiles.size()) {
            WepsDBFile wf = xmlFiles.get(row);
            if (wf != null) {
                return wf.checkProcesses(display);
            }
        }
        return false;
    }

    /**
     * This sorts the rows based on the contents of a specific column.
     *
     * @param col column to sort
     * @param status ascending, descending or nothing
     */
    @Override
    public void sort(int col, int status) {
        super.sort(col, status);

        computeRowMaps();
    }

    /**
     *
     * @param val
     */
    public void setShortNames(boolean val) {
        shortNames = val;
    }

    @Override
    public WepsTableModel.TableEnum getType() {
        return WepsTableModel.TableEnum.Operation;
    }
    
    @Override
    public String getTypeString() {
        return "operation";
    }
    
    @Override
    public String getTypeFileExtension() {
        return ".oprn";
    }

    /**
     * This method stores the current Detail Table Model's status in a hastable
     * with it's name as the key.
     * @param key
     * @param value 
     */
    public void archive(String key, InputLimits.TableStatus value)
    {
        archival.put(key, value);
    }
    
    /**
     * OprnTableModel's getTableStatus needs to override WepsTableModel's because
     * the OprnTableModel needs to check all of the Detail Models to get the status
     * from them, and return the highest value amoung them.
     * @return 
     */
    @Override
    public InputLimits.TableStatus getTableStatus()
    {
        InputLimits.TableStatus accumulate = super.getTableStatus();
        for(String item : detailSet)
        {
            InputLimits.TableStatus attempt = archival.get(item);
            if(accumulate.lessThan(attempt))
            {
                accumulate = attempt;
            }
        }
        return accumulate;
    }
    
    /**
     * OprnTableModel's getTableStatus needs to override WepsTableModel's because
     * the OprnTableModel may have incorrect data off tab; thus it accesses 
     * table status directly from the xml file referred to by that row.
     * @param row
     * @return 
     */
    @Override
    public InputLimits.TableStatus getTableStatus(int row)
    {
        WepsDBFile current = xmlFiles.get(row);
        return current.getTotalStatus();
    }
    
    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstRedRow() {
        if(super.getTableStatus() != InputLimits.TableStatus.NOSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstRedRow();
    }

    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstRedColumn() {
        if(super.getTableStatus() != InputLimits.TableStatus.NOSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstRedColumn();
    }
    
    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstRedColumn(int row)
    {
        if(super.getTableStatus() != InputLimits.TableStatus.NOSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstRedColumn(row);
    }
    
    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstYellowRow() {
        if(super.getTableStatus() != InputLimits.TableStatus.WARNSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstYellowRow();
    }
    
    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstYellowColumn() {
        if(super.getTableStatus() != InputLimits.TableStatus.WARNSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstYellowColumn();
    }
    
    /**
     * If the out of bounds cell is on this tab, we can use the super method call.
     * Otherwise, we return 0
     * @return 
     */
    @Override
    public int getFirstYellowColumn(int row)
    {
        if(super.getTableStatus() != InputLimits.TableStatus.WARNSAVE) 
        {
            if(super.xmlFiles.size() > 0) return 0;
        }
        return super.getFirstYellowColumn(row);
    }
}
