ACDCLNT.C

////////////////////////////////////////////////////////////////////////////// 
//
// ACDCLNT.C
//
// ACDClient app
//
//////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <tapi.h>
#include "acdclnt.h"
#include "resource.h"



//////////////////////////////////////////////////////////////////////////////
// PROTOTYPES
//////////////////////////////////////////////////////////////////////////////
static BOOL CreateMainWindow (int nCmdShow);

static LRESULT CALLBACK MainWndProc (HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam);

LRESULT CALLBACK AgentStateDlgProc (HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam);

VOID CALLBACK LineCallback (DWORD hDevice,
DWORD dwMsg,
DWORD dwCallbackInstance,
DWORD dwParam1,
DWORD dwParam2,
DWORD dwParam3);
BOOL InitTapi();

BOOL CloseTapi();

BOOL RedoWindow();

BOOL SetStatusMessage(LPTSTR lpszMessage);

BOOL SetButton(DWORD dwAddress,
BOOL bAnswer,
BOOL bEnable);

LRESULT WaitForLineReply();

LONG ThreadRoutine(LPVOID lpv);

//////////////////////////////////////////////////////////////////////////////
//
// GLOBALS
//
//////////////////////////////////////////////////////////////////////////////
HINSTANCE ghInstance; // main instance
HWND ghMainWnd; // main window
PADDRESSINFO pAddressInfo = NULL; // array of info about each address
HLINEAPP ghLineApp; // hlineapp
DWORD gdwAddresses; // number of addresses on our line
DWORD gdwDeviceID; // our device
HLINE ghLine; // our line

HANDLE ghCompletionPort; // tapi message completionport
CRITICAL_SECTION csLineReply;

// using global variables to keep track of line
// replies, since the main thread will only have at most one outstanding
// line reply at a time
BOOL gbReply;
LONG glResult;


//////////////////////////////////////////////////////////////////////////////
//
// WinMain()
//
//////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
MSG msg;

ghInstance = hInstance;

if(!InitTapi())
{
MessageBox(NULL,
TEXT("Failed to initialize TAPI"),
TEXT("Cannot start ACDClient"),
MB_OK);

return 0;
}

if (!CreateMainWindow(nCmdShow))
{
return 0;
}

while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 1;
}


///////////////////////////////////////////////////////////////////////////////
//
// CreateMainWindow()
//
///////////////////////////////////////////////////////////////////////////////
BOOL CreateMainWindow (int nCmdShow)
{

// main window
ghMainWnd = CreateDialog(ghInstance,
MAKEINTRESOURCE(IDD_MAINDLG),
NULL,
MainWndProc);

if (ghMainWnd == NULL)
{
return FALSE;
}

SetStatusMessage(TEXT("Waiting for call"));

// create buttons
RedoWindow();

ShowWindow(ghMainWnd, nCmdShow);

UpdateWindow(ghMainWnd);

return TRUE;
}


/////////////////////////////////////////////////////////////////////////////////
//
// BOOL SetStatusMessage(LPTSTR lpszMessage)
//
// Sets text in the static control at the bottom of the main window to
// lpszMessage
//
/////////////////////////////////////////////////////////////////////////////////
BOOL SetStatusMessage(LPTSTR lpszMessage)
{
return (SetWindowText(GetDlgItem(ghMainWnd,
IDC_STATIC1),
lpszMessage));

}


/////////////////////////////////////////////////////////////////////////////////
//
// BOOL ClearCall(HCALL hCall)
//
// Called when a CALLSTATE_IDLE message is recieved. Looks for the call in the
// global pAddressInfo array. If it finds it, is clears the appropriate members
// of the structure
//
/////////////////////////////////////////////////////////////////////////////////
BOOL ClearCall(HCALL hCall)
{
DWORD dwCount;

for (dwCount = 0; dwCount < gdwAddresses; dwCount++)
{
if (pAddressInfo[dwCount].hCall == hCall)
{
pAddressInfo[dwCount].hCall = NULL;
pAddressInfo[dwCount].bCall = FALSE;
SetButton(dwCount,
TRUE,
FALSE);
return TRUE;
}
}

return FALSE;
}


//////////////////////////////////////////////////////////////////////////////////
//
// BOOL SetButton()
//
// Sets the status and text of the answer/drop button for a specific address
//
//////////////////////////////////////////////////////////////////////////////////
BOOL SetButton(DWORD dwAddress,
BOOL bAnswer,
BOOL bEnable)
{
if (dwAddress >= gdwAddresses)
return FALSE;

if (bAnswer)
{
SetWindowText(pAddressInfo[dwAddress].hAnswer,
TEXT("Answer"));
}
else
{
SetWindowText(pAddressInfo[dwAddress].hAnswer,
TEXT("Hang Up"));
}

EnableWindow(pAddressInfo[dwAddress].hAnswer,
bEnable);

return TRUE;
}



