/*
 * ReactOS Device Manager Applet
 * Copyright (C) 2004 - 2005 ReactOS Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/* $Id$
 *
 * PROJECT:         ReactOS devmgr.dll
 * FILE:            lib/devmgr/hwpage.c
 * PURPOSE:         ReactOS Device Manager
 * PROGRAMMER:      Thomas Weidenmueller <w3seek@reactos.com>
 * UPDATE HISTORY:
 *      04-04-2004  Created
 */
#include <precomp.h>

#define NDEBUG
#include <debug.h>

typedef struct _HWDEVINFO
{
    struct _HWCLASSDEVINFO *ClassDevInfo;
    SP_DEVINFO_DATA DevInfoData;
    BOOL HideDevice;
} HWDEVINFO, *PHWDEVINFO;

typedef struct _HWCLASSDEVINFO
{
    GUID Guid;
    HDEVINFO hDevInfo;
    INT ImageIndex;
    INT ItemCount;
    PHWDEVINFO HwDevInfo;
} HWCLASSDEVINFO, *PHWCLASSDEVINFO;

typedef struct _HARDWARE_PAGE_DATA
{
    HWND hWnd;
    HWND hWndDevList;
    HINSTANCE hComCtl32; /* only save this to keep track of the references */
    INT DevListViewHeight;
    SP_CLASSIMAGELIST_DATA ClassImageListData;
    HWPAGE_DISPLAYMODE DisplayMode;

    /* parent window subclass info */
    WNDPROC ParentOldWndProc;
    HWND hWndParent;

    UINT NumberOfGuids;
    HWCLASSDEVINFO ClassDevInfo[1];
    /* struct may be dynamically expanded here! */
} HARDWARE_PAGE_DATA, *PHARDWARE_PAGE_DATA;

#define CX_TYPECOLUMN_WIDTH 80

static VOID
InitializeDevicesList(IN PHARDWARE_PAGE_DATA hpd)
{
    LVCOLUMN lvc;
    RECT rcClient;
    WCHAR szColName[255];
    int iCol = 0;

    /* set the list view style */
    (void)ListView_SetExtendedListViewStyle(hpd->hWndDevList,
                                            LVS_EX_FULLROWSELECT);

    /* set the list view image list */
    if (hpd->ClassImageListData.ImageList != NULL)
    {
        (void)ListView_SetImageList(hpd->hWndDevList,
                                    hpd->ClassImageListData.ImageList,
                                    LVSIL_SMALL);
    }

    GetClientRect(hpd->hWndDevList,
                  &rcClient);

    /* add the list view columns */
    lvc.mask = LVCF_TEXT | LVCF_WIDTH;
    lvc.fmt = LVCFMT_LEFT;
    lvc.pszText = szColName;

    if (LoadString(hDllInstance,
                   IDS_NAME,
                   szColName,
                   sizeof(szColName) / sizeof(szColName[0])))
    {
        lvc.cx = rcClient.right - CX_TYPECOLUMN_WIDTH -
                 GetSystemMetrics(SM_CXVSCROLL);
        (void)ListView_InsertColumn(hpd->hWndDevList,
                                    iCol++,
                                    &lvc);
    }
    if (LoadString(hDllInstance,
                   IDS_TYPE,
                   szColName,
                   sizeof(szColName) / sizeof(szColName[0])))
    {
        lvc.cx = CX_TYPECOLUMN_WIDTH;
        (void)ListView_InsertColumn(hpd->hWndDevList,
                                    iCol++,
                                    &lvc);
    }
}


static BOOL
DisplaySelectedDeviceProperties(IN PHARDWARE_PAGE_DATA hpd)
{
    PHWDEVINFO HwDevInfo;
    SP_DEVINFO_DATA DevInfoData;
    BOOL Ret = FALSE;

    HwDevInfo = (PHWDEVINFO)ListViewGetSelectedItemData(hpd->hWndDevList);
    if (HwDevInfo != NULL)
    {
        /* make a copy of the SP_DEVINFO_DATA structure on the stack, it may
           become invalid in case the devices are updated */
        DevInfoData = HwDevInfo->DevInfoData;

        /* display the advanced properties */
        Ret = DisplayDeviceAdvancedProperties(hpd->hWnd,
                                              NULL,
                                              HwDevInfo->ClassDevInfo->hDevInfo,
                                              &DevInfoData,
                                              hpd->hComCtl32,
                                              NULL,
                                              0) != -1;
    }

    return Ret;
}


