CHILD.C

/*-------------------------------------------------------------------------- 
Child.C --- Cursors child window procedure

Description:
This sample is spread across four files, each named for the role
the contained functions play. Each file header contains a brief
description of its purpose and the routines it contains.

CHILD.C contains those routines which maintain a child window.
These include most interfaces with ODBC including all data
retrieval and display. These functions are:

AllocChild - Allocate and prepare child window memory
AllocClipRgn - Allocate clip region for painting
Cancel - Cancel SQL request
CancelSQL - Cancel asynchronous SQL request
ChildProc - Process child window messages
CvtSqlToCType - Return the default ODBC C type for a SQL type
DeleteRow - Build and issue a positioned delete
DoChildMenu - Process a menu request
DoSQL - Issue SQL statement and prepare all required
variables necessary for displaying the data
Fetch - Retrieve one row set
FreeStmt - Issue ODBC SQLFreeStmt (and adjust child memory)
GetCurrentValue - Retrieve (in character format) column value
from current row (used by DIALOGS.C)
GetData - Issue a SQLGetData request
GetTableName - Extract table name from SQL
IsUpdateable - Check whether a column can be updated
OnDataRow - Determine if point is over displayed row of data
PaintChild - Paint child window
ParamValid - Validate the max column width of lpChild
SetCurrentValue - Set a column value in the current row
(used by DIALOGS.C)
SetPos - Set current position in row set
SetScroll - Set scroll bar states and ranges
SizeScroll - Size and position scroll bars
UpdateRow - Build and issue a positioned update request

This code is furnished on an as-is basis as part of the ODBC SDK and is
intended for example purposes only.

--------------------------------------------------------------------------*/

/* Includes --------------------------------------------------------------*/
#include "headers.h"

#pragma warning(disable:4001)
#include "resource.h"
#include "crsrdemo.h"


// Constants ---------------------------------------------------------------
#define fDISABLED (MF_BYCOMMAND | MF_DISABLED | MF_GRAYED)
#define fENABLED (MF_BYCOMMAND | MF_ENABLED)
#define NULLIFEMPTY(x) (*x?x:NULL)

const char szDELETE[] = "DELETE FROM ";
const char szFROM[] = "FROM";
const char szUPDATE[] = "UPDATE ";
const char szWHERE[] = " WHERE CURRENT OF ";
const char szNoDataTitle[] = "No data to display";
const char szNoData[] = "The query didn't return any data";
const char szSET[] = " SET ";
const char szDataAffected[] = "%ld rows were affected";
const char szRowAffected[] = "%ld row was affected";

const int cMAXCOLS = 15;

#define Async(x) lpChild->fCanceled = FALSE; \
while ((rc = (x)) == SQL_STILL_EXECUTING) \
CancelSQL(lpChild);

#define STMTError(x) ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmt, (x))


// Prototypes --------------------------------------------------------------
LPCHILD INTFUNC AllocChild(HWND);
void INTFUNC AllocClipRgn(LPCHILD);
void INTFUNC Cancel(LPCHILD);
void INTFUNC CancelSQL(LPCHILD);
SWORD INTFUNC CvtSqlToCType(SWORD);
void INTFUNC DeleteRow(LPCHILD);
BOOL INTFUNC DoChildMenu(LPCHILD, WPARAM, LPARAM);
void INTFUNC DoSQL(LPCHILD);
void INTFUNC Fetch(LPCHILD);
void INTFUNC FreeStmt(UWORD, LPCHILD);
void INTFUNC GetData(LPCHILD);
void INTFUNC GetTableName(LPSTR, LPCSTR);
int INTFUNC OnDataRow(LPCHILD, LPARAM);
void INTFUNC PaintChild(LPCHILD, HDC, BOOL, BOOL, BOOL);
BOOL INTFUNC ParamValid(LPCHILD);
void INTFUNC SetPos(LPCHILD, UWORD);
void INTFUNC SetScroll(LPCHILD);
void INTFUNC SizeScroll(LPCHILD);
void INTFUNC UpdateRow(LPCHILD);
#ifdef THREAD
void INTFUNC DeleteRowThread(LPCHILD);
void INTFUNC DoSQLThread(LPCHILD);
void INTFUNC FetchThread(LPCHILD);
void INTFUNC GetDataThread(LPCHILD);
void INTFUNC UpdateRowThread(LPCHILD);
#endif

/* AllocChild --------------------------------------------------------------
Description: Allocate and initialize child variables
--------------------------------------------------------------------------*/
LPCHILD INTFUNC AllocChild(HWND hwnd)
{
SQLHSTMT hstmt;
LPCHILD lpChild;
char sz[cbSTRLEN];

// Allocate ODBC SQLHSTMT and set cursor name
if (DBCError(hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &hstmt)))
return NULL;

#ifdef THREAD
wsprintf(sz, szTITLEFMT, (LPSTR)g_szDSN, g_cCursor, GetCurrentThreadId());
#else
wsprintf(sz, szTITLEFMT, (LPSTR)g_szDSN, g_cCursor);
#endif

SetWindowText(hwnd, sz);

wsprintf(sz, szCURSORNAME, g_cCursor);
if (ODBCError(hwnd, SQL_HANDLE_STMT, hstmt,
SQLSetCursorName(hstmt, (UCHAR FAR *)sz, SQL_NTS)))
return NULL;

// Allocate child window structure and initialize
lpChild = (LPCHILD)AllocPtr(sizeof(CHILD));

lpChild->hwnd = hwnd;
lpChild->fInSetScroll = FALSE;
lpChild->fIsMinimized = FALSE;
lpChild->fHaveMouse = FALSE;
lpChild->iMouseRow = -1;
lpChild->fNoConcurrency = FALSE;
lpChild->fNoCursorType = FALSE;

lpChild->ccols = 0;
lpChild->crowwin = 0;
lpChild->ccolwin = 0;
lpChild->fVScroll =
lpChild->fHScroll = FALSE;
lpChild->lpsz = AllocPtr(cbBUFSIZE);

lpChild->hrgn = NULL;

lpChild->hstmt = hstmt;
lpChild->hstmtTmp = SQL_NULL_HSTMT;

lpChild->fBindByRow = IDC_RADIO_BINDROW;
lpChild->fConcurrency = SQL_CONCUR_VALUES;
lpChild->crowKeyset = SQL_CURSOR_STATIC;
lpChild->crowRowset = 10;
lpChild->fAsync = FALSE;
lpChild->irowPos = 0;
lpChild->irow = 0;
lpChild->cBind = 0;
lpChild->fBindAll = TRUE;
lpChild->ccolRetrieved = 0;

lpChild->arow = 1;
lpChild->rrow = 10;
lpChild->ccol = 0;
lpChild->lpnTabs = NULL;
lpChild->lpcol = NULL;
lpChild->lpfStatus = NULL;
lpChild->fResultSetExists = FALSE;
lpChild->fDataFetched = FALSE;
lpChild->rglpv = NULL;
lpChild->crowMaxBind = DEF_MAXBIND;

lpChild->lpb = NULL;
lpChild->sql = AllocPtr(cbMAXSQL);
lpChild->cbrow = 0;
lpChild->dwGuiFlags = GUIF_ALWAYSFETCH;
#ifdef THREAD
InitializeCriticalSection (&lpChild->ThreadCreation);
#endif

// Create scroll bars
lpChild->hwndVScroll = CreateWindow(szSCROLLCLASS, NULL,
WS_CHILD | SBS_VERT,
0, 0, 0, 0,
hwnd, (HMENU)1, g_hinst, NULL);

lpChild->hwndHScroll = CreateWindow(szSCROLLCLASS, NULL,
WS_CHILD | SBS_HORZ,
0, 0, 0, 0,
hwnd, (HMENU)2, g_hinst, NULL);

// Load default SQL string
LoadString(g_hinst, IDS_SQL, sz, cbSTRLEN);
wsprintf(lpChild->sql, sz, g_szTable);

return lpChild;
}


/* AllocClipRgn ------------------------------------------------------------
Description: Allocate child window clip region
--------------------------------------------------------------------------*/
void INTFUNC AllocClipRgn(LPCHILD lpChild)
{
RECT rc;

// Determine client window size less space for scroll bars
GetClientRect(lpChild->hwnd, &rc);

if (lpChild->hrgn) DeleteObject(lpChild->hrgn);

if (lpChild->fVScroll)
rc.right -= g_cxVScroll - 1;
rc.bottom -= g_cyHScroll - 1;

// Allocate clip region
lpChild->hrgn = CreateRectRgn(rc.left,
rc.top,
rc.right,
rc.bottom);
return;
}


/* CancelSQL ---------------------------------------------------------------
Description: Display message while an async request is executing and
give the user a chance to cancel the request (if it has
not already been canceled)
--------------------------------------------------------------------------*/
void INTFUNC CancelSQL(LPCHILD lpChild)
{
char sz[cbSTRLEN];
int rc;

// Display message
LoadString(g_hinst, IDS_STILLEXEC, sz, sizeof(sz));
rc = MessageBox(lpChild->hwnd,
sz, g_szTITLE,
MB_ICONINFORMATION |
(lpChild->fCanceled
? MB_OK
: MB_OKCANCEL | MB_DEFBUTTON1));

// If the user requested, cancel the current request
if (rc == IDCANCEL)
lpChild->fCanceled = SUCCESS(SQLCancel(lpChild->hstmt));

return;
}


/* ChildProc ---------------------------------------------------------------
Description: Child window procedure
--------------------------------------------------------------------------*/
LRESULT EXPFUNC ChildProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
LPCHILD lpChild;

// Get access to child variables and set current window handle
lpChild = (LPCHILD)GetWindowLong(hwnd, 0);

