// Copyright (c) 1996-1999 Microsoft Corporation // -------------------------------------------------------------------------- // // CARET.CPP // // This file has the implementation of the caret system object. // // -------------------------------------------------------------------------- #include "oleacc_p.h" #include "default.h" #include "caret.h" // -------------------------------------------------------------------------- // prototypes for local functions // -------------------------------------------------------------------------- int AddInts (int Value1, int Value2); BOOL GetDeviceRect (HDC hDestDC,RECT ClientRect,LPRECT lpDeviceRect); BOOL GetEditCaretOffset( HWND hEdit, int nHeight, int * pnOffset ); BOOL Rect1IsOutsideRect2( RECT const & rc1, RECT const & rc2 ); // -------------------------------------------------------------------------- // // CreateCaretObject() // // -------------------------------------------------------------------------- HRESULT CreateCaretObject(HWND hwnd, long idObject, REFIID riid, void** ppvCaret) { UNUSED(idObject); return(CreateCaretThing(hwnd, riid, ppvCaret)); } // -------------------------------------------------------------------------- // // CreateCaretThing() // // -------------------------------------------------------------------------- HRESULT CreateCaretThing(HWND hwnd, REFIID riid, void **ppvCaret) { CCaret * pcaret; HRESULT hr; InitPv(ppvCaret); pcaret = new CCaret(); if (pcaret) { if (! pcaret->FInitialize(hwnd)) { delete pcaret; return(E_FAIL); } } else return(E_OUTOFMEMORY); hr = pcaret->QueryInterface(riid, ppvCaret); if (!SUCCEEDED(hr)) delete pcaret; return(hr); } // -------------------------------------------------------------------------- // // CCaret::FInitialize() // // -------------------------------------------------------------------------- BOOL CCaret::FInitialize(HWND hwnd) { // Is this an OK window? m_dwThreadId = GetWindowThreadProcessId(hwnd, NULL); if (! m_dwThreadId) return(FALSE); // // NOTE: We always initialize, even if this window doesn't own the // caret. We will treat it as invisible in that case. // m_hwnd = hwnd; return(TRUE); } // -------------------------------------------------------------------------- // // CCaret::Clone() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::Clone(IEnumVARIANT** ppenum) { return(CreateCaretThing(m_hwnd, IID_IEnumVARIANT, (void **)ppenum)); } // -------------------------------------------------------------------------- // // CCaret::get_accName() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::get_accName(VARIANT varChild, BSTR* pszName) { InitPv(pszName); // // Validate // if (!ValidateChild(&varChild)) return(E_INVALIDARG); return(HrCreateString(STR_CARETNAME, pszName)); } // -------------------------------------------------------------------------- // // CCaret::get_accRole() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::get_accRole(VARIANT varChild, VARIANT * pvarRole) { InitPvar(pvarRole); // // Validate // if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; pvarRole->lVal = ROLE_SYSTEM_CARET; return(S_OK); } // -------------------------------------------------------------------------- // // CCaret::get_accState() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::get_accState(VARIANT varChild, VARIANT * pvarState) { GUITHREADINFO gui; InitPvar(pvarState); if (!ValidateChild(&varChild)) return(E_INVALIDARG); pvarState->vt = VT_I4; pvarState->lVal = 0; if (! MyGetGUIThreadInfo(m_dwThreadId, &gui) || (gui.hwndCaret != m_hwnd)) { pvarState->lVal |= STATE_SYSTEM_INVISIBLE; return(S_FALSE); } if (!(gui.flags & GUI_CARETBLINKING)) pvarState->lVal |= STATE_SYSTEM_INVISIBLE; return(S_OK); } // -------------------------------------------------------------------------- // // CCaret::accLocation() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { GUITHREADINFO gui; HDC hDC; RECT rcDevice; InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight); // // Validate // if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (!MyGetGUIThreadInfo(m_dwThreadId, &gui) || (gui.hwndCaret != m_hwnd)) { return(S_FALSE); } BOOL fWindowsXPOrGreater = FALSE; OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); osvi.dwMajorVersion = 0; osvi.dwMinorVersion = 0; GetVersionEx( &osvi ); if ( osvi.dwMajorVersion >= 5 && osvi.dwMinorVersion >= 1 ) fWindowsXPOrGreater = TRUE; if( ! fWindowsXPOrGreater ) { // Instead of using MapWindowPoints, we use a private // function called GetDeviceRect that takes private mapping // modes, etc. into account. hDC = GetDC (m_hwnd); if( !hDC ) return E_OUTOFMEMORY; GetDeviceRect (hDC,gui.rcCaret,&rcDevice); ReleaseDC (m_hwnd,hDC); } else { // On Windows XP, GDI does all the necessary mapping when // SetCaretPos is called, so we only need to do screen->client // mapping here. rcDevice = gui.rcCaret; MapWindowPoints( m_hwnd, NULL, (POINT *) & rcDevice, 2 ); } // TODO - only do this for EDITs... // Suggest using MyRealGetWindowClass, stricmp against "EDIT" // Also get width in addition to offset - for now, assume 0. int nOffset; TCHAR szWindowClass[128]; MyGetWindowClass( m_hwnd, szWindowClass, ARRAYSIZE(szWindowClass) ); if ( lstrcmpi( szWindowClass, TEXT("EDIT") ) == 0 ) { if( GetEditCaretOffset( m_hwnd, rcDevice.bottom - rcDevice.top, & nOffset ) ) { DBPRINTF( TEXT("GetEditCaretOffset nOffset=%d\r\n"), nOffset ); rcDevice.left -= nOffset; rcDevice.right = rcDevice.left + 1; } } // For RichEdits, use an offset of 3??? // if ( lstrcmpi( szWindowClass, TEXT("RICHEDIT20A") ) == 0 || // Win9x lstrcmpi( szWindowClass, TEXT("RICHEDIT20W") ) == 0 || // Win2k + lstrcmpi( szWindowClass, TEXT("RICHEDIT") ) == 0) // NT4 { DBPRINTF( TEXT("This is and richedit\r\n") ); rcDevice.left += 3; rcDevice.right = rcDevice.left + 1; } // Sanity check against returned rect - sometimes we get back // gabage cursor coords (of the order of (FFFFB205, FFFFB3C5)) // - eg. when in notepad, place cursor at top/bottom of doc, // click on scrollbar arrowheads to scroll cursor off top/bottom // of visible area. // We still get back a valid hwnd and a CURSORBLINKING flag fom // GetGUIThreadInfo, so they aren't much use to detect this. RECT rcWindow; GetWindowRect( m_hwnd, & rcWindow ); if( Rect1IsOutsideRect2( rcDevice, rcWindow ) ) { return S_FALSE; } *pxLeft = rcDevice.left; *pyTop = rcDevice.top; *pcxWidth = rcDevice.right - rcDevice.left; *pcyHeight = rcDevice.bottom - rcDevice.top; return(S_OK); } // -------------------------------------------------------------------------- // // CCaret::accHitTest() // // -------------------------------------------------------------------------- STDMETHODIMP CCaret::accHitTest(long xLeft, long yTop, VARIANT * pvarChild) { GUITHREADINFO gui; POINT pt; InitPvar(pvarChild); if (! MyGetGUIThreadInfo(m_dwThreadId, &gui) || (gui.hwndCaret != m_hwnd)) { return(S_FALSE); } pt.x = xLeft; pt.y = yTop; ScreenToClient(m_hwnd, &pt); if (PtInRect(&gui.rcCaret, pt)) { pvarChild->vt = VT_I4; pvarChild->lVal = 0; return(S_OK); } else return(S_FALSE); } //============================================================================ // This function takes a destination DC, a rectangle in client coordinates, // and a pointer to the rectangle structure that will hold the device // coordinates of the rectangle. The device coordinates can be used as screen // coordinates. //============================================================================ BOOL GetDeviceRect (HDC hDestDC,RECT ClientRect,LPRECT lpDeviceRect) { POINT aPoint; int temp; lpDeviceRect->left = ClientRect.left; lpDeviceRect->top = ClientRect.top; // just set the device rect to the rect given and then do LPtoDP for both points lpDeviceRect->right = ClientRect.right; lpDeviceRect->bottom = ClientRect.bottom; LPtoDP (hDestDC,(LPPOINT)lpDeviceRect,2); // Now we need to convert from client coords to screen coords. We do this by // getting the DC Origin and then using the AddInts function to add the origin // of the 'drawing area' to the client coordinates. This is safer and easier // than using ClientToScreen, MapWindowPoints, and/or getting the WindowRect if // it is a WindowDC. GetDCOrgEx(hDestDC,&aPoint); lpDeviceRect->left = AddInts (lpDeviceRect->left,aPoint.x); lpDeviceRect->top = AddInts (lpDeviceRect->top,aPoint.y); lpDeviceRect->right = AddInts (lpDeviceRect->right,aPoint.x); lpDeviceRect->bottom = AddInts (lpDeviceRect->bottom,aPoint.y); // make sure that the top left is less than the bottom right!!! if (lpDeviceRect->left > lpDeviceRect->right) { temp = lpDeviceRect->right; lpDeviceRect->right = lpDeviceRect->left; lpDeviceRect->left = temp; } if (lpDeviceRect->top > lpDeviceRect->bottom) { temp = lpDeviceRect->bottom; lpDeviceRect->bottom = lpDeviceRect->top; lpDeviceRect->top = temp; } return TRUE; } // end GetDeviceRect //============================================================================ // AddInts adds two integers and makes sure that the result does not overflow // the size of an integer. // Theory: positive + positive = positive // negative + negative = negative // positive + negative = (sign of operand with greater absolute value) // negative + positive = (sign of operand with greater absolute value) // On the second two cases, it can't wrap, so I don't check those. //============================================================================ int AddInts (int Value1, int Value2) { int result; result = Value1 + Value2; if (Value1 > 0 && Value2 > 0 && result < 0) result = INT_MAX; if (Value1 < 0 && Value2 < 0 && result > 0) result = INT_MIN; return result; } #define CURSOR_USA 0xffff #define CURSOR_LTR 0xf00c #define CURSOR_RTL 0xf00d #define CURSOR_THAI 0xf00e #define LANG_ID(x) ((DWORD)(DWORD_PTR)x & 0x000003ff); #ifndef SPI_GETCARETWIDTH #define SPI_GETCARETWIDTH 0x2006 #endif // GetEditCaretOffset // // Determine the offset to the actual caret bar from the start // of the caret bitmap. // // Only applies to EDIT controls, not RichEdits. // // This code is based on \windows\core\cslpk\lpk\lpk_edit.c, (EditCreateCaret) // which does the actual caret processing for the edit control. We mimic that // code, leaving out the bits that we don't need. WCHAR GetEditCursorCode( HWND hEdit ) { DWORD idThread = GetWindowThreadProcessId( hEdit, NULL ); UINT uikl = LANG_ID( GetKeyboardLayout( idThread ) ); WCHAR wcCursorCode = CURSOR_USA; switch( uikl ) { case LANG_THAI: wcCursorCode = CURSOR_THAI; break; case LANG_ARABIC: case LANG_FARSI: case LANG_URDU: case LANG_HEBREW: wcCursorCode = CURSOR_RTL; break; default: WCHAR wcBuf[ 80 ]; // Registry read buffer int cBuf; cBuf = GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_FONTSIGNATURE, wcBuf, ARRAYSIZE( wcBuf ) ); BOOL fUserBidiLocale = ( cBuf && wcBuf[7] & 0x0800 ) ? TRUE : FALSE; if( fUserBidiLocale ) { // Other keyboards have a left-to-right pointing caret // in Bidi locales. wcCursorCode = CURSOR_LTR; } } return wcCursorCode; } BOOL GetEditCaretOffsetFromFont( HWND hEdit, WCHAR wcCursorCode, int nHeight, int * pnOffset ) { if( wcCursorCode != CURSOR_RTL ) { *pnOffset = 0; return TRUE; } BOOL fGotIt = FALSE; HDC hDC = GetDC( hEdit ); int nWidth; SystemParametersInfo( SPI_GETCARETWIDTH, 0, (LPVOID) & nWidth, 0 ); HFONT hFont = CreateFont( nHeight, 0, 0, 0, nWidth > 1 ? 700 : 400, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, TEXT("Microsoft Sans Serif") ); if( hFont ) { HFONT hfOld = SelectFont( hDC, hFont ); ABC abcWidth; if( GetCharABCWidths( hDC, wcCursorCode, wcCursorCode, & abcWidth ) ) { *pnOffset = 1 - (int) abcWidth.abcB; fGotIt = TRUE; } SelectFont( hDC, hfOld ); DeleteFont( hFont ); } ReleaseDC( hEdit, hDC ); return fGotIt; } BOOL GetEditCaretOffset( HWND hEdit, int nHeight, int * pOffset ) { WCHAR wcCursorCode = GetEditCursorCode( hEdit ); if( wcCursorCode != CURSOR_USA ) { return GetEditCaretOffsetFromFont( hEdit, wcCursorCode, nHeight, pOffset ); } else { *pOffset = 0; return TRUE; } }