///////////////////////////////////////////////////////////////////////////////
//
// VOID CALLBACK LineCallback ()
//
// TAPI callback function. Handles all tapi messages
//
///////////////////////////////////////////////////////////////////////////////
VOID CALLBACK LineCallback (DWORD hDevice,
DWORD dwMsg,
DWORD dwCallbackInstance,
DWORD dwParam1,
DWORD dwParam2,
DWORD dwParam3)
{
LPLINECALLINFO pLCI;
LPLINECALLSTATUS pLCS;
TCHAR szBuffer[64];

switch (dwMsg)
{
case LINE_REPLY:
{
EnterCriticalSection(&csLineReply);
if (dwParam1 == (DWORD)glResult)
{
gbReply = TRUE;
glResult = dwParam2;
}
LeaveCriticalSection(&csLineReply);
}
break;

case LINE_CALLSTATE:
{
if (dwParam1 == LINECALLSTATE_OFFERING)
{
// get the call privilege
// note note note the new LINE_APPNEWCALL
// give call privilege
pLCS = LineGetCallStatus((HCALL)hDevice);

if (!pLCS)
return;

if (!(pLCS->dwCallPrivilege & LINECALLPRIVILEGE_OWNER))
{
// not our call
GlobalFree(pLCS);
return;
}

GlobalFree(pLCS);

// we're getting offered a call
// first get the address
pLCI = LineGetCallInfo((HCALL)hDevice);

if (!pLCI)
{
// error
return;
}

// set the status message text
wsprintf(szBuffer,
TEXT("Incoming call on address %lu"),
pLCI->dwAddressID);

pAddressInfo[pLCI->dwAddressID].hCall = (HCALL)hDevice;

SetStatusMessage(szBuffer);

// set the button to answer
SetButton(pLCI->dwAddressID,
TRUE,
TRUE);

GlobalFree(pLCI);

break;
}

if (dwParam1 == LINECALLSTATE_IDLE)
{
// see if we have this call
ClearCall((HCALL)hDevice);
// dealloc no matter what
lineDeallocateCall((HCALL)hDevice);

break;
}
}

break;
}
}


///////////////////////////////////////////////////////////////////////////////
//
// BOOL GetAddressFromhWnd()
//
///////////////////////////////////////////////////////////////////////////////
BOOL GetAddressFromhWnd(HWND hWnd,
LPDWORD pdwAddress,
LPBOOL pbStatus)
{
DWORD dwAddress;

// go through the array of addressinfo and see
// if the hwnd matches
for (dwAddress = 0; dwAddress < gdwAddresses; dwAddress++)
{
if (pAddressInfo[dwAddress].hStatus == hWnd)
{
*pdwAddress = dwAddress;
*pbStatus = TRUE;

return TRUE;
}
if (pAddressInfo[dwAddress].hAnswer == hWnd)
{
*pdwAddress = dwAddress;
*pbStatus = FALSE;

return TRUE;
}
}

return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
//
// BOOL DoLineAnswerDrop(DWORD dwAddress)
//
// Handles what happens when the answer/drop button is pressed
//
/////////////////////////////////////////////////////////////////////////////
BOOL DoLineAnswerDrop(DWORD dwAddress)
{
// if we have a call, then we want to drop it
if (pAddressInfo[dwAddress].bCall)
{

SetStatusMessage(TEXT("Hanging up call ..."));

EnterCriticalSection(&csLineReply);
glResult = lineDrop(pAddressInfo[dwAddress].hCall,
NULL,
0);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
// error
}

else if (WaitForLineReply())
{
// error
}

// error or not, deallocate and set button
lineDeallocateCall(pAddressInfo[dwAddress].hCall);

SetButton(dwAddress,
TRUE,
FALSE);

pAddressInfo[dwAddress].hCall = NULL;
pAddressInfo[dwAddress].bCall = FALSE;

SetStatusMessage(TEXT("Waiting for a call"));

}
else
{
BOOL bError = FALSE;


// answer
SetStatusMessage(TEXT("Answering call..."));

EnterCriticalSection(&csLineReply);
glResult = lineAnswer(pAddressInfo[dwAddress].hCall,
NULL,
0);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
bError = TRUE;
//error
}
else if (WaitForLineReply())
{
bError = TRUE;
// error
}

if (bError)
{
SetStatusMessage(TEXT("Hanging up call ..."));
lineDeallocateCall(pAddressInfo[dwAddress].hCall);
pAddressInfo[dwAddress].hCall = NULL;
SetButton(dwAddress,
TRUE,
FALSE);

SetStatusMessage(TEXT("Waiting for a call"));
return FALSE;
}

SetStatusMessage(TEXT("On a call"));

pAddressInfo[dwAddress].bCall = TRUE;

SetButton(dwAddress,
FALSE,
TRUE);
}

return TRUE;
}

//////////////////////////////////////////////////////////////////////
//
// LRESULT DoCommand(WPARAM wParam,
// LPARAM lParam)
//
// Handles WM_COMMAND messages for the main window
//
//////////////////////////////////////////////////////////////////////
LRESULT DoCommand(WPARAM wParam,
LPARAM lParam)
{
DWORD dwAddress;
BOOL bStatus;

// check to see if a button is being clicked
if (HIWORD(wParam) == BN_CLICKED)
{
// check to see if it is a button we care about
if (GetAddressFromhWnd((HWND)lParam,
&dwAddress,
&bStatus))
{

// if it's the status button, display the status
// dialog
if (bStatus)
{
DialogBoxParam(ghInstance,
MAKEINTRESOURCE(IDD_AGENTSTATE),
ghMainWnd,
AgentStateDlgProc,
(LPARAM)dwAddress);
}
// else it's the answer/drop button
else
{
DoLineAnswerDrop(dwAddress);
}
}

return 1;

}


return 0;
}



////////////////////////////////////////////////////////////////////////////////
//
// MainWndProc()
//
////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK MainWndProc (HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:

return 1;

case WM_COMMAND:

return DoCommand(wParam,
lParam);

break;

case WM_CLOSE:
case WM_DESTROY:
CloseTapi();
PostQuitMessage(0);
return 1;
}

return 0;
}


///////////////////////////////////////////////////////////////////////
//
// BOOL InitTapi()
//
// Initializes TAPI. For this sample, we assume that the "agent" (the person
// logged on) can only have access to _one_ hLine. Also, they have access to
// every address on that line. This may not be true for many ACD situations.
//
// As soon as we find a device that the agent has access to, we quit
// looking, and use that device
//
///////////////////////////////////////////////////////////////////////
BOOL InitTapi()
{
LONG lResult;
LINEINITIALIZEEXPARAMS exparams;
LPLINEAGENTCAPS pLAC;
LPLINEDEVCAPS pLDC;
DWORD dwDeviceID, dwNumDevs, dwAPIVersion, dwThreadID;

// initialize completion port to receive TAPI notifications
ghCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
0);