static VOID
UpdateControlStates(IN PHARDWARE_PAGE_DATA hpd)
{
    PHWDEVINFO HwDevInfo;
    HWND hBtnTroubleShoot, hBtnProperties;

    hBtnTroubleShoot = GetDlgItem(hpd->hWnd,
                                  IDC_TROUBLESHOOT);
    hBtnProperties = GetDlgItem(hpd->hWnd,
                                IDC_PROPERTIES);

    HwDevInfo = (PHWDEVINFO)ListViewGetSelectedItemData(hpd->hWndDevList);
    if (HwDevInfo != NULL)
    {
        /* update static controls */
        WCHAR szBuffer[256];
        LPWSTR szFormatted = NULL;

        /* get the manufacturer string */
        if (GetDeviceManufacturerString(HwDevInfo->ClassDevInfo->hDevInfo,
                                        &HwDevInfo->DevInfoData,
                                        szBuffer,
                                        sizeof(szBuffer) / sizeof(szBuffer[0])) &&
            LoadAndFormatString(hDllInstance,
                                IDS_MANUFACTURER,
                                &szFormatted,
                                szBuffer) != 0)
        {
            SetDlgItemText(hpd->hWnd,
                           IDC_MANUFACTURER,
                           szFormatted);
            LocalFree((HLOCAL)szFormatted);
        }

        /* get the location string */
        if (GetDeviceLocationString(HwDevInfo->DevInfoData.DevInst,
                                    0,
                                    szBuffer,
                                    sizeof(szBuffer) / sizeof(szBuffer[0])) &&
            LoadAndFormatString(hDllInstance,
                                IDS_LOCATION,
                                &szFormatted,
                                szBuffer) != 0)
        {
            SetDlgItemText(hpd->hWnd,
                           IDC_LOCATION,
                           szFormatted);
            LocalFree((HLOCAL)szFormatted);
        }

        if (GetDeviceStatusString(HwDevInfo->DevInfoData.DevInst,
                                  NULL,
                                  szBuffer,
                                  sizeof(szBuffer) / sizeof(szBuffer[0])) &&
            LoadAndFormatString(hDllInstance,
                                IDS_STATUS,
                                &szFormatted,
                                szBuffer) != 0)
        {
            SetDlgItemText(hpd->hWnd,
                           IDC_STATUS,
                           szFormatted);
            LocalFree((HLOCAL)szFormatted);
        }
    }
    else
    {
        /* clear static controls */
        SetDlgItemText(hpd->hWnd,
                       IDC_MANUFACTURER,
                       NULL);
        SetDlgItemText(hpd->hWnd,
                       IDC_LOCATION,
                       NULL);
        SetDlgItemText(hpd->hWnd,
                       IDC_STATUS,
                       NULL);
    }

    EnableWindow(hBtnTroubleShoot,
                 HwDevInfo != NULL);
    EnableWindow(hBtnProperties,
                 HwDevInfo != NULL);
}


static VOID
FreeDevicesList(IN PHARDWARE_PAGE_DATA hpd)
{
    PHWCLASSDEVINFO ClassDevInfo, LastClassDevInfo;

    ClassDevInfo = hpd->ClassDevInfo;
    LastClassDevInfo = ClassDevInfo + hpd->NumberOfGuids;

    /* free the device info set handles and structures */
    while (ClassDevInfo != LastClassDevInfo)
    {
        if (ClassDevInfo->hDevInfo != INVALID_HANDLE_VALUE)
        {
            SetupDiDestroyDeviceInfoList(ClassDevInfo->hDevInfo);
            ClassDevInfo->hDevInfo = INVALID_HANDLE_VALUE;
        }

        ClassDevInfo->ItemCount = 0;
        ClassDevInfo->ImageIndex = 0;

        if (ClassDevInfo->HwDevInfo != NULL)
        {
            HeapFree(GetProcessHeap(),
                     0,
                     ClassDevInfo->HwDevInfo);
            ClassDevInfo->HwDevInfo = NULL;
        }

        ClassDevInfo++;
    }
}


