/*
File:       Advanced.cpp
Project:    Joystick Control Panel OLE Client
Author: Brycej
Date:       02/07/97
Comments:
    Window proc for Avanced page in cpanel

Copyright (c) 1997, Microsoft Corporation
*/
// This is necessary LVS_EX_INFOTIP
/*
#if (_WIN32_IE < 0x0500)
#undef _WIN32_IE
#define  _WIN32_IE  0x0500
#endif
*/

#include <afxcmn.h>
#include <windowsx.h>

#include <cpl.h>

#include <winuser.h>  // For RegisterDeviceNotification stuff!
#include <dbt.h>      // for DBT_ defines!!!

#include "cpanel.h"
#include "hsvrguid.h"

#include "resource.h"
#include "joyarray.h"

// MyListCtrl prototypes
#include "inplace.h"

#define USE_DEFAULT     0x1000      // If this bit is set, the device is going to use GCDEF!
#define SHOW_DEFAULT 0x2000     // Show default check box if clsidConfig is != CLSID_LegacyServer

// constants
const short NO_ITEM     = -1;                                    

#define DEVICE_ID       0 
#define DEVICE_FRIENDLY 1
#define DEVICE_TYPE     2
#define DEVICE_PORT     3

LPCWSTR lpMSANALOG_VXD = L"MSANALOG.VXD";
LPTSTR  lpstrNone;

#define ADVANCED_ID_COLUMN      0
#define ADVANCED_DEVICE_COLUMN  1

extern const DWORD gaHelpIDs[];

// externs for arguements!
extern BYTE nID, nStartPageDef, nStartPageCPL;

// Update flag!
extern short nFlags;

// local (module-scope) variables
HWND hAdvListCtrl;

#ifdef _UNICODE
static PVOID hAdvNotifyDevNode;
#endif

extern short iItem;
static HWND ghDlg;

//static UINT JoyCfgChangedMsg;
static BOOL bProcess;

// Message Procedures for handling VK_DELETE in Advanced window
static WNDPROC fpMainWndProc;
static BOOL WINAPI SubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Message Procedures for handling the VK_ENTER/VK_DELETE in Adv Window
static WNDPROC fpPageWndProc;
static BOOL WINAPI KeySubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

short iAdvItem = NO_ITEM;   // index of selected item
char  iGameportDriverItem = NO_ITEM;
short nOldID;

// externs
extern IDirectInputJoyConfig *pDIJoyConfig;
extern LPDIRECTINPUT lpDIInterface;

extern BYTE nGameportBus;
extern PJOY pAssigned[MAX_ASSIGNED];  // List of assigned devices
extern BYTE nAssigned;                // Number of elements in pAssigned array
extern HINSTANCE ghInstance;

#ifdef WINNT
    // external function defined in CPANEL.CPP
    extern void RunWDMJOY            ( void );
#endif

// local message handlers
static BOOL OnInitDialog    (HWND, HWND, LPARAM);
static void OnCommand       (HWND, int, HWND, UINT);
static BOOL OnNotify           (HWND, WPARAM, LPNMHDR);
static void OnDestroy       (HWND);
static void OnAdvHelp       (LPARAM);
static void OnContextMenu   (WPARAM wParam, LPARAM lParam);
static void OnListviewContextMenu ( LPARAM lParam );

// local utility fns
static BOOL SetActiveGlobalDriver ( void );
static BOOL AdvUpdateListCtrl        ( void );
static BOOL UpdateChangeListCtrl  ( HWND hCtrl );

#ifndef _UNICODE
static void PopulateGlobalPortDriverComboBox( void );
extern WCHAR *pwszGameportDriverArray[MAX_GLOBAL_PORT_DRIVERS];
extern BYTE nGameportDriver;          // Global Port Driver Enumeration Counter
    #define POLL_FLAGS_REG_STR  TEXT("PollFlags")
#endif

static void LaunchChange             ( HWND     hTmp );
int CALLBACK CompareIDItems      (LPARAM item1, LPARAM item2, LPARAM uDirection);