InitializeCriticalSection(&csLineReply);

// fill in lineinitex parameters
exparams.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS);
exparams.dwOptions = LINEINITIALIZEEXOPTION_USECOMPLETIONPORT;
exparams.Handles.hCompletionPort = ghCompletionPort;

dwAPIVersion = TAPI_CURRENT_VERSION;

lResult = lineInitializeEx(&ghLineApp,
ghInstance,
NULL,
SZAPPNAME,
&dwNumDevs,
&dwAPIVersion,
&exparams);

if (lResult)
{
return FALSE;
}

// if no devices, don't continue
if (dwNumDevs == 0)
{
lineShutdown(ghLineApp);
return FALSE;
}

// kick off completion port thread
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadRoutine,
NULL,
0,
&dwThreadID);


// loop through all devices
for (dwDeviceID = 0; dwDeviceID < dwNumDevs; dwDeviceID++)
{
// Get the Agent Caps. If this succeedes, this is
// a device we can use
pLAC = LineGetAgentCaps(ghLineApp,
dwDeviceID,
0);

if (pLAC)
{
// this is a device we can use
gdwDeviceID = dwDeviceID;

// get the number of addresses
pLDC = LineGetDevCaps(ghLineApp,
dwDeviceID);

if (pLDC)
{
gdwAddresses = pLDC->dwNumAddresses;
GlobalFree(pLDC);
}

GlobalFree(pLAC);
break;
}
}

// open the line in owner mode
lResult = lineOpen(ghLineApp,
gdwDeviceID,
&ghLine,
TAPI_CURRENT_VERSION,
0,
0,
LINECALLPRIVILEGE_OWNER,
LINEMEDIAMODE_INTERACTIVEVOICE,
NULL);

// if line failed, don't continue
if (lResult)
{
lineShutdown(ghLineApp);
return FALSE;
}


return TRUE;
}


//////////////////////////////////////////////////////////////////////
//
// ThreadRoutine
//
// Thread dedicated to checking completion port for TAPI events
//
//////////////////////////////////////////////////////////////////////
LONG ThreadRoutine(LPVOID lpv)
{
LPLINEMESSAGE pMsg;
DWORD dwNumBytesTransfered, dwCompletionKey;


// just wait for tapi notifications
while (GetQueuedCompletionStatus(ghCompletionPort,
&dwNumBytesTransfered,
&dwCompletionKey,
(LPOVERLAPPED *) &pMsg,
INFINITE))
{
if (pMsg)
{
// when we get one, call the handling function
LineCallback(pMsg->hDevice,
pMsg->dwMessageID,
pMsg->dwCallbackInstance,
pMsg->dwParam1,
pMsg->dwParam2,
pMsg->dwParam3);

LocalFree (pMsg);
}
else
{
break;
}
}

ExitThread(0);
return 0;
}


///////////////////////////////////////////////////////////////////////////
//
// BOOL CloseTapi()
//
// Close tapi and free resources
//
///////////////////////////////////////////////////////////////////////////
BOOL CloseTapi()
{
GlobalFree(pAddressInfo);

CloseHandle(ghCompletionPort);

lineClose(ghLine);
lineShutdown(ghLineApp);

return TRUE;
}

// static information for the status dialog
static DWORD dwAgentStates[] =
{
LINEAGENTSTATE_LOGGEDOFF,
LINEAGENTSTATE_NOTREADY,
LINEAGENTSTATE_READY,
LINEAGENTSTATE_BUSYACD,
LINEAGENTSTATE_BUSYINCOMING,
LINEAGENTSTATE_BUSYOUTBOUND,
LINEAGENTSTATE_BUSYOTHER,
LINEAGENTSTATE_WORKINGAFTERCALL,
LINEAGENTSTATE_UNKNOWN,
LINEAGENTSTATE_UNAVAIL,
0
};

static LPTSTR lpszStates[] =
{
TEXT("Logged Off"),
TEXT("Not Ready"),
TEXT("Ready"),
TEXT("Busy ACD"),
TEXT("Busy Incoming"),
TEXT("Busy Outbound"),
TEXT("Busy Other"),
TEXT("Working after call"),
TEXT("Unknown"),
TEXT("Unavail"),
NULL
};

///////////////////////////////////////////////////////////////////////////
//
// BOOL InitAgentDlg()
//
// Handles initialization of the status dialog
//
// Gets the group list and puts groups in multiselect list box
// these are the groups that the agent _can_ log into
// the groups they are logged into will be selected
// Creates comboboxes of states and nextstates, and select
// the agent's current state/nextstate
// Gets the activity list and puts each item into a combobox
// the current activity will be selected
//
///////////////////////////////////////////////////////////////////////////
BOOL InitAgentDlg(HWND hwnd,
DWORD dwAddress,
LPLINEAGENTGROUPLIST * ppLAG)
{
LPLINEAGENTCAPS pLAC;
LPLINEAGENTSTATUS pLAS;
LPLINEAGENTACTIVITYLIST pLAA;
LPLINEAGENTGROUPENTRY pEntry, pLoggedInEntry;
LPLINEAGENTACTIVITYENTRY pActivityEntry;
DWORD dwEntries, dwCount;
LONG item;

// first, get the status
// this information will be used to know which items to select
// in each of the listbox/comboboxes
pLAS = LineGetAgentStatus(ghLine,
dwAddress);

if (!pLAS)
{
return FALSE;
}

// get the group list
if (!(*ppLAG = LineGetAgentGroupList(ghLine,
dwAddress)))
{
return FALSE;
}

// get the first groupentry
pEntry = (LPLINEAGENTGROUPENTRY)(((LPBYTE)*ppLAG)+(*ppLAG)->dwListOffset);

// loop through the group entries
for (dwEntries = 0; dwEntries < (*ppLAG)->dwNumEntries; dwEntries++)
{
// add group to list box
item = SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_ADDSTRING,
0,
(LPARAM)(LPTSTR)(((LPBYTE)*ppLAG) + pEntry->dwNameOffset));

// save the entry
SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_SETITEMDATA,
(WPARAM)item,
(LPARAM)pEntry);