static VOID
BuildDevicesList(IN PHARDWARE_PAGE_DATA hpd)
{
    PHWCLASSDEVINFO ClassDevInfo, LastClassDevInfo;
    SP_DEVINFO_DATA DevInfoData;

    DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

    ClassDevInfo = hpd->ClassDevInfo;
    LastClassDevInfo = ClassDevInfo + hpd->NumberOfGuids;

    while (ClassDevInfo != LastClassDevInfo)
    {
        ClassDevInfo->ImageIndex = -1;

        /* open a class device handle for the GUID we're processing */
        ClassDevInfo->hDevInfo = SetupDiGetClassDevs(&ClassDevInfo->Guid,
                                                     NULL,
                                                     hpd->hWnd,
                                                     DIGCF_PRESENT | DIGCF_PROFILE);
        if (ClassDevInfo->hDevInfo != INVALID_HANDLE_VALUE)
        {
            DWORD MemberIndex = 0;

            SetupDiGetClassImageIndex(&hpd->ClassImageListData,
                                      &ClassDevInfo->Guid,
                                      &ClassDevInfo->ImageIndex);

            /* enumerate all devices in the class */
            while (SetupDiEnumDeviceInfo(ClassDevInfo->hDevInfo,
                                         MemberIndex++,
                                         &DevInfoData))
            {
                BOOL HideDevice = FALSE;

                if (ClassDevInfo->HwDevInfo != NULL)
                {
                    PHWDEVINFO HwNewDevInfo = HeapReAlloc(GetProcessHeap(),
                                                          0,
                                                          ClassDevInfo->HwDevInfo,
                                                          (ClassDevInfo->ItemCount + 1) *
                                                              sizeof(HWDEVINFO));
                    if (HwNewDevInfo != NULL)
                    {
                        ClassDevInfo->HwDevInfo = HwNewDevInfo;
                    }
                    else
                    {
                        DPRINT1("Unable to allocate memory for %d SP_DEVINFO_DATA structures!\n",
                                ClassDevInfo->ItemCount + 1);
                        break;
                    }
                }
                else
                {
                    ClassDevInfo->HwDevInfo = HeapAlloc(GetProcessHeap(),
                                                        0,
                                                        sizeof(HWDEVINFO));
                    if (ClassDevInfo->HwDevInfo == NULL)
                    {
                        DPRINT1("Unable to allocate memory for a SP_DEVINFO_DATA structures!\n");
                        break;
                    }
                }

                /* Find out if the device should be hidden by default */
                IsDeviceHidden(DevInfoData.DevInst,
                               NULL,
                               &HideDevice);

                /* save all information for the current device */
                ClassDevInfo->HwDevInfo[ClassDevInfo->ItemCount].ClassDevInfo = ClassDevInfo;
                ClassDevInfo->HwDevInfo[ClassDevInfo->ItemCount].DevInfoData = DevInfoData;
                ClassDevInfo->HwDevInfo[ClassDevInfo->ItemCount++].HideDevice = HideDevice;
            }
        }

        ClassDevInfo++;
    }
}


static BOOL
DeviceIdMatch(IN HDEVINFO DeviceInfoSet,
              IN PSP_DEVINFO_DATA DeviceInfoData,
              IN LPCWSTR lpDeviceId)
{
    DWORD DevIdLen;
    LPWSTR lpQueriedDeviceId;
    BOOL Ret = FALSE;

    if (!SetupDiGetDeviceInstanceId(DeviceInfoSet,
                                    DeviceInfoData,
                                    NULL,
                                    0,
                                    &DevIdLen) &&
        GetLastError() == ERROR_INSUFFICIENT_BUFFER)
    {
        if (DevIdLen == wcslen(lpDeviceId) + 1)
        {
            lpQueriedDeviceId = HeapAlloc(GetProcessHeap(),
                                          0,
                                          DevIdLen * sizeof(WCHAR));
            if (lpQueriedDeviceId != NULL)
            {
                if (SetupDiGetDeviceInstanceId(DeviceInfoSet,
                                               DeviceInfoData,
                                               lpQueriedDeviceId,
                                               DevIdLen,
                                               NULL))
                {
                    Ret = (wcscmp(lpDeviceId,
                                  lpQueriedDeviceId) == 0);
                }

                HeapFree(GetProcessHeap(),
                         0,
                         lpQueriedDeviceId);
            }
        }
    }

    return Ret;
}