switch (msg) {

// Allocate child variables and save pointer
case WM_CREATE:
lpChild = AllocChild(hwnd);

SetWindowLong(hwnd, 0, (LONG)lpChild);

if (!lpChild)
return -1;
break;

// Paint child window (active or inactive)
case WM_PAINT: {
PAINTSTRUCT ps;
BOOL fActive;

fActive = (hwnd ==
FORWARD_WM_MDIGETACTIVE(g_hwndClient, SendMessage));

BeginPaint(hwnd, &ps);
PaintChild(lpChild, ps.hdc, TRUE, FALSE, fActive);
EndPaint(hwnd, &ps);
break;
}

// Trap mouse if over a rowset row
case WM_LBUTTONDOWN:
lpChild->iMouseRow = OnDataRow(lpChild, lparam);
if (lpChild->iMouseRow >= 0) {
lpChild->fHaveMouse = TRUE;
SetCapture(hwnd);
}
break;

// Make row current row (if mouse is still on the row)
case WM_LBUTTONUP:
if (!lpChild->fHaveMouse)
break;

ReleaseCapture();
lpChild->fHaveMouse = FALSE;

if (lpChild->fDataFetched &&
lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY &&
lpChild->iMouseRow == OnDataRow(lpChild, lparam)) {
RECT rc;
int y;

GetClientRect(hwnd, &rc);

y = (int)HIWORD(lparam) - rc.top - g_cy;

SetPos(lpChild,
(UWORD)(GetScrollPos(lpChild->hwndVScroll, SB_CTL) + (y / g_cy) + 1));
}
break;

// Convert keyboard requests to scroll and change window requests
case WM_KEYDOWN:
if (wparam == VK_TAB) {
FORWARD_WM_MDINEXT(g_hwndClient, hwnd,
(GetKeyState(VK_BACK) & 0x1000 ? TRUE :FALSE),
SendMessage);
break;
}

else if (wparam == VK_DOWN || wparam == VK_UP) {

msg = WM_VSCROLL;
GET_WM_VSCROLL_CODE(wparam, lparam) =
(wparam == VK_DOWN
? SB_LINEDOWN
: SB_LINEUP);
}
else if (wparam == VK_LEFT || wparam == VK_RIGHT) {
if (!lpChild->fHScroll)
break;

msg = WM_HSCROLL;
GET_WM_HSCROLL_CODE(wparam, lparam) =
(wparam == VK_RIGHT
? SB_LINEDOWN
: SB_LINEUP);
}
else
break;

// Scroll window
case WM_HSCROLL:
case WM_VSCROLL: {
HWND hwndCtl;
int cInc;
int iPos;
int cPage;
int nPos;
int iOrig;
int nMin, nMax;

if (!lpChild->fDataFetched)
break;

// Determine scroll direction and distance
hwndCtl = (msg == WM_HSCROLL
? lpChild->hwndHScroll
: lpChild->hwndVScroll);
cInc = (msg == WM_HSCROLL ? 1 : 1);
cPage = (msg == WM_HSCROLL
? lpChild->ccolwin
: lpChild->crowwin - 1);
nPos = GET_WM_HSCROLL_POS(wparam, lparam);
iPos =
iOrig = GetScrollPos(hwndCtl, SB_CTL);

GetScrollRange(hwndCtl, SB_CTL, &nMin, &nMax);
switch (GET_WM_HSCROLL_CODE(wparam, lparam)) {
case SB_BOTTOM: iPos = nMax; break;
case SB_LINEDOWN: iPos+= cInc; break;
case SB_LINEUP: iPos-= cInc; break;
case SB_PAGEDOWN: iPos+= cPage; break;
case SB_PAGEUP: iPos-= cPage; break;
case SB_TOP: iPos = nMin; break;
case SB_THUMBPOSITION: iPos = nPos; break;
}

// For updateable cursors, vertical scroll requests move the
// current row scroll the window only as needed to keep the
// current row visible
if (msg == WM_VSCROLL &&
lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY &&
lpChild->fConcurrency != SQL_CONCUR_READ_ONLY) {
int delta;

if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEDOWN)
delta = cInc;
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEUP)
delta = -cInc;
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_PAGEDOWN) {
if (iPos <= nMax)
delta = cPage;
else
delta = lpChild->crowRowset - lpChild->irowPos;
}
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_PAGEUP) {
if (iPos >= nMin)
delta = -cPage;
else
delta = 1 - lpChild->irowPos;
}
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_BOTTOM)
delta = lpChild->crowRowset - lpChild->irowPos;
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_TOP)
delta = 1 - lpChild->irowPos;
else if (GET_WM_HSCROLL_CODE(wparam, lparam) == SB_THUMBPOSITION) {
if (lpChild->irowPos > (UWORD)iPos &&
lpChild->irowPos < (UWORD)(iPos+lpChild->crowwin))
delta = 0;
else if (iPos == nMin)
delta = 1 - lpChild->irowPos;
else if (iPos == nMax)
delta = lpChild->crowRowset - lpChild->irowPos;
else if (iPos <= iOrig)
delta = iPos + lpChild->crowwin - 1 - lpChild->irowPos;
else
delta = iPos - lpChild->irowPos + 1;
}
else
break;

SetPos(lpChild, (UWORD)(lpChild->irowPos + delta));

if ((GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEDOWN ||
GET_WM_HSCROLL_CODE(wparam, lparam) == SB_LINEUP) &&
lpChild->irowPos > (UWORD)iOrig &&
lpChild->irowPos < (UWORD)(iOrig+lpChild->crowwin))
break;
}

// Pin scroll requests within scroll boundaries
if (iPos < nMin)
iPos = nMin;
else if (iPos > nMax)
iPos = nMax;

// Scroll the window if movement has occurred
if (iPos != iOrig) {
HDC hdc;
BOOL fTitle;

hdc = GetDC(hwnd);

SetScrollPos(hwndCtl, SB_CTL, iPos, TRUE);

fTitle = (msg == WM_HSCROLL);

PaintChild(lpChild, hdc, fTitle, FALSE, TRUE);

ReleaseDC(hwnd, hdc);
}
break;
}

// Activate the child window
case WM_MDIACTIVATE: {
HDC hdc;

AdjustMenus();

hdc = GetDC(hwnd);

PaintChild(lpChild, hdc, TRUE, TRUE,
GET_WM_MDIACTIVATE_FACTIVATE(lpChild->hwnd, wparam, lparam));

ReleaseDC(lpChild->hwnd, hdc);
break;
}

// Free all child memory
case WM_DESTROY:
if (lpChild) {
FreeStmt(SQL_DROP, lpChild);

FreePtr(lpChild->lpsz);
FreePtr(lpChild->sql);
#ifdef THREAD
DeleteCriticalSection (&lpChild->ThreadCreation);
#endif
FreePtr(lpChild);

SetWindowLong(hwnd, 0, 0L);
}
break;

// Close the window
case WM_CLOSE:
g_cChild--;

if( !g_cChild )
#ifdef WIN32
SendMessage(g_hwndClient, WM_MDISETMENU,
(WPARAM)g_hmenuFrame,
(LPARAM)g_hmenuFrameWindow);
#else
FORWARD_WM_MDISETMENU(g_hwndClient, 0, g_hmenuFrame,
g_hmenuFrameWindow, SendMessage);
#endif

// Destroy child window
FORWARD_WM_MDIDESTROY(g_hwndClient, hwnd, SendMessage);
AdjustMenus();
break;

// Pass all other messages (eventually) to the MDI window procedure
default:

// Reset scroll bars (if needed) when the window is resized
if (msg == WM_SIZE) {

if (wparam == SIZE_MINIMIZED)
lpChild->fIsMinimized = TRUE;

else {
if (lpChild->fIsMinimized)
lpChild->fIsMinimized = FALSE;

SizeScroll(lpChild);

if (lpChild->fDataFetched) {
int row;

SetScroll(lpChild);

row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);

if (lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
lpChild->irowPos >= (UWORD)(row+lpChild->crowwin))
if (lpChild->crowwin > 1)
SetScrollPos(lpChild->hwndVScroll,
SB_CTL,
lpChild->irowPos-lpChild->crowwin+1,
TRUE);
else
SetScrollPos(lpChild->hwndVScroll,
SB_CTL,
lpChild->irowPos-lpChild->crowwin,
TRUE);
}

AllocClipRgn(lpChild);
}

InvalidateRect(hwnd, NULL, TRUE);
}

// Handle child window menu requests
else if (msg == WM_COMMAND)
DoChildMenu(lpChild, wparam, lparam);

// Pass message on to the MDI window procedure
return DefMDIChildProc(hwnd, msg, wparam, lparam);
}

return (LRESULT)NULL;
}


/* CvtSqlToCType -----------------------------------------------------------
Description: Determine the default ODBC C type for a given SQL type
--------------------------------------------------------------------------*/
SWORD INTFUNC CvtSqlToCType(SWORD fSqlType)
{
switch (fSqlType) {
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_LONGVARCHAR:
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_BIGINT: return SQL_C_CHAR;

case SQL_BIT: return SQL_C_BIT;
case SQL_TINYINT:
case SQL_SMALLINT: return SQL_C_SHORT;
case SQL_INTEGER: return SQL_C_LONG;
case SQL_REAL: return SQL_C_FLOAT;

case SQL_FLOAT:
case SQL_DOUBLE: return SQL_C_DOUBLE;

case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY: return SQL_C_BINARY;

case SQL_TYPE_DATE: return SQL_C_TYPE_DATE;
case SQL_TYPE_TIME: return SQL_C_TYPE_TIME;
case SQL_TYPE_TIMESTAMP: return SQL_C_TYPE_TIMESTAMP;

case SQL_DATE: return SQL_C_DATE;
case SQL_TIME: return SQL_C_TIME;
case SQL_TIMESTAMP: return SQL_C_TIMESTAMP;
}

return SQL_C_CHAR;
}


/* DeleteRow ---------------------------------------------------------------
Description: Delete the current (positioned) row
--------------------------------------------------------------------------*/
void INTFUNC DeleteRow(LPCHILD lpChild)
{
HCURSOR hcur;
LPSTR lpsz;
LPSTR lpszT;
SWORD cb;

// Ensure the delete request is valid
if (!lpChild->fDataFetched) {
DoMessage(lpChild->hwnd, IDS_NODATAFETCHED);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_NOROW) {
DoMessage(lpChild->hwnd, IDS_NOROWDELETE);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_ERROR) {
DoMessage(lpChild->hwnd, IDS_ERRORROWDELETE);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_DELETED) {
DoMessage(lpChild->hwnd, IDS_DELROWDELETE);
return;
}

if (lpChild->fConcurrency == SQL_CONCUR_READ_ONLY) {
DoMessage(lpChild->hwnd, IDS_NOUPDATE);
return;
}

lpsz = AllocPtr(2 * cbMAXSQL);
lpszT = lpsz + cbMAXSQL;

// Verify the request and allocate a new (temporary) SQLHSTMT for the delete
LoadString(g_hinst, IDS_DELETEROW, lpsz, cbMAXSQL);
if (IDYES == MessageBox(lpChild->hwnd, lpsz,
g_szTITLE, MB_ICONQUESTION | MB_YESNO) &&
!DBCError(lpChild->hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &lpChild->hstmtTmp))) {

// Build DELETE <table> WHERE CURRENT OF <cursor> statement
lstrcpy(lpsz, szDELETE);

GetTableName(lpszT, lpChild->sql);
lstrcat(lpsz, lpszT);

lstrcat(lpsz, szWHERE);

lpszT = lpsz + lstrlen(lpsz);

hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

if (!STMTError(SQLGetCursorName(lpChild->hstmt, (UCHAR FAR *)lpszT,
cbMAXSQL, &cb))) {

// Issue the request via SQLPrepare/SQLExecute
if (!ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
SQLPrepare(lpChild->hstmtTmp, (UCHAR FAR *)lpsz, SQL_NTS)) &&
!ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
SQLExecute(lpChild->hstmtTmp)) ) {
UWORD irowPos;

irowPos = lpChild->irowPos;

// Completely refresh local rowset buffer
lpChild->rrow = 0;
lpChild->FetchOP = SQL_FETCH_RELATIVE;
Fetch(lpChild);

// Reset current position (fetching sets it to the first row)
SetPos(lpChild, irowPos);

// Repaint window
InvalidateRect(lpChild->hwnd, NULL, FALSE);
}
}

