//////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2000-2001 Microsoft Corporation
//
//  Module Name:
//      DetailsDlg.cpp
//
//  Maintained By:
//      David Potter    (DavidP)    27-MAR-2001
//
//////////////////////////////////////////////////////////////////////////////

#include "Pch.h"
#include "TaskTreeView.h"
#include "DetailsDlg.h"
#include "WizardUtils.h"

DEFINE_THISCLASS("DetailsDlg");

//////////////////////////////////////////////////////////////////////////////
//  Static Function Prototypes
//////////////////////////////////////////////////////////////////////////////

static
BOOL
ButtonFaceColorIsDark( void );

static
void
SetButtonImage(
      HWND  hwndBtnIn
    , ULONG idIconIn
    );

static
void
FreeButtonImage(
    HWND hwndBtnIn
    );

static
HRESULT
HrAppendStringToClipboardString(
      BSTR *    pbstrClipboard
    , UINT      idsLabelIn
    , LPCWSTR   pszDataIn
    , bool      fNewlineBeforeTextIn
    );

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::S_HrDisplayModalDialog
//
//  Description:
//      Display the dialog box.
//
//  Arguments:
//      hwndParentIn    - Parent window for the dialog box.
//      pttvIn          - Task tree view control.
//      htiSelectedIn   - Handle to the selected item.
//
//  Return Values:
//      S_OK        - Operation completed successfully.
//
//  Remarks:
//
//--
//////////////////////////////////////////////////////////////////////////////
HRESULT
CDetailsDlg::S_HrDisplayModalDialog(
      HWND              hwndParentIn
    , CTaskTreeView *   pttvIn
    , HTREEITEM         htiSelectedIn
    )
{
    TraceFunc( "" );

    Assert( pttvIn != NULL );
    Assert( htiSelectedIn != NULL );

    HRESULT         hr      = S_OK;
    CDetailsDlg     dlg( pttvIn, htiSelectedIn );

    DialogBoxParam(
          g_hInstance
        , MAKEINTRESOURCE( IDD_DETAILS )
        , hwndParentIn
        , CDetailsDlg::S_DlgProc
        , (LPARAM) &dlg
        );

    HRETURN( hr );

} //*** CDetailsDlg::S_HrDisplayModalDialog()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::CDetailsDlg
//
//  Description:
//      Constructor.
//
//  Arguments:
//      pttvIn          - Tree view to traverse.
//      htiSelectedIn   - Handle to the selected item in the tree control.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
CDetailsDlg::CDetailsDlg(
      CTaskTreeView *   pttvIn
    , HTREEITEM         htiSelectedIn
    )
{
    TraceFunc( "" );

    Assert( pttvIn != NULL );
    Assert( htiSelectedIn != NULL );

    // m_hwnd
    m_hiconWarn     = NULL;
    m_hiconError    = NULL;
    m_pttv          = pttvIn;
    m_htiSelected   = htiSelectedIn;

    m_fControlDown  = FALSE;
    m_fAltDown      = FALSE;

    TraceFuncExit();

} //*** CDetailsDlg::CDetailsDlg()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::~CDetailsDlg
//
//  Description:
//      Destructor.
//
//  Arguments:
//      None.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
CDetailsDlg::~CDetailsDlg( void )
{
    TraceFunc( "" );

    if ( m_hiconWarn != NULL )
    {
        DeleteObject( m_hiconWarn );
    }

    if ( m_hiconError != NULL )
    {
        DeleteObject( m_hiconError );
    }

    TraceFuncExit();

} //*** CDetailsDlg::~CDetailsDlg()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::S_DlgProc
//
//  Description:
//      Dialog proc for the Details dialog box.
//
//  Arguments:
//      hwndDlgIn   - Dialog box window handle.
//      nMsgIn      - Message ID.
//      wParam      - Message-specific parameter.
//      lParam      - Message-specific parameter.
//
//  Return Values:
//      TRUE        - Message was processed by this procedure.
//      FALSE       - Message was NOT processed by this procedure.
//
//  Remarks:
//      It is expected that this dialog box is invoked by a call to
//      DialogBoxParam() with the lParam argument set to the address of the
//      instance of this class.
//
//--
//////////////////////////////////////////////////////////////////////////////
INT_PTR
CALLBACK
CDetailsDlg::S_DlgProc(
      HWND      hwndDlgIn
    , UINT      nMsgIn
    , WPARAM    wParam
    , LPARAM    lParam
    )
{
    // Don't do TraceFunc because every mouse movement
    // will cause this function to be called.

    WndMsg( hwndDlgIn, nMsgIn, wParam, lParam );

    LRESULT         lr = FALSE;
    HACCEL          haccel  = NULL;
    CDetailsDlg *   pdlg;

    //
    // Get a pointer to the class.
    //

    if ( nMsgIn == WM_INITDIALOG )
    {
        SetWindowLongPtr( hwndDlgIn, GWLP_USERDATA, lParam );
        pdlg = reinterpret_cast< CDetailsDlg * >( lParam );
        pdlg->m_hwnd = hwndDlgIn;
    }
    else
    {
        pdlg = reinterpret_cast< CDetailsDlg * >( GetWindowLongPtr( hwndDlgIn, GWLP_USERDATA ) );
    }

    if ( pdlg != NULL )
    {
        Assert( hwndDlgIn == pdlg->m_hwnd );

        switch( nMsgIn )
        {
            case WM_INITDIALOG:
                lr = pdlg->OnInitDialog();
                break;

            case WM_DESTROY:
                pdlg->OnDestroy();
                break;

            case WM_SYSCOLORCHANGE:
                pdlg->OnSysColorChange();
                break;

            case WM_NOTIFY:
                lr = pdlg->OnNotify( wParam, reinterpret_cast< LPNMHDR >( lParam ) );
                break;

            case WM_COMMAND:
                lr = pdlg->OnCommand( HIWORD( wParam ), LOWORD( wParam ), reinterpret_cast< HWND >( lParam ) );
                break;

            case WM_KEYDOWN:
                lr = pdlg->OnKeyDown( lParam );
                break;

            case WM_KEYUP:
                lr = pdlg->OnKeyUp( lParam );
                break;

            default:
                lr = FALSE;
        } // switch: nMsgIn
    } // if: page is specified

    return lr;

} //*** CDetailsDlg::S_DlgProc()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnInitDialog
//
//  Description:
//      Handler for the WM_INITDIALOG message.
//
//  Arguments:
//      None.
//
//  Return Values:
//      TRUE        Focus has been set.
//      FALSE       Focus has not been set.
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnInitDialog( void )
{
    TraceFunc( "" );

    LRESULT lr = TRUE; // did set focus
    HWND    hwnd;

    //
    // Tell the rich edit controls we want to receive notifications of clicks
    // on text that has the link (hyperlink, aka URL) format.   Also, set the
    // background color of the rich edit to match the background color of
    // the dialog.
    //

    hwnd = GetDlgItem( m_hwnd, IDC_DETAILS_RE_DESCRIPTION );

    SendMessage( hwnd, EM_SETEVENTMASK, 0, ENM_LINK );
    SendMessage( hwnd, EM_SETBKGNDCOLOR, 0, GetSysColor( COLOR_BTNFACE ) );
    SendMessage( hwnd, EM_AUTOURLDETECT, TRUE, 0 );

    hwnd = GetDlgItem( m_hwnd, IDC_DETAILS_RE_STATUS );

    SendMessage( hwnd, EM_SETEVENTMASK, 0, ENM_LINK );
    SendMessage( hwnd, EM_SETBKGNDCOLOR, 0, GetSysColor( COLOR_BTNFACE ) );
    SendMessage( hwnd, EM_AUTOURLDETECT, TRUE, 0 );

    hwnd = GetDlgItem( m_hwnd, IDC_DETAILS_RE_REFERENCE );

    SendMessage( hwnd, EM_SETEVENTMASK, 0, ENM_LINK );
    SendMessage( hwnd, EM_SETBKGNDCOLOR, 0, GetSysColor( COLOR_BTNFACE ) );
    SendMessage( hwnd, EM_AUTOURLDETECT, TRUE, 0 );

    //
    // Set the icons for the icon pushbuttons
    //

    OnSysColorChange();
    SetButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_COPY ), IDI_COPY );

    //
    // Load the status icons.
    //

    m_hiconWarn = (HICON) LoadImage(
                              g_hInstance
                            , MAKEINTRESOURCE( IDI_WARN )
                            , IMAGE_ICON
                            , 16
                            , 16
                            , LR_SHARED
                             );
    Assert( m_hiconWarn != NULL );
    m_hiconError = (HICON) LoadImage(
                              g_hInstance
                            , MAKEINTRESOURCE( IDI_FAIL )
                            , IMAGE_ICON
                            , 16
                            , 16
                            , LR_SHARED
                             );
    Assert( m_hiconError != NULL );

    //
    // Display the selected item.
    //

    THR( HrDisplayItem( m_htiSelected ) );

    //
    // Update the buttons based on what is selected.
    //

    UpdateButtons();

    //
    // Set focus to the OK button.
    //

    SetFocus( GetDlgItem( m_hwnd, IDOK ) );

    RETURN( lr );

} //*** CDetailsDlg::OnInitDialog()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnDestroy
//
//  Description:
//      Handler for the WM_DESTROY message.
//
//  Arguments:
//      None.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CDetailsDlg::OnDestroy( void )
{
    TraceFunc( "" );

    //
    // Destroy the images loaded for the icon pushbuttons
    //

    FreeButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_PREV ) );
    FreeButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_NEXT ) );
    FreeButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_COPY ) );

} //*** CDetailsDlg::OnDestroy()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnSysColorChange
//
//  Description:
//      Handler for the WM_SYSCOLORCHANGE message.
//
//  Arguments:
//      None.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CDetailsDlg::OnSysColorChange( void )
{
    TraceFunc( "" );

    if ( ButtonFaceColorIsDark() )
    {
        SetButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_PREV ), IDI_PREVIOUS_HC );
        SetButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_NEXT ), IDI_NEXT_HC );
    }
    else
    {
        SetButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_PREV ), IDI_PREVIOUS );
        SetButtonImage( GetDlgItem( m_hwnd, IDC_DETAILS_PB_NEXT ), IDI_NEXT );
    }

    SendDlgItemMessage(
          m_hwnd
        , IDC_DETAILS_RE_DESCRIPTION
        , EM_SETBKGNDCOLOR
        , 0
        , GetSysColor( COLOR_BTNFACE )
        );

    TraceFuncExit();

} //*** CDetailsDlg::OnSysColorChange()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnKeyDown
//
//  Description:
//      Handler for the WM_KEYDOWN message.
//
//  Arguments:
//      lParamIn    - Parameter containing information about the key.
//
//  Return Values:
//      TRUE        - Message was processed.
//      FALSE       - Message was not processed.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnKeyDown(
    LPARAM  lParamIn
    )
{
    TraceFunc( "" );

    LRESULT lr = FALSE;

    union
    {
        LPARAM  lParam;
        struct
        {
            BYTE        cRepeat;
            BYTE        nScanCode;
            unsigned    fExtendedKey    : 1;
            unsigned    reserved        : 4;
            unsigned    fIsAltKeyDown   : 1;    // always 0 for WM_KEYDOWN
            unsigned    fKeyDownBefore  : 1;
            unsigned    fKeyReleased    : 1;    // always 0 for WM_KEYDOWN
        };
    } uFlags;

    uFlags.lParam = lParamIn;

    switch ( uFlags.nScanCode )
    {
        case VK_CONTROL:
            m_fControlDown = TRUE;
            lr = TRUE;
            break;

        case VK_MENU:   // ALT
            m_fAltDown = TRUE;
            lr = TRUE;
            break;

        case 'c':
        case 'C':
            if ( m_fControlDown )
            {
                OnCommandBnClickedCopy();
                lr = TRUE;
            }
            break;
    } // switch: scan code

    RETURN( lr );

} //*** CDetailsDlg::OnKeyDown()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnKeyUp
//
//  Description:
//      Handler for the WM_KEYUP message.
//
//  Arguments:
//      lParamIn    - Parameter containing information about the key.
//
//  Return Values:
//      TRUE        - Message was processed.
//      FALSE       - Message was not processed.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnKeyUp(
    LPARAM  lParamIn
    )
{
    TraceFunc( "" );

    LRESULT lr = FALSE;

    union
    {
        LPARAM  lParam;
        struct
        {
            BYTE        cRepeat;
            BYTE        nScanCode;
            unsigned    fExtendedKey    : 1;
            unsigned    reserved        : 4;
            unsigned    fIsAltKeyDown   : 1;    // always 0 for WM_KEYDOWN
            unsigned    fKeyDownBefore  : 1;
            unsigned    fKeyReleased    : 1;    // always 0 for WM_KEYDOWN
        };
    } uFlags;

    uFlags.lParam = lParamIn;

    switch ( uFlags.nScanCode )
    {
        case VK_CONTROL:
            m_fControlDown = FALSE;
            lr = TRUE;
            break;

        case VK_MENU:   // ALT
            m_fAltDown = FALSE;
            lr = TRUE;
            break;
    } // switch: scan code

    RETURN( lr );

} //*** CDetailsDlg::OnKeyUp()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnCommand
//
//  Description:
//      Handler for the WM_COMMAND message.
//
//  Arguments:
//      idNotificationIn    - Notification code.
//      idControlIn         - Control ID.
//      hwndSenderIn        - Handle for the window that sent the message.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnCommand(
      UINT  idNotificationIn
    , UINT  idControlIn
    , HWND  hwndSenderIn
    )
{
    TraceFunc( "" );

    LRESULT lr = FALSE;

    switch ( idControlIn )
    {
        case IDC_DETAILS_PB_PREV:
            if ( idNotificationIn == BN_CLICKED )
            {
                lr = OnCommandBnClickedPrev();
            }
            break;

        case IDC_DETAILS_PB_NEXT:
            if ( idNotificationIn == BN_CLICKED )
            {
                lr = OnCommandBnClickedNext();
            }
            break;

        case IDC_DETAILS_PB_COPY:
            if ( idNotificationIn == BN_CLICKED )
            {
                lr = OnCommandBnClickedCopy();
            }
            break;

        case IDCANCEL:
            EndDialog( m_hwnd, IDCANCEL );
            break;

    } // switch: idControlIn

    RETURN( lr );

} //*** CDetailsDlg::OnCommand()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnCommandBnClickedPrev
//
//  Description:
//      Handler for the BN_CLICKED notification on the Prev button.
//
//  Arguments:
//      None.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnCommandBnClickedPrev( void )
{
    TraceFunc( "" );

    LRESULT     lr = FALSE;
    HRESULT     hr;
    HTREEITEM   htiPrev;

    //
    // Find the previous item.
    //

    hr = STHR( m_pttv->HrFindPrevItem( &htiPrev ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Select that item.
    //

    hr = THR( m_pttv->HrSelectItem( htiPrev ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Display the newly selected item.
    //

    if ( htiPrev != NULL )
    {
        hr = THR( HrDisplayItem( htiPrev ) );
    }

    //
    // Update the buttons based on our new position.
    //

    UpdateButtons();

    lr = TRUE;

Cleanup:
    RETURN( lr );

} //*** CDetailsDlg::OnCommandBnClickedPrev()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnCommandBnClickedNext
//
//  Description:
//      Handler for the BN_CLICKED notification on the Next button.
//
//  Arguments:
//      None.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnCommandBnClickedNext( void )
{
    TraceFunc( "" );

    LRESULT     lr = FALSE;
    HRESULT     hr;
    HTREEITEM   htiNext;

    //
    // Find the next item.
    //

    hr = STHR( m_pttv->HrFindNextItem( &htiNext ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Select that item.
    //

    hr = THR( m_pttv->HrSelectItem( htiNext ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Display the newly selected item.
    //

    if ( htiNext != NULL )
    {
        hr = THR( HrDisplayItem( htiNext ) );
    }

    //
    // Update the buttons based on our new position.
    //

    UpdateButtons();

    lr = TRUE;

Cleanup:
    RETURN( lr );

} //*** CDetailsDlg::OnCommandBnClickedNext()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnCommandBnClickedCopy
//
//  Description:
//      Handler for the BN_CLICKED notification on the Copy button.
//
//  Arguments:
//      None.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnCommandBnClickedCopy( void )
{
    TraceFunc( "" );

    LRESULT     lr = FALSE;
    HRESULT     hr;
    DWORD       sc;
    BSTR        bstrClipboard = NULL;
    HGLOBAL     hgbl = NULL;
    LPWSTR      pszGlobal = NULL;
    BOOL        fOpenedClipboard;

    //
    // Open the clipboard.
    //

    fOpenedClipboard = OpenClipboard( m_hwnd );

    if ( ! fOpenedClipboard )
    {
        sc = TW32( GetLastError() );
        hr = HRESULT_FROM_WIN32( sc );
        TraceFlow2( "Can't open clipboard (error = %#08x), currently owned by %#x", sc, GetClipboardOwner() );
        goto Cleanup;
    }

    if ( ! EmptyClipboard() )
    {
        sc = TW32( GetLastError() );
        hr = HRESULT_FROM_WIN32( sc );
        goto Cleanup;
    }

    //
    // Construct the text to put on the clipboard.
    //

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_DATE
                    , IDC_DETAILS_E_DATE
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_TIME
                    , IDC_DETAILS_E_TIME
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_COMPUTER
                    , IDC_DETAILS_E_COMPUTER
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_MAJOR
                    , IDC_DETAILS_E_MAJOR_ID
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_MINOR
                    , IDC_DETAILS_E_MINOR_ID
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_DESC
                    , IDC_DETAILS_RE_DESCRIPTION
                    , true      // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_STATUS
                    , IDC_DETAILS_E_STATUS
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , 0
                    , IDC_DETAILS_RE_STATUS
                    , false     // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    hr = THR( HrAppendControlStringToClipboardString(
                      &bstrClipboard
                    , IDS_DETAILS_CLP_INFO
                    , IDC_DETAILS_RE_REFERENCE
                    , true      // fNewlineBeforeTextIn
                    ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Set the string onto the clipboard.
    //

    {
        //
        // Allocate a global buffer for the string, since
        // clipboard needs this as HGLOBAL.
        //

        hgbl = GlobalAlloc(
                      GMEM_MOVEABLE | GMEM_DDESHARE
                    , ( wcslen( bstrClipboard ) + 1) * sizeof( *bstrClipboard )
                    );
        if ( hgbl == NULL )
        {
            hr = E_OUTOFMEMORY;
            goto Cleanup;
        }

        pszGlobal = (LPWSTR) GlobalLock( hgbl );
        if ( pszGlobal == NULL )
        {
            sc = TW32( GetLastError() );
            hr = HRESULT_FROM_WIN32( sc );
            goto Cleanup;
        }

        wcscpy( pszGlobal, bstrClipboard );

        //
        // Put it on the clipboard.
        //

        if ( SetClipboardData( CF_UNICODETEXT, hgbl ) )
        {
            // System owns it now.
            pszGlobal = NULL;
            hgbl = NULL;
        }
    } // Set the string onto the clipboard

Cleanup:
    TraceSysFreeString( bstrClipboard );

    if ( pszGlobal != NULL )
    {
        GlobalUnlock( hgbl );
    }
    GlobalFree( hgbl );

    if ( fOpenedClipboard )
    {
        CloseClipboard();
    }

    RETURN( lr );

} //*** CDetailsDlg::OnCommandBnClickedCopy()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::HrAppendControlStringToClipboardString
//
//  Description:
//      Append a string from a control on the dialog box to the clipboard string.
//
//  Arguments:
//      pbstrClipboardInout - Clipboard string.
//      idsLabelIn          - ID for the label string resource.
//      idcDataIn           - ID for the control to read the text from.
//      fNewlineBeforeTextIn- TRUE if a newline should be added before the data.
//
//  Return Values:
//      S_OK        - Operation completed successfully.
//
//--
//////////////////////////////////////////////////////////////////////////////
HRESULT
CDetailsDlg::HrAppendControlStringToClipboardString(
      BSTR *    pbstrClipboard
    , UINT      idsLabelIn
    , UINT      idcDataIn
    , bool      fNewlineBeforeTextIn
    )
{
    TraceFunc( "" );

    HRESULT hr          = S_OK;
    LPWSTR  pszData     = NULL;
    HWND    hwndControl = GetDlgItem( m_hwnd, idcDataIn );
    int     cch;

    //
    // Get the string from the control.
    //

    cch = GetWindowTextLength( hwndControl );

    pszData = new WCHAR[ cch + 1 ];
    if ( pszData == NULL )
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }

    GetWindowText( hwndControl, pszData, cch + 1 );

    //
    // Append the string to the clipboard string.
    //

    hr = THR( HrAppendStringToClipboardString( pbstrClipboard, idsLabelIn, pszData, fNewlineBeforeTextIn ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

Cleanup:
    delete [] pszData;

    HRETURN( hr );

} //*** HrAppendControlStringToClipboardString()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  HrAppendStringToClipboardString
//
//  Description:
//      Append a label and data string to the clipboard string.
//
//  Arguments:
//      pbstrClipboardInout - Clipboard string.
//      idsLabelIn          - ID for the label string resource.
//      pszDataIn           - Data string.
//      fNewlineBeforeTextIn- TRUE if a newline should be added before the data.
//
//  Return Values:
//      S_OK        - Operation completed successfully.
//
//--
//////////////////////////////////////////////////////////////////////////////
HRESULT
HrAppendStringToClipboardString(
      BSTR *    pbstrClipboard
    , UINT      idsLabelIn
    , LPCWSTR   pszDataIn
    , bool      fNewlineBeforeTextIn
    )
{
    TraceFunc( "" );

    HRESULT     hr          = S_OK;
    BSTR        bstrLabel   = NULL;
    BSTR        bstr        = NULL;
    LPCWSTR     pszLabel;
    LPCWSTR     pszFmt;

    static const WCHAR  s_szBlank[]         = L"";
    static const WCHAR  s_szNoNewlineFmt[]  = L"%1!ws!%2!ws!\n";
    static const WCHAR  s_szNewlineFmt[]    = L"%1!ws!\n%2!ws!\n";

    //
    // Load the label string.
    //

    if ( idsLabelIn == 0 )
    {
        pszLabel = s_szBlank;
    }
    else
    {
        hr = THR( HrLoadStringIntoBSTR( g_hInstance, idsLabelIn, &bstrLabel ) );
        if ( FAILED( hr ) )
        {
            goto Cleanup;
        }
        pszLabel = bstrLabel;
    }

    //
    // Get the right format string.
    //

    if ( fNewlineBeforeTextIn )
    {
        pszFmt = s_szNewlineFmt;
    }
    else
    {
        pszFmt = s_szNoNewlineFmt;
    }

    //
    // Get the string from the dialog.
    //
    //
    // Format the new label + string.
    //

    hr = THR( HrFormatStringIntoBSTR( pszFmt, &bstr, pszLabel, pszDataIn ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

    //
    // Concatenate the resulting string onto the end of the clipboard string.
    //

    hr = THR( HrConcatenateBSTRs( pbstrClipboard, bstr ) );
    if ( FAILED( hr ) )
    {
        goto Cleanup;
    }

Cleanup:
    TraceSysFreeString( bstrLabel );
    TraceSysFreeString( bstr );

    HRETURN( hr );

} //*** HrAppendStringToClipboardString()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnNotify
//
//  Description:
//      Handle the WM_NOTIFY message.
//
//  Arguments:
//      idCtrlIn    - Control ID.
//      pnmhdrIn    - Notification structure.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnNotify(
      WPARAM    idCtrlIn
    , LPNMHDR   pnmhdrIn
    )
{
    TraceFunc( "" );

    LRESULT lr = FALSE;

    switch( pnmhdrIn->code )
    {
        case EN_LINK:
            lr = OnNotifyEnLink( idCtrlIn, pnmhdrIn );
            break;
    } // switch: notify code

    RETURN( lr );

} //*** CDetailsDlg::OnNotify()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::OnNotifyEnLink
//
//  Description:
//      Handle the WM_NOTIFY message.
//
//  Arguments:
//      idCtrlIn    - Control ID.
//      pnmhdrIn    - Notification structure.
//
//  Return Values:
//      TRUE        - Message has been handled.
//      FALSE       - Message has not been handled yet.
//
//--
//////////////////////////////////////////////////////////////////////////////
LRESULT
CDetailsDlg::OnNotifyEnLink(
      WPARAM    idCtrlIn
    , LPNMHDR   pnmhdrIn
    )
{
    TraceFunc( "" );

    LRESULT     lr = FALSE;
    ENLINK *    penl = (ENLINK *) pnmhdrIn;

    switch( idCtrlIn )
    {
        case IDC_DETAILS_RE_DESCRIPTION:
        case IDC_DETAILS_RE_STATUS:
        case IDC_DETAILS_RE_REFERENCE:
            if ( penl->msg == WM_LBUTTONDOWN )
            {
                //
                // Rich edit notification user has left clicked on link
                //

                m_chrgEnLinkClick = penl->chrg;
            } // if: left button down
            else if ( penl->msg == WM_LBUTTONUP )
            {
                if (    ( penl->chrg.cpMax == m_chrgEnLinkClick.cpMax )
                    &&  ( penl->chrg.cpMin == m_chrgEnLinkClick.cpMin )
                    )
                {
                    ZeroMemory( &m_chrgEnLinkClick, sizeof m_chrgEnLinkClick );
                    HandleLinkClick( penl, idCtrlIn );
                }
            } // else if: left button up
            break;
    } // switch: notify code

    RETURN( lr );

} //*** CDetailsDlg::OnNotifyEnLink()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::HandleLinkClick
//
//  Description:
//      Handle notification that the user has clicked on text in a richedit
//      control that is marked with the hyperlink attribute.
//
//  Arguments:
//      penlIn      - Contains information about link clicked.
//      idCtrlIn    - Control in which user clicked.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CDetailsDlg::HandleLinkClick(
      ENLINK *  penlIn
    , WPARAM    idCtrlIn
    )
{
    TraceFunc( "" );

    Assert( penlIn->chrg.cpMax > penlIn->chrg.cpMin );

    PWSTR       pszLink = NULL;
    ULONG       cch;
    TEXTRANGE   tr;
    DWORD       sc;

    //
    // Get the text of the link.
    //

    cch = penlIn->chrg.cpMax - penlIn->chrg.cpMin + 1;

    pszLink = new WCHAR[ cch ];
    if ( pszLink == NULL )
    {
        goto Cleanup;
    }

    pszLink[ 0 ] = '\0';

    ZeroMemory( &tr, sizeof( tr ) );
    tr.chrg = penlIn->chrg;
    tr.lpstrText = pszLink;

    cch = (ULONG) SendDlgItemMessage(
                      m_hwnd
                    , (int) idCtrlIn
                    , EM_GETTEXTRANGE
                    , 0
                    , (LPARAM) &tr
                    );
    Assert( cch > 0 );

    //
    // Pass the URL straight through to ShellExecute.
    //
    // Note that ShellExecute returns an HINSTANCE for historical reasons,
    // but actually only returns integers.  Any value greater than 32
    // indicates success.
    //

    TraceFlow1( "Calling ShellExecute on %hs", pszLink );
    sc = HandleToULong( ShellExecute( NULL, NULL, pszLink, NULL, NULL, SW_NORMAL ) );
    if ( sc <= 32 )
    {
        TW32( sc );
        THR( HrMessageBoxWithStatus(
                          m_hwnd
                        , IDS_ERR_INVOKING_LINK_TITLE
                        , IDS_ERR_INVOKING_LINK_TEXT
                        , sc
                        , 0         // idsSubStatusIn
                        , ( MB_OK
                          | MB_ICONEXCLAMATION )
                        , NULL      // pidReturnOut
                        , pszLink
                        ) );
    } // if: error from ShellExecute

Cleanup:
    delete [] pszLink;

} //*** CDetailsDlg::HandleLinkClick()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::UpdateButtons
//
//  Description:
//      Update the buttons based on whether there is a previous or next
//      item or not.
//
//  Arguments:
//      None.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CDetailsDlg::UpdateButtons( void )
{
    TraceFunc( "" );

    Assert( m_pttv != NULL );

    HTREEITEM   hti = NULL;
    HRESULT     hr;
    BOOL        fEnablePrev;
    BOOL        fEnableNext;

    STHR( m_pttv->HrFindPrevItem( &hti ) );
    // ignore error
    fEnablePrev = ( hti != NULL );

    STHR( m_pttv->HrFindNextItem( &hti ) );
    // ignore error
    fEnableNext = ( hti != NULL );

    EnableWindow( GetDlgItem( m_hwnd, IDC_DETAILS_PB_PREV ), fEnablePrev );
    EnableWindow( GetDlgItem( m_hwnd, IDC_DETAILS_PB_NEXT ), fEnableNext );

} //*** CDetailsDlg::UpdateButtons()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  CDetailsDlg::HrDisplayItem
//
//  Description:
//      Display an item in the details dialog.
//
//  Arguments:
//      htiIn   - Handle to the item to display.
//
//  Return Values:
//      S_OK    - Operation completed successfully.
//      S_FALSE - Item not displayed.
//
//  Remarks:
//
//--
//////////////////////////////////////////////////////////////////////////////
HRESULT
CDetailsDlg::HrDisplayItem(
    HTREEITEM   htiIn
    )
{
    TraceFunc( "" );

    Assert( htiIn != NULL );

    HRESULT                 hr = S_FALSE;
    DWORD                   sc = ERROR_SUCCESS;
    BOOL                    fRet;
    BOOL                    fDisplayIcon;
    STreeItemLParamData *   ptipd;
    BSTR                    bstr = NULL;
    BSTR                    bstrAdditionalInfo = NULL;
    WCHAR                   wszText[ 64 ];
    FILETIME                filetime;
    SYSTEMTIME              systemtime;
    int                     cch;
    HICON                   hicon;

    //
    // Get information about the selected item to see if it has details.
    //

    fRet = m_pttv->FGetItem( htiIn, &ptipd );
    if ( ! fRet )
    {
        goto Cleanup;
    }

    //
    // Set the date and time information from the structure into
    // the dialog box.
    //

    if (    ( ptipd->ftTime.dwHighDateTime == 0 )
        &&  ( ptipd->ftTime.dwLowDateTime == 0 )
        )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_DATE, L"" );
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_TIME, L"" );
    } // if: no date time specified
    else
    {
        //
        // Convert the date time to local time, then to something we can
        // use to display it.
        //

        if ( ! FileTimeToLocalFileTime( &ptipd->ftTime, &filetime ) )
        {
            sc = TW32( GetLastError() );
        }
        else if ( ! FileTimeToSystemTime( &filetime, &systemtime ) )
        {
            sc = TW32( GetLastError() );
        }
        if ( sc == ERROR_SUCCESS )
        {
            //
            // Get the date string and display it.
            //

            cch = GetDateFormat(
                          LOCALE_USER_DEFAULT
                        , DATE_SHORTDATE
                        , &systemtime
                        , NULL          // lpFormat
                        , wszText
                        , ARRAYSIZE( wszText )
                        );
            if ( cch == 0 )
            {
                sc = TW32( GetLastError() );
            }
            SetDlgItemText( m_hwnd, IDC_DETAILS_E_DATE, wszText );

            //
            // Get the time string and display it.
            //

            cch = GetTimeFormat(
                          LOCALE_USER_DEFAULT
                        , 0
                        , &systemtime
                        , NULL      // lpFormat
                        , wszText
                        , ARRAYSIZE( wszText )
                        );
            if ( cch == 0 )
            {
                sc = TW32( GetLastError() );
            }
            SetDlgItemText( m_hwnd, IDC_DETAILS_E_TIME, wszText );
        } // if: time converted successfully
        else
        {
            SetDlgItemText( m_hwnd, IDC_DETAILS_E_DATE, L"" );
            SetDlgItemText( m_hwnd, IDC_DETAILS_E_TIME, L"" );
        }
    } // else: date time specified

    //
    // Set the task IDs.
    //

    THR( HrFormatGuidIntoBSTR( &ptipd->clsidMajorTaskId, &bstr ) );
    if ( SUCCEEDED( hr ) )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_MAJOR_ID, bstr );
    }

    hr = THR( HrFormatGuidIntoBSTR( &ptipd->clsidMinorTaskId, &bstr ) );
    if ( SUCCEEDED( hr ) )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_MINOR_ID, bstr );
    }

    //
    // Set the text information.
    //

    // Node name.
    if ( ptipd->bstrNodeName == NULL )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_COMPUTER, L"" );
    }
    else
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_E_COMPUTER, ptipd->bstrNodeName );
    }

    // Description.
    if ( ptipd->bstrDescription == NULL )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_RE_DESCRIPTION, L"" );
    }
    else
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_RE_DESCRIPTION, ptipd->bstrDescription );
    }

    // Reference.
    hr = THR( HrLoadStringIntoBSTR( g_hInstance, IDS_DEFAULT_DETAILS_REFERENCE, &bstrAdditionalInfo ) );
    if ( SUCCEEDED( hr ) )
    {
        if (    ( ptipd->bstrReference == NULL )
            ||  ( *ptipd->bstrReference == L'\0' )
            )
        {
            bstr = bstrAdditionalInfo;
            bstrAdditionalInfo = NULL;
        } // if: no reference specified
        else
        {
            hr = THR( HrFormatStringIntoBSTR( L"%s\n\n%s", &bstr, ptipd->bstrReference, bstrAdditionalInfo ) );
        } // else: reference specified
    }

    SetDlgItemText( m_hwnd, IDC_DETAILS_RE_REFERENCE, bstr );

    //
    // Set the status information.
    //

    _snwprintf( wszText, ARRAYSIZE( wszText ), L"0x%08x", ptipd->hr );
    SetDlgItemText( m_hwnd, IDC_DETAILS_E_STATUS, wszText );

    hr = ptipd->hr;
    if ( hr == S_FALSE )
    {
        hr = THR( HrFormatStringIntoBSTR( L"S_FALSE", &bstr ) );
    }
    else
    {
        // If not S_OK, this is a warning, so set the high bit before
        // translating to text, since the text string will not be found
        // without doing this.
        if ( hr != S_OK )
        {
            hr |= 0x80000000;
        }
        hr = THR( HrFormatErrorIntoBSTR( hr, &bstr ) );
    } // else: hr not S_FALSE
    if ( SUCCEEDED( hr ) )
    {
        SetDlgItemText( m_hwnd, IDC_DETAILS_RE_STATUS, bstr );
        if ( ptipd->hr == S_OK )
        {
            fDisplayIcon = FALSE;
        }
        else
        {
            fDisplayIcon = TRUE;
            if ( FAILED( ptipd->hr ) )
            {
                hicon = m_hiconError;
            }
            else
            {
                hicon = m_hiconWarn;
            }
            SendDlgItemMessage( m_hwnd, IDC_DETAILS_I_STATUS, STM_SETIMAGE, IMAGE_ICON, (LPARAM) hicon );
        } // else: status not informational
    }
    ShowWindow( GetDlgItem( m_hwnd, IDC_DETAILS_I_STATUS ), fDisplayIcon ? SW_SHOW : SW_HIDE );

    hr = S_OK;