static VOID
FillDevicesListViewControl(IN PHARDWARE_PAGE_DATA hpd,
                           IN LPCWSTR lpSelectDeviceId  OPTIONAL,
                           IN GUID *SelectedClassGuid  OPTIONAL)
{
    PHWCLASSDEVINFO ClassDevInfo, LastClassDevInfo;
    PHWDEVINFO HwDevInfo, LastHwDevInfo;
    WCHAR szBuffer[255];
    BOOL SelectedInClass;
    INT ItemCount = 0;

    BuildDevicesList(hpd);

    ClassDevInfo = hpd->ClassDevInfo;
    LastClassDevInfo = ClassDevInfo + hpd->NumberOfGuids;

    while (ClassDevInfo != LastClassDevInfo)
    {
        if (ClassDevInfo->HwDevInfo != NULL)
        {
            HwDevInfo = ClassDevInfo->HwDevInfo;
            LastHwDevInfo = HwDevInfo + ClassDevInfo->ItemCount;

            SelectedInClass = (SelectedClassGuid != NULL &&
                               IsEqualGUID(SelectedClassGuid,
                                           &ClassDevInfo->Guid));
            while (HwDevInfo != LastHwDevInfo)
            {
                INT iItem;
                LVITEM li = {0};

                /* get the device name */
                if (!HwDevInfo->HideDevice &&
                    GetDeviceDescriptionString(ClassDevInfo->hDevInfo,
                                               &HwDevInfo->DevInfoData,
                                               szBuffer,
                                               sizeof(szBuffer) / sizeof(szBuffer[0])))
                {
                    li.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT | LVIF_IMAGE;
                    li.iItem = ItemCount;
                    if ((ItemCount == 0 && lpSelectDeviceId == NULL) ||
                        (SelectedInClass &&
                         DeviceIdMatch(ClassDevInfo->hDevInfo,
                                       &HwDevInfo->DevInfoData,
                                       lpSelectDeviceId)))
                    {
                        li.state = LVIS_SELECTED;
                    }
                    li.stateMask = LVIS_SELECTED;
                    li.pszText = szBuffer;
                    li.iImage = ClassDevInfo->ImageIndex;
                    li.lParam = (LPARAM)HwDevInfo;

                    iItem = ListView_InsertItem(hpd->hWndDevList,
                                                &li);
                    if (iItem != -1)
                    {
                        ItemCount++;

                        /* get the device type for the second column */
                        if (GetDeviceTypeString(&HwDevInfo->DevInfoData,
                                                szBuffer,
                                                sizeof(szBuffer) / sizeof(szBuffer[0])))
                        {
                            li.mask = LVIF_TEXT;
                            li.iItem = iItem;
                            li.iSubItem = 1;

                            (void)ListView_SetItem(hpd->hWndDevList,
                                                   &li);
                        }
                    }
                }

                HwDevInfo++;
            }
        }

        ClassDevInfo++;
    }

    /* update the controls */
    UpdateControlStates(hpd);
}


static VOID
UpdateDevicesListViewControl(IN PHARDWARE_PAGE_DATA hpd)
{
    PHWDEVINFO HwDevInfo;
    GUID SelectedClassGuid = {0};
    LPWSTR lpDeviceId = NULL;

    /* if a device currently is selected, remember the device id so we can
       select the device after the update if still present */
    HwDevInfo = (PHWDEVINFO)ListViewGetSelectedItemData(hpd->hWndDevList);
    if (HwDevInfo != NULL)
    {
        DWORD DevIdLen;
        if (!SetupDiGetDeviceInstanceId(HwDevInfo->ClassDevInfo->hDevInfo,
                                        &HwDevInfo->DevInfoData,
                                        NULL,
                                        0,
                                        &DevIdLen) &&
            GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            SelectedClassGuid = HwDevInfo->DevInfoData.ClassGuid;
            lpDeviceId = HeapAlloc(GetProcessHeap(),
                                   0,
                                   DevIdLen * sizeof(WCHAR));
            if (lpDeviceId != NULL &&
                !SetupDiGetDeviceInstanceId(HwDevInfo->ClassDevInfo->hDevInfo,
                                            &HwDevInfo->DevInfoData,
                                            lpDeviceId,
                                            DevIdLen,
                                            NULL))
            {
                HeapFree(GetProcessHeap(),
                         0,
                         lpDeviceId);
                lpDeviceId = NULL;
            }
        }
    }

    /* clear the devices list view control */
    (void)ListView_DeleteAllItems(hpd->hWndDevList);

    /* free the device list */
    FreeDevicesList(hpd);

    /* build rebuild the device list and fill the list box again */
    FillDevicesListViewControl(hpd,
                               lpDeviceId,
                               (lpDeviceId != NULL ?
                                    &SelectedClassGuid :
                                    NULL));

    if (lpDeviceId != NULL)
    {
        HeapFree(GetProcessHeap(),
                 0,
                 lpDeviceId);
    }
}