DBCError(lpChild->hwnd, SQLFreeHandle(SQL_HANDLE_STMT,lpChild->hstmtTmp));
lpChild->hstmtTmp = SQL_NULL_HSTMT;

SetCursor(hcur);

}

FreePtr(lpsz);

return;
}


/* DoChildMenu -------------------------------------------------------------
Description: Respond to a request from the child window menu
--------------------------------------------------------------------------*/
BOOL INTFUNC DoChildMenu(LPCHILD lpChild, WPARAM wParam, LPARAM lParam)
{
UNREF_PARAM (lParam);
switch (GET_WM_COMMAND_ID(wParam, lparam)) {

case IDM_STMT_SEND:
if (IDOK == DoDialog(lpChild->hwnd, IDD_STATEMENT, StmtDlgProc))
#ifdef THREAD
DoSQLThread(lpChild);
#else
DoSQL(lpChild);
#endif
break;

case IDM_STMT_TABLE:
if (IDOK == DoDialog(lpChild->hwnd, IDD_TABLE_INFO, SQLTablesDlgProc))
#ifdef THREAD
DoSQLThread(lpChild);
#else
DoSQL(lpChild);
#endif
break;

case IDM_STMT_TYPE:
lpChild->dwOperation = OPER_TYPES;
#ifdef THREAD
DoSQLThread(lpChild);
#else
DoSQL(lpChild);
#endif
break;



case IDM_STMT_OPTIONS: // general
{
CHILD ChildOld;

// save old values (only works because no pointers
// get modified in options)

memcpy(&ChildOld, lpChild, sizeof(ChildOld));
// no modification on those option values yet
lpChild->fBind =
lpChild->fMaxBind =
lpChild->fRowset = FALSE;

if (IDOK == DoDialog(lpChild->hwnd, IDD_OPTION_DIALOG,
OptionsDlgProc) && ParamValid(lpChild)) {
if (lpChild->fDataFetched) {
FreeStmt(SQL_CLOSE, lpChild);
FreeStmt(SQL_UNBIND, lpChild);
}
}
else {
// restore previous state
memcpy(lpChild, &ChildOld, sizeof(ChildOld));
}
break;
}

case IDM_STMT_CANCEL: // general
Cancel(lpChild);
break;

case IDM_FETCH_FIRST:
lpChild->FetchOP = SQL_FETCH_FIRST;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
break;

case IDM_FETCH_PRIOR:
lpChild->FetchOP = SQL_FETCH_PRIOR;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
break;

case IDM_FETCH_NEXT:
lpChild->FetchOP = SQL_FETCH_NEXT;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
break;

case IDM_FETCH_LAST:
lpChild->FetchOP = SQL_FETCH_LAST;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
break;

case IDM_FETCH_ABSOLUTE:
if (IDOK == DoDialog(lpChild->hwnd, IDD_ABSOLUTE, AbsDlgProc)) {
lpChild->FetchOP = SQL_FETCH_ABSOLUTE;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
}
break;

case IDM_FETCH_RELATIVE:
if (IDOK == DoDialog(lpChild->hwnd, IDD_RELATIVE, RelDlgProc)) {
lpChild->FetchOP = SQL_FETCH_RELATIVE;
#ifdef THREAD
FetchThread(lpChild);
#else
Fetch(lpChild);
#endif
}
break;

case IDM_FETCH_GET:
#ifdef THREAD
GetDataThread(lpChild);
#else
GetData(lpChild);
#endif
break;

case IDM_FETCH_DELETEROW:
#ifdef THREAD
DeleteRowThread(lpChild);
#else
DeleteRow(lpChild);
#endif
break;

case IDM_FETCH_UPDATEROW:
#ifdef THREAD
UpdateRowThread(lpChild);
#else
UpdateRow(lpChild);
#endif
break;
default:
return FALSE;
}

AdjustMenus();
return TRUE;
}

#ifdef THREAD
void INTFUNC DoSQLThread(LPCHILD lpChild)
{
DWORD dwThreadId;

EnterCriticalSection (&lpChild->ThreadCreation);
lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)DoSQL,
(LPVOID)lpChild, 0, &dwThreadId);
LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC FetchThread(LPCHILD lpChild)
{
DWORD dwThreadId;

EnterCriticalSection (&lpChild->ThreadCreation);
lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)Fetch,
(LPVOID)lpChild, 0, &dwThreadId);
LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC GetDataThread(LPCHILD lpChild)
{
DWORD dwThreadId;

EnterCriticalSection (&lpChild->ThreadCreation);
lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)GetData,
(LPVOID)lpChild, 0, &dwThreadId);
LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC UpdateRowThread(LPCHILD lpChild)
{
DWORD dwThreadId;

EnterCriticalSection (&lpChild->ThreadCreation);
lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)UpdateRow,
(LPVOID)lpChild, 0, &dwThreadId);
LeaveCriticalSection (&lpChild->ThreadCreation);
}

void INTFUNC DeleteRowThread(LPCHILD lpChild)
{
DWORD dwThreadId;

EnterCriticalSection (&lpChild->ThreadCreation);
lpChild->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)DeleteRow,
(LPVOID)lpChild, 0, &dwThreadId);
LeaveCriticalSection (&lpChild->ThreadCreation);
}
#endif


/* DoSQL -------------------------------------------------------------------
Description: Issue a SQL statement and prepare for fetching data
--------------------------------------------------------------------------*/

void INTFUNC DoSQL(LPCHILD lpChild) 
{
SQLRETURN rc;

// Prepare the statement

if (PrepareStmt(lpChild) == SQL_ERROR)
return;

switch(lpChild->dwOperation) {
case OPER_SELECT:
// Issue the request via SQLExecDirect
Async(SQLExecDirect(lpChild->hstmt,
(UCHAR FAR *)lpChild->sql,
lstrlen(lpChild->sql)));
break;

case OPER_TYPES:
// Issue a GetTypeInfo request
Async(SQLGetTypeInfo(lpChild->hstmt, SQL_ALL_TYPES));
break;


case IDC_TABLE_RAD_TABLE:
if (!*lpChild->szTable &&
((!*lpChild->szUser && strcmp(lpChild->szQualifier, "%") == 0)||
(!*lpChild->szQualifier && strcmp(lpChild->szUser, "%") == 0))) { // Special qualifier enumeration
Async(SQLTables(lpChild->hstmt,
lpChild->szQualifier, SQL_NTS,
lpChild->szUser, SQL_NTS,
lpChild->szTable, SQL_NTS,
lpChild->szType, SQL_NTS));
}
else
{ // Normal SQLTables call
Async(SQLTables(lpChild->hstmt,
NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
NULLIFEMPTY(lpChild->szUser), SQL_NTS,
NULLIFEMPTY(lpChild->szTable), SQL_NTS,
NULLIFEMPTY(lpChild->szType), SQL_NTS));
}
break;

case IDC_TABLE_RAD_PRIV:
Async(SQLTablePrivileges(lpChild->hstmt,
NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
NULLIFEMPTY(lpChild->szUser), SQL_NTS,
NULLIFEMPTY(lpChild->szTable), SQL_NTS));

break;

case IDC_TABLE_RAD_STATISTICS:
Async(SQLStatistics(lpChild->hstmt,
NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
NULLIFEMPTY(lpChild->szUser), SQL_NTS,
NULLIFEMPTY(lpChild->szTable), SQL_NTS,
SQL_INDEX_ALL, // XXX
SQL_QUICK)); // XXX
break;



case IDC_TABLE_RAD_PROC:
Async(SQLProcedures(lpChild->hstmt,
NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
NULLIFEMPTY(lpChild->szUser), SQL_NTS,
NULLIFEMPTY(lpChild->szTable), SQL_NTS));

break;

case IDC_TABLE_RAD_COLUMN:
Async(SQLColumns(lpChild->hstmt,
NULLIFEMPTY(lpChild->szQualifier), SQL_NTS,
NULLIFEMPTY(lpChild->szUser), SQL_NTS,
NULLIFEMPTY(lpChild->szTable), SQL_NTS,
NULLIFEMPTY(lpChild->szColName), SQL_NTS));

}

if (STMTError(rc))
return;

if (ProcessResults(lpChild)) {

if (lpChild->dwGuiFlags & GUIF_ALWAYSFETCH) {
lpChild->FetchOP = SQL_FETCH_NEXT;
Fetch(lpChild);
}
}

AdjustMenus();
return;

}