// now get list of groups currently logged into from the agent status structure
// loop through them. if any of them match the group we are currently adding
// select that group
pLoggedInEntry = (LPLINEAGENTGROUPENTRY)(((LPBYTE)pLAS) + pLAS->dwGroupListOffset);
for (dwCount = 0; dwCount < pLAS->dwNumEntries; dwCount++)
{
if ((pLoggedInEntry->GroupID.dwGroupID1 == pEntry->GroupID.dwGroupID1) &&
(pLoggedInEntry->GroupID.dwGroupID2 == pEntry->GroupID.dwGroupID2) &&
(pLoggedInEntry->GroupID.dwGroupID3 == pEntry->GroupID.dwGroupID3) &&
(pLoggedInEntry->GroupID.dwGroupID4 == pEntry->GroupID.dwGroupID4))
{
SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_SETSEL,
(WPARAM)TRUE,
(LPARAM)item);
}

pLoggedInEntry++;
}

pEntry++;
}

// get the agent caps
if (pLAC = LineGetAgentCaps(ghLineApp,
gdwDeviceID,
dwAddress))
{
dwCount = 0;
// loop through all possbile agent states. if the agent state
// is selected in the agent caps, add that state to the list box
while (dwAgentStates[dwCount])
{
if (dwAgentStates[dwCount] & pLAC->dwStates)
{
item = SendDlgItemMessage(hwnd,
IDC_STATE,
CB_ADDSTRING,
0,
(LPARAM)lpszStates[dwCount]);
SendDlgItemMessage(hwnd,
IDC_STATE,
CB_SETITEMDATA,
(WPARAM)item,
(LPARAM)dwAgentStates[dwCount]);

// if the state matches the current one from the agent status
// select it
if (pLAS->dwState == dwAgentStates[dwCount])
{
SendDlgItemMessage(hwnd,
IDC_STATE,
CB_SETCURSEL,
(WPARAM)item,
0);
}
}

dwCount ++;
}

dwCount = 0;
// now do the same for the next states
while (dwAgentStates[dwCount])
{
if (dwAgentStates[dwCount] & pLAC->dwNextStates)
{
item = SendDlgItemMessage(hwnd,
IDC_NEXTSTATE,
CB_ADDSTRING,
0,
(LPARAM)lpszStates[dwCount]);
SendDlgItemMessage(hwnd,
IDC_NEXTSTATE,
CB_SETITEMDATA,
(WPARAM)item,
dwAgentStates[dwCount]);

if (pLAS->dwNextState == dwAgentStates[dwCount])
{
SendDlgItemMessage(hwnd,
IDC_NEXTSTATE,
CB_SETCURSEL,
(WPARAM)item,
0);
}
}

dwCount++;
}

GlobalFree(pLAC);
}

// get the activity list
pLAA = LineGetAgentActivityList(ghLine,
gdwDeviceID,
dwAddress);
if (pLAA)
{
dwCount = pLAA->dwNumEntries;
pActivityEntry = (LPLINEAGENTACTIVITYENTRY)(((LPBYTE)pLAA) + pLAA->dwListOffset);

// go through all the possible activities and add them to the list
while (dwCount)
{
item = SendDlgItemMessage(hwnd,
IDC_ACTIVITY,
CB_ADDSTRING,
0,
(LPARAM)(LPTSTR)(((LPBYTE)pLAA) + pActivityEntry->dwNameOffset));

SendDlgItemMessage(hwnd,
IDC_ACTIVITY,
CB_SETITEMDATA,
(WPARAM)item,
(LPARAM)pActivityEntry->dwID);

// if this is the current activity (from agent status)
// select it
if (pLAS->dwActivityID == pActivityEntry->dwID)
{
SendDlgItemMessage(hwnd,
IDC_ACTIVITY,
CB_SETCURSEL,
(WPARAM)item,
0);
}

dwCount--;
pActivityEntry++;
}

GlobalFree(pLAA);

}

}

//////////////////////////////////////////////////////////////////////////////////////////////
//
// BOOL SaveAgentStatus(HWND hwnd)
//
// Saves information from the status dialog
//
//////////////////////////////////////////////////////////////////////////////////////////////
BOOL SaveAgentStatus(HWND hwnd,
DWORD dwAddress)
{
LPLINEAGENTGROUPENTRY pGroupEntry, pNewGroupEntry;
LPLINEAGENTGROUPLIST pNewLAG;
DWORD dwCount;
LPINT pItems;
DWORD item;
DWORD dwState, dwNextState, dwActivity;

// get the number of groups selected in the group
// list box. each selected group is a group this
// agent will be logged into
dwCount = SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_GETSELCOUNT,
0,

0); 

// allocate an array to hold the selected item's indexes
pItems = (LPINT)GlobalAlloc(GPTR, sizeof(int) * dwCount);

// get the item's indexes
SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_GETSELITEMS,
dwCount,
(LPARAM)pItems);

