/*************************************************************************
   Project:     Microsoft Active Accessibility
    Module:     INSPECT

    Author:     LauraBu, SteveDon, PeteW
    Date:       5-1-96
    
    Notes:      
      It has a main dialog window.  When the
      user hits a hotkey, the DLL traps it and posts a message to INSPECT.EXE.
      INSPECT will
        * Get the properties and display them.
        * Navigate to the next/prev et al object, walking up the parent
          chain as need be.
        * Select the object
        * Do the default action for the object
      When you put the pointer over a screen object,
      the properties of that object will be displayed.

    Copyright (C) 1996 by Microsoft Corporation.  All rights reserved.
    See bottom of file for disclaimer
    
    History:    

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

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <winable.h>
#include <initguid.h>
#include <objbase.h>
#include <objerror.h>
#include <ole2.h>
#include <oleacc.h>
#include "inspect.h"
#include "voice.h"

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

#ifdef DEBUG
  #define _DEBUG
#endif
#ifdef _DEBUG
  void FAR CDECL PrintIt(LPSTR strFmt, ...);
  void dbobj(LPTSTR sz, IAccessible *pobj, LONG idChild);
  #define DBPRINTF PrintIt
  #define DBOBJ(x,y,z) dbobj(x,y,z)
#else
  #define DBPRINTF        1 ? (void)0 : (void)
  #define DBOBJ(x,y,z)
#endif



//
// Variables
//
HWND        hwndMain = NULL;
HMENU       hmnuSys = NULL;
HINSTANCE   hinstApp;
BOOL        fUseInvoke = FALSE;
BOOL        fOnTop = TRUE;
BOOL        fVoiceOn = FALSE;
HANDLE      hVoice = INVALID_HANDLE_VALUE;
SHORT       shVerbosity = VERBOSITY_LOW;
HWINEVENTHOOK	hevtCaret = NULL;
HWINEVENTHOOK	hevtFocus = NULL;
BOOL        gfAppExiting = FALSE;
BOOL        gfShowChildren = FALSE;

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

BOOL CALLBACK MyDialogProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK NotifyProc(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD, DWORD);

void DoProperties(IAccessible * pacc, VARIANT * pvarChild);
void DoNavigate(BOOL fForward);
void DoSelect(BOOL fFocus);
void DoDefault(void);
void DoDirection(UINT navDir);

IAccessible* GetObjectAtCursor(VARIANT*,HRESULT* lpResult);
void GetObjectProperty(IAccessible*, long, int, LPTSTR, UINT);

HRESULT GetObjectSelection(IAccessible*, long, LPTSTR, UINT);
HRESULT GetObjectChildren(IAccessible*, long, LPTSTR, UINT);
HRESULT GetObjectParent(IAccessible*, long, LPTSTR, UINT);
HRESULT GetObjectWindow(IAccessible*, LPTSTR, UINT);

HRESULT GetInvokeProp(IAccessible*, int, VARIANT*, void*);

int my_wsprintf(LPTSTR lpOut, UINT *lpcchOut, LPTSTR fmt, ...);

IAccessible *GetChildObject(IAccessible *pobj, VARIANT varChild); 
void Pause (int Millisecs);

// --------------------------------------------------------------------------
//
//  WinMain()
//
// --------------------------------------------------------------------------
int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    // Turn voice on only if "v", "v1", or "v2" is specified on the command line.
    // If on, text will be sent to an Accent synthersizer via COM1 or COM2.
    if (szCmdLine[0] == 'v')
    {
        switch (szCmdLine[1])
        {
             case 0:
             case '1': fVoiceOn = 1; break;
             case '2': fVoiceOn = 2; break;
        }
    }

    // Set the system screenreader flag on.
    // e.g. Word 97 will expose the caret position.
    SystemParametersInfo(SPI_SETSCREENREADER, TRUE, NULL, NULL);

    if (FInitialize(hinst))
    {
        MSG     msg;

        // Get the properties for whatever the mouse is over
        PostMessage(hwndMain, WM_DOPROPERTIES, 0, 0);

        // Main message loop
        while (GetMessage(&msg, NULL, 0, 0))
        {
            if (!IsDialogMessage(hwndMain, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }

    Terminate();

    return(0);
}



// --------------------------------------------------------------------------
//
//  FInitialize()
//
//  This creates our dialog, then calls into INSPECT.DLL to set the keyboard
//  hook.
//
// --------------------------------------------------------------------------
BOOL PASCAL FInitialize(HINSTANCE hInstance)
{
    TCHAR   szCommand[64];
    int     nCmd;
    int     cxScreen;
    int     cyScreen;
    RECT    rcWindow;
    int     xPos;
    int     yPos;
    int     cyCaption;

    hinstApp = hInstance;

    // Initialize OLE
    OleInitialize(NULL);
    
    //
    // Create our dialog.
    //
    hwndMain = CreateDialogParam(hinstApp, MAKEINTRESOURCE(IDD_MAIN),
        NULL, MyDialogProc, 0);
    if (!hwndMain)
        return(FALSE);

    hmnuSys = GetSystemMenu(hwndMain, FALSE);

    AppendMenu(hmnuSys, MF_SEPARATOR, 0, NULL);

    // Add Always On Top option
    LoadString(hinstApp, CMD_ALWAYSONTOP, szCommand, ARRAYSIZE(szCommand));
    AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, CMD_ALWAYSONTOP, szCommand);
    CheckMenuItem(hmnuSys, CMD_ALWAYSONTOP, MF_CHECKED | MF_BYCOMMAND);

    // Add Use Invoke option
    LoadString(hinstApp, CMD_USEINVOKE, szCommand, ARRAYSIZE(szCommand));
    AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, CMD_USEINVOKE, szCommand);

    // Add Follow Focus & Follow Caret option
    LoadString(hinstApp, CMD_FOLLOWFOCUS, szCommand, ARRAYSIZE(szCommand));
    AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, CMD_FOLLOWFOCUS, szCommand);

    LoadString(hinstApp, CMD_FOLLOWCARET, szCommand, ARRAYSIZE(szCommand));
    AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, CMD_FOLLOWCARET, szCommand);

    // Add show children menu option
    LoadString(hinstApp, CMD_SHOWCHILDREN, szCommand, ARRAYSIZE(szCommand));
    AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, CMD_SHOWCHILDREN, szCommand);

    AppendMenu(hmnuSys, MF_SEPARATOR, 0, NULL);

    // Add commands for direction, focus, selection, etc.
    for (nCmd = CMD_DOPROPERTIES; nCmd <= CMD_DODOWN; nCmd++)
    {
        LoadString(hinstApp, nCmd, szCommand, ARRAYSIZE(szCommand));
        AppendMenu(hmnuSys, MF_BYCOMMAND | MF_STRING, nCmd, szCommand);
    }

    // Place dialog in bottom right of screen
    cxScreen = GetSystemMetrics (SM_CXFULLSCREEN);
    cyScreen = GetSystemMetrics (SM_CYFULLSCREEN);
    cyCaption = GetSystemMetrics (SM_CYCAPTION);
    GetWindowRect (hwndMain,&rcWindow);
    xPos = cxScreen - (rcWindow.right - rcWindow.left);
    yPos = cyScreen - (rcWindow.bottom - rcWindow.top) + cyCaption;
    SetWindowPos(hwndMain, HWND_TOPMOST, xPos, yPos, 0, 0, SWP_NOSIZE |
        SWP_NOACTIVATE);
    ShowWindow(hwndMain, SW_SHOW);

    if (fVoiceOn) StartVoice(fVoiceOn);

    return(InstallHook(hwndMain, TRUE));
}



// --------------------------------------------------------------------------
//
//  Terminate()
//
// --------------------------------------------------------------------------
void PASCAL Terminate(void)
{
    // Unhook win event hooks
    if (hevtFocus)
    {
        UnhookWinEvent(hevtFocus);
        hevtFocus = NULL;
    }

    if (hevtCaret)
    {
        UnhookWinEvent(hevtCaret);
        hevtCaret = NULL;
    }

    // Uninitialize OLE
    CoUninitialize();

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

    if (fVoiceOn) EndVoice();
}



// --------------------------------------------------------------------------
//
//  MyDialogProc()
//
// --------------------------------------------------------------------------
BOOL CALLBACK
MyDialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
            // Set timer for property dialog refresh            SetTimer(hwnd, ID_HOVERTIMER, CMS_HOVERTIMER, NULL);
            // Make dialog caption look different for debug build
            #ifdef _DEBUG
            SetWindowText(hwnd, "Object Inspector (Debug)");
            #endif
            break;

        // Refresh all properties of the object under the pointer
        case WM_DOPROPERTIES:
            // Reset the timer
            KillTimer(hwnd, ID_HOVERTIMER);
            DoProperties(NULL,NULL);
            SetTimer(hwnd, ID_HOVERTIMER, CMS_HOVERTIMER, NULL);
            break;

        // Navigate next or previous from the object under the pointer
        case WM_DONAVIGATE:
            DoNavigate(wParam);
            break;

        // Set focus or select the object under the pointer
        case WM_DOSELECT:
            DoSelect(wParam);
            break;

        // Do default action on the object under the pointer
        case WM_DODEFAULT:
            DoDefault();
            break;

        // Spacial navigation from the object under the pointer
        case WM_DODIRECTION:
            DoDirection(wParam);
            break;

        case WM_TIMER:
            if (wParam == ID_HOVERTIMER)
            {
                //
                // Do not show info when mouse is moving.
                // When cursor settles down, do once.
                //
                // BOGUS!
                // Also refresh once every 10 seconds no matter what.
                //
                static BOOL fDidAlready = FALSE;
                static POINT pointOld = {0,0};
                POINT pointNew;
                GetCursorPos(&pointNew);
                if (pointOld.x == pointNew.x &&
                    pointOld.y == pointNew.y)
                {
                    // Don't do anything when point is within inspect window
                    // & it is in focus.
                    RECT rect;
                    GetWindowRect(hwndMain, &rect);
                    if (!fDidAlready &&
                        !(hwndMain==GetForegroundWindow() &&
                         pointNew.x >= rect.left &&
                         pointNew.x <= rect.right &&
                         pointNew.y >= rect.top &&
                         pointNew.y <= rect.bottom))
                    {
                        SendMessage(hwnd, WM_DOPROPERTIES, 0, 0L);
                        fDidAlready = TRUE;
                    }
                }
                else
                {
                    pointOld = pointNew;
                    fDidAlready = FALSE;
                }
            }
            else
                return(FALSE);
            break;

        case WM_WININICHANGE:
            if (gfAppExiting) break;

            // If someone else turns off the system-wide screen reader
            // flag, we want to turn it back on.
            if (wParam == SPI_SETSCREENREADER && !lParam)
                SystemParametersInfo(SPI_SETSCREENREADER, TRUE, NULL, NULL);
            return 0;

        case WM_DESTROY:
            // Let others know that you are turning off the system-wide
            // screen reader flag.
            gfAppExiting = TRUE;
            SystemParametersInfo(SPI_SETSCREENREADER, FALSE, NULL, NULL);
            SendMessage(HWND_BROADCAST, WM_WININICHANGE, SPI_SETSCREENREADER, FALSE);

            InstallHook(hwndMain, FALSE);
            hwndMain = NULL;
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDOK:
                case IDCANCEL:
                    DestroyWindow(hwnd);
                    break;
            }
            break;

        case WM_SYSCOMMAND:
            switch (wParam)
            {
                // Menu option to make dialog always on top
                case CMD_ALWAYSONTOP:
                    fOnTop = !fOnTop;
                    CheckMenuItem(hmnuSys, CMD_ALWAYSONTOP, (fOnTop ?
                        MF_CHECKED : MF_UNCHECKED) | MF_BYCOMMAND);
                    SetWindowPos(hwnd, (fOnTop ? HWND_TOPMOST : HWND_NOTOPMOST),
                        0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
                    break;

                // Menu option to make all IAccessible calls via ole automation
                case CMD_USEINVOKE:
                    fUseInvoke = !fUseInvoke;
                    CheckMenuItem(hmnuSys, CMD_USEINVOKE, (fUseInvoke ?
                        MF_CHECKED : MF_UNCHECKED) | MF_BYCOMMAND);
                    break;

                // Menu option to track focus change events
                case CMD_FOLLOWFOCUS:
                    if (hevtFocus)
                    {
                        UnhookWinEvent(hevtFocus);
                        hevtFocus = NULL;
                    }
                    else
                    {
                        hevtFocus = SetWinEventHook(EVENT_OBJECT_FOCUS,
                            EVENT_OBJECT_FOCUS, hinstApp, NotifyProc,
                            0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
                    }
                    CheckMenuItem(hmnuSys, CMD_FOLLOWFOCUS, (hevtFocus ? MF_CHECKED :
                        MF_UNCHECKED) | MF_BYCOMMAND);
                    break;

                // Menu option to track caret location change events
                case CMD_FOLLOWCARET:
                    if (hevtCaret)
                    {
                        UnhookWinEvent(hevtCaret);
                        hevtCaret = NULL;
                    }
                    else
                    {
                        hevtCaret = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE,
                            EVENT_OBJECT_LOCATIONCHANGE, hinstApp, NotifyProc,
                            0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
                    }
                    CheckMenuItem(hmnuSys, CMD_FOLLOWCARET, (hevtCaret ? MF_CHECKED :
                        MF_UNCHECKED) | MF_BYCOMMAND);
                    break;

                // Menu option to show children of given object
                case CMD_SHOWCHILDREN:
                    gfShowChildren = ~gfShowChildren;
                    CheckMenuItem(hmnuSys, CMD_SHOWCHILDREN, (gfShowChildren ? MF_CHECKED :
                        MF_UNCHECKED) | MF_BYCOMMAND);
                    break;

                // The following system menu items provide ways
                // to call the IAccessible properties or methods
                // of the object under the pointer.
                // These are also available via the ctrl+alt key sequences.

                case CMD_DODEFAULT:
                    SendMessage(hwnd, WM_DODEFAULT, 0, 0);
                    break;

                case CMD_DOPROPERTIES:
                    SendMessage(hwnd, WM_DOPROPERTIES, 0, 0);
                    break;

                case CMD_DOSELECT:
                    SendMessage(hwnd, WM_DOSELECT, 0, 0);
                    break;

                case CMD_DOFOCUS:
                    SendMessage(hwnd, WM_DOSELECT, TRUE, 0);
                    break;

                case CMD_DONEXT:
                    SendMessage(hwnd, WM_DONAVIGATE, NAVDIR_NEXT, 0);
                    break;

                case CMD_DOPREVIOUS:
                    SendMessage(hwnd, WM_DONAVIGATE, NAVDIR_PREVIOUS, 0);
                    break;

                case CMD_DOLEFT:
                    SendMessage(hwnd, WM_DODIRECTION, NAVDIR_LEFT, 0);
                    break;

                case CMD_DOUP:
                    SendMessage(hwnd, WM_DODIRECTION, NAVDIR_UP, 0);
                    break;

                case CMD_DORIGHT:
                    SendMessage(hwnd, WM_DODIRECTION, NAVDIR_RIGHT, 0);
                    break;

                case CMD_DODOWN:
                    SendMessage(hwnd, WM_DODIRECTION, NAVDIR_DOWN, 0);
                    break;


                default:
                    return(FALSE);
            }
            break;

        default:
            return(FALSE);
    }

    return(TRUE);
}



// --------------------------------------------------------------------------
//
//  NotifyProc()
//
//  This watches focus changes and/or caret moves.  Rather than moving the 
//	cursor, which caused problems with menus, we will just call the 
//	DoProperties function directly for the object that generated the event.
//
// --------------------------------------------------------------------------
void CALLBACK
NotifyProc(HWINEVENTHOOK hevent, DWORD event, HWND hwnd, LONG idObject, LONG idChild,
    DWORD idProcess, DWORD idThread)
{
    IAccessible*    pacc;
    VARIANT         varChild;
    HRESULT         hr;

    switch (event)
    {
        case EVENT_OBJECT_LOCATIONCHANGE:
            // Only the caret
            if ((idObject != OBJID_CARET) || !hevtCaret)
                return;
            break;

        case EVENT_OBJECT_FOCUS:
            if (!hevtFocus)
                return;
            break;

        default:
            Assert(FALSE);
            return;
    }

    //
    // Get the object to which the event is happening.
    //
    pacc = NULL;
    VariantInit(&varChild);
    hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pacc, &varChild);
    if (SUCCEEDED(hr) && pacc)
    {
        // Update dialog with properties of the given object
        DoProperties (pacc, &varChild);
        pacc->Release();
    }
    VariantClear(&varChild);
}



// --------------------------------------------------------------------------
//
//  GetObjectAtCursor()
//
//  Gets the object the cursor is over.
//
// --------------------------------------------------------------------------
IAccessible * GetObjectAtCursor(VARIANT * pvarChild,HRESULT* pResult)
{
    POINT   pt;
    IAccessible * poleacc;
    HRESULT hr;

    //
    // Get cursor object & position
    //
    GetCursorPos(&pt);

    //
    // Get object here.
    //
    VariantInit(pvarChild);
    hr = AccessibleObjectFromPoint(pt, &poleacc, pvarChild);
    *pResult = hr;
    if (!SUCCEEDED(hr))
        return(NULL);
    
    return(poleacc);
}




// --------------------------------------------------------------------------
//
//  DoProperties()
//
//  If no object is passed in,
//  this gets the object at the cursor, fills in the properties into the
//
// --------------------------------------------------------------------------
void DoProperties(IAccessible * pacc, VARIANT * pvarChild)
{
    IAccessible * pobj;
    VARIANT     varElement;
    TCHAR       szProperty[2048];
    int         idProperty;
    HRESULT     hr;

    if (pacc && pvarChild)
    {
        pobj = pacc;
        varElement = *pvarChild;
    }
    else
    {
        //
        // Get the object at the cursor
        //
        pobj = GetObjectAtCursor(&varElement,&hr);

        if (!pobj)
        {
#ifdef _DEBUG
            HWND    hwndAtPoint;
            POINT   ptCursor;
            LPVOID  lpMsgBuf;
            LPTSTR  lpTChar;
            int     length;
#endif
            MessageBeep(0);
            // clear all other properties
            for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
                SetDlgItemText(hwndMain, idProperty, "");

            SetDlgItemText(hwndMain, ID_NAME, "Unable to get object at point");

#ifdef _DEBUG
            // fill in role with class name of window at point
            GetCursorPos (&ptCursor);
            hwndAtPoint = WindowFromPoint (ptCursor);
            if (hwndAtPoint)
                GetClassName (hwndAtPoint,szProperty,ARRAYSIZE(szProperty));
            else
                lstrcpy (szProperty,"unknown window class");
            SetDlgItemText(hwndMain, ID_ROLE, szProperty);
            // could also get rect, but not needed now!
     
            // fill in state with hr data
            length = FormatMessage( 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                hr,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                (LPTSTR) &lpMsgBuf,
                0,
                NULL 
            );
            lpTChar = (LPTSTR)lpMsgBuf;
            // remove the \r\n at the end of the string
            if (length > 2)
                if (lpTChar[length-2] == '\r')
                    lpTChar[length-2] = 0;

            wsprintf(szProperty, "['%s' hr 0x%lx]", lpMsgBuf,hr);
            SetDlgItemText(hwndMain, ID_STATE, szProperty);

            // Free the buffer.
            LocalFree( lpMsgBuf );
#endif
			return;
        }
    }

    //
    // Get the properties
    //
    ShutUp();
    for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
    {
        GetObjectProperty(pobj, varElement.lVal, idProperty, szProperty,ARRAYSIZE(szProperty));
        SetDlgItemText(hwndMain, idProperty, szProperty);

        // voice output. Don't say all the properties.
        if (idProperty == ID_NAME ||
            idProperty == ID_ROLE ||
            idProperty == ID_VALUE)
            Say(SAY_ALWAYS, szProperty);
            Say(SAY_ALWAYS," ");
    }
    Say(SAY_ALWAYS, "."); // Flush 

    //
    // Release the object
    //
    if (!pacc || !pvarChild)
    {
        pobj->Release();
        VariantClear(&varElement);
    }
}




// --------------------------------------------------------------------------
//
//  GetObjectProperty
//
// --------------------------------------------------------------------------
void GetObjectProperty(IAccessible * pobj, LONG idChild, int idProperty,
    LPTSTR lpszName,  UINT cchName)
{
    HRESULT hr;
    VARIANT varChild;
    BSTR bstr;
    RECT rc;
    VARIANT varResult;

    *lpszName = 0;

    //
    // Clear out the possible return value objects
    //
    bstr = NULL;
    SetRectEmpty(&rc);
    VariantInit(&varResult);

    //
    // Setup the VARIANT child to pass in to the property
    //
    VariantInit(&varChild);
    varChild.vt = VT_I4;
    varChild.lVal = idChild;

    
    //
    // Get the property
    //
    switch (idProperty)
    {
        case ID_NAME:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_NAME, &varChild, &bstr);
            else
                hr = pobj->get_accName(varChild, &bstr);
            break;

        case ID_DESCRIPTION:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_DESCRIPTION, &varChild, &bstr);
            else
                hr = pobj->get_accDescription(varChild, &bstr);
            break;

        case ID_VALUE:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_VALUE, &varChild, &bstr);
            else
                hr = pobj->get_accValue(varChild, &bstr);
            break;

        case ID_HELP:
            // Future enhancement:  Try help file instead if failure
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_HELP, &varChild, &bstr);
            else
                hr = pobj->get_accHelp(varChild, &bstr);
            break;

        case ID_SHORTCUT:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_KEYBOARDSHORTCUT, &varChild, &bstr);
            else
                hr = pobj->get_accKeyboardShortcut(varChild, &bstr);
            break;

        case ID_DEFAULT:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_DEFAULTACTION, &varChild, &bstr);
            else
                hr = pobj->get_accDefaultAction(varChild, &bstr);
            break;

        case ID_ROLE:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_ROLE, &varChild, &varResult);
            else
                hr = pobj->get_accRole(varChild, &varResult);
            break;

        case ID_STATE:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_STATE, &varChild, &varResult);
            else
                hr = pobj->get_accState(varChild, &varResult);
            break;

        case ID_LOCATION:
            if (fUseInvoke)
                hr = GetInvokeProp(pobj, DISPID_ACC_LOCATION, &varChild, &rc);
            else
                hr = pobj->accLocation(&rc.left, &rc.top, &rc.right, &rc.bottom, varChild);
            break;

        case ID_CHILDREN:
            hr = GetObjectChildren(pobj, idChild, lpszName, cchName);
            break;

        case ID_SELECTION:
            hr = GetObjectSelection(pobj, idChild, lpszName, cchName);
            break;

        case ID_PARENT:
            hr = GetObjectParent(pobj, idChild, lpszName, cchName);
            break;

        case ID_WINDOW:
            hr = GetObjectWindow(pobj, lpszName, cchName);
            break;
    }

    // Return if the IAccessible call failed.
    if (!SUCCEEDED(hr))
    {
#ifdef _DEBUG
        // Pass back the error string to be displayed

        LPVOID  lpMsgBuf;
        LPTSTR  lpTChar;
        int     length;
     
        length = FormatMessage( 
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            hr,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,
            0,
            NULL 
        );
        lpTChar = (LPTSTR)lpMsgBuf;
        // remove the \r\n at the end of the string
        if (length > 2)
            if (lpTChar[length-2] == '\r')
                lpTChar[length-2] = 0;

        wsprintf(lpszName, "['%s' hr 0x%lx idProperty %d pobj 0x%lx idChild 0x%lx]", lpMsgBuf,hr, idProperty, pobj, idChild);

        // Free the buffer.
        LocalFree( lpMsgBuf );
#endif
        return;
    }

    //
    // Convert it to a display string
    //
    switch (idProperty)
    {
        // These are the cases already taken care of.
        case ID_SELECTION:
        case ID_CHILDREN:
        case ID_PARENT:
        case ID_WINDOW:
            break;

        // These are the cases where we got unicode string back
        case ID_NAME:
        case ID_DESCRIPTION:
        case ID_VALUE:
        case ID_HELP:
        case ID_SHORTCUT:
        case ID_DEFAULT:
            if (bstr)
            {
                WideCharToMultiByte(CP_ACP, 0, bstr, -1, lpszName, cchName, NULL, NULL);
                SysFreeString(bstr);
            }
            break;

        case ID_LOCATION:
            wsprintf(lpszName, "{%04d, %04d, %04d, %04d}", rc.left, rc.top,
                rc.left + rc.right, rc.top + rc.bottom);
            break;

        case ID_ROLE: // Role can be either I4 or BSTR
            if (varResult.vt == VT_I4)
            {
                // If we got back a standard role, get the string for it.
                GetRoleText(varResult.lVal, lpszName, cchName);

                // Also display class name.
                // Don't do this when voice is on though. Too verbose.
                if (!fVoiceOn)
                {
                    HRESULT hr;
                    HWND hwnd=0;
                    TCHAR buf[80];

                    IAccessible *pobjChild=NULL;
                    pobjChild = GetChildObject(pobj, varChild);
                    hr = WindowFromAccessibleObject((pobjChild) ? pobjChild: pobj, &hwnd);
                    if (pobjChild) pobjChild->Release();
                    if (SUCCEEDED(hr))
                    {
                        GetClassName(hwnd, buf, 80);
                        lstrcat(lpszName, TEXT(" ("));
                        lstrcat(lpszName, buf);
                        lstrcat(lpszName, TEXT(")"));
                    }
                }
            }
            else if (varResult.vt == VT_BSTR)
            {
                // If we got back a string, use that instead.
                WideCharToMultiByte(CP_ACP, 0, varResult.bstrVal, -1,
                    lpszName, cchName, NULL, NULL);
            }

            // This will free a BSTR, etc.
            VariantClear(&varResult);
            break;

        case ID_STATE: // State can either be I4 or BSTR
            if (varResult.vt == VT_BSTR)
            {
                // If we got back a string, use that.
                WideCharToMultiByte(CP_ACP, 0, varResult.bstrVal, -1,
                    lpszName, cchName, NULL, NULL);
            }
            else if (varResult.vt == VT_I4)
            {
                int     iStateBit;
                DWORD   lStateBits;
                LPTSTR  lpszT;
                UINT    cchT;

                // We have a mask of standard states.  Make a string.
                // Separate the states with ",".
                lpszT = lpszName;

                for (iStateBit = 0, lStateBits = 1; iStateBit < 32; iStateBit++, (lStateBits <<= 1))
                {
                    if (varResult.lVal & lStateBits)
                    {
                        cchT = GetStateText(lStateBits, lpszT, cchName);
                        lpszT += cchT;
                        cchName -= cchT;

                        *lpszT++ = ',';
                        *lpszT++ = ' ';
                    }
                }

                //
                // Clip off final ", "
                //
                if (varResult.lVal)
                {
                    *(lpszT-2) = 0;
                    *(lpszT-1) = 0;
                }
                else
                    GetStateText(0, lpszName, cchName);
            }

            VariantClear(&varResult);
            break;

        default:
            DebugBreak();
    }
}

// --------------------------------------------------------------------------
// GetChildObject()
//
// Returns child object given parent and variant.
// Takes care of IDispatch behind the scene.
//
// --------------------------------------------------------------------------
IAccessible *GetChildObject(IAccessible *pobj, VARIANT varChild)
{
    IDispatch *pdispChild=NULL;
    IAccessible *pobjChild=NULL;
    HRESULT hr;
    hr = pobj->get_accChild(varChild, &pdispChild);
    if (SUCCEEDED(hr))
    {
        if (pdispChild)
        {
            hr = pdispChild->QueryInterface(IID_IAccessible, (void**)&pobjChild);
            pdispChild->Release();
        }
        else if (!hr) DBPRINTF("ASERT! get_accChild passed back NULL pdispChild, but hr=0\r\n");
    }
    return pobjChild;
}



// --------------------------------------------------------------------------
//
//  GetObjectWindow()
//
//  Gets the HWND the object is inside of.
//  A hex string of the HWND is passed back.
//
// --------------------------------------------------------------------------
HRESULT GetObjectWindow(IAccessible* pobj, LPTSTR lpszProp, UINT cchProp)
{
    HWND    hwnd;
    HRESULT hr;

    hwnd = NULL;
    hr = WindowFromAccessibleObject(pobj, &hwnd);
    if (SUCCEEDED(hr) && hwnd)
        wsprintf(lpszProp, "%08x", hwnd);

    return(hr);
}


// --------------------------------------------------------------------------
//
//  GetObjectSelection()
//
//  Gets a list of the selected items in a container
//  Constructs a string and passes it back to the caller.
//
// --------------------------------------------------------------------------
HRESULT GetObjectSelection(IAccessible* pobj, long idChild, LPTSTR lpszProp,
    UINT cchProp)
{
    VARIANT varChild;
    VARIANT varRet;
    TCHAR   szName[64];
    TCHAR   szRole[32];
    HRESULT hr;
    IEnumVARIANT* penum;
    BOOL    fFreeChildObj;
    LPTSTR  lpszT;
    ULONG   celt;

    //
    // Non-zero means we are talking to an accessible component of an 
    // accessible object that isn't broken down further.  Hence it has no
    // children.
    //
    if (idChild)
        return(S_OK);

    //
    // Get the selection
    //
    VariantInit(&varChild);
    hr = pobj->get_accSelection(&varChild);
    if (!SUCCEEDED(hr) || (varChild.vt == VT_EMPTY))
        return(hr);

    //
    // If there is one child (VT_I4 or VT_DISPATCH), get the child info and
    // we are done.  If there are more (VT_UNKNOWN), try to enumerate.
    //
    VariantInit(&varRet);
    fFreeChildObj = FALSE;

    if (varChild.vt == VT_I4)
    {
        //
        // If VT_I4, then mention this item, and we are done
        //
        if (varChild.lVal)
        {
            varRet.pdispVal = NULL;
            hr = pobj->get_accChild(varChild, &varRet.pdispVal);
            if (SUCCEEDED(hr) && varRet.pdispVal)
            {
                varChild.vt = VT_DISPATCH;
                varChild.pdispVal = varRet.pdispVal;
                goto WeHaveOneObjectChild;
            }
        }

        goto WeHaveOneChild;
    }
    else if (varChild.vt == VT_DISPATCH)
    {
WeHaveOneObjectChild:
        //
        // Ask the child directly what its name is.
        //
        pobj = NULL;
        fFreeChildObj = TRUE;

        hr = varChild.pdispVal->QueryInterface(IID_IAccessible, (void**)&pobj);
        varChild.pdispVal->Release();

        if (SUCCEEDED(hr) && pobj)
        {
            varChild.lVal = 0;

WeHaveOneChild:
            GetObjectProperty(pobj, varChild.lVal, ID_NAME, szName, ARRAYSIZE(szName));
            GetObjectProperty(pobj, varChild.lVal, ID_ROLE, szRole, ARRAYSIZE(szRole));
            wsprintf(lpszProp, "%s %s", szName, szRole);

            if (fFreeChildObj)
                pobj->Release();
        }

        goto Done;
    }
    else if (varChild.vt != VT_UNKNOWN)
    {
        VariantClear(&varChild);
        goto Done;
    }

    //
    // We have an IEnumVARIANT.  Loop through the items getting their names.
    //
    penum = NULL;
    hr = varChild.punkVal->QueryInterface(IID_IEnumVARIANT, (void**)&penum);
    varChild.punkVal->Release();

    if (!SUCCEEDED(hr))
        goto Done;

    lpszT = lpszProp;

    //
    // Sit in a loop enumerating the children.
    //
    penum->Reset();

    while (TRUE)
    {
        VariantInit(&varChild);
        celt = 0;

        // Get the next element
        hr = penum->Next(1, &varChild, &celt);
        if (!SUCCEEDED(hr))
            goto EnumDone;

        // Are we at end?
        if (!celt)
            break;

        // Get this child's name.
        if (varChild.vt == VT_DISPATCH)
        {
            IAccessible * pobjChild;

            pobjChild = NULL;
            hr = varChild.pdispVal->QueryInterface(IID_IAccessible,
                (void**)&pobjChild);
            varChild.pdispVal->Release();

            if (!SUCCEEDED(hr))
                break;

            GetObjectProperty(pobjChild, 0, ID_NAME, szName, ARRAYSIZE(szName));
            GetObjectProperty(pobjChild, 0, ID_ROLE, szRole, ARRAYSIZE(szRole));
            pobjChild->Release();
        }
        else if (varChild.vt == VT_I4)
        {
            GetObjectProperty(pobj, varChild.lVal, ID_NAME, szName, ARRAYSIZE(szName));
            GetObjectProperty(pobj, varChild.lVal, ID_ROLE, szRole, ARRAYSIZE(szRole));
        }
        else
        {
            VariantClear(&varChild);
            *szName = 0;
            *szRole = 0;
        }

        if (*szName || *szRole)
            lpszT += wsprintf(lpszT, "%s %s; ", szName, szRole);
    }

EnumDone:
    penum->Release();

Done:
    return(S_OK);
}



// --------------------------------------------------------------------------
//
//  GetObjectChildren()
//
//  Gets a list of the children in a container.
//  Constructs a string and passses it back.
//
// --------------------------------------------------------------------------
HRESULT GetObjectChildren(IAccessible* pobj, long idChild, LPTSTR lpszProp,
    UINT cchProp)
{
VARIANT			varT;
TCHAR			szName[64];
TCHAR			szRole[64];
TCHAR           szState[20];
HRESULT			hr;
IAccessible*	pobjChild;
long			ielt;
UINT			cchRemain = cchProp;
LPTSTR			lpszT;
VARIANT*		rgvarChildren;
long			cChildren=0;
long			cObtained=0;

	//
	//  Non-zero means we are over something not a container.
	//
	if (idChild)
	{
		lstrcpy (lpszProp,"Child object has no children");
		return(S_OK);
	}

	hr = pobj->get_accChildCount(&cChildren);
	if (FAILED(hr))
	{
		#ifdef _DEBUG
		wsprintf (lpszProp, "get_accChildCount failed 0x%lx\r\n", hr);
		#endif
		return hr;
	}

	if (cChildren == 0)
	{
		lstrcpy (lpszProp,"Container has no children");
		return (S_OK);
	}

	// AccessibleChildren when called cross process is very slow 
	// on Office 97 menu. Thus, we are making show children an option.
      // Future enhancement: The best way to solve this problem is to 
	// make this and other time-consuming calls in-process.
	if (!gfShowChildren)
      {
		wsprintf (lpszProp,"# of children = %lu", cChildren);
		return S_OK;
      }

	// Allocate memory for the array of child variant
	rgvarChildren = (VARIANT*) malloc(sizeof(VARIANT)*cChildren);
	if (NULL == rgvarChildren)
	{
		lstrcpy (lpszProp,"no memory");
		return (E_OUTOFMEMORY);
	}

	hr = AccessibleChildren(pobj, 0L, cChildren, rgvarChildren, &cObtained);
	if (SUCCEEDED(hr) && (cObtained == cChildren))
	{
		lpszT = lpszProp;	
		// Loop through VARIANTs in ARRAY.  If VT_DISPATCH, convert to object 
		// and talk to it.  Else pass in to pobj for properties.
		ielt = 0;
		while (ielt <= cChildren)
		{
			*szName = 0;
			*szRole = 0;
			*szState = 0;
			if (rgvarChildren[ielt].vt == VT_DISPATCH)
			{
				pobjChild = NULL;
				hr = rgvarChildren[ielt].pdispVal->QueryInterface(IID_IAccessible,
					(void**)&pobjChild);
				rgvarChildren[ielt].pdispVal->Release();

				if (SUCCEEDED(hr) && pobjChild)
				{
					//
					// Is it visible?
					//
					VariantInit(&varT);

					pobjChild->get_accState(varT, &varT);	// NOTE: getting status of CHILDID_SELF,
															// because CAccessible::ValidateChild 
															// converts VT_EMPTY to VT_I4, lVal = 0
					if ((varT.vt == VT_I4) && (varT.lVal & STATE_SYSTEM_INVISIBLE))
					lstrcpy (szState,"invisible ");

					GetObjectProperty(pobjChild, 0, ID_NAME, szName, ARRAYSIZE(szName));
					GetObjectProperty(pobjChild, 0, ID_ROLE, szRole, ARRAYSIZE(szRole));

					VariantClear(&varT);
					pobjChild->Release();
				}
			}
			else if (rgvarChildren[ielt].vt == VT_I4)
			{
				//
				// Is it visible?
				//
				VariantInit(&varT);

				pobj->get_accState(rgvarChildren[ielt], &varT);

				if ((varT.vt == VT_I4) && (varT.lVal & STATE_SYSTEM_INVISIBLE))
				lstrcpy (szState,"invisible ");

				GetObjectProperty(pobj, rgvarChildren[ielt].lVal, ID_NAME, szName, ARRAYSIZE(szName));
				GetObjectProperty(pobj, rgvarChildren[ielt].lVal, ID_ROLE, szRole, ARRAYSIZE(szRole));

				VariantClear(&varT);
			}
			else
			{
				VariantClear(&(rgvarChildren[ielt]));
			}

			if (*szName || *szRole)
			{
				UINT    cchT;

				cchT = my_wsprintf(lpszT, &cchRemain, "%s%s %s; ", szState, szName, szRole);
				if (!cchT) break;
				lpszT += cchT;
			}

			ielt++;
		}
	}
	else 
	{
		#ifdef _DEBUG
		wsprintf(lpszProp, "AccessibleChildren failed, hr 0x%lx, got %lu, expected %lu\r\n",
		  hr, cObtained, cChildren);
		#endif
	}
	
	free (rgvarChildren);
	return (S_OK);
}



// --------------------------------------------------------------------------
//
//  GetObjectParent()
//
//  Gets the name + role of the item's parent.
//
// --------------------------------------------------------------------------
HRESULT GetObjectParent(IAccessible* pobj, long idChild, LPTSTR lpszProp,
    UINT cchProp)
{
    TCHAR   szName[64];
    TCHAR   szRole[64];
    IDispatch* pdispParent;
    IAccessible* paccParent;

    pdispParent = NULL;

    if (idChild)
        paccParent = pobj;
    else
    {
        pobj->get_accParent(&pdispParent);

        if (!pdispParent)
            return(E_FAIL);

        paccParent = NULL;
        pdispParent->QueryInterface(IID_IAccessible, (void**)&paccParent);
        if (!paccParent)
            return(E_FAIL);
        pdispParent->Release();
    }

    GetObjectProperty(paccParent, 0, ID_NAME, szName, ARRAYSIZE(szName));
    GetObjectProperty(paccParent, 0, ID_ROLE, szRole, ARRAYSIZE(szRole));

    //
    // Release the parent object if we got one via get_accParent().  That would
    // bump up the ref count, so now we are done we want to bump it down 
    // again.
    //

    if (pdispParent)
        paccParent->Release();

    wsprintf(lpszProp, "%s %s", szName, szRole);

    return(S_OK);
}



// --------------------------------------------------------------------------
//
//  DoNavigate()
//
//  This gets the next object from this one.  If we hit the end, then
//  we will ask the parent, then so on.  In other words, we traverse the
//  hierarchy, just like nested dialogs do.
//
//  The traversal is parent first, children next.  Note:  only navigate
//  to an item with screen presence.
//
//  Here is the logic:
//  If the node is a container, goto first child.
//  if the child is invisible, go to the child's visible peer.
//  if no more peer, go to parent's peer.
// --------------------------------------------------------------------------
void DoNavigate(BOOL fForward)
{
    IAccessible* pobj;
    VARIANT varElement;
    VARIANT varReturn;
    RECT    rcCursor;
    HRESULT hr;
    int     dwNavT;
    int     idProperty;

    DBPRINTF("-----------------\r\n");
    // 
    // Get what's under the cursor
    //
    pobj = GetObjectAtCursor(&varElement,&hr);
    if (! pobj)
    {
        DBPRINTF("Failed to get object at pointer\r\n");
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to get object at point");
        return;
    }

    SetRectEmpty(&rcCursor);

    //
    // Navigate from here
    //

    // If we are on the container, try getting the first child.
    if ((varElement.vt == VT_I4) && (varElement.lVal == 0))
    {
        DBPRINTF("This is a container\r\n");
        dwNavT = (fForward ? NAVDIR_FIRSTCHILD : NAVDIR_LASTCHILD);
    }
    else
    {
        DBPRINTF("Not container: vt 0x%lx , lVal 0x%lx \r\n",
          varElement.vt, varElement.lVal);
        dwNavT = (fForward ? NAVDIR_NEXT : NAVDIR_PREVIOUS);
    }

TryAgain:
    VariantInit(&varReturn);
    varReturn.vt = 0; varReturn.lVal = 0;
    DBOBJ("accNav on object", pobj, varElement.lVal);
    hr = pobj->accNavigate(dwNavT, varElement, &varReturn);
    if (!SUCCEEDED(hr))
    {
        DBPRINTF("accNav failed: dwNavT 0x%lx vt %u lVal 0x%lx, hr 0x%lx\r\n", dwNavT, varElement.vt, varElement.lVal, hr);
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to navigate.");
        goto BailOut;
    }
    DBPRINTF("accNav got back: dwNavT 0x%lx vt %u lVal 0x%lx, hr 0x%lx\r\n", dwNavT, varReturn.vt, varReturn.lVal, hr);

    //
    // What did we get back?
    //

    if (varReturn.vt == VT_I4)
    {
        // If varElement is empty/zero and dwNavT isn't NAVDIR_FIRSTCHILD,
        // then we got a non-object peer of an object.  So get this one's
        // parent, and pass the id into THAT.
        if ((dwNavT < NAVDIR_FIRSTCHILD) && 
            ((varElement.vt == VT_EMPTY) ||
             (varElement.vt == VT_I4 && varElement.lVal == 0) ))
        {
            DBPRINTF("Object's peer is non-object.\r\n");
            varElement.pdispVal = NULL;
            hr = pobj->get_accParent(&varElement.pdispVal);
            pobj->Release();
            pobj = NULL;

            if (!SUCCEEDED(hr))
            {
                DBPRINTF("accParent failed\r\n");
                MessageBeep(0);
                for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
                    SetDlgItemText(hwndMain, idProperty, "");
                SetDlgItemText(hwndMain, ID_NAME, "Unable to get object parent");
                return;
            }

            if (!varElement.pdispVal)
            {
                DBPRINTF("Parent NULL\r\n");
                return;
            }

            hr = varElement.pdispVal->QueryInterface(IID_IAccessible,
                (void**)&pobj);
            varElement.pdispVal->Release();

            if (!SUCCEEDED(hr))
            {
                DBPRINTF("Parent does not suppport IAccessible\r\n");
                MessageBeep(0);
                for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
                    SetDlgItemText(hwndMain, idProperty, "");
                SetDlgItemText(hwndMain, ID_NAME, "Parent is not an Accessible Object");
                return;
            }
        }

GotTheLocation:
		{
			// Great, a peer object.  Get its location by going through
			// the object we have.
			VARIANT varT;
			BOOL fInvisible;
			DBOBJ("Got a peer object", pobj, varReturn.lVal);

			VariantInit(&varT);
			hr = pobj->get_accState(varReturn, &varT);
			if (FAILED(hr)) DBPRINTF("get_accState failed, hr 0x%lx\r\n", hr);
			fInvisible = ((varT.vt == VT_I4) && (varT.lVal & STATE_SYSTEM_INVISIBLE)) ? TRUE : FALSE;
			VariantClear(&varT);

			hr = pobj->accLocation(&rcCursor.left, &rcCursor.top,
			    &rcCursor.right, &rcCursor.bottom, varReturn);

			// If invissible, go to the next one
                  // Note: right is width, bottom is height.
			if (fInvisible || FAILED(hr) ||
			  (!rcCursor.right && !rcCursor.bottom))
			{
			    DBPRINTF("accLocation failed: hr 0x%lx right %lu bottom %lu  lVal 0x%lx\r\n",
			      hr, rcCursor.right, rcCursor.bottom, varReturn.lVal);
			    varElement = varReturn;
			    dwNavT = (fForward ? NAVDIR_NEXT : NAVDIR_PREVIOUS);
			    goto TryAgain;
			}
		}
    }
    else if (varReturn.vt == VT_DISPATCH)
    {
//DescendAgain:
        // OK, we got back a real object peer.  Ask it directly its location.
        DBPRINTF("Real object peer. VT_IDISPATCH\r\n");
        pobj->Release();
        pobj = NULL;

        hr = varReturn.pdispVal->QueryInterface(IID_IAccessible,
            (void**)&pobj);
        varReturn.pdispVal->Release();

        // Did we get an IAccessible for this one?
        if (!SUCCEEDED(hr))
        {
            DBPRINTF("IAccessible not supported\r\n");
            MessageBeep(0);
            return;
        }

        // No need to 
        varReturn.vt = VT_I4;
        varReturn.lVal = 0;
        goto GotTheLocation;
    }
    else if (varReturn.vt == VT_EMPTY)
    {
        //
        // Are we on a node that is a real object that in fact does
        // handle peer-to-peer navigation?
        //
        if ((dwNavT >= NAVDIR_FIRSTCHILD) && (varElement.vt == VT_I4) &&
            (varElement.lVal == 0))
        {
            DBPRINTF("No child in container. Try peer of container\r\n");
            dwNavT = (fForward ? NAVDIR_NEXT : NAVDIR_PREVIOUS);
            goto TryAgain;
        }

        dwNavT = (fForward ? NAVDIR_NEXT : NAVDIR_PREVIOUS);

        // There are no more next items. So get the next object after the
        // container.
        if ((varElement.vt == VT_I4) && (varElement.lVal != 0))
        {
            DBPRINTF("No more peer for the child. Try peer of container\r\n");
            VariantInit(&varElement);
            varElement.vt = VT_I4;
            varElement.lVal = 0;
            goto TryAgain;
        }
        else
        {
            // There aren't any peers of this one.  So move
            // up to our parent and ask IT what is next.
            DBPRINTF("There aren't any peers of this object. Move up to our parent and ask IT what is next.\r\n");

            // We would like to be able to know what our own 
            // id is. I.E. What the parent thinks of as our ID.
            varReturn.pdispVal = NULL;
            hr = pobj->get_accParent(&varReturn.pdispVal);
            pobj->Release();
            pobj = NULL;

            if (!SUCCEEDED(hr))
            {
                DBPRINTF("accParent failed\r\n");
                MessageBeep(0);
                return;
            }

            if (!varReturn.pdispVal)
            {
                DBPRINTF("NULL pdisp from accParent\r\n");
                return;
            }

            hr = varReturn.pdispVal->QueryInterface(IID_IAccessible,
                (void**)&pobj);
            varReturn.pdispVal->Release();

            if (!SUCCEEDED(hr))
            {
                DBPRINTF("IAccessible not supported\r\n");
                MessageBeep(0);
                return;
            }

            // Ask this guy from himself.
            // this is a problem. We should be starting at the parent's
            // child that we used to get the parent in the first place.
            // But the child object doesn't know it's own ID!!
            VariantInit(&varElement);
            varElement.vt = VT_I4;
            varElement.lVal = 0;
            goto TryAgain;
        }
    }
    else
    {
        DBPRINTF("No clue\r\n");
        // No clue what this.
        VariantClear(&varReturn);
        MessageBeep(0);
    }

    if (rcCursor.right || rcCursor.bottom)
    {
        // This tiny offset is to make sure that we do place
        // the pointer within the object boundary. since
        // hittest, visual and accLocation boundaries sometimes
        // do not align perfectly, especially the top left
        // locaiton. e.g. Windows Explorer detail view
        // list items; IE3 toolbar buttons.
        long xOff = (rcCursor.right<2) ? 0: 2; 
        long yOff = (rcCursor.bottom<2) ? 0: 2;
        SetCursorPos(rcCursor.left+xOff, rcCursor.top+yOff);
        // The timer willl take care of updating the inspect dialog
    }
    else DBPRINTF("No valid location\r\n");

BailOut:
    pobj->Release();
    return;
}



// --------------------------------------------------------------------------
//
//  DoDirection()
//
//  This finds the next object to the left/above/right/bottom of the current
//  one.  Then it moves the cursor there.
//
// --------------------------------------------------------------------------
void DoDirection(UINT navDir)
{
    IAccessible* pobj;
    VARIANT      varCur;
    VARIANT      varRet;
    RECT        rcItem;
    HRESULT     hr;
    int         idProperty;

    //
    // What's under the cursor?
    //
    pobj = GetObjectAtCursor(&varCur,&hr);
    if (!pobj)
    {
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to get object at point");
        return;
    }

    SetRectEmpty(&rcItem);

    VariantInit(&varRet);
    hr = pobj->accNavigate(navDir, varCur, &varRet);
    if (!SUCCEEDED(hr) || (varRet.vt == VT_EMPTY))
    {
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to navigate");
        goto BailOut;
    }

    //
    // What did we get back?
    //
    if (varRet.vt == VT_I4)
    {
        // 
        // Ask for this object's location through the container.
        //
        pobj->accLocation(&rcItem.left, &rcItem.top, &rcItem.right,
            &rcItem.bottom, varRet);
    }
    else if ((varRet.vt == VT_DISPATCH) && varRet.pdispVal)
    {
        pobj->Release();
        pobj = NULL;

        hr = varRet.pdispVal->QueryInterface(IID_IAccessible, (void**)&pobj);
        varRet.pdispVal->Release();

        // Did we get an IAccessible?
        if (!SUCCEEDED(hr))
        {
            MessageBeep(0);
            return;
        }

        VariantInit(&varCur);
        pobj->accLocation(&rcItem.left, &rcItem.top, &rcItem.right,
            &rcItem.bottom, varCur);
    }
    else
    {
        VariantClear(&varRet);
        MessageBeep(0);
        goto BailOut;
    }

    if (rcItem.right || rcItem.bottom)
    {
        // rcItem.right & rcItem.bottom have the width & height values
        // not the right & bottom coords.
        SetCursorPos(rcItem.left + rcItem.right/2, rcItem.top +
            rcItem.bottom/2);
    }

BailOut:
    pobj->Release();
    return;
}



// --------------------------------------------------------------------------
//
//  DoSelect()
//
// --------------------------------------------------------------------------
void DoSelect(BOOL fFocus)
{
    IAccessible* pobj;
    VARIANT      varElement;
    HRESULT      hr;
    int         idProperty;

    //
    // Get the object under the cursor
    //
    pobj = GetObjectAtCursor(&varElement,&hr);
    if (!pobj)
    {
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to get object at point");
        return;
    }

    Pause (2000);

#ifndef _DEBUG
    pobj->accSelect((fFocus ? SELFLAG_TAKEFOCUS : SELFLAG_TAKESELECTION), varElement);
#else
	// This provides a way to test combinations of SELFLAGs.
	// Read in from c:\dbmsaa.ini.
	// The following example shows that ctrl+shift+F4 does a combination of
	// of select and focus. (ctrl+shift+F5 can also be mapped.)
	// [SELFLAG4]
	// TAKEFOCUS=1
	// TAKESELECT=1
	{
	 TCHAR buf[4];
	 long lFlag=0;
	 if (fFocus)
	 {
		if (GetPrivateProfileString("SELFLAG5", "TAKEFOCUS", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_TAKEFOCUS;
		if (GetPrivateProfileString("SELFLAG5", "TAKESELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_TAKESELECTION;
		if (GetPrivateProfileString("SELFLAG5", "EXTENDSELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_EXTENDSELECTION;
		if (GetPrivateProfileString("SELFLAG5", "ADDSELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_ADDSELECTION;
		if (GetPrivateProfileString("SELFLAG5", "REMOVESELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_REMOVESELECTION;
	 }
	 else 
	 {
		if (GetPrivateProfileString("SELFLAG4", "TAKEFOCUS", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_TAKEFOCUS;
		if (GetPrivateProfileString("SELFLAG4", "TAKESELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_TAKESELECTION;
		if (GetPrivateProfileString("SELFLAG4", "EXTENDSELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_EXTENDSELECTION;
		if (GetPrivateProfileString("SELFLAG4", "ADDSELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_ADDSELECTION;
		if (GetPrivateProfileString("SELFLAG4", "REMOVESELECTION", "", buf, 4, "c:\\dbmsaa.ini")) lFlag |= SELFLAG_REMOVESELECTION;
	 }
         if (!lFlag) lFlag = (fFocus ? SELFLAG_TAKEFOCUS : SELFLAG_TAKESELECTION);
	 hr = pobj->accSelect(lFlag, varElement);
	 if (hr != S_OK) DBPRINTF("accSelect failed, pobj 0x%lx, lVal 0x%lx, flag 0x%lx, hr 0x%lx\r\n", pobj, varElement.lVal, lFlag, hr);
	}
#endif
    VariantClear(&varElement);
    pobj->Release();

    return;
}



// --------------------------------------------------------------------------
//
//  DoDefault()
//
//  This gets the object at the cursor, then does its default action.
//
// --------------------------------------------------------------------------
void DoDefault(void)
{
    IAccessible* pobj;
    VARIANT      varElement;
    HRESULT      hr;
    int          idProperty;

    //
    // Get the object at the cursor
    //
    pobj = GetObjectAtCursor(&varElement,&hr);
    if (!pobj)
    {
        MessageBeep(0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to get object at point");
        return;
    }

    Pause (2000);

    hr = pobj->accDoDefaultAction(varElement);

    if (!SUCCEEDED (hr))
    {
        MessageBeep (0);
        for (idProperty = ID_PROPERTYFIRST; idProperty <= ID_PROPERTYLAST; idProperty++)
            SetDlgItemText(hwndMain, idProperty, "");
        SetDlgItemText(hwndMain, ID_NAME, "Unable to perform Default Action");
    }

    VariantClear(&varElement);

    //
    // Release the object
    //
    pobj->Release();
    return;
}


// --------------------------------------------------------------------------
//
//  Functions supporting voice output.
//
// --------------------------------------------------------------------------

// Open device for voice. If successful, the global hVoice is set.
BOOL StartVoice(SHORT shComPort)
{
    DCB dcb;
    HANDLE hCom;
    DWORD dwError;
    BOOL fSuccess;
    TCHAR szComPort[]="COM1";

    if (hVoice != INVALID_HANDLE_VALUE) {
        MessageBox(NULL, "Voice already started", "", MB_OK);
        return FALSE;
    }

    szComPort[3]='0'+shComPort;
    hCom = CreateFile(szComPort,
        GENERIC_READ | GENERIC_WRITE,
        0,    /* comm devices must be opened w/exclusive-access */
        NULL, /* no security attrs */
        OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
        0,    /* not overlapped I/O */
        NULL  /* hTemplate must be NULL for comm devices */
        );
    if (hCom == INVALID_HANDLE_VALUE) {
        dwError = GetLastError();
        MessageBox(NULL, "CreateFile failed", "", MB_OK);
        goto END1;
    }

    /*
     * Omit the call to SetupComm to use the default queue sizes.
     * Get the current configuration.
     */

    fSuccess = GetCommState(hCom, &dcb);
    if (!fSuccess) {
        MessageBox(NULL, "GetComState failed", "", MB_OK);
        goto END2;
    }

    /* Fill in the DCB: baud=9600, 8 data bits, no parity, 1 stop bit. */

    dcb.BaudRate = 9600;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;

    fSuccess = SetCommState(hCom, &dcb);
    if (!fSuccess) {
        MessageBox(NULL, "SetComState failed", "", MB_OK);
        goto END2;
    }

    hVoice = hCom;
    ShutUp();
    fSuccess = _Say("Voice ready.");
    if (!fSuccess) {
        MessageBox(NULL, "Say() failed", "", MB_OK);
        hVoice = INVALID_HANDLE_VALUE;
        goto END2;
    }

    return TRUE;