/* ProcessResults---------------------------------------------------------------
Description: Process the results from a query or statement
--------------------------------------------------------------------------*/
BOOL INTFUNC ProcessResults(LPCHILD lpChild)
{
LPINT lptab;
LPCOL lpcol;
LPBYTE lpb;
SDWORD cbMsg;
SDWORD cbNull;
int nLastTab;
SWORD i;
SQLRETURN rc;


// Retrieve number of columns in the result set
Async(SQLNumResultCols(lpChild->hstmt, &i));
if (STMTError(rc))
return FALSE;
lpChild->ccol = ((UWORD)i < lpChild->cBind || lpChild->fBindAll
? i
: lpChild->cBind);

// If a result set exists, continue; otherwise, return immediately
if (lpChild->ccol)
lpChild->fResultSetExists = TRUE;
else
return FALSE;

// Allocate painting related storage and row status array

lpChild->rglpv = (char *)AllocPtr(sizeof(char *) * lpChild->ccol);
lpChild->lpnTabs = (LPINT)AllocPtr(sizeof(int) * (lpChild->ccol+1));
lpChild->lpcol = (LPCOL)AllocPtr(sizeof(COL) * lpChild->ccol);
lpChild->lpfStatus = (LPUWORD)AllocPtr((DWORD) sizeof(UWORD)
* lpChild->crowRowset);

for (i=1, lpcol=lpChild->lpcol; i <= lpChild->ccol; i++, lpcol++) {
lpcol->lpb = NULL;
lpcol->lpcb = NULL;
}

cbMsg = lstrlen(g_szRowDeleted);
cbNull = lstrlen(g_szNull);
cbMsg = max(cbMsg, lstrlen(g_szNoRow));
cbMsg = max(cbMsg, cbNull);

// Initialize row width (in bytes) and total number of characters per line
lpChild->cbrow = 0;
lpChild->ccols = 0;

lpcol = lpChild->lpcol;
lptab = lpChild->lpnTabs;

nLastTab =
*lptab++ = cxBORDER;

// For each bound column
// a) Get column attributes (e.g., name, size, data type)
// b) Add column to physical and display row widths
// c) Determine tab location
for (i=1; i <= lpChild->ccol; i++, lpcol++, lptab++) {

// Get column name
Async(SQLColAttribute(lpChild->hstmt, i,
SQL_DESC_NAME,
lpcol->szName, sizeof(lpcol->szName), NULL,
NULL));
if (STMTError(rc)) {
FreeStmt(SQL_DROP, lpChild);
return FALSE;
}

// Get actual column length (number of physical data bytes)
// if the sample were to be used with a 3.0 driver only SQLColAttribute
// with fattribute of SQL_DESC_OCTECT_LENGTH could be called instead.
Async(SQLColAttributes(lpChild->hstmt, i,
SQL_COLUMN_LENGTH,
NULL, 0, NULL,
&lpcol->cb));


if (STMTError(rc)) {
FreeStmt(SQL_DROP, lpChild);
return FALSE;
}

// Get display width
Async(SQLColAttribute(lpChild->hstmt, i,
SQL_DESC_DISPLAY_SIZE,
NULL, 0, NULL,
&lpcol->cbc));
if (STMTError(rc)) {
FreeStmt(SQL_DROP, lpChild);
return FALSE;
}


// if the display size is too big, force to the the maximum
if (lpcol->cbc > lpChild->crowMaxBind)
lpcol->cbc = lpChild->crowMaxBind;

// Ensure display width is wide enough for:
// a) Column name
// b) Null string
// c) Row status (for the first column only)
if (lpcol->cbc < cbMsg)
lpcol->cbc = cbMsg;

if (lstrlen(lpcol->szName) > lpcol->cbc)
lpcol->cbc = lstrlen(lpcol->szName);

lpcol->cbc++;

// Get column SQL type
Async(SQLColAttribute(lpChild->hstmt, i,
SQL_DESC_CONCISE_TYPE,
NULL, 0, NULL,
(SDWORD FAR*) &lpcol->fSqlType));
if (STMTError(rc)) {
FreeStmt(SQL_DROP, lpChild);
return FALSE;
}

// Determine target C type
lpcol->fCType = CvtSqlToCType(lpcol->fSqlType);

// For hard to handle C types, let the driver convert to character
if (lpcol->fCType == SQL_C_BIT ||
lpcol->fCType == SQL_C_BINARY ||
lpcol->fCType == SQL_C_DATE ||
lpcol->fCType == SQL_C_TIME ||
lpcol->fCType == SQL_C_TIMESTAMP ||
lpcol->fCType == SQL_C_TYPE_DATE ||
lpcol->fCType == SQL_C_TYPE_TIME ||
lpcol->fCType == SQL_C_TYPE_TIMESTAMP) {
lpcol->fCType = SQL_C_CHAR;
lpcol->cb = lpcol->cbc;
}

// Determine next column tab (based on column width plus border)
nLastTab =
*lptab = nLastTab + ((int)lpcol->cbc * g_cx) + (2 * cxBORDER);

// Set maximum column length for character data
if( lpcol->cb > lpChild->crowMaxBind &&
lpcol->fCType == SQL_C_CHAR )
lpcol->cb = lpChild->crowMaxBind;

if( lpcol->fCType == SQL_C_CHAR )
lpcol->cb++;

// Increment total phsyical row width and display width
lpChild->cbrow += lpcol->cb;
lpChild->ccols += (int)lpcol->cbc;

cbMsg = cbNull;
}

// Include a count field for each bound column in physical row width
lpChild->cbrow += lpChild->ccol * sizeof(SDWORD);

// Add intra-column border amounts to total character width
lpChild->ccols += (lpChild->ccol * (2 * cxBORDER)) / g_cx;

// For each column, include an element in the format string
lpb = (LPBYTE)lpChild->szFmt;
for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
*(lpb++) = '\t';
*(lpb++) = '%';
*(lpb++) = 's';
}
*lpb = 0;

// If row-wise binding, allocate a buffer; switch to column-wise
// if the entire row-set cannot fit into 64K minus cursor library
// headers
lpb = NULL;
if (ROW_BINDING(lpChild)) {
if ((lpChild->cbrow * lpChild->crowRowset) > 65500L) {
DoMessage(lpChild->hwnd, IDS_BIGROWSET);
lpChild->fBindByRow = IDC_RADIO_BINDCOL ;
}
else {
lpb =
lpChild->lpb = (LPBYTE)AllocPtr(lpChild->cbrow *
lpChild->crowRowset);
}
}

// Set binding type
if (STMTError(SQLSetStmtAttr(lpChild->hstmt,
SQL_ATTR_ROW_BIND_TYPE,
(SQLPOINTER) (ROW_BINDING(lpChild)
? lpChild->cbrow
: SQL_BIND_BY_COLUMN),
SQL_IS_INTEGER)))
return FALSE;

// Finally, for each bound column, bind the data value
for (i=1, lpcol=lpChild->lpcol; i <= lpChild->ccol; i++, lpcol++) {
if (!ROW_BINDING(lpChild)) {
lpcol->lpb = (LPBYTE)AllocPtr(lpcol->cb * lpChild->crowRowset);
lpcol->lpcb = (LPSDWORD)AllocPtr((DWORD) sizeof(SDWORD) *
lpChild->crowRowset);
}
else {
lpcol->lpb = (LPBYTE)lpb;
lpcol->lpcb = (LPSDWORD)(lpb + lpcol->cb);
lpb += lpcol->cb + sizeof(SDWORD);
}

if (STMTError(SQLBindCol(lpChild->hstmt, i, (SWORD)lpcol->fCType,
(PTR)(lpcol->lpb),
lpcol->cb,
lpcol->lpcb)))
return FALSE;
}

return TRUE;
}


/* PrepareStmt -------------------------------------------------------------
Description: Prepare a statement for future processing

Returns: SQL_ERROR if an error occurs
--------------------------------------------------------------------------*/
SQLRETURN INTFUNC PrepareStmt(LPCHILD lpChild)
{

// Close the statement and drop bindings
FreeStmt(SQL_CLOSE, lpChild);
FreeStmt(SQL_UNBIND, lpChild);

// Set scroll options
if (!(SUCCESS(SQLSetStmtAttr(lpChild->hstmt,
SQL_ATTR_CURSOR_TYPE,
(SQLPOINTER) lpChild->crowKeyset,
SQL_IS_INTEGER))))
lpChild->fNoCursorType = TRUE;

if (!(SUCCESS(SQLSetStmtAttr(lpChild->hstmt,
SQL_ATTR_CONCURRENCY,
(SQLPOINTER) lpChild->fConcurrency,
SQL_IS_INTEGER))))
lpChild->fNoConcurrency = TRUE;


if (STMTError(SQLSetStmtAttr(lpChild->hstmt,
SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER) lpChild->crowRowset,
SQL_IS_INTEGER)))
return SQL_ERROR;

// Set async mode (if supported by the driver)
if (g_fAsyncSupported &&
STMTError(SQLSetStmtAttr(lpChild->hstmt,
SQL_ATTR_ASYNC_ENABLE,
(SQLPOINTER) (lpChild->fAsync ? 1 : 0),
SQL_IS_INTEGER)))
return SQL_ERROR;



return FALSE;

}

/* Cancel -------------------------------------------------------------------
Description: Issue cancel request
--------------------------------------------------------------------------*/
void INTFUNC Cancel(LPCHILD lpChild)
{
SQLRETURN rc;

// Call SQLCancel
if (lpChild->hstmtTmp != SQL_NULL_HSTMT) {
rc = SQLCancel(lpChild->hstmtTmp); // Cancel temp hstmt
STMTError(rc);
}
else {
rc = SQLCancel(lpChild->hstmt);
STMTError(rc);
FreeStmt(SQL_CLOSE, lpChild); // Cleanup statement
}

return;
}