Cleanup:
    TraceSysFreeString( bstr );
    TraceSysFreeString( bstrAdditionalInfo );

    HRETURN( hr );

} //*** CDetailsDlg::HrDisplayItem()


//****************************************************************************
//
//  Private Functions
//
//****************************************************************************


//////////////////////////////////////////////////////////////////////////////
//++
//
//  ButtonFaceColorIsDark
//
//  Description:
//      Return TRUE if the button face color is dark (implying that
//      the light colored button icons should be used).
//
//  Arguments:
//      None.
//
//  Return Values:
//      TRUE        - Button face color is dark.
//      FALSE       - Button face color is light.
//
//--
//////////////////////////////////////////////////////////////////////////////
BOOL
ButtonFaceColorIsDark( void )
{
    TraceFunc( "" );

    COLORREF    rgbBtnFace = GetSysColor( COLOR_BTNFACE );

    ULONG   ulColors = GetRValue( rgbBtnFace ) +
                       GetGValue( rgbBtnFace ) +
                       GetBValue( rgbBtnFace );

    RETURN( ulColors < 300 );  // arbitrary threshold

} //*** ButtonFaceColorIsDark()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  SetButtonImage
//
//  Description:
//      Set an image on a button.
//
//  Arguments:
//      hwndBtnIn   - Handle to the button window.
//      idIconIn    - ID for the icon resource.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
SetButtonImage(
      HWND  hwndBtnIn
    , ULONG idIconIn
    )
{
    TraceFunc( "" );

    HICON hIcon = (HICON) LoadImage( g_hInstance,
                                     MAKEINTRESOURCE( idIconIn ),
                                     IMAGE_ICON,
                                     16,
                                     16,
                                     LR_DEFAULTCOLOR
                                     );
    if ( hIcon != NULL )
    {
        HICON hIconPrev = (HICON) SendMessage( hwndBtnIn,
                                               BM_SETIMAGE,
                                               (WPARAM) IMAGE_ICON,
                                               (LPARAM) hIcon
                                               );

        if ( hIconPrev )
        {
            DestroyIcon( hIconPrev );
        }
    }

    TraceFuncExit();

} //*** SetButtonImage()

//////////////////////////////////////////////////////////////////////////////
//++
//
//  FreeButtonImage
//
//  Description:
//      Free an image used by button.
//
//  Arguments:
//      hwndBtnIn   - Handle to the button window.
//
//  Return Values:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
FreeButtonImage(
    HWND hwndBtnIn
    )
{
    HANDLE hIcon = (HANDLE) SendMessage( hwndBtnIn, BM_GETIMAGE, IMAGE_ICON, 0 );

    if ( hIcon != NULL )
    {
        SendMessage( hwndBtnIn, BM_SETIMAGE, IMAGE_ICON, 0 );
        DestroyIcon( (HICON) hIcon );
    }

} //*** FreeButtonImage()