void EditSubLabel( BYTE nItem, BYTE nCol );

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    AdvancedProc(HWND hDlg, ULONG uMsg, WPARAM wParam,  LPARAM lParam)
//
// PARAMETERS:  hDlg    - 
//             uMsg    - 
//             wParam  -
//             lParam  -
//
// PURPOSE:     Main callback function for "Advanced"  sheet
///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI AdvancedProc(HWND hDlg, ULONG uMsg, WPARAM wParam,  LPARAM lParam)
{
    switch( uMsg )
    {
    case WM_ACTIVATEAPP:
        if( wParam )
            SetListCtrlItemFocus(hAdvListCtrl, (BYTE)iAdvItem);
        break;

    case WM_DEVICECHANGE:
        switch( (UINT)wParam )
        {
        case DBT_DEVICEARRIVAL:
//         case DBT_DEVICEREMOVECOMPLETE:
            // Clear the old "known devices" list
            nFlags |= UPDATE_ALL;

            // Clear pAssigned 
            while( nAssigned )
            {
                if( pAssigned[--nAssigned] )
                {
                    delete[] (pAssigned[nAssigned]);

                    pAssigned[nAssigned] = 0;
                }
            }

            // Rebuild the "known devices" list - pAssigned
            lpDIInterface->EnumDevices(DIDEVTYPE_JOYSTICK, (LPDIENUMDEVICESCALLBACK)DIEnumDevicesProc, (LPVOID)hDlg, DIEDFL_ALLDEVICES);

            AdvUpdateListCtrl();
            break;
        }
        break;

    case WM_LBUTTONDOWN:
        // Click Drag service for PropSheets!
        PostMessage(GetParent(hDlg), WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, lParam);
        break;

    case WM_INITDIALOG:
        if( !HANDLE_WM_INITDIALOG(hDlg, wParam, lParam, OnInitDialog) )
        {
            // Fix #108983 NT, Remove Flash on Error condition.
            SetWindowPos(::GetParent(hDlg), HWND_BOTTOM, 0, 0, 0, 0, SWP_HIDEWINDOW);
            DestroyWindow(hDlg);
        }
        return(TRUE);

    case WM_COMMAND:
        HANDLE_WM_COMMAND(hDlg, wParam, lParam, OnCommand);
        return(TRUE);

    case WM_DESTROY:
        return(HANDLE_WM_DESTROY(hDlg, wParam, lParam, OnDestroy));

    case WM_NOTIFY:
        return(HANDLE_WM_NOTIFY(hDlg, wParam, lParam, OnNotify));

    case WM_HELP:
        OnAdvHelp(lParam);
        return(TRUE);

    case WM_CONTEXTMENU:
        OnContextMenu(wParam, lParam);
        return(TRUE);

    default:
        break;
    }
    return(0);
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnInitDialog(HWND hDlg, HWND hWnd, LPARAM lParam)
//
// PARAMETERS:  hDlg    - 
//                 hWnd    - 
//                 lParam  -
//
// PURPOSE:     WM_INITDIALOG message handler
///////////////////////////////////////////////////////////////////////////////
BOOL OnInitDialog(HWND hDlg, HWND hWnd, LPARAM lParam)
{
    bProcess = TRUE;

    // Just in case Advanced is launched as the startup page!
    if( !lpDIInterface )
    {
        if( FAILED(DirectInputCreate(ghInstance, DIRECTINPUT_VERSION, &lpDIInterface, NULL)) )
        {
#ifdef _DEBUG
            OutputDebugString(TEXT("GCDEF.DLL: DirectInputCreate() failed\n"));
#endif
            Error((short)IDS_INTERNAL_ERROR, (short)IDS_NO_DIJOYCONFIG);
            return(FALSE);
        }

        if( !pDIJoyConfig )
        {
            if( FAILED(lpDIInterface->QueryInterface(IID_IDirectInputJoyConfig, (LPVOID*)&pDIJoyConfig)) )
            {
#ifdef _DEBUG
                OutputDebugString (TEXT("JOY.CPL: CoCreateInstance Failed... Closing CPL!\n"));
#endif
                Error((short)IDS_INTERNAL_ERROR, (short)IDS_NO_DIJOYCONFIG);

                return(FALSE);
            }

            VERIFY(SUCCEEDED(pDIJoyConfig->SetCooperativeLevel(hDlg, DISCL_EXCLUSIVE | DISCL_BACKGROUND)));

            // Enumerate all the Types! 
            VERIFY(SUCCEEDED(pDIJoyConfig->EnumTypes((LPDIJOYTYPECALLBACK)DIEnumJoyTypeProc, NULL)));

            // If you're here, you came in via the CMD line arg and you need to enumerate for devices so...
            lpDIInterface->EnumDevices(DIDEVTYPE_JOYSTICK, (LPDIENUMDEVICESCALLBACK)DIEnumDevicesProc, (LPVOID)hDlg, DIEDFL_ALLDEVICES);
        }
    }

    // if we find an object, then enable the Change... button
    //HWND hChangeCtrl = GetDlgItem(hDlg, IDC_ADV_CHANGE);

    // Determine Privilege and disable Change accordingly!
    if( pDIJoyConfig->Acquire() == DIERR_INSUFFICIENTPRIVS )
    {
        // Assign here because the Advanced sheet could be launched first
        // via command line args!
        nFlags |= USER_MODE;

        //PostEnableWindow(hChangeCtrl, FALSE);
    }
#ifdef WINNT
    else
    {
        // Run the WDMJOY.INF file!!!
        RunWDMJOY();
    }
#endif

    // set the global dialog handle
    ghDlg = hDlg;

    // blj: TODO: Make advanced page update on JOYCONFIGCHANGED message!
    // JOY_CONFIGCHANGED_MSGSTRING defined in MMDDK.H   
    //JoyCfgChangedMsg = RegisterWindowMessage(JOY_CONFIGCHANGED_MSGSTRING);

    // initialize our list control
    hAdvListCtrl = GetDlgItem(hDlg, IDC_ADV_LIST_DEVICE);

#ifdef _UNICODE
    // Set the Attributes!                                                   Removed LVS_EX_ONECLICKACTIVATE per GSeirra  | LVS_EX_INFOTIP
    ::SendMessage(hAdvListCtrl, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP);
#else
    ::SendMessage(hAdvListCtrl, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
#endif

    RECT rc;
    GetClientRect(hAdvListCtrl, &rc);
    rc.left = (short)((rc.right-GetSystemMetrics(SM_CXVSCROLL))/5);

    // Set up the columns!
#ifdef _UNICODE
    InsertColumn(hAdvListCtrl, DEVICE_ID,         IDS_ADV_DEVICE_HEADING,  (USHORT)(rc.left >> 1 ));
    InsertColumn(hAdvListCtrl, DEVICE_FRIENDLY, IDS_ADV_DEVICE_FRIENDLY, (USHORT)(rc.left + (rc.left>>1)));
    InsertColumn(hAdvListCtrl, DEVICE_TYPE,       IDS_ADV_GAME_CONTROLLERS,    (USHORT)(rc.left << 1    ));
    InsertColumn(hAdvListCtrl, DEVICE_PORT,       IDS_ADV_DEVICE_PORT,     (USHORT)(rc.left         ));

    // Remove the Global Port Driver stuff!!!
    const USHORT nCtrlArray[] = {IDC_TEXT_PORTDRIVER, IDC_COMBO1, IDC_ADV_GRP2, IDC_TEXT_DRIVER};
    BYTE nIndex = sizeof(nCtrlArray)/sizeof(short);

    while( DestroyWindow(GetDlgItem(hDlg, nCtrlArray[--nIndex])) );

#else
    rc.right = (rc.left << 1) + (rc.left >> 2);
    InsertColumn(hAdvListCtrl, DEVICE_ID,         IDS_ADV_DEVICE_HEADING,  (USHORT)(rc.left >> 1));
    InsertColumn(hAdvListCtrl, DEVICE_FRIENDLY, IDS_ADV_DEVICE_FRIENDLY, (USHORT)rc.right);
    InsertColumn(hAdvListCtrl, DEVICE_TYPE,       IDS_ADV_GAME_CONTROLLERS,    (USHORT)rc.right);
#endif


    lpstrNone = new TCHAR[STR_LEN_32];
    ASSERT (lpstrNone);

    // everyone needs the "None" string so I've loaded it here!
    VERIFY(LoadString(ghInstance, IDS_NONE, lpstrNone, STR_LEN_32));

    fpMainWndProc = (WNDPROC)SetWindowLongPtr(hAdvListCtrl, GWLP_WNDPROC, (LONG_PTR)SubClassProc);

    // Only center the dialog if this was the page that we started on!
    if( nStartPageCPL == 1 )
    {
        HWND hParentWnd = GetParent(hDlg);

        GetWindowRect(hParentWnd, &rc);

        // Centre the Dialog!
        SetWindowPos(hParentWnd, NULL, 
                     (GetSystemMetrics(SM_CXSCREEN) - (rc.right-rc.left))>>1, 
                     (GetSystemMetrics(SM_CYSCREEN) - (rc.bottom-rc.top))>>1, 
                     NULL, NULL, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);

        // Do that move button thing!
        MoveOK(hParentWnd);

        // Set the Update flag...
        nFlags |= UPDATE_FOR_ADV;
    }

    // the user is requesting that the CPL be shown
    // and an extention associated with nID be Launched.
    if( nID < NUMJOYDEVS )
    {
        LaunchExtention(hDlg);

        // Zero out so you don't do it twice!
        nID = 0;
    }

    // SetActive will use this flag to make sure that the ListCtrl is populated!
    nFlags |= UPDATE_FOR_ADV;

    return(TRUE);
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnCommand(HWND hDlg, int id, HWND hWndCtl, UINT code)
//
// PARAMETERS:  hDlg    - 
//             id      - 
//             hWndCtl -
//             code    -
//
// PURPOSE:     WM_COMMAND message handler
///////////////////////////////////////////////////////////////////////////////
void OnCommand(HWND hDlg, int id, HWND hWndCtl, UINT code)
{
    // Hit the "What's This..."
    switch( id )
    {
    case IDC_RENAME:
        // Only SubClass when we need to!
        if( !(nFlags & USER_MODE) )
        {
            HWND hParentWnd = GetParent(GetDlgItem(hDlg, IDC_ADV_LIST_DEVICE));
            // this is required because the CPL can be launched via RUNDLL32
            if( ::IsWindow(hParentWnd) )
                hParentWnd = GetParent(hParentWnd);

            if( !fpPageWndProc )
                fpPageWndProc = (WNDPROC)SetWindowLongPtr(hParentWnd, GWLP_WNDPROC, (LONG_PTR)KeySubClassProc);

            nFlags |= UPDATE_INPROCESS;

            // Find the column as it Could have been moved!
            LPTSTR szName = new (TCHAR[STR_LEN_32]);
            ASSERT (szName);

            // First, Load the string of the column we are looking to find!
            if( LoadString(ghInstance, IDS_ADV_DEVICE_FRIENDLY, szName, STR_LEN_32) )
            {
                // Now, traverse the columns to find the one with the title that matches szName!
                HWND hHeader = GetDlgItem(hAdvListCtrl, 0);

                BYTE nColumns = (BYTE)::SendMessage(hHeader, HDM_GETITEMCOUNT, 0, 0L);


                HDITEM *phdItem = new (HDITEM);
                ASSERT (phdItem);

                ZeroMemory(phdItem, sizeof(HD_ITEM));


                phdItem->pszText      = new TCHAR[STR_LEN_32];
                ASSERT (phdItem->pszText);

                phdItem->cchTextMax = STR_LEN_32;
                phdItem->mask         = HDI_TEXT | HDI_ORDER;

                do
                {
                    ::SendMessage(hHeader, HDM_GETITEM, (WPARAM)(int)--nColumns, (LPARAM)(LPHDITEM)phdItem);

                    if( _tcscmp(phdItem->pszText, szName) == 0 )
                    {
                        nColumns = (BYTE)phdItem->iOrder;
                        break;  
                    }
                } while( nColumns );

                if( phdItem->pszText )
                    delete[] (phdItem->pszText);

                if( phdItem )
                    delete (phdItem);

                EditSubLabel( (BYTE)iAdvItem, nColumns );
            }

            if( szName )
                delete[] (szName);
        }
        break;

    case IDS_WHATSTHIS:
        {
            // point to help file
            LPTSTR pszHelpFileName = new TCHAR[STR_LEN_32];
            ASSERT (pszHelpFileName);

            if( LoadString(ghInstance, IDS_HELPFILENAME, pszHelpFileName, STR_LEN_32) )
                WinHelp((HWND)hAdvListCtrl, pszHelpFileName, HELP_WM_HELP, (ULONG_PTR)gaHelpIDs);
#ifdef _DEBUG
            else OutputDebugString(TEXT("JOY.CPL: Advanced.cpp: OnCommand: LoadString Failed to find IDS_HELPFILENAME!\n"));
#endif // _DEBUG

            if( pszHelpFileName )
                delete[] (pszHelpFileName);
        }
        break;

#ifndef _UNICODE
        // this is the handler for the Global Port Driver Combo box
    case IDC_COMBO1:
        if( code == CBN_SELCHANGE )
            SetActiveGlobalDriver();
        break;

        // handler for PollFlags entry in the registry for the Global Port Driver
    case IDC_POLLFLAGS:
        if( iGameportDriverItem == NO_ITEM )
            break;

        if( SUCCEEDED(pDIJoyConfig->Acquire()) )
        {
            HKEY hKey; 

            VERIFY(SUCCEEDED(pDIJoyConfig->OpenTypeKey(pwszGameportDriverArray[iGameportDriverItem], KEY_ALL_ACCESS, &hKey)));

            // this entry is only valid if the user is running MSANALOG.VXD!
            DWORD nFlags = (IsDlgButtonChecked(hDlg, id)) ? 1 : 0;

            RegSetValueEx(hKey, POLL_FLAGS_REG_STR, 0, REG_BINARY, (PBYTE)&nFlags, sizeof(nFlags));

            RegCloseKey(hKey);

            pDIJoyConfig->SendNotify();

            pDIJoyConfig->Unacquire();
        }
        break;
#endif // _UNICODE

        // this is the handler for the Device list box
    case IDC_ADV_LIST_DEVICE:
        // Fall into Change on DBLCLK
        if( code != LBN_DBLCLK )
            break;

    case IDC_ADV_CHANGE:
        if( nFlags & USER_MODE )
            Error((short)IDS_USER_MODE_TITLE, (short)IDS_USER_MODE);
        else
        {
            LaunchChange(hDlg);           
        }
        break;

    case IDC_ADV_USEOEMPAGE:
        if( !nAssigned ) {
            break;
        }

        if( IsWindowVisible(GetDlgItem(hDlg,IDC_ADV_USEOEMPAGE)) )
        {
            // Boy are you going to pay the price for making that selection...
            LPDIJOYCONFIG_DX5 lpDIJoyConfig = new (DIJOYCONFIG_DX5);
            ASSERT (lpDIJoyConfig);

            ZeroMemory(lpDIJoyConfig, sizeof(DIJOYCONFIG_DX5));

            lpDIJoyConfig->dwSize = sizeof (DIJOYCONFIG_DX5);

            // Get the index from the selected item (iAdvItem)
            BYTE n1 = (BYTE)GetItemData(hAdvListCtrl, (BYTE)iAdvItem);
            BYTE n = 0;
            do
            {
                if( pAssigned[n] && (n1 == pAssigned[n]->ID) )
                    break;
                n++;
            } while( n < NUMJOYDEVS );

            // Find out the type name...
            if( SUCCEEDED(pDIJoyConfig->GetConfig(pAssigned[n]->ID, (LPDIJOYCONFIG)lpDIJoyConfig, DIJC_REGHWCONFIGTYPE)) )
            {
                LPDIJOYTYPEINFO lpDIJoyTypeInfo = new (DIJOYTYPEINFO);
                ASSERT (lpDIJoyTypeInfo);

                ZeroMemory(lpDIJoyTypeInfo, sizeof(DIJOYTYPEINFO));

                lpDIJoyTypeInfo->dwSize = sizeof(DIJOYTYPEINFO);

                // Get the TypeInfo you start with!
                if( SUCCEEDED(pDIJoyConfig->GetTypeInfo(lpDIJoyConfig->wszType, lpDIJoyTypeInfo, DITC_FLAGS1 | DITC_CLSIDCONFIG)) )
                {
                    DWORD dwFlags = GetItemData(hAdvListCtrl, (BYTE)iAdvItem);

                    // If it's checked... you want the OEM supplied property sheet page!
                    if( IsDlgButtonChecked(hDlg, IDC_ADV_USEOEMPAGE) )
                    {
                        // Turn off the USE_DEFAULT flag
                        dwFlags &= ~USE_DEFAULT;

                        // Update the global pointer!!!
                        pAssigned[n]->clsidPropSheet = lpDIJoyTypeInfo->clsidConfig;

                        // Update the pointer being sent to the registry
                        lpDIJoyTypeInfo->dwFlags1 &= ~JOYTYPE_DEFAULTPROPSHEET;
                    } else
                    {
                        // Turn on the USE_DEFAULT flag
                        dwFlags |= USE_DEFAULT;

                        // Update the global list!
                        pAssigned[n]->clsidPropSheet = CLSID_LegacyServer;

                        // Update the pointer being sent to the registry
                        lpDIJoyTypeInfo->dwFlags1 |= JOYTYPE_DEFAULTPROPSHEET;
                    }

                    if( SUCCEEDED(pDIJoyConfig->Acquire()) ) {
                    
                        // Update the registry
                        VERIFY(SUCCEEDED(pDIJoyConfig->SetTypeInfo(lpDIJoyConfig->wszType, lpDIJoyTypeInfo, DITC_FLAGS1)));
    
                        // Set the data in the list control!
                        SetItemData(hAdvListCtrl, (BYTE)iAdvItem, dwFlags);
                    }
                    
                    pDIJoyConfig->Unacquire();
                }

                if( lpDIJoyTypeInfo )
                    delete (lpDIJoyTypeInfo);
            }

            if( lpDIJoyConfig )
                delete (lpDIJoyConfig);
        }
        break;

    default:
        break;
    }
}

////////////////////////////////////////////////////////////////////////////////
// 
// FUNCTION:    OnNotify(HWND hDlg, WPARAM idFrom, NMHDR* pnmhdr)
//
// PARAMETERS:  hDlg   - 
//             idFrom - ID of control sending WM_NOTIFY message
//             pnmhdr -
//
// PURPOSE:     WM_NOTIFY message handler
////////////////////////////////////////////////////////////////////////////////
BOOL OnNotify(HWND hDlg, WPARAM idFrom, LPNMHDR pnmhdr)
{
    switch( pnmhdr->code )
    {
    case PSN_QUERYCANCEL:
        if( nFlags & UPDATE_INPROCESS )
        {
            nFlags &= ~UPDATE_INPROCESS;
            SetFocus(hAdvListCtrl);
        }
        break;
/*
    case LVN_GETINFOTIP:
        {
        LPLVHITTESTINFO lpHit = new (LVHITTESTINFO);
        ASSERT (lpHit);

        BOOL bRet = FALSE;

        POINT pt;
        GetCursorPos(&pt);
        ScreenToClient(hAdvListCtrl, &pt);

        lpHit->pt    = pt;
        lpHit->flags = lpHit->iItem = lpHit->iSubItem = 0;

        ::SendMessage(hAdvListCtrl, LVM_SUBITEMHITTEST, 0, (LPARAM)(LPLVHITTESTINFO)lpHit);

        if (lpHit->flags & LVHT_ONITEMLABEL)
        {
            // Determine the text length of the column text
            LPTSTR lpStr = new (TCHAR[MAX_STR_LEN+1]);
            ASSERT (lpStr);

            GetItemText(hAdvListCtrl, lpHit->iItem, lpHit->iSubItem, lpStr, MAX_STR_LEN);

            // Determine if the latter will fit inside the former...
            SIZE size;
            HDC hDC = GetDC(hAdvListCtrl);
           GetTextExtentPoint(hDC, lpStr, lstrlen(lpStr), &size);
            ReleaseDC(hAdvListCtrl, hDC);

            // Determine how wide the column is!
            short nWidth = (short)::SendMessage(hAdvListCtrl, LVM_GETCOLUMNWIDTH, lpHit->iSubItem, 0);

            bRet = (BOOL)(size.cx > nWidth);

            if (bRet)
                // if not, copy the text into lpHit->pszText
                _tcscpy(((LPNMLVGETINFOTIP)pnmhdr)->pszText, lpStr);

            if (lpStr)
                delete[] (lpStr);
        }
        if (lpHit)
            delete (lpHit);

        return bRet;
        }
*/

    case LVN_BEGINLABELEDIT:
        if( !(GetItemData(hAdvListCtrl, (BYTE)iAdvItem) & ID_NONE) )
            OnCommand(hDlg, IDC_RENAME, 0, 0);
        SetWindowLongPtr(hDlg, DWLP_MSGRESULT, 1);
        break;

    case LVN_ENDLABELEDIT:
        if( !(nFlags & UPDATE_INPROCESS) )
            return(FALSE);

        if( !bProcess )
            return(FALSE);

        nFlags &= ~UPDATE_INPROCESS;

        if( fpPageWndProc )
        {
            HWND hParentWnd = GetParent(hDlg);
            // this is required because the CPL can be launched via RUNDLL32
            if( ::IsWindow(hParentWnd) )
                hParentWnd = GetParent(hParentWnd);
            // Reset the subclass proc
//         SetWindowLongPtr(hParentWnd, GWLP_WNDPROC, (LONG_PTR)fpPageWndProc);
        }

        // Make sure the name is usable!
        if( _tcschr(((NMLVDISPINFO *)pnmhdr)->item.pszText, TEXT('\\')) )
        {
            Error((short)IDS_INVALID_NAME_TITLE, (short)IDS_INVALID_NAME);
        } else
        {
            nFlags |= UPDATE_ALL;

            LPDIPROPSTRING pDIPropString = new (DIPROPSTRING);
            ASSERT (pDIPropString);

            ZeroMemory(pDIPropString, sizeof(DIPROPSTRING));

            pDIPropString->diph.dwSize       = sizeof(DIPROPSTRING);
            pDIPropString->diph.dwHeaderSize = sizeof(DIPROPHEADER);
            pDIPropString->diph.dwHow        = DIPH_DEVICE;

#ifdef _UNICODE
            wcscpy(pDIPropString->wsz, ((NMLVDISPINFO *)pnmhdr)->item.pszText);
#else
            USES_CONVERSION;
            wcscpy(pDIPropString->wsz, A2W(((NMLVDISPINFO *)pnmhdr)->item.pszText));
#endif
            // Search nAssigned for the ID...
            BYTE n = nAssigned;

            do
            {
                if( pAssigned[--n]->ID == ((NMLVDISPINFO *)pnmhdr)->item.iItem )
                    break;

            } while( n );

            if( SUCCEEDED(pAssigned[n]->fnDeviceInterface->SetProperty(DIPROP_INSTANCENAME, &pDIPropString->diph)) )
            {
                SetItemText(hAdvListCtrl, (BYTE)((NMLVDISPINFO *)pnmhdr)->item.iItem, 1, ((NMLVDISPINFO *)pnmhdr)->item.pszText);
            } else
            {
                Error((short)IDS_NO_RENAME_TITLE, (short)IDS_NO_RENAME);
            }

            if( pDIPropString )
                delete (pDIPropString);
        }
        break;

#if 0
    case LVN_COLUMNCLICK:
        switch( ((NM_LISTVIEW*)pnmhdr)->iSubItem )
        {
        case DEVICE_ID:
            {
                static BOOL bIDDirection = TRUE;
                ::SendMessage(hAdvListCtrl, LVM_SORTITEMS, (WPARAM)(LPARAM)(bIDDirection =! bIDDirection), (LPARAM)(PFNLVCOMPARE)CompareIDItems);
            }
            break;

        default:
            {
                BOOL bDirection;

                CListCtrl *pCtrl = new (CListCtrl);
                ASSERT(pCtrl);

                pCtrl->Attach(hAdvListCtrl);

                switch( ((NM_LISTVIEW*)pnmhdr)->iSubItem )
                {
                case DEVICE_FRIENDLY:
                    {
                        static BOOL bFriendlyDirection   = FALSE;
                        bDirection = (bFriendlyDirection =! bFriendlyDirection);
                    }
                    break;

                case DEVICE_TYPE:
                    {
                        static BOOL bTypeDirection       = FALSE;
                        bDirection = (bTypeDirection        =! bTypeDirection);
                    }
                    break;

                case DEVICE_PORT:
                    {
                        static BOOL bPortDirection          = FALSE;
                        bDirection = (bPortDirection        =! bPortDirection);
                    }
                    break;
                }

                SortTextItems(pCtrl, (short)((NM_LISTVIEW*)pnmhdr)->iSubItem, bDirection, 0, 15);

                pCtrl->Detach();

                if( pCtrl )
                    delete (pCtrl);
            }
            break;
        }

        if( nAssigned )
        {
            iAdvItem = (short)::SendMessage(hAdvListCtrl, LVM_GETNEXTITEM, (WPARAM)(int)-1, MAKELPARAM(LVNI_SELECTED, 0));
            ::PostMessage(hAdvListCtrl, LVM_ENSUREVISIBLE, iAdvItem, TRUE);

            if( !(nFlags & USER_MODE) )
                PostDlgItemEnableWindow(hDlg, IDC_ADV_CHANGE, (GetItemData(hAdvListCtrl, (BYTE)iAdvItem) & ID_NONE) ? FALSE : TRUE);

            SetListCtrlItemFocus(hAdvListCtrl, (BYTE)iAdvItem);
        }
        break;
#endif

    case PSN_KILLACTIVE:
        if( nFlags & UPDATE_INPROCESS )
            SetFocus(hAdvListCtrl);

#ifdef _UNICODE
        if( hAdvNotifyDevNode )
            UnregisterDeviceNotification(hAdvNotifyDevNode);
#endif
        break;

    case NM_DBLCLK:
        switch( idFrom )
        {
        case IDC_ADV_LIST_DEVICE:
            if( !(GetItemData(hAdvListCtrl, (BYTE)iAdvItem) & ID_NONE) )
                LaunchChange(hDlg);
            break;
        }
        break;

    case PSN_SETACTIVE:
#ifdef _UNICODE
        RegisterForDevChange(hDlg, &hAdvNotifyDevNode);
#endif 

        if( nFlags & UPDATE_FOR_ADV )
        {
            if( !AdvUpdateListCtrl() )
            {
#ifdef _DEBUG
                OutputDebugString(TEXT("JOY.CPL: OnNotify: Failed UpdateListCtrl!\n"));
#endif
            }
        }

        if( nAssigned )
        {
            iAdvItem = 0;

            // This will happen when the user comes in via the CMD line!
            if( iItem != NO_ITEM )
            {
                // Find the ID of the device... the Brute Force Method!
                do
                {
                    if( (pAssigned[iItem] != NULL) && ((BYTE)GetItemData(hAdvListCtrl, (BYTE)iAdvItem) == pAssigned[iItem]->ID) )
                        break;

                    iAdvItem++;
                } while( iAdvItem < NUMJOYDEVS );
            }

            if( iAdvItem == NUMJOYDEVS ) {
                iAdvItem = 0;
            }

            SetListCtrlItemFocus(hAdvListCtrl, (BYTE)iAdvItem);
            ::PostMessage(hAdvListCtrl, LVM_ENSUREVISIBLE, iAdvItem, FALSE );
        }

        // No global port drivers in NT so...
        //if (nGameportDriver)
#ifndef _UNICODE
        if( !(nFlags & ON_NT) )
            PopulateGlobalPortDriverComboBox();
#endif

        // disable the Change button if iAdvItem points to a (none) selection
        if( !(nFlags & USER_MODE) )
            PostDlgItemEnableWindow(hDlg, IDC_ADV_CHANGE,  (nAssigned) ? ((iAdvItem & ID_NONE) ? FALSE : TRUE) : FALSE);
        break;

    case LVN_ITEMCHANGED:
        if( iAdvItem != (short)((NM_LISTVIEW*)pnmhdr)->iItem )
        {
            iAdvItem = (short)((NM_LISTVIEW*)pnmhdr)->iItem;

            HWND hCtrl = GetDlgItem(hDlg, IDC_ADV_USEOEMPAGE);

            if( nAssigned )
            {
                SetWindowPos( hCtrl, NULL, NULL, NULL, NULL, NULL, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 
                              ((((NM_LISTVIEW*)pnmhdr)->lParam & SHOW_DEFAULT) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW) );

                // Check the box appropriatly!
                if( ((NM_LISTVIEW*)pnmhdr)->lParam & SHOW_DEFAULT )
                    ::PostMessage(GetDlgItem(hDlg, IDC_ADV_USEOEMPAGE), BM_SETCHECK, (((NM_LISTVIEW*)pnmhdr)->lParam & USE_DEFAULT) ? BST_UNCHECKED : BST_CHECKED, 0);

                if( ((NM_LISTVIEW*)pnmhdr)->lParam )
                    PostEnableWindow(hCtrl, (BOOL)!(((NM_LISTVIEW*)pnmhdr)->lParam & ID_NONE));

                if( !(nFlags & USER_MODE) )
                    PostDlgItemEnableWindow(hDlg, IDC_ADV_CHANGE, (BOOL)!(((NM_LISTVIEW*)pnmhdr)->lParam & ID_NONE));
            } else SetWindowPos( hCtrl, NULL, NULL, NULL, NULL, NULL, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW);
        }
        break;

    case LVN_KEYDOWN:
        switch( ((LV_KEYDOWN*)pnmhdr)->wVKey )
        {
        case    VK_DELETE:
            iAdvItem = (short)::SendMessage(hAdvListCtrl, LVM_GETNEXTITEM, (WPARAM)(int)-1, MAKELPARAM(LVNI_SELECTED, 0));
            {
                BYTE nRet = (BYTE)GetItemData(hAdvListCtrl, (BYTE)iAdvItem);
                DeleteSelectedItem((PBYTE)&nRet);
            }
            // Missing break intentional!

        case VK_F5:
            Enumerate( hDlg );
            AdvUpdateListCtrl();
            SetListCtrlItemFocus(hAdvListCtrl, (BYTE)iAdvItem);
            ::PostMessage(hAdvListCtrl, LVM_ENSUREVISIBLE, iAdvItem, FALSE );
            break;
        }
        break;
    }
    return(TRUE);
}

////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnDestroy ( HWND hWnd )
//
// PARAMETERS:  hWnd - Handle to window being destroyed
//
// PURPOSE:     WM_DESTROY message handler
////////////////////////////////////////////////////////////////////////////////////////
void OnDestroy(HWND hWnd)
{
    ASSERT (hWnd);

    if( lpstrNone )
        delete[] (lpstrNone);

    // Reset the subclass proc
    SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)fpMainWndProc);

    // release the DI JoyConfig interface pointer
    if( pDIJoyConfig )
    {
        pDIJoyConfig->Release();
        pDIJoyConfig = 0;
    }
}

////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnAdvHelp ( LPARAM lParam )
//
// PARAMETERS:  lParam - Pointer to HELPINFO struct
//
// PURPOSE:     WM_HELP message handler
////////////////////////////////////////////////////////////////////////////////////////
void OnAdvHelp(LPARAM lParam)
{
    ASSERT (lParam);

    // point to help file
    LPTSTR pszHelpFileName = new TCHAR[STR_LEN_32];
    ASSERT (pszHelpFileName);

    if( LoadString(ghInstance, IDS_HELPFILENAME, pszHelpFileName, STR_LEN_32) )
    {
        if( ((LPHELPINFO)lParam)->iContextType == HELPINFO_WINDOW )
            WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle, pszHelpFileName, HELP_WM_HELP, (ULONG_PTR)gaHelpIDs);
    }
#ifdef _DEBUG
    else OutputDebugString(TEXT("JOY.CPL: Advanced.cpp: OnAdvHelp: LoadString Failed to find IDS_HELPFILENAME!\n"));
#endif // _DEBUG

    if( pszHelpFileName )
        delete[] (pszHelpFileName);
}