/* Fetch -------------------------------------------------------------------
Description: Issue fetch request
--------------------------------------------------------------------------*/
void INTFUNC Fetch(LPCHILD lpChild)
{
SQLRETURN rc;
UDWORD crow;
HCURSOR hcur;
SDWORD sdwIrowLast = lpChild->irow;
SDWORD sdwRowsAffected;
SDWORD irow;
UWORD fFetchType;

static UWORD fFetchTypeLast = SQL_FETCH_NEXT;

fFetchType = lpChild->FetchOP;
if (fFetchType == SQL_FETCH_ABSOLUTE)
irow = lpChild->arow;
else if (fFetchType == SQL_FETCH_RELATIVE)
irow = lpChild->rrow;
else
irow = 0;
if (!(lpChild->fResultSetExists)) {
STMTError(SQLRowCount(lpChild->hstmt, &sdwRowsAffected));

if (sdwRowsAffected > -1) {
UCHAR szBuffer[200];
wsprintf(szBuffer,
(sdwRowsAffected == 1) ? szRowAffected:szDataAffected,
sdwRowsAffected);

MessageBox(lpChild->hwnd,
szBuffer,
szNoDataTitle,
MB_ICONINFORMATION);
}
else
{
MessageBox( lpChild->hwnd,
szNoDataTitle,
szNoData,
MB_ICONINFORMATION);
}
return;
}

hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

SQLSetStmtAttr(lpChild->hstmt, SQL_ATTR_ROWS_FETCHED_PTR,
&crow, SQL_IS_POINTER);
SQLSetStmtAttr(lpChild->hstmt, SQL_ATTR_ROW_STATUS_PTR,
lpChild->lpfStatus, SQL_IS_POINTER);

// Call SQLFetchScroll
Async(SQLFetchScroll(lpChild->hstmt, fFetchType, irow));

if ((rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) &&
lpChild->irowPos > crow) {
SetPos(lpChild, (UWORD) crow);
}

if (!STMTError(rc) && rc != SQL_NO_DATA) {

// Reset columns retrieved count
lpChild->ccolRetrieved = (UWORD)lpChild->ccol;

// If this is the first fetch, initialize child variables
if (!lpChild->fDataFetched) {

lpChild->fDataFetched = TRUE;

lpChild->irow = -((SDWORD)lpChild->crowRowset);
lpChild->irowPos = 1;

SetScrollPos(lpChild->hwndVScroll, SB_CTL, 0, FALSE);
SetScrollPos(lpChild->hwndHScroll, SB_CTL, 0, FALSE);

SetScroll(lpChild);
AllocClipRgn(lpChild);
}

// Otherwise, maintain current row position in the row-set
else if (lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
lpChild->crowKeyset != SQL_CURSOR_FORWARD_ONLY)
SetPos(lpChild, lpChild->irowPos);

// if (fFetchType == SQL_FETCH_RESUME)
// fFetchType = fFetchTypeLast;

// Adjust absolute row number by fetch amount
switch (fFetchType) {
case SQL_FETCH_FIRST:
lpChild->irow = 1;
break;

case SQL_FETCH_PRIOR:
lpChild->irow -= crow;
if (lpChild->irow < 1)
lpChild->irow = 1;
break;

case SQL_FETCH_NEXT:
lpChild->irow += lpChild->crowCurrent; // Previous RS
break;

case SQL_FETCH_LAST: {
SDWORD iRowT; // scratch variable

if (STMTError(SQLGetStmtAttr(lpChild->hstmt,
SQL_ATTR_ROW_NUMBER,
&iRowT,
SQL_IS_INTEGER,
NULL))) {
// the cursor library does not support SQL_ROW_NUMBER,
// so use the following calculation. The result might
// be incorrect
STMTError(SQLRowCount(lpChild->hstmt, &iRowT));
lpChild->irow += (iRowT - crow);
if (lpChild->irow < 1) lpChild->irow = 1;

}
else { // the cursor library supports SQL_ROW_NUMBER
lpChild->irow = iRowT;
}
}
break;

case SQL_FETCH_RELATIVE:
lpChild->irow += irow;
if (lpChild->irow < 1)
lpChild->irow = 1;
break;

case SQL_FETCH_ABSOLUTE:
if (irow > 0)
lpChild->irow = irow;
else {
STMTError(SQLRowCount(lpChild->hstmt, &lpChild->irow));
lpChild->irow = lpChild->irow + irow + 1;
if (lpChild->irow < 1) lpChild->irow = 1;
}
break;
}

// Repaint window
InvalidateRect(lpChild->hwnd, NULL, FALSE);

lpChild->crowCurrent = crow;
}

fFetchTypeLast = fFetchType;

AdjustMenus();
SetCursor(hcur);
return;
}


/* FreeStmt ----------------------------------------------------------------
Description: Free SQLHSTMT and reset associated variables of a child window
NOTE: Only SQL_CLOSE, SQL_DROP, and SQL_UNBIND are valid
--------------------------------------------------------------------------*/
void INTFUNC FreeStmt(UWORD fOption, LPCHILD lpChild)
{
SWORD i;
LPCOL lpcol;

if (!lpChild->hstmt)
return;

// Issue the real SQLFreeStmt call
if (fOption == SQL_DROP) {
if (STMTError(SQLFreeHandle(SQL_HANDLE_STMT, lpChild->hstmt)))
return;
}
else {
if (STMTError(SQLFreeStmt(lpChild->hstmt, fOption)))
return;
}

// Drop data buffers for SQL_DROP and SQL_UNBIND requests
if (fOption == SQL_DROP || fOption == SQL_UNBIND) {
if (!ROW_BINDING(lpChild)) {
for (i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++) {
FreePtr(lpcol->lpb); lpcol->lpb = NULL;
FreePtr(lpcol->lpcb); lpcol->lpcb = NULL;
}
}
else if (lpChild->lpb) {
FreePtr(lpChild->lpb);
lpChild->lpb = NULL;
}
}

// Only drop memory for SQL_UNBIND requests
if (fOption == SQL_UNBIND)
return;

// Clear SQLHSTMT handle for SQL_DROP
if (fOption == SQL_DROP)
lpChild->hstmt = SQL_NULL_HSTMT;

// Always reset and free result set related variables
lpChild->fResultSetExists = FALSE;
lpChild->ccol = 0;
lpChild->fDataFetched = FALSE;
lpChild->cbrow = 0;
lpChild->ccols = 0;


FreePtr(lpChild->rglpv); lpChild->rglpv = NULL;
FreePtr(lpChild->lpnTabs); lpChild->lpnTabs = NULL;
FreePtr(lpChild->lpcol); lpChild->lpcol = NULL;
FreePtr(lpChild->lpfStatus); lpChild->lpfStatus = NULL;

if (lpChild->hrgn) {
DeleteObject(lpChild->hrgn);
lpChild->hrgn = NULL;
}

if (lpChild->hwnd) {
lpChild->fVScroll = FALSE;
lpChild->fHScroll = FALSE;

ShowWindow(lpChild->hwndVScroll, SW_HIDE);
ShowWindow(lpChild->hwndHScroll, SW_HIDE);
InvalidateRect(lpChild->hwnd, NULL, FALSE);
}
return;
}


/* GetCurrentValue ---------------------------------------------------------
Description: Convert to character and return column data from current
row
--------------------------------------------------------------------------*/
#pragma optimize("ceglntw", off)
void INTFUNC GetCurrentValue(LPSTR lpsz, LPCOL lpcol, LPCHILD lpChild)
{
LPBYTE lpb;
SDWORD FAR *lpcb;
UWORD irowPos;

irowPos = lpChild->irowPos - 1;

// Get data and count field pointers based on binding type
if (ROW_BINDING(lpChild)) {
lpb = lpcol->lpb + (irowPos * lpChild->cbrow);
lpcb = (LPSDWORD)(lpb + lpcol->cb);
}
else {
lpb = lpcol->lpb + (irowPos * lpcol->cb);
lpcb = lpcol->lpcb + irowPos;
}

// Convert column data to character using the supplied buffer
if (*lpcb == SQL_NULL_DATA) {
lstrcpy(lpsz, g_szNull);
return;
}

switch (lpcol->fSqlType) {

case SQL_CHAR:
case SQL_VARCHAR:
lstrcpy(lpsz, (LPSTR)lpb);
break;

case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT: {
long l;

l = (lpcol->fSqlType == SQL_INTEGER
? *((DWORD FAR *)lpb)
: lpcol->fSqlType == SQL_SMALLINT
? *((WORD FAR *)lpb)
: *((UCHAR FAR *)lpb));

_ltoa(l, lpsz, 10);
break;
}

case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE: {
double d;

d = (lpcol->fSqlType == SQL_REAL
? *((float FAR *)lpb)
: *((double FAR *)lpb));

_gcvt(d, 15, lpsz);
break;
}

default:
*lpsz = '\0';
break;
}

return;
}
#pragma optimize("ceglntw", on)


/* GetData -----------------------------------------------------------------
Description: Retrieve the next unbound column and display the data
--------------------------------------------------------------------------*/
void INTFUNC GetData(LPCHILD lpChild)
{
HCURSOR hcur;
BIGCOL bcol;
UWORD icol;
SQLRETURN rc;

// Prevent Cancel from closing statement
lpChild->hstmtTmp = lpChild->hstmt;

// Determine next unbound column index
icol = lpChild->ccolRetrieved + 1;

hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

// Get column name
Async(SQLColAttribute(lpChild->hstmt, icol,
SQL_DESC_NAME,
bcol.szName, sizeof(bcol.szName), NULL,
NULL));

SetCursor(hcur);

if (STMTError(rc)) {
lpChild->hstmtTmp = SQL_NULL_HSTMT;
return;
}

bcol.lpsz = lpChild->lpsz;

hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

// Get the data converting it to character
Async(SQLGetData(lpChild->hstmt, icol, SQL_C_CHAR,
bcol.lpsz, cbBUFSIZE-1, &bcol.cb));

SetCursor(hcur);

lpChild->hstmtTmp = SQL_NULL_HSTMT;

if (STMTError(rc) || rc == SQL_NO_DATA)
return;

// Display the retrieved data
DialogBoxParam(g_hinst,
MAKEINTRESOURCE(IDD_DATADLG),
lpChild->hwnd,
DataDlgProc,
(LPARAM)((LPSTR)&bcol));

lpChild->ccolRetrieved = icol;
return;
}


/* GetTableName ------------------------------------------------------------
Description: Extract table name from a SELECT statement
--------------------------------------------------------------------------*/
void INTFUNC GetTableName(LPSTR lpszTable, LPCSTR szSql)
{
LPCSTR lpsz;
int cp;
int cb;

cb = lstrlen(szFROM);

for (lpsz=szSql, cp=0; *lpsz; ) {

while (*lpsz && ISWHITE(*lpsz)) lpsz++;

if (!cp && !_fstrnicmp(lpsz, szFROM, cb) && ISWHITE(*(lpsz+cb)))
break;

if (ISLPAREN(*lpsz))
cp++;
else if (ISRPAREN(*lpsz))
cp--;

while (*lpsz && !ISWHITE(*lpsz)) lpsz++;
}

while (*lpsz && !ISWHITE(*lpsz)) lpsz++;
while (*lpsz && ISWHITE(*lpsz)) lpsz++;

if (*lpsz == *g_szQuoteChar) {
*lpszTable++ = *lpsz++; // Copy beginning quote
while (*lpsz && *lpsz != *g_szQuoteChar) *lpszTable++ = *lpsz++;
*lpszTable++ = *lpsz++; // Copy ending quote
}
else // Not a quoted identifier
while (*lpsz && !ISCOMMA(*lpsz) && !ISWHITE(*lpsz)) *lpszTable++ = *lpsz++;

*lpszTable = '\0';

return;
}


