/*************************************************************************
    Project:    AXAccess
    Module:     SNAPSHOT

    Author:     Laura Butler, Steve Donie & Peter Wong
    Date:       
    
    Notes:      
      Simple applet that lets the end user choose a top level window, then
      shows all the objects in outline fashion which are within it.  It
      uses AXA to do so.

    Copyright (C) 1996 by Microsoft Corporation.  All rights reserved.
    See bottom of file for disclaimer
    
    History:    05/01/96 LauraBu Created.
*************************************************************************/
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <initguid.h>
#include <objbase.h>
#include <objerror.h>
#include <ole2.h>
#include <oleacc.h>
#include <winable.h>
#include <commctrl.h>
#include "commdlg.h"
#include "snapshot.h"
#include "resource.h"

//
// Variables
//
HWND        hwndMain = NULL;
HWND        hwndTreeView = NULL;
HINSTANCE   hinstApp = NULL;
TCHAR       szSnapShotClass[] = TEXT("SnapShotClass");
BOOL        fSkipInvisible = TRUE;

//
// Functions
//
BOOL PASCAL FInitialize(HINSTANCE);
void PASCAL Terminate(void);

LRESULT CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK WndListEnumProc(HWND, LPARAM);

void    PickAWindow(HWND hwnd);
void    InitWndList(HWND hwndList);
void    DrawWndListItem(LPDRAWITEMSTRUCT lpdis);
void    FillInTreeView(HWND hwndDisplay);
long    AddAccessibleObjects(IAccessible*, VARIANT*, HTREEITEM htreeParent);
HTREEITEM    AddTreeViewItem(IAccessible*, VARIANT*, HTREEITEM);
void    SaveToFile(HWND hwnd);
void    DumpTreeViewItems(HFILE, HTREEITEM, int);
void    ExpandAll(HWND hwnd, HTREEITEM hItem, UINT nAction);

// 
// Constants
//
#define CMS_HOVERTIMER  500
#define ID_HOVERTIMER   50

// --------------------------------------------------------------------------
//
//  WinMain()
//
// --------------------------------------------------------------------------
int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    if (FInitialize(hinst))
    {
        MSG     msg;

        // Put up the choose window dialog on startup
        PickAWindow(hwndMain);

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

    Terminate();

    return(0);
}



// --------------------------------------------------------------------------
//
//  FInitialize()
//
//  This creates our main window with an empty listview, then shows us.
//
// --------------------------------------------------------------------------
BOOL PASCAL FInitialize(HINSTANCE hInstance)
{
    WNDCLASSEX      wc;
    INITCOMMONCONTROLSEX ice;

    hinstApp = hInstance;

    // Init OLE
    CoInitialize(NULL);

    //
    // Register our window class
    //
    wc.cbSize       = sizeof(wc);
    wc.style        = 0;
    wc.lpfnWndProc  = MyWndProc;
    wc.cbClsExtra   = 0;
    wc.cbWndExtra   = 0;
    wc.hInstance    = hInstance;
    wc.hIcon        = LoadIcon(hinstApp, MAKEINTRESOURCE(IDI_SNAPSHOT));
    wc.hCursor      = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = MAKEINTRESOURCE(IDM_MENU);
    wc.lpszClassName = szSnapShotClass;
    wc.hIconSm      = NULL;

    if (!RegisterClassEx(&wc))
        return(FALSE);

    // Init COMCTL32
    ice.dwSize = sizeof(ice);
    ice.dwICC = ICC_TREEVIEW_CLASSES;
    if (!InitCommonControlsEx(&ice))
        return(FALSE);

    //
    // Create our main window
    //
    hwndMain = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_APPWINDOW,
        szSnapShotClass, "Snapshot", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
        hInstance, NULL);
    if (!hwndMain)
        return(FALSE);

    ShowWindow(hwndMain, SW_SHOW);
    UpdateWindow(hwndMain);

    return(TRUE);
}



// --------------------------------------------------------------------------
//
//  Terminate()
//
// --------------------------------------------------------------------------
void PASCAL Terminate(void)
{
    CoUninitialize();

    if (hwndMain)
    {
        DestroyWindow(hwndMain);
        hwndMain = NULL;
    }

}