END2:
    if (hCom != INVALID_HANDLE_VALUE)
    {
        fSuccess = CloseHandle(hCom); 
        if (!fSuccess) MessageBox(NULL, "CloseHandle failed", "", MB_OK);
    }
END1:
    return FALSE;
}

// Close device for voice output. hvoice is set back to invalid handle.
BOOL EndVoice()
{
    BOOL fSuccess;
    if (hVoice == INVALID_HANDLE_VALUE) return FALSE;
    ShutUp();
    _Say("See you.");
    fSuccess = CloseHandle(hVoice); 
    if (!fSuccess) {
        MessageBox(NULL, "CloseHandle failed", "", MB_OK);
        return FALSE;
    }
    else {
        hVoice = INVALID_HANDLE_VALUE;
        return TRUE;
    }
}

// Voice a string.
BOOL _Say(LPSTR lpstr)
{
    DWORD dwWritten;
    DWORD dwToWrite = lstrlen(lpstr);
    BOOL fSuccess;

    if (hVoice == INVALID_HANDLE_VALUE || !lpstr) return FALSE;
    fSuccess = WriteFile(hVoice, lpstr, dwToWrite, &dwWritten, NULL);
    if (!fSuccess || dwWritten != dwToWrite) return FALSE;
    else return TRUE;
}

