TDCARR.CPP
//+----------------------------------------------------------------------- 
// 
//  Tabular Data Control Array 
//  Copyright (C) Microsoft Corporation, 1996, 1997 
// 
//  File:       TDCArr.cpp 
// 
//  Contents:   Implementation of the CTDCArr object. 
//              This class forms the heart of the Tabular Data Control. 
//              It provides the core 2D array of variant values, plus 
//              a (possibly filtered/sorted) view of this data for 
//              presentation through an ISimpleTabularData interface. 
// 
//------------------------------------------------------------------------ 
 
#include "stdafx.h" 
#include "wch.h" 
#include "STD.h" 
#include "TDC.h" 
#include <MLang.h> 
#include "Notify.h" 
#include "TDCParse.h" 
#include "TDCArr.h" 
#include "SimpData.h" 
#include "TDCIds.h" 
 
//------------------------------------------------------------------------ 
// 
//  Function:  fWCHIsSpace() 
// 
//  Synopsis:  Indicates whether a WCHAR is considered a space character 
// 
//  Arguments: wch      Character to test 
// 
//  Returns:   TRUE/FALSE indicating whether the given character is 
//             considered a space. 
// 
//------------------------------------------------------------------------ 
 
inline boolean fWCHIsSpace(WCHAR wch) 
{ 
    return (wch == L' ' || wch == L'\t'); 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Function:  fWCHEatTest() 
// 
//  Synopsis:  Advances a string pointer over a given test character. 
// 
//  Arguments: ppwch      Pointer to string to test 
//             wch        Match character 
// 
//  Returns:   TRUE indicating the character matched and the pointer has 
//               been advanceed 
//             FALSE indicating no match (character pointer left unchanged) 
// 
//------------------------------------------------------------------------ 
 
inline boolean fWCHEatTest(LPWCH *ppwch, WCHAR wch) 
{ 
    if (**ppwch != wch) 
        return FALSE; 
    (*ppwch)++; 
    return TRUE; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Function:  fWCHEatSpace() 
// 
//  Synopsis:  Advances a string pointer over white space. 
// 
//  Arguments: ppwch      String pointer. 
// 
//  Returns:   Nothing. 
// 
//------------------------------------------------------------------------ 
 
inline void fWCHEatSpace(LPWCH *ppwch) 
{ 
    while (fWCHIsSpace(**ppwch)) 
        (*ppwch)++; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:    CTDCArr() 
// 
//  Synopsis:  Class constructor.  Due to the COM model, the 
//             member function "Create" should be called to actually 
//             initialise the STD data structures. 
// 
//  Arguments: None. 
// 
//------------------------------------------------------------------------ 
 
CTDCArr::CTDCArr() : m_cRef(1) 
{ 
    m_pEventBroker = NULL; 
    m_pSortList = NULL; 
    m_bstrSortExpr = NULL; 
    m_pFilterTree = NULL; 
    m_bstrFilterExpr = NULL; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    Init() 
// 
//  Synopsis:  Initialises the internal data. 
// 
//  Arguments: pEventBroker       Object to delegate notifications to. 
// 
//  Returns:   S_OK upon success. 
//             Error code upon failure. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::Init(CEventBroker *pEventBroker, IMultiLanguage *pML) 
{ 
    HRESULT hr = S_OK; 
 
    hr = m_arrparrCells.Init(0); 
    if (SUCCEEDED(hr)) 
        hr = m_arrparrFilter.Init(0); 
    if (SUCCEEDED(hr)) 
        hr = m_arrColInfo.Init(0); 
    m_iFilterRows = CalcFilterRows(); 
    m_iDataRows = CalcDataRows(); 
    m_iCols = CalcCols(); 
    m_fLastFilter = FALSE; 
    m_fSortFilterDisrupted = FALSE; 
    m_state = LS_UNINITIALISED; 
    m_lcid = GetUserDefaultLCID(); 
    m_lcidRead = m_lcid; 
    _ASSERT(pEventBroker != NULL); 
 
    m_pEventBroker = pEventBroker; 
    m_pEventBroker->AddRef();           // better not be called with NULL 
 
    m_pML = pML; 
    m_pML->AddRef(); 
 
    m_fAsync = FALSE;                   // assume non-asynch for error cases 
    return hr; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Member:    ~CTDCArr() 
// 
//  Synopsis:  Destructor for CTDCArr 
// 
//------------------------------------------------------------------------ 
 
CTDCArr::~CTDCArr() 
{ 
    for (LONG iRow = CalcDataRows(); iRow >= 0; iRow--) 
    { 
        m_arrparrCells[iRow]->Passivate(); 
        delete m_arrparrCells[iRow]; 
    } 
    m_arrparrCells.Passivate(); 
    m_arrparrFilter.Passivate(); 
    m_arrColInfo.Passivate(); 
    if (m_pSortList != NULL) 
        delete m_pSortList; 
    SysFreeString(m_bstrSortExpr); 
    if (m_pFilterTree != NULL) 
        delete m_pFilterTree; 
    SysFreeString(m_bstrFilterExpr); 
 
    if (m_pEventBroker) 
    { 
        m_pEventBroker->Release(); 
        m_pEventBroker = NULL; 
    } 
 
    ClearInterface(&m_pML); 
} 
 
LONG CTDCArr::CalcDataRows() 
{ 
    return m_arrparrCells.GetSize() - 1; 
} 
 
LONG CTDCArr::CalcFilterRows() 
{ 
    return m_arrparrFilter.GetSize() - 1; 
} 
 
LONG CTDCArr::CalcCols() 
{ 
    return m_arrparrCells.GetSize() > 0 
                ? m_arrparrCells[0]->GetSize() : 0; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    GetRowCount() 
// 
//  Synopsis:  Retrieves the number of rows in the table. 
// 
//  Arguments: pcRows          pointer to number of rows    (OUT) 
// 
//  Returns:   S_OK to indicate success. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::getRowCount(LONG *pcRows) 
{ 
    *pcRows = m_iFilterRows; 
    return S_OK; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    GetColumnCount() 
// 
//  Synopsis:  Retrieves the number of column in the table. 
// 
//  Arguments: pcCols       pointer to number of columns (OUT) 
// 
//  Returns:   S_OK to indicate success. 
//             E_UNEXPECTED if the table has not been loaded yet. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::getColumnCount(LONG *pcCols) 
{ 
    *pcCols = m_iCols; 
    return S_OK; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    GetRWStatus() 
// 
//  Synopsis:  Gets the read/write status of a cell, row, column or the 
//             entire array.  Since this implementation of STD can never 
//             set the read/write status of a cell anywhere, all data 
//             cells are presumed to have the default access and all 
//             column heading cells are presumed to be read-only.  Therefore, 
//             it is not necessary to keep track of this information in 
//             individual cells, and this function need only return 
//             the value OSPRW_DEFAULT. 
// 
//  Arguments: iRow            row index (-1 means all rows) 
//             iCols           column index (-1 means all columns) 
//             prwStatus       pointer to read/write status (OUT) 
// 
//  Returns:   S_OK if indices are correct (prwStatus set). 
//             E_INVALIDARG if indices are out of bounds. 
//             E_UNEXPECTED if the table has not been loaded yet. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::getRWStatus(LONG iRow, LONG iCol, OSPRW *prwStatus) 
{ 
    HRESULT hr  = S_OK; 
 
    if ((fValidFilterRow(iRow) || iRow == -1) && 
        (fValidCol(iCol) || iCol == -1)) 
    { 
        if (iRow == -1) 
        { 
            //  Should return READONLY if there is only a label row, 
            //  but frameworks tend to get confused if they want to 
            //  later insert data. 
            // 
//          *prwStatus = m_iDataRows > 0 ? OSPRW_MIXED : OSPRW_READONLY; 
            *prwStatus = OSPRW_MIXED; 
        } 
        else if (iRow == 0) 
            *prwStatus = OSPRW_READONLY; 
        else 
            *prwStatus = OSPRW_DEFAULT; 
    } 
    else 
        hr = E_INVALIDARG; 
    return hr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    GetVariant() 
// 
//  Synopsis:  Retrieves a variant value for a cell. 
// 
//  Arguments: iRow            row index 
//             iCols           column index 
//             format          output format 
//             pVar            pointer to storage for resulting value 
// 
//  Returns:   S_OK upon success (contents of pVar set). 
//             E_INVALIDARG if indices are out of bounds. 
//             E_UNEXPECTED if the table has not been loaded yet. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::getVariant(LONG iRow, LONG iCol, OSPFORMAT format, VARIANT *pVar) 
{ 
    HRESULT hr  = S_OK; 
 
    if (fValidFilterCell(iRow, iCol)) 
    { 
        CTDCCell    *pCell  = GetFilterCell(iRow, iCol); 
 
        if (format == OSPFORMAT_RAW) 
        { 
            //  Copy the raw variant value 
            // 
            hr = VariantCopy(pVar, pCell); 
        } 
        else if (format == OSPFORMAT_FORMATTED || format == OSPFORMAT_HTML) 
        { 
            //  Construct a BSTR value representing the cell 
            // 
            if (pCell->vt == VT_BOOL) 
            { 
                //  For OLE DB spec compliance: 
                //    VariantChangeTypeEx converts booleans in BSTR "0", "-1". 
                //    This code yields BSTR "False", "True" instead. 
                // 
                VariantClear(pVar); 
                pVar->vt = VT_BSTR; 
                hr = VarBstrFromBool(pCell->boolVal, m_lcid, 0, &pVar->bstrVal); 
            } 
            else 
            { 
                hr = VariantChangeTypeEx(pVar, pCell, m_lcid, 0, VT_BSTR); 
            } 
            if (!SUCCEEDED(hr)) 
            { 
                VariantClear(pVar); 
                pVar->vt = VT_BSTR; 
                pVar->bstrVal = SysAllocString(L"#Error"); 
            } 
        } 
        else 
            hr = E_INVALIDARG; 
    } 
    else 
        hr = E_INVALIDARG; 
 
    return hr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    SetVariant() 
// 
//  Synopsis:  Sets a cell's variant value from a given variant value. 
//             The given variant type is coerced into the column's 
//             underlying type. 
// 
//  Arguments: iRow            row index 
//             iCols           column index 
//             format          output format 
//             Var             value to be stored in the cell. 
// 
//  Returns:   S_OK upon success (contents of pVar set). 
//             E_INVALIDARG if indices are out of bounds. 
//             E_UNEXPECTED if the table has not been loaded yet. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::setVariant(LONG iRow, LONG iCol, OSPFORMAT format, VARIANT Var) 
{ 
    HRESULT hr; 
 
    if (fValidFilterCell(iRow, iCol)) 
    { 
        CTDCCell    *pCell  = GetFilterCell(iRow, iCol); 
        CTDCColInfo *pColInfo   = GetColInfo(iCol); 
 
        if (format == OSPFORMAT_RAW || 
            format == OSPFORMAT_FORMATTED || format == OSPFORMAT_HTML) 
        { 
            if (m_pEventBroker != NULL) 
            { 
                hr = m_pEventBroker->aboutToChangeCell(iRow, iCol); 
                if (!SUCCEEDED(hr)) 
                    goto Cleanup; 
            } 
 
            if (Var.vt == pColInfo->vtType) 
                hr = VariantCopy(pCell, &Var); 
            else 
            { 
                //  For OLE DB spec compliance: 
                //    VariantChangeTypeEx converts booleans in BSTR "0", "-1". 
                //    This code yields BSTR "False", "True" instead. 
                // 
                if (Var.vt == VT_BOOL && pColInfo->vtType==VT_BSTR) 
                { 
                    VariantClear(pCell); 
                    pCell->vt = VT_BSTR; 
                    hr = VarBstrFromBool(Var.boolVal, m_lcid, 0, &pCell->bstrVal); 
                } 
                else 
                    hr = VariantChangeTypeEx(pCell, &Var, m_lcid, 
                                             0, pColInfo->vtType); 
            } 
            if (SUCCEEDED(hr) && m_pEventBroker != NULL) 
                hr = m_pEventBroker->cellChanged(iRow, iCol); 
            m_fSortFilterDisrupted = TRUE; 
        } 
        else 
            hr = E_INVALIDARG; 
    } 
    else 
        hr = E_INVALIDARG; 
 
Cleanup: 
    return hr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Member:    GetLocale() 
// 
//  Synopsis:  Returns the locale of our data. 
//              
//  Arguments: Returns a BSTR, representing an RFC1766 form string for our 
//             locale.  Note this may not necessarily match our LANGUAGE 
//             param, if we had one, because teh string is canoncialized 
//             by using MLang to convert it to an LCID and back to a string 
//             again. 
// 
//  Returns:   S_OK to indicate success. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::getLocale(BSTR *pbstrLocale) 
{ 
    return m_pML->GetRfc1766FromLcid(m_lcid, pbstrLocale); 
} 
 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    DeleteRows 
// 
//  Synopsis:  Used to delete rows from the table.  Bounds are checked 
//             to make sure that the rows can all be deleted.  Label row 
//             cannot be deleted. 
// 
//  Arguments: iRow            first row to delete 
//             cRows           number of rows to delete 
//             pcRowsDeleted   actual number of rows deleted (OUT) 
// 
//  Returns:   S_OK upon success, i.e. all rows could be deleted 
//             E_INVALIDARG if cRows < 0 or any rows to be deleted 
//               are out of bounds 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::deleteRows(LONG iRow, LONG cRows, LONG *pcRowsDeleted) 
{ 
    HRESULT hr; 
    *pcRowsDeleted = 0; 
 
    if (fValidFilterRow(iRow) && iRow > 0 && cRows >= 0 && 
        fValidFilterRow(iRow + cRows - 1)) 
    { 
        if (m_pEventBroker != NULL) 
        { 
            hr = m_pEventBroker->aboutToDeleteRows(iRow, cRows); 
            if (!SUCCEEDED(hr)) 
                goto Cleanup; 
        } 
 
        *pcRowsDeleted = cRows; 
        hr = S_OK; 
        if (cRows > 0) 
        { 
            //  Delete the rows from the array 
            // 
            m_arrparrFilter.DeleteElems(iRow, cRows); 
            m_iFilterRows = CalcFilterRows(); 
 
            m_fSortFilterDisrupted = TRUE; 
            //  Notify the event-handler of the deletion 
            // 
            if (m_pEventBroker != NULL) 
                hr = m_pEventBroker->deletedRows(iRow, cRows); 
        } 
    } 
    else 
    { 
        hr = E_INVALIDARG; 
    } 
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    InsertRows() 
// 
//  Synopsis:  Allows for the insertion of new rows.  This can either be 
//             used to insert new rows between existing rows, or to 
//             append new rows to the end of the table.  Thus, to 
//             insert new rows at the end of the table, a user would 
//             specify the initial row as 1 greater than the current 
//             row dimension. 
//             Note that iRow is checked to ensure that it is within the 
//             proper bounds (1..<current # of rows>+1). 
//             User cannot delete column heading row. 
// 
//  Arguments: iRow            rows will be inserted *before* row 'iRow' 
//             cRows           how many rows to insert 
//             pcRowsInserted  actual number of rows inserted (OUT) 
// 
//  Returns:   S_OK upon success, i.e. all rows could be inserted. 
//             E_INVALIDARG if row is out of allowed bounds. 
//             It is possible that fewer than the requested rows were 
//             inserted.  In this case, E_OUTOFMEMORY would be returned, 
//             and the actual number of rows inserted would be set. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::insertRows(LONG iRow, LONG cRows, LONG *pcRowsInserted) 
{ 
    HRESULT hr  = S_OK; 
    TSTDArray<CTDCCell> **pRows = NULL; 
    LONG    iTmpRow; 
 
    //  Verify that the insertion row is within range 
    // 
    if (iRow < 1 || iRow > m_iFilterRows + 1) 
    { 
        hr = E_INVALIDARG; 
        goto Cleanup; 
    } 
 
    if (cRows <= 0) 
    { 
        if (cRows < 0) 
            hr = E_INVALIDARG; 
        goto Cleanup; 
    } 
 
    //  Unless success is complete, assume 0 rows inserted. 
    // 
    *pcRowsInserted = 0; 
 
    //  Allocate a temporary array of rows 
    // 
    pRows = new TSTDArray<CTDCCell>* [cRows]; 
    if (pRows == NULL) 
    { 
        hr = E_OUTOFMEMORY; 
        goto Cleanup; 
    } 
 
    memset(pRows, '\0', sizeof(pRows[0]) * cRows); 
 
    for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) 
    { 
        if ((pRows[iTmpRow] = new TSTDArray<CTDCCell>) == NULL) 
        { 
            hr = E_OUTOFMEMORY; 
            goto CleanupTmpRows; 
        } 
        hr = pRows[iTmpRow]->InsertElems(0, m_iCols); 
        if (!SUCCEEDED(hr)) 
            goto CleanupTmpRows; 
    } 
 
 
    //  Expand the Cell-Rows and Filter-Rows arrays to cope with the new rows. 
    // 
    _ASSERT(m_iFilterRows <= m_iDataRows); 
    hr = m_arrparrCells.InsertElems(iRow, cRows); 
    if (!SUCCEEDED(hr)) 
        goto CleanupTmpRows; 
    hr = m_arrparrFilter.InsertElems(iRow, cRows); 
    if (!SUCCEEDED(hr)) 
    { 
        //  Undo the previous allocation 
        // 
        m_arrparrCells.DeleteElems(iRow, cRows); 
        goto CleanupTmpRows; 
    } 
 
    if (m_pEventBroker != NULL) 
    { 
        hr = m_pEventBroker->aboutToInsertRows(iRow, cRows); 
        if (FAILED(hr)) 
            goto CleanupTmpRows; 
    } 
     
    //  Copy across the row pointers 
    // 
    for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) 
    { 
        m_arrparrCells[iRow + iTmpRow] = pRows[iTmpRow]; 
        m_arrparrFilter[iRow + iTmpRow] = pRows[iTmpRow]; 
    } 
 
    //  Return indicating success 
    // 
    *pcRowsInserted = cRows; 
    m_iFilterRows = CalcFilterRows();; 
    m_iDataRows = CalcDataRows(); 
 
    // Fire events: 
    if (*pcRowsInserted != 0) 
    { 
        m_fSortFilterDisrupted = TRUE; 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->insertedRows(iRow, cRows); 
    } 
    goto Cleanup; 
 
CleanupTmpRows: 
    //  Free the memory associated with the tmp rows. 
    // 
    for (iTmpRow = 0; iTmpRow < cRows; iTmpRow++) 
        if (pRows[iTmpRow] != NULL) 
            delete pRows[iTmpRow]; 
 
Cleanup: 
    if (pRows != NULL) 
        delete pRows; 
    return hr; 
} 
 
// ;begin_internal 
//+----------------------------------------------------------------------- 
// 
//  Member:    DeleteColumns() 
// 
//  Synopsis:  Used to delete columns from the table.  Bounds are checked 
//             to make sure that the columns can all be deleted.  Label 
//             column cannot be deleted. 
// 
//  Arguments: iCol               first column to delete 
//             cCols              number of columns to delete 
//             pcColsDeleted      actual number of rows deleted (OUT) 
// 
//  Returns:   S_OK upon succes, i.e. all columns could be deleted 
//             E_INVALIDARG if column is out of allowed bounds. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::DeleteColumns(LONG iCol, LONG cCols, LONG *pcColsDeleted) 
{ 
    HRESULT hr; 
 
    if (fValidCol(iCol) && iCol > 0 && cCols >= 0 && 
        fValidCol(iCol + cCols - 1)) 
    { 
        *pcColsDeleted = cCols; 
        hr = S_OK; 
        if (cCols > 0) 
        { 
            for (LONG iRow = 0; iRow < m_iFilterRows; iRow++) 
            { 
                TSTDArray<CTDCCell> *pRow; 
 
                pRow = m_arrparrCells[iRow]; 
                pRow->DeleteElems(iCol - 1, cCols); 
            } 
            m_arrColInfo.DeleteElems(iCol - 1, cCols); 
            m_iCols = CalcCols(); 
 
            if (!m_fUseHeader) 
                RenumberColumnHeadings(); 
 
            m_fSortFilterDisrupted = TRUE; 
 
            //  Notify the event-handler of the deletion 
            // 
#ifdef NEVER 
            if (m_pEventBroker != NULL) 
                hr = m_pEventBroker->DeletedCols(iCol, cCols); 
#endif 
        } 
        _ASSERT(m_arrColInfo.GetSize() == (ULONG) m_iCols); 
    } 
    else 
    { 
        hr = E_INVALIDARG; 
        *pcColsDeleted = 0; 
    } 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    InsertColumns() 
// 
//  Synopsis:  Allows for the insertion of new columns.  This can either be 
//             used to insert new columns between existing columns, or to 
//             append new columns to the end of the table.  Thus, to 
//             insert new columns at the end of the table, a user would 
//             specify the initial columns as 1 greater than the current 
//             columns dimension. 
//             Note that iColumn is checked to ensure that it is within the 
//             proper bounds (1..<current # of cols>+1). 
// 
//  Arguments: iCol            columns will be inserted *before* row 'iCol' 
//             cCols           how many columns to insert 
//             pcColsInserted  actual number of columns inserted (OUT) 
// 
//  Returns:   S_OK upon success, i.e. all columns could be inserted. 
//             E_INVALIDARG if column is out of allowed bounds. 
//             It is possible that fewer than the requested columns were 
//             inserted.  In this case, E_OUTOFMEMORY would be returned, 
//             and the actual number of columns inserted would be set. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::InsertColumns(LONG iCol, LONG cCols, LONG *pcColsInserted) 
{ 
    HRESULT hr  = S_OK; 
    LONG iTmpRow; 
 
    //  Verify that the insertion column is within range 
    // 
    if (iCol < 1 || iCol > m_iCols + 1) 
    { 
        hr = E_INVALIDARG; 
        goto Cleanup; 
    } 
 
    if (cCols <= 0) 
    { 
        if (cCols < 0) 
            hr = E_INVALIDARG; 
        goto Cleanup; 
    } 
 
    //  Unless success is complete, assume 0 columns inserted. 
    // 
    *pcColsInserted = 0; 
 
    for (iTmpRow = 0; iTmpRow <= m_iDataRows; iTmpRow++) 
    { 
        hr = m_arrparrCells[iTmpRow]->InsertElems(iCol, cCols); 
        if (!SUCCEEDED(hr)) 
        { 
            //  Undo the changes we've done 
            // 
            while (--iTmpRow >= 0) 
                m_arrparrCells[iTmpRow]->DeleteElems(iCol, cCols); 
            goto Cleanup; 
        } 
    } 
 
    //  Return indicating success 
    // 
    *pcColsInserted = cCols; 
    m_iCols = CalcCols(); 
     
    // Fire events: 
    if (*pcColsInserted != 0) 
    { 
        m_fSortFilterDisrupted = TRUE; 
#ifdef NEVER 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->InsertedCols(iCol, cCols); 
#endif 
    } 
 
Cleanup: 
    //  If we're using automatically numbered column headings and some 
    //  columns were inserted, then renumber the columns. 
    // 
    if (*pcColsInserted > 0 && !m_fUseHeader) 
        RenumberColumnHeadings(); 
 
    return hr; 
} 
// ;end_internal 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    Find() 
// 
//  Synopsis:  Searches for a row matching the specified criteria 
// 
//  Arguments: iRowStart       The starting row for the search 
//             iCol            The column being tested 
//             vTest           The value against which cells in the 
//                               test column are tested 
//             findFlags       Flags indicating whether to search up/down 
//                               and whether comparisons are case sensitive. 
//             compType        The comparison operator for matching (find a 
//                             cell =, >=, <=, >, <, <> the test value) 
//             piRowFound      The row with a matching cell [OUT] 
// 
//  Returns:   S_OK upon success, i.e. a row was found (piRowFound set). 
//             E_FAIL upon failure, i.e. a row was not found. 
//             E_INVALIDARG if starting row 'iRowStart' or test column 'iCol' 
//               are out of bounds. 
//             DISP_E_TYPEMISMATCH if the test value's type does not match 
//               the test column's type. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::find(LONG iRowStart, LONG iCol, VARIANT vTest, 
        OSPFIND findFlags, OSPCOMP compType, LONG *piRowFound) 
{ 
    HRESULT hr = S_OK; 
    boolean fUp = FALSE; 
    boolean fCaseSensitive  = FALSE; 
    LONG    iRow; 
 
    *piRowFound = -1; 
 
    //  Validate arguments 
    // 
    if (iRowStart < 1 || !fValidFilterRow(iRowStart) || !fValidCol(iCol)) 
    { 
        hr = E_INVALIDARG; 
        goto Cleanup; 
    } 
 
    if (GetColInfo(iCol)->vtType != vTest.vt) 
    { 
        //  Search-value type does not match the underlying column type 
        //  Fail as per spec. 
        // 
        hr = DISP_E_TYPEMISMATCH; 
        goto Cleanup; 
    } 
 
    if ((findFlags & OSPFIND_UP) != 0) 
        fUp = TRUE; 
    if ((findFlags & OSPFIND_CASESENSITIVE) != 0) 
        fCaseSensitive = TRUE; 
 
    for (iRow = iRowStart; 
         fUp ? iRow > 0 : iRow <= m_iFilterRows; 
         fUp ? iRow-- : iRow++) 
    { 
        int         iCmp = VariantComp(GetFilterCell(iRow, iCol), &vTest, vTest.vt, fCaseSensitive); 
        boolean     fFound  = FALSE; 
 
        switch (compType) 
        { 
        case OSPCOMP_LT:    fFound = iCmp <  0; break; 
        case OSPCOMP_LE:    fFound = iCmp <= 0; break; 
        case OSPCOMP_GT:    fFound = iCmp >  0; break; 
        case OSPCOMP_GE:    fFound = iCmp >= 0; break; 
        case OSPCOMP_EQ:    fFound = iCmp == 0; break; 
        case OSPCOMP_NE:    fFound = iCmp != 0; break; 
        default: 
            hr = E_INVALIDARG; 
            goto Cleanup; 
        } 
        if (fFound) 
        { 
            *piRowFound = iRow; 
            hr = S_OK; 
            goto Cleanup; 
        } 
    } 
 
    hr = E_FAIL; 
         
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    addOLEDBSimpleProviderListener() 
// 
//  Synopsis:  Sets or clears a reference to the COM object which receives 
//             notifications of cell changes, row/column insert/deletes etc. 
// 
//  Arguments: pEvent          Pointer to the COM object to receive 
//                             notifications, or NULL if no notifications 
//                             are to be sent. 
// 
//  Returns:   S_OK upon success. 
//             Error code upon success. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCArr::addOLEDBSimpleProviderListener(OLEDBSimpleProviderListener *pSTDEvents) 
{ 
    HRESULT hr = S_OK; 
 
    if (m_pEventBroker == NULL) 
        hr = E_FAIL; 
    else 
    { 
        hr = m_pEventBroker->SetSTDEvents(pSTDEvents); 
        // If the event sink has been added, and we're already loaded, 
        // then fire transferComplete, because we probably couldn't before. 
        if (LS_LOADED==m_state) 
            m_pEventBroker->STDLoadCompleted(); 
    } 
    return hr; 
} 
 
STDMETHODIMP 
CTDCArr::removeOLEDBSimpleProviderListener(OLEDBSimpleProviderListener *pSTDEvents) 
{ 
    HRESULT hr = S_OK; 
 
    if (m_pEventBroker && pSTDEvents==m_pEventBroker->GetSTDEvents()) 
        hr = m_pEventBroker->SetSTDEvents(NULL); 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    FindCol() 
// 
//  Synopsis:  Locate a column by name 
// 
//  Arguments: pwchColName        Name of column to locate 
// 
//  Returns:   +ve Column number upon success (column name matched) 
//             0 upon failure (no column name matched) 
// 
//------------------------------------------------------------------------ 
 
LONG 
CTDCArr::FindCol(LPWCH pwchColName) 
{ 
    LONG iCol = 0; 
 
    if (pwchColName != NULL) 
    { 
        for (iCol = m_iCols; iCol > 0; iCol--) 
{ 
            CTDCCell    *pCell  = GetDataCell(0, iCol); 
 
            _ASSERT(pCell->vt == VT_BSTR); 
            if (wch_icmp(pwchColName, pCell->bstrVal) == 0) 
                break; 
        } 
    } 
    return iCol; 
} 
 
class SortElt 
{ 
public: 
    CTDCArr *pInstance; 
    int     iRow; 
    TSTDArray<CTDCCell> *   parrRow; 
}; 
 
//+----------------------------------------------------------------------- 
// 
//  Function:  CompareSort() 
// 
//  Synopsis:  Called by qsort() to order the rows of a table. 
// 
//  Arguments: pElt1, pElt1       pointers to elements to be compared 
// 
//  Returns:   -1 if the first element is less than the second element 
//              0 if the first element equals the second element 
//             +1 if the first element is greater than the second element 
// 
//------------------------------------------------------------------------ 
 
static int 
CompareSort(const void *pElt1, const void *pElt2) 
{ 
    SortElt *pse1   = (SortElt *) pElt1; 
    SortElt *pse2   = (SortElt *) pElt2; 
 
    return pse1->pInstance->SortComp(pse1->iRow, pse2->iRow); 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Function:  extract_num() 
// 
//  Synopsis:  Extracts the first non-negative number from the character 
//             stream referenced by 'ppwch'.  Updates 'ppwch' to point 
//             to the character following the digits found. 
// 
//  Arguments: ppwch      Pointer to null-terminated WCHAR string 
// 
//  Returns:   Non-negative number extracted upon success (pointer updated) 
//             -1 upon failure (no digits found; pointer moved to end-of-string 
// 
//+----------------------------------------------------------------------- 
 
static int 
extract_num(WCHAR **ppwch) 
{ 
    int retval  = 0; 
    boolean fFoundDigits    = FALSE; 
 
    if (*ppwch != NULL) 
    { 
        //  Skip over leading non-digits 
        // 
        while ((**ppwch) != 0 && ((**ppwch) < L'0' || (**ppwch) > L'9')) 
            (*ppwch)++; 
 
        //  Accumulate digits 
        // 
        fFoundDigits = *ppwch != 0; 
        while ((**ppwch) >= L'0' && (**ppwch) <= L'9') 
            retval = 10 * retval + *(*ppwch)++ - L'0'; 
    } 
 
    return fFoundDigits ? retval : -1; 
} 
 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    CreateNumberedColumnHeadings() 
// 
//  Synopsis:  Allocates cells for numbered column headings. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if there was insufficient memory to complete 
//             the operation. 
// 
//------------------------------------------------------------------------ 
 
HRESULT CTDCArr::CreateNumberedColumnHeadings() 
{ 
    HRESULT hr  = S_OK; 
    LONG    iCol; 
 
    iCol = m_iCols; 
 
    //  Allocate a new row entry 
    // 
    hr = m_arrparrCells.InsertElems(0, 1); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    //  Allocate a new row of cells 
    // 
    m_arrparrCells[0] = new TSTDArray<CTDCCell>; 
    if (m_arrparrCells[0] == NULL) 
    { 
        hr = E_OUTOFMEMORY; 
        goto Cleanup; 
    } 
    hr = m_arrparrCells[0]->InsertElems(0, iCol); 
 
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    RenumberColumnHeadings() 
// 
//  Synopsis:  Set the automatic name "Column<column-number>" for each column 
// 
//  Arguments: None. 
// 
//  Returns:   Nothing. 
// 
//------------------------------------------------------------------------ 
 
void CTDCArr::RenumberColumnHeadings() 
{ 
    for (LONG iCol = m_iCols; iCol > 0; iCol--) 
    { 
        CTDCCell    *pCell  = GetDataCell(0, iCol); 
        WCHAR       awchLabel[20]; 
 
        wch_cpy(awchLabel, L"Column"); 
        _ltow(iCol, &awchLabel[6], 10); 
 
        pCell->clear(); 
        pCell->vt = VT_BSTR; 
        pCell->bstrVal = SysAllocString(awchLabel); 
    } 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    ParseColumnHeadings() 
// 
//  Synopsis:  Extracts type information (if present) from column 
//             headings, removes leadning 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if there was insufficient memory to complete 
//             the operation. 
// 
//------------------------------------------------------------------------ 
 
HRESULT CTDCArr::ParseColumnHeadings() 
{ 
    LPWCH   pwchIntType     = L"int"; 
    LPWCH   pwchFloatType   = L"float"; 
    LPWCH   pwchStringType  = L"string"; 
    LPWCH   pwchBooleanType = L"boolean"; 
    LPWCH   pwchDateType    = L"date"; 
 
    HRESULT hr  = S_OK; 
    LONG    iCol; 
 
    iCol = m_iCols; 
 
    //  Allocate space for column type info 
    // 
    hr = m_arrColInfo.InsertElems(0, iCol); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    for (; iCol > 0; iCol--) 
    { 
        //  Column headings have the format: 
        //      <column-name>[:<typename>[,<format>]] 
        // 
        CTDCColInfo *pColInfo   = GetColInfo(iCol); 
        CTDCCell    *pCell      = GetDataCell(0, iCol); 
 
        _ASSERT(pCell->vt == VT_BSTR); 
 
        BSTR        bstr; 
        LPWCH       pColon; 
 
        bstr = pCell->bstrVal; 
        pColInfo->vtType = VT_BSTR;     //  Default type for a column is BSTR 
        pColon = wch_chr(bstr, L':'); 
        if (pColon != NULL) 
        { 
            WCHAR   *pwchFormat = NULL; 
            LPWCH   pSpace; 
 
            *pColon++ = 0; 
            pSpace = wch_chr(pColon, L' '); 
 
            if (pSpace != NULL) 
            { 
                *pSpace++ = '\0'; 
                pwchFormat = pSpace; 
            } 
            if (wch_icmp(pColon, pwchIntType) == 0) 
                pColInfo->vtType = VT_I4; 
            else if (wch_icmp(pColon, pwchFloatType) == 0) 
                pColInfo->vtType = VT_R8; 
            else if (wch_icmp(pColon, pwchStringType) == 0) 
                pColInfo->vtType = VT_BSTR; 
            else if (wch_icmp(pColon, pwchBooleanType) == 0) 
                pColInfo->vtType = VT_BOOL; 
            else if (wch_icmp(pColon, pwchDateType) == 0) 
            { 
                pColInfo->vtType = VT_DATE; 
 
                TDCDateFmt  fmt = TDCDF_NULL; 
 
                if (pwchFormat != NULL) 
                { 
                    int nPos    = 0; 
                    int nDayPos = 0; 
                    int nMonPos = 0; 
                    int nYearPos= 0; 
 
                    //  Convert the format string into an internal enum type 
                    //  Find the relative positions of the letters 'D' 'M' 'Y' 
                    // 
                    for (; *pwchFormat != 0; nPos++, pwchFormat++) 
                    { 
                        switch (*pwchFormat) 
                        { 
                        case L'D': 
                        case L'd': 
                            nDayPos = nPos; 
                            break; 
                        case L'M': 
                        case L'm': 
                            nMonPos = nPos; 
                            break; 
                        case L'Y': 
                        case L'y': 
                            nYearPos = nPos; 
                            break; 
                        } 
                    } 
                    //  Compare the relative positions to work out the format 
                    // 
                    if (nDayPos < nMonPos && nMonPos < nYearPos) 
                        fmt = TDCDF_DMY; 
                    else if (nMonPos < nDayPos && nDayPos < nYearPos) 
                        fmt = TDCDF_MDY; 
                    else if (nDayPos < nYearPos && nYearPos < nMonPos) 
                        fmt = TDCDF_DYM; 
                    else if (nMonPos < nYearPos && nYearPos < nDayPos) 
                        fmt = TDCDF_MYD; 
                    else if (nYearPos < nMonPos && nMonPos < nDayPos) 
                        fmt = TDCDF_YMD; 
                    else if (nYearPos < nDayPos && nDayPos < nMonPos) 
                        fmt = TDCDF_YDM; 
                } 
                pColInfo->datefmt = fmt; 
            } 
        } 
 
        if (bstr != NULL) 
        { 
            //  Remove leading/trailing spaces from column name 
            // 
            LPWCH       pwch; 
            LPWCH       pwchDest = NULL; 
            LPWCH       pLastNonSpace = NULL; 
 
            for (pwch = bstr; *pwch != 0; pwch++) 
            { 
                if (!fWCHIsSpace(*pwch)) 
                { 
                    if (pwchDest == NULL) 
                        pwchDest = bstr; 
                    pLastNonSpace = pwchDest; 
                } 
                if (pwchDest != NULL) 
                    *pwchDest++ = *pwch; 
            } 
            if (pLastNonSpace == NULL) 
                bstr[0] = 0;        // all spaces!  Make it null string. 
            else 
                pLastNonSpace[1] = 0; 
        } 
 
        //  Copy the modified column header and free the original 
        // 
        pCell->bstrVal = SysAllocString(bstr); 
        SysFreeString(bstr); 
 
        if (pCell->bstrVal == NULL) 
        { 
            hr = E_OUTOFMEMORY; 
            goto Cleanup; 
        } 
    } 
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    VariantFromBSTR() 
// 
//  Synopsis:  Convert a BSTR value into a variant compatible with a 
//             given column type. 
// 
//  Arguments: pVar       Pointer to resultant variant value 
//             bstr       Source BSTR 
//             pColInfo   Column information (type, format options etc). 
//             lcid       Locale # for locale-specific conversion. 
// 
//  Returns:   S_OK upon success (pVar set) 
//             OLE_E_CANTCONVERT if the given BSTR is badly formatted 
//               (e.g. contains an invalid date value for a date conversion). 
//             E_OUTOFMEMORY if insufficient memory is available for 
//               a conversion. 
// 
//------------------------------------------------------------------------ 
 
HRESULT CTDCArr::VariantFromBSTR(VARIANT *pVar, BSTR bstr, CTDCColInfo *pColInfo, LCID lcid) 
{ 
    HRESULT hr  = E_FAIL; 
 
    VariantInit(pVar); 
    switch (pColInfo->vtType) 
    { 
    case VT_DATE: 
        if (pColInfo->datefmt != TDCDF_NULL) 
        { 
            //  Parse the date string according to specified format. 
            //  First, find the three numeric components in the date. 
            // 
            int  n1; 
            int  n2; 
            int  n3; 
            WCHAR   *pwch   = bstr; 
            SYSTEMTIME  st; 
 
            n1 = extract_num(&pwch); 
            n2 = extract_num(&pwch); 
            n3 = extract_num(&pwch); 
 
            memset(&st, '\0', sizeof(st)); 
            switch (pColInfo->datefmt) 
            { 
            case TDCDF_DMY: 
                st.wDay = n1; 
                st.wMonth = n2; 
                st.wYear = n3; 
                break; 
            case TDCDF_MDY: 
                st.wDay = n2; 
                st.wMonth = n1; 
                st.wYear = n3; 
                break; 
            case TDCDF_DYM: 
                st.wDay = n1; 
                st.wMonth = n3; 
                st.wYear = n2; 
                break; 
            case TDCDF_MYD: 
                st.wDay = n3; 
                st.wMonth = n1; 
                st.wYear = n2; 
                break; 
            case TDCDF_YMD: 
                st.wDay = n3; 
                st.wMonth = n2; 
                st.wYear = n1; 
                break; 
            case TDCDF_YDM: 
                st.wDay = n2; 
                st.wMonth = n3; 
                st.wYear = n1; 
                break; 
            } 
 
            VariantClear(pVar); 
            if (n1 >= 0 && n2 >= 0 && n3 >= 0 && 
                SystemTimeToVariantTime(&st, &pVar->date)) 
            { 
                pVar->vt = VT_DATE; 
                hr = S_OK; 
            } 
            else 
                hr = OLE_E_CANTCONVERT; 
        } 
        else 
        { 
            //  No date format specified - just use the default conversion 
            // 
            VARIANT vSrc; 
 
            VariantInit(&vSrc); 
            vSrc.vt = VT_BSTR; 
            vSrc.bstrVal = bstr; 
            hr = VariantChangeTypeEx(pVar, &vSrc, lcid, 0, pColInfo->vtType); 
        } 
        break; 
    case VT_BOOL: 
    case VT_I4: 
    case VT_R8: 
    default: 
        // 
        //  Perform a standard conversion. 
        // 
        { 
            VARIANT vSrc; 
 
            VariantInit(&vSrc); 
            vSrc.vt = VT_BSTR; 
            vSrc.bstrVal = bstr; 
            hr = VariantChangeTypeEx(pVar, &vSrc, lcid, 0, pColInfo->vtType); 
        } 
        break; 
    case VT_BSTR: 
        // 
        //  Duplicate the BSTR 
        // 
        pVar->bstrVal = SysAllocString(bstr); 
        if (bstr != NULL && pVar->bstrVal == NULL) 
            hr = E_OUTOFMEMORY; 
        else 
        { 
            pVar->vt = VT_BSTR; 
            hr = S_OK; 
        } 
        break; 
    } 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Member:    VariantComp() 
// 
//  Synopsis:  Compares two variant values. 
// 
//  Arguments: pVar1            First variant value 
//             pVar2            Second variant value 
//             fCaseSensitive   TRUE if string comparisons should be 
//                              case-sensitive, false if string comparisons 
//                              should be case-insensitive.  Ignored if 
//                              non-string data types are being compared. 
// 
//  Returns:   -1 if  
//             OLE_E_CANTCONVERT if the given BSTR is badly formatted 
//               (e.g. contains an invalid date value for a date conversion). 
//             E_OUTOFMEMORY if insufficient memory is available for 
//               a conversion. 
// 
//------------------------------------------------------------------------ 
 
int CTDCArr::VariantComp(VARIANT *pVar1, VARIANT *pVar2, VARTYPE type, 
    boolean fCaseSensitive) 
{ 
    int retval = 0; 
 
    //  NULLs are lexically less than anything else 
    // 
    if (pVar1->vt == VT_NULL) 
        retval = pVar2->vt == VT_NULL ? 0 : -1; 
    else if (pVar2->vt == VT_NULL) 
        retval = 1; 
    else if (pVar1->vt != type) 
    { 
        //  Type-mismatches are lexically greater than anything else 
        // 
        retval = pVar2->vt == type ? 1 : 0; 
    } 
    else if (pVar2->vt != type) 
    { 
        //  Type-mismatches are lexically greater than anything else 
        // 
        retval = -1; 
    } 
    else 
    { 
        switch (type) 
        { 
        case VT_I4: 
            retval = pVar1->lVal < pVar2->lVal 
                ? -1 
                : pVar1->lVal > pVar2->lVal 
                    ? 1 
                    : 0; 
            break; 
        case VT_R8: 
            retval = pVar1->dblVal < pVar2->dblVal 
                ? -1 
                : pVar1->dblVal > pVar2->dblVal 
                    ? 1 
                    : 0; 
            break; 
        case VT_BSTR: 
            retval = fCaseSensitive 
                ? wch_cmp(pVar1->bstrVal, pVar2->bstrVal) 
                : wch_icmp(pVar1->bstrVal, pVar2->bstrVal); 
            break; 
        case VT_BOOL: 
            retval = pVar1->boolVal 
                ? (pVar2->boolVal ? 0 : 1) 
                : (pVar2->boolVal ? -1 : 0); 
            break; 
        case VT_DATE: 
            retval = pVar1->date < pVar2->date 
                ? -1 
                : pVar1->date > pVar2->date 
                    ? 1 
                    : 0; 
            break; 
        default: 
            retval = 0;     //  Unrecognised types are all lexically equal 
            break; 
        } 
    } 
 
    return retval; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    CreateSortList() 
// 
//  Synopsis:  Creates a list of sort criteria from a text description. 
// 
//  Arguments: bstrSortCols          ';'-separted list of column names, 
//                                   optionally prefixed with '+' (default) 
//                                   or '-' indicating ascending or descending 
//                                   sort order respectively for that column. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if insufficient memory is available for 
//               the construction of sort criteria. 
// 
//  Side Effect:   Saves created list in m_pSortList 
// 
//+----------------------------------------------------------------------- 
 
HRESULT CTDCArr::CreateSortList(BSTR bstrSortCols) 
{ 
    HRESULT     hr = S_OK; 
 
    if (m_pSortList != NULL) 
    { 
        delete m_pSortList; 
        m_pSortList = NULL; 
    } 
    if (bstrSortCols != NULL) 
    { 
        WCHAR   *pwchEnd  = bstrSortCols; 
        CTDCSortCriterion   **pLast = &m_pSortList; 
 
        while (*pwchEnd != 0) 
        { 
            WCHAR   *pwchStart  = pwchEnd; 
            boolean fSortAscending = TRUE; 
 
            //  Discard leading white space and field-separators 
            // 
            while (*pwchStart == L';' || fWCHIsSpace(*pwchStart)) 
                pwchStart++; 
             
            //  Strip off optional direction indicator + white space 
            // 
            if (*pwchStart == L'+' || *pwchStart == '-') 
            { 
                fSortAscending = *pwchStart++ == L'+'; 
                while (fWCHIsSpace(*pwchStart)) 
                    pwchStart++; 
            } 
 
            //  Find the field terminator. 
            //  Strip out trailing white spaces. 
            // 
            for (pwchEnd = pwchStart; *pwchEnd != 0 && *pwchEnd != L';';) 
                pwchEnd++; 
 
            while (pwchStart < pwchEnd && fWCHIsSpace(pwchEnd[-1])) 
                pwchEnd--; 
 
            //  Ignore blank column names - this could be the result of 
            //  a leading or trailing ';'. 
            // 
            if (pwchStart >= pwchEnd) 
                continue; 
 
            //  Find the column number from the column name 
            // 
            BSTR bstrColName = SysAllocStringLen(pwchStart, pwchEnd - pwchStart); 
 
            if (bstrColName == NULL) 
            { 
                hr = E_OUTOFMEMORY; 
                goto Cleanup; 
            } 
            LONG iCol = FindCol(bstrColName); 
            SysFreeString(bstrColName); 
 
            if (iCol > 0) 
            { 
                //  Allocate a node for this criterion 
                // 
                _ASSERT(*pLast == NULL); 
                *pLast = new CTDCSortCriterion; 
                if (*pLast == NULL) 
                { 
                    hr = E_OUTOFMEMORY; 
                    goto Cleanup; 
                } 
                (*pLast)->m_fSortAscending = fSortAscending; 
                (*pLast)->m_iSortCol = iCol; 
                pLast = &(*pLast)->m_pNext; 
            } 
        } 
    } 
 
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    SortComp() 
// 
//  Synopsis:  Compares two rows using the elememts in the columns specified 
//             by the current sort criteria. 
// 
//  Arguments: iRow1      Index of first row being compared 
//             iRow2      Index of second row being compared 
// 
//  Returns:   -1 if first row should be sorted before the second row 
//             0 if rows are equal 
//             1 if first row should be sorted after the second row 
// 
//+----------------------------------------------------------------------- 
 
int CTDCArr::SortComp(LONG iRow1, LONG iRow2) 
{ 
    CTDCSortCriterion   *pCriterion; 
    int     cmp = 0; 
 
    for (pCriterion = m_pSortList; 
         pCriterion != NULL && cmp == 0; 
         pCriterion = pCriterion->m_pNext) 
    { 
        CTDCCell    *pCell1 = GetFilterCell(iRow1, pCriterion->m_iSortCol); 
        CTDCCell    *pCell2 = GetFilterCell(iRow2, pCriterion->m_iSortCol); 
 
        cmp = VariantComp(pCell1, pCell2, GetColInfo(pCriterion->m_iSortCol)->vtType, 
                         m_fCaseSensitive); 
        if (!pCriterion->m_fSortAscending) 
            cmp = -cmp; 
    } 
    return cmp; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    FilterParseComplex() 
// 
//  Synopsis:  Takes the text of a filter query, parses it and creates 
//             a tree of CTDCFilterNode representing the query. 
// 
//  Arguments: phr: pointer to HRESULT value, set to indicate success/failure. 
// 
//             ppwchQuery:  This is a textual representation of a query.  The 
//                          query language syntax is: 
// 
//               Query ::== Complex 
// 
//               Complex ::== Simple 
//                       ::== Simple '&' Simple ( '&' Simple ... ) 
//                       ::== Simple '|' Simple ( '|' Simple ... ) 
// 
//               Simple ::== '(' Complex ')' 
//                      ::== Atom Relop Atom 
// 
//               Relop ::== '=' | '>' | '>=' | '<' | '<=' | '<>' 
// 
//               Atom ::== Bunch of characters up to a (, ), >, <, =, & or | 
//                         If it's recognisable as field name, then it's 
//                         treated as a field name.  Otherwise it's treated 
//                         as a value.  Quotes (") are processed, and force 
//                         the atom to be treated as a value.  Escape 
//                         characters (\) are processed and allow the 
//                         use of special characters within a field name. 
// 
//               Notes: 
//               ----- 
//                  * The definition of 'Complex' expressly forbids mixing 
//                    logical ANDs and ORs ('&' and '|') unless parentheses 
//                    are used to clarify the query.  Something like: 
//                          field1 > 2 & field3 = "lime" | field4 < 5 
//                    is illegal, but: 
//                          (field1 > 2 & field3 = "lime") | field4 < 5 
//                    is fine. 
// 
//                  * It is illegal to attempt a comparison of two columns 
//                    with different types. 
// 
// 
//  Returns:   Pointer to parsed Filter Node upon success (*phr set to S_OK) 
//             NULL upon failure (*phr set to an appropriate error code) 
// 
//+----------------------------------------------------------------------- 
 
CTDCFilterNode *CTDCArr::FilterParseComplex(LPWCH *ppwchQuery, HRESULT *phr) 
{ 
    *phr = S_OK; 
    CTDCFilterNode  *retval; 
    WCHAR   wchBoolOp   = 0; 
 
    retval = FilterParseSimple(ppwchQuery, phr); 
 
    //  Stop if there's an error, or we encounter a terminating ')' or '\0' 
    // 
    while (retval != NULL && **ppwchQuery != L')' && **ppwchQuery != 0) 
    { 
        //  Next character should be a matching logical connector ... 
        // 
        if (**ppwchQuery != L'&' && **ppwchQuery != L'|') 
        { 
            *phr = E_FAIL; 
            break; 
        } 
        if (wchBoolOp == 0) 
            wchBoolOp = **ppwchQuery; 
        else if (wchBoolOp != **ppwchQuery) 
        { 
            *phr = E_FAIL; 
            break; 
        } 
        (*ppwchQuery)++; 
        CTDCFilterNode *pTmp = new CTDCFilterNode; 
        if (pTmp == NULL) 
        { 
            *phr = E_OUTOFMEMORY; 
            break; 
        } 
        pTmp->m_type = (wchBoolOp == L'&') 
            ? CTDCFilterNode::NT_AND 
            : CTDCFilterNode::NT_OR; 
        pTmp->m_pLeft = retval; 
        retval = pTmp; 
        retval->m_pRight = FilterParseSimple(ppwchQuery, phr); 
        if (retval->m_pRight == NULL) 
            break; 
    } 
    if (!SUCCEEDED(*phr) && retval != NULL) 
    { 
        delete retval; 
        retval = NULL; 
    } 
    return retval; 
} 
 
CTDCFilterNode *CTDCArr::FilterParseSimple(LPWCH *ppwch, HRESULT *phr) 
{ 
    *phr = S_OK; 
    CTDCFilterNode  *retval = NULL; 
 
    fWCHEatSpace(ppwch);    //  Eat up white space 
 
    if (fWCHEatTest(ppwch, L'(')) 
    { 
        retval = FilterParseComplex(ppwch, phr); 
        if (retval != NULL) 
        { 
            if (fWCHEatTest(ppwch, L')')) 
                fWCHEatSpace(ppwch);    //  Eat up white space 
            else 
                *phr = E_FAIL; 
        } 
        goto Cleanup; 
    } 
 
    retval = FilterParseAtom(ppwch, phr); 
    if (retval == NULL) 
        goto Cleanup; 
 
    { 
        CTDCFilterNode *pTmp = new CTDCFilterNode; 
        if (pTmp == NULL) 
        { 
            *phr = E_OUTOFMEMORY; 
            goto Cleanup; 
        } 
        pTmp->m_pLeft = retval; 
        retval = pTmp; 
    } 
 
    retval->m_vt = retval->m_pLeft->m_vt; 
 
    //  Get the relational operator 
    // 
    if (fWCHEatTest(ppwch, L'=')) 
        retval->m_type = CTDCFilterNode::NT_EQ; 
    else if (fWCHEatTest(ppwch, L'>')) 
        retval->m_type = fWCHEatTest(ppwch, L'=') 
            ? CTDCFilterNode::NT_GE 
            : CTDCFilterNode::NT_GT; 
    else if (fWCHEatTest(ppwch, L'<')) 
        retval->m_type = fWCHEatTest(ppwch, L'=') 
                ? CTDCFilterNode::NT_LE 
                : fWCHEatTest(ppwch, L'>') 
                    ? CTDCFilterNode::NT_NE 
                    : CTDCFilterNode::NT_LT; 
    else 
    { 
        *phr = E_FAIL; 
        goto Cleanup; 
    } 
 
    retval->m_pRight = FilterParseAtom(ppwch, phr); 
    if (retval->m_pRight == NULL) 
        goto Cleanup; 
 
    if (retval->m_pLeft->m_iCol <= 0 && retval->m_pRight->m_iCol <= 0) 
    { 
        //  At least one of the atoms being compared must be a column 
        // 
        //  This condition means we don't have to test for comparison 
        //  of two wildcard values. 
        // 
        *phr = E_FAIL; 
        goto Cleanup; 
    } 
 
    //  Check type compatibility of atoms 
    // 
    if (retval->m_pRight->m_vt != retval->m_vt) 
    { 
        CTDCFilterNode  *pSrc = retval->m_pRight; 
        CTDCFilterNode  *pTarg= retval->m_pLeft; 
 
        if (retval->m_pLeft->m_iCol > 0) 
        { 
            if (retval->m_pRight->m_iCol > 0) 
            { 
                //  Two columns of incompatible type - can't resolve 
                // 
                *phr = E_FAIL; 
                goto Cleanup; 
            } 
            pSrc = retval->m_pLeft; 
            pTarg = retval->m_pRight; 
        } 
        _ASSERT(pTarg->m_vt == VT_BSTR); 
        _ASSERT(pTarg->m_iCol == 0); 
        _ASSERT(pSrc->m_iCol > 0); 
        CTDCColInfo *pColInfo = GetColInfo(pSrc->m_iCol); 
        _ASSERT(pColInfo->vtType == pSrc->m_vt); 
        VARIANT vtmp; 
        VariantInit(&vtmp); 
        *phr = VariantFromBSTR(&vtmp, pTarg->m_value.bstrVal, pColInfo, m_lcid); 
        if (!SUCCEEDED(*phr)) 
            goto Cleanup; 
        VariantClear(&pTarg->m_value); 
        pTarg->m_value = vtmp; 
        pTarg->m_vt = pSrc->m_vt; 
        retval->m_vt = pSrc->m_vt; 
    } 
 
Cleanup: 
    if (!SUCCEEDED(*phr) && retval != NULL) 
    { 
        delete retval; 
        retval = NULL; 
    } 
    return retval; 
} 
 
CTDCFilterNode *CTDCArr::FilterParseAtom(LPWCH *ppwch, HRESULT *phr) 
{ 
    *phr = S_OK; 
    CTDCFilterNode  *retval = NULL; 
    int nQuote  = 0; 
    boolean fDone = FALSE; 
    LPWCH   pwchDest; 
    LPWCH   pwchLastStrip; 
 
    fWCHEatSpace(ppwch);    //  Eat up white space 
 
    WCHAR   *pwchTmpBuf = new WCHAR[wch_len(*ppwch) + 1]; 
    if (pwchTmpBuf == NULL) 
    { 
        *phr = E_OUTOFMEMORY; 
        goto Cleanup; 
    } 
 
    pwchDest = pwchTmpBuf; 
    pwchLastStrip = pwchTmpBuf; 
 
    while (**ppwch != 0 && !fDone) 
        switch (**ppwch) 
        { 
        case L'\\': 
            //  Handle escape characters 
            // 
            if ((*pwchDest++ = *++(*ppwch)) != 0) 
                pwchLastStrip = (*ppwch)++; 
            break; 
        case L'"': 
            //  Quotes 
            // 
            pwchLastStrip = (*ppwch)++; 
            nQuote++; 
            break; 
        case L'>': 
        case L'<': 
        case L'=': 
        case L'(': 
        case L')': 
        case L'&': 
        case L'|': 
            if (fDone = ((nQuote & 1) == 0)) 
                break; 
        default: 
            *pwchDest++ = *(*ppwch)++; 
        } 
 
    //  Strip off trailing white space 
    // 
    while (pwchDest > pwchLastStrip && fWCHIsSpace(pwchDest[-1])) 
        pwchDest--; 
    *pwchDest = 0; 
 
    if ((pwchDest == pwchTmpBuf && nQuote == 0) || (nQuote & 1 != 0)) 
    { 
        //  Empty string or mismatched quote 
// 
        *phr = E_FAIL; 
        goto Cleanup; 
    } 
 
    retval = new CTDCFilterNode; 
    if (retval == NULL) 
    { 
        *phr = E_OUTOFMEMORY; 
        goto Cleanup; 
    } 
 
    retval->m_type = CTDCFilterNode::NT_ATOM; 
    retval->m_iCol = nQuote > 0 ? 0 : FindCol(pwchTmpBuf); 
    if (retval->m_iCol == 0) 
    { 
        retval->m_vt = VT_BSTR; 
        retval->m_value.vt = VT_BSTR; 
        retval->m_value.bstrVal = SysAllocString(pwchTmpBuf); 
        if (retval->m_value.bstrVal == NULL) 
        { 
            *phr = E_OUTOFMEMORY; 
            goto Cleanup; 
        } 
        retval->m_fWildcard = wch_chr(retval->m_value.bstrVal, L'*') != NULL; 
    } 
    else 
    { 
        retval->m_vt = GetColInfo(retval->m_iCol)->vtType; 
        retval->m_fWildcard = FALSE; 
    } 
 
Cleanup: 
    if (pwchTmpBuf != NULL) 
        delete pwchTmpBuf; 
    if (!SUCCEEDED(*phr) && retval != NULL) 
    { 
        delete retval; 
        retval = NULL; 
    } 
    return retval; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    EvalDataRow() 
// 
//  Synopsis:  Evaluates the given data row # against the filter query 
//             represented by 'pNode'. 
// 
//  Arguments: iRow      The number of the row to evaluate. 
//             pNode     A filter query to the row against. 
// 
//  Returns:   TRUE if the given row satisfies the filter query. 
//             FALSE otherwise. 
// 
//+----------------------------------------------------------------------- 
 
boolean CTDCArr::EvalDataRow(LONG iRow, CTDCFilterNode *pNode) 
{ 
    boolean retval  = TRUE; 
    VARIANT *pVar1; 
    VARIANT *pVar2; 
 
    _ASSERT(pNode != NULL); 
    switch (pNode->m_type) 
    { 
    case CTDCFilterNode::NT_AND: 
        retval = EvalDataRow(iRow, pNode->m_pLeft) && 
                 EvalDataRow(iRow, pNode->m_pRight); 
        break; 
    case CTDCFilterNode::NT_OR: 
        retval = EvalDataRow(iRow, pNode->m_pLeft) || 
                 EvalDataRow(iRow, pNode->m_pRight); 
        break; 
    case CTDCFilterNode::NT_EQ: 
    case CTDCFilterNode::NT_NE: 
    case CTDCFilterNode::NT_LT: 
    case CTDCFilterNode::NT_GT: 
    case CTDCFilterNode::NT_LE: 
    case CTDCFilterNode::NT_GE: 
        pVar1 = &pNode->m_pLeft->m_value; 
        pVar2 = &pNode->m_pRight->m_value; 
 
        if (pNode->m_pLeft->m_iCol > 0) 
            pVar1 = GetDataCell(iRow, pNode->m_pLeft->m_iCol); 
        if (pNode->m_pRight->m_iCol > 0) 
            pVar2 = GetDataCell(iRow, pNode->m_pRight->m_iCol); 
 
        if ((pNode->m_pLeft->m_fWildcard || pNode->m_pRight->m_fWildcard) && 
            (pNode->m_type == CTDCFilterNode::NT_EQ || 
             pNode->m_type == CTDCFilterNode::NT_NE) && 
             pVar1->vt == VT_BSTR && pVar2->vt == VT_BSTR) 
        { 
            //  Wildcards are only meaningful in comparing strings 
            //  for equlaity / inequality 
            // 
            VARIANT *pText; 
            VARIANT *pPattern; 
 
            if (pNode->m_pLeft->m_fWildcard) 
            { 
                pPattern = pVar1; 
                pText = pVar2; 
            } 
            else 
            { 
                pText = pVar1; 
                pPattern = pVar2; 
            } 
 
            retval = wch_wildcardMatch(pText->bstrVal, pPattern->bstrVal, 
                                       m_fCaseSensitive) 
                ? (pNode->m_type == CTDCFilterNode::NT_EQ) 
                : (pNode->m_type == CTDCFilterNode::NT_NE); 
        } 
        else 
        { 
            int     cmp; 
 
            cmp = VariantComp(pVar1, pVar2, pNode->m_vt, m_fCaseSensitive); 
 
            switch (pNode->m_type) 
            { 
            case CTDCFilterNode::NT_LT:    retval = cmp <  0;  break; 
            case CTDCFilterNode::NT_LE:    retval = cmp <= 0;  break; 
            case CTDCFilterNode::NT_GT:    retval = cmp >  0;  break; 
            case CTDCFilterNode::NT_GE:    retval = cmp >= 0;  break; 
            case CTDCFilterNode::NT_EQ:    retval = cmp == 0;  break; 
            case CTDCFilterNode::NT_NE:    retval = cmp != 0;  break; 
            } 
        } 
        break; 
 
    default: 
        _ASSERT(FALSE); 
    } 
    return retval; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    ApplySortFilterCriteria() 
// 
//  Synopsis:  Resets any filter and sorting criterion for the control to 
//             the values specified.  Initiates sort/filter operations 
//             if appropriate. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if there was insufficient memory to complete 
//               the operation. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::ApplySortFilterCriteria() 
{ 
    HRESULT hr  = S_OK; 
    LONG iRow; 
 
    if (!m_fSortFilterDisrupted || 
        m_state == LS_UNINITIALISED || 
        m_state == LS_LOADING_HEADER_UNAVAILABLE) 
    { 
        //  No change, or can't do anything yet. 
        // 
        goto Cleanup; 
    } 
 
    //  Discard the old parse trees 
    // 
    if (m_pSortList != NULL) 
        delete m_pSortList; 
    if (m_pFilterTree != NULL) 
        delete m_pFilterTree; 
 
    m_pSortList = NULL; 
    m_pFilterTree = NULL; 
 
    //  Discard old filtered rows 
    // 
    if (m_arrparrFilter.GetSize() > 0) 
        m_arrparrFilter.DeleteElems(0, m_arrparrFilter.GetSize()); 
 
    //  Create an array of filter rows from the data rows 
    // 
    hr = m_arrparrFilter.InsertElems(0, m_iDataRows + 1); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
    for (iRow = 0; iRow <= m_iDataRows; iRow++) 
        m_arrparrFilter[iRow] = m_arrparrCells[iRow]; 
    m_iFilterRows = CalcFilterRows(); 
 
    //  Create the filter parse tree 
    // 
    if (m_bstrFilterExpr != NULL) 
    { 
        LPWCH pwchQuery = m_bstrFilterExpr; 
 
        m_pFilterTree = FilterParseComplex(&pwchQuery, &hr); 
        if (hr == E_FAIL || (m_pFilterTree != NULL && *pwchQuery != 0)) 
        { 
            //  Parse failed or there were unparsed characters left over. 
            //  This gets treated as an 'include everything' filter. 
            // 
            if (m_pFilterTree != NULL) 
            { 
                delete m_pFilterTree; 
                m_pFilterTree = NULL; 
            } 
            hr = S_OK; 
        } 
    } 
 
    //  Filter the rows 
    // 
    if (m_pFilterTree != NULL) 
    { 
        LONG    iRowDest    = 1; 
 
        for (iRow = 1; iRow <= m_iFilterRows; iRow++) 
            if (EvalDataRow(iRow, m_pFilterTree)) 
                m_arrparrFilter[iRowDest++] = m_arrparrFilter[iRow]; 
        if (iRowDest < iRow) 
            m_arrparrFilter.DeleteElems(iRowDest, iRow - iRowDest); 
        m_iFilterRows = CalcFilterRows(); 
    } 
 
    //  Create the sort list 
    // 
    hr = CreateSortList(m_bstrSortExpr); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    //  Sort the filtered rows 
    // 
    if (m_pSortList != NULL && m_iFilterRows > 0) 
    { 
        SortElt *pSortArr   = new SortElt[m_iFilterRows + 1]; 
        if (pSortArr == NULL) 
        { 
            hr = E_OUTOFMEMORY; 
            goto Cleanup; 
        } 
        for (iRow = 0; iRow <= m_iFilterRows; iRow++) 
        { 
            pSortArr[iRow].pInstance = this; 
            pSortArr[iRow].iRow = iRow; 
            pSortArr[iRow].parrRow = m_arrparrFilter[iRow]; 
        } 
 
        qsort((void *)&pSortArr[1], m_iFilterRows, sizeof(pSortArr[0]), CompareSort); 
 
        for (iRow = 0; iRow <= m_iFilterRows; iRow++) 
            m_arrparrFilter[iRow] = pSortArr[iRow].parrRow; 
 
        delete pSortArr; 
 
    } 
 
    m_fSortFilterDisrupted = FALSE; 
 
    if (m_state == LS_LOADING_HEADER_AVAILABLE && m_iDataRows > 0) 
    { 
        //  We've just parsed the sort/filter expressions - there 
        //  was no data to sort/filter, so dont register a change. 
    } 
    else 
    { 
        //  Notify the event-broker of the changes 
        // 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->STDDataSetChanged(); 
    } 
 
Cleanup: 
    return hr; 
} 
 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    SetSortFilterCriteria() 
// 
//  Synopsis:  Resets any filter and sorting criterion for the control to 
//             the values specified.  Initiates sort/filter operations 
//             if any the changes invalidate existing criteria. 
// 
//  Arguments: bstrSortExpr    List of columns for sorting ("" = no sorting) 
//             bstrFilterExpr  Expression for filtering ("" = no filtering) 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if there was insufficient memory to complete 
//               the operation. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::SetSortFilterCriteria(BSTR bstrSortExpr, BSTR bstrFilterExpr, 
                              boolean fCaseSensitive) 
{ 
    HRESULT hr  = S_OK; 
 
 
    //  Check if we need to reparse the sort/filter criteria 
    // 
 
    if (wch_cmp(bstrSortExpr, m_bstrSortExpr) != 0 || 
        wch_cmp(bstrFilterExpr, m_bstrFilterExpr) != 0 || 
        fCaseSensitive != m_fCaseSensitive) 
    { 
        m_fSortFilterDisrupted = TRUE; 
    } 
    m_bstrSortExpr = bstrSortExpr; 
    m_bstrFilterExpr = bstrFilterExpr; 
    m_fCaseSensitive = fCaseSensitive; 
 
    //  If not loaded, leave it to the load process to apply any changes 
    // 
    if (m_state == LS_LOADED) 
        hr = ApplySortFilterCriteria(); 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    CTDCCStartDataLoad() 
// 
//  Synopsis:  Preparation for a load operation via FieldSink routines below 
// 
//  Arguments: fUseHeader      TRUE if the first line of fields should 
//                               be interpreted as column name/type info. 
//             bstrSortExpr    Sort expression for ordering rows 
//             bstrFilterExpr  Filter expression for including/excluding rows 
//             lcidRead        Locale ID to use for interpreting locale- 
//                               dependent data formats (date, number etc). 
//             pBSC            COM object performing the data transfer 
//             fAppend         Flag indicating whether the data should be 
//                             appended to any existing data. 
// 
//  Returns:   S_OK indicating success. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::StartDataLoad(boolean fUseHeader, BSTR bstrSortExpr, 
    BSTR bstrFilterExpr, LCID lcidRead, 
    CComObject<CMyBindStatusCallback<CTDCCtl> > *pBSC, 
    boolean fAppend, boolean fCaseSensitive) 
{ 
    HRESULT hr  = S_OK; 
 
    //  If we're asked to append to existing data AND 
    //     - there isn't any OR 
    //     - the previous load didn't load a header row 
    //  then treat it as an initial load. 
    // 
    if (fAppend && m_state == LS_UNINITIALISED) 
        fAppend = FALSE; 
 
    if (fAppend) 
    { 
        if (m_state != LS_LOADED || m_iDataRows < 0) 
        { 
            hr = E_FAIL; 
            goto Cleanup; 
        } 
        m_state = LS_LOADING_HEADER_AVAILABLE; 
    } 
    else 
    { 
        if (m_state != LS_UNINITIALISED || 
            m_iDataRows != -1 || 
            m_iFilterRows != -1 || 
            m_iCols != 0) 
        { 
            hr = E_FAIL; 
            goto Cleanup; 
        } 
        m_state = LS_LOADING_HEADER_UNAVAILABLE; 
        m_fSortFilterDisrupted = TRUE; 
    } 
 
    SetSortFilterCriteria(bstrSortExpr, bstrFilterExpr, 
                         fCaseSensitive); 
 
    m_fUseHeader = fUseHeader; 
    m_fSkipRow = fAppend && fUseHeader; 
    _ASSERT(m_iFilterRows == CalcFilterRows()); 
    _ASSERT(m_iDataRows == CalcDataRows()); 
    _ASSERT(m_iCols == CalcCols()); 
    m_iCurrRow = m_iDataRows + 1; 
    m_iCurrCol = 1; 
    m_lcidRead = lcidRead; 
    if (m_pEventBroker != NULL) 
    hr = m_pEventBroker->STDLoadStarted(pBSC, fAppend); 
 
Cleanup: 
    return hr; 
} 
 
 
 
////////////////////////////////////////////////////////////////////////// 
// 
//        CTDCFieldSink Methods - see comments in file TDCParse.h 
//        --------------------- 
// 
////////////////////////////////////////////////////////////////////////// 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    AddField() 
// 
//  Synopsis:  Adds a data cell to the growing cell grid. 
// 
//  Arguments: pwch         Wide-char string holding data for the cell 
//             dwSize       # of significant bytes in 'pwch' 
// 
//  Returns:   S_OK indicating success. 
//             E_OUTOFMEMORY if there was not enough memory to add the cell. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::AddField(LPWCH pwch, DWORD dwSize) 
{ 
    _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || 
            m_state == LS_LOADING_HEADER_AVAILABLE); 
 
    HRESULT hr          = S_OK; 
    LONG    nCols       = 0; 
    BSTR    bstr        = NULL; 
 
    if (m_fSkipRow) 
        goto Cleanup; 
#ifdef TDC_ATL_DEBUG_ADDFIELD 
    ATLTRACE( _T("CTDCArr::AddField called: %d, %d\n"), m_iCurrRow, m_iCurrCol); 
#endif 
 
    if (m_iCurrRow > m_iDataRows && m_iCurrCol == 1) 
    { 
        TSTDArray<CTDCCell> *pRow; 
 
        //  Need to insert a new row 
        // 
        _ASSERT(m_iCurrRow == m_iDataRows + 1); 
        hr = m_arrparrCells.InsertElems(m_iCurrRow, 1); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
        pRow = new TSTDArray<CTDCCell>; 
        if (pRow == NULL) 
        { 
            hr = E_OUTOFMEMORY; 
            goto Cleanup; 
        } 
        m_arrparrCells[m_iCurrRow] = pRow; 
        if (m_iCurrRow > 0) 
        { 
            //  We've already read at least one row, so we know how 
            //  many columns to insert for this row 
            // 
            hr = m_arrparrCells[m_iCurrRow]->InsertElems(0, m_iCols); 
            if (!SUCCEEDED(hr)) 
                goto Cleanup; 
        } 
    } 
    if (m_iCurrRow == 0) 
    { 
        //  This is the first row - we don't know how many columns there 
        //  will be, so just insert a single cell for this new element. 
        // 
        _ASSERT(m_iCurrCol == m_iCols + 1); 
        hr = m_arrparrCells[m_iCurrRow]->InsertElems(m_iCurrCol - 1, 1); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
        m_iCols++; 
    } 
 
    if (m_iCurrCol <= m_iCols) 
    { 
        CTDCCell    *pCell  = GetDataCell(m_iCurrRow, m_iCurrCol); 
 
        pCell->clear(); 
        pCell->vt = VT_BSTR; 
 
        if (dwSize <= 0) 
            pCell->bstrVal = NULL; 
        else 
        { 
            pCell->bstrVal = SysAllocStringLen(pwch, dwSize); 
            if (pCell->bstrVal == NULL) 
            { 
                hr = E_OUTOFMEMORY; 
                goto Cleanup; 
            } 
        } 
 
        if (m_iCurrRow > 0) 
        { 
            CTDCColInfo *pColInfo = GetColInfo(m_iCurrCol); 
 
            if (pColInfo->vtType != VT_BSTR) 
            { 
                VARIANT     v; 
                HRESULT     hr; 
                 
                hr = VariantFromBSTR(&v, pCell->bstrVal, pColInfo, m_lcidRead); 
                if (SUCCEEDED(hr)) 
                { 
                    hr = VariantCopy(pCell, &v); 
                    VariantClear(&v); 
                } 
                else 
                { 
                    //  Leave it as a BSTR 
                    // 
                    hr = S_OK; 
                } 
            } 
        } 
    } 
 
    m_iCurrCol++; 
 
Cleanup: 
    return hr; 
} 
 
// InsertionSortHelper - 
//  returns -1 if the candidate row < current filter array row 
//           0 if the candidate row = current filter array row 
//           1 if the candidate row > current filter array row 
 
int 
CTDCArr::InsertionSortHelper(int iRow) 
{ 
    CTDCSortCriterion *pCriterion; 
    int cmp = 0; 
 
    for (pCriterion = m_pSortList; pCriterion != NULL; 
         pCriterion = pCriterion->m_pNext) 
    { 
        CTDCCell    *pCell1 = GetDataCell(m_iDataRows, pCriterion->m_iSortCol); 
        CTDCCell    *pCell2 = GetFilterCell(iRow, pCriterion->m_iSortCol); 
 
        cmp = VariantComp(pCell1, pCell2, 
                          GetColInfo(pCriterion->m_iSortCol)->vtType, m_fCaseSensitive); 
 
        if (!pCriterion->m_fSortAscending) 
            cmp = -cmp; 
 
        // if < or >, we don't have to look at any further criterion 
        if (cmp) 
            break; 
    } 
    return cmp; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    EOLN() 
// 
//  Synopsis:  Closes of the current row in the growing cell grid, 
//               handling column headings if it's the first row. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK indicating success. 
//             E_OUTOFMEMORY if insufficient memory is available for 
//               a conversion. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::EOLN() 
{ 
    ATLTRACE(_T("CTDCArr::EOLN called, row: %d\n"), m_iCurrRow); 
 
    HRESULT hr  = S_OK; 
 
    _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || 
            m_state == LS_LOADING_HEADER_AVAILABLE); 
 
    if (m_fSkipRow) 
    { 
        //  Appending to existing data; skip over the first (header) line 
        m_fSkipRow = FALSE; 
        goto Cleanup; 
    } 
 
    if (m_iCurrRow == 0) 
    { 
        //  The first row has been inserted - if m_fUseHeader indicates 
        //  that the first row contains header information then parse 
        //  it; otherwise, create some numbered column headings. 
        // 
        if (!m_fUseHeader) 
        { 
            hr = CreateNumberedColumnHeadings(); 
            if (!SUCCEEDED(hr)) 
                goto Cleanup; 
 
            //  An extra row has been inserted - update the insertion 
            //  row index for later insertion of new elements 
            // 
            m_iCurrRow++; 
 
            //  Initialise each column heading as "Column<column#>" 
            // 
            RenumberColumnHeadings(); 
        } 
 
        m_iDataRows++; 
        m_iFilterRows++; 
        _ASSERT(m_iDataRows == 0); 
        _ASSERT(m_iFilterRows == 0); 
 
        ParseColumnHeadings(); 
 
        m_state = LS_LOADING_HEADER_AVAILABLE; 
 
        //  Insert the hedaer row into the list of filtered rows 
        // 
        hr = m_arrparrFilter.InsertElems(0, 1); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
        m_arrparrFilter[0] = m_arrparrCells[0]; 
 
        //  Notify the event handler that the headers have been loaded 
        // 
        if (m_pEventBroker != NULL) 
        { 
            hr = m_pEventBroker->STDLoadedHeader(); 
            OutputDebugStringX(_T("TDCCtl: header loaded\n")); 
            if (!SUCCEEDED(hr)) 
                goto Cleanup; 
        } 
    } 
 
    if (m_iCurrRow > 0) 
    { 
        //  Convert uninitialised cells into their column's type. 
        // 
        LONG    iCol; 
 
        for (iCol = m_iCurrCol; iCol < m_iCols; iCol++) 
        { 
            CTDCCell    *pCell    = GetDataCell(m_iCurrRow, iCol); 
 
            //  This uninitialised VARIANT is assumed to be the result 
            //  of specifying too few cells in a row. 
            // 
            _ASSERT(pCell->vt == VT_EMPTY); 
            pCell->vt = VT_BSTR; 
            pCell->bstrVal = NULL; 
 
            CTDCColInfo *pColInfo = GetColInfo(iCol); 
 
            if (pColInfo->vtType != VT_BSTR) 
            { 
                VARIANT     v; 
                HRESULT     tmp_hr; 
                 
                tmp_hr = VariantFromBSTR(&v, pCell->bstrVal, pColInfo, m_lcidRead); 
                if (SUCCEEDED(tmp_hr)) 
                { 
                    hr = VariantCopy(pCell, &v); 
                    VariantClear(&v); 
                    if (!SUCCEEDED(hr)) 
                        goto Cleanup; 
                } 
                else 
                { 
                    //  Leave the cell as a BSTR 
                    // 
                } 
            } 
        } 
        m_iDataRows++; 
    } 
 
    m_iCurrCol = 1; 
    m_iCurrRow++; 
 
    if (m_fSortFilterDisrupted) 
    { 
        //  This will have the side-effect of incorporating any new data rows 
        // 
        hr = ApplySortFilterCriteria(); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
    } 
    else if (m_iDataRows > 0 && 
        (m_pFilterTree == NULL || EvalDataRow(m_iDataRows, m_pFilterTree))) 
    { 
        //  The new row passed the filter criteria. 
        //  Insert the new row into the filtered list 
        // 
        LONG iRowInsertedAt = m_iFilterRows + 1; 
 
        //  at the correct insertion point according to the current 
        //  sort criteria, if there is one.  We only need to do the search 
        //  if this is not the first row, and if the candidate row is less 
        //  than the last row. 
        if (m_pSortList != NULL && m_iFilterRows != 0 
            && InsertionSortHelper(m_iFilterRows) < 0) 
        { 
            // not at end, do traditional binary search. 
            LONG lLow = 1;          // we don't use element zero! 
            LONG lHigh = m_iFilterRows + 1; 
            LONG lMid; 
 
            while (lLow < lHigh) 
            { 
                lMid = (lLow + lHigh) / 2; 
                // Note that InsertionSortHelper automatically flips the comparison 
                // if m_fAscending flag is off. 
                if (InsertionSortHelper(lMid) <= 0) 
                { 
                    lHigh = lMid; 
                } 
                else 
                { 
                    lLow = lMid + 1; 
                } 
            } 
            iRowInsertedAt = lLow; 
        } 
 
        hr = m_arrparrFilter.InsertElems(iRowInsertedAt, 1); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
        m_arrparrFilter[iRowInsertedAt] = m_arrparrCells[m_iDataRows]; 
        ++m_iFilterRows; 
 
        //  Notify event handler of row insertion 
        // 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->rowsAvailable(iRowInsertedAt, 1); 
 
    } 
 
Cleanup: 
    return hr; 
} 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    EOF() 
// 
//  Synopsis:  Indicates no more cells will be added to the cell grid. 
//             The column-heading cells are added unless it was indicated 
//             that cell headings should be taken from the data read. 
//             The cells in each column are converted to that column's 
//             specified data type. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK indicating success. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::EOF() 
{ 
    OutputDebugStringX(_T("CTDArr::EOF() called\n")); 
    _ASSERT(m_state == LS_LOADING_HEADER_UNAVAILABLE || 
            m_state == LS_LOADING_HEADER_AVAILABLE); 
    HRESULT hr = S_OK; 
 
    if (m_iCurrCol > 1) 
        EOLN(); 
    m_state = LS_LOADED; 
    m_iFilterRows = CalcFilterRows(); 
    _ASSERT(m_iDataRows == CalcDataRows()); 
    _ASSERT(m_iCols == CalcCols()); 
 
    if (m_fSortFilterDisrupted) 
    { 
        hr = ApplySortFilterCriteria(); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
    } 
    if (m_pEventBroker != NULL) 
        hr = m_pEventBroker->STDLoadCompleted(); 
 
Cleanup: 
    return hr; 
} 
 
// GetEstimatedRows..  
// We should really see if URLMon has a means of giving a byte count on the file 
// we're downloading.  For now though.. 
STDMETHODIMP 
CTDCArr::getEstimatedRows(LONG *pcRows) 
{ 
    *pcRows = m_iFilterRows; 
    if (m_state<LS_LOADED) 
    { 
        // Return twice number of rows, but be careful not to return 2 * 0. 
        *pcRows = m_iFilterRows ? m_iFilterRows * 2 : -1; 
    } 
    return S_OK; 
} 
 
STDMETHODIMP 
CTDCArr::isAsync(BOOL *pbAsync) 
{ 
//    *pbAsync = m_fAsync; 
    // The TDC always behaves as if it's Async.  Specifically, we always fire 
    // TransferComplete, even if we have to buffer the notification until our 
    // addOLEDBSimplerProviderListener is actually called. 
    *pbAsync = TRUE; 
    return S_OK; 
} 
 
STDMETHODIMP 
CTDCArr::stopTransfer() 
{ 
    HRESULT hr = S_OK; 
 
    //  Force the load state into UNINITIALISED or LOADED ... 
    // 
    switch (m_state) 
    { 
    case LS_UNINITIALISED: 
    case LS_LOADED: 
        break; 
 
    case LS_LOADING_HEADER_UNAVAILABLE: 
        //  Free any allocated cell memory 
        // 
        if (m_arrparrFilter.GetSize() > 0) 
            m_arrparrFilter.DeleteElems(0, m_arrparrFilter.GetSize()); 
        if (m_arrparrCells.GetSize() > 0) 
            m_arrparrCells.DeleteElems(0, m_arrparrCells.GetSize()); 
        m_state = LS_UNINITIALISED; 
        m_iFilterRows = CalcFilterRows(); 
        m_iDataRows = CalcDataRows(); 
        m_iCols = CalcCols(); 
 
        // If we stop the load before the header was parsed, we won't 
        // have a dataset, but we still need to fire datasetchanged, 
        // to let our customer know the query failed. 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->STDDataSetChanged(); 
 
        // 
        // fall through to LOADING_HEADER_AVAILABLE! 
        // 
 
    case LS_LOADING_HEADER_AVAILABLE: 
        m_state = LS_LOADED;            // mark us as finished now 
 
        // LoadStopped will abort any transfer in progress, and fire 
        // transferComplete with the OSPXFER_ABORT flag. 
        if (m_pEventBroker != NULL) 
            hr = m_pEventBroker->STDLoadStopped(); 
        break; 
    } 
 
    return hr; 
} 
 
 
////////////////////////////////////////////////////////////////////////// 
// 
//        Implementation of IUnknown COM interface. 
//        ----------------------------------------- 
// 
////////////////////////////////////////////////////////////////////////// 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    QueryInterface() 
// 
//  Synopsis:  Implements part of the standard IUnknown COM interface. 
//               (Returns a pointer to this COM object) 
// 
//  Arguments: riid          GUID to recognise 
//             ppv           Pointer to this COM object [OUT] 
// 
//  Returns:   S_OK upon success. 
//             E_NOINTERFACE if queried for an unrecognised interface. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP 
CTDCArr::QueryInterface (REFIID riid, LPVOID * ppv) 
{ 
    HRESULT hr; 
 
    _ASSERTE(ppv != NULL); 
 
    // This is the non-delegating IUnknown implementation 
    if (riid == IID_IUnknown || riid == IID_OLEDBSimpleProvider) 
    { 
        *ppv = this; 
        ((LPUNKNOWN)*ppv)->AddRef(); 
        hr = S_OK; 
    } 
    else 
    { 
        *ppv = NULL; 
        hr = E_NOINTERFACE; 
    } 
 
#ifdef _ATL_DEBUG_QI 
    AtlDumpIID(riid, _T("CTDCArr"), hr); 
#endif 
    return hr; 
} 
 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    AddRef() 
// 
//  Synopsis:  Implements part of the standard IUnknown COM interface. 
//               (Adds a reference to this COM object) 
// 
//  Arguments: None 
// 
//  Returns:   Number of references to this COM object. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP_(ULONG) 
CTDCArr::AddRef () 
{ 
    return ++m_cRef; 
} 
 
 
//+----------------------------------------------------------------------- 
// 
//  Method:    Release() 
// 
//  Synopsis:  Implements part of the standard IUnknown COM interface. 
//               (Removes a reference to this COM object) 
// 
//  Arguments: None 
// 
//  Returns:   Number of remaining references to this COM object. 
//             0 if the COM object is no longer referenced. 
// 
//+----------------------------------------------------------------------- 
 
STDMETHODIMP_(ULONG) 
CTDCArr::Release () 
{ 
    ULONG retval; 
 
    m_cRef -= 1; 
    retval = m_cRef; 
    if (!m_cRef) 
    { 
        m_cRef = 0xffff;    //MM: Use this 'flag' for debug? 
        delete this; 
    } 
 
    return retval; 
}