// --------------------------------------------------------------------------
//
//  MyWndProc()
//
// --------------------------------------------------------------------------
LRESULT CALLBACK
MyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT    rc;

    switch (uMsg)
    {
        case WM_CREATE:
            //
            // Create a listview outline
            //
            GetClientRect(hwnd, &rc);
            hwndTreeView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW,
                "Object Outline", WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE
                | TVS_HASBUTTONS | TVS_HASLINES | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS,
                rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
                hwnd, NULL, hinstApp, NULL);
            if (!hwndTreeView)
                return(-1);
            // Set timer for checking pointer hovering
            SetTimer(hwnd, ID_HOVERTIMER, CMS_HOVERTIMER, NULL);
            break;

        case WM_SIZE:
            if (wParam != SIZEICONIC)
                SetWindowPos(hwndTreeView, NULL, 0, 0, LOWORD(lParam),
                    HIWORD(lParam), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            break;

        case WM_DESTROY:
            KillTimer(hwnd, ID_HOVERTIMER);

            hwndMain = NULL;
            PostQuitMessage(0);
            break;

        case WM_TIMER:
            // Update treeview when pointer hovors 
            if (wParam == ID_HOVERTIMER)
            {
                //
                // Do not show info when mouse is moving.
                // When cursor settles down, do once.
                //
                static BOOL fDidAlready = FALSE;
                static POINT pointOld = {0,0};
                POINT pointNew;
                GetCursorPos(&pointNew);
                if (pointOld.x == pointNew.x &&
                    pointOld.y == pointNew.y)
                {
                    if (!fDidAlready)
                    {
                        FillInTreeView(0);
                        fDidAlready = TRUE;
                    }
                }
                else
                {
                    pointOld = pointNew;
                    fDidAlready = FALSE;
                }
            }
            else
                return(FALSE);
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case CMD_EXIT:
                    PostMessage(hwnd, WM_CLOSE, 0, 0);
                    break;

                case CMD_CHOOSEWINDOW:
                    PickAWindow(hwnd);
                    break;

                case CMD_SAVEAS:
                    SaveToFile(hwnd);
                    if (hwndTreeView) SetFocus(hwndTreeView);
                    break;

                case CMD_EXPANDTREE:
                   {
                    HTREEITEM hRoot, hCurrentItem;
                    hRoot = TreeView_GetRoot(hwndTreeView);
                    if (hRoot) 
                    {
                        hCurrentItem = TreeView_GetSelection(hwndTreeView);
                        ExpandAll(hwndTreeView, hRoot, TVE_EXPAND);
                        TreeView_Select(hwndTreeView, hCurrentItem, TVGN_FIRSTVISIBLE);
                    }
                    break;
                   }
                case ID_VIEW_COLLAPSEALL:
                    {    
                    HTREEITEM hRoot, hCurrentItem;
                    hRoot = TreeView_GetRoot(hwndTreeView);
                    if (hRoot) 
                        {
                        hCurrentItem = TreeView_GetSelection(hwndTreeView);
                        ExpandAll(hwndTreeView, hRoot, TVE_COLLAPSE);
                        TreeView_Select(hwndTreeView, hCurrentItem, TVGN_FIRSTVISIBLE);
                        }
                    break;
                   }

                case CMD_SKIPINVISIBLE:
                    fSkipInvisible = !fSkipInvisible;
                    CheckMenuItem(GetMenu(hwnd), CMD_SKIPINVISIBLE,
                        (fSkipInvisible ? MF_CHECKED : MF_UNCHECKED) |
                        MF_BYCOMMAND);
                    break;
            }
            break;

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

    return(0);
}



// --------------------------------------------------------------------------
//
//  MyDlgProc()
//
// --------------------------------------------------------------------------
BOOL CALLBACK
MyDlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    long    lCurSel;

    switch (uMsg)
    {
        case WM_INITDIALOG:
            InitWndList(GetDlgItem(hdlg, IDD_WNDLIST));
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDD_WNDLIST:
                    if (GET_WM_COMMAND_CMD(wParam, lParam) != LBN_DBLCLK)
                        break;
                    // FALL THRU

                case IDOK:
                    lCurSel = SendDlgItemMessage(hdlg, IDD_WNDLIST, LB_GETCURSEL, 0, 0);
                    if (lCurSel != -1)
                        lCurSel = SendDlgItemMessage(hdlg, IDD_WNDLIST, LB_GETITEMDATA, lCurSel, 0);
                    else
                        lCurSel = 0;
                    EndDialog(hdlg, lCurSel);
                    break;

                case IDCANCEL:
                    EndDialog(hdlg, 0);
                    break;
            }
            break;

        case WM_DRAWITEM:
            DrawWndListItem((LPDRAWITEMSTRUCT)lParam);
            break;

        default:
            return(FALSE);
    }

    return(TRUE);
}



