/*******************************************************************************
 *
 *  (C) COPYRIGHT MICROSOFT CORPORATION, 1998
 *
 *  TITLE:       MISCUTIL.CPP
 *
 *  VERSION:     1.0
 *
 *  AUTHOR:      ShaunIv
 *
 *  DATE:        5/28/1998
 *
 *  DESCRIPTION: Various utility functions we use in more than one place
 *
 *******************************************************************************/

#include "precomp.h"
#pragma hdrstop
#include <advpub.h>  // For RegInstall and related data structures
#include <windowsx.h>  // For RegInstall and related data structures
#include "wiaffmt.h"
#include "shellext.h"

namespace WiaUiUtil
{

    LONG Align( LONG n , LONG m )
    {
        return(n % m) ? (((n/m)+1)*m) : (n);
    }

    /*
     * StringToLong: Convert a string to a long. ASCII Arabic numerals only
     */
    LONG StringToLong( LPCTSTR pszStr )
    {
        LPTSTR pstr = (LPTSTR)pszStr;
        bool bNeg = (*pstr == TEXT('-'));
        if (bNeg)
            pstr++;
        LONG nTotal = 0;
        while (*pstr && *pstr >= TEXT('0') && *pstr <= TEXT('9'))
        {
            nTotal *= 10;
            nTotal += *pstr - TEXT('0');
            ++pstr;
        }
        return(bNeg ? -nTotal : nTotal);
    }

    SIZE MapDialogSize( HWND hwnd, const SIZE &size )
    {
        RECT rcTmp;
        rcTmp.left = rcTmp.top = 0;
        rcTmp.right = size.cx;
        rcTmp.bottom = size.cy;
        MapDialogRect( hwnd, &rcTmp );
        SIZE sizeTmp;
        sizeTmp.cx = rcTmp.right;
        sizeTmp.cy = rcTmp.bottom;
        return (sizeTmp);
    }

    /*******************************************************************************
    *
    *  GetBmiSize
    *
    *  DESCRIPTION:
    *   Should never get biCompression == BI_RLE.
    *
    *  PARAMETERS:
    *
    *******************************************************************************/
    LONG GetBmiSize(PBITMAPINFO pbmi)
    {
        WIA_PUSH_FUNCTION((TEXT("WiaUiUtil::GetBmiSize(0x%p)"), pbmi ));
        // determine the size of bitmapinfo
        LONG lSize = pbmi->bmiHeader.biSize;

        // no color table cases
        if (
           (pbmi->bmiHeader.biBitCount == 24) ||
           ((pbmi->bmiHeader.biBitCount == 32) &&
            (pbmi->bmiHeader.biCompression == BI_RGB)))
        {

            // no colors unless stated
            lSize += sizeof(RGBQUAD) * pbmi->bmiHeader.biClrUsed;
            return(lSize);
        }

        // bitfields cases
        if (((pbmi->bmiHeader.biBitCount == 32) &&
             (pbmi->bmiHeader.biCompression == BI_BITFIELDS)) ||
            (pbmi->bmiHeader.biBitCount == 16))
        {

            lSize += 3 * sizeof(RGBQUAD);
            return(lSize);
        }

        // palette cases
        if (pbmi->bmiHeader.biBitCount == 1)
        {

            LONG lPal = pbmi->bmiHeader.biClrUsed;

            if ((lPal == 0) || (lPal > 2))
            {
                lPal = 2;
            }

            lSize += lPal * sizeof(RGBQUAD);
            return(lSize);
        }

        // palette cases
        if (pbmi->bmiHeader.biBitCount == 4)
        {

            LONG lPal = pbmi->bmiHeader.biClrUsed;

            if ((lPal == 0) || (lPal > 16))
            {
                lPal = 16;
            }

            lSize += lPal * sizeof(RGBQUAD);
            return(lSize);
        }

        // palette cases
        if (pbmi->bmiHeader.biBitCount == 8)
        {

            LONG lPal = pbmi->bmiHeader.biClrUsed;

            if ((lPal == 0) || (lPal > 256))
            {
                lPal = 256;
            }

            lSize += lPal * sizeof(RGBQUAD);
            return(lSize);
        }

        // error
        return(0);
    }