// Say string if the given verbosity level is <= that set by user.
BOOL Say(SHORT shStringVLevel, LPSTR lpString)
{
    if (shStringVLevel <= shVerbosity) return _Say(lpString);
    else return FALSE;
}

// Force the device to say the given string immediately.
BOOL SayLine(SHORT shStringVLevel, LPSTR lpString)
{
    BOOL b;
    b = Say(shStringVLevel, lpString);
    Say(shStringVLevel, ".");
    return b;
}

// Send command to device to stop speech.
BOOL ShutUp()
{
    char buf[]="0=x";
    *buf=27;
    return _Say(buf);
}

// Take away & . character and append "or" + the quick key.
void ParseQuickKey(LPSTR lpszOut, LPSTR lpszIn)
{
    int i,j;
    char chKey=0;
    char buf[128];

    if (!lpszOut || !lpszIn) return;

    // Copy all except & character.
    for (i=0,j=0; lpszIn[i]; i++,j++)
    {
        // skip all & and .
        if (lpszIn[i]=='&') 
        {
            while (lpszIn[++i]=='&');
            chKey = lpszIn[i];
        }
        if (lpszIn[i]=='.') while (lpszIn[++i]=='.');

        buf[j] = lpszIn[i];
    }
    buf[j]=0;

    // append key found
    if (chKey) wsprintf(lpszOut, "%s or %c", buf, chKey);
    
    return;
}