// --------------------------------------------------------------------------
//
//  InitWndList()
//
//  Fills in the listbox with the top level windows hanging around.
//
// --------------------------------------------------------------------------
void InitWndList(HWND hwndList)
{
    SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
    SendMessage(hwndList, WM_SETREDRAW, FALSE, 0);

    EnumWindows(WndListEnumProc, (LPARAM)hwndList);

    SendMessage(hwndList, WM_SETREDRAW, TRUE, 0);
    InvalidateRect(hwndList, NULL, TRUE);
    UpdateWindow(hwndList);
}



// --------------------------------------------------------------------------
//
//  WndListEnumProc()
//
//  This adds a string + the hwnd of this window into our list of top level
//  windows + desktop.
//
//  The string is
//      <window text>, <class text>, [(invisible)]
//
// --------------------------------------------------------------------------
BOOL CALLBACK
WndListEnumProc(HWND hwndAdd, LPARAM lParam)
{
    HWND    hwndList;
    TCHAR   szText[256];
    TCHAR   szWindow[128];
    TCHAR   szClass[128];
    long    lIndex;
    BOOL    fInvisible;

    hwndList = (HWND)lParam;

    fInvisible = !IsWindowVisible(hwndAdd);
    if (!fInvisible || !fSkipInvisible)
    {
        GetWindowText(hwndAdd, szWindow, sizeof(szWindow) / sizeof(TCHAR));
        if (! *szWindow)
            lstrcpy(szWindow, "NAMELESS");
        GetClassName(hwndAdd, szClass, sizeof(szClass) / sizeof(TCHAR));

        wsprintf(szText, "%s (%s%s)", szWindow, szClass,
            (fInvisible ? "  - invisible" : ""));

        lIndex = SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)szText);
        if (lIndex == -1)
            return(FALSE);

        SendMessage(hwndList, LB_SETITEMDATA, lIndex, (LPARAM)hwndAdd);
    }
    return(TRUE);
}




// --------------------------------------------------------------------------
//
//  DrawWndListItem()
//
// --------------------------------------------------------------------------
void DrawWndListItem(LPDRAWITEMSTRUCT lpdis)
{
    TCHAR   szItemString[256];
    DWORD   rgbBk;
    DWORD   rgbText;

    if (lpdis->itemID == -1)
        return;

    //
    // Get text
    //
    *szItemString = 0;
    SendMessage(lpdis->hwndItem, LB_GETTEXT, lpdis->itemID, (LPARAM)szItemString);

    //
    // Set colors
    //
    if (lpdis->itemState & ODS_SELECTED)
    {
        rgbBk = SetBkColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHT));
        rgbText = SetTextColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
    }

    //
    // Draw text
    //
    ExtTextOut(lpdis->hDC, lpdis->rcItem.left+2, lpdis->rcItem.top+1,
        ETO_OPAQUE | ETO_CLIPPED, &lpdis->rcItem, szItemString, lstrlen(szItemString), NULL);

    //
    // Focus rect needed?
    //
    if (lpdis->itemState & ODS_FOCUS)
        DrawFocusRect(lpdis->hDC, &lpdis->rcItem);

    //
    // Restore colors
    //
    if (lpdis->itemState & ODS_SELECTED)
    {
        SetBkColor(lpdis->hDC, rgbBk);
        SetTextColor(lpdis->hDC, rgbText);
    }
}



// --------------------------------------------------------------------------
//
//  PickAWindow()
//
//  This puts up a dialog that lists the top level windows around (visible
//  and not).  The end user can select one, and that is returned to this
//  function via DialogBoxParam().  Then we go enumerate that dudes children
//  and fill in the treeview.
//
// --------------------------------------------------------------------------
void PickAWindow(HWND hwndMe)
{
    HWND    hwndDisplay;

    hwndDisplay = (HWND)DialogBoxParam(hinstApp, MAKEINTRESOURCE(IDD_PICK),
        hwndMe, MyDlgProc, 0);

    if (hwndDisplay)
    {
        FillInTreeView(hwndDisplay);
    }

    return;
}