// alloc a LINEAGENTGROUP array for groups
pNewLAG = (LPLINEAGENTGROUPLIST)GlobalAlloc(GPTR,
sizeof(LINEAGENTGROUPLIST) +
dwCount * sizeof(LINEAGENTGROUPENTRY));

// fill in sizes
pNewLAG->dwTotalSize = sizeof(LINEAGENTGROUPLIST) + dwCount * sizeof(LINEAGENTGROUPENTRY);
pNewLAG->dwUsedSize = pNewLAG->dwTotalSize;
pNewLAG->dwNeededSize = pNewLAG->dwTotalSize;
pNewLAG->dwListSize = sizeof(LINEAGENTGROUPENTRY) * dwCount;
pNewLAG->dwListOffset = sizeof(LINEAGENTGROUPLIST);

// count
pNewLAG->dwNumEntries = dwCount;

// get pointer to first entry in array
pNewGroupEntry = (LPLINEAGENTGROUPENTRY)(((LPBYTE)pNewLAG) + pNewLAG->dwListOffset);
// loop though all selected item
while (dwCount)
{
// get the item data associated with the item. this data
// is a group entry struct
pGroupEntry = (LPLINEAGENTGROUPENTRY)SendDlgItemMessage(hwnd,
IDC_GROUPS,
LB_GETITEMDATA,
(WPARAM)pItems[dwCount-1],
0);

// copy the GroupID to the new array
CopyMemory(&pNewGroupEntry->GroupID,
&pGroupEntry->GroupID,
sizeof(pGroupEntry->GroupID));

// these fields are not used
pNewGroupEntry->dwNameSize = 0;
pNewGroupEntry->dwNameOffset = 0;

// next entry
pNewGroupEntry++;

dwCount--;
}

// now that we've created the AGENTGROUPLIST, set it
EnterCriticalSection(&csLineReply);
glResult = lineSetAgentGroup(ghLine,
dwAddress,
pNewLAG);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
//error
}
else if (WaitForLineReply())
{
//error
}

GlobalFree(pNewLAG);

// now get the current state
item = SendDlgItemMessage(hwnd,
IDC_STATE,
CB_GETCURSEL,
0,
0);

// get item data. this is the state flag
dwState = SendDlgItemMessage(hwnd,
IDC_STATE,
CB_GETITEMDATA,
(WPARAM)item,
0);

// same for next state
item = SendDlgItemMessage(hwnd,
IDC_NEXTSTATE,
CB_GETCURSEL,
0,
0);

dwNextState = SendDlgItemMessage(hwnd,
IDC_NEXTSTATE,
CB_GETITEMDATA,
(WPARAM)item,
0);

// set it
EnterCriticalSection(&csLineReply);
glResult = lineSetAgentState(ghLine,
dwAddress,
dwState,
dwNextState);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
//error
}
else if (WaitForLineReply())
{
//error
}

// get the activity selected
item = SendDlgItemMessage(hwnd,
IDC_ACTIVITY,
CB_GETCURSEL,
0,
0);

// get the item data. this is the activity ID
dwActivity = SendDlgItemMessage(hwnd,
IDC_ACTIVITY,
CB_GETITEMDATA,
(WPARAM)item,
0);

// set it
EnterCriticalSection(&csLineReply);
glResult = lineSetAgentActivity(ghLine,
dwAddress,
dwActivity);



if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
//error
}
else if (WaitForLineReply())
{
//error
}

return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//
// LRESULT WaitForLineReply()
//
// waiting for a line reply.
//
// 2 issues:
// - using global variables for line reply information. only recommended
// in the most simple situations
//
// - using completion ports to demonstrate the completion port mechanism.
// since this app has ui, the wait loop has a message loop and a sleep()!!
//
///////////////////////////////////////////////////////////////////////////////
LRESULT WaitForLineReply()
{
MSG msg;

gbReply = FALSE;

LeaveCriticalSection(&csLineReply);

while (!gbReply)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Sleep(5);
}

return glResult;

}

///////////////////////////////////////////////////////////////////////////////////
//
// LRESULT CALLBACK AgentStateDlgProc ()
//
// Dialog proc for the agent status dialog
//
///////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK AgentStateDlgProc (HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
static DWORD dwAddress;
static LPLINEAGENTGROUPLIST pLAG;

switch (uMsg)
{
case WM_INITDIALOG:

dwAddress = (DWORD)lParam;

InitAgentDlg(hwnd,
dwAddress,
&pLAG);

SetFocus(GetDlgItem(hwnd,
IDC_GROUPS));
return 1;

case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
{
SaveAgentStatus(hwnd,
dwAddress);
GlobalFree(pLAG);
EndDialog(hwnd,
1);
return 1;
}

if (LOWORD(wParam) == IDCANCEL)
{
GlobalFree(pLAG);
EndDialog(hwnd,
1);

return 1;
}
}

