// Copyright (c) 1996-1999 Microsoft Corporation // -------------------------------------------------------------------------- // // CLIENT.CPP // // Window client class. // // This handles navigation to other frame elements, and does its best // to manage the client area. We recognize special classes, like listboxes, // and those have their own classes to do stuff. // // -------------------------------------------------------------------------- #include "oleacc_p.h" #include "default.h" #include "classmap.h" #include "ctors.h" #include "window.h" #include "client.h" #include "debug.h" #define CH_PREFIX ((TCHAR)'&') #define CCH_WINDOW_SHORTCUTMAX 32 #define CCH_SHORTCUT 16 extern HRESULT DirNavigate(HWND, long, VARIANT *); // -------------------------------------------------------------------------- // // CreateClientObject() // // EXTERNAL function for CreatStdOle... // // -------------------------------------------------------------------------- HRESULT CreateClientObject(HWND hwnd, long idObject, REFIID riid, void** ppvObject) { UNUSED(idObject); InitPv(ppvObject); if (!IsWindow(hwnd)) return(E_FAIL); // Look for (and create) a suitable proxy/handler if one // exists. Use CreateClient as default if none found. // (FALSE => use client, as opposed to window, classes) return FindAndCreateWindowClass( hwnd, FALSE, CLASS_ClientObject, OBJID_CLIENT, 0, riid, ppvObject ); } // -------------------------------------------------------------------------- // // CreateClient() // // INTERNAL function for CreateClientObject() and ::Clone() // // -------------------------------------------------------------------------- HRESULT CreateClient(HWND hwnd, long idChildCur, REFIID riid, void** ppvObject) { CClient * pclient; HRESULT hr; pclient = new CClient(); if (!pclient) return(E_OUTOFMEMORY); pclient->Initialize(hwnd, idChildCur); hr = pclient->QueryInterface(riid, ppvObject); if (!SUCCEEDED(hr)) delete pclient; return(hr); } // -------------------------------------------------------------------------- // // CClient::Initialize() // // -------------------------------------------------------------------------- void CClient::Initialize(HWND hwnd, long idChildCur) { m_hwnd = hwnd; m_idChildCur = idChildCur; // If this is a comboex32, we want to pick up a preceeding // label, if one exists (just like we do for regular combos - // which set m_fUseLabel to TRUE in their own ::Initialize(). // The combo will ask the parent comboex32 for its name, and // it in turn will look for a label. if( IsComboEx( m_hwnd ) ) { m_fUseLabel = TRUE; } } // -------------------------------------------------------------------------- // // CClient::ValidateHwnd() // // This will validate VARIANTs for both HWND-children clients and normal // clients. If m_cChildren is non-zero, // // -------------------------------------------------------------------------- BOOL CClient::ValidateHwnd(VARIANT* pvar) { HWND hwndChild; switch (pvar->vt) { case VT_ERROR: if (pvar->scode != DISP_E_PARAMNOTFOUND) return(FALSE); // FALL THRU case VT_EMPTY: pvar->vt = VT_I4; pvar->lVal = 0; break; #ifdef VT_I2_IS_VALID // It should not be valid. That's why this is removed. case VT_I2: pvar->vt = VT_I4; pvar->lVal = (long)pvar->iVal; // FALL THROUGH #endif case VT_I4: if (pvar->lVal == 0) break; hwndChild = HwndFromHWNDID(m_hwnd, pvar->lVal); // This works for top-level AND child windows if (MyGetAncestor(hwndChild, GA_PARENT) != m_hwnd) return(FALSE); break; default: return(FALSE); } return(TRUE); } // -------------------------------------------------------------------------- // // CClient::get_accChildCount() // // This handles both non-HWND and HWND children. // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accChildCount(long *pcCount) { HWND hwndChild; HRESULT hr; hr = CAccessible::get_accChildCount(pcCount); if (!SUCCEEDED(hr)) return hr; // popup menus (CMenuPopup) can have a NULL hwnd if created for an 'invisible' // menu. We probably shouldn't create them in the first place, but don't want // to change what objects we expose at this stage - so instead special case // NULL. This is to avoid calling GetWindow( NULL ), which would produce // debug output complaining, and those annoy the stress team. if( m_hwnd != NULL ) { for (hwndChild = ::GetWindow(m_hwnd, GW_CHILD); hwndChild; hwndChild = ::GetWindow(hwndChild, GW_HWNDNEXT)) ++(*pcCount); } return S_OK; } // -------------------------------------------------------------------------- // // CClient::get_accName() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accName(VARIANT varChild, BSTR *pszName) { InitPv(pszName); // // Validate--this does NOT accept a child ID. // if (! ValidateChild(&varChild)) return(E_INVALIDARG); return(HrGetWindowName(m_hwnd, m_fUseLabel, pszName)); } // -------------------------------------------------------------------------- // // CClient::get_accRole() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accRole(VARIANT varChild, VARIANT *pvarRole) { InitPvar(pvarRole); // // Validate--this does NOT accept a child ID. // if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; pvarRole->lVal = ROLE_SYSTEM_CLIENT; return(S_OK); } // -------------------------------------------------------------------------- // // CClient::get_accState() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accState(VARIANT varChild, VARIANT *pvarState) { WINDOWINFO wi; HWND hwndActive; InitPvar(pvarState); // // Validate--this does NOT accept a child ID. // if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarState->vt = VT_I4; pvarState->lVal = 0; // // Are we the focus? Are we enabled, visible, etc? // if (!MyGetWindowInfo(m_hwnd, &wi)) { pvarState->lVal |= STATE_SYSTEM_INVISIBLE; return(S_OK); } if (!(wi.dwStyle & WS_VISIBLE)) pvarState->lVal |= STATE_SYSTEM_INVISIBLE; if (wi.dwStyle & WS_DISABLED) pvarState->lVal |= STATE_SYSTEM_UNAVAILABLE; if (MyGetFocus() == m_hwnd) pvarState->lVal |= STATE_SYSTEM_FOCUSED; hwndActive = GetForegroundWindow(); if (hwndActive == MyGetAncestor(m_hwnd, GA_ROOT)) pvarState->lVal |= STATE_SYSTEM_FOCUSABLE; return(S_OK); } // -------------------------------------------------------------------------- // // CClient::get_accKeyboardShortcut() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut) { InitPv(pszShortcut); // // Validate--this does NOT accept a child ID // if (!ValidateChild(&varChild)) return(E_INVALIDARG); // reject child elements - shortcut key only applies to the overall // control. if ( varChild.lVal != 0 ) return(E_NOT_APPLICABLE); return(HrGetWindowShortcut(m_hwnd, m_fUseLabel, pszShortcut)); } // -------------------------------------------------------------------------- // // CClient::get_accFocus() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::get_accFocus(VARIANT *pvarFocus) { HWND hwndFocus; InitPvar(pvarFocus); // // This RETURNS a child ID. // hwndFocus = MyGetFocus(); // // Is the current focus a child of us? // if (m_hwnd == hwndFocus) { pvarFocus->vt = VT_I4; pvarFocus->lVal = 0; } else if (IsChild(m_hwnd, hwndFocus)) return(GetWindowObject(hwndFocus, pvarFocus)); return(S_OK); } // -------------------------------------------------------------------------- // // CClient::accLocation() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { RECT rc; InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight); // // Validate--this does NOT take a child ID // if (!ValidateChild(&varChild)) return(E_INVALIDARG); MyGetRect(m_hwnd, &rc, FALSE); MapWindowPoints(m_hwnd, NULL, (LPPOINT)&rc, 2); *pxLeft = rc.left; *pyTop = rc.top; *pcxWidth = rc.right - rc.left; *pcyHeight = rc.bottom - rc.top; return(S_OK); } // -------------------------------------------------------------------------- // // CClient::accSelect() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::accSelect( long lSelFlags, VARIANT varChild ) { if( ! ValidateChild( & varChild ) || ! ValidateSelFlags( lSelFlags ) ) return E_INVALIDARG; if( lSelFlags != SELFLAG_TAKEFOCUS ) return E_NOT_APPLICABLE; if( varChild.lVal ) return S_FALSE; MySetFocus( m_hwnd ); return S_OK; } // -------------------------------------------------------------------------- // // CClient::accNavigate() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::accNavigate(long dwNavDir, VARIANT varStart, VARIANT * pvarEnd) { HWND hwndChild; int gww; InitPvar(pvarEnd); // // Validate--this accepts an HWND id. // if (!ValidateHwnd(&varStart) || !ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (dwNavDir == NAVDIR_FIRSTCHILD) { gww = GW_HWNDNEXT; hwndChild = ::GetWindow(m_hwnd, GW_CHILD); if (!hwndChild) return(S_FALSE); goto NextPrevChild; } else if (dwNavDir == NAVDIR_LASTCHILD) { gww = GW_HWNDPREV; hwndChild = ::GetWindow(m_hwnd, GW_CHILD); if (!hwndChild) return(S_FALSE); // Start at the end and work backwards hwndChild = ::GetWindow(hwndChild, GW_HWNDLAST); goto NextPrevChild; } else if (!varStart.lVal) return(GetParentToNavigate(OBJID_CLIENT, m_hwnd, OBJID_WINDOW, dwNavDir, pvarEnd)); hwndChild = HwndFromHWNDID(m_hwnd, varStart.lVal); if ((dwNavDir == NAVDIR_NEXT) || (dwNavDir == NAVDIR_PREVIOUS)) { gww = ((dwNavDir == NAVDIR_NEXT) ? GW_HWNDNEXT : GW_HWNDPREV); while (hwndChild = ::GetWindow(hwndChild, gww)) { NextPrevChild: if (IsWindowVisible(hwndChild)) return(GetWindowObject(hwndChild, pvarEnd)); } } else return(DirNavigate(hwndChild, dwNavDir, pvarEnd)); return(S_FALSE); } // -------------------------------------------------------------------------- // // CClient::accHitTest() // // This ALWAYS returns a real object. // // -------------------------------------------------------------------------- STDMETHODIMP CClient::accHitTest(long xLeft, long yTop, VARIANT *pvarHit) { HWND hwndChild; POINT pt; InitPvar(pvarHit); pt.x = xLeft; pt.y = yTop; ScreenToClient(m_hwnd, &pt); hwndChild = MyRealChildWindowFromPoint(m_hwnd, pt); if (hwndChild) { if (hwndChild == m_hwnd) { pvarHit->vt = VT_I4; pvarHit->lVal = 0; return(S_OK); } else return(GetWindowObject(hwndChild, pvarHit)); } else { // Null window means point isn't in us at all... return(S_FALSE); } } // -------------------------------------------------------------------------- // // CClient::Next() // // This loops through non-HWND children first, then HWND children. // // -------------------------------------------------------------------------- STDMETHODIMP CClient::Next(ULONG celt, VARIANT *rgvar, ULONG* pceltFetched) { HWND hwndChild; VARIANT* pvar; long cFetched; HRESULT hr; if( m_idChildCur == -1 ) { // If we're at the end, can't return any more... *pceltFetched = 0; return celt == 0 ? S_OK : S_FALSE; } SetupChildren(); // Can be NULL if (pceltFetched) *pceltFetched = 0; // Grab the non-HWND dudes first. if (!IsHWNDID(m_idChildCur) && (m_idChildCur < m_cChildren)) { cFetched = 0; hr = CAccessible::Next(celt, rgvar, (ULONG*)&cFetched); if (!SUCCEEDED(hr)) return hr; celt -= cFetched; rgvar += cFetched; if (pceltFetched) *pceltFetched += cFetched; if (!celt) return S_OK; } pvar = rgvar; cFetched = 0; if (!IsHWNDID(m_idChildCur)) { Assert(m_idChildCur == m_cChildren); hwndChild = ::GetWindow(m_hwnd, GW_CHILD); } else { hwndChild = HwndFromHWNDID(m_hwnd, m_idChildCur); } // // Loop through our HWND children now // while (hwndChild && (cFetched < (long)celt)) { hr = GetWindowObject(hwndChild, pvar); if (SUCCEEDED(hr)) { ++pvar; ++cFetched; } else { // Failed - skip this one - but keep going. TraceWarningHR( hr, TEXT("CClient::Next - GetWindowObject failed on hwnd 0x%p, skipping"), hwndChild ); } hwndChild = ::GetWindow(hwndChild, GW_HWNDNEXT); } // Remember current position // Have to special-case NULL - GetWindow(...) returns NULL // when we reach the end - have to store a special value // so we know that we're at the end the next time we're // called. if( hwndChild == NULL ) m_idChildCur = -1; else m_idChildCur = HWNDIDFromHwnd(m_hwnd, hwndChild); // // Fill in the number fetched // if (pceltFetched) *pceltFetched += cFetched; // // Return S_FALSE if we grabbed fewer items than requested // return (cFetched < (long)celt) ? S_FALSE : S_OK; } // -------------------------------------------------------------------------- // // CClient::Skip() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::Skip(ULONG celt) { HWND hwndT; if( m_idChildCur == -1 ) { // If we're at the end, can't return any more... return celt == 0 ? S_FALSE : S_OK; } SetupChildren(); // Skip non-HWND items if (!IsHWNDID(m_idChildCur) && (m_idChildCur < m_cChildren)) { long dAway; dAway = m_cChildren - m_idChildCur; if (celt >= (DWORD)dAway) { celt -= dAway; m_idChildCur = m_cChildren; } else { m_idChildCur += celt; return S_OK; } } // Skip the HWND children next if (!IsHWNDID(m_idChildCur)) { Assert(m_idChildCur == m_cChildren); hwndT = ::GetWindow(m_hwnd, GW_CHILD); } else hwndT = HwndFromHWNDID(m_hwnd, m_idChildCur); while (hwndT && (celt-- > 0)) { hwndT = ::GetWindow(hwndT, GW_HWNDNEXT); } // Remember current position // Have to special-case NULL - GetWindow(...) returns NULL // when we reach the end - have to store a special value // so we know that we're at the end the next time we're // called. if( hwndT == NULL ) m_idChildCur = -1; else m_idChildCur = HWNDIDFromHwnd(m_hwnd, hwndT); return celt ? S_FALSE : S_OK; } // -------------------------------------------------------------------------- // // CClient::Reset() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::Reset(void) { m_idChildCur = 0; return S_OK; } // -------------------------------------------------------------------------- // // CClient::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CClient::Clone(IEnumVARIANT** ppenum) { InitPv(ppenum); // Look for (and create) a suitable proxy/handler if one // exists. Use CreateClient as default if none found. // (FALSE => use client, as opposed to window, classes) return FindAndCreateWindowClass( m_hwnd, FALSE, CLASS_ClientObject, OBJID_CLIENT, m_idChildCur, IID_IEnumVARIANT, (void **)ppenum ); } // -------------------------------------------------------------------------- // // GetTextString() // // Parameters: hwnd of the window to get the text from, and a boolean // that indicates whether or not we should always allocate memory to // return. I.E., if the window says the size of the text is 0, and // fAllocIfEmpty is TRUE, then we'll still allocate 1 byte (size+1). // // This contains a bit of a hack. The way it was originally written, this // will try to get the ENTIRE text of say, a RichEdit control, even if that // document is HUGE. Eventually we want to support that, but we are going to // need to do better than LocalAlloc. With a big document, we would page // fault sometimes, because even though the memory is allocated, it // may not be able to be paged in. JeffBog suggested that the way to // check is to try to read/write both ends of the allocated space, and // assume that if that works everything in between is OK too. // // So here's the temporary hack (BOGUS!) // I am putting an artificial limit of 4096 bytes on the allocation. // I am also going to do IsBadWritePtr on the thing, instead of just // checking if the pointer returned by alloc is null. duh. // -------------------------------------------------------------------------- LPTSTR GetTextString(HWND hwnd, BOOL fAllocIfEmpty) { UINT cchText; LPTSTR lpText; #define MAX_TEXT_SIZE 4096 // // Look for a name property! // lpText = NULL; if (!IsWindow(hwnd)) return (NULL); // // Barring that, use window text. // BOGUS! Strip out the '&'. // cchText = SendMessageINT(hwnd, WM_GETTEXTLENGTH, 0, 0); // hack cchText = (cchText > MAX_TEXT_SIZE ? MAX_TEXT_SIZE : cchText); // Allocate a buffer if (cchText || fAllocIfEmpty) { lpText = (LPTSTR)LocalAlloc(LPTR, (cchText+1)*sizeof(TCHAR)); if (IsBadWritePtr (lpText,cchText+1)) return(NULL); if (cchText) SendMessage(hwnd, WM_GETTEXT, cchText+1, (LPARAM)lpText); } return(lpText); } // -------------------------------------------------------------------------- // // GetLabelString() // // This walks backwards among peer windows to find a static field. It stops // if it gets to the front or hits a group/tabstop, just like the dialog // manager does. // // -------------------------------------------------------------------------- LPTSTR GetLabelString(HWND hwnd) { HWND hwndLabel; LONG lStyle; LRESULT lResult; LPTSTR lpszLabel; lpszLabel = NULL; if (!IsWindow(hwnd)) return (NULL); hwndLabel = hwnd; while (hwndLabel = ::GetWindow(hwndLabel, GW_HWNDPREV)) { lStyle = GetWindowLong(hwndLabel, GWL_STYLE); // // Is this a static dude? // lResult = SendMessage(hwndLabel, WM_GETDLGCODE, 0, 0L); if (lResult & DLGC_STATIC) { // // Great, we've found our label. // lpszLabel = GetTextString(hwndLabel, FALSE); break; } // // Skip if invisible // Note that we do this after checking if its a staic, // so that we give invisible statics a chance. Using invisible // statics is a easy workaround to add names to controls // without changing the visual UI. // if (!(lStyle & WS_VISIBLE)) continue; // // Is this a tabstop or group? If so, bail out now. // if (lStyle & (WS_GROUP | WS_TABSTOP)) break; } return(lpszLabel); } // -------------------------------------------------------------------------- // // HrGetWindowName() // // -------------------------------------------------------------------------- HRESULT HrGetWindowName(HWND hwnd, BOOL fLookForLabel, BSTR* pszName) { LPTSTR lpText; lpText = NULL; if (!IsWindow(hwnd)) return (E_INVALIDARG); // // Look for a name property! // // // If use a label, do that instead // if (!fLookForLabel) { // // Try using a label anyway if this control has no window text // and the parent is a dialog. // lpText = GetTextString(hwnd, FALSE); if (!lpText) { HWND hwndParent = MyGetAncestor( hwnd, GA_PARENT ); if( hwndParent && CLASS_DialogClient == GetWindowClass( hwndParent ) ) { fLookForLabel = TRUE; } } } if (fLookForLabel) lpText = GetLabelString(hwnd); if (! lpText) return(S_FALSE); // // Strip out the mnemonic. // StripMnemonic(lpText); // Get a BSTR *pszName = TCharSysAllocString(lpText); // Free our buffer LocalFree((HANDLE)lpText); // Did the BSTR succeed? if (! *pszName) return(E_OUTOFMEMORY); return(S_OK); } // -------------------------------------------------------------------------- // // HrGetWindowShortcut() // // -------------------------------------------------------------------------- HRESULT HrGetWindowShortcut(HWND hwnd, BOOL fUseLabel, BSTR* pszShortcut) { // // Get the window text, and see if the '&' character is in it. // LPTSTR lpText; TCHAR chMnemonic; if (!IsWindow(hwnd)) return (E_INVALIDARG); lpText = NULL; if (! fUseLabel) { // // Try using a label anyway if this control has no window text // and the parent is a dialog. // lpText = GetTextString(hwnd, FALSE); if (!lpText) { HWND hwndParent = MyGetAncestor( hwnd, GA_PARENT ); if( hwndParent && CLASS_DialogClient == GetWindowClass( hwndParent ) ) { fUseLabel = TRUE; } } } if (fUseLabel) lpText = GetLabelString(hwnd); if (! lpText) return(S_FALSE); chMnemonic = StripMnemonic(lpText); LocalFree((HANDLE)lpText); // // Is there a mnemonic? // if (chMnemonic) { // // Make a string of the form "Alt+ch". // TCHAR szKey[2]; *szKey = chMnemonic; *(szKey+1) = 0; return(HrMakeShortcut(szKey, pszShortcut)); } return(S_FALSE); } // -------------------------------------------------------------------------- // // HrMakeShortcut() // // This takes a string for the hotkey, then combines it with the "Alt+%s" // shortcut format to make the real string combination. If asked, it will // free the hotkey string passed in. // // -------------------------------------------------------------------------- HRESULT HrMakeShortcut(LPTSTR lpszKey, BSTR* pszShortcut) { TCHAR szFormat[CCH_SHORTCUT]; TCHAR szResult[CCH_WINDOW_SHORTCUTMAX]; // Get the format string LoadString(hinstResDll, STR_MENU_SHORTCUT_FORMAT, szFormat, ARRAYSIZE(szFormat)); // Make the result wsprintf(szResult, szFormat, lpszKey); // Alloc a BSTR of the result *pszShortcut = TCharSysAllocString(szResult); // Should we free the key string? // Did the allocation fail? if (!*pszShortcut) return(E_OUTOFMEMORY); else return(S_OK); } // 'Slide' string along by one char, in-place, to effectively remove the // char pointed to be pStr. // eg. if pStr points to the 'd' of 'abcdefg', the string // will be transformed to 'abcefg'. // Note: the char pointed to by pStr is assumed to be a single-byte // char (if compiled under ANSI - not an issue if compiled inder UNICODE) // Note: Makes use of the fact that no DBCS char has NUL as the trail byte. void SlideStrAndRemoveChar( LPTSTR pStr ) { LPTSTR pLead = pStr + 1; // Checking the trailing pStr ptr means that we continue until we've // copied (not just encountered) the terminating NUL. while( *pStr ) *pStr++ = *pLead++; } // -------------------------------------------------------------------------- // // StripMnemonic() // // This removes the mnemonic prefix. However, if we see '&&', we keep // one '&'. // // // Modified to be DBCS 'aware' - uses CharNext() instead of ptr++ to // advance through the string. Will only return shortcut char if its a // single byte char, though. (Would have to change all usages of this // function to allow return of a potentially DBCS char.) Will remove this // restriction in the planned-for-future fully-UNICODE OLEACC. // This restriction should not be much of a problem, because DBCS chars, // which typically require an IME to compose, are very unlikely to be // used as 'shortcut' chars. eg. Japanese Windows uses underlined roman // chars as shortcut chars. // (This will all be replaced by simpler code when we go UNICODE...) // -------------------------------------------------------------------------- TCHAR StripMnemonic(LPTSTR lpszText) { TCHAR ch; TCHAR chNext = 0; while( *lpszText == (TCHAR)' ' ) lpszText = CharNext( lpszText ); while( ch = *lpszText ) { lpszText = CharNext( lpszText ); if (ch == CH_PREFIX) { // Get the next character. chNext = *lpszText; // If it too is '&', then this isn't a mnemonic, it's the // actual '&' character. if (chNext == CH_PREFIX) chNext = 0; // Skip 'n' strip the '&' character SlideStrAndRemoveChar( lpszText - 1 ); #ifdef UNICODE CharLowerBuff(&chNext, 1); #else if( IsDBCSLeadByte( chNext ) ) { // We're ignoring DBCS chars as shortcut chars // - would need to change this func and all callers // to handle a returned DB char otherwise. // For the moment, we just ensure we don't return // an 'orphaned' lead byte... chNext = '\0'; } else { CharLowerBuff(&chNext, 1); } #endif break; } } return(chNext); } // -------------------------------------------------------------------------- // // DirNavigate() // // Figures out which peer window is closest to us in the given direction. // // -------------------------------------------------------------------------- HRESULT DirNavigate(HWND hwndSelf, long dwNavDir, VARIANT* pvarEnd) { HWND hwndPeer; RECT rcSelf; RECT rcPeer; int dwClosest; int dwT; HWND hwndClosest; if (!IsWindow(hwndSelf)) return (E_INVALIDARG); MyGetRect(hwndSelf, &rcSelf, TRUE); dwClosest = 0x7FFFFFFF; hwndClosest = NULL; for (hwndPeer = ::GetWindow(hwndSelf, GW_HWNDFIRST); hwndPeer; hwndPeer = ::GetWindow(hwndPeer, GW_HWNDNEXT)) { if ((hwndPeer == hwndSelf) || !IsWindowVisible(hwndPeer)) continue; MyGetRect(hwndPeer, &rcPeer, TRUE); dwT = 0x7FFFFFFF; switch (dwNavDir) { case NAVDIR_LEFT: // // Bogus! Only try this one if it intersects us vertically // if (rcPeer.left < rcSelf.left) dwT = rcSelf.left - rcPeer.left; break; case NAVDIR_UP: // // Bogus! Only try this one if it intersects us horizontally // if (rcPeer.top < rcSelf.top) dwT = rcSelf.top - rcPeer.top; break; case NAVDIR_RIGHT: // // Bogus! Only try this one if it intersects us vertically // if (rcPeer.right > rcSelf.right) dwT = rcPeer.right - rcSelf.right; break; case NAVDIR_DOWN: // // Bogus! Only try this one if it intersects us horizontally // if (rcPeer.bottom > rcSelf.bottom) dwT = rcPeer.bottom - rcSelf.bottom; break; default: AssertStr( TEXT("INVALID NAVDIR") ); } if (dwT < dwClosest) { dwClosest = dwT; hwndClosest = hwndPeer; } } if (hwndClosest) return(GetWindowObject(hwndClosest, pvarEnd)); else return(S_FALSE); } // -------------------------------------------------------------------------- // // InTheShell() // // Returns TRUE if the object is on the shell tray, desktop, or process. // // -------------------------------------------------------------------------- BOOL InTheShell(HWND hwnd, int nPart) { HWND hwndShell; static TCHAR szShellTray[] = TEXT("Shell_TrayWnd"); DWORD idProcessUs; DWORD idProcessShell; hwndShell = GetShellWindow(); switch (nPart) { case SHELL_TRAY: // Use the tray window instead. hwndShell = FindWindowEx(NULL, NULL, szShellTray, NULL); // Fall thru case SHELL_DESKTOP: if (!hwndShell) return(FALSE); return(MyGetAncestor(hwnd, GA_ROOT) == hwndShell); case SHELL_PROCESS: idProcessUs = NULL; idProcessShell = NULL; GetWindowThreadProcessId(hwnd, &idProcessUs); GetWindowThreadProcessId(hwndShell, &idProcessShell); return(idProcessUs && (idProcessUs == idProcessShell)); } AssertStr( TEXT("GetShellWindow returned strange part") ); return(FALSE); } // --- start of original comment --- // // We need a way for HWND and non-HWND children to live in the same // namespace together. Since children pass up peer-to-peer navigation to // their parent, we need a way for HWND children to identify themselves in // the navigate call. Since HWND children are always objects, it is fine // for the client parent to not accept HWND ids in all other methods. One // can do it, but it is a lot of work. // // Examples to date of mixed: // (1) Comboboxes (dropdown always a window, cur item may or may not be, // button never is) // (2) Toolbars (dropdown is a window, buttons aren't) // // We want the client manager to handle IEnumVARIANT, validation, etc. // // --- end of original comment --- // // A 'HWNDID' is basically a HWND squeezed (somehow) into a DWORD idChild. // // IsHWNDID checks if a idChild is one of these HWNDIDs, or just a regular // idChild (ie. a 1-based child element index) // // HWNDIDFromHwnd and HwndFromHWNDID encode and decode HWNDs as idChilds. // // Previous versions of these didn't have a hwndParent parameter, // and squeezed a HWND into bits 0..30, with bit31 set to 1 as the // 'this is a HWND id' flag. That scheme doesn't work for HWNDs // which have bt31 set... (these do exist on long-running systems - // the top WORD of the HWND is a 'uniqueifier', which gets inc'd every // time the slot - indicated by the bottom HWND - is reused. Of course, // this implementation can change at any time in the furure, so we // shouldn't rely on it, or rely on any bits being 'always 0' or // otherwise.) // // The current sceheme still uses the high bit as a flag, but if set, // the remaining bits are now a count into the parent window's children // chain. // // // It may be possible to remove these althogther - if the destination object // corresponds to a full HWND, instead of returning a HWNDID, instead return // the full IAccessible for that object. (Still have to figure out what happens // when that IAccessible needs to navigate to one of its siblings, though.) BOOL IsHWNDID( DWORD id ) { // hight bit indicates that it represents a HWND. return id & 0x80000000; } DWORD HWNDIDFromHwnd( HWND hwndParent, HWND hwnd ) { // Traverse the child list, counting as we go, till we hit the HWND we want... int i = 0; HWND hChild = GetWindow( hwndParent, GW_CHILD ); while( hChild != NULL ) { if( hChild == hwnd ) { return i | 0x80000000; } i++; hChild = GetWindow( hChild, GW_HWNDNEXT ); } return 0; } HWND HwndFromHWNDID( HWND hwndParent, DWORD id ) { // Traverse the child list, till we get to the one with this index... int i = id & ~ 0x80000000; HWND hChild = GetWindow( hwndParent, GW_CHILD ); while( i != 0 && hChild != NULL ) { i--; hChild = GetWindow( hChild, GW_HWNDNEXT ); } return hChild; }