/* IsUpdateable ------------------------------------------------------------
Description: Return TRUE if this app supports updating the particular
SQL data type (due to limited conversion support)
--------------------------------------------------------------------------*/
BOOL INTFUNC IsUpdateable(SDWORD fSqlType)
{
switch (fSqlType) {
case SQL_CHAR:
case SQL_VARCHAR:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_TINYINT:
return TRUE;

default:
return FALSE;
}
}


/* OnDataRow ---------------------------------------------------------------
Description: Return 0 or greater if the mouse coordinates in lparam are
over a valid data row, Otherwise return -1
--------------------------------------------------------------------------*/
int INTFUNC OnDataRow(LPCHILD lpChild, LPARAM lparam)
{
RECT rc;
int row;

if (!(lpChild->hrgn))
return -1;

GetRgnBox(lpChild->hrgn, &rc);

row = (int)HIWORD(lparam) - g_cy;

if (row < 0)
return FALSE;

row /= g_cy;

if (row >= 0 &&
(UWORD)row < lpChild->crowRowset )
return row;
else
return -1;
}


/* PaintChild --------------------------------------------------------------
Description: Paint child window
--------------------------------------------------------------------------*/
void INTFUNC PaintChild(LPCHILD lpChild,
HDC hdc,
BOOL fTitle,
BOOL fRefresh,
BOOL fActive)
{
RECT rc;

GetClientRect(lpChild->hwnd, &rc);

// If no data exists, just erase the window
if (!lpChild->fDataFetched)
FillRect(hdc, &rc, g_hbrWin);

// Otherwise paint the data in a simple scrollable grid
else {
HRGN hrgn;
HFONT hfontOld;
HBRUSH hbrOld;
UWORD ir;
int ic;
int icFirst, icLast;
LPCOL lpcol;
UWORD row;
int col;
int cx, cy;
LPINT lpnTab;
LPSTR lpszValues;
LPSTR lpsz;
LPUWORD lpfStatus;
UWORD irowLast;
UWORD irowPos;
COLORREF clrfTxt;
COLORREF clrfBkg;
char szFmt[cbSTRLEN];

// Prepare the device context
SetBkColor(hdc, GetSysColor(COLOR_WINDOW));

clrfTxt = GetTextColor(hdc);
clrfBkg = GetBkColor(hdc);

// Determine first (row,col) and corresponding (x,y) offset
row = (UWORD)GetScrollPos(lpChild->hwndVScroll, SB_CTL);
col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

cx = col * g_cx;
cy = row * g_cy;

// Determine last row to be painted
irowLast = (UWORD)row + ((UWORD)lpChild->crowwin < lpChild->crowRowset
? lpChild->crowwin
: lpChild->crowRowset);
if (irowLast > lpChild->crowRowset)
irowLast--;

// Get current row number as a zero based index
irowPos = lpChild->irowPos - 1;

// Determine which columns will be painted
lpnTab = lpChild->lpnTabs;
for (icFirst=0;
icFirst < (lpChild->ccol-1) && (cx + cxBORDER) > *(lpnTab+1);
icFirst++, lpnTab++);
for (icLast=icFirst+1;
icLast < lpChild->ccol &&
(cx + cxBORDER + (lpChild->ccolwin * g_cx)) > *lpnTab;
icLast++, lpnTab++);

// Offset the device context to appropriate position in the rowset
SetWindowOrgEx(hdc, cx, cy, NULL);

// Set the clip region (to keep from erasing scroll bars, etc.)

if (!(lpChild->hrgn))
return;

SelectClipRgn(hdc, lpChild->hrgn);

// Allocate working buffer to converted data values
lpszValues = AllocPtr((DWORD) lpChild->ccol * cbSTRLEN);

// If requested, paint column titles
if (fTitle) {

// Prepare device context
SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
hfontOld = SelectObject(hdc, g_hfontName);

// Determine bounding rectangle in logical coordinates
rc.top = cy;
rc.bottom = rc.top + g_cy;
rc.left = *(lpChild->lpnTabs + icFirst) - cxBORDER;
rc.right = *(lpChild->lpnTabs + icLast) - cxBORDER;

// Fill rectangle with appropriate color
FillRect(hdc, &rc, g_hbrBtn);


// Paint white bar across the top
PatBlt(hdc, 0, rc.top, rc.right, 1, WHITENESS);

// For each visible (or partially visible) column, paint
// separating lines and column name
lpcol = lpChild->lpcol + icFirst;
lpnTab = lpChild->lpnTabs + icFirst + 1;

for (ic=icFirst; ic < icLast; ic++, lpcol++, lpnTab++) {

PatBlt(hdc, rc.left, rc.top, 1, g_cy, WHITENESS);

rc.right = *lpnTab - cxBORDER + 1;

DrawText(hdc, lpcol->szName, lstrlen(lpcol->szName),
&rc, DT_CENTER | DT_SINGLELINE | DT_VCENTER);

rc.left = rc.right;

PatBlt(hdc, rc.left-1, rc.top, 1, g_cy, BLACKNESS);
}

// Paint black line across the bottom
PatBlt(hdc, 1, rc.bottom-1, rc.right-1, 1, BLACKNESS);

// Reset device context
SelectObject(hdc, hfontOld);
SetBkColor(hdc, clrfBkg);
}

// Determine bounding rectangle for first row to be painted
rc.top = cy + g_cy;
rc.bottom = rc.top + g_cy - 1;
rc.left = *(lpChild->lpnTabs + icFirst) - cxBORDER;
rc.right = *(lpChild->lpnTabs + icLast) - cxBORDER;

// Make format string from the whole format string created earlier
#ifdef WIN32
_fstrncpy(szFmt, lpChild->szFmt+(icFirst*3), ((icLast-icFirst)*3)+1);
#else
lstrcpyn(szFmt, lpChild->szFmt+(icFirst*3), ((icLast-icFirst)*3)+1);
#endif
szFmt[((icLast-icFirst)*3)+1] = '\0';

// Offset into the row status array
lpfStatus = lpChild->lpfStatus + row;

// Prepare device context
hfontOld = SelectObject(hdc, g_hfontData);
hbrOld = SelectObject(hdc, g_hbrBtn);

// Paint each row
for (ir=row; ir < irowLast; ir++) {

// Erase row if requested (i.e., !fRefresh) or if just
// painting the row is insufficient to remove old data
if (!fRefresh ||
(ir == irowPos && !fActive) ||
*lpfStatus == SQL_ROW_NOROW ||
*lpfStatus == SQL_ROW_ERROR ||
*lpfStatus == SQL_ROW_DELETED)
FillRect(hdc, &rc, g_hbrWin);

// For an empty row, write the empty row string
if (*lpfStatus == SQL_ROW_NOROW) {
if (icFirst)
*lpChild->lpsz = '\0';
else
lstrcpy(lpChild->lpsz, g_szNoRow);
}

// For a deleted row, write the deleted row string
else if (*lpfStatus == SQL_ROW_DELETED) {
if (icFirst)
*lpChild->lpsz = '\0';
else
lstrcpy(lpChild->lpsz, g_szRowDeleted);
}

// For an error row, write the error row string
else if (*lpfStatus == SQL_ROW_ERROR) {
if (icFirst)
*lpChild->lpsz = '\0';
else
lstrcpy(lpChild->lpsz, g_szRowError);
}

// For all other rows, build a string of data values
else {
LPSTR FAR *lplpsz;
LPBYTE lpb;
LPSDWORD lpcb;


// Paint updated rows in RED
if (*lpfStatus == SQL_ROW_UPDATED)
SetTextColor(hdc, RGB(255,0,0));


lplpsz = (LPSTR FAR *)lpChild->rglpv;

lpsz = lpszValues;

lpcol = lpChild->lpcol + icFirst;
lpnTab = lpChild->lpnTabs + icFirst;

// Convert each column to character data
for (ic=icFirst; ic < icLast; ic++, lpcol++) {

lpb = lpcol->lpb + (ir * (ROW_BINDING(lpChild)
? lpChild->cbrow
: lpcol->cb));
lpcb = (ROW_BINDING(lpChild)
? (LPSDWORD)(lpb + lpcol->cb)
: lpcol->lpcb + ir);

if (*lpcb == SQL_NULL_DATA)
*lplpsz++ = g_szNull;

else if (lpcol->fCType == SQL_C_CHAR)
*lplpsz++ = (LPSTR)lpb;

else {

if (lpcol->fCType == SQL_C_FLOAT ||
lpcol->fCType == SQL_C_DOUBLE ) {
double d;

d = (lpcol->fCType == SQL_C_FLOAT
? *((float FAR *)lpb)
: *((double FAR *)lpb));

_gcvt(d, 15, lpsz);
}

else {
long l;

l = ( lpcol->fCType == SQL_C_SHORT
? *((short FAR *)lpb)
: lpcol->fCType == SQL_C_LONG
? *((long FAR *)lpb)
: *((signed char FAR *)lpb));

_ltoa(l, lpsz, 10);
}

*lplpsz++ = lpsz;
lpsz += cbSTRLEN;
}
}

// Combine all columns into one string (with tab markers)
Print(lpChild->lpsz, szFmt, lpChild->rglpv );



}

// Paint the row
TabbedTextOut(hdc,
rc.left, rc.top,
lpChild->lpsz, lstrlen(lpChild->lpsz),
icLast - icFirst, lpnTab, 0);

// Paint bottom separator
PatBlt(hdc, 0, rc.bottom, rc.right, 1, PATCOPY);

// Paint inter-column separators
lpnTab = lpChild->lpnTabs + icFirst + 1;
for (ic=icFirst+1; ic < (icLast+1); ic++, lpnTab++)
PatBlt(hdc, *lpnTab-cxBORDER, rc.top, 1, g_cy, PATCOPY);

// Reset text color (if it was changed)
if (*lpfStatus == SQL_ROW_UPDATED)
SetTextColor(hdc, clrfTxt);

// Hilite the current row
if (ir == irowPos &&
lpChild->fConcurrency != SQL_CONCUR_READ_ONLY &&
fActive ) {
RECT rcTemp, rcInvert;

GetClipBox(hdc, &rcTemp);

IntersectRect(&rcInvert, &rc, &rcTemp);

GetClientRect(lpChild->hwnd, &rcTemp);

if( rcInvert.top == rc.top )
rcInvert.top++;

if( rcInvert.bottom == rc.bottom )
rcInvert.bottom--;

if( rcInvert.left == rcTemp.left )
rcInvert.left++;

if( rcInvert.right == rcTemp.right ||
rcInvert.right == rc.right )
rcInvert.right--;

InvertRect(hdc, &rcInvert);
}

// Advance row rectangle and status array offset
rc.top += g_cy;
rc.bottom += g_cy;
lpfStatus++;
}

// Erase any partial row which may be displayed after the last
// row of the row-set has been painted
if (ir == lpChild->crowRowset)
FillRect(hdc, &rc, g_hbrWin);

// Erase any partial column which may be displayed after the last
// column has been painted
if (icLast == lpChild->ccol) {
GetClipBox(hdc, &rc);
rc.left = *(lpChild->lpnTabs + icLast) - cxBORDER + 1;
FillRect(hdc, &rc, g_hbrWin);
}

FreePtr(lpszValues);

// Reset clip region to paint areas outside data grid
GetClientRect(lpChild->hwnd, &rc);

hrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);

SelectClipRgn(hdc, hrgn);

OffsetRect(&rc, cx, cy);

// Paint bottom record display bar and/or the corner box
// between the scroll bars
rc.top = rc.bottom - g_cyHScroll + 1;

PatBlt(hdc, rc.left, rc.top, rc.right, 1, BLACKNESS);

SelectObject(hdc, g_hbrScroll);

rc.top++;

cx = (2 * cxBORDER) + g_cxRecord + g_cxRecnum + 2;

if (lpChild->fHScroll) {
if (lpChild->fVScroll)
PatBlt(hdc,
rc.right - g_cxVScroll + 2, rc.top,
g_cxVScroll, g_cyHScroll,
PATCOPY);
}
else
PatBlt(hdc,
rc.left + cx, rc.top,
rc.right - rc.left, g_cyHScroll,
PATCOPY);

rc.right = rc.left + cx;

PatBlt(hdc, rc.right, rc.top, 1, rc.bottom, BLACKNESS);

rc.right--;

// Paint current row number
if (fRefresh)
FillRect(hdc, &rc, g_hbrWin);

rc.left += cxBORDER;

TextOut(hdc, rc.left, rc.top, szRECORD, lstrlen(szRECORD));

rc.left += g_cxRecord + 2;

FillRect(hdc, &rc, g_hbrWin);

wsprintf(lpChild->lpsz, szRECNUM, lpChild->irow-1 + lpChild->irowPos);

TextOut(hdc, rc.left, rc.top, lpChild->lpsz, lstrlen(lpChild->lpsz));

DeleteObject(hrgn);

// Reset device context
SelectObject(hdc, hbrOld);

SelectObject(hdc, hfontOld);
}