// --------------------------------------------------------------------------
//
//  GetInvokeProp()
//
// --------------------------------------------------------------------------
HRESULT GetInvokeProp(IAccessible* pacc, int dispID, VARIANT* pvarChild,
    void* pResult)
{
    DISPPARAMS  dispp;
    EXCEPINFO   excepInfo;
    UINT        errArg;
    VARIANT     varResult;
    VARIANT     rgvarg[5];
    HRESULT     hr;

    VariantInit(&varResult);

    FillMemory(&excepInfo, sizeof(excepInfo), 0);

    dispp.cNamedArgs = 0;
    dispp.rgdispidNamedArgs = NULL;

    if (dispID != DISPID_ACC_LOCATION)
    {
        dispp.cArgs = 1;
        dispp.rgvarg = pvarChild;

        hr = pacc->Invoke(dispID, IID_NULL, 0, DISPATCH_PROPERTYGET,
            &dispp, &varResult, &excepInfo, &errArg);

        if (SUCCEEDED(hr))
        {
            if ((dispID == DISPID_ACC_ROLE) || (dispID == DISPID_ACC_STATE))
            {
                VariantInit((VARIANT *)pResult);
                VariantCopy((VARIANT *)pResult, &varResult);
                return(hr);
            }
            else
            {
                if (varResult.vt == VT_BSTR)
                {
                    *((BSTR *)pResult) = varResult.bstrVal;
                    return(hr);
                }
            }
        }

        VariantClear(&varResult);
    }
    else
    {
        dispp.cArgs = 5;
        dispp.rgvarg = rgvarg;

        //
        // Arguments are BACKWARDS
        // So [0] is varChild, [1] - [4] is height, width, top, left
        //
        for (errArg = 4; errArg >= 1; errArg--)
        {
            VariantInit(&rgvarg[errArg]);
            rgvarg[errArg].vt = VT_I4 | VT_BYREF;
            rgvarg[errArg].plVal = &(((long *)pResult)[4 - errArg]);
        }

        VariantInit(&rgvarg[0]);
        VariantCopy(&rgvarg[0], pvarChild);

        hr = pacc->Invoke(dispID, IID_NULL, 0, DISPATCH_METHOD,
            &dispp, NULL, &excepInfo, &errArg);
    }

    return(hr);
}