// --------------------------------------------------------------------------
//
//  FillInTreeView()
//
//  Fills in a tree view with the descendants of the given top level window.
//  If hwnd is 0, use the previously saved hwnd to build the tree.
//
// --------------------------------------------------------------------------
void FillInTreeView(HWND hwndDisplay)
{
    IAccessible*  pacc;
    static HWND hwndOld=NULL;
    // If there is a hwnd passed in, we just show the tree for it.
    if (hwndDisplay) hwndOld = hwndDisplay;
    // If the foreground window is not the window of interest, bale out.
    else if (GetForegroundWindow()!=hwndOld) return;
    // If the foreground window is the window of interest, we show the tree for it.
    else {hwndDisplay = hwndOld; MessageBeep(0);}

    //
    // Clear out the outline view
    //
    TreeView_DeleteAllItems(hwndTreeView);

    //
    // Get the object for the root.
    //
    pacc = NULL;
    AccessibleObjectFromWindow(hwndDisplay, OBJID_WINDOW, IID_IAccessible,
        (void**)&pacc);
    if (pacc)
    {
        VARIANT varT;
        HCURSOR hcurPrevious;

        hcurPrevious = SetCursor(LoadCursor(NULL, IDC_WAIT));

        //
        // Add the root item and its children.
        //
        VariantInit(&varT);
        AddAccessibleObjects(pacc, &varT, TVI_ROOT);
        pacc->Release();

        SetCursor(hcurPrevious);
    }

    SetFocus(hwndTreeView);        
}



// --------------------------------------------------------------------------
//
//  AddTreeViewItem()
//
// --------------------------------------------------------------------------
HTREEITEM AddTreeViewItem(IAccessible* pacc, VARIANT* pvar, HTREEITEM hParent)
{
    TCHAR           szItemString[384];
    TVINSERTSTRUCT  tvi;
    TCHAR           szName[128];
    TCHAR           szRole[128];
    TCHAR           szState[128];
	char			szClass[128];
	HWND			hwnd;
    RECT            rc;
    VARIANT         varT;
    BSTR            bszT;

    //
    // Get object state first.  If we are skipping invisible dudes, we want
    // to bail out now.
    //
    VariantInit(&varT);
    pacc->get_accState(*pvar, &varT);
    if (varT.vt == VT_BSTR)
        WideCharToMultiByte(CP_ACP, 0, varT.bstrVal, -1, szState, 128, NULL,  NULL);
    else if (varT.vt == VT_I4)
    {
        // Only use the ones we care about
        varT.lVal &= STATE_SYSTEM_UNAVAILABLE | STATE_SYSTEM_SELECTED |
            STATE_SYSTEM_FOCUSED | STATE_SYSTEM_INVISIBLE;

        // Bail out if shown
        if ((varT.lVal & STATE_SYSTEM_INVISIBLE) && fSkipInvisible)
            return(NULL);

        GetStateText(varT.lVal, szState, 128);
    }
    VariantClear(&varT);

    //
    // Get object name.
    //
    bszT = NULL;
    pacc->get_accName(*pvar, &bszT);
    if (bszT)
    {
        WideCharToMultiByte(CP_ACP, 0, bszT, -1, szName, 128, NULL, NULL);
        SysFreeString(bszT);
    }
    else
        lstrcpy(szName, "NAMELESS");

    //
    // Get object role.
    //
    VariantInit(&varT);
    pacc->get_accRole(*pvar, &varT);
    if (varT.vt == VT_BSTR)
        WideCharToMultiByte(CP_ACP, 0, varT.bstrVal, -1, szRole, 128, NULL, NULL);
    else if (varT.vt == VT_I4)
        GetRoleText(varT.lVal, szRole, 128);
    else
        lstrcpy(szRole, "UNKNOWN");
    VariantClear(&varT);

    //
    // Get object location
    //
    SetRectEmpty(&rc);
    pacc->accLocation((long*)&rc.left, (long*)&rc.top, (long*)&rc.right,
        (long*)&rc.bottom, *pvar);

    //
    // Get window handle and class name
    //
    WindowFromAccessibleObject (pacc,&hwnd);
    GetClassName (hwnd,szClass,sizeof(szClass));
	
    wsprintf(szItemString, "%s %s {%04d, %04d, %04d, %04d} (%s) 0x%lX '%s'",
        szName, szRole, rc.left, rc.top, rc.left+rc.right, rc.top+rc.bottom,
        szState,hwnd,szClass);

    //OutputDebugString (szItemString);
    //OutputDebugString ("\r\n");

    tvi.hParent = hParent;
    tvi.hInsertAfter = TVI_LAST;
    tvi.item.mask = TVIF_TEXT;
    tvi.item.hItem = NULL;
    tvi.item.state = 0;
    tvi.item.stateMask = 0;
    tvi.item.pszText = szItemString;
    tvi.item.cchTextMax = lstrlen(szItemString);
    tvi.item.iImage = 0;
    tvi.item.iSelectedImage = 0;
    tvi.item.cChildren = 0;
    tvi.item.lParam = 0;

    return(TreeView_InsertItem(hwndTreeView, &tvi));
}
        