static LRESULT
CALLBACK
ParentSubWndProc(IN HWND hwnd,
                 IN UINT uMsg,
                 IN WPARAM wParam,
                 IN LPARAM lParam)
{
    PHARDWARE_PAGE_DATA hpd;

    hpd = (PHARDWARE_PAGE_DATA)GetProp(hwnd,
                                       L"DevMgrSubClassInfo");
    if (hpd != NULL)
    {
        if (uMsg == WM_SIZE)
        {
            /* resize the hardware page */
            SetWindowPos(hpd->hWnd,
                         NULL,
                         0,
                         0,
                         LOWORD(lParam),
                         HIWORD(lParam),
                         SWP_NOZORDER);
        }
        else if (uMsg == WM_DEVICECHANGE && IsWindowVisible(hpd->hWnd))
        {
            /* forward a WM_DEVICECHANGE message to the hardware
               page which wouldn't get the message itself as it is
               a child window */
            SendMessage(hpd->hWnd,
                        WM_DEVICECHANGE,
                        wParam,
                        lParam);
        }

        /* pass the message the the old window proc */
        return CallWindowProc(hpd->ParentOldWndProc,
                              hwnd,
                              uMsg,
                              wParam,
                              lParam);
    }
    else
    {
        /* this is not a good idea if the subclassed window was an ansi
           window, but we failed finding out the previous window proc
           so we can't use CallWindowProc. This should rarely - if ever -
           happen. */

        return DefWindowProc(hwnd,
                             uMsg,
                             wParam,
                             lParam);
    }
}