return;
}



/* SetCurrentValue ---------------------------------------------------------
Description: Set a column value from the user buffer
--------------------------------------------------------------------------*/
#pragma optimize("ceglntw", off)
BOOL INTFUNC SetCurrentValue(LPSTR lpsz, LPCOL lpcol, LPCHILD lpChild)
{
LPBYTE lpb;
LPSDWORD lpcb;
BOOL fNew;
UWORD irowPos;

irowPos = lpChild->irowPos - 1;

// Get data and count field pointers based on binding type
if (ROW_BINDING(lpChild)) {
lpb = lpcol->lpb + (irowPos * lpChild->cbrow);
lpcb = (LPSDWORD)(lpb + lpcol->cb);
}
else {
lpb = lpcol->lpb + (irowPos * lpcol->cb);
lpcb = lpcol->lpcb + irowPos;
}

// If the data is NULL, just set the count field to SQL_NULL_DATA
if (!lstrcmpi(lpsz, g_szNull)) {
if (*lpcb != SQL_NULL_DATA) {
*lpcb = SQL_NULL_DATA;
fNew = TRUE;
}
}

// Otherwise, convert the character data back to the appropriate type
else switch (lpcol->fSqlType) {

case SQL_CHAR:
case SQL_VARCHAR:
if (lstrcmp(lpsz, (LPSTR)lpb)) {
lstrcpy((LPSTR)lpb, lpsz);
*lpcb = lstrlen(lpsz);
fNew = TRUE;
}
break;

case SQL_INTEGER:
case SQL_SMALLINT:
case SQL_TINYINT: {
long lNew, lCur;
char *EndPtr;

lNew = strtol(lpsz, &EndPtr, 10);
for (; *EndPtr && ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
if (*EndPtr) { // check to see if there exists non-numeric chars
UCHAR szBuffer[128];

LoadString(g_hinst, IDS_BADNUMERIC, szBuffer, sizeof(szBuffer));
MessageBox(lpChild->hwnd,
szBuffer, NULL, MB_ICONSTOP);
fNew = FALSE;
break;
}

lCur = (lpcol->fSqlType == SQL_INTEGER
? *((DWORD FAR *)lpb)
: lpcol->fSqlType == SQL_SMALLINT
? *((WORD FAR *)lpb)
: *((UCHAR FAR *)lpb));

if (lNew != lCur) {

switch (lpcol->fSqlType) {
case SQL_INTEGER:
*((DWORD FAR *)lpb) = lNew;
*lpcb = sizeof(DWORD);
break;

case SQL_SMALLINT:
*((WORD FAR *)lpb) = (WORD)lNew;
*lpcb = sizeof(WORD);
break;

case SQL_TINYINT:
*((UCHAR FAR *)lpb) = (UCHAR)lNew;
*lpcb = sizeof(UCHAR);
break;
}

fNew = TRUE;
}
break;
}

case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE: {
double dNew, dCur;
char *EndPtr;

dNew = strtod(lpsz, &EndPtr);
for (; *EndPtr && ISWHITE(*EndPtr); EndPtr = AnsiNext(EndPtr));
if (*EndPtr) { // check to see if there exists non-numeric chars
UCHAR szBuffer[128];

LoadString(g_hinst, IDS_BADNUMERIC, szBuffer, sizeof(szBuffer));
MessageBox(lpChild->hwnd,
szBuffer, NULL, MB_ICONSTOP);
fNew = FALSE;
break;
}

dCur = (lpcol->fSqlType == SQL_REAL
? *((float FAR *)lpb)
: *((double FAR *)lpb));

if (dNew != dCur) {

switch (lpcol->fSqlType) {
case SQL_REAL:
*((float FAR *)lpb) = (float)dNew;
*lpcb = sizeof(float);
break;

case SQL_FLOAT:
case SQL_DOUBLE:
*((double FAR *)lpb) = dNew;
*lpcb = sizeof(double);
break;
}

fNew = TRUE;
}
break;
}
}

return fNew;
}
#pragma optimize("ceglntw", on)


/* SetPos ------------------------------------------------------------------
Description: Set current row, de-hilite last current row and hilite
new current row
--------------------------------------------------------------------------*/
void INTFUNC SetPos(LPCHILD lpChild, UWORD irowPos)
{
HDC hdc;
HFONT hfont;
RECT rc, rcClip, rcInvert;
int row, col;
int cx, cy;

if (!(lpChild->hrgn))
return ;

if (irowPos < 1)
irowPos = 1;

else if (irowPos > lpChild->crowRowset)
irowPos = lpChild->crowRowset;

// Call SQLSetPos to set current row in the row-set
if (STMTError(SQLSetPos(lpChild->hstmt, irowPos, SQL_POSITION,
SQL_LOCK_NO_CHANGE)))
return;

// Reset number of columns retrieved
lpChild->ccolRetrieved = (UWORD)lpChild->ccol;

// Obtain a device context for painting
hdc = GetDC(lpChild->hwnd);

row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);
col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

cx = col * g_cx;
cy = row * g_cy;

SetWindowOrgEx(hdc, cx, cy, NULL);

GetClientRect(lpChild->hwnd, &rc);
GetRgnBox(lpChild->hrgn, &rcClip);
OffsetRect(&rcClip, cx, cy);

rcClip.top += g_cy;

// Offset to last current row
rc.top = ((lpChild->irowPos - 1) * g_cy) + g_cy;
rc.bottom = rc.top + g_cy - 1;
rc.left = 0;
rc.right = *(lpChild->lpnTabs + lpChild->ccol) - cxBORDER;

// De-hilite last current row
IntersectRect(&rcInvert, &rc, &rcClip);
InflateRect(&rcInvert, -1, -1);
InvertRect(hdc, &rcInvert);

// Save new current row
lpChild->irowPos = irowPos;

// Offset to new current row
rc.top = ((lpChild->irowPos - 1) * g_cy) + g_cy;
rc.bottom = rc.top + g_cy - 1;

// Hilite current row (if visible)
if (lpChild->irowPos > (UWORD)row) {

IntersectRect(&rcInvert, &rc, &rcClip);
InflateRect(&rcInvert, -1, -1);
InvertRect(hdc, &rcInvert);
}

// Update record number
GetClientRect(lpChild->hwnd, &rc);
OffsetRect(&rc, cx, cy);

rc.left += cxBORDER + g_cxRecord + 2;
rc.top = rc.bottom - g_cyHScroll + 2;
rc.right = rc.left + g_cxRecnum;

FillRect(hdc, &rc, g_hbrWin);

wsprintf(lpChild->lpsz, szRECNUM, lpChild->irow-1 + lpChild->irowPos);

hfont = SelectObject(hdc, g_hfontData);

TextOut(hdc, rc.left, rc.top, lpChild->lpsz, lstrlen(lpChild->lpsz));

SelectObject(hdc, hfont);

// Release device context
ReleaseDC(lpChild->hwnd, hdc);

return;
}


