//************************************************* // r e u t i l . c p p // // Purpose: // implements calls to Richedit controls // // Owner: // dhaws // // History: // March 1999: Created //************************************************* #include "pch.hxx" #include "reutil.h" #include "richole.h" #include "richedit.h" #include "textserv.h" #include "shlwapip.h" #include "mirror.h" #include "strconst.h" #include // must be last! static HINSTANCE g_hRichEditDll = NULL; static DWORD g_dwRicheditVer = 0; // @hack [dhaws] {55073} Do RTL mirroring only in special richedit versions. static BOOL g_fSpecialRTLRichedit = FALSE; // Keep these around for future reference static const CHAR g_szRE10[] = "RichEdit"; static const CHAR g_szRE20[] = "RichEdit20A"; static const WCHAR g_wszRE10[] = L"RichEdit"; static const WCHAR g_wszRE20[] = L"RichEdit20W"; BOOL FInitRichEdit(BOOL fInit) { if(fInit) { if(!g_hRichEditDll) { g_hRichEditDll = LoadLibrary("RICHED20.DLL"); if (g_hRichEditDll) { TCHAR szPath[MAX_PATH], szGet[MAX_PATH]; BOOL fSucceeded = FALSE; DWORD dwVerInfoSize = 0, dwVerHnd = 0, dwProdNum = 0, dwMajNum = 0, dwMinNum = 0; LPSTR lpInfo = NULL, lpVersion = NULL; LPWORD lpwTrans; UINT uLen; HKEY hkey; DWORD dw=0, cb; if (GetModuleFileName(g_hRichEditDll, szPath, ARRAYSIZE(szPath))) { dwVerInfoSize = GetFileVersionInfoSize(szPath, &dwVerHnd); if (dwVerInfoSize) { lpInfo = (LPSTR)GlobalAlloc(GPTR, dwVerInfoSize); if (lpInfo) { if (GetFileVersionInfo(szPath, dwVerHnd, dwVerInfoSize, lpInfo)) { if (VerQueryValue(lpInfo, "\\VarFileInfo\\Translation", (LPVOID *)&lpwTrans, &uLen) && uLen >= (2 * sizeof(WORD))) { // set up buffer for calls to VerQueryValue() wnsprintf(szGet, ARRAYSIZE(szGet), "\\StringFileInfo\\%04X%04X\\FileVersion", lpwTrans[0], lpwTrans[1]); if (VerQueryValue(lpInfo, szGet, (LPVOID *)&lpVersion, &uLen) && uLen) { while (('.' != *lpVersion) && (',' != *lpVersion) && (0 != *lpVersion)) { dwProdNum *= 10; dwProdNum += (*lpVersion++ - '0'); } if (5 == dwProdNum) { if (('.' == *lpVersion) || (',' == *lpVersion)) lpVersion++; while (('.' != *lpVersion) && (',' != *lpVersion) && (0 != *lpVersion)) { dwMajNum *= 10; dwMajNum += (*lpVersion++ - '0'); } g_dwRicheditVer = (dwMajNum >= 30) ? 3 : 2; // @hack [dhaws] {55073} Do RTL mirroring only in special richedit versions. if ((2 == g_dwRicheditVer) && (0 != *lpVersion)) { // Check to see if we have turned on the magic key to disable this stuff if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegRoot, 0, KEY_QUERY_VALUE, &hkey)) { cb = sizeof(dw); RegQueryValueEx(hkey, c_szRegRTLRichEditHACK, 0, NULL, (LPBYTE)&dw, &cb); RegCloseKey(hkey); } // If we didn't find the key, or it is 0, then check to see if we are // dealing with those special richedits if (0 == dw) { if (('.' == *lpVersion) || (',' == *lpVersion)) lpVersion++; while (('.' != *lpVersion) && (',' != *lpVersion) && (0 != *lpVersion)) { dwMinNum *= 10; dwMinNum += (*lpVersion++ - '0'); } g_fSpecialRTLRichedit = (dwMinNum >= 330); } } } else { // Treat this as richedit version 3.0 Assert(5 < dwProdNum); g_dwRicheditVer = 3; } fSucceeded = TRUE; } } } GlobalFree((HGLOBAL)lpInfo); } } } if (!fSucceeded) { FreeLibrary(g_hRichEditDll); g_hRichEditDll=NULL; } } } if(!g_hRichEditDll) { g_hRichEditDll=LoadLibrary("RICHED32.DLL"); g_dwRicheditVer = 1; } if(!g_hRichEditDll) AthMessageBoxW(g_hwndInit, MAKEINTRESOURCEW(idsAthena), MAKEINTRESOURCEW(idsErrLoadingHtmlEdit), NULL, MB_OK); return (BOOL)(0 != g_hRichEditDll); } else { if(g_hRichEditDll) FreeLibrary(g_hRichEditDll); g_hRichEditDll=NULL; g_dwRicheditVer = 0; return TRUE; } } LPCSTR GetREClassStringA(void) { switch (g_dwRicheditVer) { case 1: return g_szRE10; case 2: case 3: return g_szRE20; default: AssertSz(FALSE, "Bad Richedit Version"); return NULL; } } LPCWSTR GetREClassStringW(void) { switch (g_dwRicheditVer) { case 1: return g_wszRE10; case 2: case 3: return g_wszRE20; default: AssertSz(FALSE, "Bad Richedit Version"); return NULL; } } typedef struct tagHWNDORDERSTRUCT { HWND hwndTemplate; HWND hwndRichEdit; HWND *phwnd; } HWNDORDERSTRUCT; BOOL CALLBACK CountChildrenProc(HWND hwnd, LPARAM lParam) { DWORD *pcCount = (DWORD*)lParam; (*pcCount)++; return TRUE; } // This function basically will take the order of the hwnds on the dialog // and store that order in the prder->phwnd variable so that we can maintain // the taborder on the dialog windows. The only special case we have to do // is with the template. In the case of the template, we need to substitute // in the richedit into the order. BOOL CALLBACK BuildOrderProc(HWND hwnd, LPARAM lParam) { HWNDORDERSTRUCT *pOrder = (HWNDORDERSTRUCT*)lParam; if (hwnd == pOrder->hwndTemplate) *pOrder->phwnd = pOrder->hwndRichEdit; else *pOrder->phwnd = hwnd; pOrder->phwnd++; return TRUE; } HWND CreateREInDialogA(HWND hwndParent, int iID) { HWND hwndTemplate = GetDlgItem(hwndParent, idredtTemplate); RECT rcTemplate; int width, height; HWND hwndNewRE = 0; DWORD cCount = 0; HWNDORDERSTRUCT hos; CHAR szTitle[CCHMAX_STRINGRES]; Assert(IsWindow(hwndParent)); Assert(iID); AssertSz(IsWindow(hwndTemplate), "Must have a template control for this to work."); *szTitle = 0; ShowWindow(hwndTemplate, SW_HIDE); GetWindowText(hwndTemplate, szTitle, ARRAYSIZE(szTitle)); // Get location of place holder so we can create the richedit over it GetWindowRect(hwndTemplate, &rcTemplate); // Map Screen coordinates to dialog coordinates MapWindowPoints(NULL, hwndParent, (LPPOINT)&rcTemplate, 2); width = rcTemplate.right - rcTemplate.left; height = rcTemplate.bottom - rcTemplate.top; hwndNewRE = CreateWindow(GetREClassStringA(), szTitle, ES_MULTILINE|ES_READONLY|ES_SAVESEL|ES_AUTOVSCROLL| WS_BORDER|WS_VSCROLL|WS_TABSTOP|WS_CHILD, rcTemplate.left, rcTemplate.top, width, height, hwndParent, (HMENU)IntToPtr(iID), g_hInst, NULL); // Count number of child windows in the dialog if (EnumChildWindows(hwndParent, CountChildrenProc, (LPARAM)&cCount)) { HWND *phwnd = (HWND*)ZeroAllocate(cCount*sizeof(HWND)); if (phwnd) { hos.hwndRichEdit = hwndNewRE; hos.hwndTemplate = hwndTemplate; hos.phwnd = phwnd; // Create ordered list of dialog children hwnds. if (EnumChildWindows(hwndParent, BuildOrderProc, (LPARAM)&hos)) { cCount--; // So this next section is basically going to reparent all the // controls in the order setup in the phwnd array. By setting // the parent of the window, we basically set the taborder of that // item to be the first in line. That is why we reparent the controls // in reverse order. HWND *pCurr = &phwnd[cCount]; while(cCount) { SetParent(*pCurr, SetParent(*pCurr, NULL)); pCurr--; cCount--; } } MemFree(phwnd); } } return hwndNewRE; } LONG RichEditNormalizeCharPos(HWND hwnd, LONG lByte, LPCSTR pszText) { LPWSTR pwszText = NULL; LPSTR pszConv = NULL; LONG cch; HRESULT hr = S_OK; //on anything other than richedit 1, we're already normalized if(1!=g_dwRicheditVer) return lByte; if(NULL == pszText) { cch = GetWindowTextLengthWrapW(hwnd); if (0 == cch) return 0; cch += 1; // for a null if (lByte >= cch) return cch; IF_NULLEXIT(MemAlloc((LPVOID*) &pwszText, cch * sizeof(WCHAR))); *pwszText = '\0'; GetRichEditText(hwnd, pwszText, cch, FALSE, NULL); IF_NULLEXIT((pszConv = PszToANSI(CP_ACP, pwszText))); pszText = pszConv; } cch = 0; while(lByte > 0) // lByte is zero-based { cch++; if (IsDBCSLeadByte(*pszText)){ pszText++; lByte--; } pszText++; lByte--; } exit: MemFree(pwszText); MemFree(pszConv); return FAILED(hr)?0:cch; } LONG GetRichEditTextLen(HWND hwnd) { switch (g_dwRicheditVer) { // we always want to return the number of chars; riched 1 will always return // the number of bytes; we need to convert... case 1: { LPWSTR pwszText = NULL; DWORD cch; cch = GetWindowTextLengthWrapW(hwnd); if (0 == cch) return 0; cch += 1; // for a null if (!MemAlloc((LPVOID*) &pwszText, cch * sizeof(WCHAR))) return 0; *pwszText = '\0'; GetRichEditText(hwnd, pwszText, cch, FALSE, NULL); cch = lstrlenW(pwszText); MemFree(pwszText); return cch; } case 2: case 3: { GETTEXTLENGTHEX rTxtStruct; if (!hwnd) return 0; rTxtStruct.flags = GTL_DEFAULT; rTxtStruct.codepage = CP_UNICODE; return (LONG) SendMessage(hwnd, EM_GETTEXTLENGTHEX, (WPARAM)&rTxtStruct, 0); } default: AssertSz(FALSE, "Bad Richedit Version"); return 0; } } DWORD GetRichEditText(HWND hwnd, LPWSTR pwchBuff, DWORD cchNumChars, BOOL fSelection, ITextDocument *pDoc) { switch (g_dwRicheditVer) { case 1: { if (fSelection) return (DWORD)SendMessageWrapW(hwnd, EM_GETSELTEXT, 0, (LPARAM)pwchBuff); else return GetWindowTextWrapW(hwnd, pwchBuff, cchNumChars); } case 2: case 3: { DWORD cchLen = 0; if (!hwnd || !cchNumChars) return 0; Assert(pwchBuff); *pwchBuff = 0; if (!fSelection) { GETTEXTEX rTxtStruct; rTxtStruct.cb = cchNumChars * sizeof(WCHAR); rTxtStruct.flags = GT_DEFAULT; rTxtStruct.codepage = CP_UNICODE; cchLen = (DWORD) SendMessage(hwnd, EM_GETTEXTEX, (WPARAM)&rTxtStruct, (LPARAM)pwchBuff); } else { // There is no way to get a selection of UNICODE // without going through TOM ITextSelection *pSelRange = NULL; ITextDocument *pDocToFree = NULL; BSTR bstr = NULL; if (!pDoc) { LPRICHEDITOLE preole = NULL; LRESULT result = SendMessage(hwnd, EM_GETOLEINTERFACE, NULL, (LPARAM)&preole); Assert(result); Assert(preole); HRESULT hr = preole->QueryInterface(IID_ITextDocument, (LPVOID*)&pDocToFree); ReleaseObj(preole); TraceResult(hr); if (FAILED(hr)) return 0; pDoc = pDocToFree; } if (SUCCEEDED(pDoc->GetSelection(&pSelRange))) { if (SUCCEEDED(pSelRange->GetText(&bstr)) && bstr) { cchLen = SysStringLen(bstr); if (cchLen > 0) { if (cchLen < cchNumChars) StrCpyNW(pwchBuff, bstr, cchNumChars); else cchLen = 0; } SysFreeString(bstr); } ReleaseObj(pSelRange); } ReleaseObj(pDocToFree); } return cchLen; } default: AssertSz(FALSE, "Bad Richedit Version"); return 0; } } void SetRichEditText(HWND hwnd, LPWSTR pwchBuff, BOOL fReplaceSel, ITextDocument *pDoc, BOOL fReadOnly) { if (!hwnd) return; switch (g_dwRicheditVer) { case 1: { if (fReplaceSel) SendMessageWrapW(hwnd, EM_REPLACESEL, 0, (LPARAM)pwchBuff); else SetWindowTextWrapW(hwnd, pwchBuff); break; } case 2: { ITextServices *pService = NULL; ITextDocument *pDocToFree = NULL; if (!pDoc) { LPRICHEDITOLE preole = NULL; LRESULT result = SendMessage(hwnd, EM_GETOLEINTERFACE, NULL, (LPARAM)&preole); Assert(result); Assert(preole); HRESULT hr = preole->QueryInterface(IID_ITextDocument, (LPVOID*)&pDocToFree); ReleaseObj(preole); TraceResult(hr); if (FAILED(hr)) return; pDoc = pDocToFree; } if (FAILED(pDoc->QueryInterface(IID_ITextServices, (LPVOID*)&pService))) return; if (!fReplaceSel) { // TxSetText is documented as a LPCTSTR, but RichEdit always // compiles with UNICODE turned on, so we are OK here. pService->TxSetText(pwchBuff); } else { HRESULT hr; ITextSelection *pSelRange = NULL; BSTR bstr = SysAllocString(pwchBuff); hr = pDoc->GetSelection(&pSelRange); if (SUCCEEDED(hr) && pSelRange) { if (fReadOnly) pService->OnTxPropertyBitsChange(TXTBIT_READONLY, 0); // If we are readonly, SetText will fail if we don't do the TXTBIT setting hr = pSelRange->SetText(bstr); if (FAILED(hr)) TraceResult(hr); // Richedit 2.0 doesn't always collapse to the end of the selection. pSelRange->Collapse(tomEnd); if (fReadOnly) pService->OnTxPropertyBitsChange(TXTBIT_READONLY, TXTBIT_READONLY); pSelRange->Release(); } else TraceResult(hr); SysFreeString(bstr); } ReleaseObj(pDocToFree); pService->Release(); break; } case 3: { // pwchBuff can be NULL. NULL means that we are clearing the field. SETTEXTEX rTxtStruct; rTxtStruct.flags = fReplaceSel ? ST_SELECTION : ST_DEFAULT; rTxtStruct.codepage = CP_UNICODE; // EM_SETTEXTEX will fail with RichEdit 2.0. SendMessage(hwnd, EM_SETTEXTEX, (WPARAM)&rTxtStruct, (LPARAM)pwchBuff); break; } default: AssertSz(FALSE, "Bad Richedit Version"); break; } } void SetFontOnRichEdit(HWND hwnd, HFONT hfont) { switch (g_dwRicheditVer) { case 1: case 3: { CHARFORMAT cf; if (SUCCEEDED(FontToCharformat(hfont, &cf))) { SideAssert(FALSE != SendMessage(hwnd, EM_SETCHARFORMAT, (WPARAM) 0, (LPARAM) &cf)); } break; } case 2: SideAssert(FALSE != SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0))); break; default: AssertSz(FALSE, "Bad Richedit Version"); break; } } /////////////////////////////////////////////////////////////////////////////// // RichEditExSetSel // // Raid 79304. Rich Edit Control version 1.0 does not handle // the EM_EXSETSEL message correctly for text with DBCS characters. It sets // the selection range based on bytes on not double byte characters. /////////////////////////////////////////////////////////////////////////////// LRESULT RichEditExSetSel(HWND hwnd, CHARRANGE *pchrg) { // We only need to deal with this on richedit 1 or we aren't selecting entire text Assert(pchrg); if ((1 == g_dwRicheditVer) && (-1 != pchrg->cpMax)) { LRESULT lResult = 0; LPWSTR pwszText = NULL; LPSTR pszConv = NULL, pszText = NULL; CHARRANGE chrg={0}; CHARRANGE chrgIn; DWORD cch, cDBCSBefore = 0, cDBCSIn = 0; chrgIn = *pchrg; // Get the text string for this control cch = GetRichEditTextLen(hwnd) + 1; if (0 == cch) return lResult; if (!MemAlloc((LPVOID*) &pwszText, cch * sizeof(WCHAR))) return lResult; *pwszText = '\0'; // Richedit 1.0 will never have a pDoc, so don't pass one in GetRichEditText(hwnd, pwszText, cch, FALSE, NULL); pszConv = PszToANSI(CP_ACP, pwszText); pszText = pszConv; SafeMemFree(pwszText); if (!pszText) { Assert(0); return lResult; } // Compute the BYTE range based on DBCS characters if (chrgIn.cpMax >= chrgIn.cpMin) { chrgIn.cpMax -= chrgIn.cpMin; // Look for DBCS chars before selection while ((chrgIn.cpMin > 0) && *pszText) { if ( IsDBCSLeadByte(*pszText) ) { cDBCSBefore++; pszText += 2; } else { pszText++; } chrgIn.cpMin--; } // Look for DBCS chars in selection while ((chrgIn.cpMax > 0) && *pszText) { if ( IsDBCSLeadByte(*pszText) ) { cDBCSIn++; pszText += 2; } else { pszText++; } chrgIn.cpMax--; } chrg.cpMin = pchrg->cpMin + cDBCSBefore; chrg.cpMax = pchrg->cpMax + cDBCSBefore + cDBCSIn; } MemFree(pszConv); lResult = SendMessageA(hwnd, EM_EXSETSEL, 0, (LPARAM)&chrg); return lResult; } else return SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)pchrg); } LRESULT RichEditExGetSel(HWND hwnd, CHARRANGE *pchrg) { LRESULT lResult; LPWSTR pwszText = NULL; LPSTR pszText = NULL; LONG cch; HRESULT hr; Assert(pchrg); // We only need to deal with this on richedit 1 if (1 == g_dwRicheditVer) { lResult = SendMessageA(hwnd, EM_EXGETSEL, 0, (LPARAM)pchrg); cch = GetWindowTextLengthWrapW(hwnd); if (0 == cch) goto exit; IF_NULLEXIT(MemAlloc((LPVOID*) &pwszText, cch * sizeof(WCHAR))); *pwszText = '\0'; GetRichEditText(hwnd, pwszText, cch, FALSE, NULL); IF_NULLEXIT((pszText = PszToANSI(CP_ACP, pwszText))); pchrg->cpMin = RichEditNormalizeCharPos(hwnd, pchrg->cpMin, pszText); pchrg->cpMax = RichEditNormalizeCharPos(hwnd, pchrg->cpMax, pszText); } else lResult = SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)pchrg); exit: MemFree(pszText); MemFree(pwszText); return lResult; } // *************************** // RichEditProtectENChange // // hwnd = richedit to be protected // pdwOldMask = when fProtect, will return the original mask for the richedit // when !fProtect, will be passing in the new mask for the richedit // The new value should be the one that was passed back when first called // fProtect = Are we starting the protection or ending it // // Notes: // // This function is to protect the possiblity of a re-entrant notificition // while processing an EN_CHANGE message within richedit. It turns out that // all richedits except for ver3 that shipped with Office 2k will protect against // this themselves. // // This call should always be used in pairs with the first passing TRUE and the // second passing in false. At this point, is only used to Protect the EN_CHANGE // notification that is sent to the richedit. // // BUGBUG: It might be beneficial at some future date to make the granularity // of these richedit issues more fine to handle cases like this where most // v3 richedits handle this correctly. void RichEditProtectENChange(HWND hwnd, DWORD *pdwOldMask, BOOL fProtect) { Assert(IsWindow(hwnd)); Assert(pdwOldMask); if (3 == g_dwRicheditVer) { DWORD dwMask = *pdwOldMask; if (fProtect) { dwMask = (DWORD) SendMessage(hwnd, EM_GETEVENTMASK, 0, 0); SendMessage(hwnd, EM_SETEVENTMASK, 0, dwMask & (~ENM_CHANGE)); *pdwOldMask = dwMask; } else SendMessage(hwnd, EM_SETEVENTMASK, 0, dwMask); } } // @hack [dhaws] {55073} Do RTL mirroring only in special richedit versions. void RichEditRTLMirroring(HWND hwndHeader, BOOL fSubject, LONG *plExtendFlags, BOOL fPreRECreation) { if (!g_fSpecialRTLRichedit) { if (fPreRECreation) { // a-msadek; RichEdit have a lot of problems with mirroring // let's disable mirroring for this child and rather 'simulate' it if (IS_WINDOW_RTL_MIRRORED(hwndHeader)) { DISABLE_LAYOUT_INHERITANCE(hwndHeader); *plExtendFlags |= WS_EX_LEFTSCROLLBAR |WS_EX_RIGHT; if (fSubject) { *plExtendFlags |= WS_EX_RTLREADING; } } } else { if (IS_WINDOW_RTL_MIRRORED(hwndHeader)) { ENABLE_LAYOUT_INHERITANCE(hwndHeader); } } } }