static VOID
HardwareDlgResize(IN PHARDWARE_PAGE_DATA hpd,
                  IN INT cx,
                  IN INT cy)
{
    HDWP dwp;
    HWND hControl, hButton;
    INT Width, x, y;
    RECT rc, rcButton;
    POINT pt = {0};
    POINT ptMargin = {0};
    POINT ptMarginGroup = {0};

    /* use left margin of the IDC_DEVICES label as the right
       margin of all controls outside the group box */
    hControl = GetDlgItem(hpd->hWnd,
                          IDC_DEVICES);
    GetWindowRect(hControl,
                  &rc);
    MapWindowPoints(hControl,
                    hpd->hWnd,
                    &ptMargin,
                    1);

    Width = cx - (2 * ptMargin.x);

    if ((dwp = BeginDeferWindowPos(8)))
    {
        /* rc already has the window rect of IDC_DEVICES! */
        if (!(dwp = DeferWindowPos(dwp,
                                   hControl,
                                   NULL,
                                   0,
                                   0,
                                   Width,
                                   rc.bottom - rc.top,
                                   SWP_NOMOVE | SWP_NOZORDER)))
        {
            return;
        }

        /* resize the devices list view control */
        GetWindowRect(hpd->hWndDevList,
                      &rc);
        MapWindowPoints(hpd->hWndDevList,
                        hpd->hWnd,
                        &pt,
                        1);
        y = pt.y + hpd->DevListViewHeight + ptMargin.y;
        if (!(dwp = DeferWindowPos(dwp,
                                   hpd->hWndDevList,
                                   NULL,
                                   0,
                                   0,
                                   Width,
                                   hpd->DevListViewHeight,
                                   SWP_NOMOVE | SWP_NOZORDER)))
        {
            return;
        }

        /* resize the group box control */
        hControl = GetDlgItem(hpd->hWnd,
                              IDC_PROPERTIESGROUP);
        GetWindowRect(hControl,
                      &rc);
        if (!(dwp = DeferWindowPos(dwp,
                                   hControl,
                                   NULL,
                                   ptMargin.x,
                                   y,
                                   Width,
                                   cy - y - ptMargin.y,
                                   SWP_NOZORDER)))
        {
            return;
        }

        /* use left margin of the IDC_MANUFACTURER label as the right
           margin of all controls inside the group box */
        hControl = GetDlgItem(hpd->hWnd,
                              IDC_MANUFACTURER);
        GetWindowRect(hControl,
                      &rc);
        MapWindowPoints(hControl,
                        hpd->hWnd,
                        &ptMarginGroup,
                        1);

        ptMarginGroup.y = ptMargin.y * 2;
        Width = cx - (2 * ptMarginGroup.x);
        y += ptMarginGroup.y;
        if (!(dwp = DeferWindowPos(dwp,
                                   hControl,
                                   NULL,
                                   ptMarginGroup.x,
                                   y,
                                   Width,
                                   rc.bottom - rc.top,
                                   SWP_NOZORDER)))
        {
            return;
        }
        y += rc.bottom - rc.top + (ptMargin.y / 2);

        /* resize the IDC_LOCATION label */
        hControl = GetDlgItem(hpd->hWnd,
                              IDC_LOCATION);
        GetWindowRect(hControl,
                      &rc);
        if (!(dwp = DeferWindowPos(dwp,
                                   hControl,
                                   NULL,
                                   ptMarginGroup.x,
                                   y,
                                   Width,
                                   rc.bottom - rc.top,
                                   SWP_NOZORDER)))
        {
            return;
        }
        y += rc.bottom - rc.top + (ptMargin.y / 2);

        /* measure the size of the buttons */
        hButton = GetDlgItem(hpd->hWnd,
                             IDC_PROPERTIES);
        GetWindowRect(hButton,
                      &rcButton);

        /* resize the IDC_STATUS label */
        hControl = GetDlgItem(hpd->hWnd,
                              IDC_STATUS);
        GetWindowRect(hControl,
                      &rc);
        if (!(dwp = DeferWindowPos(dwp,
                                   hControl,
                                   NULL,
                                   ptMarginGroup.x,
                                   y,
                                   Width,
                                   cy - y - (3 * ptMargin.y) -
                                       (rcButton.bottom - rcButton.top),
                                   SWP_NOZORDER)))
        {
            return;
        }

        /* move the IDC_PROPERTIES button */
        y = cy - (2 * ptMargin.y) - (rcButton.bottom - rcButton.top);
        x = cx - ptMarginGroup.x - (rcButton.right - rcButton.left);
        if (!(dwp = DeferWindowPos(dwp,
                                   hButton,
                                   NULL,
                                   x,
                                   y,
                                   0,
                                   0,
                                   SWP_NOSIZE | SWP_NOZORDER)))
        {
            return;
        }

        /* move the IDC_TROUBLESHOOT button */
        hButton = GetDlgItem(hpd->hWnd,
                             IDC_TROUBLESHOOT);
        GetWindowRect(hButton,
                      &rcButton);
        x -= (ptMargin.x / 2) + (rcButton.right - rcButton.left);
        if (!(dwp = DeferWindowPos(dwp,
                                   hButton,
                                   NULL,
                                   x,
                                   y,
                                   0,
                                   0,
                                   SWP_NOSIZE | SWP_NOZORDER)))
        {
            return;
        }

        EndDeferWindowPos(dwp);
    }
}


static VOID
EnableTroubleShoot(PHARDWARE_PAGE_DATA hpd,
                   BOOL Enable)
{
    HWND hBtnTroubleShoot = GetDlgItem(hpd->hWnd,
                                       IDC_TROUBLESHOOT);

    ShowWindow(hBtnTroubleShoot,
               Enable ? SW_SHOW : SW_HIDE);
}


