Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1622 lines
45 KiB

//---------------------------------------------------------------------------
// CursorPosition.cpp : CursorPosition implementation
//
// Copyright (c) 1996 Microsoft Corporation, All Rights Reserved
// Developed by Sheridan Software Systems, Inc.
//---------------------------------------------------------------------------
#include "stdafx.h"
#include "Notifier.h"
#include "RSColumn.h"
#include "RSSource.h"
#include "CursMain.h"
#include "ColUpdat.h"
#include "CursPos.h"
#include "fastguid.h"
#include "MSR2C.h"
#include "resource.h"
SZTHISFILE
//=--------------------------------------------------------------------------=
// CVDCursorPosition - Constructor
//
CVDCursorPosition::CVDCursorPosition()
{
m_pCursorMain = NULL;
m_pRowPosition = NULL;
m_pSameRowClone = NULL;
m_dwEditMode = CURSOR_DBEDITMODE_NONE;
m_ppColumnUpdates = NULL;
m_fTempEditMode = FALSE;
m_fConnected = FALSE;
m_dwAdviseCookie = 0;
m_fPassivated = FALSE;
m_fInternalSetRow = FALSE;
#ifdef _DEBUG
g_cVDCursorPositionCreated++;
#endif
}
//=--------------------------------------------------------------------------=
// ~CVDCursorPosition - Destructor
//
CVDCursorPosition::~CVDCursorPosition()
{
Passivate();
#ifdef _DEBUG
g_cVDCursorPositionDestroyed++;
#endif
}
//=--------------------------------------------------------------------------=
// Pasivate when external ref count gets to zero
//
void CVDCursorPosition::Passivate()
{
if (m_fPassivated)
return;
m_fPassivated = TRUE;
DestroyColumnUpdates();
ReleaseCurrentRow();
ReleaseAddRow();
LeaveFamily(); // remove myself from pCursorMain's notification family
if (m_pCursorMain)
m_pCursorMain->Release(); // release associated cursor main object
if (m_fConnected)
DisconnectIRowPositionChange(); // disconnect IRowPosition change
if (m_pRowPosition)
m_pRowPosition->Release(); // release associated row position
}
//=--------------------------------------------------------------------------=
// Create - Create cursor position object
//=--------------------------------------------------------------------------=
// This function creates and initializes a new cursor position object
//
// Parameters:
// pRowPosition - [in] IRowPosition provider (may be NULL)
// pCursorMain - [in] backwards pointer to CVDCursorMain object
// ppCursorPosition - [out] a pointer in which to return pointer to cursor position object
//
// Output:
// HRESULT - S_OK if successful
// E_INVALIDARG bad parameter
// E_OUTOFMEMORY not enough memory to create object
//
// Notes:
//
HRESULT CVDCursorPosition::Create(IRowPosition * pRowPosition,
CVDCursorMain * pCursorMain,
CVDCursorPosition ** ppCursorPosition,
CVDResourceDLL * pResourceDLL)
{
ASSERT_POINTER(pCursorMain, CVDCursorMain)
ASSERT_POINTER(ppCursorPosition, CVDCursorPosition*)
if (!pCursorMain || !ppCursorPosition)
{
VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_ICursorMove, pResourceDLL);
return E_INVALIDARG;
}
*ppCursorPosition = NULL;
CVDCursorPosition * pCursorPosition = new CVDCursorPosition();
if (!pCursorPosition)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursorMove, pResourceDLL);
return E_OUTOFMEMORY;
}
pCursorPosition->m_pResourceDLL = pResourceDLL;
pCursorPosition->m_pCursorMain = pCursorMain;
pCursorPosition->m_pRowPosition = pRowPosition;
pCursorMain->AddRef(); // add reference to associated cursor main object
if (pRowPosition) // add reference to associated row position (if needed)
{
pRowPosition->AddRef();
// connect IRowPositionChange
HRESULT hr = pCursorPosition->ConnectIRowPositionChange();
if (SUCCEEDED(hr))
pCursorPosition->m_fConnected = TRUE;
}
// add to pCursorMain's notification family
pCursorPosition->JoinFamily(pCursorMain);
pCursorPosition->PositionToFirstRow();
*ppCursorPosition = pCursorPosition;
return S_OK;
}
//=--------------------------------------------------------------------------=
// CreateColumnUpdates - Create array of column update pointers
//
HRESULT CVDCursorPosition::CreateColumnUpdates()
{
const ULONG ulColumns = m_pCursorMain->GetColumnsCount();
m_ppColumnUpdates = new CVDColumnUpdate*[ulColumns];
if (!m_ppColumnUpdates)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursorUpdateARow, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// set all column update pointers to NULL
memset(m_ppColumnUpdates, 0, ulColumns * sizeof(CVDColumnUpdate*));
return S_OK;
}
//=--------------------------------------------------------------------------=
// ResetColumnUpdates - Reset column updates array
//
HRESULT CVDCursorPosition::ResetColumnUpdates()
{
HRESULT hr = S_OK;
if (m_ppColumnUpdates)
{
const ULONG ulColumns = m_pCursorMain->GetColumnsCount();
// set all column update pointers to NULL
for (ULONG ulCol = 0; ulCol < ulColumns; ulCol++)
SetColumnUpdate(ulCol, NULL);
}
else
{
// create array of column update pointers
hr = CreateColumnUpdates();
}
return hr;
}
//=--------------------------------------------------------------------------=
// DestroyColumnUpdates - Destroy column updates and array of update pointers
//
void CVDCursorPosition::DestroyColumnUpdates()
{
if (m_ppColumnUpdates)
{
// set all column update pointers to NULL
ResetColumnUpdates();
// destroy array of column update pointers
delete [] m_ppColumnUpdates;
m_ppColumnUpdates = NULL;
}
}
//=--------------------------------------------------------------------------=
// GetColumnUpdate - Get column update
//
CVDColumnUpdate * CVDCursorPosition::GetColumnUpdate(ULONG ulColumn) const
{
CVDColumnUpdate * pColumnUpdate = NULL;
const ULONG ulColumns = m_pCursorMain->GetColumnsCount();
// make sure column index is in range
if (ulColumn < ulColumns)
pColumnUpdate = m_ppColumnUpdates[ulColumn];
return pColumnUpdate;
}
//=--------------------------------------------------------------------------=
// SetColumnUpdate - Set column update
//
void CVDCursorPosition::SetColumnUpdate(ULONG ulColumn, CVDColumnUpdate * pColumnUpdate)
{
const ULONG ulColumns = m_pCursorMain->GetColumnsCount();
// make sure column index is in range
if (ulColumn < ulColumns)
{
// release update if it already exists
if (m_ppColumnUpdates[ulColumn])
m_ppColumnUpdates[ulColumn]->Release();
// store new column update
m_ppColumnUpdates[ulColumn] = pColumnUpdate;
}
}
//=--------------------------------------------------------------------------=
// PositionToFirstRow
//=--------------------------------------------------------------------------=
// Positions to the first row in the rowset
//
void CVDCursorPosition::PositionToFirstRow()
{
m_bmCurrent.Reset();
ULONG cRowsObtained = 0;
HROW * rghRows = NULL;
BYTE bSpecialBM;
bSpecialBM = DBBMK_FIRST;
HRESULT hr = GetRowsetSource()->GetRowsetLocate()->GetRowsAt(0, 0, sizeof(BYTE), &bSpecialBM, 0,
1, &cRowsObtained, &rghRows);
if (cRowsObtained)
{
// set current row to first row
SetCurrentHRow(rghRows[0]);
// release hRows and associated memory
GetRowsetSource()->GetRowset()->ReleaseRows(cRowsObtained, rghRows, NULL, NULL, NULL);
g_pMalloc->Free(rghRows);
}
}
//=--------------------------------------------------------------------------=
// ReleaseCurrentRow
//=--------------------------------------------------------------------------=
// Releases old current row
//
void CVDCursorPosition::ReleaseCurrentRow()
{
if (!GetRowsetSource()->IsRowsetValid() ||
m_bmCurrent.GetStatus() != VDBOOKMARKSTATUS_CURRENT)
return;
if (m_bmCurrent.m_hRow)
{
GetRowsetSource()->GetRowset()->ReleaseRows(1, &m_bmCurrent.m_hRow, NULL, NULL, NULL);
m_bmCurrent.m_hRow = NULL;
}
}
//=--------------------------------------------------------------------------=
// ReleaseAddRow
//=--------------------------------------------------------------------------=
// Releases temporary add row
//
void CVDCursorPosition::ReleaseAddRow()
{
if (!GetRowsetSource()->IsRowsetValid())
return;
if (m_bmAddRow.m_hRow)
{
GetRowsetSource()->GetRowset()->ReleaseRows(1, &m_bmAddRow.m_hRow, NULL, NULL, NULL);
m_bmAddRow.m_hRow = NULL;
}
}
//=--------------------------------------------------------------------------=
// SetCurrentRowStatus
//=--------------------------------------------------------------------------=
// Sets status to beginning or end (releasing current hrow)
//
void CVDCursorPosition::SetCurrentRowStatus(WORD wStatus)
{
if (VDBOOKMARKSTATUS_BEGINNING == wStatus ||
VDBOOKMARKSTATUS_END == wStatus)
{
ReleaseCurrentRow();
m_bmCurrent.SetBookmark(wStatus);
}
}
//=--------------------------------------------------------------------------=
// SetCurrentHRow
//=--------------------------------------------------------------------------=
// Reads the bookmark from the hrow and sets the m_bmCurrent
//
// Parameters:
// hRowNew - [in] hrow of new current row
//
// Output:
// HRESULT - S_OK if successful
//
// Notes:
//
HRESULT CVDCursorPosition::SetCurrentHRow(HROW hRowNew)
{
if (!GetRowsetSource()->IsRowsetValid())
{
VDSetErrorInfo(IDS_ERR_ROWSETRELEASED, IID_ICursor, m_pResourceDLL);
return E_FAIL;
}
IRowset * pRowset = GetRowsetSource()->GetRowset();
// allocate buffer for bookmark plus length indicator
BYTE * pBuff = new BYTE[GetCursorMain()->GetMaxBookmarkLen() + sizeof(ULONG)];
if (!pBuff)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// get the bookmark data
HRESULT hr = pRowset->GetData(hRowNew,
GetCursorMain()->GetBookmarkAccessor(),
pBuff);
if (S_OK == hr)
{
ReleaseCurrentRow();
pRowset->AddRefRows(1, &hRowNew, NULL, NULL);
ULONG * pulLen = (ULONG*)pBuff;
BYTE * pbmdata = pBuff + sizeof(ULONG);
m_bmCurrent.SetBookmark(VDBOOKMARKSTATUS_CURRENT, hRowNew, pbmdata, *pulLen);
}
else
{
ASSERT_(FALSE);
hr = VDMapRowsetHRtoCursorHR(hr,
IDS_ERR_GETDATAFAILED,
IID_ICursorMove,
pRowset,
IID_IRowset,
m_pResourceDLL);
}
delete [] pBuff;
return hr;
}
//=--------------------------------------------------------------------------=
// IsSameRowAsCurrent - Compares current bookmark to supplied hrow
//=--------------------------------------------------------------------------=
//
// Parameters:
// hRow - [in] hrow to check
// fCacheIfNotSame - [in] If TRUE same hrow in cached CVDBookmark
//
// Output:
// HRESULT - S_OK if both hrows correspond to the same logical row
// S_FALSE if not same row
// E_INVALIDARG
// E_UNEXPECTED
// DB_E_BADROWHANDLE
// DB_E_DELETEDROW
// DB_E_NEWLYINSERTED
//
// Notes:
//
HRESULT CVDCursorPosition::IsSameRowAsCurrent(HROW hRow, BOOL fCacheIfNotSame)
{
if (!GetRowsetSource()->IsRowsetValid())
{
VDSetErrorInfo(IDS_ERR_ROWSETRELEASED, IID_ICursor, m_pResourceDLL);
return E_FAIL;
}
if (m_bmCurrent.IsSameHRow(hRow))
return S_OK;
HRESULT hrSame = S_FALSE;
IRowsetIdentity * pRowsetIdentity = GetRowsetSource()->GetRowsetIdentity();
if (pRowsetIdentity)
{
hrSame = pRowsetIdentity->IsSameRow(hRow, m_bmCurrent.GetHRow());
// return if hrow matches or not cache flag set
if (S_OK == hrSame || !fCacheIfNotSame)
return hrSame;
}
else
if (fCacheIfNotSame)
{
// check if hRow matches cache
if (m_bmCache.IsSameHRow(hRow))
{
// return TRUE if bookmark matches cache
return m_bmCurrent.IsSameBookmark(&m_bmCache) ? S_OK : S_FALSE;
}
}
// allocate buffer for bookmark plus length indicator
BYTE * pBuff = new BYTE[GetCursorMain()->GetMaxBookmarkLen() + sizeof(ULONG)];
if (!pBuff)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// get the bookmark data
HRESULT hrWork = GetRowsetSource()->GetRowset()->GetData(hRow, GetCursorMain()->GetBookmarkAccessor(), pBuff);
if (S_OK == hrWork)
{
ULONG * pulLen = (ULONG*)pBuff;
BYTE * pbmdata = pBuff + sizeof(ULONG);
// if IRowsetIdentity isn't supported, compare bookmarks
if (!pRowsetIdentity)
{
DBCOMPARE dbcompare;
hrWork = GetRowsetSource()->GetRowsetLocate()->Compare(0,
m_bmCurrent.GetBookmarkLen(),
m_bmCurrent.GetBookmark(),
*pulLen,
pbmdata,
&dbcompare);
if (SUCCEEDED(hrWork))
{
if (DBCOMPARE_EQ == dbcompare)
hrSame = S_OK;
else
hrSame = S_FALSE;
}
}
if (fCacheIfNotSame && S_OK != hrSame)
m_bmCache.SetBookmark(VDBOOKMARKSTATUS_CURRENT, hRow, pbmdata, *pulLen);
}
else
hrSame = hrWork;
delete [] pBuff;
return hrSame;
}
//=--------------------------------------------------------------------------=
// IsSameRowAsAddRow - Compares addrow bookmark to supplied hrow
//=--------------------------------------------------------------------------=
//
// Parameters:
// hRow - [in] hrow to check
//
// Output:
// HRESULT - S_OK if both hrows correspond to the same logical row
// S_FALSE if not same row
// E_INVALIDARG
// E_UNEXPECTED
// DB_E_BADROWHANDLE
// DB_E_DELETEDROW
// DB_E_NEWLYINSERTED
//
// Notes:
//
HRESULT CVDCursorPosition::IsSameRowAsNew(HROW hRow)
{
if (!GetRowsetSource()->IsRowsetValid())
{
VDSetErrorInfo(IDS_ERR_ROWSETRELEASED, IID_ICursor, m_pResourceDLL);
return E_FAIL;
}
if (m_bmAddRow.IsSameHRow(hRow))
return S_OK;
if (m_bmAddRow.m_hRow == NULL)
return S_FALSE;
HRESULT hrSame = S_FALSE;
IRowsetIdentity * pRowsetIdentity = GetRowsetSource()->GetRowsetIdentity();
if (pRowsetIdentity)
{
hrSame = pRowsetIdentity->IsSameRow(hRow, m_bmAddRow.GetHRow());
// return result
return hrSame;
}
// allocate buffer for bookmark plus length indicator
BYTE * pBuff = new BYTE[GetCursorMain()->GetMaxBookmarkLen() + sizeof(ULONG)];
if (!pBuff)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// get the bookmark data
HRESULT hrWork = GetRowsetSource()->GetRowset()->GetData(hRow, GetCursorMain()->GetBookmarkAccessor(), pBuff);
if (S_OK == hrWork)
{
ULONG * pulLen = (ULONG*)pBuff;
BYTE * pbmdata = pBuff + sizeof(ULONG);
// since IRowsetIdentity isn't supported, compare bookmarks
DBCOMPARE dbcompare;
hrWork = GetRowsetSource()->GetRowsetLocate()->Compare(0,
m_bmAddRow.GetBookmarkLen(),
m_bmAddRow.GetBookmark(),
*pulLen,
pbmdata,
&dbcompare);
if (SUCCEEDED(hrWork))
{
if (DBCOMPARE_EQ == dbcompare)
hrSame = S_OK;
else
hrSame = S_FALSE;
}
}
else
hrSame = hrWork;
delete [] pBuff;
return hrSame;
}
//=--------------------------------------------------------------------------=
// SetAddHRow
//=--------------------------------------------------------------------------=
// Reads the bookmark from the hrow and sets the m_bmAddRow
//
// Parameters:
// hRowNew - [in] hrow of new add row
//
// Output:
// HRESULT - S_OK if successful
//
// Notes:
//
HRESULT CVDCursorPosition::SetAddHRow(HROW hRowNew)
{
if (!GetRowsetSource()->IsRowsetValid())
{
VDSetErrorInfo(IDS_ERR_ROWSETRELEASED, IID_ICursor, m_pResourceDLL);
return E_FAIL;
}
IRowset * pRowset = GetRowsetSource()->GetRowset();
// allocate buffer for bookmark plus length indicator
BYTE * pBuff = new BYTE[GetCursorMain()->GetMaxBookmarkLen() + sizeof(ULONG)];
if (!pBuff)
{
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_ICursor, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// get the bookmark data
HRESULT hr = pRowset->GetData(hRowNew,
GetCursorMain()->GetBookmarkAccessor(),
pBuff);
if (S_OK == hr)
{
ReleaseAddRow();
pRowset->AddRefRows(1, &hRowNew, NULL, NULL);
ULONG * pulLen = (ULONG*)pBuff;
BYTE * pbmdata = pBuff + sizeof(ULONG);
m_bmAddRow.SetBookmark(VDBOOKMARKSTATUS_CURRENT, hRowNew, pbmdata, *pulLen);
}
else
{
ASSERT_(FALSE);
hr = VDMapRowsetHRtoCursorHR(hr,
IDS_ERR_GETDATAFAILED,
IID_ICursorMove,
pRowset,
IID_IRowset,
m_pResourceDLL);
}
delete [] pBuff;
return hr;
}
//=--------------------------------------------------------------------------=
// GetEditRow - Get hRow of the row currently being edited
//
HROW CVDCursorPosition::GetEditRow() const
{
HROW hRow = NULL;
switch (m_dwEditMode)
{
case CURSOR_DBEDITMODE_UPDATE:
hRow = m_bmCurrent.m_hRow;
break;
case CURSOR_DBEDITMODE_ADD:
hRow = m_bmAddRow.m_hRow;
break;
}
return hRow;
}
//=--------------------------------------------------------------------------=
// SetRowPosition - Set new current hRow
//
HRESULT CVDCursorPosition::SetRowPosition(HROW hRow)
{
if (!m_pRowPosition)
return S_OK;
// set new current row (set/clear internal set row flag)
m_fInternalSetRow = TRUE;
HRESULT hr = m_pRowPosition->ClearRowPosition();
if (SUCCEEDED(hr))
hr = m_pRowPosition->SetRowPosition(NULL, hRow, DBPOSITION_OK);
m_fInternalSetRow = FALSE;
return hr;
}
#ifndef VD_DONT_IMPLEMENT_ISTREAM
//=--------------------------------------------------------------------------=
// UpdateEntryIDStream - Update entry identifier from stream
//=--------------------------------------------------------------------------=
// This function updates the entry identifier's data from stream
//
// Parameters:
// pColumn - [in] rowset column pointer
// hRow - [in] the row handle
// pStream - [in] stream pointer
//
// Output:
// HRESULT - S_OK if successful
// E_INVALIDARG bad parameter
// E_OUTOFMEMORY not enough memory
//
HRESULT CVDCursorPosition::UpdateEntryIDStream(CVDRowsetColumn * pColumn, HROW hRow, IStream * pStream)
{
ASSERT_POINTER(pStream, IStream)
IAccessor * pAccessor = GetCursorMain()->GetAccessor();
IRowsetChange * pRowsetChange = GetCursorMain()->GetRowsetChange();
// make sure we have valid accessor and change pointers
if (!pAccessor || !pRowsetChange || !GetCursorMain()->IsRowsetValid())
{
VDSetErrorInfo(IDS_ERR_ROWSETRELEASED, IID_IEntryID, m_pResourceDLL);
return E_FAIL;
}
// make sure we have all necessary pointers
if (!pColumn || !pStream)
{
VDSetErrorInfo(IDS_ERR_INVALIDARG, IID_IEntryID, m_pResourceDLL);
return E_INVALIDARG;
}
STATSTG statstg;
// retrieve status structure
HRESULT hr = pStream->Stat(&statstg, STATFLAG_NONAME);
if (FAILED(hr))
{
VDSetErrorInfo(IDS_ERR_STATFAILED, IID_IEntryID, m_pResourceDLL);
return E_FAIL;
}
// determine length of data
ULONG cbData = statstg.cbSize.LowPart;
HGLOBAL hData;
// get handle to data
hr = GetHGlobalFromStream(pStream, &hData);
if (FAILED(hr))
return hr;
// get pointer to data
BYTE * pData = (BYTE*)GlobalLock(hData);
DBBINDING binding;
// clear out binding
memset(&binding, 0, sizeof(DBBINDING));
// create value binding
binding.iOrdinal = pColumn->GetOrdinal();
binding.obValue = sizeof(DBSTATUS) + sizeof(ULONG);
binding.obLength = sizeof(DBSTATUS);
binding.obStatus = 0;
binding.dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
binding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
binding.cbMaxLen = cbData;
binding.wType = DBTYPE_BYREF | DBTYPE_BYTES;
HACCESSOR hAccessor;
// create update accessor
hr = pAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, &binding, 0, &hAccessor, NULL);
hr = VDMapRowsetHRtoCursorHR(hr, IDS_ERR_CREATEACCESSORFAILED, IID_IEntryID, pAccessor, IID_IAccessor, m_pResourceDLL);
if (FAILED(hr))
{
// release pointer to data
GlobalUnlock(hData);
return hr;
}
// create update buffer
BYTE * pBuffer = new BYTE[sizeof(DBSTATUS) + sizeof(ULONG) + sizeof(LPBYTE)];
if (!pBuffer)
{
// release pointer to data
GlobalUnlock(hData);
// release update accessor
pAccessor->ReleaseAccessor(hAccessor, NULL);
VDSetErrorInfo(IDS_ERR_OUTOFMEMORY, IID_IEntryID, m_pResourceDLL);
return E_OUTOFMEMORY;
}
// set status, length and value
*(DBSTATUS*)pBuffer = DBSTATUS_S_OK;
*(ULONG*)(pBuffer + sizeof(DBSTATUS)) = cbData;
*(LPBYTE*)(pBuffer + sizeof(DBSTATUS) + sizeof(ULONG)) = pData;
// modify column
hr = pRowsetChange->SetData(hRow, hAccessor, pBuffer);
hr = VDMapRowsetHRtoCursorHR(hr, IDS_ERR_SETDATAFAILED, IID_IEntryID, pRowsetChange, IID_IRowsetChange,
m_pResourceDLL);
// release pointer to data
GlobalUnlock(hData);
// release update accessor
pAccessor->ReleaseAccessor(hAccessor, NULL);
// destroy update buffer
delete [] pBuffer;
return hr;
}
#endif //VD_DONT_IMPLEMENT_ISTREAM
//=--------------------------------------------------------------------------=
// ReleaseSameRowClone - Release same-row clone, if we still have one
//
void CVDCursorPosition::ReleaseSameRowClone()
{
if (m_pSameRowClone)
{
// must be set to NULL before release
ICursor * pSameRowClone = m_pSameRowClone;
m_pSameRowClone = NULL;
pSameRowClone->Release();
}
}
//=--------------------------------------------------------------------------=
// IRowsetNotify Methods
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// IRowsetNotify OnFieldChange
//=--------------------------------------------------------------------------=
// This function is called on any change to the value of a field
//
// Parameters:
// pRowset - [in] the IRowset that is generating the notification
// (we can ignore this since we are only ever dealing
// with a single rowset).
// hRow - [in] the HROW of the row in which the field value has
// changed
// cColumns - [in] the count of columns in rgColumns
// rgColumns - [in] an array of column (ordinal positions) in the row
// for which the value has changed
// eReason - [in] the kind of action which caused this change
// ePhase - [in] the phase of this notification
// fCantDeny - [in] when this flag is set to TRUE, the consumer cannot
// veto the event (by returning S_FALSE)
//
// Output:
// HRESULT - S_OK if successful
// S_FALSE the event/phase is vetoed
// DB_S_UNWANTEDPHASE
// DB_S_UNWANTEDREASON
//
// Notes:
//
HRESULT CVDCursorPosition::OnFieldChange(IUnknown *pRowset,
HROW hRow,
ULONG cColumns,
ULONG rgColumns[],
DBREASON eReason,
DBEVENTPHASE ePhase,
BOOL fCantDeny)
{
// make sure rowset is valid
if (!GetRowsetSource()->IsRowsetValid())
return S_OK;
// check for columns
if (0 == cColumns)
return S_OK;
// check for known reasons
if (eReason != DBREASON_COLUMN_SET && eReason != DBREASON_COLUMN_RECALCULATED)
return S_OK;
HRESULT hr = S_OK;
// send edit mode notification if needed
if (ePhase == DBEVENTPHASE_OKTODO && m_dwEditMode == CURSOR_DBEDITMODE_NONE)
{
// setup notification structures
DWORD dwEventWhat = CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
CURSOR_DBNOTIFYREASON rgReasons[1];
VariantInit((VARIANT*)&rgReasons[0].arg1);
VariantInit((VARIANT*)&rgReasons[0].arg2);
rgReasons[0].dwReason = CURSOR_DBREASON_EDIT;
// notify other interested parties of action
hr = NotifyBefore(dwEventWhat, 1, rgReasons);
if (hr == S_OK)
{
// notify other interested parties of success
NotifyAfter(dwEventWhat, 1, rgReasons);
// temporarily place cursor into edit mode
m_fTempEditMode = TRUE;
}
else
{
// notify other interested parties of failure
NotifyFail(dwEventWhat, 1, rgReasons);
}
}
// sent set column notifications
if (hr == S_OK && (ePhase == DBEVENTPHASE_OKTODO ||
ePhase == DBEVENTPHASE_DIDEVENT ||
ePhase == DBEVENTPHASE_FAILEDTODO))
{
// setup notification structures
DWORD dwEventWhat = CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED;
CURSOR_DBNOTIFYREASON rgReasons[1];
VariantInit((VARIANT*)&rgReasons[0].arg1);
VariantInit((VARIANT*)&rgReasons[0].arg2);
switch (eReason)
{
case DBREASON_COLUMN_SET:
rgReasons[0].dwReason = CURSOR_DBREASON_SETCOLUMN;
break;
case DBREASON_COLUMN_RECALCULATED:
rgReasons[0].dwReason = CURSOR_DBREASON_RECALC;
break;
}
// get internal column pointers
ULONG ulColumns = m_pCursorMain->GetColumnsCount();
CVDRowsetColumn * pColumn = m_pCursorMain->InternalGetColumns();
for (ULONG ulCol = 0; ulCol < cColumns; ulCol++)
{
// determine which column is changing
for (ULONG ulRSCol = 0; ulRSCol < ulColumns; ulRSCol++)
{
if (pColumn[ulRSCol].GetOrdinal() == rgColumns[ulCol])
{
rgReasons[0].arg1.vt = VT_I4;
rgReasons[0].arg1.lVal = ulRSCol;
}
}
HRESULT hrNotify = S_OK;
// notify other interested parties
switch (ePhase)
{
case DBEVENTPHASE_OKTODO:
hrNotify = NotifyBefore(dwEventWhat, 1, rgReasons);
break;
case DBEVENTPHASE_DIDEVENT:
NotifyAfter(dwEventWhat, 1, rgReasons);
break;
case DBEVENTPHASE_FAILEDTODO:
NotifyFail(dwEventWhat, 1, rgReasons);
break;
}
if (hrNotify != S_OK)
hr = S_FALSE;
}
}
// take cursor out of edit mode if we placed it into that mode (success)
if (ePhase == DBEVENTPHASE_DIDEVENT && m_fTempEditMode)
{
// setup notification structures
DWORD dwEventWhat = CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_NONCURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
CURSOR_DBNOTIFYREASON rgReasons[1];
VariantInit((VARIANT*)&rgReasons[0].arg1);
VariantInit((VARIANT*)&rgReasons[0].arg2);
rgReasons[0].dwReason = CURSOR_DBREASON_MODIFIED;
// notify other interested parties of action
NotifyBefore(dwEventWhat, 1, rgReasons);
NotifyAfter(dwEventWhat, 1, rgReasons);
// take out of edit mode
m_fTempEditMode = FALSE;
}
// take cursor out of edit mode if we placed it into that mode (failure)
if (ePhase == DBEVENTPHASE_FAILEDTODO && m_fTempEditMode)
{
// setup notification structures
DWORD dwEventWhat = CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED;
CURSOR_DBNOTIFYREASON rgReasons[1];
VariantInit((VARIANT*)&rgReasons[0].arg1);
VariantInit((VARIANT*)&rgReasons[0].arg2);
rgReasons[0].dwReason = CURSOR_DBREASON_CANCELUPDATE;
// notify other interested parties of action
NotifyBefore(dwEventWhat, 1, rgReasons);
NotifyAfter(dwEventWhat, 1, rgReasons);
// take out of edit mode
m_fTempEditMode = FALSE;
}
// reset cache on ending phase
if (DBEVENTPHASE_FAILEDTODO == ePhase ||
DBEVENTPHASE_DIDEVENT == ePhase)
m_bmCache.Reset();
return hr;
}
//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowChange
//=--------------------------------------------------------------------------=
// This function is called on the first change to a row, or any whole-row change
//
// Parameters:
// pRowset - [in] the IRowset that is generating the notification
// (we can ignore this since we are only ever dealing
// with a single rowset).
// cRows - [in] the count of HROWs in rghRows
// rghRows - [in] an array of HROWs which are changing
// eReason - [in] the kind of action which caused this change
// ePhase - [in] the phase of this notification
// fCantDeny - [in] when this flag is set to TRUE, the consumer cannot
// veto the event (by returning S_FALSE)
//
// Output:
// HRESULT - S_OK if successful
// S_FALSE the event/phase is vetoed
// DB_S_UNWANTEDPHASE
// DB_S_UNWANTEDREASON
//
// Notes:
//
HRESULT CVDCursorPosition::OnRowChange(IUnknown *pRowset,
ULONG cRows,
const HROW rghRows[],
DBREASON eReason,
DBEVENTPHASE ePhase,
BOOL fCantDeny)
{
// make sure we still have a valid rowset
if (!(GetRowsetSource()->IsRowsetValid()))
return S_OK;
// check for rows
if (0 == cRows)
return S_OK;
// filter notifications
switch (eReason)
{
case DBREASON_ROW_DELETE:
case DBREASON_ROW_INSERT:
case DBREASON_ROW_RESYNCH:
case DBREASON_ROW_UPDATE:
case DBREASON_ROW_UNDOCHANGE:
case DBREASON_ROW_UNDOINSERT:
break;
// the following do not generate notifications
//
// case DBREASON_ROW_ACTIVATE:
// case DBREASON_ROW_RELEASE:
// case DBREASON_ROW_FIRSTCHANGE:
// case DBREASON_ROW_UNDODELETE:
default:
return S_OK;
}
// create variables
DWORD dwEventWhat = 0;
CURSOR_DBNOTIFYREASON * pReasons = (CURSOR_DBNOTIFYREASON *)g_pMalloc->Alloc(cRows * sizeof(CURSOR_DBNOTIFYREASON));
if (!pReasons)
return S_OK;
memset(pReasons, 0, cRows * sizeof(CURSOR_DBNOTIFYREASON));
HRESULT hr;
BOOL fCurrentRow;
// iterate through supplied rows
for (ULONG ul = 0; ul < cRows; ul++)
{
if (eReason != DBREASON_ROW_UNDOINSERT)
{
// check to see if this row is current
hr = IsSameRowAsCurrent(rghRows[ul], TRUE);
switch (hr)
{
case S_OK:
fCurrentRow = TRUE;
pReasons[ul].arg1 = m_bmCurrent.GetBookmarkVariant();
break;
case S_FALSE:
fCurrentRow = FALSE;
pReasons[ul].arg1 = m_bmCache.GetBookmarkVariant();
break;
default:
hr = S_OK;
goto cleanup;
}
}
else
{
// check to see of this row is current add-row
if (m_dwEditMode == CURSOR_DBEDITMODE_ADD)
hr = IsSameRowAsNew(rghRows[ul]);
else
hr = E_FAIL;
switch (hr)
{
case S_OK:
fCurrentRow = TRUE;
pReasons[ul].arg1 = m_bmAddRow.GetBookmarkVariant();
break;
default:
hr = S_OK;
goto cleanup;
}
}
// setup variables
switch (eReason)
{
case DBREASON_ROW_DELETE:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_CHANGED |
CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
else
dwEventWhat |= CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_DELETED;
break;
case DBREASON_ROW_INSERT:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
else
dwEventWhat |= CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_INSERTED;
break;
case DBREASON_ROW_RESYNCH:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED;
else
dwEventWhat |= CURSOR_DBEVENT_NONCURRENT_ROW_DATA_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_REFRESH;
break;
case DBREASON_ROW_UPDATE:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_CHANGED |
CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
else
dwEventWhat |= CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_MODIFIED;
break;
case DBREASON_ROW_UNDOCHANGE:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
else
dwEventWhat |= CURSOR_DBEVENT_NONCURRENT_ROW_DATA_CHANGED |
CURSOR_DBEVENT_SET_OF_ROWS_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_MODIFIED;
break;
case DBREASON_ROW_UNDOINSERT:
if (fCurrentRow)
dwEventWhat |= CURSOR_DBEVENT_CURRENT_ROW_DATA_CHANGED;
pReasons[ul].dwReason = CURSOR_DBREASON_CANCELUPDATE;
break;
}
}
// notify interested cursor listeners
hr = SendNotification(ePhase, dwEventWhat, cRows, pReasons);
// take cursor out of add-mode if we received UNDOINSERT on current add-row
if (eReason == DBREASON_ROW_UNDOINSERT && ePhase == DBEVENTPHASE_DIDEVENT && hr == S_OK)
{
// if acquired, release same-row clone
if (GetSameRowClone())
ReleaseSameRowClone();
// also, release add row if we have one
if (m_bmAddRow.GetHRow())
ReleaseAddRow();
// reset edit mode
SetEditMode(CURSOR_DBEDITMODE_NONE);
// reset column updates
ResetColumnUpdates();
}
cleanup:
g_pMalloc->Free(pReasons);
// reset cache on ending phase
if (DBEVENTPHASE_FAILEDTODO == ePhase ||
DBEVENTPHASE_DIDEVENT == ePhase)
m_bmCache.Reset();
return hr;
}
//=--------------------------------------------------------------------------=
// IRowsetNotify OnRowsetChange
//=--------------------------------------------------------------------------=
// This function is called on any change affecting the entire rowset
//
// Parameters:
// pRowset - [in] the IRowset that is generating the notification
// (we can ignore this since we are only ever dealing
// with a single rowset).
// eReason - [in] the kind of action which caused this change
// ePhase - [in] the phase of this notification
// fCantDeny - [in] when this flag is set to TRUE, the consumer cannot
// veto the event (by returning S_FALSE)
//
// Output:
// HRESULT - S_OK if successful
// S_FALSE the event/phase is vetoed
// DB_S_UNWANTEDPHASE
// DB_S_UNWANTEDREASON
//
// Notes:
//
HRESULT CVDCursorPosition::OnRowsetChange(IUnknown *pRowset,
DBREASON eReason,
DBEVENTPHASE ePhase,
BOOL fCantDeny)
{
if (!(GetRowsetSource()->IsRowsetValid()))
return S_OK;
switch (eReason)
{
case DBREASON_ROWSET_RELEASE:
GetRowsetSource()->SetRowsetReleasedFlag();
break;
case DBREASON_ROWSET_FETCHPOSITIONCHANGE:
{
/*
What do we do here
DWORD dwEventWhat = CURSOR_DBEVENT_CURRENT_ROW_CHANGED;
CURSOR_DBNOTIFYREASON reason;
memset(&reason, 0, sizeof(CURSOR_DBNOTIFYREASON));
reason.dwReason = CURSOR_DBREASON_MOVE;
reason.arg1 = m_bmCurrent.GetBookmarkVariant();
VariantInit((VARIANT*)&reason.arg2);
reason.arg2.vt = VT_I4;
// the ICursor spec states that this is the value of dlOffset in
// iCursorMove::Move. Since we can't get that from the Rowset spec
// we are setting the value to an arbitrary 1
reason.arg2.lVal = 1;
return SendNotification(ePhase, CURSOR_DBEVENT_CURRENT_ROW_CHANGED, 1, &reason);
*/
break;
}
}
return S_OK;
}
//=--------------------------------------------------------------------------=
// ConnectIRowPositionChange - Connect IRowPositionChange interface
//
HRESULT CVDCursorPosition::ConnectIRowPositionChange()
{
IConnectionPointContainer * pConnectionPointContainer;
HRESULT hr = m_pRowPosition->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);
if (FAILED(hr))
return VD_E_CANNOTCONNECTIROWPOSITIONCHANGE;
IConnectionPoint * pConnectionPoint;
hr = pConnectionPointContainer->FindConnectionPoint(IID_IRowPositionChange, &pConnectionPoint);
if (FAILED(hr))
{
pConnectionPointContainer->Release();
return VD_E_CANNOTCONNECTIROWPOSITIONCHANGE;
}
hr = pConnectionPoint->Advise(&m_RowPositionChange, &m_dwAdviseCookie);
pConnectionPointContainer->Release();
pConnectionPoint->Release();
return hr;
}
//=--------------------------------------------------------------------------=
// DisconnectIRowPositionChange - Disconnect IRowPositionChange interface
//
void CVDCursorPosition::DisconnectIRowPositionChange()
{
IConnectionPointContainer * pConnectionPointContainer;
HRESULT hr = m_pRowPosition->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);
if (FAILED(hr))
return;
IConnectionPoint * pConnectionPoint;
hr = pConnectionPointContainer->FindConnectionPoint(IID_IRowPositionChange, &pConnectionPoint);
if (FAILED(hr))
{
pConnectionPointContainer->Release();
return;
}
hr = pConnectionPoint->Unadvise(m_dwAdviseCookie);
if (SUCCEEDED(hr))
m_dwAdviseCookie = 0; // clear connection point identifier
pConnectionPointContainer->Release();
pConnectionPoint->Release();
}
//=--------------------------------------------------------------------------=
// SendNotification maps the event phases to the corresponding INotifyDBEvents
// methods
//
HRESULT CVDCursorPosition::SendNotification(DBEVENTPHASE ePhase,
DWORD dwEventWhat,
ULONG cReasons,
CURSOR_DBNOTIFYREASON rgReasons[])
{
HRESULT hr = S_OK;
switch (ePhase)
{
case DBEVENTPHASE_OKTODO:
hr = NotifyOKToDo(dwEventWhat, cReasons, rgReasons);
break;
case DBEVENTPHASE_ABOUTTODO:
hr = NotifyAboutToDo(dwEventWhat, cReasons, rgReasons);
if (S_OK == hr)
hr = NotifySyncBefore(dwEventWhat, cReasons, rgReasons);
break;
case DBEVENTPHASE_SYNCHAFTER:
// SyncAfter fired from DidEvent for reentrant safety
break;
case DBEVENTPHASE_FAILEDTODO:
NotifyCancel(dwEventWhat, cReasons, rgReasons);
NotifyFail(dwEventWhat, cReasons, rgReasons);
break;
case DBEVENTPHASE_DIDEVENT:
hr = NotifySyncAfter(dwEventWhat, cReasons, rgReasons);
if (S_OK == hr)
hr = NotifyDidEvent(dwEventWhat, cReasons, rgReasons);
break;
}
if (CURSOR_DB_S_CANCEL == hr)
hr = S_FALSE;
return hr;
}
//=--------------------------------------------------------------------------=
// IUnknown QueryInterface
//
HRESULT CVDCursorPosition::QueryInterface(REFIID riid, void **ppvObjOut)
{
ASSERT_POINTER(ppvObjOut, IUnknown*)
if (!ppvObjOut)
return E_INVALIDARG;
*ppvObjOut = NULL;
if (DO_GUIDS_MATCH(riid, IID_IUnknown))
{
*ppvObjOut = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
//=--------------------------------------------------------------------------=
// IUnknown AddRef (needed to resolve ambiguity)
//
ULONG CVDCursorPosition::AddRef(void)
{
return CVDNotifier::AddRef();
}
//=--------------------------------------------------------------------------=
// IUnknown Release (needed to resolve ambiguity)
//
ULONG CVDCursorPosition::Release(void)
{
if (1 == m_dwRefCount)
Passivate(); // unhook everything including notification sink
if (1 > --m_dwRefCount)
{
if (0 == m_RowPositionChange.GetRefCount())
delete this;
return 0;
}
return m_dwRefCount;
}
//=--------------------------------------------------------------------------=
// IRowPositionChange methods implemented
//=--------------------------------------------------------------------------=
//=--------------------------------------------------------------------------=
// IRowPositionChange OnRowPositionChange
//=--------------------------------------------------------------------------=
// This function is called on any change affecting the current row
//
// Parameters:
// eReason - [in] the kind of action which caused this change
// ePhase - [in] the phase of this notification
// fCantDeny - [in] when this flag is set to TRUE, the consumer cannot
// veto the event (by returning S_FALSE)
//
// Output:
// HRESULT - S_OK if successful
// S_FALSE the event/phase is vetoed
// DB_S_UNWANTEDPHASE
// DB_S_UNWANTEDREASON
//
// Notes:
//
HRESULT CVDCursorPosition::OnRowPositionChange(DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny)
{
// return if notification caused by internal set row call
if (m_fInternalSetRow)
return S_OK;
// return if reason has anything to do with chapter changes
if (eReason == DBREASON_ROWPOSITION_CHAPTERCHANGED)
return S_OK;
IRowset * pRowset = GetRowsetSource()->GetRowset();
// make sure we have valid row position and rowset pointers
if (!m_pRowPosition || !pRowset || !GetRowsetSource()->IsRowsetValid())
return S_OK;
// synchronize hRow after event occurs
if (ePhase == DBEVENTPHASE_SYNCHAFTER)
{
HROW hRow = NULL;
HCHAPTER hChapterDummy = NULL;
DBPOSITIONFLAGS dwPositionFlags = NULL;
// get new current hRow and position flags from row position object
HRESULT hr = m_pRowPosition->GetRowPosition(&hChapterDummy, &hRow, &dwPositionFlags);
if (FAILED(hr))
return hr;
if (hRow)
{
// set new hRow
SetCurrentHRow(hRow);
pRowset->ReleaseRows(1, &hRow, NULL, NULL, NULL);
}
else
{
// set row status to beginning or end
if (dwPositionFlags == DBPOSITION_BOF)
SetCurrentRowStatus(VDBOOKMARKSTATUS_BEGINNING);
else if (dwPositionFlags == DBPOSITION_EOF)
SetCurrentRowStatus(VDBOOKMARKSTATUS_END);
}
}
CURSOR_DBNOTIFYREASON rgReasons[1];
rgReasons[0].dwReason = CURSOR_DBREASON_MOVE;
rgReasons[0].arg1 = m_bmCurrent.GetBookmarkVariant();
VariantInit((VARIANT*)&rgReasons[0].arg2);
// notify other interested parties
return SendNotification(ePhase, CURSOR_DBEVENT_CURRENT_ROW_CHANGED, 1, rgReasons);
}
//=--------------------------------------------------------------------------=
// CVDCursorPosition::CVDRowPositionChange::m_pMainUnknown
//=--------------------------------------------------------------------------=
// this method is used when we're sitting in the private unknown object,
// and we need to get at the pointer for the main unknown. basically, it's
// a little better to do this pointer arithmetic than have to store a pointer
// to the parent, etc.
//
inline CVDCursorPosition *CVDCursorPosition::CVDRowPositionChange::m_pMainUnknown
(
void
)
{
return (CVDCursorPosition *)((LPBYTE)this - offsetof(CVDCursorPosition, m_RowPositionChange));
}
//=--------------------------------------------------------------------------=
// CVDCursorPosition::CVDRowPositionChange::QueryInterface
//=--------------------------------------------------------------------------=
// this is the non-delegating internal QI routine.
//
// Parameters:
// REFIID - [in] interface they want
// void ** - [out] where they want to put the resulting object ptr.
//
// Output:
// HRESULT - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP CVDCursorPosition::CVDRowPositionChange::QueryInterface
(
REFIID riid,
void **ppvObjOut
)
{
if (!ppvObjOut)
return E_INVALIDARG;
*ppvObjOut = NULL;
if (DO_GUIDS_MATCH(riid, IID_IUnknown))
*ppvObjOut = (IUnknown *)this;
else
if (DO_GUIDS_MATCH(riid, IID_IRowPositionChange))
*ppvObjOut = (IUnknown *)this;
if (*ppvObjOut)
{
m_cRef++;
return S_OK;
}
return E_NOINTERFACE;
}
//=--------------------------------------------------------------------------=
// CVDCursorPosition::CVDRowPositionChange::AddRef
//=--------------------------------------------------------------------------=
// adds a tick to the current reference count.
//
// Output:
// ULONG - the new reference count
//
// Notes:
//
ULONG CVDCursorPosition::CVDRowPositionChange::AddRef
(
void
)
{
return ++m_cRef;
}
//=--------------------------------------------------------------------------=
// CVDCursorPosition::CVDRowPositionChange::Release
//=--------------------------------------------------------------------------=
// removes a tick from the count, and delets the object if necessary
//
// Output:
// ULONG - remaining refs
//
// Notes:
//
ULONG CVDCursorPosition::CVDRowPositionChange::Release
(
void
)
{
ULONG cRef = --m_cRef;
if (!m_cRef && !m_pMainUnknown()->m_dwRefCount)
delete m_pMainUnknown();
return cRef;
}
//=--------------------------------------------------------------------------=
// IRowPositionChange OnRowPositionChange
//=--------------------------------------------------------------------------=
// Forward to all CVDCursor objects in our family
//
HRESULT CVDCursorPosition::CVDRowPositionChange::OnRowPositionChange(DBREASON eReason,
DBEVENTPHASE ePhase,
BOOL fCantDeny)
{
return m_pMainUnknown()->OnRowPositionChange(eReason, ePhase, fCantDeny);
}