Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

814 lines
26 KiB

//*************************************************
// 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 <demand.h> // 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);
}
}
}
}