// --------------------------------------------------------------------------
//
//  AddAccessibleObjects()
//
//  This is a recursive function.  It adds an item for the parent, then 
//  adds items for its children if it has any.  It returns the number of
//  items added.  That's because we assign hokey HTREEITEM values that
//  are just index+1s.  Once filled in, the treeview stays constant.
//
//	Parameters:
//	IAccessible*	pacc	Pointer to an IAccessible interface for the
//							object being added. Start with the 'root' object.
//	VARIANT*		pvarT	Pointer to a variant that contains the ID of the
//							child object to retrieve.
//	HTREEITEM		hParent	Handle to the tree item to add
//
//	The first call, pacc points to the top level window object,
//					pvarT points to a variant that is VT_EMPTY
//					hParent is TVI_ROOT
// 
//	There are 2 possible ways to find all the children of an object:
//	The first, and most efficient, is to ask for the IEnumVARIANT interface,
//	which (if it is supported) will allow you to quickly get all the 
//	children, or if that interface does not exist, then you should call
//  IAccessible:get_accChildCount to see how many children there are, then
//	IAccessible:get_accChild to get each child, passing child id's of 1,2,3,
//  ..cChildren.
//
// --------------------------------------------------------------------------
long AddAccessibleObjects(IAccessible* pacc, VARIANT* pvarT, HTREEITEM hParent)
{
    VARIANT         varT;
    long            lAdded;
    HTREEITEM       hChild;

    lAdded = 0;

    hChild = AddTreeViewItem(pacc, pvarT, hParent);
    if (hChild)
    {
        lAdded++;

        //
        // Is this an object in its own right?
		// We can tell it is an object because the variant is empty.
		// 
		// If the variant is not empty, the value is the child ID.
        //
        if (pvarT->vt == VT_EMPTY)
        {
            IEnumVARIANT*   penum;		// pointer to enumeration interface
            IDispatch*      pdisp;		// pointer to dispatch interface
            IAccessible*    paccChild;	// pointer to child's accessible interface
            int             ichild;		// which child (1...cchildren)
            long            cchildren;	// number of children
            ULONG           cfetched;	// number of objects returned (used by penum->Next)

            //
            // Loop through our children.
            //
            penum = NULL;
            pacc->QueryInterface(IID_IEnumVARIANT, (void**)&penum);

            if (penum)
                penum->Reset();

            cchildren = 0;
            pacc->get_accChildCount(&cchildren);
            ichild = 1;

            while (ichild <= cchildren)
            {
                VariantInit(&varT);

                //      
                // Get the next child.
                //
                if (penum)
                {
                    cfetched = 0;
                    penum->Next(1, &varT, &cfetched);	// get next (1 child,child in varT,&cfetched = num children-should be 1)
														// note that varT may be an IDispatch interface
                    if (!cfetched)
                        break;	// leave the while (all children) loop
                }
                else
                {
                    varT.vt = VT_I4;
                    varT.lVal = ichild;
                }

				// varT can have two values right now. It can be a VT_I4 or
				// a VT_DISPATCH. If it is a VT_I4, it is either because
				// we set it that way (the parent object doesn't support 
				// IEnumVARIANT), or because the parent wants us to talk to
				// it to get information about its children. 
				//
				// In any case, if varT is a VT_I4, we now need to try to 
				// get a pointer to the Child's IAccessible by asking the 
				// parent for it. (pacc->get_accChild) The parent will 
				// either give us the child's IAccessible by changing 
				// varT to be VT_DISPATCH and filling in pdisp, or it
				// will return the child's ID in varT and pdisp will be 
				// null. If pdisp is null, then we call AddAccessibleObjects 
				// with the child's ID, otherwise we jump down to the place 
				// we would be at if we got back a VT_DISPATCH when we called
				// penum->Next().
				//
				// If varT is a VT_DISPATCH, then we QI for IAccessible
				// using varT.pdispval and then call AddAccessibleObjects()
				if (varT.vt == VT_I4) 
                {
                    //
                    // Is this child really an object, make sure?
                    //
                    pdisp = NULL;
                    pacc->get_accChild(varT, &pdisp);
                    if (pdisp)
                        goto ChildIsAnObject;

                    // Add just it to the outline
                    lAdded += AddAccessibleObjects(pacc, &varT, hChild);
                }
                else if ((varT.vt == VT_DISPATCH) && varT.pdispVal)
                {
                    pdisp = varT.pdispVal;

ChildIsAnObject:
                    paccChild = NULL;
                    pdisp->QueryInterface(IID_IAccessible, (void**)&paccChild);
                    pdisp->Release();

                    //
                    // Add this object and all its children to the tree also
                    //
                    if (paccChild)
                    {
                        VariantInit(&varT);
                        lAdded += AddAccessibleObjects(paccChild, &varT, hChild);
                        paccChild->Release();
                    }
                }
                else
                    VariantClear(&varT);

                ichild++;
            }

            if (penum)
                penum->Release();
        }

        //
        // If we are all done, expand the first level.
        //
        if (hParent == TVI_ROOT)
            TreeView_Expand(hwndTreeView, hChild, TVE_EXPAND);
    }

    return(lAdded);
}