////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnContextMenu ( WPARAM wParam )
//
// PARAMETERS:  wParam - HWND of window under the pointer
//
// PURPOSE:     Handle WM_RBUTTONDOWN over all client windows 
// (except the list control... that's OnListviewContextMenu() job)
////////////////////////////////////////////////////////////////////////////////////////
void OnContextMenu(WPARAM wParam, LPARAM lParam)
{
    ASSERT (wParam);

    // If you are on the ListCtrl...
    if( (HWND)wParam == hAdvListCtrl )
    {
        SetFocus(hAdvListCtrl);

        // Don't attempt if nothing selected
        if( iAdvItem != NO_ITEM )
            OnListviewContextMenu(lParam);
    } else
    {
        // point to help file
        LPTSTR pszHelpFileName = new TCHAR[STR_LEN_32];
        ASSERT (pszHelpFileName);

        if( LoadString(ghInstance, IDS_HELPFILENAME, pszHelpFileName, STR_LEN_32) )
            WinHelp((HWND)wParam, pszHelpFileName, HELP_CONTEXTMENU, (ULONG_PTR)gaHelpIDs);
#ifdef _DEBUG
        else OutputDebugString(TEXT("JOY.CPL: Advanced.cpp: OnContextMenu: LoadString Failed to find IDS_HELPFILENAME!\n"));
#endif // _DEBUG

        if( pszHelpFileName )
            delete[] (pszHelpFileName);
    }
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    SetActiveGlobalDriver( void )
//
// PURPOSE:     Commit user selection to persistent storage.
//
// RETURN:      TRUE if successfull, FALSE otherwise
///////////////////////////////////////////////////////////////////////////////
#ifndef _UNICODE
BOOL SetActiveGlobalDriver( void )
{
    // It's Perfectly valid to not have a Global Port Driver so... be prepared!
    short n = (short)SendDlgItemMessage(ghDlg, IDC_COMBO1, CB_GETCURSEL, 0, 0);

    if( n == CB_ERR )
        return(FALSE);

    LPDIJOYUSERVALUES pDIJoyUserValues = new (DIJOYUSERVALUES);
    ASSERT (pDIJoyUserValues);

    ZeroMemory(pDIJoyUserValues, sizeof(DIJOYUSERVALUES));

    pDIJoyUserValues->dwSize = sizeof(DIJOYUSERVALUES);

    HWND hCtrl = GetDlgItem(ghDlg, IDC_COMBO1);

    // Don't worry about this not being a TCHAR, this code will never be executed in NT!
    LPSTR pszDisplayName = new char[SendMessage(hCtrl, LB_GETTEXTLEN, (WPARAM)n, 0)+1];
    ASSERT (pszDisplayName);

    SendMessage(hCtrl, CB_GETLBTEXT, n, (LPARAM)(LPCTSTR)pszDisplayName);

    hCtrl = GetDlgItem(ghDlg, IDC_POLLFLAGS);

    SetWindowPos( hCtrl, NULL, NULL, NULL, NULL, NULL, 
                  SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW );

    // Fix #9815, Set wszGlobalDriver to 
    if( _tcsncmp(pszDisplayName, lpstrNone, sizeof(lpstrNone)/sizeof(TCHAR)) == 0 )
    {
        //wcscpy(pDIJoyUserValues->wszGlobalDriver, L"");

        if( SUCCEEDED(pDIJoyConfig->Acquire()) )
        {
            if( FAILED(pDIJoyConfig->SetUserValues(pDIJoyUserValues, DIJU_GLOBALDRIVER)) )
            {
                TRACE (TEXT("JOY.CPL: SetUserValues failed to set DIJU_GLOBALDRIVER!\n"));
            }
        }
    } else
    {
        LPDIJOYTYPEINFO lpdiJoyInfo = new DIJOYTYPEINFO;
        ASSERT (lpdiJoyInfo);

        ZeroMemory(lpdiJoyInfo, sizeof(DIJOYTYPEINFO));

        lpdiJoyInfo->dwSize = sizeof(DIJOYTYPEINFO);

        USES_CONVERSION;

        short nIndex = 0;

        // traverse the list of Global Port Drivers 'till you find the matching display name
        // this also disallows the user from doing something ugly when they only have "standard gameport"
        while( pwszGameportDriverArray[nIndex] )
        {
            // populate the Type Info                                         
            if( SUCCEEDED(pDIJoyConfig->GetTypeInfo(pwszGameportDriverArray[nIndex], lpdiJoyInfo, DITC_DISPLAYNAME | DITC_CALLOUT)) )
            {
                if( _wcsicmp(lpdiJoyInfo->wszDisplayName, A2W(pszDisplayName)) == 0 )
                {
                    wcscpy(pDIJoyUserValues->wszGlobalDriver, lpdiJoyInfo->wszCallout );

                    if( SUCCEEDED(pDIJoyConfig->Acquire()) )
                    {
                        if( FAILED(pDIJoyConfig->SetUserValues(pDIJoyUserValues, DIJU_GLOBALDRIVER)) )
                        {
                            TRACE (TEXT("JOY.CPL: SetUserValues failed to set DIJU_GLOBALDRIVER!\n"));
                        }

                        // check to see if you need to display the poll flags check box!
                        if( _wcsicmp(pDIJoyUserValues->wszGlobalDriver, lpMSANALOG_VXD) == 0 )
                        {
                            SetWindowPos( hCtrl, NULL, NULL, NULL, NULL, NULL, 
                                          SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );

                            // Get the state from the registry and update the check mark
                            HKEY hKey; 

                            if( FAILED(pDIJoyConfig->OpenTypeKey(pwszGameportDriverArray[nIndex], KEY_ALL_ACCESS, &hKey)) )
                            {
                                TRACE (TEXT("JOY.CPL: OpenTypeKey failed to open key %s!\n"), pwszGameportDriverArray[nIndex]);
                            }

                            DWORD dwFlag;
                            ULONG ulType = REG_BINARY;
                            ULONG ulSize = sizeof(dwFlag);

                            // this will happen if there is no entry for POLL_FLAGS_REG_STR
                            if( ERROR_SUCCESS != RegQueryValueEx(hKey, POLL_FLAGS_REG_STR, NULL, &ulType, (PBYTE)&dwFlag, &ulSize) )
                                dwFlag = 0;

                            ::PostMessage(GetDlgItem(ghDlg, IDC_POLLFLAGS), BM_SETCHECK, (dwFlag) ? BST_CHECKED : BST_UNCHECKED, 0);

                            RegCloseKey(hKey);
                        }
                    }
                    break;
                }
            }
            nIndex++;
        }

        // delete the DIJOYTYPEINFO variable
        if( lpdiJoyInfo )
            delete (lpdiJoyInfo);
    }

    pDIJoyConfig->SendNotify();

    pDIJoyConfig->Unacquire();

    if( pszDisplayName )
        delete[] (pszDisplayName);

    if( pDIJoyUserValues )
        delete pDIJoyUserValues;

    return(TRUE);
}
#endif // _UNICODE


///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    OnListviewContextMenu( void )
//
// PURPOSE:     Handle Context menu in Listview
//
// RETURN:      TRUE if successfull, FALSE otherwise
///////////////////////////////////////////////////////////////////////////////
static void OnListviewContextMenu( LPARAM lParam )
{
    HMENU hPopupMenu = CreatePopupMenu();
    ASSERT (hPopupMenu);

    // unlike life, bRet defaults to bliss
    BOOL bRet = TRUE;

    LPTSTR pszText = new TCHAR[STR_LEN_32];
    ASSERT (pszText);

    // Don't display Rename/Change if on (none) entry
    if( !(GetItemData(hAdvListCtrl, (BYTE)iAdvItem) & ID_NONE) )
    {
        if( !(nFlags & USER_MODE) )
        {
            // add the "Change..." string
            ::SendDlgItemMessage(GetParent(hAdvListCtrl), IDC_ADV_CHANGE, WM_GETTEXT, (WPARAM)STR_LEN_32, (LPARAM)(LPCTSTR)pszText);

            bRet = AppendMenu(hPopupMenu, MF_ENABLED, IDC_ADV_CHANGE, pszText); 
#ifdef _DEBUG
            if( !bRet )
                TRACE(TEXT("JOY.CPL: OnListviewCOntextMenu: AppendMenu Failed to insert %s\n"), pszText);
#endif //_DEBUG

            // Add the Rename text
            VERIFY(LoadString(ghInstance, IDS_RENAME, pszText, STR_LEN_32));
            bRet = AppendMenu(hPopupMenu, MF_ENABLED, IDC_RENAME, pszText);
#ifdef _DEBUG
            if( !bRet )
                TRACE(TEXT("JOY.CPL: OnListviewCOntextMenu: AppendMenu Failed to insert %s\n"), pszText);
#endif //_DEBUG

            // Add the SEPERATOR and "What's this?"

            //PREFIX #WI279965. False positive.
            //MSDN: if uFlags==MF_SEPARATOR, LPCTSTR lpNewItem is ignored.
            bRet = AppendMenu(hPopupMenu, MF_SEPARATOR, 0, 0); 
#ifdef _DEBUG
            if( !bRet )
                TRACE(TEXT("JOY.CPL: OnListviewCOntextMenu: AppendMenu Failed to insert SEPERATOR!\n"));
#endif //_DEBUG
        }
    }

    VERIFY(LoadString(ghInstance, IDS_WHATSTHIS, pszText, STR_LEN_32));
    bRet = AppendMenu(hPopupMenu, MF_ENABLED, IDS_WHATSTHIS, pszText); 
#ifdef _DEBUG
    if( !bRet )
        TRACE(TEXT("JOY.CPL: OnListviewCOntextMenu: AppendMenu Failed to insert %s\n"), pszText);
#endif //_DEBUG

    if( pszText ) delete[] (pszText);

    POINT pt;

    // lParam is -1 if we got here via Shift+F10
    if( lParam > 0 )
    {
        pt.x = GET_X_LPARAM(lParam);
        pt.y = GET_Y_LPARAM(lParam);
    } else
    {
        // Centre the popup on the selected item!

        // This get's a good X pos, but the y is the start of the control!
        ::SendMessage(hAdvListCtrl, LVM_GETITEMPOSITION, iAdvItem, (LPARAM)&pt);

        RECT rc;
        ::GetClientRect(hAdvListCtrl, &rc);

        pt.x = rc.right>>1;

        ClientToScreen(hAdvListCtrl, &pt);
    }

    bRet = TrackPopupMenu (hPopupMenu, TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, pt.x, pt.y, 0, ghDlg, NULL);
#ifdef _DEBUG
    if( !bRet )
        TRACE (TEXT("JOY.CPL: OnListviewContextMenu: TrackPopupMenu Failed!\n"));
#endif //_DEBUG

    if(hPopupMenu) DestroyMenu (hPopupMenu); // PREFIX 45089
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    UpdateListCtrl( void )
//
// PURPOSE:     Refreshes enumerated device list
//
// RETURN:      TRUE if successfull, FALSE otherwise
///////////////////////////////////////////////////////////////////////////////
static BOOL AdvUpdateListCtrl()
{
    // Turn Redraw off here else it will flicker!
    ::SendMessage(hAdvListCtrl, WM_SETREDRAW, (WPARAM)FALSE, 0);

    // Out with the old...
    ::SendMessage(hAdvListCtrl, LVM_DELETEALLITEMS, 0, 0);

    // This buffer is so large because it is used to hold IDS_GEN_STATUS_UNKNOWN
    TCHAR sz1[16];

    // find and assign ID's 
    BYTE n = NUMJOYDEVS;
    BYTE nIndex;

    SendMessage(hAdvListCtrl, LVM_SETITEMCOUNT, (WPARAM)(int)NUMJOYDEVS, (LPARAM)LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);

    // Set everything to NONE to start!
    do
    {
        itoa((BYTE)n--, (LPTSTR)&sz1);

        // Insert the ID
        // Set the device ID and ID_NONE in Extended info...
        nIndex = InsertItem( hAdvListCtrl, sz1, n);

        // Populate the columns with "(none)"
        SetItemText(hAdvListCtrl, nIndex, DEVICE_FRIENDLY, lpstrNone);
        SetItemText(hAdvListCtrl, nIndex, DEVICE_TYPE,     lpstrNone);
#ifdef _UNICODE
        SetItemText(hAdvListCtrl, nIndex, DEVICE_PORT,      lpstrNone);
#endif
    }   while( n );

    if( nAssigned )
    {
        // insert the assigned ones!
        n = nAssigned;

        DIPROPSTRING *pDIPropStr = new (DIPROPSTRING);
        ASSERT (pDIPropStr);

        ZeroMemory(pDIPropStr, sizeof(DIPROPSTRING));

        pDIPropStr->diph.dwSize       = sizeof(DIPROPSTRING);
        pDIPropStr->diph.dwHeaderSize = sizeof(DIPROPHEADER);
        pDIPropStr->diph.dwHow        = DIPH_DEVICE;


#ifndef _UNICODE
        USES_CONVERSION;
#endif               

        // The low half will be populated by the ID, the upper by bit flags!
        DWORD dwData;

        do
        {
            // Set the Product Column!
            if( SUCCEEDED(pAssigned[--n]->fnDeviceInterface->GetProperty(DIPROP_PRODUCTNAME, &pDIPropStr->diph)) )
            {
#ifdef _UNICODE
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_TYPE, (LPTSTR)pDIPropStr->wsz);
#else
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_TYPE, (LPTSTR)W2A(pDIPropStr->wsz));
#endif
            }

            // Set the Friendly Name!
            if( SUCCEEDED(pAssigned[n]->fnDeviceInterface->GetProperty(DIPROP_INSTANCENAME, &pDIPropStr->diph)) )
            {
#ifdef _UNICODE
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_FRIENDLY, (LPTSTR)pDIPropStr->wsz);
#else
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_FRIENDLY, (LPTSTR)W2A(pDIPropStr->wsz));
#endif
            }

#ifdef _UNICODE
            // Set the Game Port Column!
            if( SUCCEEDED(pAssigned[n]->fnDeviceInterface->GetProperty(DIPROP_GETPORTDISPLAYNAME, &pDIPropStr->diph)) )
            {
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_PORT, (LPTSTR)pDIPropStr->wsz);
            } else
            {
                VERIFY(LoadString(ghInstance, IDS_GEN_STATUS_UNKNOWN, sz1, sizeof(sz1)/sizeof(TCHAR)));
                SetItemText(hAdvListCtrl, pAssigned[n]->ID, DEVICE_PORT, (LPTSTR)sz1);
            }