static INT_PTR
CALLBACK
HardwareDlgProc(IN HWND hwndDlg,
                IN UINT uMsg,
                IN WPARAM wParam,
                IN LPARAM lParam)
{
    PHARDWARE_PAGE_DATA hpd;
    INT_PTR Ret = FALSE;

    hpd = (PHARDWARE_PAGE_DATA)GetWindowLongPtr(hwndDlg,
                                                DWL_USER);

    if (hpd != NULL || uMsg == WM_INITDIALOG)
    {
        switch (uMsg)
        {
            case WM_NOTIFY:
            {
                NMHDR *pnmh = (NMHDR*)lParam;
                if (pnmh->hwndFrom == hpd->hWndDevList)
                {
                    switch (pnmh->code)
                    {
                        case LVN_ITEMCHANGED:
                        {
                            LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;

                            if ((pnmv->uChanged & LVIF_STATE) &&
                                ((pnmv->uOldState & (LVIS_FOCUSED | LVIS_SELECTED)) ||
                                 (pnmv->uNewState & (LVIS_FOCUSED | LVIS_SELECTED))))
                            {
                                UpdateControlStates(hpd);
                            }
                            break;
                        }

                        case NM_DBLCLK:
                        {
                            DisplaySelectedDeviceProperties(hpd);
                            break;
                        }
                    }
                }
                break;
            }

            case WM_COMMAND:
            {
                switch (LOWORD(wParam))
                {
                    case IDC_TROUBLESHOOT:
                    {
                        /* FIXME - start the help using the command in the window text */
                        break;
                    }

                    case IDC_PROPERTIES:
                    {
                        DisplaySelectedDeviceProperties(hpd);
                        break;
                    }
                }
                break;
            }

            case WM_SIZE:
                HardwareDlgResize(hpd,
                                  (INT)LOWORD(lParam),
                                  (INT)HIWORD(lParam));
                break;

            case WM_SETTEXT:
            {
                LPCWSTR szWndText = (LPCWSTR)lParam;
                EnableTroubleShoot(hpd,
                                   (szWndText != NULL && szWndText[0] != L'\0'));
                break;
            }

            case WM_DEVICECHANGE:
            {
                /* FIXME - don't call UpdateDevicesListViewControl for all events */
                UpdateDevicesListViewControl(hpd);
                Ret = TRUE;
                break;
            }

            case WM_INITDIALOG:
            {
                hpd = (PHARDWARE_PAGE_DATA)lParam;
                if (hpd != NULL)
                {
                    HWND hWndParent;

                    hpd->hWnd = hwndDlg;
                    SetWindowLongPtr(hwndDlg,
                                     DWL_USER,
                                     (DWORD_PTR)hpd);

                    hpd->ClassImageListData.cbSize = sizeof(SP_CLASSIMAGELIST_DATA);

                    SetupDiGetClassImageList(&hpd->ClassImageListData);

                    /* calculate the size of the devices list view control */
                    hpd->hWndDevList = GetDlgItem(hwndDlg,
                                                  IDC_LV_DEVICES);
                    if (hpd->hWndDevList != NULL)
                    {
                        RECT rcClient;
                        GetClientRect(hpd->hWndDevList,
                                      &rcClient);
                        hpd->DevListViewHeight = rcClient.bottom;

                        if (hpd->DisplayMode == HWPD_LARGELIST)
                        {
                            hpd->DevListViewHeight = (hpd->DevListViewHeight * 3) / 2;
                        }
                    }

                    /* subclass the parent window */
                    hWndParent = GetAncestor(hwndDlg,
                                             GA_PARENT);
                    if (hWndParent != NULL)
                    {
                        RECT rcClient;

                        if (GetClientRect(hWndParent,
                                          &rcClient) &&
                            SetWindowPos(hwndDlg,
                                         NULL,
                                         0,
                                         0,
                                         rcClient.right,
                                         rcClient.bottom,
                                         SWP_NOZORDER))
                        {
                            /* subclass the parent window. This is not safe
                               if the parent window belongs to another thread! */
                            hpd->ParentOldWndProc = (WNDPROC)SetWindowLongPtr(hWndParent,
                                                                              GWLP_WNDPROC,
                                                                              (LONG_PTR)ParentSubWndProc);

                            if (hpd->ParentOldWndProc != NULL &&
                                SetProp(hWndParent,
                                        L"DevMgrSubClassInfo",
                                        (HANDLE)hpd))
                            {
                                hpd->hWndParent = hWndParent;
                            }
                        }
                    }

                    /* initialize the devices list view control */
                    InitializeDevicesList(hpd);

                    /* fill the devices list view control */
                    FillDevicesListViewControl(hpd,
                                               NULL,
                                               NULL);

                    /* decide whether to show or hide the troubleshoot button */
                    EnableTroubleShoot(hpd,
                                       GetWindowTextLength(hwndDlg) != 0);
                }
                Ret = TRUE;
                break;
            }

            case WM_DESTROY:
            {
                /* free devices list */
                FreeDevicesList(hpd);

                /* restore the old window proc of the subclassed parent window */
                if (hpd->hWndParent != NULL && hpd->ParentOldWndProc != NULL)
                {
                    SetWindowLongPtr(hpd->hWndParent,
                                     GWLP_WNDPROC,
                                     (LONG_PTR)hpd->ParentOldWndProc);
                }

                if (hpd->ClassImageListData.ImageList != NULL)
                {
                    SetupDiDestroyClassImageList(&hpd->ClassImageListData);
                }

                /* free the reference to comctl32 */
                FreeLibrary(hpd->hComCtl32);
                hpd->hComCtl32 = NULL;

                /* free the allocated resources */
                HeapFree(GetProcessHeap(),
                         0,
                         hpd);
                break;
            }
        }
    }

    return Ret;
}