// --------------------------------------------------------------------------
//
//  SaveToFile()
//
//  This saves the current object listing (expanded fully) into a file
//
// --------------------------------------------------------------------------
void SaveToFile(HWND hwndMe)
{
    TCHAR           szFile[MAX_PATH+1]="";
    OPENFILENAME    ofn;
    HTREEITEM		hTop;
    DWORD			ErrCode;
    char			szErrMsg[255];

    //
    // Is there anything in the client?
    //
    hTop = TreeView_GetRoot(hwndTreeView);
    if (!hTop)
    {
        MessageBeep(0);
        return;
    }

	//
    // Init the OFN structure for COMDLG32
    //
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner   = hwndMe;
    ofn.hInstance   = hinstApp;
    ofn.lpstrFilter = NULL;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = NULL;
    ofn.Flags = OFN_EXPLORER;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = TEXT("txt");
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    if (GetSaveFileName(&ofn))
    {
        HFILE   hFile;

        hFile = _lcreat(szFile, 0);
        if (hFile != (HFILE)-1)
        {
            HCURSOR hcur;

            // Dump stuff out
            hcur = SetCursor(LoadCursor(NULL, IDC_WAIT));
            DumpTreeViewItems(hFile, hTop, 0);
            SetCursor(hcur);

            _lclose(hFile);
        }
    }
	else	// GetSaveFileName failed
	{
		if (ErrCode = CommDlgExtendedError() != 0)
		{
			switch (ErrCode)
			{
				case CDERR_DIALOGFAILURE: 
					lstrcpy (szErrMsg,"The dialog box could not be created. The common dialog box function's call to the DialogBox function failed. For example, this error occurs if the common dialog box call specifies an invalid window handle.");
					break;
				case CDERR_FINDRESFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function failed to find a specified resource.");
					break;
				case CDERR_INITIALIZATION:	
					lstrcpy (szErrMsg,"The common dialog box function failed during initialization. This error often occurs when sufficient memory is not available.");
					break;
				case CDERR_LOADRESFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function failed to load a specified resource.");
					break;
				case CDERR_LOADSTRFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function failed to load a specified string.");
					break;
				case CDERR_LOCKRESFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function failed to lock a specified resource.");
					break;
				case CDERR_MEMALLOCFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function was unable to allocate memory for internal structures.");
					break;
				case CDERR_MEMLOCKFAILURE:	
					lstrcpy (szErrMsg,"The common dialog box function was unable to lock the memory associated with a handle.");
					break;
				case CDERR_NOHINSTANCE:
					lstrcpy (szErrMsg,"The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but you failed to provide a corresponding instance handle.");
					break;
				case CDERR_NOHOOK:
					lstrcpy (szErrMsg,"The ENABLEHOOK flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but you failed to provide a pointer to a corresponding hook procedure.");
					break;
				case CDERR_NOTEMPLATE:
					lstrcpy (szErrMsg,"The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but you failed to provide a corresponding template.");
					break;
				case CDERR_REGISTERMSGFAIL:
					lstrcpy (szErrMsg,"The RegisterWindowMessage function returned an error code when it was called by the common dialog box function.");
					break;
				case CDERR_STRUCTSIZE:
					lstrcpy (szErrMsg,"The lStructSize member of the initialization structure for the corresponding common dialog box is invalid.");
					break;
				case FNERR_BUFFERTOOSMALL:
					lstrcpy (szErrMsg,"The buffer pointed to by the lpstrFile member of the OPENFILENAME structure is too small for the filename specified by the user. The first two bytes of the lpstrFile buffer contain an integer value specifying the size, in bytes (ANSI version) or characters (Unicode version), required to receive the full name. ");
					break;
				case FNERR_INVALIDFILENAME:
					lstrcpy (szErrMsg,"A filename is invalid.");
					break;
				case FNERR_SUBCLASSFAILURE:
					lstrcpy (szErrMsg,"An attempt to subclass a list box failed because sufficient memory was not available.");
					break;
				default:
					lstrcpy (szErrMsg,"Unknown Error.");
					break;
			}

			MessageBeep(MB_ICONASTERISK);
			MessageBox (NULL,szErrMsg,"Can't Save! Poo.",MB_OK|MB_ICONASTERISK);
		}
	}

    return;
}