#endif // _UNICODE

            // Set the ID in the data... 
            // This is necessary for Sorting!
            dwData = pAssigned[n]->ID;

            //if( pAssigned[n]->clsidPropSheet != CLSID_LegacyServer )
            if( pAssigned[n]->fHasOemSheet )
            {
                LPDIJOYCONFIG_DX5 lpDIJoyConfig = new (DIJOYCONFIG_DX5);
                ASSERT (lpDIJoyConfig);

                ZeroMemory(lpDIJoyConfig, sizeof(DIJOYCONFIG_DX5));

                lpDIJoyConfig->dwSize = sizeof (DIJOYCONFIG_DX5);


                // Set the DefaultPropertySheet flag 
                if( SUCCEEDED(pDIJoyConfig->GetConfig(pAssigned[n]->ID, (LPDIJOYCONFIG)lpDIJoyConfig, DIJC_REGHWCONFIGTYPE)) )
                {
                    LPDIJOYTYPEINFO lpDIJoyTypeInfo = new (DIJOYTYPEINFO);
                    ASSERT (lpDIJoyTypeInfo);

                    ZeroMemory(lpDIJoyTypeInfo, sizeof(DIJOYTYPEINFO));

                    lpDIJoyTypeInfo->dwSize = sizeof (DIJOYTYPEINFO);

                    if( SUCCEEDED(pDIJoyConfig->GetTypeInfo(lpDIJoyConfig->wszType, lpDIJoyTypeInfo, DITC_FLAGS1 )) )
                    {
                        if( lpDIJoyTypeInfo->dwFlags1 & JOYTYPE_DEFAULTPROPSHEET )
                        {
                            // Set the USE_DEFAULT mask!
                            dwData |= USE_DEFAULT;

                            // Update the global list!
                            pAssigned[n]->clsidPropSheet = CLSID_LegacyServer;
                        }
                    }

                    if( lpDIJoyTypeInfo )
                        delete (lpDIJoyTypeInfo);
                }

                dwData |= SHOW_DEFAULT;

                if( lpDIJoyConfig )
                    delete (lpDIJoyConfig);
            }

            // Set the Item Data to the ID!                        
            SetItemData(hAdvListCtrl, pAssigned[n]->ID, dwData);

        }  while( n );

        // clean up, clean up... everybody do your share!
        if( pDIPropStr )
            delete (pDIPropStr);
    }

    // turn the flag off!
    if( nFlags & UPDATE_FOR_ADV )
        nFlags &= ~UPDATE_FOR_ADV;

    // Turn the redraw flag back on!
    ::SendMessage (hAdvListCtrl, WM_SETREDRAW, (WPARAM)TRUE, 0);
    InvalidateRect(hAdvListCtrl, NULL, TRUE);

    return(TRUE);
}