    // Simple wrapper for MsgWaitForMultipleObjects
    bool MsgWaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds )
    {
        bool bEventOccurred = false;
        const int nCount = 1;
        while (true)
        {
            DWORD dwRes = MsgWaitForMultipleObjects(nCount,&hHandle,FALSE,dwMilliseconds,QS_ALLINPUT|QS_ALLPOSTMESSAGE);
            if (WAIT_OBJECT_0==dwRes)
            {
                // The handle was signalled, so we can break out of our loop, returning true
                bEventOccurred = true;
                break;
            }
            else if (WAIT_OBJECT_0+nCount==dwRes)
            {
                // pull all of the messages out of the queue and process them
                MSG msg;
                while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
                {
                    if (msg.message == WM_QUIT)
                        break;
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            else
            {
                // The handle either timed out, or the mutex was abandoned, so we can break out of our loop, returning false
                break;
            }
        }
        return bEventOccurred;
    }

    void CenterWindow( HWND hWnd, HWND hWndParent )
    {
        if (IsWindow(hWnd))
        {

            if (!hWndParent)
            {
                //
                // If the window to be centered on is NULL, use the desktop window
                //
                hWndParent = GetDesktopWindow();
            }
            else
            {
                //
                // If the window to be centered on is minimized, use the desktop window
                //
                DWORD dwStyle = GetWindowLong(hWndParent, GWL_STYLE);
                if (dwStyle & WS_MINIMIZE)
                {
                    hWndParent = GetDesktopWindow();
                }
            }

            //
            // Get the window rects
            //
            RECT rcParent, rcCurrent;
            GetWindowRect( hWndParent, &rcParent );
            GetWindowRect( hWnd, &rcCurrent );

            //
            // Get the desired coordinates for the upper-left hand corner
            //
            RECT rcFinal;
            rcFinal.left = rcParent.left + (RectWidth(rcParent) - RectWidth(rcCurrent))/2;
            rcFinal.top = rcParent.top + (RectHeight(rcParent) - RectHeight(rcCurrent))/2;
            rcFinal.right = rcFinal.left + RectWidth(rcCurrent);
            rcFinal.bottom = rcFinal.top + RectHeight(rcCurrent);

            //
            // Make sure we're not off the screen
            //
            HMONITOR hMonitor = MonitorFromRect( &rcFinal, MONITOR_DEFAULTTONEAREST );
            if (hMonitor)
            {
                MONITORINFO MonitorInfo;
                ZeroMemory( &MonitorInfo, sizeof(MonitorInfo) );
                MonitorInfo.cbSize = sizeof(MonitorInfo);
                //
                // Get the screen coordinates of this monitor
                //
                if (GetMonitorInfo(hMonitor, &MonitorInfo))
                {
                    //
                    // Ensure the window is in the working area's region
                    //
                    rcFinal.left = Max<int>(MonitorInfo.rcWork.left, Min<int>( MonitorInfo.rcWork.right - RectWidth(rcCurrent), rcFinal.left ));
                    rcFinal.top = Max<int>(MonitorInfo.rcWork.top, Min<int>( MonitorInfo.rcWork.bottom - RectHeight(rcCurrent), rcFinal.top ));
                }
            }

            // Move it
            SetWindowPos( hWnd, NULL, rcFinal.left, rcFinal.top, 0, 0, SWP_NOSIZE|SWP_NOACTIVATE|SWP_NOZORDER );
        }
    }


    // Flip an image horizontally
    bool FlipImage( PBYTE pBits, LONG nWidth, LONG nHeight, LONG nBitDepth )
    {
        bool bResult = false;
        if (pBits && nWidth>=0 && nHeight>=0 && nBitDepth>=0)
        {
            LONG nLineWidthInBytes = WiaUiUtil::Align(nWidth*nBitDepth,sizeof(DWORD)*8)/8;
            PBYTE pTempLine = new BYTE[nLineWidthInBytes];
            if (pTempLine)
            {
                for (int i=0;i<nHeight/2;i++)
                {
                    PBYTE pSrc = pBits + (i * nLineWidthInBytes);
                    PBYTE pDst = pBits + ((nHeight-i-1) * nLineWidthInBytes);
                    CopyMemory( pTempLine, pSrc, nLineWidthInBytes );
                    CopyMemory( pSrc, pDst, nLineWidthInBytes );
                    CopyMemory( pDst, pTempLine, nLineWidthInBytes );
                }
                bResult = true;
            }
            delete[] pTempLine;
        }
        return bResult;
    }

    HRESULT InstallInfFromResource( HINSTANCE hInstance, LPCSTR pszSectionName )
    {
        HRESULT hr;
        HINSTANCE hInstAdvPackDll = LoadLibrary(TEXT("ADVPACK.DLL"));
        if (hInstAdvPackDll)
        {
            REGINSTALL pfnRegInstall = reinterpret_cast<REGINSTALL>(GetProcAddress( hInstAdvPackDll, "RegInstall" ));
            if (pfnRegInstall)
            {
#if defined(WINNT)
                STRENTRY astrEntry[] =
                {
                    { "25", "%SystemRoot%"           },
                    { "11", "%SystemRoot%\\system32" }
                };
                STRTABLE strTable = { sizeof(astrEntry)/sizeof(astrEntry[0]), astrEntry };
                hr = pfnRegInstall(hInstance, pszSectionName, &strTable);
#else
                hr = pfnRegInstall(hInstance, pszSectionName, NULL);
#endif
            } else hr = HRESULT_FROM_WIN32(GetLastError());
            FreeLibrary(hInstAdvPackDll);
        } else hr = HRESULT_FROM_WIN32(GetLastError());
        return hr;
    }


    /******************************************************************************

    WriteDIBToFile

    Writes a DIB to a file.

    ******************************************************************************/
    HRESULT
    WriteDIBToFile( HBITMAP hDib, HANDLE hFile )
    {
        if (!hDib)
        {
            return E_INVALIDARG;
        }

        // Make sure this is a valid DIB and get this useful info.
        DIBSECTION ds;
        if (!GetObject( hDib, sizeof(DIBSECTION), &ds ))
        {
            return E_INVALIDARG;
        }

        // We only deal with DIBs
        if (ds.dsBm.bmPlanes != 1)
        {
            return E_INVALIDARG;
        }

        // Calculate some color table sizes
        int nColors = ds.dsBmih.biBitCount <= 8 ? 1 << ds.dsBmih.biBitCount : 0;
        int nBitfields = ds.dsBmih.biCompression == BI_BITFIELDS ? 3 : 0;

        // Calculate the data size
        int nImageDataSize = ds.dsBmih.biSizeImage ? ds.dsBmih.biSizeImage : ds.dsBm.bmWidthBytes * ds.dsBm.bmHeight;

        // Get the color table (if needed)
        RGBQUAD rgbqaColorTable[256] = {0};
        if (nColors)
        {
            HDC hDC = CreateCompatibleDC(NULL);
            if (hDC)
            {
                HBITMAP hOldBitmap = reinterpret_cast<HBITMAP>(SelectObject(hDC,hDib));
                GetDIBColorTable( hDC, 0, nColors, rgbqaColorTable );
                SelectObject(hDC,hOldBitmap);
                DeleteDC( hDC );
            }
        }

        // Create the file header
        BITMAPFILEHEADER bmfh;
        bmfh.bfType = 'MB';
        bmfh.bfSize = 0;
        bmfh.bfReserved1 = 0;
        bmfh.bfReserved2 = 0;
        bmfh.bfOffBits = sizeof(bmfh) + sizeof(ds.dsBmih) + nBitfields*sizeof(DWORD) + nColors*sizeof(RGBQUAD);

        // Start writing!  Note that we write out the bitfields and the color table.  Only one,
        // at most, will actually result in data being written
        DWORD dwBytesWritten;
        if (!WriteFile( hFile, &bmfh, sizeof(bmfh), &dwBytesWritten, NULL ))
            return HRESULT_FROM_WIN32(GetLastError());
        if (!WriteFile( hFile, &ds.dsBmih, sizeof(ds.dsBmih), &dwBytesWritten, NULL ))
            return HRESULT_FROM_WIN32(GetLastError());
        if (!WriteFile( hFile, &ds.dsBitfields, nBitfields*sizeof(DWORD), &dwBytesWritten, NULL ))
            return HRESULT_FROM_WIN32(GetLastError());
        if (!WriteFile( hFile, rgbqaColorTable, nColors*sizeof(RGBQUAD), &dwBytesWritten, NULL ))
            return HRESULT_FROM_WIN32(GetLastError());
        if (!WriteFile( hFile, ds.dsBm.bmBits, nImageDataSize, &dwBytesWritten, NULL ))
            return HRESULT_FROM_WIN32(GetLastError());
        return S_OK;
    }


    HFONT ChangeFontFromWindow( HWND hWnd, int nPointSizeDelta )
    {
        HFONT hFontResult = NULL;

        //
        // Get the window's font
        //
        HFONT hFont = GetFontFromWindow(hWnd);
        if (hFont)
        {
            LOGFONT LogFont = {0};
            if (GetObject( hFont, sizeof(LogFont), &LogFont ))
            {
                HDC hDC = GetDC(hWnd);
                if (hDC)
                {
                    HFONT hOldFont = SelectFont(hDC,hFont);
                    TEXTMETRIC TextMetric = {0};
                    if (GetTextMetrics( hDC, &TextMetric ))
                    {
                        //
                        // Get the current font's point size
                        //
                        int nPointSize = MulDiv( TextMetric.tmHeight-TextMetric.tmInternalLeading, 72, GetDeviceCaps(hDC, LOGPIXELSY) ) + nPointSizeDelta;

                        //
                        // Calculate the height of the new font
                        //
                        LogFont.lfHeight = -MulDiv(nPointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

                        //
                        // Create the font
                        //
                        hFontResult = CreateFontIndirect( &LogFont );
                    }

                    if (hOldFont)
                    {
                        SelectFont( hDC, hOldFont );
                    }

                    ReleaseDC( hWnd, hDC );
                }
            }
        }
        return hFontResult;
    }

    HFONT GetFontFromWindow( HWND hWnd )
    {
        //
        // Get the window's font
        //
        HFONT hFontResult = reinterpret_cast<HFONT>(SendMessage(hWnd,WM_GETFONT,0,0));
        if (!hFontResult)
        {
            hFontResult = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
        }
        return hFontResult;
    }



    HFONT CreateFontWithPointSizeFromWindow( HWND hWnd, int nPointSize, bool bBold, bool bItalic )
    {
        HFONT hFontResult = NULL;
        HFONT hFont = GetFontFromWindow(hWnd);
        if (hFont)
        {
            LOGFONT LogFont = {0};
            if (GetObject( hFont, sizeof(LogFont), &LogFont ))
            {
                HDC hDC = GetDC(NULL);
                if (hDC)
                {
                    if (nPointSize)
                    {
                        LogFont.lfHeight = -MulDiv(nPointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
                    }
                    if (bBold)
                    {
                        LogFont.lfWeight = FW_BOLD;
                    }
                    if (bItalic)
                    {
                        LogFont.lfItalic = TRUE;
                    }
                    hFontResult = CreateFontIndirect( &LogFont );
                    ReleaseDC( NULL, hDC );
                }
            }
        }
        return hFontResult;
    }

     /*******************************************************************************
     * FindLowestNumberedFile
     *
     * Returns lowest numbered file that can contain the requested series of file
     * name, or zero if it can't find such a range.
     *
     * pszFileAndPathnameMask should be a printf-style format string containing the
     * full path name to a file:
     *
     *  Example:  "C:\foo\bar\filename %03d.ext"
     *
     * It would check for the existence of:
     *
     *  "C:\foo\bar\filename 001.ext", "C:\foo\bar\filename 002.ext", etc...
     *
     * until it found a block large enough to hold nCount files.
     *
     *******************************************************************************/
    int FindLowestNumberedFile( LPCTSTR pszFileAndPathnameMask, int nCount, int nMax )
    {
        // Start at one (users never start counting at 0)
        int i=1;
        while (i<=nMax-nCount+1)
        {
            // Assume we'll be able to store the sequence
            bool bEnoughRoom = true;
            for (int j=0;j<nCount && bEnoughRoom;j++)
            {
                // Add a few bytes to the max len, just in case
                TCHAR szFile[MAX_PATH + 10];

                // Create the potential filename
                wsprintf( szFile, pszFileAndPathnameMask, i + j );

                // Look for this file
                WIN32_FIND_DATA FindFileData;
                ZeroMemory( &FindFileData, sizeof(FindFileData));
                HANDLE hFindFiles = FindFirstFile( szFile, &FindFileData );
                if (hFindFiles != INVALID_HANDLE_VALUE)
                {
                    FindClose(hFindFiles);

                    // Didn't make it
                    bEnoughRoom = false;

                    // Skip this series.  No need to start at the bottom.
                    i += j;
                }
            }
            // If we made it through, return the base number, otherwise increment by one
            if (bEnoughRoom)
                return i;
            else i++;
        }
        // We never found room...
        return -1;
    }

    /*******************************************************************************
     * FindLowestNumberedFile
     *
     * Returns lowest numbered file that can contain the requested series of file
     * name, or zero if it can't find such a range.
     *
     * pszFileAndPathnameMask should be a printf-style format string containing the
     * full path name to a file:
     *
     *  Example:  "C:\foo\bar\filename %03d.ext"
     *
     * It would check for the existence of:
     *
     *  "C:\foo\bar\filename 001.ext", "C:\foo\bar\filename 002.ext", etc...
     *
     * until it found a block large enough to hold nCount files.
     *
     *******************************************************************************/
    int FindLowestNumberedFile( LPCTSTR pszFileAndPathnameMaskPrefix, LPCTSTR pszFormatString, LPCTSTR pszFileAndPathnameMaskSuffix, int nCount, int nMax )
    {
        TCHAR szFilename[MAX_PATH];
        if (nCount == 1)
        {
            lstrcpyn( szFilename, pszFileAndPathnameMaskPrefix, ARRAYSIZE(szFilename) );
            lstrcpyn( szFilename+lstrlen(szFilename), pszFileAndPathnameMaskSuffix, ARRAYSIZE(szFilename)-lstrlen(szFilename) );
            if (GetFileAttributes(szFilename) == 0xFFFFFFFF)
                return 0;
        }

        // Use the normal method of calculating the lowest numbered file
        lstrcpyn( szFilename, pszFileAndPathnameMaskPrefix, ARRAYSIZE(szFilename) );
        lstrcpyn( szFilename+lstrlen(szFilename), pszFormatString, ARRAYSIZE(szFilename)-lstrlen(szFilename) );
        lstrcpyn( szFilename+lstrlen(szFilename), pszFileAndPathnameMaskSuffix, ARRAYSIZE(szFilename)-lstrlen(szFilename) );
        return FindLowestNumberedFile( szFilename, nCount, nMax );
    }


    SIZE GetTextExtentFromWindow( HWND hFontWnd, LPCTSTR pszString )
    {
        SIZE sizeResult = {0,0};
        HDC hDC = GetDC( hFontWnd );
        if (hDC)
        {
            HFONT hFont = GetFontFromWindow(hFontWnd);
            if (hFont)
            {
                HFONT hOldFont = SelectFont( hDC, hFont );

                SIZE sizeExtent = {0,0};
                if (GetTextExtentPoint32( hDC, pszString, lstrlen(pszString), &sizeExtent ))
                {
                    sizeResult = sizeExtent;
                }
                //
                // Restore the DC
                //
                if (hOldFont)
                {
                    SelectFont( hDC, hOldFont );
                }
            }
            ReleaseDC( hFontWnd, hDC );
        }
        return sizeResult;
    }

    CSimpleString TruncateTextToFitInRect( HWND hFontWnd, LPCTSTR pszString, RECT rectTarget, UINT nDrawTextFormat )
    {
        WIA_PUSH_FUNCTION((TEXT("WiaUiUtil::TruncateTextToFitInRect( 0x%p, %s, (%d,%d,%d,%d), 0x%08X"), hFontWnd, pszString, rectTarget.left, rectTarget.top, rectTarget.right, rectTarget.bottom, nDrawTextFormat ));
        CSimpleString strResult = pszString;

        //
        // Make sure we have valid parameters
        //
        if (IsWindow(hFontWnd) && hFontWnd && pszString && lstrlen(pszString))
        {
            //
            // Make a copy of the string.  If it fails, we will just return the original string.
            //
            LPTSTR pszTemp = new TCHAR[lstrlen(pszString)+1];
            if (pszTemp)
            {
                lstrcpy( pszTemp, pszString );

                //
                // Get a client DC for the window
                //
                HDC hDC = GetDC( hFontWnd );
                if (hDC)
                {
                    //
                    // Create a memory DC
                    //
                    HDC hMemDC = CreateCompatibleDC( hDC );
                    if (hMemDC)
                    {
                        //
                        // Get the font the window is using and select it into our client dc
                        //
                        HFONT hFont = GetFontFromWindow(hFontWnd);
                        if (hFont)
                        {
                            //
                            // Select the font
                            //
                            HFONT hOldFont = SelectFont( hMemDC, hFont );

                            //
                            // Modify the string using DrawText
                            //
                            if (DrawText( hMemDC, pszTemp, lstrlen(pszTemp), &rectTarget, nDrawTextFormat|DT_MODIFYSTRING|DT_SINGLELINE ))
                            {
                                strResult = pszTemp;
                            }
                            else
                            {
                                WIA_ERROR((TEXT("DrawText failed")));
                            }
                            //
                            // Restore the DC
                            //
                            if (hOldFont)
                            {
                                SelectFont( hMemDC, hOldFont );
                            }

                        }

                        //
                        // Clean up the memory DC
                        //
                        DeleteDC( hMemDC );
                    }
                    else
                    {
                        WIA_ERROR((TEXT("Unable to create the compatible DC")));
                    }

                    //
                    // Release the DC
                    //
                    ReleaseDC( hFontWnd, hDC );
                }
                else
                {
                    WIA_ERROR((TEXT("Unable to get the DC")));
                }

                //
                // Clean up our temp buffer
                //
                delete[] pszTemp;
            }
            else
            {
                WIA_ERROR((TEXT("Unable to allocate the temp buffer")));
            }
        }
        else
        {
            WIA_ERROR((TEXT("Argument validation failed")));
        }
        return strResult;
    }


    CSimpleString FitTextInStaticWithEllipsis( LPCTSTR pszString, HWND hWndStatic, UINT nDrawTextStyle )
    {
        //
        // Make sure we have valid parameters
        //
        if (!hWndStatic || !pszString || !IsWindow(hWndStatic))
        {
            return pszString;
        }

        //
        // Hide prefix characters?
        //
        if (GetWindowLong( hWndStatic, GWL_STYLE ) & SS_NOPREFIX)
        {
            nDrawTextStyle |= DT_NOPREFIX;
        }

        //
        // How big is the area we are trying to fit this in?
        //
        RECT rcClient;
        GetClientRect( hWndStatic, &rcClient );

        //
        // Calculate the result and return it
        //
        return TruncateTextToFitInRect( hWndStatic, pszString, rcClient, nDrawTextStyle );
    }

    //
    // Get the size of an icon
    //
    bool GetIconSize( HICON hIcon, SIZE &sizeIcon )
    {
        //
        // Assume failure
        //
        bool bSuccess = false;

        //
        // Get the icon information
        //
        ICONINFO IconInfo = {0};
        if (GetIconInfo( hIcon, &IconInfo ))
        {
            //
            // Get one of the bitmaps
            //
            BITMAP bm;
            if (GetObject( IconInfo.hbmColor, sizeof(bm), &bm ))
            {
                //
                // Save the size of the icon
                //
                sizeIcon.cx = bm.bmWidth;
                sizeIcon.cy = bm.bmHeight;

                //
                // Everything worked
                //
                bSuccess = true;
            }

            //
            // Free the bitmaps
            //
            DeleteObject(IconInfo.hbmMask);
            DeleteObject(IconInfo.hbmColor);
        }
        else
        {
            WIA_PRINTHRESULT((HRESULT_FROM_WIN32(GetLastError()),TEXT("GetIconInfo failed")));
        }

        return bSuccess;
    }

    HBITMAP CreateIconThumbnail( HWND hWnd, int nWidth, int nHeight, HICON hIcon, LPCTSTR pszText )
    {
        WIA_PUSH_FUNCTION((TEXT("CreateIconThumbnail( hWnd: 0x%p, nWidth: %d, nHeight: %d, hIcon: 0x%p, pszText: \"%s\" )"), hWnd, nWidth, nHeight, hIcon, pszText ? pszText : TEXT("") ));

        //
        // Initialize return value to NULL
        //
        HBITMAP hBmp = NULL;

        //
        // This will be set to true if all steps succeed.
        //
        bool bSuccess = false;

        //
        // The minimum whitespace around the icon and the text border
        //
        const int nIconBorder = 2;

        //
        // Get the DC to the window
        //
        HDC hDC = GetDC(hWnd);
        if (hDC)
        {
            //
            // Get a halftone palette
            //
            HPALETTE hHalftonePalette = CreateHalftonePalette(hDC);
            if (hHalftonePalette)
            {
                //
                // Initialize the bitmap information
                //
                BITMAPINFO BitmapInfo;
                ZeroMemory( &BitmapInfo, sizeof(BITMAPINFO) );
                BitmapInfo.bmiHeader.biSize            = sizeof(BITMAPINFOHEADER);
                BitmapInfo.bmiHeader.biWidth           = nWidth;
                BitmapInfo.bmiHeader.biHeight          = nHeight;
                BitmapInfo.bmiHeader.biPlanes          = 1;
                BitmapInfo.bmiHeader.biBitCount        = 24;
                BitmapInfo.bmiHeader.biCompression     = BI_RGB;

                //
                // Create the DIB section
                //
                PBYTE pBitmapData = NULL;
                hBmp = CreateDIBSection( hDC, &BitmapInfo, DIB_RGB_COLORS, (LPVOID*)&pBitmapData, NULL, 0 );
                if (hBmp)
                {
                    //
                    // Create the source dc
                    //
                    HDC hMemoryDC = CreateCompatibleDC( hDC );
                    if (hMemoryDC)
                    {
                        //
                        // Set up the palette
                        //
                        HPALETTE hOldPalette = SelectPalette( hMemoryDC, hHalftonePalette , 0 );
                        RealizePalette( hMemoryDC );
                        SetBrushOrgEx( hMemoryDC, 0,0, NULL );

                        //
                        // Set up the DC
                        //
                        int nOldBkMode = SetBkMode( hMemoryDC, TRANSPARENT );
                        COLORREF crOldTextColor = SetTextColor( hMemoryDC, GetSysColor(COLOR_WINDOWTEXT) );
                        DWORD dwOldLayout = SetLayout( hMemoryDC, LAYOUT_BITMAPORIENTATIONPRESERVED );

                        //
                        // Select the bitmap into the memory DC
                        //
                        HBITMAP hOldBitmap = reinterpret_cast<HBITMAP>(SelectObject( hMemoryDC, hBmp ));

                        //
                        // Get the font to use
                        //
                        HFONT hFont = GetFontFromWindow(hWnd);

                        //
                        // Select the font
                        //
                        HFONT hOldFont = reinterpret_cast<HFONT>(SelectObject( hMemoryDC, hFont ) );

                        //
                        // Ensure we have a valid icon
                        //
                        if (hIcon)
                        {
                            //
                            // Try to get the size of the icon
                            //
                            SIZE sizeIcon;
                            if (GetIconSize( hIcon, sizeIcon ))
                            {
                                //
                                // Fill the bitmap with the window color
                                //
                                RECT rc = { 0, 0, nWidth, nHeight };
                                FillRect( hMemoryDC, &rc, GetSysColorBrush( COLOR_WINDOW ) );

                                //
                                // Get the text height for one line of text
                                //
                                SIZE sizeText = {0};
                                if (pszText)
                                {
                                    GetTextExtentPoint32( hMemoryDC, TEXT("X"), 1, &sizeText );
                                }

                                //
                                // Center the icon + 1 line of text + margin in the thumbnail
                                // We are assuming this bitmap can actually hold an icon + text
                                //
                                int nIconTop = rc.top + (RectHeight(rc) - ( sizeIcon.cy + sizeText.cy + nIconBorder )) / 2;

                                //
                                // Draw the icon
                                //
                                DrawIconEx( hMemoryDC, (nWidth - sizeIcon.cx)/2, nIconTop, hIcon, sizeIcon.cx, sizeIcon.cy, 0, NULL, DI_NORMAL );

                                //
                                // Only compute text things if there's text to draw
                                //
                                if (pszText && *pszText)
                                {
                                    //
                                    // Decrease the rectangle's width by the icon border
                                    //
                                    InflateRect( &rc, -nIconBorder, 0 );

                                    //
                                    // Set the top of the text to the bottom of icon + the icon border
                                    //
                                    rc.top = nIconTop + sizeIcon.cy + nIconBorder;

                                    //
                                    // Draw the text
                                    //
                                    DrawTextEx( hMemoryDC, const_cast<LPTSTR>(pszText), -1, &rc, DT_CENTER|DT_END_ELLIPSIS|DT_NOPREFIX|DT_WORDBREAK, NULL );
                                }

                                //
                                // Everything worked OK
                                //
                                bSuccess = true;
                            }
                            else
                            {
                                WIA_ERROR((TEXT("Couldn't get an icon size")));
                            }

                        }
                        else
                        {
                            WIA_ERROR((TEXT("Didn't have a valid icon")));
                        }

                        //
                        // Restore the dc's state
                        //
                        SelectObject( hMemoryDC, hOldFont );
                        SelectObject( hMemoryDC, hOldBitmap );
                        SelectPalette( hMemoryDC, hOldPalette , 0 );
                        SetBkMode( hMemoryDC, nOldBkMode );
                        SetTextColor( hMemoryDC, crOldTextColor );
                        SetLayout( hMemoryDC, dwOldLayout );

                        //
                        // Delete the compatible DC
                        //
                        DeleteDC( hMemoryDC );

                    }
                    else
                    {
                        WIA_ERROR((TEXT("Unable to create a memory DC")));
                    }
                }
                else
                {
                    WIA_ERROR((TEXT("Unable to create a DIB section")));
                }

                //
                // Delete the halftone palette
                //
                if (hHalftonePalette)
                {
                    DeleteObject( hHalftonePalette );
                }
            }
            else
            {
                WIA_ERROR((TEXT("Unable to get a halftone palette")));
            }

            //
            // Release the client DC
            //
            ReleaseDC( hWnd, hDC );
        }
        else
        {
            WIA_ERROR((TEXT("Unable to get a DC")));
        }

        //
        // Clean up in the event of failure
        //
        if (!bSuccess)
        {
            if (hBmp)
            {
                DeleteObject(hBmp);
                hBmp = NULL;
            }
        }
        return hBmp;
    }
    //
    // Create a bitmap with an icon and optional text
    //
    HBITMAP CreateIconThumbnail( HWND hWnd, int nWidth, int nHeight, HINSTANCE hIconInstance, const CResId &resIconId, LPCTSTR pszText )
    {
        //
        // Assume failure
        //
        HBITMAP hBmp = NULL;

        //
        // Load the specified icon
        //
        HICON hIcon = (HICON)LoadImage( hIconInstance, resIconId.ResourceName(), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR );
        if (hIcon)
        {
            //
            // Create the thumbnail
            //
            hBmp = CreateIconThumbnail( hWnd, nWidth, nHeight, hIcon, pszText );

            //
            // Free the icon (even though MSDN doesn't mention this, it will result in a leak if you don't)
            //
            DestroyIcon(hIcon);
        }

        return hBmp;
    }

    HRESULT SaveWiaItemAudio( IWiaItem *pWiaItem, LPCTSTR pszBaseFilename, CSimpleString &strAudioFilename )
    {
        //
        // Check the arguments
        //
        if (!pWiaItem || !pszBaseFilename || !lstrlen(pszBaseFilename))
        {
            return E_INVALIDARG;
        }

        //
        // Get the audio data property, if present
        //
        CComPtr<IWiaPropertyStorage> pWiaPropertyStorage;
        HRESULT hr = pWiaItem->QueryInterface( IID_IWiaPropertyStorage, (void**)(&pWiaPropertyStorage) );
        if (SUCCEEDED(hr))
        {
            PROPVARIANT PropVar[3];
            PROPSPEC    PropSpec[3];

            PropSpec[0].ulKind = PRSPEC_PROPID;
            PropSpec[0].propid = WIA_IPC_AUDIO_DATA;

            PropSpec[1].ulKind = PRSPEC_PROPID;
            PropSpec[1].propid = WIA_IPC_AUDIO_AVAILABLE;

            PropSpec[2].ulKind = PRSPEC_PROPID;
            PropSpec[2].propid = WIA_IPC_AUDIO_DATA_FORMAT;

            hr = pWiaPropertyStorage->ReadMultiple( ARRAYSIZE(PropSpec), PropSpec, PropVar );
            if (SUCCEEDED(hr))
            {
                if (PropVar[1].lVal && PropVar[0].caub.cElems)
                {
                    TCHAR szFile[MAX_PATH + 4];
                    lstrcpyn( szFile, pszBaseFilename, ARRAYSIZE(szFile) );

                    //
                    // Figure out where the extension should go.
                    //
                    LPTSTR pszExtensionPoint = PathFindExtension(szFile);

                    //
                    // Replace the extension.  If the item specifies the clsid, use it.  Otherwise assume WAV
                    //
                    if (PropVar[2].vt == VT_CLSID && PropVar[2].puuid)
                    {
                        lstrcpy( pszExtensionPoint, TEXT(".") );
                        lstrcat( pszExtensionPoint, CWiaFileFormat::GetExtension(*PropVar[2].puuid) );
                    }
                    else
                    {
                        lstrcpy( pszExtensionPoint, TEXT(".") );
                        lstrcat( pszExtensionPoint, TEXT("wav") );
                    }

                    //
                    // Save the filename for the caller
                    //
                    strAudioFilename = szFile;

                    //
                    // Open the file and save the data to the file
                    //
                    HANDLE hFile = CreateFile( szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
                    if (INVALID_HANDLE_VALUE != hFile)
                    {
                        DWORD dwBytesWritten;
                        if (WriteFile( hFile, PropVar[0].caub.pElems, PropVar[0].caub.cElems, &dwBytesWritten, NULL ))
                        {
                            // Success
                        }
                        else
                        {
                            hr = HRESULT_FROM_WIN32(GetLastError());
                        }
                        CloseHandle(hFile);
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                    }
                }
                else
                {
                    hr = E_FAIL;
                    WIA_PRINTHRESULT((hr,TEXT("There is no audio data")));
                }
                FreePropVariantArray( ARRAYSIZE(PropVar), PropVar );
            }
        }
        return hr;
    }

    bool IsDeviceCommandSupported( IWiaItem *pWiaItem, const GUID &guidCommand )
    {
        //
        // Assume failure
        //
        bool bResult = false;

        //
        // Make sure we have a valid item
        //
        if (pWiaItem)
        {
            //
            // Get the device capabilities enumerator
            //
            CComPtr<IEnumWIA_DEV_CAPS> pDeviceCapabilities;
            HRESULT hr = pWiaItem->EnumDeviceCapabilities( WIA_DEVICE_COMMANDS, &pDeviceCapabilities );
            if (SUCCEEDED(hr))
            {
                //
                // Enumerate the capabilities
                //
                WIA_DEV_CAP WiaDeviceCapability;
                while (!bResult && S_OK == pDeviceCapabilities->Next(1, &WiaDeviceCapability, NULL))
                {
                    //
                    // If we have a match, set the return value to true
                    //
                    if (guidCommand == WiaDeviceCapability.guid)
                    {
                        bResult = true;
                    }

                    //
                    // Clean up the allocated data in the dev caps structure
                    //
                    if (WiaDeviceCapability.bstrName)
                    {
                        SysFreeString(WiaDeviceCapability.bstrName);
                    }
                    if (WiaDeviceCapability.bstrDescription)
                    {
                        SysFreeString(WiaDeviceCapability.bstrDescription);
                    }
                    if (WiaDeviceCapability.bstrIcon)
                    {
                        SysFreeString(WiaDeviceCapability.bstrIcon);
                    }
                    if (WiaDeviceCapability.bstrCommandline)
                    {
                        SysFreeString(WiaDeviceCapability.bstrCommandline);
                    }
                }
            }
        }

        return bResult;
    }

    HRESULT StampItemTimeOnFile( IWiaItem *pWiaItem, LPCTSTR pszFilename )
    {
        if (!pWiaItem || !pszFilename || !lstrlen(pszFilename))
        {
            return E_INVALIDARG;
        }
        //
        // All this, just to set the stinking file time...
        // Allows for the possibility of using a VT_FILETIME
        // just in case we ever make the intelligent decision
        // to support VT_FILETIME
        //
        CComPtr<IWiaPropertyStorage> pWiaPropertyStorage;
        HRESULT hr = pWiaItem->QueryInterface( IID_IWiaPropertyStorage, (void **)&pWiaPropertyStorage );
        if (SUCCEEDED(hr))
        {
            //
            // Get the file time
            //
            PROPSPEC PropSpec[1] = {0};
            PROPVARIANT PropVar[1] = {0};

            PropSpec[0].ulKind = PRSPEC_PROPID;
            PropSpec[0].propid = WIA_IPA_ITEM_TIME;
            hr = pWiaPropertyStorage->ReadMultiple( ARRAYSIZE(PropSpec), PropSpec, PropVar );
            if (SUCCEEDED(hr))
            {
                //
                // Check to see if we are using a SYSTEMTIME structure
                //
                if (PropVar[0].vt > VT_NULL &&  PropVar[0].caub.pElems && PropVar[0].caub.cElems >= (sizeof(SYSTEMTIME)>>1))
                {
                    //
                    // Convert the systemtime to a local filetime
                    //
                    FILETIME FileTimeLocal;
                    if (SystemTimeToFileTime( reinterpret_cast<SYSTEMTIME*>(PropVar[0].caub.pElems), &FileTimeLocal ))
                    {
                        //
                        // Convert the local filetime to a UTC filetime
                        //
                        FILETIME FileTimeUTC;
                        if (LocalFileTimeToFileTime( &FileTimeLocal, &FileTimeUTC ))
                        {
                            //
                            // Open the file handle
                            //
                            HANDLE hFile = CreateFile( pszFilename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
                            if (INVALID_HANDLE_VALUE != hFile)
                            {
                                //
                                // Set the file creation time
                                //
                                if (!SetFileTime( hFile, &FileTimeUTC, NULL, NULL ))
                                {
                                    hr = HRESULT_FROM_WIN32(GetLastError());
                                    WIA_PRINTHRESULT((hr,TEXT("SetFileTime failed")));
                                }
                                CloseHandle( hFile );
                            }
                            else
                            {
                                hr = HRESULT_FROM_WIN32(GetLastError());
                                WIA_PRINTHRESULT((hr,TEXT("CreateFile failed")));
                            }
                        }
                        else
                        {
                            hr = HRESULT_FROM_WIN32(GetLastError());
                            WIA_PRINTHRESULT((hr,TEXT("FileTimeToLocalFileTime failed")));
                        }
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                        WIA_PRINTHRESULT((hr,TEXT("SystemTimeToFileTime failed")));
                    }
                }
                else if (VT_FILETIME == PropVar[0].vt)
                {
                    //
                    // Convert the local filetime to a UTC filetime
                    //
                    FILETIME FileTimeUTC;
                    if (LocalFileTimeToFileTime( &PropVar[0].filetime, &FileTimeUTC ))
                    {
                        //
                        // Open the file handle
                        //
                        HANDLE hFile = CreateFile( pszFilename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
                        if (INVALID_HANDLE_VALUE != hFile)
                        {
                            //
                            // Set the file creation time
                            //
                            if (!SetFileTime( hFile, &FileTimeUTC, NULL, NULL ))
                            {
                                hr = HRESULT_FROM_WIN32(GetLastError());
                                WIA_PRINTHRESULT((hr,TEXT("SetFileTime failed")));
                            }
                            CloseHandle( hFile );
                        }
                        else
                        {
                            hr = HRESULT_FROM_WIN32(GetLastError());
                            WIA_PRINTHRESULT((hr,TEXT("CreateFile failed")));
                        }
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                        WIA_PRINTHRESULT((hr,TEXT("FileTimeToLocalFileTime failed")));
                    }
                }
                else
                {
                    hr = E_FAIL;
                    WIA_PRINTHRESULT((hr,TEXT("The time property is invalid")));
                }
            }
            else
            {
                WIA_ERROR((TEXT("ReadMultiple on WIA_IPA_ITEM_TIME failed")));
            }
        }
        else
        {
            WIA_ERROR((TEXT("QueryInterface on IWiaPropertyStorage failed")));
        }
        return hr;
    }


    HRESULT MoveOrCopyFile( LPCTSTR pszSrc, LPCTSTR pszTgt )
    {
        WIA_PUSH_FUNCTION((TEXT("CDownloadImagesThreadMessage::MoveOrCopyFile( %s, %s )"), pszSrc, pszTgt ));
        //
        // Verify the arguments
        //
        if (!pszSrc || !pszTgt || !lstrlen(pszSrc) || !lstrlen(pszTgt))
        {
            return E_INVALIDARG;
        }

        //
        // Assume everything worked ok
        //
        HRESULT hr = S_OK;

        //
        // First try to move the file, since that will be lots faster
        //
        if (!MoveFile( pszSrc, pszTgt ))
        {
            //
            // If moving the file failed, try to copy it and the delete it
            //
            if (CopyFile( pszSrc, pszTgt, FALSE ))
            {
                //
                // We are going to ignore failures from DeleteFile.  It is possible the file is legitimately in
                // use, and there is probably no need to fail the entire operation because of this.
                //
                if (!DeleteFile( pszSrc ))
                {
                    WIA_PRINTHRESULT((HRESULT_FROM_WIN32(GetLastError()),TEXT("DeleteFile failed.  Ignoring failure.")));
                }
                //
                // Everything worked OK
                //
                hr = S_OK;
            }
            else
            {
                //
                // This is where we catch the main errors
                //
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
        return hr;
    }

    CSimpleString CreateTempFileName( UINT nId )
    {
        //
        // Initialize the return value to an empty string
        //
        CSimpleString strResult(TEXT(""));

        //
        // Get the temp folder path
        //
        TCHAR szTempDirectory[MAX_PATH] = {0};
        DWORD dwResult = GetTempPath( ARRAYSIZE(szTempDirectory), szTempDirectory );
        if (dwResult)
        {
            //
            // Make sure the path length didn't exceed the buffer we allocated on the stack
            //
            if (ARRAYSIZE(szTempDirectory) >= dwResult)
            {
                //
                // Get the temp file name
                //
                TCHAR szFileName[MAX_PATH] = {0};
                if (GetTempFileName( szTempDirectory, TEXT("scw"), nId, szFileName ))
                {
                    //
                    // Save the filename
                    //
                    strResult = szFileName;
                }
            }
        }

        //
        // Return the result.  An e mpty string denotes an error.
        //
        return strResult;
    }

    bool CanWiaImageBeSafelyRotated( const GUID &guidFormat, LONG nImageWidth, LONG nImageHeight )
    {
        WIA_PUSH_FUNCTION((TEXT("WiaUiUtil::CanWiaImageBeSafelyRotated( guidFormat, %d, %d )"), nImageWidth, nImageHeight ));
        WIA_PRINTGUID((guidFormat,TEXT("guidFormat")));

        //
        // These are the image types we can possibly rotate (there may be exceptions below)
        //
        static const GUID *guidSafeFormats[] = { &WiaImgFmt_BMP, &WiaImgFmt_JPEG, &WiaImgFmt_PNG, &WiaImgFmt_GIF };

        //
        // Search for this image type
        //
        for (int i=0;i<ARRAYSIZE(guidSafeFormats);i++)
        {
            //
            // If we've found it
            //
            if (*guidSafeFormats[i] == guidFormat)
            {
                //
                // Handle exceptions to the rule
                //
                if (guidFormat == WiaImgFmt_JPEG)
                {
                    //
                    // We can't do lossless rotation on JPG images that are not even multiples of 16 in size
                    //
                    if ((nImageWidth % 16) || (nImageHeight % 16))
                    {
                        WIA_TRACE((TEXT("This image is not valid for rotation because it is not an even multiple of 16")));
                        return false;
                    }
                }

                //
                // If none of the exceptions applied, return TRUE
                //
                WIA_TRACE((TEXT("Returning true")));
                return true;
            }
        }

        //
        // If it is not known that we CAN rotate, we report false
        //
        WIA_TRACE((TEXT("Format type not found in safe list")));
        return false;
    }

    HRESULT ExploreWiaDevice( LPCWSTR pszDeviceId )
    {
        HRESULT hr;

        //
        // Make sure we have a valid device id
        //
        if (!pszDeviceId || !lstrlenW(pszDeviceId))
        {
            return E_INVALIDARG;
        }

        //
        // Load the shell extension's dll
        //
        HINSTANCE hInstWiaShellDll = LoadLibrary(TEXT("WIASHEXT.DLL"));
        if (hInstWiaShellDll)
        {
            //
            // Get the function that creates pidls
            //
            WIAMAKEFULLPIDLFORDEVICE pfnMakeFullPidlForDevice = reinterpret_cast<WIAMAKEFULLPIDLFORDEVICE>(GetProcAddress(hInstWiaShellDll, "MakeFullPidlForDevice"));
            if (pfnMakeFullPidlForDevice)
            {
                //
                // Get the device PIDL
                //
                LPITEMIDLIST pidlDevice = NULL;
                hr = pfnMakeFullPidlForDevice( const_cast<LPWSTR>(pszDeviceId), &pidlDevice );
                if (SUCCEEDED(hr))
                {
                    //
                    // First, ask the shell to refresh any active views
                    //
                    SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, pidlDevice, 0);

                    //
                    // Now show the folder
                    //
                    SHELLEXECUTEINFO ShellExecuteInfo = {0};
                    ShellExecuteInfo.cbSize   = sizeof(ShellExecuteInfo);
                    ShellExecuteInfo.fMask    = SEE_MASK_IDLIST;
                    ShellExecuteInfo.nShow    = SW_SHOW;
                    ShellExecuteInfo.lpIDList = pidlDevice;
                    if (ShellExecuteEx( &ShellExecuteInfo ))
                    {
                        hr = S_OK;
                    }

                    //
                    // Free the pidl
                    //
                    LPMALLOC pMalloc = NULL;
                    if (SUCCEEDED(SHGetMalloc(&pMalloc)) && pMalloc)
                    {
                        pMalloc->Free(pidlDevice);
                        pMalloc->Release();
                    }
                }
            }
            else
            {
                hr = E_FAIL;
            }

            //
            // Unload the DLL
            //
            FreeLibrary( hInstWiaShellDll );
        }
        else
        {
            //
            // Can't load the DLL
            //
            hr = E_FAIL;
        }

        return hr;
    }

    //
    // Modify a combo box's drop down list so that it is
    // long enough to store the longest string in the list
    // Taken from TaoYuan's code in photowiz.dll and modified
    // to handle ComboBoxEx32 controls
    //
    BOOL ModifyComboBoxDropWidth( HWND hWndCombobox )
    {
        //
        // Make sure we have a valid window
        //
        if (!hWndCombobox)
        {
            return FALSE;
        }

        //
        // Find out how many items are in the combobox.  If there are none, don't bother resizing.
        //
        LRESULT lRes = SendMessage( hWndCombobox, CB_GETCOUNT, 0, 0 );
        if (lRes <= 0)
        {
            return FALSE;
        }
        UINT nCount = static_cast<UINT>(lRes);

        //
        // We only work with fixed-height comboboxes
        //
        lRes = SendMessage( hWndCombobox, CB_GETITEMHEIGHT, 0, 0 );
        if (lRes < 0)
        {
            return FALSE;
        }
        UINT nItemHeight = static_cast<UINT>(lRes);

        //
        // We will be going through to figure out the desired size of the drop down list
        //
        UINT nDesiredWidth = 0;

        //
        // Add the size of the scrollbar to the desired witdth, of there is one
        //
        RECT rcDropped = {0};
        SendMessage( hWndCombobox, CB_GETDROPPEDCONTROLRECT, 0, reinterpret_cast<LPARAM>(&rcDropped) );

        //
        // Get the size of the control's window
        //
        RECT rcWnd = {0};
        GetWindowRect( hWndCombobox, &rcWnd );


        //
        // If not all of the items will fit in the dropped list,
        // we have to account for a vertical scrollbar
        //
        if (((WiaUiUtil::RectHeight(rcDropped) - GetSystemMetrics(SM_CYEDGE)*2) / nItemHeight) < nCount)
        {
            nDesiredWidth += GetSystemMetrics(SM_CXEDGE)*2 + GetSystemMetrics( SM_CXVSCROLL );
        }

        //
        // Find the widest string
        //
        LONG nMaxStringLen = 0;
        HDC hDC = GetDC( hWndCombobox );
        if (hDC)
        {
            //
            // Use the control's font
            //
            HFONT hOldFont = NULL, hFont = reinterpret_cast<HFONT>(SendMessage(hWndCombobox,WM_GETFONT,0,0));
            if (hFont)
            {
                hOldFont = SelectFont( hDC, hFont );
            }

            for (UINT i = 0; i < nCount; i++ )
            {
                //
                // Get the length of this item's text
                //
                LRESULT nLen = SendMessage( hWndCombobox, CB_GETLBTEXTLEN, i, 0 );
                if (nLen > 0)
                {
                    //
                    // Allocate a buffer for the string
                    //
                    LPTSTR pszItem = new TCHAR[nLen+1];
                    if (pszItem)
                    {
                        //
                        // Get the string
                        //
                        pszItem[0] = TEXT('\0');
                        if (SendMessage( hWndCombobox, CB_GETLBTEXT, i, reinterpret_cast<LPARAM>(pszItem) ) > 0)
                        {
                            //
                            // Measure it
                            //
                            SIZE sizeText = {0};
                            if (GetTextExtentPoint32( hDC, pszItem, lstrlen( pszItem ), &sizeText ))
                            {
                                //
                                // If this is the longest one, save its length
                                //
                                if (sizeText.cx > nMaxStringLen)
                                {
                                    nMaxStringLen = sizeText.cx;
                                }
                            }
                        }

                        //
                        // Free the string
                        //
                        delete[] pszItem;
                    }
                }
            }

            //
            // Restore and release the DC
            //
            if (hOldFont)
            {
                SelectFont( hDC, hOldFont );
            }
            ReleaseDC( hWndCombobox, hDC );
        }
        //
        // Add in the longest string's length
        //
        nDesiredWidth += nMaxStringLen;


        //
        // If this is a ComboBoxEx32, add in the width of the icon
        //
        TCHAR szClassName[MAX_PATH] = {0};
        if (GetClassName( hWndCombobox, szClassName, ARRAYSIZE(szClassName)))
        {
            //
            // Compare the classname with ComboBoxEx32
            //
            if (!lstrcmp(szClassName,WC_COMBOBOXEX))
            {
                //
                // Get the image list from the control
                //
                HIMAGELIST hImageList = reinterpret_cast<HIMAGELIST>(SendMessage( hWndCombobox, CBEM_GETIMAGELIST, 0, 0 ));
                if (hImageList)
                {
                    //
                    // Get the width and add it to the desired size
                    //
                    INT nWidth=0, nHeight=0;
                    if (ImageList_GetIconSize( hImageList, &nWidth, &nHeight ))
                    {
                        //
                        // I don't know what the margin should be, but nWidth*2
                        // should account for the width of icon and its margin
                        //
                        nDesiredWidth += nWidth * 2;
                    }
                }
            }
        }

        //
        // Add in the border of the control
        //
        nDesiredWidth += GetSystemMetrics(SM_CXFIXEDFRAME)*2;

        //
        // Make sure our drop down is no wider than the current monitor
        //
        HMONITOR hMonitor = MonitorFromWindow( hWndCombobox, MONITOR_DEFAULTTONEAREST );
        if (hMonitor)
        {
            MONITORINFO MonitorInfo = {0};
            MonitorInfo.cbSize = sizeof(MonitorInfo);
            //
            // Get the screen coordinates of this monitor
            //
            if (GetMonitorInfo(hMonitor, &MonitorInfo))
            {
                //
                // If the desired width is larger than the monitor, shorten it
                //
                if (nDesiredWidth > static_cast<UINT>(WiaUiUtil::RectWidth(MonitorInfo.rcMonitor)))
                {
                    nDesiredWidth = RectWidth(MonitorInfo.rcMonitor);
                }
            }
        }


        //
        // If our size is smaller than the control's current size, grow it
        //
        if (static_cast<UINT>(WiaUiUtil::RectWidth(rcDropped)) < nDesiredWidth)
        {
            //
            // Disable redrawing
            //
            SendMessage( hWndCombobox, WM_SETREDRAW, FALSE, 0 );


            SendMessage( hWndCombobox, CB_SETDROPPEDWIDTH, static_cast<WPARAM>(nDesiredWidth), 0 );

            //
            // Allow redrawing
            //
            SendMessage( hWndCombobox, WM_SETREDRAW, TRUE, 0 );

            //
            // Force a repaint
            //
            InvalidateRect( hWndCombobox, NULL, FALSE );
            UpdateWindow( hWndCombobox );

            //
            // TRUE means we actually changed it
            //
            return TRUE;
        }

        return FALSE;
    }

    static LPCTSTR s_pszComboBoxExWndProcPropName = TEXT("WiaComboBoxExWndProcPropName");

    static LRESULT WINAPI ComboBoxExWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
    {
        static WNDPROC s_pfnDefProc = NULL;

        WNDPROC pfnWndProc = reinterpret_cast<WNDPROC>(GetProp( hWnd, s_pszComboBoxExWndProcPropName ));

        if (!s_pfnDefProc)
        {
            WNDCLASS wc = {0};
            GetClassInfo( GetModuleHandle(TEXT("user32.dll")), TEXT("ComboBox"), &wc );
            s_pfnDefProc = wc.lpfnWndProc;
        }
        if (nMsg == WM_LBUTTONDOWN || nMsg == WM_RBUTTONDOWN)
        {
            if (s_pfnDefProc)
            {
                return CallWindowProc( s_pfnDefProc, hWnd, nMsg, wParam, lParam );
            }
        }
        if (nMsg == WM_DESTROY)
        {
            RemoveProp( hWnd, s_pszComboBoxExWndProcPropName );
        }
        if (pfnWndProc)
        {
            return CallWindowProc( pfnWndProc, hWnd, nMsg, wParam, lParam );
        }
        else
        {
            return CallWindowProc( DefWindowProc, hWnd, nMsg, wParam, lParam );
        }
    }

    //
    // This subclasses the ComboBoxEx32 to work around a bug
    // that causes the list to drop down at bad times.
    // Uses a window property to store the previous wndproc.
    // Taken from DavidShi's code in wiashext.dll
    //
    void SubclassComboBoxEx( HWND hWnd )
    {
        HWND hComboBox = FindWindowEx( hWnd, NULL, TEXT("ComboBox"), NULL );
        if (hComboBox)
        {
            LONG_PTR pfnOldWndProc = SetWindowLongPtr( hComboBox, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ComboBoxExWndProc));
            SetProp( hComboBox, s_pszComboBoxExWndProcPropName, reinterpret_cast<HANDLE>(pfnOldWndProc) );
        }
    }

    HRESULT IssueWiaCancelIO( IUnknown *pUnknown )
    {
        if (!pUnknown)
        {
            return E_POINTER;
        }

        CComPtr<IWiaItemExtras> pWiaItemExtras;
        HRESULT hr = pUnknown->QueryInterface( IID_IWiaItemExtras, (void**)&pWiaItemExtras );
        if (SUCCEEDED(hr))
        {
            hr = pWiaItemExtras->CancelPendingIO();
        }
        return hr;
    }


    HRESULT VerifyScannerProperties( IUnknown *pUnknown )
    {
        HRESULT hr = E_FAIL;

        //
        // Table of required properties
        //
        static const PROPID s_RequiredProperties[] =
        {
            WIA_IPS_CUR_INTENT
        };

        //
        // Make sure we have a valid item
        //
        if (pUnknown)
        {
            //
            // Assume success at this point
            //
            hr = S_OK;

            //
            // Get the IWiaPropertyStorage interface
            //
            CComPtr<IWiaPropertyStorage> pWiaPropertyStorage;
            hr = pUnknown->QueryInterface(IID_IWiaPropertyStorage, (void**)&pWiaPropertyStorage);
            if (SUCCEEDED(hr))
            {
                //
                // Loop through each property and make sure it exists
                // Break out if hr != S_OK
                //
                for (int i=0;i<ARRAYSIZE(s_RequiredProperties) && S_OK==hr;i++)
                {
                    //
                    // Prepare the propspec
                    //
                    PROPSPEC PropSpec = {0};
                    PropSpec.ulKind = PRSPEC_PROPID;
                    PropSpec.propid = s_RequiredProperties[i];

                    //
                    // Attempt to get the property attributes
                    //
                    ULONG nAccessFlags = 0;
                    PROPVARIANT PropVariant = {0};
                    hr = pWiaPropertyStorage->GetPropertyAttributes( 1, &PropSpec, &nAccessFlags, &PropVariant );
                    if (SUCCEEDED(hr))
                    {
                        //
                        // Prevent a leak
                        //
                        PropVariantClear(&PropVariant);

                        //
                        // If everything is OK so far
                        //
                        if (S_OK == hr)
                        {
                            //
                            // Zero out the structure
                            //
                            PropVariantInit(&PropVariant);

                            //
                            // Attempt to read the actual value
                            //
                            hr = pWiaPropertyStorage->ReadMultiple( 1, &PropSpec, &PropVariant );
                            if (SUCCEEDED(hr))
                            {
                                //
                                // Free the actual value
                                //
                                PropVariantClear(&PropVariant);
                            }
                        }
                    }
                }
            }
        }

        //
        // S_FALSE means a property doesn't exist, so change this to an error
        //
        if (S_FALSE == hr)
        {
            hr = E_FAIL;
        }

        //
        // All done
        //
        return hr;
    }

    CSimpleString GetErrorTextFromHResult( HRESULT hr )
    {
        CSimpleString strResult;
        LPTSTR szErrMsg = NULL;
        FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                       NULL,
                       hr,
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                       reinterpret_cast<LPTSTR>(&szErrMsg),
                       0,
                       NULL
                      );
        if (szErrMsg)
        {
            strResult = szErrMsg;
            LocalFree( szErrMsg );
        }
        return strResult;
    }

} // End namespace WiaUiUtil