// --------------------------------------------------------------------------
//
//  DumpTreeViewItems()
//
//  This dumps out ourself, then our children.  We make the depth correspond
//  to the tab level to get an outline.
//
// --------------------------------------------------------------------------
void DumpTreeViewItems(HFILE hf, HTREEITEM hUs, int iIndent)
{
    HTREEITEM   hChild;
    TVITEM      tvi;
    TCHAR       szItem[384];
    int         iTab;

    //
    // Get the item's string
    //
    tvi.mask = TVIF_TEXT;
    tvi.hItem = hUs;
    tvi.pszText = szItem;
    tvi.cchTextMax = 384;

    *szItem = 0;
    TreeView_GetItem(hwndTreeView, &tvi);
    
    // 
    // Print indents, string, then newline
    //
    for (iTab = 0; iTab < iIndent; iTab++)
        _lwrite(hf, "\t", 1);
    _lwrite(hf, szItem, lstrlen(szItem));
    _lwrite(hf, "\nt", 1);


    //
    // Recurse through our children.
    //
    hChild = TreeView_GetNextItem(hwndTreeView, hUs, TVGN_CHILD);
    while (hChild)
    {
        DumpTreeViewItems(hf, hChild, iIndent+1);
        hChild = TreeView_GetNextItem(hwndTreeView, hChild, TVGN_NEXT);
    }

    return;
}

/*************************************************************************
    Function:   ExpandAll
    Purpose:    Expand everything recursively under the given treeview item 
                & its next sibling.
    Inputs:     hwnd    The window handle associated with the treeview control
                hItem   Handle to the treeview item to expand on.
    Returns:    None.
    History:    10/09/96 PeteW Created.
*************************************************************************/
void ExpandAll(HWND hwnd, HTREEITEM hItem, UINT nAction)
{
    HTREEITEM hChild, hNext;

    if (TreeView_Expand(hwnd, hItem, nAction))
    {
        hChild = TreeView_GetChild(hwnd, hItem);
        if (hChild) ExpandAll(hwnd, hChild, nAction);
    }

    hNext = TreeView_GetNextSibling(hwnd, hItem);

    if (hNext)
        ExpandAll(hwnd, hNext, nAction);

    return;
}




/*
    THE INFORMATION AND CODE PROVIDED HEREUNDER (COLLECTIVELY REFERRED TO
    AS "SOFTWARE") IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN
    NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR
    ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL,
    CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF
    MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR
    LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE
    FOREGOING LIMITATION MAY NOT APPLY.
*/