////////////////////////////////////////////////////////////////////////////////
// 
// FUNCTION:    ChangeDialogProc(HWND hDlg, ULONG uMsg, WPARAM wParam, LPARAM lParam)
//
// PARAMETERS:  HWND     hDlg   -
//             ULONG    uMsg   -
//             WPARAM   wParam -
//             LPARAM   lParam -
//
// PURPOSE:     
////////////////////////////////////////////////////////////////////////////////
BOOL WINAPI ChangeDialogProc(HWND hDlg, ULONG uMsg, WPARAM wParam, LPARAM lParam)
{
    switch( uMsg )
    {
    case WM_LBUTTONDOWN:
        // Click Drag service for PropSheets!
        PostMessage(hDlg, WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, lParam);
        break;

    case WM_HELP:
        OnHelp(lParam);
        return(1);

    case WM_CONTEXTMENU:
        OnContextMenu(wParam, lParam);
        return(TRUE);

    case WM_INITDIALOG:
        {
            HICON hIcon = (HICON)LoadImage(ghInstance, MAKEINTRESOURCE(IDI_CPANEL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
            ASSERT (hIcon);

            if( hIcon )
                ::PostMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);

            HWND hCtrl = GetDlgItem(hDlg, IDC_CHANGE_LIST);
            ASSERT (hCtrl);

            UpdateChangeListCtrl( hCtrl );

            BYTE nCounter = nAssigned;
            while( nCounter-- )
            {
                if( (BYTE)::SendMessage(hCtrl, LB_GETITEMDATA, (WPARAM)nCounter, 0) == nOldID )
                    break;
            }

            // Set the list box selections!
            ::PostMessage(hCtrl, LB_SETCURSEL, (WPARAM)nCounter, 0);

            // Done with the ListCtrl, now... on to the ComboBox
            if( nFlags & ON_NT )
            {

                if( !PopulatePortList(hDlg) )
                {
#ifdef _DEBUG 
                    OutputDebugString(TEXT("JOY.CPL: ADVANCED.CPP: PopulatePortList failed!\n"));
#endif
                }
            }

            // Set up the Spin Control!
            HWND hSpinCtrl = GetDlgItem(hDlg, IDC_SPIN);

            ::PostMessage(hSpinCtrl, UDM_SETRANGE,  0, MAKELPARAM(NUMJOYDEVS, 1));
            ::PostMessage(hSpinCtrl, UDM_SETBASE,  10, 0L);
            ::PostMessage(hSpinCtrl, UDM_SETPOS,     0, MAKELPARAM(nOldID+1, 0));
        }
        return(FALSE);

    case WM_COMMAND:
        {
            switch( LOWORD(wParam) )
            {
            /* I'll leave this commented out, as it's likely they'll want double clicking back once I take it out
        case IDC_CHANGE_LIST:
        // Fall into Change on DBLCLK
           if (HIWORD(wParam) != LBN_DBLCLK)
               break;
            */
            
            case IDOK:
                {
                    HWND hCtrl = GetDlgItem(hDlg, IDC_CHANGE_LIST);
                    ASSERT (hCtrl);

                    char nSelectedItem = (char)(SendMessage(hCtrl, LB_GETITEMDATA, SendMessage(hCtrl, LB_GETCURSEL, 0, 0), 0));

                    TCHAR tsz[4];

                    hCtrl = GetDlgItem(hDlg, IDC_SPINBUDDY);

                    tsz[0] = 4;

                    SendMessage(hCtrl, EM_GETLINE, 0, (LPARAM)(LPCSTR)&tsz);

                    // The '-1' is to account for the 1 based list
                    // and the 0 based id's!
                    char nSelectedID = (char)atoi((LPCTSTR)&tsz)-1;

                    pDIJoyConfig->Acquire();

                    // first check to see if the user has selected NONE!
                    if( nSelectedItem == -2 )
                    {
                        // User has selected NONE!
                        VERIFY (SUCCEEDED(pDIJoyConfig->DeleteConfig(nSelectedID)));
                    } else
                    {
                        // see if the selected item and the ID match!
                        // if so... get out of here!
                        if( nSelectedID == nSelectedItem )
                        {
#ifdef _DEBUG
                            OutputDebugString(TEXT("JOY.CPL: ADVANCED.CPP: OnChangeCommand: IDOK: Device already at selected ID!\n"));
#endif
                        } else
                        {
                            SwapIDs(nSelectedID, nSelectedItem);
//                          SetListCtrlItemFocus(hAdvListCtrl, (BYTE)nSelectedID);
                        }
                    }

                    pDIJoyConfig->Unacquire();
                }
                // missing break intentional!

            case IDCANCEL:
                EndDialog(hDlg, LOWORD(wParam));
                break;
            }
        }
        return(1);

    case WM_DESTROY:
        DestroyIcon((HICON)SendMessage(hDlg, WM_GETICON, (WPARAM)ICON_SMALL, 0));
        return(TRUE);
    }
    return(FALSE);
} 



///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    PopulateGlobalPortDriverComboBox( void )
//
// PARAMETERS:  
//
// PURPOSE:     
///////////////////////////////////////////////////////////////////////////////
#ifndef UNICODE
void PopulateGlobalPortDriverComboBox( void )
{
    HWND hCtrl = GetDlgItem(ghDlg, IDC_COMBO1);

    // make sure the combo is clear before we start populating it!
    SendMessage(hCtrl, CB_RESETCONTENT, 0, 0);

    // Type info
    LPDIJOYTYPEINFO_DX5 lpdiJoyInfo = new (DIJOYTYPEINFO_DX5);
    ASSERT (lpdiJoyInfo);

    ZeroMemory(lpdiJoyInfo, sizeof(DIJOYTYPEINFO_DX5));

    lpdiJoyInfo->dwSize = sizeof(DIJOYTYPEINFO_DX5);

    BYTE nIndex = nGameportDriver;
    USES_CONVERSION;

    // Populate the Combobox
    while( nIndex-- )
    {
        // populate the Type Info and place the Index in the extra memory!
        if( SUCCEEDED(pDIJoyConfig->GetTypeInfo(pwszGameportDriverArray[nIndex], (LPDIJOYTYPEINFO)lpdiJoyInfo, DITC_DISPLAYNAME)) )
          ::SendMessage(hCtrl, CB_SETITEMDATA, ::SendMessage(hCtrl, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)W2A(lpdiJoyInfo->wszDisplayName)), nIndex);
    #ifdef _DEBUG
        else
          OutputDebugString (TEXT("JOY.CPL: ADVANCED.CPP: PopulateGlobalPortDriverComboBox: GetTypeInfo failed!\n"));
    #endif
    }

    // Display the Current selected GlobalPortDriver or None!
    LPDIJOYUSERVALUES pDIJoyUserValues = new DIJOYUSERVALUES;
    ASSERT (pDIJoyUserValues);

    ZeroMemory(pDIJoyUserValues, sizeof(DIJOYUSERVALUES));

    pDIJoyUserValues->dwSize = sizeof(DIJOYUSERVALUES);

    VERIFY (SUCCEEDED(pDIJoyConfig->GetUserValues(pDIJoyUserValues, DIJU_GLOBALDRIVER)));

    // Fix #9815, If the user has No Global port driver label as NONE!
    if( !(*pDIJoyUserValues->wszGlobalDriver && pDIJoyUserValues->wszGlobalDriver) )
    {
        iGameportDriverItem = NO_ITEM; 

        PostMessage(hCtrl, CB_SETCURSEL, (WPARAM)SendMessage(hCtrl, CB_FINDSTRING, (WPARAM)-1, (LPARAM)lpstrNone), (LPARAM)0);
    } else
    {
        ZeroMemory(lpdiJoyInfo, sizeof(DIJOYTYPEINFO_DX5));

        lpdiJoyInfo->dwSize = sizeof(DIJOYTYPEINFO_DX5);

        nIndex = 0;

        // get type info 'till you find the one you want and place it's callout 
        while( pwszGameportDriverArray[nIndex] )
        {
            VERIFY(SUCCEEDED(pDIJoyConfig->GetTypeInfo(pwszGameportDriverArray[nIndex], (LPDIJOYTYPEINFO)lpdiJoyInfo, DITC_CALLOUT | DITC_DISPLAYNAME)));

            if( _wcsicmp(lpdiJoyInfo->wszCallout, pDIJoyUserValues->wszGlobalDriver) == 0 )
            {
                ::PostMessage(hCtrl, CB_SETCURSEL, (WPARAM)::SendMessage(hCtrl, CB_FINDSTRING, (WPARAM)-1, (LPARAM)W2A(lpdiJoyInfo->wszDisplayName)), (LPARAM)0);

                iGameportDriverItem = nIndex; 

                // enable the PollFlags checkbox!
                if( _wcsicmp(pDIJoyUserValues->wszGlobalDriver, lpMSANALOG_VXD) == 0 )
                {
                    SetWindowPos( GetDlgItem( ghDlg, IDC_POLLFLAGS), NULL, NULL, NULL, NULL, NULL, 
                                  SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );

                    VERIFY(SUCCEEDED(pDIJoyConfig->Acquire()));

                    // Get the state from the registry and update the check mark
                    HKEY hKey; 
                    DWORD dwFlag;

                    if( SUCCEEDED(pDIJoyConfig->OpenTypeKey(pwszGameportDriverArray[nIndex], KEY_ALL_ACCESS, &hKey)) )
                    {
                        ULONG ulType = REG_BINARY;
                        ULONG ulSize = sizeof(dwFlag);

                        // this will happen if there is no entry for POLL_FLAGS_REG_STR
                        if( ERROR_SUCCESS != RegQueryValueEx(hKey, POLL_FLAGS_REG_STR, NULL, &ulType, (PBYTE)&dwFlag, &ulSize) )
                            dwFlag = 0;

                        RegCloseKey(hKey);
                    }

                    pDIJoyConfig->Unacquire();

                    ::PostMessage(GetDlgItem(ghDlg, IDC_POLLFLAGS), BM_SETCHECK, (dwFlag) ? BST_CHECKED : BST_UNCHECKED, 0);
                }
                break;
            }
            nIndex++;
        }
    }

    // delete the DIJOYUSERVALUES variable
    if( pDIJoyUserValues )
        delete pDIJoyUserValues;

    // clean up, clean up... everybody do your share!
    if( lpdiJoyInfo )
        delete (lpdiJoyInfo);

//    VERIFY(SendMessage(hCtrl, CB_ADDSTRING, 0, (LPARAM)(LPCSTR)lpstrNone) != CB_ERR);
}
#endif

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    SubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
//
// PARAMETERS:  HWND   hWnd   -
//             UINT   uMsg   -
//             WPARAM wParam -
//             LPARAM lParam -
//
// PURPOSE:     SubClass Procedure for Setting the ID to NONE on VK_DELETE on the Advanced Page
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KeySubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch( uMsg )
    {
    case WM_COMMAND:
        switch( LOWORD(wParam) )
        {
        case IDCANCEL:
        case IDOK:     
            if( nFlags & UPDATE_INPROCESS )
            {
                bProcess = (LOWORD(wParam) == IDOK) ? TRUE : FALSE;
                SetFocus(hAdvListCtrl);
                nFlags &= ~UPDATE_INPROCESS;
                return(FALSE);
            }
            break;
        }
        break;
    }
    return(BOOL)CallWindowProc(fpPageWndProc, hWnd, uMsg, wParam, lParam);
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    SubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
//
// PARAMETERS:  HWND   hWnd   -
//             UINT   uMsg   -
//             WPARAM wParam -
//             LPARAM lParam -
//
// PURPOSE:     SubClass Procedure for Setting the ID to NONE on VK_DELETE on the Advanced Page
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI SubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch( uMsg )
    {
    case WM_PARENTNOTIFY:
        if( LOWORD(wParam) == WM_LBUTTONDOWN )
        {
            if( nFlags & UPDATE_INPROCESS )
                SetFocus(hAdvListCtrl);
        }
        break;

    case WM_SYSCOMMAND:
        if( wParam & SC_VSCROLL )
        {
            if( nFlags & UPDATE_INPROCESS )
                SetFocus(hAdvListCtrl);
        }
        break;
    }

    return(BOOL)CallWindowProc(fpMainWndProc, hWnd, uMsg, wParam, lParam);
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    UpdateChangeListCtrl ( HWND hCtrl )
//
// PARAMETERS:  HWND   hCtrl - Handle to Change list box
//
// PURPOSE: Update the Change List box
///////////////////////////////////////////////////////////////////////////////
static BOOL UpdateChangeListCtrl ( HWND hCtrl )
{
#ifndef _UNICODE
    USES_CONVERSION;
#endif

    DIPROPSTRING *pDIPropStr = new (DIPROPSTRING);
    ASSERT (pDIPropStr);

    ZeroMemory(pDIPropStr, sizeof(DIPROPSTRING));

    pDIPropStr->diph.dwSize       = sizeof(DIPROPSTRING);
    pDIPropStr->diph.dwHeaderSize = sizeof(DIPROPHEADER);
    pDIPropStr->diph.dwHow        = DIPH_DEVICE;

    BYTE n = nAssigned;

    LPTSTR lpStr = new (TCHAR[MAX_STR_LEN]);
    ASSERT (lpStr);

    // find and assign ID's
    while( n-- )
    {
        if( SUCCEEDED(pAssigned[n]->fnDeviceInterface->GetProperty(DIPROP_INSTANCENAME, &pDIPropStr->diph)) )
        {

            // Our buffer is Only soooo big...
            // besides, it just doesn't make sence to display Everything!
            if( wcslen(pDIPropStr->wsz) > STR_LEN_64 )
            {
                pDIPropStr->wsz[60] = pDIPropStr->wsz[61] = pDIPropStr->wsz[62] = TEXT('.');
                pDIPropStr->wsz[63] = TEXT('\0');
            }

#ifdef _UNICODE
            _tcscpy(lpStr, pDIPropStr->wsz);
#else
            _tcscpy(lpStr, W2A(pDIPropStr->wsz));
#endif

            // Put the first bracket on...
            _tcscat(lpStr, TEXT("  ("));

            // Now, get the productname of the device!
            if( SUCCEEDED(pAssigned[n]->fnDeviceInterface->GetProperty(DIPROP_PRODUCTNAME, &pDIPropStr->diph)) )
            {
#ifdef _UNICODE
                _tcscat(lpStr, pDIPropStr->wsz);
#else
                _tcscat(lpStr, W2A(pDIPropStr->wsz));
#endif
            }

            // put the end bracket on...
            _tcscat(lpStr, TEXT(")"));

            BYTE n1 = (BYTE)SendMessage(hCtrl, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)lpStr);
            SendMessage(hCtrl, LB_SETITEMDATA, n1, pAssigned[n]->ID);
        }
#ifdef _DEBUG
        else OutputDebugString(TEXT("JOY.CPL: Advanced.cpp: UpdateChangeListCtrl: GetProperty failed!\n"));
#endif // _DEBUG
    }

    if( lpStr )
        delete[] (lpStr);

    if( pDIPropStr )
        delete (pDIPropStr);

    return(TRUE);
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION:    itoa(BYTE n, LPTSTR lpStr)
//
// PARAMETERS:  BYTE   n     - Number to be translated
//             LPTSTR lpStr - Buffer to recieve translated value
//
// PURPOSE: Convert BYTE values < 20 to strings.
///////////////////////////////////////////////////////////////////////////////
void itoa(BYTE n, LPTSTR lpStr)
{
    // designed for use with the CPL ONLY!
    // Only supports values < NUMJOYDEVS!
    if( n > NUMJOYDEVS )
    {
#ifdef _DEBUG
        OutputDebugString(TEXT("JOY.CPL: itoa: n > NUMJOYDEVS!\n"));
#endif      
        return;
    }

    lpStr[0] = n % 10 + '0';

    if( n > 9 )
    {
        // Reverse the string and send it back...
        lpStr[1] = lpStr[0];
        lpStr[0] = '1';
        lpStr[2] = '\0';
    } else lpStr[1] = '\0';
}