/***************************************************************************
 * NAME                                                         EXPORTED
 *      DeviceCreateHardwarePageEx
 *
 * DESCRIPTION
 *   Creates a hardware page
 *
 * ARGUMENTS
 *   hWndParent:     Handle to the parent window
 *   lpGuids:        An array of guids of devices that are to be listed
 *   uNumberOfGuids: Numbers of guids in the Guids array
 *   DisplayMode:    Sets the size of the device list view control
 *
 * RETURN VALUE
 *   Returns the handle of the hardware page window that has been created or
 *   NULL if it failed.
 *
 * @implemented
 */
HWND
WINAPI
DeviceCreateHardwarePageEx(IN HWND hWndParent,
                           IN LPGUID lpGuids,
                           IN UINT uNumberOfGuids,
                           IN HWPAGE_DISPLAYMODE DisplayMode)
{
    PHARDWARE_PAGE_DATA hpd;

    /* allocate the HARDWARE_PAGE_DATA structure. Make sure it is
       zeroed because the initialization code assumes that in
       failure cases! */
    hpd = HeapAlloc(GetProcessHeap(),
                    HEAP_ZERO_MEMORY,
                    FIELD_OFFSET(HARDWARE_PAGE_DATA,
                                 ClassDevInfo[uNumberOfGuids]));
    if (hpd != NULL)
    {
        HWND hWnd;
        UINT i;

        hpd->DisplayMode = ((DisplayMode > HWPD_MAX) ? HWPD_STANDARDLIST : DisplayMode);

        /* initialize the HARDWARE_PAGE_DATA structure */
        hpd->NumberOfGuids = uNumberOfGuids;
        for (i = 0;
             i < uNumberOfGuids;
             i++)
        {
            hpd->ClassDevInfo[i].hDevInfo = INVALID_HANDLE_VALUE;
            hpd->ClassDevInfo[i].Guid = lpGuids[i];
        }

        /* load comctl32.dll dynamically */
        hpd->hComCtl32 = LoadAndInitComctl32();
        if (hpd->hComCtl32 == NULL)
        {
            goto Cleanup;
        }

        /* create the dialog */
        hWnd = CreateDialogParam(hDllInstance,
                                 MAKEINTRESOURCE(IDD_HARDWARE),
                                 hWndParent,
                                 HardwareDlgProc,
                                 (LPARAM)hpd);
        if (hWnd != NULL)
        {
            return hWnd;
        }
        else
        {
Cleanup:
            /* oops, something went wrong... */
            if (hpd->hComCtl32 != NULL)
            {
                FreeLibrary(hpd->hComCtl32);
            }

            HeapFree(GetProcessHeap(),
                     0,
                     hpd);
        }
    }

    return NULL;
}


/***************************************************************************
 * NAME                                                         EXPORTED
 *      DeviceCreateHardwarePage
 *
 * DESCRIPTION
 *   Creates a hardware page
 *
 * ARGUMENTS
 *   hWndParent: Handle to the parent window
 *   lpGuid:     Guid of the device
 *
 * RETURN VALUE
 *   Returns the handle of the hardware page window that has been created or
 *   NULL if it failed.
 *
 * @implemented
 */
HWND
WINAPI
DeviceCreateHardwarePage(IN HWND hWndParent,
                         IN LPGUID lpGuid)
{
    return DeviceCreateHardwarePageEx(hWndParent,
                                      lpGuid,
                                      1,
                                      HWPD_LARGELIST);
}