int my_wsprintf(
    LPTSTR lpOut,
    UINT *lpcchOut,
    LPTSTR fmt,
    ...
    )
{
    va_list marker;
    TCHAR szBuf[256];
    UINT cch;

    if (!lpOut || !lpcchOut) return 0;

    va_start(marker, fmt);
    cch = wvsprintf(szBuf, fmt, marker);
    va_end(marker);

    if (*lpcchOut >= cch) 
    {
        lstrcpy(lpOut, szBuf);
        *lpcchOut -= cch;
        return cch;
    }
    else return 0;
}

//---------------------------------------------------------------------------
// This is used to provide a pause before doing the default action on an item.
//---------------------------------------------------------------------------
void Pause (int Millisecs)
{
DWORD Start;
DWORD Now;
int   nSecondsRemaining;
int   nOldSeconds = 0;  
TCHAR szOldText[80];
TCHAR szNewText[80];

	Start = Now = GetTickCount();
    GetWindowText (hwndMain,szOldText,80);
	while (Now < (Start + Millisecs))
    {
        nSecondsRemaining = (((Start+Millisecs) - Now) / 1000) + 1;
        if (nSecondsRemaining != nOldSeconds)
        {
            wsprintf (szNewText,"Waiting: %d...",nSecondsRemaining);
	        SetWindowText (hwndMain,szNewText);
            nOldSeconds = nSecondsRemaining;
        }
        Now = GetTickCount();
    }
    SetWindowText (hwndMain,szOldText);
}

#ifdef _DEBUG
//--------------------------------------------------------------------------
//
// This new PrintIt function takes the arguments and then uses wvsprintf to
// format the string into a buffer. It then uses OutputDebugString to show 
// the string.
//
//--------------------------------------------------------------------------
void FAR CDECL PrintIt(LPSTR strFmt, ...)
{
static char	StringBuf[4096] = {0};
static int  len;
va_list		marker;

	va_start(marker, strFmt);
	len = wvsprintf (StringBuf,strFmt,marker);
	if (len > 0)
		OutputDebugString (StringBuf);
	return;
}

void dbobj(LPTSTR sz, IAccessible *pobj, LONG idChild)
{
    TCHAR szName[256]="", szRole[256]="";
    GetObjectProperty(pobj, idChild, ID_NAME, szName, 250);
    GetObjectProperty(pobj, idChild, ID_ROLE, szRole, 250);
    DBPRINTF("%s: %s %s, pobj 0x%lx idChild 0x%lx\r\n", sz, szName, szRole, pobj, idChild);
    return;
}

#endif

/*
    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.
*/