int CALLBACK CompareIDItems(LPARAM item1, LPARAM item2, LPARAM uDirection)
{
    if( LOWORD(item1) == LOWORD(item2) )
        return(0);

    short nRet = (LOWORD(item1) > LOWORD(item2)) ? 1 : -1;

    return(uDirection) ? nRet : (nRet < 0) ? 1 : -1;
}

void LaunchChange( HWND hTmp )
{
    // Don't allow if you're in USER mode!
    if( (nFlags & USER_MODE) )
        return;

    iAdvItem = (short)::SendMessage(hAdvListCtrl, LVM_GETNEXTITEM, (WPARAM)(int)-1, MAKELPARAM(LVNI_SELECTED, 0));

    nOldID = (BYTE)GetItemData(hAdvListCtrl, (BYTE)iAdvItem);

    if( nOldID & ID_NONE )
        return;

    // valid returns are IDOK, IDCANCEL, and IDC_CHANGE_LIST
    if( IDCANCEL != DialogBox(ghInstance, (PTSTR)IDD_ADV_CHANGE, ghDlg, (DLGPROC)ChangeDialogProc) )
    {
        // Yeah, it's not really a DEVICEARRIVAL...
        ::SendMessage(hTmp, WM_DEVICECHANGE, DBT_DEVICEARRIVAL, 0);
        ::PostMessage(hAdvListCtrl, LVM_ENSUREVISIBLE, (BYTE)iAdvItem, TRUE);
        SetFocus(hAdvListCtrl);
        SetListCtrlItemFocus(hAdvListCtrl, (BYTE)iAdvItem);
    }
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION: SortTextItems( CListCtrl *pCtrl, short nCol, BOOL bAscending, int low, int high )
//
//    SortTextItems - Sort the list based on column text
//
// PARAMETERS:  
//    pCtrl          - pointer to list to sort
//    nCol             - column that contains the text to be sorted
//    bAscending        - indicate sort order
//    low              - row to start scanning from - default row is 0
//    high             - row to end scan. -1 indicates last row
//
// PURPOSE: Sort text items in ListCtrl
//    Returns          - Returns true for success
///////////////////////////////////////////////////////////////////////////////
BOOL SortTextItems( CListCtrl *pCtrl, short nCol, BOOL bAscending, short low, short high )
{
    CHeaderCtrl* pHeader = (CHeaderCtrl*) pCtrl->GetDlgItem(0);

    if( nCol >= pHeader->GetItemCount() )
        return(FALSE);

    if( high == -1 )
        high = pCtrl->GetItemCount() - 1;

    short lo = low; 
    short hi = high;

    if( hi <= lo ) return(FALSE);

    // The choices here are to malloc a buffer large enough for the largest
    // string, or malloc an LV_ITEM struct to get the length, then malloc 
    // just the size we need.
    CString midItem = pCtrl->GetItemText( (lo+hi)/2, nCol );

    // loop through the list until indices cross    
    while( lo <= hi )
    {
        // rowText will hold all column text for one row        
        CStringArray rowText;

        // find the first element that is greater than or equal to 
        // the partition element starting from the left Index.      
        if( bAscending )
            while( ( lo < high ) && ( pCtrl->GetItemText(lo, nCol) < midItem ) )
                ++lo;
        else
            while( ( lo < high ) && ( pCtrl->GetItemText(lo, nCol) > midItem ) )
                ++lo;

        // find an element that is smaller than or equal to 
        // the partition element starting from the right Index.     
        if( bAscending )
            while( ( hi > low ) && ( pCtrl->GetItemText(hi, nCol) > midItem ) )
                --hi;
        else
            while( ( hi > low ) && ( pCtrl->GetItemText(hi, nCol) < midItem ) )
                --hi;

        // if the indexes have not crossed, swap        
        // and if the items are not equal
        if( lo <= hi )
        {
            // swap only if the items are not equal
            if( pCtrl->GetItemText(lo, nCol) != pCtrl->GetItemText(hi, nCol) )
            {
                // swap the rows
                LV_ITEM lvitemlo, lvitemhi;             
                BYTE nColCount = (BYTE)pHeader->GetItemCount();
                rowText.SetSize( nColCount );                

                for( BYTE i = 0; i < nColCount; i++ )
                    rowText[i] = pCtrl->GetItemText(lo, i);

                lvitemlo.mask      = LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
                lvitemlo.iItem     = lo;                
                lvitemlo.iSubItem  = 0;
                lvitemlo.stateMask = LVIS_CUT | LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED | LVIS_OVERLAYMASK | LVIS_STATEIMAGEMASK;               
                lvitemhi           = lvitemlo;
                lvitemhi.iItem     = hi;                

                ListView_GetItem(pCtrl->GetSafeHwnd(), &lvitemlo );

                ListView_GetItem(pCtrl->GetSafeHwnd(), &lvitemhi );

                for( i=0; i<nColCount; i++ )
                    pCtrl->SetItemText(lo, i, pCtrl->GetItemText(hi, i));

                lvitemhi.iItem = lo;                
                ListView_SetItem(pCtrl->GetSafeHwnd(), &lvitemhi );

                for( i=0; i<nColCount; i++ )
                    pCtrl->SetItemText(hi, i, rowText[i]);              

                lvitemlo.iItem = hi;
                ListView_SetItem(pCtrl->GetSafeHwnd(), &lvitemlo );
            }

            ++lo;            
            --hi;        
        }
    }

    // If the right index has not reached the left side of array
    // must now sort the left partition.    
    if( low < hi )
        SortTextItems( pCtrl, nCol, bAscending, low, hi);

    // If the left index has not reached the right side of array
    // must now sort the right partition.   
    if( lo < high )
        SortTextItems( pCtrl, nCol, bAscending, lo, high);

    return(TRUE);
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: EditSubLabel( BYTE nItem, BYTE nCol )
//
// PARAMETERS:  
//       EditSubLabel - Start edit of a sub item label
//       Returns         - Temporary pointer to the new edit control
//       nItem           - The row index of the item to edit
//       nCol            - The column of the sub item.
// PURPOSE:  Provide editing services for any column in a CListCtrl
///////////////////////////////////////////////////////////////////////////////
void EditSubLabel( BYTE nItem, BYTE nCol )
{
#ifdef _DEBUG
    // Make sure that the item is visible
    if( !SendMessage(hAdvListCtrl, LVM_ENSUREVISIBLE, nItem, TRUE ) )
    {
        OutputDebugString(TEXT("JOY.CPL: ADVANCED.CPP: EditSubLabel: requested item not visible!\n"));
        return;
    }
#endif // _DEBUG

    // Get the column offset
    short offset = 0;
    BYTE i = 0;

    // OK, so here's what we have to do...
    // Traverse the columns incrementing the widths of the ones lesser than the one we're looking for!

    HDITEM *phdItem = new (HDITEM);
    ASSERT (phdItem);

    phdItem->mask = HDI_ORDER | HDI_WIDTH;

    HWND hHeader   = GetDlgItem(hAdvListCtrl, 0);
    BYTE nColumns  = (BYTE)::SendMessage(hHeader, HDM_GETITEMCOUNT, 0, 0L);
    BYTE nColWidth;

    do
    {
        ::SendMessage(hHeader, HDM_GETITEM, (WPARAM)(int)--nColumns, (LPARAM)(LPHDITEM)phdItem);

        if( phdItem->iOrder < nCol )
            offset += (short)phdItem->cxy;

        if( phdItem->iOrder == nCol )
            nColWidth = (BYTE)phdItem->cxy;
    } while( nColumns ); 

    if( phdItem )
        delete (phdItem);

    RECT rect;
    ListView_GetItemRect(hAdvListCtrl, nItem, &rect, LVIR_BOUNDS );

    // Now scroll if we need to expose the column
    CRect rcClient;
    ::GetClientRect(hAdvListCtrl, &rcClient);

    if( offset + rect.left < 0 || offset + rect.left > rcClient.right )
    {
        ::SendMessage(hAdvListCtrl, LVM_SCROLL, (WPARAM)(int)offset + rect.left, (LPARAM)(int)0);
        rect.left -= (offset + rect.left);
    }

    rect.left += offset+4;
    rect.right = rect.left + nColWidth - 3;  // + ::SendMessage(hAdvListCtrl, LVM_GETCOLUMNWIDTH, (WPARAM)(int)nCol, (LPARAM)0L) - 3 ;

    if( rect.right > rcClient.right )
        rect.right = rcClient.right;

    CEdit *pEdit = new CInPlaceEdit(nItem, 1); 
    ASSERT (pEdit);

    // malloc the list ctrl
    CWnd  *pListCtrl = new (CWnd);
    ASSERT (pListCtrl);

    pListCtrl->Attach(hAdvListCtrl);

    pEdit->Create(WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_CENTER, rect, pListCtrl, IDC_IPEDIT );

    pListCtrl->Detach();

    if( pListCtrl )
        delete (pListCtrl);
}

#ifdef _UNICODE
void SwapIDs(BYTE nSource, BYTE nTarget)
{
    // malloc and retrieve the data from the selected item
    LPDIJOYCONFIG lpSelectedID = new (DIJOYCONFIG);
    ASSERT (lpSelectedID);

    ZeroMemory(lpSelectedID, sizeof(DIJOYCONFIG));

    lpSelectedID->dwSize = sizeof(DIJOYCONFIG);

    // Get the Config of device on ID taken from Change List box!
    HRESULT hr = pDIJoyConfig->GetConfig(nSource, lpSelectedID, DIJC_ALL);

    if( hr == DIERR_NOTFOUND || hr == S_FALSE )
    {
        // No Object on Selected ID!
        if( lpSelectedID )
            delete (lpSelectedID);

        lpSelectedID = NULL;
    }

    // malloc and retrieve the data from the item associated 
    // with the ID taken from the Item selected in the List box!
    LPDIJOYCONFIG lpSelectedItem = new (DIJOYCONFIG);
    ASSERT (lpSelectedItem);

    ZeroMemory(lpSelectedItem, sizeof(DIJOYCONFIG));

    lpSelectedItem->dwSize = sizeof (DIJOYCONFIG);

    hr = pDIJoyConfig->GetConfig(nTarget, lpSelectedItem, DIJC_ALL);

    if( hr == DIERR_NOTFOUND || hr == S_FALSE )
    {
        if( lpSelectedItem )
            delete (lpSelectedItem);

        lpSelectedItem = NULL;
    }

    // ***********************************************************
    // Delete the configurations!
    // ***********************************************************

    // Set the hour glass
    SetCursor(LoadCursor(NULL, IDC_WAIT));

    // ********************************************************
    // OK, now... at this point you have:
    //    lpSelectedID: containing NULL if the device wasn't present or...
    //                  a valid pointer containing the configuration of nSelectedID
    //    lpSelected  : containing NULL if the device wasn't present or...
    //                  a valid pointer containing the configuration of id
    // ********************************************************
    // Time to Set the Configurations!

    if( lpSelectedID )
    {
        hr = pDIJoyConfig->SetConfig(nTarget, lpSelectedID, DIJC_ALL);

        if( lpSelectedID )
            delete (lpSelectedID);
    } else pDIJoyConfig->DeleteConfig(nSource);

    // delete both configurations
    // pointers will be NULL if the config was not found!
    if( lpSelectedItem )
    {
        hr = pDIJoyConfig->SetConfig(nSource, lpSelectedItem, DIJC_ALL );

        if( lpSelectedItem )
            delete (lpSelectedItem);
    }

    pDIJoyConfig->SendNotify();

    // Set the hour glass
    SetCursor(LoadCursor(NULL, IDC_ARROW));
}

#else

// Memphis version of SwapIDs... 
// Caution!!! Very Sensitive!!!
void SwapIDs(BYTE nSelectedID, BYTE nSelectedItem)
{
    // malloc and retrieve the data from the selected item
    LPDIJOYCONFIG lpSelectedID = new (DIJOYCONFIG);

    DWORD dwSelectedID = 0, dwSelectedItem = 0;

    ASSERT (lpSelectedID);

    ZeroMemory(lpSelectedID, sizeof(DIJOYCONFIG));

    lpSelectedID->dwSize = sizeof(DIJOYCONFIG);

    // Get the Config of device on ID taken from Change List box!
    HRESULT hr = pDIJoyConfig->GetConfig(nSelectedID, lpSelectedID, DIJC_ALL);

    if( hr == DIERR_NOTFOUND || hr == S_FALSE )
    {
        // No Object on Selected ID!
        if( lpSelectedID )
            delete (lpSelectedID);

        lpSelectedID = NULL;
    } else {
        if( lpSelectedID )
            delete (lpSelectedID);

        lpSelectedID = NULL;
        
        Error((short)IDS_DEST_ID_OCCUPIED_TITLE, (short)IDS_DEST_ID_OCCUPIED);
        
        return;
    }


    // malloc and retrieve the data from the item associated 
    // with the ID taken from the Item selected in the List box!
    LPDIJOYCONFIG lpSelectedItem = new (DIJOYCONFIG);
    ASSERT (lpSelectedItem);

    ZeroMemory(lpSelectedItem, sizeof(DIJOYCONFIG));

    lpSelectedItem->dwSize = sizeof (DIJOYCONFIG);

    hr = pDIJoyConfig->GetConfig(nSelectedItem, lpSelectedItem, DIJC_ALL);

    if( hr == DIERR_NOTFOUND || hr == S_FALSE )
    {
        if( lpSelectedItem )
            delete (lpSelectedItem);

        lpSelectedItem = NULL;
        
        return;
    }

    // ***********************************************************
    // Delete the configurations!
    // ***********************************************************

    // Set the hour glass
    SetCursor(LoadCursor(NULL, IDC_WAIT));

    // ********************************************************
    // OK, now... at this point you have:
    //    lpSelectedID: containing NULL if the device wasn't present or...
    //                  a valid pointer containing the configuration of nSelectedID
    //    lpSelected  : containing NULL if the device wasn't present or...
    //                  a valid pointer containing the configuration of id
    // ********************************************************
    // Time to Set the Configurations!


    if( lpSelectedID )
    {
        DWORD dwFlags = DIJC_REGHWCONFIGTYPE |  DIJC_CALLOUT  | DIJC_GAIN | DIJC_GUIDINSTANCE;
        USES_CONVERSION;

        // if the callout is joyhid.vxd then the device is USB so...
        // OR on the DIJC_WDMGAMEPORT flag!
        if( !_stricmp(TEXT("joyhid.vxd"), W2A(lpSelectedID->wszCallout)) )
        {
//            dwFlags |= DIJC_WDMGAMEPORT;
            dwSelectedID = 2;
        } else
        {
            dwSelectedID = 1;
        }

        if( dwSelectedID == 1 ) {
            hr = pDIJoyConfig->DeleteConfig(nSelectedID);
    
            /*
             * This notify is to fix the bug changing id on 2-axis 2-button joystick.
             */
            pDIJoyConfig->SendNotify();
    
            if( SUCCEEDED(hr) )
            {
                hr = pDIJoyConfig->SetConfig(nSelectedItem, lpSelectedID, dwFlags );
                
                if( SUCCEEDED(hr) ) {
                    pDIJoyConfig->SendNotify();
                }
            }
        } else {
            hr = pDIJoyConfig->SetConfig(nSelectedItem, lpSelectedID, dwFlags);
    
            pDIJoyConfig->SendNotify();

            if( SUCCEEDED(hr) )
            {
                hr = pDIJoyConfig->DeleteConfig(nSelectedID);
                
                pDIJoyConfig->SendNotify();

                if( nSelectedID < nSelectedItem ) {
                    pDIJoyConfig->SendNotify();
                }
            
            }
        }

        if( lpSelectedID )
            delete (lpSelectedID);
    }

    // delete both configurations
    // pointers will be NULL if the config was not found!
    if( lpSelectedItem )
    {
        DWORD dwFlags = DIJC_REGHWCONFIGTYPE |  DIJC_CALLOUT  | DIJC_GAIN | DIJC_GUIDINSTANCE;
        USES_CONVERSION;

        if( _tcsicmp(TEXT("joyhid.vxd"), W2A(lpSelectedItem->wszCallout)) == 0 )
        {
//            dwFlags |= DIJC_WDMGAMEPORT;
            dwSelectedItem = 2;  //joyhid.vxd
        } 
#if 0
        /*
         * Since MSGAME.VXD will directly write to the registry with some unhealthy data.
         * We have to change it before we move to other ID.
         */
        else if( _tcsicmp(TEXT("MSGAME.VXD"), W2A(lpSelectedItem->wszCallout)) == 0 )
        {
            lpSelectedItem->hwc.dwType += 1;
            dwSelectedItem = 3;  //msgame.vxd (Sidewinder driver)
        } 
#endif        
        else {
            dwSelectedItem = 1;  //vjoyd.vxd
        }

        if( dwSelectedItem == 1     // VJOYD.VXD,
//         || dwSelectedItem == 3 
         ){  // MSGAME.VXD
            hr = pDIJoyConfig->DeleteConfig(nSelectedItem);
    
            /*
             * This notify is to fix the bug changing id on 2-axis 2-button joystick.
             */
            pDIJoyConfig->SendNotify();
    
            if( SUCCEEDED(hr) )
            {
                hr = pDIJoyConfig->SetConfig(nSelectedID, lpSelectedItem, dwFlags );
                
                if( SUCCEEDED(hr) ) {
                    pDIJoyConfig->SendNotify();
                }
            }
        } else {
            hr = pDIJoyConfig->SetConfig(nSelectedID, lpSelectedItem, dwFlags );
    
            /*
             * This notify is to fix the bug changing id on 2-axis 2-button joystick.
             */
            pDIJoyConfig->SendNotify();
    
            if( SUCCEEDED(hr) )
            {
                hr = pDIJoyConfig->DeleteConfig(nSelectedItem);
                
                pDIJoyConfig->SendNotify();
                
                if( nSelectedID < nSelectedItem ) {
                    pDIJoyConfig->SendNotify();
                }
            
            }

        }

        if( lpSelectedItem )
        {
            delete (lpSelectedItem);
        }
    }

    // Set the hour glass
    SetCursor(LoadCursor(NULL, IDC_ARROW));
}
#endif