return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
// **************TAPI WRAPPER FUNCTIONS**************
//
///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
//
// LineGetAgentGroupList()
//
///////////////////////////////////////////////////////////////////////////////
LINEAGENTGROUPLIST * LineGetAgentGroupList (HLINE hLine,
DWORD dwAddressID)
{
LINEAGENTGROUPLIST * pLineAgentGroupList;
static DWORD dwMaxNeededSize = sizeof(LINEAGENTGROUPLIST);

// Allocate an initial block of memory for the LINEAGENTGROUPLIST structure,
// which may or may not be big enough to hold all of the information.
//
pLineAgentGroupList = GlobalAlloc(GPTR, dwMaxNeededSize);

while (TRUE)
{
BOOL bError = FALSE;


if (pLineAgentGroupList == NULL)
{
return NULL;
}
pLineAgentGroupList->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINEAGENTGROUPLIST information
//
EnterCriticalSection(&csLineReply);
glResult = lineGetAgentGroupList(hLine,
dwAddressID,
pLineAgentGroupList);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
bError = TRUE;
//error
}
else if (WaitForLineReply())
{
bError = TRUE;
//error
}


if (bError)
{
GlobalFree((HLOCAL)pLineAgentGroupList);
return NULL;
}

// If the currently allocated LINEAGENTGROUPLIST memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineAgentGroupList->dwNeededSize <= dwMaxNeededSize)
{
return pLineAgentGroupList;
}
else
{
dwMaxNeededSize = pLineAgentGroupList->dwNeededSize;
pLineAgentGroupList = GlobalReAlloc((HLOCAL)pLineAgentGroupList,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}


///////////////////////////////////////////////////////////////////////////////
//
// LineGetAgentStatus()
//
///////////////////////////////////////////////////////////////////////////////
LINEAGENTSTATUS * LineGetAgentStatus (HLINE hLine,
DWORD dwAddressID)
{
LINEAGENTSTATUS * pLineAgentStatus;
static DWORD dwMaxNeededSize = sizeof(LINEAGENTSTATUS);

// Allocate an initial block of memory for the LINEAGENTSTATUS structure,
// which may or may not be big enough to hold all of the information.
//
pLineAgentStatus = GlobalAlloc(GPTR, dwMaxNeededSize);

while (TRUE)
{
BOOL bError = FALSE;
if (pLineAgentStatus == NULL)
{
return NULL;
}
pLineAgentStatus->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINEAGENTSTATUS information
//
EnterCriticalSection(&csLineReply);
glResult = lineGetAgentStatus(hLine,
dwAddressID,
pLineAgentStatus);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
bError = TRUE;
//error
}
else if (WaitForLineReply())
{
bError = TRUE;
//error
}

if (bError)
{
GlobalFree((HLOCAL)pLineAgentStatus);
return NULL;
}

// If the currently allocated LINEAGENTSTATUS memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineAgentStatus->dwNeededSize <= dwMaxNeededSize)
{
return pLineAgentStatus;
}
else
{
dwMaxNeededSize = pLineAgentStatus->dwNeededSize;
pLineAgentStatus = GlobalReAlloc((HLOCAL)pLineAgentStatus,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}


///////////////////////////////////////////////////////////////////////////////
//
// LineGetAgentCaps()
//
///////////////////////////////////////////////////////////////////////////////
LINEAGENTCAPS * LineGetAgentCaps (HLINEAPP hLineApp,
DWORD dwDeviceID,
DWORD dwAddressID)
{
LINEAGENTCAPS * pLineAgentCaps;
static DWORD dwMaxNeededSize = sizeof(LINEAGENTCAPS);

// Allocate an initial block of memory for the LINEAGENTCAPS structure,
// which may or may not be big enough to hold all of the information.
//
pLineAgentCaps = GlobalAlloc(GPTR, dwMaxNeededSize);

while (TRUE)
{
BOOL bError = FALSE;

if (pLineAgentCaps == NULL)
{
return NULL;
}
pLineAgentCaps->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINEAGENTCAPS information
//
EnterCriticalSection(&csLineReply);
glResult = lineGetAgentCaps(hLineApp,
dwDeviceID,
dwAddressID,
TAPI_CURRENT_VERSION,
pLineAgentCaps);

if (glResult < 0)
{
bError = TRUE;
LeaveCriticalSection(&csLineReply);
//error
}
else if (WaitForLineReply())
{
bError = TRUE;
//error
}


if (bError)
{
GlobalFree((HLOCAL)pLineAgentCaps);
return NULL;
}

// If the currently allocated LINEAGENTCAPS memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineAgentCaps->dwNeededSize <= dwMaxNeededSize)
{
return pLineAgentCaps;
}
else
{
dwMaxNeededSize = pLineAgentCaps->dwNeededSize;
pLineAgentCaps = GlobalReAlloc((HLOCAL)pLineAgentCaps,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}


///////////////////////////////////////////////////////////////////////////////
//
// LineGetAgentActivityList()
//
///////////////////////////////////////////////////////////////////////////////
LPLINEAGENTACTIVITYLIST LineGetAgentActivityList (HLINE hLine,
DWORD dwDeviceID,
DWORD dwAddressID)
{
LINEAGENTACTIVITYLIST * pLineAgentActivityList;
static DWORD dwMaxNeededSize = sizeof(LINEAGENTACTIVITYLIST);

// Allocate an initial block of memory for the LINEAGENTACTIVITYLIST structure,
// which may or may not be big enough to hold all of the information.
//
pLineAgentActivityList = GlobalAlloc(GPTR, dwMaxNeededSize);

for (;;)
{
BOOL bError = FALSE;
if (pLineAgentActivityList == NULL)
{
return NULL;
}
pLineAgentActivityList->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINEAGENTACTIVITYLIST information
//
EnterCriticalSection(&csLineReply);
glResult = lineGetAgentActivityList(hLine,
dwAddressID,
pLineAgentActivityList);

if (glResult < 0)
{
LeaveCriticalSection(&csLineReply);
bError = TRUE;
//error
}
else if (WaitForLineReply())
{
bError = TRUE;
//error
}


if (bError)
{
GlobalFree((HLOCAL)pLineAgentActivityList);
return NULL;
}


// If the currently allocated LINEAGENTACTIVITYLIST memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineAgentActivityList->dwNeededSize <= dwMaxNeededSize)
{
return pLineAgentActivityList;
}
else
{
dwMaxNeededSize = pLineAgentActivityList->dwNeededSize;
pLineAgentActivityList = GlobalReAlloc((HLOCAL)pLineAgentActivityList,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}

///////////////////////////////////////////////////////////////////////////////
//
// LineGetAddressCaps()
//
///////////////////////////////////////////////////////////////////////////////
LINEADDRESSCAPS * LineGetAddressCaps (HLINEAPP hLineApp,
DWORD dwDeviceID,
DWORD dwAddressID)
{
LONG lRetVal;
LINEADDRESSCAPS * pLineAddressCaps;
static DWORD dwMaxNeededSize = sizeof(LINEADDRESSCAPS);

// Allocate an initial block of memory for the LINEADDRESSCAPS structure,
// which may or may not be big enough to hold all of the information.
//
pLineAddressCaps = GlobalAlloc(GPTR, dwMaxNeededSize);

for (;;)
{
if (pLineAddressCaps == NULL)
{
return NULL;
}
pLineAddressCaps->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINEADDRESSCAPS information
//
lRetVal = lineGetAddressCaps(hLineApp,
dwDeviceID,
dwAddressID,
TAPI_CURRENT_VERSION,
0,
pLineAddressCaps);
if (lRetVal < 0)
{
GlobalFree((HLOCAL)pLineAddressCaps);
return NULL;
}

// If the currently allocated LINEADDRESSCAPS memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineAddressCaps->dwNeededSize <= dwMaxNeededSize)
{
return pLineAddressCaps;
}
else
{
dwMaxNeededSize = pLineAddressCaps->dwNeededSize;
pLineAddressCaps = GlobalReAlloc((HLOCAL)pLineAddressCaps,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}

///////////////////////////////////////////////////////////////////////////////
//
// LineGetCallInfo()
//
///////////////////////////////////////////////////////////////////////////////
LINECALLINFO * LineGetCallInfo (HCALL hCall)
{
LONG lRetVal;
LINECALLINFO * pLineCallInfo;
static DWORD dwMaxNeededSize = sizeof(LINECALLINFO);

// Allocate an initial block of memory for the LINECALLINFO structure,
// which may or may not be big enough to hold all of the information.
//
pLineCallInfo = GlobalAlloc(GPTR, dwMaxNeededSize);

for (;;)
{
if (pLineCallInfo == NULL)
{
return NULL;
}
pLineCallInfo->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINECALLINFO information
//
lRetVal = lineGetCallInfo(hCall,
pLineCallInfo);
if (lRetVal < 0)
{
GlobalFree((HLOCAL)pLineCallInfo);
return NULL;
}

// If the currently allocated LINECALLINFO memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineCallInfo->dwNeededSize <= dwMaxNeededSize)
{
return pLineCallInfo;
}
else
{
dwMaxNeededSize = pLineCallInfo->dwNeededSize;
pLineCallInfo = GlobalReAlloc((HLOCAL)pLineCallInfo,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}


///////////////////////////////////////////////////////////////////////////////
//
// LineGetDevCaps()
//
///////////////////////////////////////////////////////////////////////////////
LINEDEVCAPS * LineGetDevCaps (HLINEAPP hLineApp,
DWORD dwDeviceID)
{
LONG lRetVal;
LINEDEVCAPS * pLineDevCaps;
static DWORD dwMaxNeededSize = sizeof(LINEDEVCAPS);

pLineDevCaps = GlobalAlloc(GPTR, dwMaxNeededSize);
for (;;)
{
if (pLineDevCaps == NULL)
{
return NULL;
}
pLineDevCaps->dwTotalSize = dwMaxNeededSize;
lRetVal = lineGetDevCaps(hLineApp,
dwDeviceID,
TAPI_CURRENT_VERSION,
0,
pLineDevCaps);
if (lRetVal < 0)
{
GlobalFree((HLOCAL)pLineDevCaps);
return NULL;
}
if (pLineDevCaps->dwNeededSize <= dwMaxNeededSize)
{
return pLineDevCaps;
}
else
{
dwMaxNeededSize = pLineDevCaps->dwNeededSize;
pLineDevCaps = GlobalReAlloc((HLOCAL)pLineDevCaps,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}

///////////////////////////////////////////////////////////////////////////////
//
// LineGetID()
//
///////////////////////////////////////////////////////////////////////////////
VARSTRING * LineGetID (HLINE hLine,
DWORD dwAddressID,
HCALL hCall,
DWORD dwSelect,
LPCTSTR lpszDeviceClass)
{
LONG lRetVal;
VARSTRING * pVarString;
static DWORD dwMaxNeededSize = sizeof(VARSTRING);

// Allocate an initial block of memory for the VARSTRING structure,
// which may or may not be big enough to hold all of the information.
//
pVarString = GlobalAlloc(GPTR, dwMaxNeededSize);

for (;;)
{
if (pVarString == NULL)
{
return NULL;
}
pVarString->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the VARSTRING information
//
lRetVal = lineGetID(hLine,
dwAddressID,
hCall,
dwSelect,
pVarString,
lpszDeviceClass);
if (lRetVal < 0)
{
GlobalFree((HLOCAL)pVarString);
return NULL;
}

// If the currently allocated VARSTRING memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pVarString->dwNeededSize <= dwMaxNeededSize)
{
return pVarString;
}
else
{
dwMaxNeededSize = pVarString->dwNeededSize;
pVarString = GlobalReAlloc((HLOCAL)pVarString,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}


///////////////////////////////////////////////////////////////////////////////
//
// LineGetCallStatus()
//
///////////////////////////////////////////////////////////////////////////////
LINECALLSTATUS * LineGetCallStatus (HCALL hCall)
{
LONG lRetVal;
LINECALLSTATUS * pLineCallStatus;
static DWORD dwMaxNeededSize = sizeof(LINECALLSTATUS);

// Allocate an initial block of memory for the LINECALLSTATUS structure,
// which may or may not be big enough to hold all of the information.
//
pLineCallStatus = GlobalAlloc(GPTR, dwMaxNeededSize);

while (TRUE)
{
if (pLineCallStatus == NULL)
{
return NULL;
}
pLineCallStatus->dwTotalSize = dwMaxNeededSize;

// Try (or retry) to get the LINECALLSTATUS information
//
lRetVal = lineGetCallStatus(hCall,
pLineCallStatus);
if (lRetVal < 0)
{
GlobalFree((HLOCAL)pLineCallStatus);
return NULL;
}

// If the currently allocated LINECALLSTATUS memory block was big
// enough, we're all done, else we need to realloc the memory block
// and try again.
//
if (pLineCallStatus->dwNeededSize <= dwMaxNeededSize)
{
return pLineCallStatus;
}
else
{
dwMaxNeededSize = pLineCallStatus->dwNeededSize;
pLineCallStatus = GlobalReAlloc((HLOCAL)pLineCallStatus,
dwMaxNeededSize,
GMEM_MOVEABLE);
}
}
}

////////////////////////////////////////////////////////////////////////////
//
//
// constants for creating buttons in the main window
//
#define YSTART 8
#define XSTART 8
#define STATICX 57
#define BUTTONX 50
#define BUTTONGAP 20
#define BUTTONY 14
#define LINEGAP 8


/////////////////////////////////////////////////////////////////////////////
//
// BOOL RedoWindow()
//
// Creates the buttons and static controls in the main window
// For each address on the line, create a static control with the name off
// the address, a button to get/set status, and a button to answer/drop
//
// Right now, this should only be done when the app is starting. It does
// not check to see if pAddressInfo has already been allocated
//
/////////////////////////////////////////////////////////////////////////////
BOOL RedoWindow()
{
DWORD dwAddress;
LPLINEADDRESSCAPS pLAC;
TCHAR szBuffer[64];
LONG lBaseUnits, lxbase, lybase;
HFONT hFont;
HWND hWnd;

int x,y,w,h,xstart,ystart,buttonx,buttony,staticx,buttongap,linegap;


// alloc for address info
pAddressInfo = (PADDRESSINFO)GlobalAlloc(GPTR, sizeof(ADDRESSINFO) * gdwAddresses);

if (!pAddressInfo)
{
return FALSE;
}

// get conversions
lBaseUnits = GetDialogBaseUnits();
lxbase = (LONG)LOWORD(lBaseUnits);
lybase = (LONG)HIWORD(lBaseUnits);

// convert dialog units to pixels
xstart = (XSTART * lxbase) / 4;
ystart = (YSTART * lybase) / 8;
buttonx = (BUTTONX * lxbase) / 4;
buttony = (BUTTONY * lybase) / 8;
staticx = (STATICX * lxbase) / 4;
buttongap = (BUTTONGAP * lxbase) / 4;
linegap = (LINEGAP * lybase) / 8;

// init
x = xstart;
y = ystart;
w = buttonx;
h = buttony;

// get the font used by the static control
hFont = (HFONT)SendDlgItemMessage(ghMainWnd,
IDC_STATIC1,
WM_GETFONT,
0,
0);

// loop through all addressed
for (dwAddress = 0; dwAddress < gdwAddresses; dwAddress++)
{
// get the name of the address
pLAC = LineGetAddressCaps(ghLineApp,
gdwDeviceID,
dwAddress);

if (!pLAC || !pLAC->dwAddressSize)
{
wsprintf(szBuffer,
TEXT("Address %lu"),
dwAddress);
}
else
{
lstrcpy(szBuffer,
(LPTSTR)(((LPBYTE)pLAC)+pLAC->dwAddressOffset));
}

if (pLAC)
{
GlobalFree(pLAC);
}

w = staticx;
x = xstart;
// create the static control
hWnd = CreateWindow(TEXT("STATIC"),
szBuffer,
WS_CHILD | SS_LEFT | WS_VISIBLE,
x,y+(buttony/3),w,h,
ghMainWnd,
NULL,
ghInstance,
NULL);

// set the font
SendMessage(hWnd,
WM_SETFONT,
(WPARAM)hFont,
0);

x += staticx;
w = buttonx;
// create the status button
pAddressInfo[dwAddress].hStatus = CreateWindow(TEXT("BUTTON"),
TEXT("Set Status..."),
WS_CHILD | BS_PUSHBUTTON | WS_VISIBLE,
x,y,w,h,
ghMainWnd,
NULL,
ghInstance,
NULL);

// set the font
SendMessage(pAddressInfo[dwAddress].hStatus,
WM_SETFONT,
(WPARAM)hFont,

0); 

x += buttonx + buttongap;

// create the answer/drop button
pAddressInfo[dwAddress].hAnswer = CreateWindow(TEXT("BUTTON"),
TEXT("Answer"),
WS_CHILD | WS_DISABLED | BS_PUSHBUTTON | WS_VISIBLE,
x,y,w,h,
ghMainWnd,
NULL,
ghInstance,
NULL);

// set the font
SendMessage(pAddressInfo[dwAddress].hAnswer,
WM_SETFONT,
(WPARAM)hFont,
0);

y += buttony + linegap;
}


// adjust position of message static control
SetWindowPos(GetDlgItem(ghMainWnd,
IDC_STATIC1),
NULL,
xstart,y,0,0,
SWP_NOZORDER | SWP_NOSIZE);

// adjust the size of th main window
SetWindowPos(ghMainWnd,
NULL,
0,0,xstart+staticx+buttonx+buttonx+buttongap+50,y+50,
SWP_NOZORDER | SWP_NOMOVE);

return TRUE;

}