/* SetScroll ---------------------------------------------------------------
Description: Determine if scroll bars are required and their ranges
--------------------------------------------------------------------------*/
void INTFUNC SetScroll(LPCHILD lpChild)
{
RECT rc;
int cx, cy;
int row, col;

// Use the fInSetScroll flag to prevent recursion due WM_SIZE messages
if (lpChild->fInSetScroll)
return;

lpChild->fInSetScroll = TRUE;

// Save current scroll positions
row = GetScrollPos(lpChild->hwndVScroll, SB_CTL);
col = GetScrollPos(lpChild->hwndHScroll, SB_CTL);

// Get window dimensions
GetClientRect(lpChild->hwnd, &rc);
cx = rc.right - rc.left;
cy = rc.bottom - rc.top - g_cy - g_cyHScroll;

// Assume no scrolling is required
lpChild->fHScroll =
lpChild->fVScroll = FALSE;

// Include a vertical scroll bar if all rows do not fit
lpChild->crowwin = cy / g_cy;
if ((UWORD)lpChild->crowwin < lpChild->crowRowset) {
lpChild->fVScroll = TRUE;
cx -= g_cxVScroll;
}

// Include a horizontal scroll bar if all columns do not fit
lpChild->ccolwin = cx / g_cx;
if (lpChild->ccolwin < lpChild->ccols)
lpChild->fHScroll = TRUE;

// Reset scroll positions if no scrolling is necessary
if (!lpChild->fVScroll) row = 0;
if (!lpChild->fHScroll) col = 0;

// Set scroll ranges, positions, and scroll bar visibility
SetScrollRange(lpChild->hwndVScroll, SB_CTL, 0, lpChild->crowRowset - lpChild->crowwin, TRUE);
SetScrollRange(lpChild->hwndHScroll, SB_CTL, 0, lpChild->ccols - lpChild->ccolwin, TRUE);

SetScrollPos(lpChild->hwndVScroll, SB_CTL, row, TRUE);
SetScrollPos(lpChild->hwndHScroll, SB_CTL, col, TRUE);

ShowWindow(lpChild->hwndVScroll, (lpChild->fVScroll ? SW_SHOW : SW_HIDE));
ShowWindow(lpChild->hwndHScroll, (lpChild->fHScroll ? SW_SHOW : SW_HIDE));

// Add one extra to window depth so no white space is left between
// the last full row and bottom record display bar
lpChild->crowwin++;

// Size and position scroll bars
SizeScroll(lpChild);

lpChild->fInSetScroll = FALSE;

return;
}


/* SizeScroll --------------------------------------------------------------
Description: Size and position scroll bars
--------------------------------------------------------------------------*/
void INTFUNC SizeScroll(LPCHILD lpChild)
{
RECT rc;
int cxRecord;

GetClientRect(lpChild->hwnd, &rc);

// Place vertical scroll bar
MoveWindow(lpChild->hwndVScroll,
rc.right - g_cxVScroll + 1,
rc.top,
g_cxVScroll,
rc.bottom - g_cyHScroll + 2,
TRUE);

// Place horizontal scroll bar
cxRecord = (2 * cxBORDER) + g_cxRecord + g_cxRecnum + 2;

MoveWindow(lpChild->hwndHScroll,
rc.left + cxRecord,
rc.bottom - g_cyHScroll + 1,
(lpChild->fVScroll
? rc.right - g_cxVScroll + 2 - cxRecord
: rc.right + 2 - cxRecord),
g_cyHScroll,
TRUE);

return;
}


/* UpdateRow ---------------------------------------------------------------
Description: Update current (positioned) row
--------------------------------------------------------------------------*/
void INTFUNC UpdateRow(LPCHILD lpChild)
{
HCURSOR hcur;
LPSTR lpsz;
LPSTR lpszT;
SWORD cb;
LPCOL lpcol;
int cCtls;
UWORD irowPos;
int i;

// Ensure the update request is valid
if (!lpChild->fDataFetched) {
DoMessage(lpChild->hwnd, IDS_NODATAFETCHED);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_NOROW) {
DoMessage(lpChild->hwnd, IDS_NOROWUPDATE);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_ERROR) {
DoMessage(lpChild->hwnd, IDS_ERRORROWUPDATE);
return;
}

if (*(lpChild->lpfStatus + lpChild->irowPos - 1) == SQL_ROW_DELETED) {
DoMessage(lpChild->hwnd, IDS_DELROWUPDATE);
return;
}

if (lpChild->fConcurrency == SQL_CONCUR_READ_ONLY) {
DoMessage(lpChild->hwnd, IDS_NOUPDATE);
return;
}

// Count number of updateable columns
for (cCtls=0, i=0, lpcol=lpChild->lpcol; i < lpChild->ccol; i++, lpcol++)
if (IsUpdateable(lpcol->fSqlType))
cCtls++;

// Ensure a valid number of updateable columns exist
if (cCtls > cMAXCOLS) {
DoMessage(lpChild->hwnd, IDS_TOOMANYCOLS);
return;
}

if (!cCtls) {
DoMessage(lpChild->hwnd, IDS_CANNOTUPDATE);
return;
}

hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));

lpsz = AllocPtr(2 * cbMAXSQL);
lpszT = lpsz + cbMAXSQL;

// Build and display update dialog to get new values, then issue update
// (use a temporary SQLHSTMT for the update request)
if (IDOK == DoDialog(lpChild->hwnd, IDD_UPDATEROW, UpdateDlgProc) &&
!DBCError(lpChild->hwnd, SQLAllocHandle(SQL_HANDLE_STMT,g_hdbc, &lpChild->hstmtTmp))) {

// Build UPDATE <table> WHERE CURRENT OF <cursor name> statement
lstrcpy(lpsz, szUPDATE);

GetTableName(lpszT, lpChild->sql);
lstrcat(lpsz, lpszT);

// Build the Set Clause
lstrcat(lpsz, szSET);
for (lpcol = lpChild->lpcol, i = 0; i < lpChild->ccol; i++, lpcol++) {
if (IsUpdateable(lpcol->fSqlType)) {
LPSDWORD lpcb;
LPBYTE lpb;

if (i == 0) wsprintf(lpszT, "%s=?", lpcol->szName);
else wsprintf(lpszT, ",%s=?", lpcol->szName);
lstrcat(lpsz, lpszT);

irowPos = lpChild->irowPos - 1;

// Get data and count field pointers based on binding type
if (ROW_BINDING(lpChild)) {
lpb = lpcol->lpb + (irowPos * lpChild->cbrow);
lpcb = (LPSDWORD)(lpb + lpcol->cb);
}
else {
lpb = lpcol->lpb + (irowPos * lpcol->cb);
lpcb = lpcol->lpcb + irowPos;
}

// set parameter
ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
SQLSetParam(lpChild->hstmtTmp, (UWORD) (i+1), lpcol->fCType,
lpcol->fSqlType, (UDWORD) lpcol->cb, (SWORD) 0,
lpb, lpcb));
}
}

lstrcat(lpsz, szWHERE);

lpszT = lpsz + lstrlen(lpsz);

if (!STMTError(SQLGetCursorName(lpChild->hstmt, (UCHAR FAR *)lpszT,
cbMAXSQL, &cb))) {

// Issue update request via SQLExecDirect
ODBCError(lpChild->hwnd, SQL_HANDLE_STMT, lpChild->hstmtTmp,
SQLExecDirect(lpChild->hstmtTmp, (UCHAR FAR *)lpsz, SQL_NTS));
}

DBCError(lpChild->hwnd, SQLFreeHandle(SQL_HANDLE_STMT,lpChild->hstmtTmp));
lpChild->hstmtTmp = SQL_NULL_HSTMT;

}

// Refresh entire row-set buffer (saving current row position)
irowPos = lpChild->irowPos;

lpChild->FetchOP = SQL_FETCH_RELATIVE;
lpChild->rrow = 0;
Fetch(lpChild);

SetPos(lpChild, irowPos);

// Repaint the row-set
InvalidateRect(lpChild->hwnd, NULL, FALSE);

FreePtr(lpsz);

SetCursor(hcur);

return;
}

/*--------------------------------------------------------------------
Input: lpChild
Output: TRUE if the option parameters are valid, FALSE otherwise
--------------------------------------------------------------------*/
BOOL INTFUNC ParamValid(LPCHILD lpChild)
{
char szBuffer[128]; // error message
char *EndMaxBind, *EndRowset, *EndBind;

// the maximum column width
wsprintf((LPSTR) szBuffer,
"Maximum column width must be at least 1 and at most %d",
MAX_MAXBIND);

if (lpChild->fMaxBind) { // it's been changed
lpChild->crowMaxBind =
(SDWORD) strtol((char*) lpChild->szMaxBind, &EndMaxBind, 10);
for (; *EndMaxBind && ISWHITE(*EndMaxBind);
EndMaxBind = AnsiNext(EndMaxBind));
}

if (lpChild->fBind) { // cBind has been changed
lpChild->cBind = (UWORD) strtol((char*) lpChild->szBind, &EndBind, 10);
for (; *EndBind && ISWHITE(*EndBind); EndBind = AnsiNext(EndBind));
}

if (lpChild->fRowset) { // rowset has been changed
lpChild->crowRowset =
(UWORD) strtol((char*) lpChild->szRowset, &EndRowset, 10);
for (; *EndRowset && ISWHITE(*EndRowset);
EndRowset = AnsiNext(EndRowset));
}

while (lpChild->fMaxBind && *EndMaxBind ||
lpChild->crowMaxBind < 1 ||
lpChild->crowMaxBind > MAX_MAXBIND ||
lpChild->fRowset && *EndRowset ||
lpChild->crowRowset < 1 ||
lpChild->crowRowset > 4096 ||
lpChild->fBind && *EndBind) {

// the maximum column width
if (lpChild->crowMaxBind < 1 || lpChild->crowMaxBind > MAX_MAXBIND
|| lpChild->fMaxBind && *EndMaxBind)
MessageBox(lpChild->hwnd, szBuffer, NULL, MB_ICONSTOP);

// the rowset size
if (lpChild->crowRowset < 1 || lpChild->crowRowset > 4096
|| lpChild->fRowset && *EndRowset) {
char szBuffer[128];

LoadString(g_hinst, IDS_BADROWSET, szBuffer, sizeof(szBuffer));
MessageBox(lpChild->hwnd, szBuffer, NULL, MB_ICONSTOP);
}

// number of bound columns
if (lpChild->fBind && *EndBind)
MessageBox(lpChild->hwnd,
"Invalid number of bound columns", NULL, MB_ICONSTOP);

if (IDOK != DoDialog(lpChild->hwnd, IDD_OPTION_DIALOG, OptionsDlgProc))
return FALSE;

if (lpChild->fMaxBind) { // it's been changed
lpChild->crowMaxBind =
(SDWORD) strtol((char*) lpChild->szMaxBind, &EndMaxBind, 10);
for (; *EndMaxBind && ISWHITE(*EndMaxBind);
EndMaxBind = AnsiNext(EndMaxBind));
}

if (lpChild->fBind) { // cBind has been changed
lpChild->cBind =
(UWORD) strtol((char*) lpChild->szBind, &EndBind, 10);

for (; *EndBind && ISWHITE(*EndBind); EndBind = AnsiNext(EndBind));
}

if (lpChild->fRowset) { // rowset has been changed
lpChild->crowRowset =
(UWORD) strtol((char*) lpChild->szRowset, &EndRowset, 10);
for (; *EndRowset && ISWHITE(*EndRowset);
EndRowset = AnsiNext(EndRowset));
}
}

return TRUE;

}