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.
4266 lines
120 KiB
4266 lines
120 KiB
/*
|
|
* spell.c
|
|
*
|
|
* Implementation of spelling
|
|
*
|
|
* Owner: v-brakol
|
|
* [email protected]
|
|
*/
|
|
#include "pch.hxx"
|
|
#include "richedit.h"
|
|
#include "resource.h"
|
|
#include "util.h"
|
|
#include <mshtml.h>
|
|
#include <mshtmcid.h>
|
|
#include "mshtmhst.h"
|
|
#include "mshtmdid.h"
|
|
#include <docobj.h>
|
|
#include "spell.h"
|
|
#include "strconst.h"
|
|
#include "bodyutil.h"
|
|
#include <error.h>
|
|
#include "htmlstr.h"
|
|
#include "dllmain.h"
|
|
#include "msi.h"
|
|
#include "lid.h"
|
|
#include "demand.h"
|
|
|
|
typedef struct
|
|
{
|
|
LPTSTR pszString;
|
|
DWORD cchSize;
|
|
} STRING_AND_SIZE;
|
|
|
|
#define GetAddr(var, cast, name) {if ((var = (cast)GetProcAddress(m_hinstDll, name)) == NULL) \
|
|
goto error;}
|
|
|
|
#define TESTHR(hr) (FAILED(hr) || hr == HR_S_ABORT || hr == HR_S_SPELLCANCEL)
|
|
#define SPELLER_GUID "{CC29EB3F-7BC2-11D1-A921-00A0C91E2AA2}"
|
|
#define DICTIONARY_GUID "{CC29EB3D-7BC2-11D1-A921-00A0C91E2AA2}"
|
|
#define CSAPI3T1_GUID "{CC29EB41-7BC2-11D1-A921-00A0C91E2AA2}"
|
|
#ifdef DEBUG
|
|
#define SPELLER_DEBUG_GUID "{CC29EB3F-7BC2-11D1-A921-10A0C91E2AA2}"
|
|
#define DICTIONARY_DEBUG_GUID "{CC29EB3D-7BC2-11D1-A921-10A0C91E2AA2}"
|
|
#define CSAPI3T1_DEBUG_GUID "{CC29EB41-7BC2-11D1-A921-10A0C91E2AA2}"
|
|
#endif // DEBUG
|
|
|
|
#define MAX_SPELLWORDS 10
|
|
|
|
typedef BOOL (LPFNENUMLANG)(DWORD_PTR, LPTSTR);
|
|
typedef BOOL (LPFNENUMUSERDICT)(DWORD_PTR, LPTSTR);
|
|
|
|
typedef struct _FILLLANG
|
|
{
|
|
HWND hwndCombo;
|
|
BOOL fUnknownFound;
|
|
BOOL fDefaultFound;
|
|
BOOL fCurrentFound;
|
|
UINT lidDefault;
|
|
UINT lidCurrent;
|
|
} FILLLANG, * LPFILLLANG;
|
|
|
|
BOOL TestLangID(LPCTSTR szLangId);
|
|
BOOL GetLangID(IOleCommandTarget* pParentCmdTarget, LPTSTR szLangID, DWORD cchLangId);
|
|
WORD WGetLangID(IOleCommandTarget* pParentCmdTarget);
|
|
DWORD GetSpellingPaths(LPCTSTR szKey, LPTSTR szReturnBuffer, LPTSTR szMdr, UINT cchReturnBufer);
|
|
VOID EnumLanguages(DWORD_PTR, LPFNENUMLANG);
|
|
BOOL FindLangCallback(DWORD_PTR dwLangId, LPTSTR lpszLang);
|
|
void RemoveTrailingSpace(LPTSTR lpszWord);
|
|
void DumpRange(IHTMLTxtRange *pRange);
|
|
BOOL FBadSpellChecker(LPSTR rgchBufDigit);
|
|
void EnableRepeatedWindows(CSpell* pSpell, HWND hwndDlg);
|
|
BOOL GetNewSpellerEngine(LANGID lgid, TCHAR *rgchEngine, DWORD cchEngine, TCHAR *rgchLex, DWORD cchLex, BOOL bTestAvail);
|
|
VOID EnumUserDictionaries(DWORD_PTR dwCookie, LPFNENUMUSERDICT pfn);
|
|
BOOL GetDefaultUserDictionary(TCHAR *rgchUserDict, int cchBuff);
|
|
WORD GetWCharType(WCHAR wc);
|
|
HRESULT OpenDirectory(TCHAR *szDir);
|
|
|
|
BOOL TestLangID(LPCTSTR pszLangId)
|
|
{
|
|
// check for new speller
|
|
{
|
|
TCHAR rgchEngine[MAX_PATH];
|
|
int cchEngine = sizeof(rgchEngine) / sizeof(rgchEngine[0]);
|
|
TCHAR rgchLex[MAX_PATH];
|
|
int cchLex = sizeof(rgchLex) / sizeof(rgchLex[0]);
|
|
|
|
if (GetNewSpellerEngine((USHORT) StrToInt(pszLangId), rgchEngine, cchEngine, rgchLex, cchLex, TRUE))
|
|
return TRUE;
|
|
}
|
|
|
|
// use the old code to check for an old speller
|
|
{
|
|
TCHAR rgchBufKeyTest[cchMaxPathName];
|
|
TCHAR rgchBufTest[cchMaxPathName];
|
|
TCHAR szMdr[cchMaxPathName];
|
|
|
|
wnsprintf(rgchBufKeyTest, ARRAYSIZE(rgchBufKeyTest), c_szRegSpellKeyDef, pszLangId);
|
|
if (GetSpellingPaths(rgchBufKeyTest, rgchBufTest, szMdr, sizeof(rgchBufTest)/sizeof(TCHAR)))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* GetSpellLangID
|
|
*
|
|
* Returns the LangID that should be used as the base for all registry
|
|
* operations
|
|
*
|
|
*/
|
|
BOOL GetLangID(IOleCommandTarget* pParentCmdTarget, LPTSTR pszLangId, DWORD cchLangId)
|
|
{
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
TCHAR rgchBufKey[cchMaxPathName];
|
|
BOOL fRet;
|
|
VARIANT va;
|
|
|
|
pszLangId[0] = 0;
|
|
|
|
Assert(pParentCmdTarget);
|
|
if (pParentCmdTarget && pParentCmdTarget->Exec(&CMDSETID_MimeEditHost, MEHOSTCMDID_SPELL_LANGUAGE, 0, NULL, &va)== S_OK)
|
|
{
|
|
TCHAR rgchLangId[cchMaxPathName];
|
|
|
|
if (WideCharToMultiByte (CP_ACP, 0, va.bstrVal, -1,
|
|
rgchLangId, sizeof(rgchLangId), NULL, NULL))
|
|
{
|
|
StrCpyN(pszLangId, rgchLangId, cchLangId);
|
|
}
|
|
|
|
SysFreeString(va.bstrVal);
|
|
}
|
|
|
|
if (*pszLangId == 0)
|
|
{
|
|
wnsprintf(pszLangId, cchLangId, "%d", GetUserDefaultLangID());
|
|
Assert(lstrlen(pszLangId) == 4);
|
|
}
|
|
|
|
wnsprintf(rgchBufKey, ARRAYSIZE(rgchBufKey), c_szRegSpellKeyDef, pszLangId);
|
|
// copy c_szRegSpellProfile to buffer
|
|
StrCpyN(rgchBuf, c_szRegSpellProfile, ARRAYSIZE(rgchBuf));
|
|
// add key to buffer
|
|
StrCatBuff(rgchBuf, rgchBufKey, ARRAYSIZE(rgchBuf));
|
|
|
|
// and see if it's legit:
|
|
if(!(fRet = TestLangID(pszLangId)))
|
|
{
|
|
// couldn't open it!
|
|
// check for other languages that might be installed...
|
|
STRING_AND_SIZE stringAndSize;
|
|
|
|
stringAndSize.pszString = pszLangId;
|
|
stringAndSize.cchSize = cchLangId;
|
|
|
|
pszLangId[0] = 0;
|
|
EnumLanguages((DWORD_PTR) &stringAndSize, FindLangCallback);
|
|
if(*pszLangId == 0)
|
|
wnsprintf(pszLangId, cchLangId, TEXT("%d"), GetUserDefaultLangID());
|
|
}
|
|
|
|
fRet = (pszLangId[0] != 0) && TestLangID(pszLangId);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
WORD WGetLangID(IOleCommandTarget* pParentCmdTarget)
|
|
{
|
|
TCHAR rgchBufDigit[10];
|
|
|
|
if (!GetLangID(pParentCmdTarget, rgchBufDigit, sizeof(rgchBufDigit)/sizeof(TCHAR)))
|
|
return GetUserDefaultLangID();
|
|
|
|
return (WORD) StrToInt(rgchBufDigit);
|
|
}
|
|
|
|
BOOL FindLangCallback(DWORD_PTR dwLangId, LPTSTR lpszLang)
|
|
{
|
|
// dwLangID is long pointer to szLang ID. Copy it and return FALSE
|
|
STRING_AND_SIZE * pStringAndSize = (STRING_AND_SIZE *) dwLangId;
|
|
|
|
if (pStringAndSize && pStringAndSize->pszString)
|
|
{
|
|
StrCpyN(pStringAndSize->pszString, lpszLang, pStringAndSize->cchSize);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL EnumOldSpellerLanguages(DWORD_PTR dwCookie, LPFNENUMLANG pfn)
|
|
{
|
|
DWORD iKey = 0;
|
|
FILETIME ft;
|
|
HKEY hkey = NULL;
|
|
LONG lRet;
|
|
TCHAR szLangId[cchMaxPathName];
|
|
DWORD cchLangId;
|
|
BOOL fContinue;
|
|
|
|
// [email protected] - changed KEY_QUERY_VALUE to KEY_ENUMERATE_SUB_KEYS - 26203
|
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegSpellKeyDefRoot, 0, KEY_ENUMERATE_SUB_KEYS, &hkey) == ERROR_SUCCESS)
|
|
{
|
|
do
|
|
{
|
|
cchLangId = (cchMaxPathName - 1) * sizeof(TCHAR);
|
|
|
|
lRet = RegEnumKeyEx(hkey,
|
|
iKey++,
|
|
szLangId,
|
|
&cchLangId,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&ft);
|
|
|
|
if (lRet != ERROR_SUCCESS || lRet == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
|
|
// do some quick sanity checking
|
|
if (cchLangId != 4 ||
|
|
!IsCharAlphaNumeric(szLangId[0]) ||
|
|
IsCharAlpha(szLangId[0]))
|
|
{
|
|
fContinue = TRUE;
|
|
}
|
|
else
|
|
fContinue = (!TestLangID(szLangId) || (*pfn)(dwCookie, szLangId));
|
|
|
|
} while (fContinue);
|
|
}
|
|
|
|
if (hkey)
|
|
RegCloseKey(hkey);
|
|
|
|
return fContinue;
|
|
}
|
|
|
|
BOOL EnumNewSpellerLanguages(DWORD_PTR dwCookie, LPFNENUMLANG pfn)
|
|
{
|
|
BOOL fContinue = TRUE;
|
|
DWORD i;
|
|
UINT installState;
|
|
UINT componentState;
|
|
TCHAR rgchQualifier[MAX_PATH];
|
|
DWORD cchQualifier;
|
|
|
|
#ifdef DEBUG
|
|
for(i=0; fContinue; i++)
|
|
{
|
|
cchQualifier = sizeof(rgchQualifier) / sizeof(rgchQualifier[0]);
|
|
componentState = MsiEnumComponentQualifiers(DICTIONARY_DEBUG_GUID, i, rgchQualifier, &cchQualifier, NULL, NULL);
|
|
|
|
if (componentState != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// find the language ID
|
|
// the string is formatted as 1033\xxxxxx
|
|
// or 1042
|
|
{
|
|
TCHAR szLangId[cchMaxPathName];
|
|
TCHAR *pSlash;
|
|
|
|
StrCpyN(szLangId, rgchQualifier, ARRAYSIZE(szLangId));
|
|
pSlash = StrChr(szLangId, '\\');
|
|
if (pSlash)
|
|
*pSlash = 0;
|
|
|
|
fContinue = (*pfn)(dwCookie, szLangId);
|
|
}
|
|
}
|
|
#endif // DEBUG
|
|
|
|
for(i=0; fContinue; i++)
|
|
{
|
|
cchQualifier = sizeof(rgchQualifier) / sizeof(rgchQualifier[0]);
|
|
componentState = MsiEnumComponentQualifiers(DICTIONARY_GUID, i, rgchQualifier, &cchQualifier, NULL, NULL);
|
|
|
|
if (componentState != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// find the language ID
|
|
// the string is formatted as 1033\xxxxxx
|
|
// or 1042
|
|
{
|
|
TCHAR szLangId[cchMaxPathName];
|
|
TCHAR *pSlash;
|
|
|
|
StrCpyN(szLangId, rgchQualifier, ARRAYSIZE(szLangId));
|
|
pSlash = StrChr(szLangId, '\\');
|
|
if (pSlash)
|
|
*pSlash = 0;
|
|
|
|
fContinue = (*pfn)(dwCookie, szLangId);
|
|
}
|
|
}
|
|
|
|
return fContinue;
|
|
}
|
|
|
|
VOID EnumLanguages(DWORD_PTR dwCookie, LPFNENUMLANG pfn)
|
|
{
|
|
EnumNewSpellerLanguages(dwCookie, pfn);
|
|
EnumOldSpellerLanguages(dwCookie, pfn);
|
|
}
|
|
|
|
BOOL EnumOffice9UserDictionaries(DWORD_PTR dwCookie, LPFNENUMUSERDICT pfn)
|
|
{
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
HKEY hkey = NULL;
|
|
FILETIME ft;
|
|
DWORD iKey = 0;
|
|
LONG lRet;
|
|
TCHAR szValue[cchMaxPathName];
|
|
DWORD cchValue;
|
|
TCHAR szCustDict[cchMaxPathName];
|
|
DWORD cchCustDict;
|
|
BOOL fContinue = TRUE;
|
|
BOOL fFoundUserDict = FALSE;
|
|
TCHAR szOffice9Proof[cchMaxPathName]={0};
|
|
|
|
// SOFTWARE\\Microsoft\\Shared Tools\\Proofing Tools\\Custom Dictionaries
|
|
StrCpyN(rgchBuf, c_szRegSpellProfile, ARRAYSIZE(rgchBuf));
|
|
StrCatBuff(rgchBuf, c_szRegSpellKeyCustom, ARRAYSIZE(rgchBuf));
|
|
|
|
if(RegOpenKeyEx(HKEY_CURRENT_USER, rgchBuf, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS)
|
|
{
|
|
do
|
|
{
|
|
cchValue = sizeof(szValue) / sizeof(szValue[0]);
|
|
cchCustDict = sizeof(szCustDict) / sizeof(szCustDict[0]);
|
|
|
|
lRet = RegEnumValue(hkey,
|
|
iKey++,
|
|
szValue,
|
|
&cchValue,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)szCustDict,
|
|
&cchCustDict);
|
|
|
|
if (lRet != ERROR_SUCCESS || lRet == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
fFoundUserDict = TRUE;
|
|
|
|
// check to see if we have a path
|
|
if (!(StrChr(szCustDict, ':') || StrChr(szCustDict, '\\')))
|
|
{
|
|
TCHAR szTemp[cchMaxPathName];
|
|
|
|
if (!strlen(szOffice9Proof))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
|
|
if (S_OK == SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl))
|
|
SHGetPathFromIDList(pidl, szOffice9Proof);
|
|
else
|
|
{
|
|
// if the Shell call fails (as it can on Win9x sometimes) let's get the info
|
|
// from the registry
|
|
HKEY hKeyShellFolders;
|
|
ULONG cchAppData;
|
|
|
|
if(RegOpenKeyEx(HKEY_CURRENT_USER, c_szRegShellFoldersKey, 0, KEY_QUERY_VALUE, &hKeyShellFolders) == ERROR_SUCCESS)
|
|
{
|
|
cchAppData = ARRAYSIZE(szOffice9Proof);
|
|
RegQueryValueEx(hKeyShellFolders, c_szValueAppData, 0, NULL, (LPBYTE)szOffice9Proof, &cchAppData);
|
|
RegCloseKey(hKeyShellFolders);
|
|
}
|
|
}
|
|
|
|
// if this fails then we will try the current path
|
|
}
|
|
|
|
StrCpyN(szTemp, szOffice9Proof, ARRAYSIZE(szTemp));
|
|
StrCatBuff(szTemp, "\\", ARRAYSIZE(szTemp));
|
|
StrCatBuff(szTemp, c_szSpellOffice9ProofPath, ARRAYSIZE(szTemp));
|
|
StrCatBuff(szTemp, szCustDict, ARRAYSIZE(szTemp));
|
|
StrCpyN(szCustDict, szTemp, ARRAYSIZE(szCustDict));
|
|
}
|
|
|
|
fContinue = (*pfn)(dwCookie, szCustDict);
|
|
|
|
} while (fContinue);
|
|
}
|
|
|
|
if (hkey)
|
|
RegCloseKey(hkey);
|
|
|
|
return fFoundUserDict;
|
|
}
|
|
|
|
BOOL EnumOfficeUserDictionaries(DWORD_PTR dwCookie, LPFNENUMUSERDICT pfn)
|
|
{
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
HKEY hkey = NULL;
|
|
FILETIME ft;
|
|
DWORD iKey = 0;
|
|
LONG lRet;
|
|
TCHAR szValue[cchMaxPathName];
|
|
DWORD cchValue;
|
|
TCHAR szCustDict[cchMaxPathName];
|
|
DWORD cchCustDict;
|
|
BOOL fFoundUserDict = FALSE;
|
|
BOOL fContinue = TRUE;
|
|
|
|
// SOFTWARE\\Microsoft\\Shared Tools\\Proofing Tools\\Custom Dictionaries
|
|
StrCpyN(rgchBuf, c_szRegSpellProfile, ARRAYSIZE(rgchBuf));
|
|
StrCatBuff(rgchBuf, c_szRegSpellKeyCustom, ARRAYSIZE(rgchBuf));
|
|
|
|
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, rgchBuf, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS)
|
|
{
|
|
do
|
|
{
|
|
cchValue = sizeof(szValue) / sizeof(szValue[0]);
|
|
cchCustDict = sizeof(szCustDict) / sizeof(szCustDict[0]);
|
|
|
|
lRet = RegEnumValue(hkey,
|
|
iKey++,
|
|
szValue,
|
|
&cchValue,
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)szCustDict,
|
|
&cchCustDict);
|
|
|
|
if (lRet != ERROR_SUCCESS || lRet == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
|
|
fFoundUserDict = TRUE;
|
|
|
|
fContinue = (*pfn)(dwCookie, szCustDict);
|
|
|
|
} while (fContinue);
|
|
}
|
|
|
|
if (hkey)
|
|
RegCloseKey(hkey);
|
|
|
|
return fFoundUserDict;
|
|
}
|
|
|
|
VOID EnumUserDictionaries(DWORD_PTR dwCookie, LPFNENUMUSERDICT pfn)
|
|
{
|
|
// check for Office9 user dictionaries. If we find any
|
|
// we bail.
|
|
if (EnumOffice9UserDictionaries(dwCookie, pfn))
|
|
return;
|
|
|
|
EnumOfficeUserDictionaries(dwCookie, pfn);
|
|
}
|
|
|
|
/*
|
|
* GetSpellingPaths
|
|
*
|
|
* Purpose:
|
|
* Function to get Spelling DLL names.
|
|
*
|
|
* Arguments:
|
|
* szKey c_szRegSpellKeyDef (with correct language)
|
|
* szDefault c_szRegSpellEmpty
|
|
* szReturnBuffer dll filename
|
|
* szMdr dictionary filename
|
|
* cchReturnBufer
|
|
*
|
|
* Returns:
|
|
* DWORD
|
|
*/
|
|
DWORD GetSpellingPaths(LPCTSTR szKey, LPTSTR szReturnBuffer, LPTSTR szMdr, UINT cchReturnBufer)
|
|
{
|
|
DWORD dwRet = 0;
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
DWORD dwType, cbData;
|
|
HKEY hkey = NULL;
|
|
LPTSTR szValue;
|
|
|
|
szReturnBuffer[0] = 0;
|
|
StrCpyN(rgchBuf, c_szRegSpellProfile, ARRAYSIZE(rgchBuf));
|
|
StrCatBuff(rgchBuf, szKey, ARRAYSIZE(rgchBuf));
|
|
|
|
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, rgchBuf, 0, KEY_QUERY_VALUE, &hkey))
|
|
goto err;
|
|
|
|
cbData = cchReturnBufer * sizeof(TCHAR);
|
|
szValue = (LPTSTR) (szMdr ? c_szRegSpellPath : c_szRegSpellPathDict);
|
|
if (ERROR_SUCCESS != SHQueryValueEx(hkey, szValue, 0L, &dwType, (BYTE *) szReturnBuffer, &cbData))
|
|
goto err;
|
|
|
|
// Parse off the main dictionary filename
|
|
if(szMdr)
|
|
{
|
|
szMdr[0] = 0;
|
|
cbData = cchReturnBufer * sizeof(TCHAR);
|
|
if (ERROR_SUCCESS != SHQueryValueEx(hkey, c_szRegSpellPathLex, 0L, &dwType, (BYTE *) szMdr, &cbData))
|
|
goto err;
|
|
}
|
|
|
|
dwRet = cbData;
|
|
|
|
err:
|
|
if(hkey)
|
|
RegCloseKey(hkey);
|
|
return dwRet;
|
|
}
|
|
|
|
/*
|
|
* SpellingDlgProc
|
|
*
|
|
* Purpose:
|
|
* Dialog procedure for the Tools.Spelling dialog
|
|
*
|
|
* Arguments:
|
|
* HWND Dialog procedure arguments.
|
|
* UINT
|
|
* WPARAM
|
|
* LPARAM
|
|
*
|
|
* Returns:
|
|
* BOOL TRUE if the message has been processed.
|
|
*/
|
|
INT_PTR CALLBACK SpellingDlgProc(HWND hwndDlg, UINT wMsg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
CSpell* pSpell;
|
|
HWND hwndEdited;
|
|
HWND hwndSuggest;
|
|
|
|
switch (wMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
SetWindowLongPtr(hwndDlg, DWLP_USER, lparam);
|
|
pSpell = (CSpell*)lparam;
|
|
pSpell->m_hwndDlg = hwndDlg;
|
|
|
|
hwndEdited = GetDlgItem(hwndDlg, EDT_Spell_ChangeTo);
|
|
hwndSuggest = GetDlgItem(hwndDlg, LBX_Spell_Suggest);
|
|
|
|
pSpell->m_fEditWasEmpty = TRUE;
|
|
SetDlgItemText(hwndDlg, TXT_Spell_Error, pSpell->m_szErrType);
|
|
SetDlgItemText(hwndDlg, EDT_Spell_WrongWord, pSpell->m_szWrongWord);
|
|
SetWindowText(hwndEdited, pSpell->m_szEdited);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Options), FALSE);
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Add), (0 != pSpell->m_rgprflex[1]));
|
|
pSpell->m_fRepeat = (pSpell->m_wsrb.sstat == sstatRepeatWord);
|
|
EnableRepeatedWindows(pSpell, hwndDlg);
|
|
|
|
if (!pSpell->m_fRepeat)
|
|
pSpell->FillSuggestLbx();
|
|
else
|
|
ListBox_ResetContent(GetDlgItem(hwndDlg, LBX_Spell_Suggest));
|
|
|
|
if (pSpell->m_fSuggestions && !pSpell->m_fNoneSuggested && !pSpell->m_fRepeat)
|
|
{
|
|
EnableWindow(hwndSuggest, TRUE);
|
|
ListBox_SetCurSel(hwndSuggest, 0);
|
|
UpdateEditedFromSuggest(hwndDlg, hwndEdited, hwndSuggest);
|
|
EnableWindow(GetDlgItem(hwndDlg, TXT_Spell_Suggest), TRUE);
|
|
|
|
// Set initial default button to "Change"
|
|
SendMessage(hwndDlg, DM_SETDEFID, PSB_Spell_Change, 0L);
|
|
Button_SetStyle(GetDlgItem(hwndDlg, PSB_Spell_Change), BS_DEFPUSHBUTTON, TRUE);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Suggest), FALSE);
|
|
}
|
|
else
|
|
{
|
|
Edit_SetSel(hwndEdited, 0, 32767); // select the whole thing
|
|
EnableWindow(hwndSuggest, FALSE);
|
|
EnableWindow(GetDlgItem(hwndDlg, TXT_Spell_Suggest), FALSE);
|
|
|
|
// Set initial default button to "Ignore"
|
|
SendMessage(hwndDlg, DM_SETDEFID, PSB_Spell_Ignore, 0L);
|
|
Button_SetStyle(GetDlgItem(hwndDlg, PSB_Spell_Ignore), BS_DEFPUSHBUTTON, TRUE);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Suggest), !pSpell->m_fSuggestions && !pSpell->m_fRepeat);
|
|
}
|
|
|
|
AthFixDialogFonts(hwndDlg);
|
|
SetFocus(hwndEdited);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
return SpellingOnCommand(hwndDlg, wMsg, wparam, lparam);
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void EnableRepeatedWindows(CSpell* pSpell, HWND hwndDlg)
|
|
{
|
|
INT ids;
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Add), (!pSpell->m_fRepeat && (0 != pSpell->m_rgprflex[1])));
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_IgnoreAll), !pSpell->m_fRepeat);
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_ChangeAll), !pSpell->m_fRepeat);
|
|
if (pSpell->m_fRepeat)
|
|
{
|
|
SetWindowText(GetDlgItem(hwndDlg, EDT_Spell_ChangeTo), "");
|
|
*pSpell->m_szEdited = 0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
BOOL SpellingOnCommand(HWND hwndDlg, UINT wMsg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
CSpell* pSpell;
|
|
BOOL fChange;
|
|
BOOL fAlwaysSuggest;
|
|
BOOL fUndoing = FALSE;
|
|
HRESULT hr = 0;
|
|
|
|
pSpell = (CSpell*) GetWindowLongPtr(hwndDlg, DWLP_USER);
|
|
Assert(pSpell);
|
|
|
|
// Update our replacement word? Only do this when a button is clicked
|
|
// or a double-click from the suggest listbox, and the word has been modified.
|
|
if ((GET_WM_COMMAND_CMD(wparam, lparam) == BN_CLICKED ||
|
|
GET_WM_COMMAND_CMD(wparam, lparam) == LBN_DBLCLK) &&
|
|
Edit_GetModify(GetDlgItem(hwndDlg, EDT_Spell_ChangeTo)))
|
|
{
|
|
HWND hwndEditChangeTo;
|
|
|
|
hwndEditChangeTo = GetDlgItem(pSpell->m_hwndDlg, EDT_Spell_ChangeTo);
|
|
Edit_SetModify(hwndEditChangeTo, FALSE);
|
|
pSpell->m_fSuggestions = FALSE;
|
|
GetWindowText(hwndEditChangeTo, pSpell->m_szEdited, 512);
|
|
}
|
|
|
|
switch(GET_WM_COMMAND_ID(wparam, lparam))
|
|
{
|
|
case LBX_Spell_Suggest:
|
|
if (GET_WM_COMMAND_CMD(wparam, lparam) == LBN_SELCHANGE)
|
|
{
|
|
UpdateEditedFromSuggest(hwndDlg, GetDlgItem(hwndDlg, EDT_Spell_ChangeTo),
|
|
GetDlgItem(hwndDlg, LBX_Spell_Suggest));
|
|
return TRUE;
|
|
}
|
|
else if (GET_WM_COMMAND_CMD(wparam, lparam) == LBN_DBLCLK)
|
|
{
|
|
goto ChangeIt;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
case EDT_Spell_ChangeTo:
|
|
if (GET_WM_COMMAND_CMD(wparam, lparam) == EN_CHANGE)
|
|
{
|
|
INT idClearDefault;
|
|
INT idSetDefault;
|
|
BOOL fEditModified;
|
|
|
|
|
|
// We get EN_CHANGE notifications for both a SetWindowText() and user modifications.
|
|
// Look at the dirty flag (only set on user mods) and the state of the suggestions
|
|
// selection to see which button should get the default style.
|
|
fEditModified = Edit_GetModify(GET_WM_COMMAND_HWND(wparam, lparam));
|
|
if (fEditModified || pSpell->m_fSuggestions && !pSpell->m_fNoneSuggested)
|
|
{
|
|
idClearDefault = PSB_Spell_Ignore;
|
|
idSetDefault = PSB_Spell_Change;
|
|
}
|
|
else
|
|
{
|
|
idClearDefault = PSB_Spell_Change;
|
|
idSetDefault = PSB_Spell_Ignore;
|
|
}
|
|
|
|
// Enable/disable Suggest button
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Suggest), fEditModified);
|
|
|
|
// Set default button
|
|
Button_SetStyle(GetDlgItem(hwndDlg, idClearDefault), BS_PUSHBUTTON, TRUE);
|
|
SendMessage(hwndDlg, DM_SETDEFID, idSetDefault, 0L);
|
|
Button_SetStyle(GetDlgItem(hwndDlg, idSetDefault), BS_DEFPUSHBUTTON, TRUE);
|
|
|
|
// "Change" button title
|
|
if (GetWindowTextLength(GET_WM_COMMAND_HWND(wparam, lparam)) && pSpell->m_fEditWasEmpty)
|
|
{
|
|
pSpell->m_fEditWasEmpty = FALSE;
|
|
LoadString(g_hLocRes, idsSpellChange, pSpell->m_szTempBuffer,
|
|
sizeof(pSpell->m_szTempBuffer)/sizeof(TCHAR));
|
|
SetDlgItemText(hwndDlg, PSB_Spell_Change, pSpell->m_szTempBuffer);
|
|
LoadString(g_hLocRes, idsSpellChangeAll, pSpell->m_szTempBuffer,
|
|
sizeof(pSpell->m_szTempBuffer)/sizeof(TCHAR));
|
|
SetDlgItemText(hwndDlg, PSB_Spell_ChangeAll, pSpell->m_szTempBuffer);
|
|
}
|
|
else if (GetWindowTextLength(GET_WM_COMMAND_HWND(wparam, lparam)) == 0)
|
|
{
|
|
pSpell->m_fEditWasEmpty = TRUE;
|
|
LoadString(g_hLocRes, idsSpellDelete, pSpell->m_szTempBuffer,
|
|
sizeof(pSpell->m_szTempBuffer)/sizeof(TCHAR));
|
|
SetDlgItemText(hwndDlg, PSB_Spell_Change, pSpell->m_szTempBuffer);
|
|
LoadString(g_hLocRes, idsSpellDeleteAll, pSpell->m_szTempBuffer,
|
|
sizeof(pSpell->m_szTempBuffer)/sizeof(TCHAR));
|
|
SetDlgItemText(hwndDlg, PSB_Spell_ChangeAll, pSpell->m_szTempBuffer);
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
case PSB_Spell_IgnoreAll:
|
|
{
|
|
PROOFLEX lexIgnoreAll;
|
|
|
|
lexIgnoreAll = pSpell->m_pfnSpellerBuiltInUdr(pSpell->m_pid, lxtIgnoreAlways);
|
|
if (0 != lexIgnoreAll)
|
|
{
|
|
RemoveTrailingSpace(pSpell->m_szWrongWord);
|
|
pSpell->AddToUdrA(pSpell->m_szWrongWord, lexIgnoreAll);
|
|
pSpell->m_fCanUndo = FALSE;
|
|
}
|
|
}
|
|
// [email protected] - remove the annoying dialog and just break out of here - 34229
|
|
break;
|
|
|
|
case PSB_Spell_Ignore:
|
|
// Due to limitations with the spelling engine and our single undo level,
|
|
// we can't allow undo's of Ignore if the error was a Repeated Word.
|
|
if (pSpell->m_wsrb.sstat == sstatRepeatWord)
|
|
pSpell->m_fCanUndo = FALSE;
|
|
else
|
|
pSpell->SpellSaveUndo(PSB_Spell_Ignore);
|
|
|
|
// [email protected] - remove the annoying dialog and just break out of here - 34229
|
|
break;
|
|
|
|
case PSB_Spell_ChangeAll:
|
|
if (pSpell->FVerifyThisText(pSpell->m_szEdited, FALSE))
|
|
{
|
|
pSpell->m_fCanUndo = FALSE;
|
|
hr = pSpell->HrReplaceErrorText(TRUE, TRUE);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
case PSB_Spell_Change:
|
|
ChangeIt:
|
|
if (pSpell->FVerifyThisText(pSpell->m_szEdited, FALSE))
|
|
{
|
|
pSpell->m_fUndoChange = TRUE;
|
|
pSpell->SpellSaveUndo(PSB_Spell_Change);
|
|
hr = pSpell->HrReplaceErrorText(FALSE, TRUE);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
case PSB_Spell_Add:
|
|
Assert(pSpell->m_rgprflex[1]);
|
|
pSpell->m_fCanUndo = FALSE;
|
|
fChange = FALSE;
|
|
RemoveTrailingSpace(pSpell->m_szWrongWord);
|
|
// [email protected] - removed the FVerifyThisText that was here - no need
|
|
// to FVerifyThisText if the user is asking us to Add this word - fixes 55587
|
|
pSpell->AddToUdrA(pSpell->m_szWrongWord, pSpell->m_rgprflex[1]);
|
|
if (fChange)
|
|
hr = pSpell->HrReplaceErrorText(FALSE, TRUE);
|
|
break;
|
|
|
|
case PSB_Spell_UndoLast:
|
|
pSpell->SpellDoUndo();
|
|
fUndoing = TRUE;
|
|
break;
|
|
|
|
case PSB_Spell_Suggest:
|
|
hr = pSpell->HrSpellSuggest();
|
|
if (FAILED(hr))
|
|
goto bail;
|
|
goto loadcache;
|
|
|
|
case IDCANCEL:
|
|
pSpell->m_fShowDoneMsg = FALSE;
|
|
EndDialog(hwndDlg, IDCANCEL);
|
|
return TRUE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
// If no current error, then proceed with checking the rest
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Change "Cancel" button to "Close"
|
|
LoadString(g_hLocRes, idsSpellClose, pSpell->m_szTempBuffer,
|
|
sizeof(pSpell->m_szTempBuffer)/sizeof(TCHAR));
|
|
SetDlgItemText(hwndDlg, IDCANCEL, pSpell->m_szTempBuffer);
|
|
|
|
pSpell->m_wsrb.sstat = sstatNoErrors;
|
|
hr = pSpell->HrFindErrors();
|
|
if(FAILED(hr))
|
|
goto bail;
|
|
|
|
if(pSpell->m_wsrb.sstat==sstatNoErrors)
|
|
{
|
|
EndDialog(hwndDlg, GET_WM_COMMAND_ID(wparam, lparam));
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
bail:
|
|
if(FAILED(hr))
|
|
{
|
|
EndDialog(hwndDlg, IDCANCEL);
|
|
return TRUE;
|
|
}
|
|
|
|
SetDlgItemText(hwndDlg, EDT_Spell_WrongWord, pSpell->m_szWrongWord);
|
|
SetDlgItemText(hwndDlg, TXT_Spell_Error, pSpell->m_szErrType);
|
|
SetDlgItemText(hwndDlg, EDT_Spell_ChangeTo, pSpell->m_szEdited);
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_UndoLast), pSpell->m_fCanUndo);
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Add), (0 != pSpell->m_rgprflex[1]));
|
|
|
|
pSpell->m_fRepeat = (pSpell->m_wsrb.sstat == sstatRepeatWord);
|
|
EnableRepeatedWindows(pSpell, hwndDlg);
|
|
|
|
loadcache:
|
|
if (!pSpell->m_fRepeat)
|
|
pSpell->FillSuggestLbx();
|
|
else
|
|
ListBox_ResetContent(GetDlgItem(hwndDlg, LBX_Spell_Suggest));
|
|
|
|
EnableWindow(GetDlgItem(hwndDlg, PSB_Spell_Suggest), !pSpell->m_fSuggestions && !pSpell->m_fRepeat);
|
|
if (pSpell->m_fSuggestions && !pSpell->m_fNoneSuggested && !pSpell->m_fRepeat)
|
|
{
|
|
EnableWindow(GetDlgItem(hwndDlg, TXT_Spell_Suggest), TRUE);
|
|
EnableWindow(GetDlgItem(hwndDlg, LBX_Spell_Suggest), TRUE);
|
|
ListBox_SetCurSel(GetDlgItem(hwndDlg, LBX_Spell_Suggest), 0);
|
|
UpdateEditedFromSuggest(hwndDlg, GetDlgItem(hwndDlg, EDT_Spell_ChangeTo),
|
|
GetDlgItem(hwndDlg, LBX_Spell_Suggest));
|
|
}
|
|
else
|
|
{
|
|
EnableWindow(GetDlgItem(hwndDlg, TXT_Spell_Suggest), FALSE);
|
|
EnableWindow(GetDlgItem(hwndDlg, LBX_Spell_Suggest), FALSE);
|
|
}
|
|
|
|
SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, EDT_Spell_ChangeTo), MAKELONG(TRUE,0));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void RemoveTrailingSpace(LPTSTR lpszWord)
|
|
{
|
|
LPTSTR lpsz;
|
|
|
|
lpsz = StrChrI(lpszWord, ' ');
|
|
if(lpsz)
|
|
*lpsz = 0;
|
|
}
|
|
|
|
BOOL GetNewSpellerEngine(LANGID lgid, TCHAR *rgchEngine, DWORD cchEngine, TCHAR *rgchLex, DWORD cchLex, BOOL bTestAvail)
|
|
{
|
|
DWORD er;
|
|
LPCSTR rgpszDictionaryTypes[] = {"Normal", "Consise", "Complete"};
|
|
int cDictTypes = sizeof(rgpszDictionaryTypes) / sizeof(LPCSTR);
|
|
int i;
|
|
TCHAR rgchQual[MAX_PATH];
|
|
bool fFound = FALSE;
|
|
DWORD cch;
|
|
INSTALLUILEVEL iuilOriginal;
|
|
|
|
if (rgchEngine == NULL || rgchLex == NULL)
|
|
return FALSE;
|
|
|
|
*rgchEngine = 0;
|
|
*rgchLex = 0;
|
|
|
|
wnsprintf(rgchQual, ARRAYSIZE(rgchQual), "%d\\Normal", lgid);
|
|
cch = cchEngine;
|
|
|
|
if (bTestAvail)
|
|
{
|
|
// Explicitly Turn off internal installer UI
|
|
// Eg: A feature is set to "run from CD," and CD is not present - fail silently
|
|
// OE Bug 74697
|
|
iuilOriginal = MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
er = MsiProvideQualifiedComponent(SPELLER_DEBUG_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchEngine, &cch);
|
|
if ((er != ERROR_SUCCESS) && (er != ERROR_FILE_NOT_FOUND))
|
|
{
|
|
cch = cchEngine;
|
|
er = MsiProvideQualifiedComponent(SPELLER_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchEngine, &cch);
|
|
}
|
|
#else
|
|
er = MsiProvideQualifiedComponent(SPELLER_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchEngine, &cch);
|
|
#endif
|
|
|
|
if ((er != ERROR_SUCCESS) && (er != ERROR_FILE_NOT_FOUND))
|
|
{
|
|
fFound = FALSE;
|
|
goto errorExit;
|
|
}
|
|
|
|
// Hebrew has main lex in new speller
|
|
for (i = 0; i < cDictTypes; i++)
|
|
{
|
|
int nDictionaryIndex = (int)min(i, ARRAYSIZE(rgpszDictionaryTypes)-1);
|
|
wnsprintf(rgchQual, ARRAYSIZE(rgchQual), "%d\\%s", lgid, rgpszDictionaryTypes[nDictionaryIndex]);
|
|
cch = cchLex;
|
|
|
|
#ifdef DEBUG
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_DEBUG_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchLex, &cch);
|
|
if ((er != ERROR_SUCCESS) && (er != ERROR_FILE_NOT_FOUND))
|
|
{
|
|
cch = cchLex;
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchLex, &cch);
|
|
}
|
|
#else // DEBUG
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_GUID, rgchQual, (bTestAvail ? INSTALLMODE_EXISTING : INSTALLMODE_DEFAULT), rgchLex, &cch);
|
|
#endif // DEBUG
|
|
|
|
if ((er == ERROR_SUCCESS) || (er == ERROR_FILE_NOT_FOUND))
|
|
{
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
errorExit:
|
|
if (bTestAvail)
|
|
{
|
|
// Restore original UI Level
|
|
MsiSetInternalUI(iuilOriginal, NULL);
|
|
}
|
|
return fFound;
|
|
}
|
|
|
|
BOOL FIsNewSpellerInstaller(IOleCommandTarget* pParentCmdTarget)
|
|
{
|
|
LANGID langid;
|
|
TCHAR rgchEngine[MAX_PATH];
|
|
int cchEngine = sizeof(rgchEngine) / sizeof(rgchEngine[0]);
|
|
TCHAR rgchLex[MAX_PATH];
|
|
int cchLex = sizeof(rgchLex) / sizeof(rgchLex[0]);
|
|
|
|
// first try to load dictionaries for various languages
|
|
langid = WGetLangID(pParentCmdTarget);
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex, TRUE))
|
|
{
|
|
langid = GetSystemDefaultLangID();
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex, TRUE))
|
|
{
|
|
langid = 1033; // bloody cultural imperialists.
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex, TRUE))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// [email protected] - copied from "old" spell code - 32675
|
|
/*
|
|
* FIsSpellingInstalled
|
|
*
|
|
* Purpose:
|
|
* Is the spelling stuff installed
|
|
*
|
|
* Arguments:
|
|
* none
|
|
*
|
|
* Returns:
|
|
* BOOL Returns TRUE if spelling is installed, else FALSE.
|
|
*/
|
|
BOOL FIsSpellingInstalled(IOleCommandTarget* pParentCmdTarget)
|
|
{
|
|
TCHAR rgchBufDigit[10];
|
|
|
|
if (GetLangID(pParentCmdTarget, rgchBufDigit, sizeof(rgchBufDigit)/sizeof(TCHAR)) && !FBadSpellChecker(rgchBufDigit))
|
|
return true;
|
|
|
|
if (FIsNewSpellerInstaller(pParentCmdTarget))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Does a quick check to see if spelling is available; caches result.
|
|
BOOL FCheckSpellAvail(IOleCommandTarget* pParentCmdTarget)
|
|
{
|
|
// [email protected] - copied from "old" spell code - 32675
|
|
static int fSpellAvailable = -1;
|
|
|
|
if (fSpellAvailable < 0)
|
|
fSpellAvailable = (FIsSpellingInstalled(pParentCmdTarget) ? 1 : 0);
|
|
|
|
return (fSpellAvailable > 0);
|
|
}
|
|
|
|
HRESULT CSpell::HrSpellReset()
|
|
{
|
|
m_fSpellContinue = FALSE;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
/*
|
|
* UlNoteCmdToolsSpelling
|
|
*
|
|
* Purpose:
|
|
* An interface layer between the note and core spelling code
|
|
*
|
|
* Arguments:
|
|
* HWND Owning window handle, main window
|
|
* HWND Subject line window, checked first, actually.
|
|
* BOOL Suppress done message (used when spell-check on send)
|
|
*
|
|
* Returns:
|
|
* ULONG Returns 0 if spelling check was completed, else returns non-zero
|
|
* if an error occurred or user cancelled the spell check.
|
|
*/
|
|
HRESULT CSpell::HrSpellChecking(IHTMLTxtRange *pRangeIgnore, HWND hwndMain, BOOL fSuppressDoneMsg)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
hr = HrSpellReset();
|
|
if (FAILED(hr))
|
|
goto End;
|
|
|
|
hr = HrInitRanges(pRangeIgnore, hwndMain, fSuppressDoneMsg);
|
|
if(FAILED(hr))
|
|
goto End;
|
|
|
|
hr = HrFindErrors();
|
|
if(FAILED(hr))
|
|
goto End;
|
|
|
|
if(m_wsrb.sstat==sstatNoErrors && m_fShowDoneMsg)
|
|
AthMessageBoxW(m_hwndNote, MAKEINTRESOURCEW(idsSpellCaption), MAKEINTRESOURCEW(idsSpellMsgDone), NULL, MB_OK | MB_ICONINFORMATION);
|
|
|
|
End:
|
|
DeInitRanges();
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
HRESULT CSpell::HrBkgrndSpellTimer()
|
|
{
|
|
HRESULT hr=NOERROR;
|
|
LONG cSpellWords = 0;
|
|
IHTMLTxtRange *pTxtRange=0;
|
|
LONG cb;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
if (m_Stack.fEmpty())
|
|
goto error;
|
|
|
|
while (!(m_Stack.fEmpty()) && cSpellWords <= MAX_SPELLWORDS)
|
|
{
|
|
m_Stack.HrGetRange(&pTxtRange);
|
|
if (pTxtRange)
|
|
{
|
|
while(cSpellWords <= MAX_SPELLWORDS)
|
|
{
|
|
pTxtRange->collapse(VARIANT_TRUE);
|
|
if (SUCCEEDED(pTxtRange->expand((BSTR)c_bstr_Word, &fSuccess)) && fSuccess==VARIANT_TRUE)
|
|
{
|
|
HrBkgrndSpellCheck(pTxtRange);
|
|
cSpellWords++;
|
|
}
|
|
else
|
|
{
|
|
m_Stack.pop();
|
|
SafeRelease(pTxtRange);
|
|
break;
|
|
}
|
|
|
|
cb=0;
|
|
if (FAILED(pTxtRange->moveStart((BSTR)c_bstr_Word, 1, &cb)) || cb!=1)
|
|
{
|
|
m_Stack.pop();
|
|
SafeRelease(pTxtRange);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
error:
|
|
SafeRelease(pTxtRange);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
HRESULT CSpell::HrBkgrndSpellCheck(IHTMLTxtRange *pTxtRange)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPSTR pszText = 0;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
hr = pTxtRange->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = HrGetText(pTxtRange, &pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (hr == HR_S_SPELLCONTINUE)
|
|
goto error;
|
|
|
|
StripNBSPs(pszText);
|
|
|
|
// ignore words with wildcards
|
|
if (StrChr(pszText, '*'))
|
|
goto error;
|
|
|
|
RemoveTrailingSpace(pszText);
|
|
hr = HrCheckWord(pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
if(m_wsrb.sstat!=sstatNoErrors && m_wsrb.sstat!=sstatRepeatWord) //found an error.
|
|
{
|
|
// FIgnore should take pTxtRange as the parameter.
|
|
if(FIgnore(pTxtRange))
|
|
{
|
|
m_wsrb.sstat = sstatNoErrors;
|
|
goto error;
|
|
}
|
|
|
|
if (HrHasSquiggle(pTxtRange)==S_OK)
|
|
{
|
|
DebugTrace("Spell: Bad word %s\n", pszText);
|
|
goto error;
|
|
}
|
|
|
|
//put red squiggle
|
|
HrSetSquiggle(pTxtRange);
|
|
|
|
}
|
|
else //if the wrong word is corrected, delete <U> tag.
|
|
{
|
|
if (HrHasSquiggle(pTxtRange)==S_OK)
|
|
HrDeleteSquiggle(pTxtRange);
|
|
|
|
}
|
|
|
|
error:
|
|
SafeMemFree(pszText);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
//const static CHAR c_szSquiggleFmt[] = "<U style='color:red'>%s</U>";
|
|
const static CHAR c_szSquiggleFmt[] = "<SPAN class=badspelling STYLE='text-decoration:underline;color:red'>%s</SPAN>";
|
|
HRESULT CSpell::HrSetSquiggle(IHTMLTxtRange *pTxtRange)
|
|
{
|
|
CHAR szBuf[MAX_PATH]={0};
|
|
BSTR bstr=0;
|
|
HRESULT hr=NOERROR;
|
|
LPSTR pszText=0;
|
|
INT nSpaces=0;
|
|
int i;
|
|
|
|
hr = HrGetText(pTxtRange, &pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (hr == HR_S_SPELLCONTINUE)
|
|
goto error;
|
|
|
|
hr = HrGetSpaces(pszText, &nSpaces);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
RemoveTrailingSpace(pszText);
|
|
wnsprintf(szBuf, ARRAYSIZE(szBuf), c_szSquiggleFmt, pszText);
|
|
for(i=0; i<(nSpaces-1); i++)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
if (nSpaces>0)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
|
|
HrLPSZToBSTR(szBuf, &bstr);
|
|
hr = pTxtRange->pasteHTML(bstr);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
SafeSysFreeString(bstr);
|
|
SafeMemFree(pszText);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
HRESULT CSpell::HrDeleteSquiggle(IHTMLTxtRange *pTxtRange)
|
|
{
|
|
CHAR szBuf[MAX_PATH]={0};
|
|
BSTR bstr=0;
|
|
HRESULT hr=NOERROR;
|
|
LPSTR pszText=0;
|
|
INT nSpaces=0;
|
|
int i;
|
|
|
|
hr = HrGetText(pTxtRange, &pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (hr == HR_S_SPELLCONTINUE)
|
|
goto error;
|
|
|
|
hr = HrGetSpaces(pszText, &nSpaces);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
StrCpyN(szBuf, pszText, ARRAYSIZE(szBuf));
|
|
for(i=0; i<(nSpaces-1); i++)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
if (nSpaces>0)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
HrLPSZToBSTR(szBuf, &bstr);
|
|
hr = pTxtRange->pasteHTML(bstr);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
SafeSysFreeString(bstr);
|
|
SafeMemFree(pszText);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
HRESULT CSpell::HrGetSpaces(LPSTR pszText, INT* pnSpaces)
|
|
{
|
|
LPSTR p;
|
|
*pnSpaces = 0;
|
|
p = StrChrI(pszText, ' ');
|
|
if(p)
|
|
{
|
|
*pnSpaces = (INT) (&pszText[lstrlen(pszText)] - p);
|
|
Assert(*pnSpaces>=0);
|
|
}
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CSpell::HrInsertMenu(HMENU hmenu, IHTMLTxtRange *pTxtRange)
|
|
{
|
|
LPSTR pch=0;
|
|
LPSTR pszText=0;
|
|
INT index=0;
|
|
HRESULT hr;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
hr = pTxtRange->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = HrGetText(pTxtRange, &pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (pszText==NULL || *pszText=='\0')
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
StrCpyN(m_szEdited, pszText, ARRAYSIZE(m_szEdited));
|
|
HrSpellSuggest();
|
|
pch = m_szSuggest;
|
|
if (*pch == '\0')
|
|
{
|
|
LoadString(g_hLocRes, idsSpellNoSuggestions, m_szTempBuffer,
|
|
sizeof(m_szTempBuffer)/sizeof(TCHAR));
|
|
InsertMenu(hmenu, (UINT)0, MF_BYPOSITION|MF_STRING, idmSuggest0, m_szTempBuffer);
|
|
EnableMenuItem(hmenu, idmSuggest0, MF_BYCOMMAND|MF_GRAYED);
|
|
//ListBox_AddString(hwndLbx, m_szTempBuffer);
|
|
}
|
|
else
|
|
{
|
|
while(*pch != '\0' && index<5)
|
|
{
|
|
InsertMenu(hmenu, (UINT)index, MF_BYPOSITION|MF_STRING, idmSuggest0 + index, pch);
|
|
index++;
|
|
//ListBox_AddString(hwndLbx, pch);
|
|
while(*pch != '\0')
|
|
pch++;
|
|
pch++;
|
|
}
|
|
}
|
|
|
|
error:
|
|
SafeMemFree(pszText);
|
|
return hr;
|
|
}
|
|
|
|
|
|
const static TCHAR c_szFmt[] = "%s%s";
|
|
HRESULT CSpell::HrReplaceBySuggest(IHTMLTxtRange *pTxtRange, INT index)
|
|
{
|
|
CHAR szBuf[MAX_PATH] = {0};
|
|
BSTR bstr=0;
|
|
BSTR bstrPut=0;
|
|
LPSTR pch=0;
|
|
INT i=0;
|
|
HRESULT hr;
|
|
|
|
pch = m_szSuggest;
|
|
while(*pch != '\0' && i<5)
|
|
{
|
|
if (index == i)
|
|
{
|
|
StrCpyN(szBuf, pch, ARRAYSIZE(szBuf));
|
|
|
|
if (SUCCEEDED(pTxtRange->get_text(&bstr)) && bstr)
|
|
{
|
|
LPSTR pszText = 0;
|
|
if (SUCCEEDED(HrBSTRToLPSZ(CP_ACP, bstr, &pszText)) && pszText)
|
|
{
|
|
LPSTR psz;
|
|
INT nSpaces=0;
|
|
psz = StrChrI(pszText, ' ');
|
|
if(psz)
|
|
{
|
|
nSpaces = (INT) (&pszText[lstrlen(pszText)] - psz);
|
|
Assert(nSpaces>=0);
|
|
for(int i=0; i<(nSpaces-1); i++)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
if (nSpaces>0)
|
|
StrCatBuff(szBuf, " ", ARRAYSIZE(szBuf));
|
|
}
|
|
hr = HrLPSZToBSTR(szBuf, &bstrPut);
|
|
|
|
SafeMemFree(pszText);
|
|
}
|
|
SafeSysFreeString(bstr);
|
|
}
|
|
if (bstrPut)
|
|
{
|
|
pTxtRange->pasteHTML(bstrPut);
|
|
SafeSysFreeString(bstrPut);
|
|
}
|
|
break;
|
|
}
|
|
i++;
|
|
while(*pch != '\0')
|
|
pch++;
|
|
pch++;
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HRESULT CSpell::HrFindErrors()
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
if(m_State == SEL)
|
|
{
|
|
hr = HrCheckRange(m_pRangeSelExpand);
|
|
// if hr==HR_S_ABORT, quit so as to pass control to dialog procedure which calls HrFindErrors.
|
|
if(TESTHR(hr))
|
|
goto error;
|
|
|
|
if(AthMessageBoxW(m_hwndDlg ? m_hwndDlg : m_hwndNote,
|
|
MAKEINTRESOURCEW(idsSpellCaption),
|
|
MAKEINTRESOURCEW(idsSpellMsgContinue),
|
|
NULL,
|
|
MB_YESNO | MB_ICONEXCLAMATION ) != IDYES)
|
|
{
|
|
m_fShowDoneMsg = FALSE;
|
|
goto error;
|
|
}
|
|
|
|
CleanupState();
|
|
}
|
|
|
|
if(m_State == SELENDDOCEND)
|
|
{
|
|
DumpRange(m_pRangeSelEndDocEnd);
|
|
m_fIgnoreScope = TRUE;
|
|
hr = HrCheckRange(m_pRangeSelEndDocEnd);
|
|
m_fIgnoreScope = FALSE;
|
|
if(TESTHR(hr))
|
|
goto error;
|
|
|
|
CleanupState();
|
|
|
|
hr = HrSpellReset();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
if(m_State == DOCSTARTSELSTART)
|
|
{
|
|
hr = HrCheckRange(m_pRangeDocStartSelStart);
|
|
if(TESTHR(hr))
|
|
goto error;
|
|
|
|
CleanupState();
|
|
}
|
|
|
|
error:
|
|
// save the hr so as to know if something went wrong when dialog procedure calls HrFindErrors.
|
|
m_hr = hr;
|
|
return hr;
|
|
}
|
|
|
|
VOID CSpell::CleanupState()
|
|
{
|
|
m_State++;
|
|
SafeRelease(m_pRangeChecking);
|
|
}
|
|
|
|
HRESULT CSpell::HrCheckRange(IHTMLTxtRange* pRange)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
INT_PTR nCode;
|
|
LPSTR pszText = 0;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
if(m_pRangeChecking == NULL)
|
|
{
|
|
hr = pRange->duplicate(&m_pRangeChecking);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeChecking->collapse(VARIANT_TRUE);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
while(TRUE)
|
|
{
|
|
SafeMemFree(pszText);
|
|
|
|
hr = HrGetNextWordRange(m_pRangeChecking);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (hr == HR_S_SPELLBREAK)
|
|
break;
|
|
if (hr == HR_S_SPELLCONTINUE)
|
|
continue;
|
|
|
|
// Do we really need it?
|
|
if (!m_fIgnoreScope)
|
|
{
|
|
hr = pRange->inRange(m_pRangeChecking, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if(fSuccess != VARIANT_TRUE)
|
|
break;
|
|
}
|
|
|
|
// check if we are on the original text of a reply/forward message.
|
|
if(m_pRangeIgnore)
|
|
{
|
|
fSuccess = VARIANT_FALSE;
|
|
|
|
m_pRangeIgnore->inRange(m_pRangeChecking, &fSuccess);
|
|
|
|
// normally don't spellcheck words in m_pRangeIgnore.
|
|
// but if it's selected, we check it.
|
|
if(fSuccess==VARIANT_TRUE)
|
|
{
|
|
hr = m_pRangeSelExpand->inRange(m_pRangeChecking, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if(fSuccess != VARIANT_TRUE)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
tryWordAgain:
|
|
hr = HrGetText(m_pRangeChecking, &pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if (hr == HR_S_SPELLBREAK)
|
|
break;
|
|
if (hr == HR_S_SPELLCONTINUE)
|
|
continue;
|
|
|
|
hr = HrCheckWord(pszText);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
if(m_wsrb.sstat!=sstatNoErrors) //found an error.
|
|
{
|
|
if(FIgnore(m_pRangeChecking))
|
|
{
|
|
m_wsrb.sstat = sstatNoErrors;
|
|
continue;
|
|
}
|
|
|
|
// if it contains a period, remove it for processing
|
|
if (StrChr(pszText, '.'))
|
|
{
|
|
BOOL fResult;
|
|
|
|
hr = HrStripTrailingPeriod(m_pRangeChecking, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
goto tryWordAgain;
|
|
}
|
|
|
|
HrProcessSpellErrors();
|
|
if(!m_hwndDlg) //spellchecking dialog is lauched only once.
|
|
{
|
|
nCode = DialogBoxParam(g_hLocRes, MAKEINTRESOURCE(iddSpelling), m_hwndNote,
|
|
SpellingDlgProc, (LPARAM)this);
|
|
|
|
}
|
|
if(nCode == -1)
|
|
hr = E_FAIL;
|
|
else if(FAILED(m_hr))
|
|
// something was wrong when dialog calls HrFindErrors.
|
|
// it has higher priority than IDCANCEL.
|
|
hr = m_hr;
|
|
else if(nCode == IDCANCEL)
|
|
hr = HR_S_SPELLCANCEL;
|
|
else
|
|
// we quit here to pass control to the dialog procedure which calls HrFindErrors.
|
|
hr = HR_S_ABORT;
|
|
goto error;
|
|
}
|
|
|
|
}
|
|
|
|
error:
|
|
SafeMemFree(pszText);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpell::HrGetText(IMarkupPointer* pRangeStart, IMarkupPointer* pRangeEnd, LPSTR *ppszText)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
IHTMLTxtRange *pTxtRange = NULL;
|
|
BSTR bstr = NULL;
|
|
|
|
if (ppszText == NULL || pRangeStart == NULL || pRangeEnd == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppszText = NULL;
|
|
|
|
hr = _EnsureInited();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pBodyElem->createTextRange(&pTxtRange);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->MoveRangeToPointers(pRangeStart, pRangeEnd, pTxtRange);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pTxtRange->get_text(&bstr);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if(bstr==NULL || SysStringLen(bstr)==0)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
// never spell check Japanese.
|
|
if(((WORD)*bstr > (WORD)0x3000) && //DBCS
|
|
(GetACP() == 932 || FIgnoreDBCS()))
|
|
{
|
|
hr = HR_S_SPELLCONTINUE;
|
|
goto error;
|
|
}
|
|
|
|
hr = HrBSTRToLPSZ(CP_ACP, bstr, ppszText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
if (*ppszText == NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
SafeRelease(pTxtRange);
|
|
SafeSysFreeString(bstr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpell::HrGetText(IHTMLTxtRange* pRange, LPSTR *ppszText)
|
|
{
|
|
BSTR bstr=0;
|
|
HRESULT hr = NOERROR;
|
|
UINT uCodePage;
|
|
|
|
if (ppszText==NULL || pRange==NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppszText = 0;
|
|
|
|
hr = pRange->get_text(&bstr);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
if(bstr==NULL || SysStringLen(bstr)==0)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
// never spell check Japanese.
|
|
if(((WORD)*bstr > (WORD)0x3000) && //DBCS
|
|
(GetACP() == 932 || FIgnoreDBCS()))
|
|
{
|
|
hr = HR_S_SPELLCONTINUE;
|
|
goto error;
|
|
}
|
|
|
|
uCodePage = GetCodePage();
|
|
*ppszText = PszToANSI(uCodePage, bstr);
|
|
if (*ppszText == NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
SafeSysFreeString(bstr);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpell::HrStripTrailingPeriod(IHTMLTxtRange* pRange, BOOL* pfResult)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
IMarkupPointer *pRangeStart = NULL;
|
|
IMarkupPointer *pRangeEnd = NULL;
|
|
IMarkupPointer *pRangeTemp = NULL;
|
|
MARKUP_CONTEXT_TYPE markupContext;
|
|
long cch;
|
|
OLECHAR chText[64];
|
|
BOOL fResult;
|
|
|
|
if (pRange==NULL || pfResult == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfResult = FALSE;
|
|
|
|
hr = _EnsureInited();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->MovePointersToRange(pRange, pRangeStart, pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pRangeStart->IsEqualTo(pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
// check for '.' and remove
|
|
{
|
|
hr = pRangeTemp->MoveToPointer(pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finished;
|
|
|
|
hr = pRangeTemp->IsRightOf(pRangeStart, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (!fResult)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && chText[0] != L'.')
|
|
goto finished;
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
hr = pRangeEnd->MoveToPointer(pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
*pfResult = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
finished:
|
|
hr = m_pMarkup->MoveRangeToPointers(pRangeStart, pRangeEnd, pRange);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
SafeRelease(pRangeStart);
|
|
SafeRelease(pRangeEnd);
|
|
SafeRelease(pRangeTemp);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpell::HrHasWhitespace(IMarkupPointer* pRangeStart, IMarkupPointer* pRangeEnd, BOOL *pfResult)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPSTR pszText = NULL;
|
|
LPSTR psz;
|
|
|
|
if (pRangeStart == NULL || pRangeEnd == NULL || pfResult == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfResult = FALSE;
|
|
|
|
hr = HrGetText(pRangeStart, pRangeEnd, &pszText);
|
|
if (FAILED(hr) || HR_S_SPELLCONTINUE == hr || HR_S_SPELLBREAK == hr)
|
|
goto error;
|
|
|
|
Assert(NULL != pszText);
|
|
for(psz = pszText; *psz; psz = CharNext(psz))
|
|
{
|
|
if (IsSpace(psz))
|
|
{
|
|
*pfResult = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
error:
|
|
SafeMemFree(pszText);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSpell::HrGetNextWordRange(IHTMLTxtRange* pRange)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
IMarkupPointer *pRangeStart = NULL;
|
|
IMarkupPointer *pRangeEnd = NULL;
|
|
IMarkupPointer *pRangeTemp = NULL;
|
|
MARKUP_CONTEXT_TYPE markupContext;
|
|
long cch;
|
|
OLECHAR chText[64];
|
|
BOOL fResult;
|
|
BOOL fFoundWhite;
|
|
|
|
if (pRange==NULL)
|
|
return E_INVALIDARG;
|
|
|
|
hr = _EnsureInited();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->MovePointersToRange(pRange, pRangeStart, pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pRangeStart->IsEqualTo(pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (!fResult)
|
|
{
|
|
do
|
|
{
|
|
hr = pRangeStart->MoveUnit(MOVEUNIT_NEXTWORDBEGIN);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// make sure beyond the old end
|
|
hr = pRangeStart->IsLeftOf(pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
} while(fResult);
|
|
|
|
hr = pRangeEnd->MoveToPointer(pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
hr = pRangeEnd->MoveUnit(MOVEUNIT_NEXTWORDEND);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
processNextWord:
|
|
// check to see if we have anything
|
|
hr = pRangeEnd->IsRightOf(pRangeStart, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// if the end is not to the right of the start then we do not have a word
|
|
if (!fResult)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
// strip preceding puncts or white space
|
|
// words can also be created with just puncts and whitespace
|
|
{
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeStart->Right(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finished;
|
|
|
|
hr = pRangeStart->IsLeftOf(pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (!fResult)
|
|
{
|
|
hr = pRangeEnd->MoveUnit(MOVEUNIT_NEXTWORDEND);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && 0 == ((C1_SPACE | C1_PUNCT) & GetWCharType(chText[0])))
|
|
break;
|
|
|
|
cch = 1;
|
|
hr = pRangeStart->Right(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
// check for white space and remove
|
|
{
|
|
fFoundWhite = FALSE;
|
|
|
|
hr = pRangeTemp->MoveToPointer(pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finished;
|
|
|
|
hr = pRangeTemp->IsRightOf(pRangeStart, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (!fResult)
|
|
{
|
|
hr = HR_S_SPELLBREAK;
|
|
goto error;
|
|
}
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
if (0 == (C1_SPACE & GetWCharType(chText[0])))
|
|
{
|
|
if (!fFoundWhite)
|
|
break;
|
|
|
|
goto finished;
|
|
}
|
|
|
|
fFoundWhite = TRUE;
|
|
}
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
hr = pRangeEnd->MoveToPointer(pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now look for a period
|
|
{
|
|
hr = pRangeTemp->MoveToPointer(pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finished;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && chText[0] != L'.')
|
|
goto finished;
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && chText[0] == L'.')
|
|
{
|
|
hr = HrHasWhitespace(pRangeStart, pRangeTemp, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
goto finished;
|
|
|
|
hr = pRangeEnd->MoveToPointer(pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// scan ahead for characters
|
|
// need to check for i.e. -- abbreviations
|
|
// it sure would be nice if Trident could do this!!
|
|
{
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finished;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && 0 == (C1_SPACE & GetWCharType(chText[0])))
|
|
goto finished;
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// we found more text
|
|
// need to check to see if we crossed white space to get here
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
hr = pRangeTemp->MoveToPointer(pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pRangeTemp->MoveUnit(MOVEUNIT_NEXTWORDEND);
|
|
if (FAILED(hr))
|
|
goto finished;
|
|
|
|
hr = HrHasWhitespace(pRangeStart, pRangeTemp, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
goto finished;
|
|
|
|
pRangeEnd->MoveToPointer(pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
goto processNextWord;
|
|
}
|
|
}
|
|
}
|
|
|
|
goto finished;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
finished:
|
|
hr = m_pMarkup->MoveRangeToPointers(pRangeStart, pRangeEnd, pRange);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
SafeRelease(pRangeStart);
|
|
SafeRelease(pRangeEnd);
|
|
SafeRelease(pRangeTemp);
|
|
|
|
return hr;
|
|
}
|
|
|
|
BOOL CSpell::FIgnore(IHTMLTxtRange *pRangeChecking)
|
|
{
|
|
HRESULT hr;
|
|
IHTMLAnchorElement *pAE=0;
|
|
IHTMLElement *pElemParent=0;
|
|
BOOL fRet = FALSE;
|
|
BSTR bstr=0;
|
|
IHTMLTxtRange *pRange=0;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
if(pRangeChecking == NULL)
|
|
return fRet;
|
|
|
|
if(FIgnoreURL())
|
|
{
|
|
hr = pRangeChecking->duplicate(&pRange);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
hr = pRange->collapse(VARIANT_TRUE);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
hr = pRange->expand((BSTR)c_bstr_Character, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
// check pRangeChecking if we are on URL
|
|
hr = pRange->parentElement(&pElemParent);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
hr = pElemParent->QueryInterface(IID_IHTMLAnchorElement, (LPVOID *)&pAE);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
hr = pAE->get_href(&bstr);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
if(bstr != NULL)
|
|
{
|
|
fRet = TRUE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
ReleaseObj(pElemParent);
|
|
ReleaseObj(pAE);
|
|
ReleaseObj(pRange);
|
|
SafeSysFreeString(bstr);
|
|
return fRet;
|
|
}
|
|
|
|
// [email protected] - can now specify dict index - 53193
|
|
HRESULT CSpell::AddToUdrW(WCHAR* pwsz, PROOFLEX lex)
|
|
{
|
|
m_pfnSpellerAddUdr(m_pid, lex, pwsz);
|
|
return NOERROR;
|
|
}
|
|
|
|
// [email protected] - can now specify dict index - 53193
|
|
HRESULT CSpell::AddToUdrA(CHAR* psz, PROOFLEX lex)
|
|
{
|
|
WCHAR wszBuf[cchEditBufferMax]={0};
|
|
MultiByteToWideChar(CP_ACP, 0, psz, -1, wszBuf, ARRAYSIZE(wszBuf)-1);
|
|
return AddToUdrW(wszBuf, lex);
|
|
}
|
|
|
|
HRESULT CSpell::HrProcessSpellErrors()
|
|
{
|
|
int idSpellErrorString;
|
|
HRESULT hr = S_OK;
|
|
|
|
WideCharToMultiByte(GetCodePage(), 0, m_wsib.pwsz, -1, m_szWrongWord, sizeof(m_szWrongWord)-1, NULL, NULL);
|
|
|
|
// Select error word in edit control except for Abbreviation warnings
|
|
if (m_wsrb.sstat != sstatWordConsideredAbbreviation && m_pRangeChecking)
|
|
{
|
|
hr = m_pRangeChecking->select();
|
|
if(FAILED(hr))
|
|
goto End;
|
|
}
|
|
|
|
// Process spelling error
|
|
if (m_wsrb.sstat == sstatReturningChangeAlways ||
|
|
m_wsrb.sstat == sstatReturningChangeOnce)
|
|
{
|
|
WideCharToMultiByte(GetCodePage(), 0, m_wsrb.pwsz, -1, m_szEdited, sizeof(m_szEdited)-1, NULL, NULL);
|
|
|
|
// "Change always" was returned. This means we have to do the replacement
|
|
// automatically and then find the next spelling error.
|
|
if (m_wsrb.sstat==sstatReturningChangeAlways)
|
|
{
|
|
FVerifyThisText(m_szEdited, TRUE);
|
|
m_fCanUndo = FALSE; // can't undo automatic replacements
|
|
hr = HrReplaceErrorText(TRUE, FALSE);
|
|
if (FAILED(hr))
|
|
goto End;
|
|
m_wsrb.sstat = sstatNoErrors;
|
|
HrFindErrors();
|
|
}
|
|
}
|
|
else if (m_wsrb.sstat == sstatWordConsideredAbbreviation)
|
|
{
|
|
// An abbreviation was returned. We need to add it to the IgnoreAlways cache and
|
|
// find the next spelling error.
|
|
AddToUdrW((WCHAR*)m_wsib.pwsz, m_rgprflex[1]);
|
|
m_wsrb.sstat = sstatNoErrors;
|
|
HrFindErrors();
|
|
|
|
}
|
|
else
|
|
StrCpyN(m_szEdited, m_szWrongWord, ARRAYSIZE(m_szEdited));
|
|
|
|
|
|
// Load the right error description string
|
|
switch (m_wsrb.sstat)
|
|
{
|
|
case sstatUnknownInputWord:
|
|
case sstatReturningChangeOnce:
|
|
case sstatInitialNumeral:
|
|
idSpellErrorString = idsSpellWordNotFound;
|
|
break;
|
|
case sstatRepeatWord:
|
|
idSpellErrorString = idsSpellRepeatWord;
|
|
break;
|
|
case sstatErrorCapitalization:
|
|
idSpellErrorString = idsSpellWordNeedsCap;
|
|
break;
|
|
}
|
|
|
|
LoadString(g_hLocRes, idSpellErrorString, m_szErrType,
|
|
sizeof(m_szErrType)/sizeof(TCHAR));
|
|
|
|
// Handle suggestions
|
|
m_fSuggestions = FALSE;
|
|
#ifdef __WBK__NEVER__
|
|
if (m_wsrb.sstat == sstatReturningChangeOnce)
|
|
{
|
|
// Automatic suggestion of one word
|
|
m_fSuggestions = TRUE;
|
|
m_fNoneSuggested = FALSE;
|
|
}
|
|
else
|
|
#endif // __WBK__NEVER__
|
|
{
|
|
// Enumerate suggestion list if requested
|
|
if (m_fAlwaysSuggest)
|
|
hr = HrSpellSuggest();
|
|
}
|
|
|
|
End:
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::HrReplaceErrorText(BOOL fChangeAll, BOOL fAddToUdr)
|
|
{
|
|
HRESULT hr=NOERROR;
|
|
WCHAR wszWrong[cchEditBufferMax]={0};
|
|
WCHAR wszEdited[cchEditBufferMax]={0};
|
|
int cwch;
|
|
|
|
if (fAddToUdr)
|
|
{
|
|
RemoveTrailingSpace(m_szWrongWord);
|
|
|
|
cwch = MultiByteToWideChar(GetCodePage(), 0, m_szWrongWord, -1, wszWrong, ARRAYSIZE(wszWrong)-1);
|
|
Assert(cwch);
|
|
|
|
cwch = MultiByteToWideChar(GetCodePage(), 0, m_szEdited, -1, wszEdited, ARRAYSIZE(wszEdited)-1);
|
|
Assert(cwch);
|
|
|
|
hr = m_pfnSpellerAddChangeUdr(m_pid, fChangeAll ? lxtChangeAlways : lxtChangeOnce, wszWrong, wszEdited);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
hr = HrReplaceSel(m_szEdited);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::HrCheckWord(LPCSTR pszWord)
|
|
{
|
|
DWORD cwchWord;
|
|
PTEC ptec;
|
|
SPELLERSUGGESTION sugg;
|
|
|
|
cwchWord = MultiByteToWideChar(GetCodePage(), 0, pszWord, -1, m_wszIn, ARRAYSIZE(m_wszIn)-1);
|
|
ZeroMemory(&m_wsrb, sizeof(m_wsrb));
|
|
ZeroMemory(&m_wsib, sizeof(m_wsib));
|
|
m_wsib.pwsz = m_wszIn;
|
|
m_wsib.cch = cwchWord;
|
|
|
|
m_wsib.clex = m_clex;
|
|
m_wsib.prglex = m_rgprflex;
|
|
m_wsib.ichStart = 0;
|
|
m_wsib.cchUse = cwchWord;
|
|
m_wsrb.pwsz = m_wszRet;
|
|
m_wsrb.cchAlloc = ARRAYSIZE(m_wszRet);
|
|
m_wsrb.cszAlloc = 1; // we've got space for 1 speller suggestion
|
|
m_wsrb.prgsugg = &sugg;
|
|
|
|
// [email protected] - "repeat word" bug fix - 2757, 13573, 56057
|
|
// m_wsib.sstate should only be sstateIsContinued after the first call to this function
|
|
// (e.g., when a new speller session is invoked using F7 or the menu item).
|
|
// This allows the spell code to accurately track "repeat" words.
|
|
if (m_fSpellContinue)
|
|
m_wsib.sstate = sstateIsContinued;
|
|
else
|
|
m_fSpellContinue = TRUE;
|
|
|
|
ptec = m_pfnSpellerCheck(m_pid, scmdVerifyBuffer, &m_wsib, &m_wsrb);
|
|
|
|
// do we haveinvalid characters, if so return NOERR
|
|
if (ProofMajorErr(ptec) != ptecNoErrors && ProofMinorErr(ptec) == ptecInvalidEntry)
|
|
{
|
|
// force it to be correct
|
|
m_wsrb.sstat = sstatNoErrors;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
if (ptec != ptecNoErrors)
|
|
|
|
return E_FAIL;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CSpell::HrSpellSuggest()
|
|
{
|
|
int cchWord;
|
|
WCHAR wszBuff[cchMaxSuggestBuff]={0};
|
|
WCHAR wszWord[cchEditBufferMax]={0};
|
|
SPELLERSUGGESTION rgsugg[20];
|
|
TCHAR *pchNextSlot=0;
|
|
ULONG iszSuggestion;
|
|
int cchSuggestion;
|
|
SPELLERSUGGESTION *pSuggestion;
|
|
TCHAR *pchLim=0;
|
|
PTEC ptec;
|
|
SPELLERSTATUS sstat;
|
|
|
|
sstat = m_wsrb.sstat;
|
|
cchWord = MultiByteToWideChar(GetCodePage(), 0, m_szEdited, -1, wszWord, ARRAYSIZE(wszWord)-1);
|
|
m_wsib.cch = cchWord;
|
|
m_wsib.clex = m_clex;
|
|
m_wsib.prglex = m_rgprflex;
|
|
m_wsib.ichStart = 0;
|
|
m_wsib.cchUse = cchWord;
|
|
m_wsib.pwsz = wszWord;
|
|
|
|
m_wsrb.prgsugg = rgsugg;
|
|
m_wsrb.cszAlloc = ARRAYSIZE(rgsugg);
|
|
m_wsrb.pwsz = wszBuff;
|
|
m_wsrb.cchAlloc = ARRAYSIZE(wszBuff);
|
|
|
|
ptec = m_pfnSpellerCheck(m_pid, scmdSuggest, &m_wsib, &m_wsrb);
|
|
m_fNoneSuggested = (m_wsrb.csz == 0);
|
|
|
|
pchLim = &m_szSuggest[ARRAYSIZE(m_szSuggest)-1];
|
|
pchNextSlot = m_szSuggest;;
|
|
do
|
|
{
|
|
pSuggestion = m_wsrb.prgsugg;
|
|
if (sstatMoreInfoThanBufferCouldHold == m_wsrb.sstat)
|
|
{
|
|
m_wsrb.csz = m_wsrb.cszAlloc;
|
|
}
|
|
for (iszSuggestion = 0; iszSuggestion < m_wsrb.csz; iszSuggestion++)
|
|
{
|
|
cchSuggestion = WideCharToMultiByte(GetCodePage(), 0, pSuggestion->pwsz, -1,
|
|
pchNextSlot, (int) (pchLim-pchNextSlot), NULL, NULL);
|
|
|
|
// [email protected] - raid 29322
|
|
// make sure words do not have trailing spaces
|
|
// only the French speller returns words with trailing spaces
|
|
RemoveTrailingSpace(pchNextSlot);
|
|
cchSuggestion = lstrlen(pchNextSlot)+1;
|
|
|
|
pSuggestion++;
|
|
if (cchSuggestion > 0)
|
|
pchNextSlot += cchSuggestion;
|
|
Assert(pchNextSlot <= pchLim);
|
|
}
|
|
ptec = m_pfnSpellerCheck(m_pid, scmdSuggestMore, &m_wsib, &m_wsrb);
|
|
} while (ptec == ptecNoErrors && m_wsrb.sstat!=sstatNoMoreSuggestions);
|
|
*pchNextSlot = '\0';
|
|
m_wsrb.sstat = sstat;
|
|
m_fSuggestions = TRUE;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
VOID CSpell::FillSuggestLbx()
|
|
{
|
|
HWND hwndLbx;
|
|
INT isz;
|
|
LPTSTR sz;
|
|
LPTSTR pch;
|
|
|
|
// Empty contents regardless
|
|
hwndLbx = GetDlgItem(m_hwndDlg, LBX_Spell_Suggest);
|
|
ListBox_ResetContent(hwndLbx);
|
|
|
|
// We didn't even try to get any suggestions
|
|
if (!m_fSuggestions)
|
|
return;
|
|
|
|
// We tried to get suggestions
|
|
pch = m_szSuggest;
|
|
if (*pch == '\0')
|
|
{
|
|
LoadString(g_hLocRes, idsSpellNoSuggestions, m_szTempBuffer,
|
|
sizeof(m_szTempBuffer)/sizeof(TCHAR));
|
|
ListBox_AddString(hwndLbx, m_szTempBuffer);
|
|
}
|
|
else
|
|
{
|
|
while(*pch != '\0')
|
|
{
|
|
ListBox_AddString(hwndLbx, pch);
|
|
while(*pch != '\0')
|
|
pch++;
|
|
pch++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
VOID UpdateEditedFromSuggest(HWND hwndDlg, HWND hwndEdited, HWND hwndSuggest)
|
|
{
|
|
INT nSel;
|
|
INT cch;
|
|
LPSTR szTemp;
|
|
|
|
nSel = ListBox_GetCurSel(hwndSuggest);
|
|
cch = ListBox_GetTextLen(hwndSuggest, nSel) + 1;
|
|
if (MemAlloc((LPVOID *) &szTemp, cch))
|
|
{
|
|
ListBox_GetText(hwndSuggest, nSel, szTemp);
|
|
SetWindowText(hwndEdited, szTemp);
|
|
|
|
// Clear default button style from "Ignore" button and set default to "Change"
|
|
Button_SetStyle(GetDlgItem(hwndDlg, PSB_Spell_Ignore), BS_PUSHBUTTON, TRUE);
|
|
SendMessage(hwndDlg, DM_SETDEFID, PSB_Spell_Change, 0L);
|
|
Button_SetStyle(GetDlgItem(hwndDlg, PSB_Spell_Change), BS_DEFPUSHBUTTON, TRUE);
|
|
|
|
Edit_SetSel(hwndEdited, 0, 32767); // select the whole thing
|
|
Edit_SetModify(hwndEdited, TRUE);
|
|
MemFree(szTemp);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL CSpell::FVerifyThisText(LPSTR szThisText, BOOL /*fProcessOnly*/)
|
|
{
|
|
BOOL fReturn=FALSE;
|
|
HRESULT hr;
|
|
|
|
Assert(szThisText);
|
|
|
|
hr = HrCheckWord(szThisText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
switch (m_wsrb.sstat)
|
|
{
|
|
case sstatUnknownInputWord:
|
|
case sstatInitialNumeral:
|
|
case sstatErrorCapitalization:
|
|
if (AthMessageBoxW(m_hwndDlg, MAKEINTRESOURCEW(idsSpellCaption), MAKEINTRESOURCEW(idsSpellMsgConfirm), NULL, MB_YESNO | MB_ICONEXCLAMATION ) == IDYES)
|
|
fReturn = TRUE;
|
|
else
|
|
fReturn = FALSE;
|
|
break;
|
|
default:
|
|
fReturn = TRUE;
|
|
break;
|
|
}
|
|
|
|
error:
|
|
return fReturn;
|
|
}
|
|
|
|
|
|
VOID CSpell::SpellSaveUndo(INT idButton)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
if(!m_pRangeChecking)
|
|
return;
|
|
|
|
SafeRelease(m_pRangeUndoSave);
|
|
m_pRangeChecking->duplicate(&m_pRangeUndoSave);
|
|
if(!m_pRangeUndoSave)
|
|
goto error;
|
|
|
|
m_fCanUndo = TRUE;
|
|
|
|
error:
|
|
return;
|
|
}
|
|
|
|
VOID CSpell::SpellDoUndo()
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
IOleCommandTarget* pCmdTarget = NULL;
|
|
CHARRANGE chrg = {0};
|
|
LONG lMin = 0;
|
|
|
|
m_fCanUndo = FALSE;
|
|
|
|
if(!m_pRangeUndoSave)
|
|
goto Cleanup;
|
|
|
|
SafeRelease(m_pRangeChecking);
|
|
m_pRangeUndoSave->duplicate(&m_pRangeChecking);
|
|
if(!m_pRangeChecking)
|
|
goto Cleanup;
|
|
|
|
|
|
hr = m_pRangeChecking->collapse(VARIANT_TRUE);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
if (m_fUndoChange)
|
|
{
|
|
m_fUndoChange = FALSE;
|
|
hr = m_pDoc->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&pCmdTarget);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
|
|
hr = pCmdTarget->Exec(&CMDSETID_Forms3,
|
|
IDM_UNDO,
|
|
MSOCMDEXECOPT_DONTPROMPTUSER,
|
|
NULL, NULL);
|
|
if(FAILED(hr))
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
ReleaseObj(pCmdTarget);
|
|
|
|
}
|
|
|
|
|
|
CSpell::CSpell(IHTMLDocument2* pDoc, IOleCommandTarget* pParentCmdTarget, DWORD dwSpellOpt)
|
|
{
|
|
HRESULT hr;
|
|
|
|
Assert(pDoc);
|
|
m_pDoc = pDoc;
|
|
m_pDoc->AddRef();
|
|
|
|
Assert(pParentCmdTarget);
|
|
m_pParentCmdTarget = pParentCmdTarget;
|
|
m_pParentCmdTarget->AddRef();
|
|
|
|
m_hwndDlg = NULL;
|
|
m_cRef = 1;
|
|
m_fSpellContinue = FALSE;
|
|
m_fCanUndo = FALSE;
|
|
m_fUndoChange = FALSE;
|
|
m_State = SEL;
|
|
m_pRangeDocStartSelStart = NULL;
|
|
m_pRangeSel = NULL;
|
|
m_pRangeSelExpand = NULL;
|
|
m_pRangeSelEndDocEnd = NULL;
|
|
m_pRangeChecking = NULL;
|
|
m_pRangeUndoSave = NULL;
|
|
m_hr = NOERROR;
|
|
m_hinstDll = NULL;
|
|
ZeroMemory(&m_wsib, sizeof(m_wsib));
|
|
ZeroMemory(&m_wsrb, sizeof(m_wsrb));
|
|
ZeroMemory(&m_pid, sizeof(m_pid));
|
|
m_fIgnoreScope = FALSE;
|
|
m_dwCookieNotify = 0;
|
|
m_dwOpt = dwSpellOpt;
|
|
|
|
m_langid = lidUnknown;
|
|
|
|
m_clex = 0;
|
|
ZeroMemory(&m_rgprflex, sizeof(m_rgprflex));
|
|
|
|
m_pMarkup = NULL;
|
|
m_pBodyElem = NULL;
|
|
|
|
m_fCSAPI3T1 = FALSE;
|
|
}
|
|
|
|
CSpell::~CSpell()
|
|
{
|
|
CloseSpeller();
|
|
|
|
SafeRelease(m_pDoc);
|
|
SafeRelease(m_pParentCmdTarget);
|
|
|
|
SafeRelease(m_pMarkup);
|
|
|
|
SafeRelease(m_pBodyElem);
|
|
}
|
|
|
|
|
|
ULONG CSpell::AddRef()
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
|
|
ULONG CSpell::Release()
|
|
{
|
|
if (--m_cRef==0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
return m_cRef;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::QueryInterface(REFIID riid, LPVOID *lplpObj)
|
|
{
|
|
if(!lplpObj)
|
|
return E_INVALIDARG;
|
|
|
|
*lplpObj = NULL; // set to NULL, in case we fail.
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown))
|
|
*lplpObj = (LPVOID)this;
|
|
|
|
else if (IsEqualIID(riid, IID_IDispatch))
|
|
*lplpObj = (LPVOID)(IDispatch*)this;
|
|
|
|
else
|
|
return E_NOINTERFACE;
|
|
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IDispatch
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
STDMETHODIMP CSpell::GetIDsOfNames(
|
|
REFIID /*riid*/,
|
|
OLECHAR ** /*rgszNames*/,
|
|
UINT /*cNames*/,
|
|
LCID /*lcid*/,
|
|
DISPID * /*rgDispId*/)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CSpell::GetTypeInfo(
|
|
UINT /*iTInfo*/,
|
|
LCID /*lcid*/,
|
|
ITypeInfo **ppTInfo)
|
|
{
|
|
if (ppTInfo)
|
|
*ppTInfo=NULL;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CSpell::GetTypeInfoCount(UINT *pctinfo)
|
|
{
|
|
if (pctinfo)
|
|
{
|
|
*pctinfo=0;
|
|
return NOERROR;
|
|
}
|
|
else
|
|
return E_POINTER;
|
|
}
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
STDMETHODIMP CSpell::Invoke(
|
|
DISPID dispIdMember,
|
|
REFIID /*riid*/,
|
|
LCID /*lcid*/,
|
|
WORD wFlags,
|
|
DISPPARAMS FAR* /*pDispParams*/,
|
|
VARIANT * /*pVarResult*/,
|
|
EXCEPINFO * /*pExcepInfo*/,
|
|
UINT * /*puArgErr*/)
|
|
{
|
|
IHTMLWindow2 *pWindow=0;
|
|
IHTMLEventObj *pEvent=0;
|
|
BSTR bstr=0;
|
|
HRESULT hr=E_NOTIMPL;
|
|
LONG lKeyCode=0;
|
|
LONG cb;
|
|
|
|
|
|
if (dispIdMember == DISPID_HTMLDOCUMENTEVENTS_ONKEYUP &&
|
|
(wFlags & DISPATCH_METHOD))
|
|
{
|
|
// Order of events:
|
|
// document gives us window gives us event object
|
|
// the event object can tell us which button was clicked
|
|
// event gives us source element gives us ID
|
|
// a couple lstrcmps will tell us which one got hit
|
|
if (!m_pDoc)
|
|
return E_UNEXPECTED;
|
|
|
|
m_pDoc->get_parentWindow(&pWindow);
|
|
if (pWindow)
|
|
{
|
|
pWindow->get_event(&pEvent);
|
|
if (pEvent)
|
|
{
|
|
pEvent->get_keyCode(&lKeyCode);
|
|
if (lKeyCode == 32 || lKeyCode == 188/*','*/ || lKeyCode == 190/*'.'*/ || lKeyCode == 185/*':'*/ || lKeyCode == 186/*';'*/)
|
|
{
|
|
IHTMLTxtRange *pTxtRange=0;
|
|
VARIANT_BOOL fSuccess;
|
|
GetSelection(&pTxtRange);
|
|
if (pTxtRange)
|
|
{
|
|
pTxtRange->move((BSTR)c_bstr_Character, -2, &cb);
|
|
pTxtRange->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
//DumpRange(pRangeDup);
|
|
//pTxtRange->setEndPoint((BSTR)c_bstr_StartToStart, pRangeDup);
|
|
//DumpRange(pTxtRange);
|
|
//pRangeDup->Release();
|
|
|
|
m_Stack.push(pTxtRange);
|
|
pTxtRange->Release();
|
|
}
|
|
}
|
|
else if (lKeyCode == 8 /*backspace*/|| lKeyCode == 46/*del*/)
|
|
{
|
|
IHTMLTxtRange *pTxtRange=0;
|
|
VARIANT_BOOL fSuccess;
|
|
LONG cb;
|
|
GetSelection(&pTxtRange);
|
|
if (pTxtRange)
|
|
{
|
|
pTxtRange->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
if (HrHasSquiggle(pTxtRange)==S_OK)
|
|
{
|
|
//DumpRange(pTxtRange);
|
|
m_Stack.push(pTxtRange);
|
|
}
|
|
pTxtRange->Release();
|
|
}
|
|
}
|
|
pEvent->Release();
|
|
}
|
|
pWindow->Release();
|
|
}
|
|
}
|
|
else if (dispIdMember == DISPID_HTMLDOCUMENTEVENTS_ONKEYPRESS && (wFlags & DISPATCH_METHOD))
|
|
{
|
|
if (!m_pDoc)
|
|
return E_UNEXPECTED;
|
|
|
|
m_pDoc->get_parentWindow(&pWindow);
|
|
if (pWindow)
|
|
{
|
|
pWindow->get_event(&pEvent);
|
|
if (pEvent)
|
|
{
|
|
pEvent->get_keyCode(&lKeyCode);
|
|
if (lKeyCode == 18)// CTRL+R
|
|
{
|
|
IHTMLTxtRange* pRangeDoc = NULL;
|
|
|
|
if (m_pBodyElem)
|
|
m_pBodyElem->createTextRange(&pRangeDoc);
|
|
|
|
if (pRangeDoc)
|
|
{
|
|
pRangeDoc->move((BSTR)c_bstr_Character, -1, &cb);
|
|
m_Stack.push(pRangeDoc);
|
|
pRangeDoc->Release();
|
|
}
|
|
|
|
|
|
}
|
|
pEvent->Release();
|
|
}
|
|
pWindow->Release();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
HRESULT CSpell::HrHasSquiggle(IHTMLTxtRange *pTxtRange)
|
|
{
|
|
BSTR bstr=0;
|
|
HRESULT hr;
|
|
LPWSTR pwszSquiggleStart=0, pwszSquiggleEnd=0, pwszSquiggleAfter=0;
|
|
|
|
hr = pTxtRange->get_htmlText(&bstr);
|
|
if(FAILED(hr) || bstr==0 || *bstr==L'\0')
|
|
{
|
|
hr = S_FALSE;
|
|
goto error;
|
|
}
|
|
|
|
hr = S_FALSE;
|
|
pwszSquiggleStart = StrStrIW(bstr, L"<SPAN class=badspelling");
|
|
if (pwszSquiggleStart)
|
|
{
|
|
pwszSquiggleEnd = StrStrIW(bstr, L"</SPAN>");
|
|
if (pwszSquiggleEnd)
|
|
{
|
|
pwszSquiggleAfter = pwszSquiggleEnd + 7;
|
|
if (*pwszSquiggleAfter == L' ' || *pwszSquiggleAfter == L'\0' || *pwszSquiggleAfter == L'&')
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
error:
|
|
SafeSysFreeString(bstr);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
BOOL CSpell::OpenSpeller()
|
|
{
|
|
SpellerParams params;
|
|
DWORD dwSel;
|
|
LANGID langid;
|
|
|
|
// LoadOldSpeller is called within LoadNewSpeller
|
|
// We should be checking for V1 spellers after failing
|
|
// for the desired V3 speller, then go on to check for
|
|
// default speller and the speller for 1033.
|
|
if (!LoadNewSpeller())
|
|
goto error;
|
|
|
|
if (!OpenUserDictionaries())
|
|
goto error;
|
|
|
|
dwSel = sobitStdOptions;
|
|
m_fAlwaysSuggest = !!FAlwaysSuggest();
|
|
if (FIgnoreNumber())
|
|
dwSel |= sobitIgnoreMixedDigits;
|
|
else
|
|
dwSel &= ~sobitIgnoreMixedDigits;
|
|
|
|
if (FIgnoreUpper())
|
|
dwSel |= sobitIgnoreAllCaps;
|
|
else
|
|
dwSel &= ~sobitIgnoreAllCaps;
|
|
|
|
if (m_pfnSpellerSetOptions(m_pid, soselBits, dwSel) != ptecNoErrors)
|
|
goto error;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
CloseSpeller();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL FNewer(WORD *pwVerOld, WORD *pwVerNew)
|
|
{
|
|
BOOL fOK = FALSE;
|
|
|
|
Assert(pwVerOld);
|
|
Assert(pwVerNew);
|
|
|
|
if (pwVerNew[0] > pwVerOld[0])
|
|
fOK = TRUE;
|
|
else if (pwVerNew[0] == pwVerOld[0])
|
|
{
|
|
if (pwVerNew[1] > pwVerOld[1])
|
|
fOK = TRUE;
|
|
else if (pwVerNew[1] == pwVerOld[1])
|
|
{
|
|
if (pwVerNew[2] > pwVerOld[2])
|
|
fOK = TRUE;
|
|
else if (pwVerNew[2] == pwVerOld[2])
|
|
{
|
|
if (pwVerNew[3] >= pwVerOld[3])
|
|
fOK = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fOK;
|
|
}
|
|
|
|
BOOL GetDllVersion(LPTSTR pszDll, WORD *pwVer, int nCountOfVers)
|
|
{
|
|
Assert(pszDll);
|
|
Assert(pwVer);
|
|
|
|
BOOL fOK = FALSE;
|
|
DWORD dwVerInfoSize, dwVerHnd;
|
|
LPSTR pszInfo, pszVersion, pszT;
|
|
LPWORD pwTrans;
|
|
UINT uLen;
|
|
char szGet[MAX_PATH];
|
|
int i;
|
|
|
|
ZeroMemory(pwVer, nCountOfVers * sizeof(pwVer[0]));
|
|
|
|
if (dwVerInfoSize = GetFileVersionInfoSize(pszDll, &dwVerHnd))
|
|
{
|
|
if (pszInfo = (LPSTR)GlobalAlloc(GPTR, dwVerInfoSize))
|
|
{
|
|
if (GetFileVersionInfo(pszDll, dwVerHnd, dwVerInfoSize, pszInfo))
|
|
{
|
|
if (VerQueryValue(pszInfo, "\\VarFileInfo\\Translation", (LPVOID*)&pwTrans, &uLen) &&
|
|
uLen >= (2 * sizeof(WORD)))
|
|
{
|
|
// set up buffer for calls to VerQueryValue()
|
|
wnsprintf(szGet, ARRAYSIZE(szGet), "\\StringFileInfo\\%04X%04X\\FileVersion", pwTrans[0], pwTrans[1]);
|
|
|
|
if (VerQueryValue(pszInfo, szGet, (LPVOID *)&pszVersion, &uLen) && uLen)
|
|
{
|
|
i = 0;
|
|
while (*pszVersion)
|
|
{
|
|
if ((',' == *pszVersion) || ('.' == *pszVersion))
|
|
i++;
|
|
else
|
|
{
|
|
pwVer[i] *= 10;
|
|
pwVer[i] += (*pszVersion - '0');
|
|
}
|
|
|
|
pszVersion++;
|
|
}
|
|
|
|
fOK = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
GlobalFree((HGLOBAL)pszInfo);
|
|
}
|
|
}
|
|
|
|
return fOK;
|
|
}
|
|
|
|
HINSTANCE LoadCSAPI3T1()
|
|
{
|
|
static BOOL s_fInit = FALSE;
|
|
HINSTANCE hinstLocal;
|
|
|
|
EnterCriticalSection(&g_csCSAPI3T1);
|
|
|
|
// Avoid doing this for every note!
|
|
if (!s_fInit)
|
|
{
|
|
typedef enum
|
|
{
|
|
CSAPI_FIRST,
|
|
CSAPI_DARWIN = CSAPI_FIRST,
|
|
CSAPI_COMMON,
|
|
CSAPI_OE,
|
|
CSAPI_MAX,
|
|
} CSAPISRC;
|
|
|
|
BOOL fCheck;
|
|
|
|
// cb is for BYTE counts, cch for CHARACTER counts
|
|
DWORD cbDllPath;
|
|
DWORD cchDllPath;
|
|
|
|
int csapisrc;
|
|
// Info about the dll currently being examined
|
|
TCHAR szDllPath[MAX_PATH];
|
|
WORD wVer[4] = {0};
|
|
|
|
// Info about the dll we will ultimately load
|
|
TCHAR szNewestDllPath[MAX_PATH];
|
|
WORD wVerNewest[4] = {0};
|
|
|
|
szDllPath[0] = TEXT('\0');
|
|
szNewestDllPath[0] = TEXT('\0');
|
|
|
|
// Avoid doing this for every note!
|
|
s_fInit = TRUE;
|
|
|
|
for (csapisrc = CSAPI_FIRST; csapisrc < CSAPI_MAX; csapisrc++)
|
|
{
|
|
// Assume we can't find the dll using the current method, so there's no need to look at its version
|
|
fCheck = FALSE;
|
|
|
|
switch (csapisrc)
|
|
{
|
|
// see if Darwin knows where it is
|
|
case CSAPI_DARWIN:
|
|
{
|
|
UINT installState;
|
|
|
|
cchDllPath = ARRAYSIZE(szDllPath);
|
|
#ifdef DEBUG
|
|
installState = MsiLocateComponent(CSAPI3T1_DEBUG_GUID, szDllPath, &cchDllPath);
|
|
if (installState != INSTALLSTATE_LOCAL)
|
|
{
|
|
cchDllPath = ARRAYSIZE(szDllPath);
|
|
installState = MsiLocateComponent(CSAPI3T1_GUID, szDllPath, &cchDllPath);
|
|
}
|
|
#else // DEBUG
|
|
installState = MsiLocateComponent(CSAPI3T1_GUID, szDllPath, &cchDllPath);
|
|
#endif // DEBUG
|
|
|
|
// Only bother looking at the version if dll is installed
|
|
fCheck = (INSTALLSTATE_LOCAL == installState);
|
|
}
|
|
break;
|
|
|
|
// Is it in Common Files\Microsoft Shared\Proof?
|
|
case CSAPI_COMMON:
|
|
{
|
|
DWORD dwType;
|
|
HKEY hkey = NULL;
|
|
LPTSTR pszEnd;
|
|
|
|
if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegSharedTools, 0, KEY_QUERY_VALUE, &hkey))
|
|
{
|
|
cbDllPath = sizeof(szDllPath);
|
|
if (SHQueryValueEx(hkey, c_szRegSharedToolsPath, 0L, &dwType, szDllPath, &cbDllPath) == ERROR_SUCCESS)
|
|
{
|
|
pszEnd = PathAddBackslash(szDllPath);
|
|
StrCpyN(pszEnd, c_szSpellCSAPI3T1Path, ARRAYSIZE(szDllPath) - (DWORD)(pszEnd - szDllPath));
|
|
fCheck = TRUE;
|
|
}
|
|
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Is it in the OE directory?
|
|
case CSAPI_OE:
|
|
{
|
|
DWORD dwType;
|
|
HKEY hkey = NULL;
|
|
LPTSTR pszEnd;
|
|
|
|
if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegFlat, 0, KEY_QUERY_VALUE, &hkey))
|
|
{
|
|
cbDllPath = sizeof(szDllPath);
|
|
if (SHQueryValueEx(hkey, c_szInstallRoot, 0L, &dwType, szDllPath, &cbDllPath) == ERROR_SUCCESS)
|
|
{
|
|
pszEnd = PathAddBackslash(szDllPath);
|
|
StrCpyN(pszEnd, c_szCSAPI3T1, ARRAYSIZE(szDllPath) - (DWORD)(pszEnd - szDllPath));
|
|
fCheck = TRUE;
|
|
}
|
|
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE, "Unhandled case hit while looking for csapi3t1.dll!");
|
|
break;
|
|
}
|
|
|
|
// Figure out the version of the dll if needed
|
|
if (fCheck && GetDllVersion(szDllPath, wVer, ARRAYSIZE(wVer)))
|
|
{
|
|
// If it's newer, remember the new version and the file's location
|
|
if (FNewer(wVerNewest, wVer))
|
|
{
|
|
CopyMemory(wVerNewest, wVer, sizeof(wVer));
|
|
StrCpyN(szNewestDllPath, szDllPath, ARRAYSIZE(szNewestDllPath));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Assuming we found something, try to load it
|
|
if (szNewestDllPath[0])
|
|
g_hinstCSAPI3T1 = LoadLibrary(szNewestDllPath);
|
|
}
|
|
|
|
hinstLocal = g_hinstCSAPI3T1;
|
|
LeaveCriticalSection(&g_csCSAPI3T1);
|
|
|
|
return hinstLocal;
|
|
}
|
|
|
|
BOOL CSpell::LoadOldSpeller()
|
|
{
|
|
TCHAR szLangId[MAX_PATH] = {0};
|
|
TCHAR rgchBufKeyTest[MAX_PATH] = {0};
|
|
TCHAR rgchBuf[MAX_PATH] = {0};
|
|
WCHAR rgchBufW[MAX_PATH] = {0};
|
|
TCHAR rgchLex[MAX_PATH] = {0};
|
|
WCHAR rgchLexW[MAX_PATH] = {0};
|
|
WCHAR rgchUserDictW[MAX_PATH]={0};
|
|
PROOFLEXIN plxin;
|
|
PROOFLEXOUT plxout;
|
|
SpellerParams params;
|
|
LANGID langid;
|
|
|
|
m_hinstDll = LoadCSAPI3T1();
|
|
if (!m_hinstDll)
|
|
{
|
|
m_pfnSpellerCloseLex = 0;
|
|
m_pfnSpellerTerminate = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
// We are using the global csapi3t1.dll, so don't free it!
|
|
m_fCSAPI3T1 = TRUE;
|
|
|
|
GetAddr(m_pfnSpellerSetDllName, PROOFSETDLLNAME,"SpellerSetDllName");
|
|
GetAddr(m_pfnSpellerVersion, PROOFVERSION, "SpellerVersion");
|
|
GetAddr(m_pfnSpellerInit, PROOFINIT, "SpellerInit");
|
|
GetAddr(m_pfnSpellerTerminate, PROOFTERMINATE, "SpellerTerminate");
|
|
GetAddr(m_pfnSpellerSetOptions, PROOFSETOPTIONS,"SpellerSetOptions");
|
|
GetAddr(m_pfnSpellerOpenLex, PROOFOPENLEX, "SpellerOpenLex");
|
|
GetAddr(m_pfnSpellerCloseLex, PROOFCLOSELEX, "SpellerCloseLex");
|
|
GetAddr(m_pfnSpellerCheck, SPELLERCHECK, "SpellerCheck");
|
|
GetAddr(m_pfnSpellerAddUdr, SPELLERADDUDR, "SpellerAddUdr");
|
|
GetAddr(m_pfnSpellerBuiltInUdr, SPELLERBUILTINUDR, "SpellerBuiltinUdr");
|
|
GetAddr(m_pfnSpellerAddChangeUdr, SPELLERADDCHANGEUDR, "SpellerAddChangeUdr");
|
|
|
|
langid = WGetLangID(m_pParentCmdTarget);
|
|
wnsprintf(szLangId, ARRAYSIZE(szLangId), "%d", langid);
|
|
wnsprintf(rgchBufKeyTest, ARRAYSIZE(rgchBufKeyTest), c_szRegSpellKeyDef, szLangId);
|
|
GetSpellingPaths(rgchBufKeyTest, rgchBuf, rgchLex, sizeof(rgchBuf)/sizeof(TCHAR));
|
|
|
|
if (!*rgchBuf)
|
|
return FALSE;
|
|
|
|
MultiByteToWideChar(GetCodePage(), 0, rgchBuf, -1, rgchBufW, ARRAYSIZE(rgchBufW)-1);
|
|
m_pfnSpellerSetDllName(rgchBufW, GetCodePage());
|
|
|
|
params.versionAPI = PROOFTHISAPIVERSION;
|
|
if (m_pfnSpellerInit(&m_pid, ¶ms) != ptecNoErrors)
|
|
return FALSE;
|
|
|
|
m_langid = langid;
|
|
|
|
// Tell the speller the name of the dictionary. This requires unicode conversion.
|
|
MultiByteToWideChar(CP_ACP, 0, rgchLex, -1, rgchLexW, ARRAYSIZE(rgchLexW)-1);
|
|
|
|
// open the main dict
|
|
plxin.pwszLex = rgchLexW;
|
|
plxin.fCreate = FALSE;
|
|
plxin.lxt = lxtMain;
|
|
plxin.lidExpected = langid;
|
|
|
|
memset(&plxout, 0, sizeof(plxout));
|
|
|
|
if (m_pfnSpellerOpenLex(m_pid, &plxin, &plxout) != ptecNoErrors)
|
|
return FALSE;
|
|
|
|
m_rgprflex[0] = plxout.lex;
|
|
m_clex++;
|
|
|
|
return TRUE;
|
|
|
|
// needed by the GetAddr macro -- bite me!!!!!!
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL CSpell::LoadNewSpeller()
|
|
{
|
|
SpellerParams params;
|
|
LANGID langid;
|
|
TCHAR rgchEngine[MAX_PATH];
|
|
int cchEngine = sizeof(rgchEngine) / sizeof(rgchEngine[0]);
|
|
TCHAR rgchLex[MAX_PATH];
|
|
int cchLex = sizeof(rgchLex) / sizeof(rgchLex[0]);
|
|
|
|
langid = WGetLangID(m_pParentCmdTarget);
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex))
|
|
{
|
|
if (!LoadOldSpeller())
|
|
{
|
|
langid = GetSystemDefaultLangID();
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex))
|
|
{
|
|
langid = 1033; // bloody cultural imperialists.
|
|
if (!GetNewSpellerEngine(langid, rgchEngine, cchEngine, rgchLex, cchLex))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
Assert(rgchEngine[0]); // should be something in the engine name!
|
|
m_hinstDll = LoadLibrary(rgchEngine);
|
|
if (!m_hinstDll)
|
|
{
|
|
m_pfnSpellerCloseLex = 0;
|
|
m_pfnSpellerTerminate = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
// We are not using csapi3t1.dll, so we should free it
|
|
m_fCSAPI3T1 = FALSE;
|
|
|
|
GetAddr(m_pfnSpellerVersion, PROOFVERSION, "SpellerVersion");
|
|
GetAddr(m_pfnSpellerInit, PROOFINIT, "SpellerInit");
|
|
GetAddr(m_pfnSpellerTerminate, PROOFTERMINATE, "SpellerTerminate");
|
|
GetAddr(m_pfnSpellerSetOptions, PROOFSETOPTIONS,"SpellerSetOptions");
|
|
GetAddr(m_pfnSpellerOpenLex, PROOFOPENLEX, "SpellerOpenLex");
|
|
GetAddr(m_pfnSpellerCloseLex, PROOFCLOSELEX, "SpellerCloseLex");
|
|
GetAddr(m_pfnSpellerCheck, SPELLERCHECK, "SpellerCheck");
|
|
GetAddr(m_pfnSpellerAddUdr, SPELLERADDUDR, "SpellerAddUdr");
|
|
GetAddr(m_pfnSpellerBuiltInUdr, SPELLERBUILTINUDR, "SpellerBuiltinUdr");
|
|
GetAddr(m_pfnSpellerAddChangeUdr, SPELLERADDCHANGEUDR, "SpellerAddChangeUdr");
|
|
|
|
params.versionAPI = PROOFTHISAPIVERSION;
|
|
if (m_pfnSpellerInit(&m_pid, ¶ms) != ptecNoErrors)
|
|
return FALSE;
|
|
if (m_pfnSpellerSetOptions(m_pid, soselBits,
|
|
sobitSuggestFromUserLex | sobitIgnoreAllCaps | sobitIgnoreSingleLetter) != ptecNoErrors)
|
|
return FALSE;
|
|
|
|
m_langid = langid;
|
|
|
|
// Hebrew does not have a main lex
|
|
if ((langid != lidHebrew) || !m_fCSAPI3T1)
|
|
{
|
|
PROOFLEXIN plxin;
|
|
PROOFLEXOUT plxout;
|
|
WCHAR rgchLexW[MAX_PATH]={0};
|
|
|
|
// Tell the speller the name of the dictionary. This requires unicode conversion.
|
|
MultiByteToWideChar(CP_ACP, 0, rgchLex, -1, rgchLexW, ARRAYSIZE(rgchLexW)-1);
|
|
|
|
// open the main dict
|
|
plxin.pwszLex = rgchLexW;
|
|
plxin.fCreate = FALSE;
|
|
plxin.lxt = lxtMain;
|
|
plxin.lidExpected = langid;
|
|
|
|
memset(&plxout, 0, sizeof(plxout));
|
|
|
|
if (m_pfnSpellerOpenLex(m_pid, &plxin, &plxout) != ptecNoErrors)
|
|
return FALSE;
|
|
|
|
m_rgprflex[0] = plxout.lex;
|
|
m_clex++;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
// needed by the GetAddr macro -- bite me!!!!!!
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL EnumUserDictCallback(DWORD_PTR dwCookie, LPTSTR lpszDict)
|
|
{
|
|
CSpell *pSpell = (CSpell*)dwCookie;
|
|
|
|
Assert(pSpell);
|
|
return pSpell->OpenUserDictionary(lpszDict);
|
|
}
|
|
|
|
BOOL CSpell::OpenUserDictionary(LPTSTR lpszDict)
|
|
{
|
|
PROOFLEXIN plxin;
|
|
PROOFLEXOUT plxout;
|
|
WCHAR rgchUserDictW[MAX_PATH]={0};
|
|
|
|
// make sure our directory exists
|
|
{
|
|
TCHAR rgchDictDir[MAX_PATH];
|
|
|
|
StrCpyN(rgchDictDir, lpszDict, ARRAYSIZE(rgchDictDir));
|
|
|
|
PathRemoveFileSpec(rgchDictDir);
|
|
OpenDirectory(rgchDictDir);
|
|
}
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, lpszDict, -1, rgchUserDictW, ARRAYSIZE(rgchUserDictW)-1);
|
|
|
|
plxin.pwszLex = rgchUserDictW;
|
|
plxin.fCreate = TRUE;
|
|
plxin.lxt = lxtUser;
|
|
plxin.lidExpected = m_langid;
|
|
|
|
memset(&plxout, 0, sizeof(plxout));
|
|
|
|
if ( m_pfnSpellerOpenLex(m_pid, &plxin, &plxout) != ptecNoErrors)
|
|
return TRUE;
|
|
|
|
m_rgprflex[m_clex++] = plxout.lex;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CSpell::OpenUserDictionaries()
|
|
{
|
|
// now open the user dicts
|
|
EnumUserDictionaries((DWORD_PTR)this, EnumUserDictCallback);
|
|
|
|
// if only one dict open then we need to create default user dict
|
|
if (m_clex == 1)
|
|
{
|
|
PROOFLEXIN plxin;
|
|
PROOFLEXOUT plxout;
|
|
TCHAR rgchUserDict[MAX_PATH]={0};
|
|
|
|
if (GetDefaultUserDictionary(rgchUserDict, ARRAYSIZE(rgchUserDict)))
|
|
{
|
|
WCHAR rgchUserDictW[MAX_PATH];
|
|
|
|
// make sure our directory exists
|
|
{
|
|
TCHAR rgchDictDir[MAX_PATH];
|
|
|
|
StrCpyN(rgchDictDir, rgchUserDict, ARRAYSIZE(rgchDictDir));
|
|
|
|
PathRemoveFileSpec(rgchDictDir);
|
|
OpenDirectory(rgchDictDir);
|
|
}
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, rgchUserDict, -1, rgchUserDictW, ARRAYSIZE(rgchUserDictW)-1);
|
|
|
|
plxin.pwszLex = rgchUserDictW;
|
|
plxin.fCreate = TRUE;
|
|
plxin.lxt = lxtUser;
|
|
plxin.lidExpected = m_langid;
|
|
|
|
memset(&plxout, 0, sizeof(plxout));
|
|
|
|
if (m_pfnSpellerOpenLex(m_pid, &plxin, &plxout) != ptecNoErrors)
|
|
return TRUE;
|
|
|
|
m_rgprflex[m_clex++] = plxout.lex;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID CSpell::CloseSpeller()
|
|
{
|
|
SafeRelease(m_pDoc);
|
|
SafeRelease(m_pParentCmdTarget);
|
|
|
|
if (m_pfnSpellerCloseLex)
|
|
{
|
|
for(int i=0; i<cchMaxDicts; i++)
|
|
{
|
|
if (m_rgprflex[i])
|
|
{
|
|
m_pfnSpellerCloseLex(m_pid, m_rgprflex[i], TRUE);
|
|
m_rgprflex[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_pfnSpellerTerminate)
|
|
m_pfnSpellerTerminate(m_pid, TRUE);
|
|
|
|
m_pid = 0;
|
|
|
|
m_pfnSpellerVersion = 0;
|
|
m_pfnSpellerInit = 0;
|
|
m_pfnSpellerTerminate = 0;
|
|
m_pfnSpellerSetOptions = 0;
|
|
m_pfnSpellerOpenLex = 0;
|
|
m_pfnSpellerCloseLex = 0;
|
|
m_pfnSpellerCheck = 0;
|
|
m_pfnSpellerAddUdr = 0;
|
|
m_pfnSpellerAddChangeUdr= 0;
|
|
m_pfnSpellerBuiltInUdr = 0;
|
|
|
|
// As long as we are not using the global CSAPI3T1.DLL, free it
|
|
if (m_hinstDll && !m_fCSAPI3T1)
|
|
{
|
|
FreeLibrary(m_hinstDll);
|
|
m_hinstDll = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL CSpell::GetNewSpellerEngine(LANGID lgid, TCHAR *rgchEngine, DWORD cchEngine, TCHAR *rgchLex, DWORD cchLex)
|
|
{
|
|
DWORD er;
|
|
LPCSTR rgpszDictionaryTypes[] = {"Normal", "Consise", "Complete"};
|
|
int cDictTypes = sizeof(rgpszDictionaryTypes) / sizeof(LPCSTR);
|
|
int i;
|
|
TCHAR rgchQual[MAX_PATH];
|
|
DWORD cch;
|
|
|
|
if (rgchEngine == NULL || rgchLex == NULL)
|
|
return FALSE;
|
|
|
|
*rgchEngine = 0;
|
|
*rgchLex = 0;
|
|
|
|
wnsprintf(rgchQual, ARRAYSIZE(rgchQual), "%d\\Normal", lgid);
|
|
cch = cchEngine;
|
|
|
|
#ifdef DEBUG
|
|
er = MsiProvideQualifiedComponent(SPELLER_DEBUG_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchEngine, &cch);
|
|
if (er != ERROR_SUCCESS)
|
|
{
|
|
cch = cchEngine;
|
|
er = MsiProvideQualifiedComponent(SPELLER_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchEngine, &cch);
|
|
}
|
|
#else
|
|
er = MsiProvideQualifiedComponent(SPELLER_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchEngine, &cch);
|
|
#endif
|
|
|
|
if (er != ERROR_SUCCESS)
|
|
return FALSE;
|
|
|
|
bool fFound = FALSE;
|
|
|
|
// Hebrew does not have a lex
|
|
if ((lgid != lidHebrew) || !m_fCSAPI3T1)
|
|
{
|
|
for (i = 0; i < cDictTypes; i++)
|
|
{
|
|
wnsprintf(rgchQual, ARRAYSIZE(rgchQual), "%d\\%s", lgid, rgpszDictionaryTypes[i]);
|
|
cch = cchLex;
|
|
|
|
#ifdef DEBUG
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_DEBUG_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchLex, &cch);
|
|
if (er != ERROR_SUCCESS)
|
|
{
|
|
cch = cchLex;
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchLex, &cch);
|
|
}
|
|
#else // DEBUG
|
|
er = MsiProvideQualifiedComponent(DICTIONARY_GUID, rgchQual, INSTALLMODE_DEFAULT, rgchLex, &cch);
|
|
#endif // DEBUG
|
|
|
|
if (ERROR_SUCCESS == er)
|
|
{
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return fFound;
|
|
}
|
|
|
|
BOOL GetDefaultUserDictionary(TCHAR *rgchUserDict, int cchBuff)
|
|
{
|
|
DWORD dwType;
|
|
DWORD cbUserDict;
|
|
HKEY hkey = NULL;
|
|
BOOL fFound = FALSE;
|
|
LPTSTR pszEnd;
|
|
|
|
if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegSharedTools, 0, KEY_QUERY_VALUE, &hkey))
|
|
{
|
|
cbUserDict = cchBuff * sizeof(rgchUserDict[0]);
|
|
|
|
if (SHQueryValueEx(hkey, c_szRegSharedToolsPath, 0L, &dwType, rgchUserDict, &cbUserDict) == ERROR_SUCCESS)
|
|
{
|
|
pszEnd = PathAddBackslash(rgchUserDict);
|
|
if (pszEnd)
|
|
{
|
|
StrCpyN(pszEnd, c_szRegDefCustomDict, cchBuff - (DWORD)(pszEnd - rgchUserDict));
|
|
|
|
fFound = TRUE;
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hkey);
|
|
}
|
|
|
|
// if we where able to create a path to the user dict store it in the regdb
|
|
if (fFound)
|
|
{
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
|
|
StrCpyN(rgchBuf, c_szRegSpellProfile, ARRAYSIZE(rgchBuf));
|
|
StrCatBuff(rgchBuf, c_szRegSpellKeyCustom, ARRAYSIZE(rgchBuf));
|
|
|
|
if(RegCreateKeyEx(HKEY_LOCAL_MACHINE, rgchBuf, 0, rgchBuf, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &hkey, NULL) == ERROR_SUCCESS)
|
|
{
|
|
RegSetValueEx(hkey, c_szRegSpellPathDict, 0, REG_SZ, (BYTE *)rgchUserDict, (lstrlen(rgchUserDict) + 1) * sizeof(TCHAR));
|
|
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
|
|
return fFound;
|
|
}
|
|
|
|
VOID CSpell::DeInitRanges()
|
|
{
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
if(m_pRangeSel)
|
|
m_pRangeSel->select();
|
|
|
|
SafeRelease(m_pRangeDocStartSelStart);
|
|
SafeRelease(m_pRangeSel);
|
|
SafeRelease(m_pRangeSelExpand);
|
|
SafeRelease(m_pRangeSelEndDocEnd);
|
|
SafeRelease(m_pRangeChecking);
|
|
SafeRelease(m_pRangeUndoSave);
|
|
SafeRelease(m_pBodyElem);
|
|
SafeRelease(m_pMarkup);
|
|
m_hwndDlg = NULL;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::HrInitRanges(IHTMLTxtRange *pRangeIgnore, HWND hwndMain, BOOL fSuppressDoneMsg)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
IDispatch* pID=0;
|
|
VARIANT_BOOL fSuccess;
|
|
IHTMLTxtRange* pRangeDoc = NULL;
|
|
IHTMLSelectionObject* pSel = NULL;
|
|
BSTR bstr = NULL;
|
|
IMarkupPointer *pRangeStart = NULL;
|
|
IMarkupPointer *pRangeEnd = NULL;
|
|
IMarkupPointer *pRangeTemp = NULL;
|
|
MARKUP_CONTEXT_TYPE markupContext;
|
|
long cch;
|
|
OLECHAR chText[64];
|
|
BOOL fResult;
|
|
|
|
Assert(m_pDoc);
|
|
|
|
m_hwndNote = hwndMain;
|
|
m_fShowDoneMsg = !fSuppressDoneMsg;
|
|
|
|
m_pRangeIgnore = pRangeIgnore;
|
|
|
|
hr = _EnsureInited();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
m_pBodyElem->createTextRange(&pRangeDoc);
|
|
if(!pRangeDoc)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
m_pDoc->get_selection(&pSel);
|
|
if(!pSel)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
pSel->createRange(&pID);
|
|
if(!pID)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
pID->QueryInterface(IID_IHTMLTxtRange, (LPVOID *)&m_pRangeSel);
|
|
if(!m_pRangeSel)
|
|
{
|
|
// if the selection is on an image or something rather than text, it fails.
|
|
// So we just start spellchecking from the beginning.
|
|
pRangeDoc->duplicate(&m_pRangeSel);
|
|
if(!m_pRangeSel)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
hr = m_pRangeSel->collapse(VARIANT_TRUE);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
Assert(m_pRangeSel);
|
|
m_pRangeSel->duplicate(&m_pRangeSelExpand);
|
|
if(!m_pRangeSelExpand)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
hr = m_pRangeSelExpand->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeSel->get_text(&bstr);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
if(!bstr || lstrlenW(bstr) == 0)
|
|
{
|
|
m_State = SELENDDOCEND;
|
|
hr = m_pRangeSelExpand->collapse(VARIANT_TRUE);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
}
|
|
else
|
|
m_State = SEL;
|
|
|
|
// make sure we backup over any abbreviations
|
|
// it would be nice if Trident could do this!
|
|
{
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->CreateMarkupPointer(&pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->MovePointersToRange(m_pRangeSelExpand, pRangeStart, pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// first check to see if we have a character to the right or a '.'
|
|
// if not it's not an abbreviation
|
|
{
|
|
hr = pRangeTemp->MoveToPointer(pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto noAbbreviation;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
WORD wType;
|
|
|
|
wType = GetWCharType(chText[0]);
|
|
if ((C1_SPACE & wType) || ((C1_PUNCT & wType) && chText[0] != L'.'))
|
|
goto noAbbreviation;
|
|
}
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Right(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
hr = HrHasWhitespace(pRangeStart, pRangeTemp, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
goto noAbbreviation;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now look for a period
|
|
{
|
|
processNextWord:
|
|
hr = pRangeEnd->MoveToPointer(pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pRangeTemp->MoveToPointer(pRangeStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
while(TRUE)
|
|
{
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(FALSE, &markupContext, NULL, &cch, chText);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_None)
|
|
goto finishedAbbreviation;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text)
|
|
{
|
|
WORD wType;
|
|
|
|
wType = GetWCharType(chText[0]);
|
|
if ((C1_SPACE & wType) || ((C1_PUNCT & wType) && chText[0] != L'.'))
|
|
goto finishedAbbreviation;
|
|
}
|
|
|
|
cch = 1;
|
|
hr = pRangeTemp->Left(TRUE, NULL, NULL, &cch, NULL);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (markupContext == CONTEXT_TYPE_Text && chText[0] == L'.')
|
|
{
|
|
hr = pRangeTemp->MoveUnit(MOVEUNIT_PREVWORDBEGIN);
|
|
if (FAILED(hr))
|
|
goto finishedAbbreviation;
|
|
|
|
hr = HrHasWhitespace(pRangeTemp, pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fResult)
|
|
goto finishedAbbreviation;
|
|
|
|
pRangeStart->MoveToPointer(pRangeTemp);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
goto processNextWord;
|
|
}
|
|
}
|
|
}
|
|
|
|
finishedAbbreviation:
|
|
hr = m_pMarkup->MovePointersToRange(m_pRangeSelExpand, pRangeTemp, pRangeEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
// check to see if we had a selection
|
|
// if not be sure to set new selection correctly
|
|
hr = pRangeTemp->IsEqualTo(pRangeEnd, &fResult);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pMarkup->MoveRangeToPointers(pRangeStart, fResult ? pRangeStart : pRangeEnd, m_pRangeSelExpand);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
noAbbreviation:
|
|
;
|
|
}
|
|
|
|
m_pBodyElem->createTextRange(&m_pRangeSelEndDocEnd);
|
|
if(!m_pRangeSelEndDocEnd)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
m_pRangeSelEndDocEnd->duplicate(&m_pRangeDocStartSelStart);
|
|
if(!m_pRangeDocStartSelStart)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
hr = m_pRangeSelEndDocEnd->setEndPoint((BSTR)c_bstr_StartToEnd, m_pRangeSelExpand);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeSelEndDocEnd->setEndPoint((BSTR)c_bstr_EndToEnd, pRangeDoc);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeDocStartSelStart->setEndPoint((BSTR)c_bstr_StartToStart, pRangeDoc);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeDocStartSelStart->setEndPoint((BSTR)c_bstr_EndToStart, m_pRangeSelExpand);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
ReleaseObj(pRangeDoc);
|
|
ReleaseObj(pID);
|
|
ReleaseObj(pSel);
|
|
SafeSysFreeString(bstr);
|
|
|
|
SafeRelease(pRangeStart);
|
|
SafeRelease(pRangeEnd);
|
|
SafeRelease(pRangeTemp);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::HrReplaceSel(LPSTR szWord)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
BSTR bstrGet=0, bstrPut=0;
|
|
INT cch;
|
|
TCHAR szBuf[cchEditBufferMax]={0};
|
|
UINT uCodePage;
|
|
LPSTR psz;
|
|
BOOL fSquiggle=FALSE;
|
|
LONG cb = 0;
|
|
|
|
if(!m_pRangeChecking || szWord==NULL)
|
|
return E_INVALIDARG;
|
|
|
|
|
|
if (*szWord == 0)
|
|
{
|
|
hr = m_pRangeChecking->moveStart((BSTR)c_bstr_Character, -1, &cb);
|
|
//If we failed to movestart, we just delete the word.
|
|
hr = m_pRangeChecking->put_text(L"");
|
|
goto error;
|
|
}
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
if (HrHasSquiggle(m_pRangeChecking)==S_OK)
|
|
fSquiggle = TRUE;
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
hr = m_pRangeChecking->get_text(&bstrGet);
|
|
if(!bstrGet || lstrlenW(bstrGet)==0)
|
|
goto error;
|
|
|
|
uCodePage = GetCodePage();
|
|
|
|
cch = SysStringLen(bstrGet);
|
|
if (!WideCharToMultiByte(uCodePage, 0, bstrGet, -1, szBuf, sizeof(szBuf), NULL, NULL))
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
psz = StrChr(szBuf, ' ');
|
|
if(psz)
|
|
{
|
|
TCHAR szPut[cchEditBufferMax]={0};
|
|
wnsprintf(szPut, ARRAYSIZE(szPut), c_szFmt, szWord, psz);
|
|
hr = HrLPSZToBSTR(szPut, &bstrPut);
|
|
}
|
|
else
|
|
hr = HrLPSZToBSTR(szWord, &bstrPut);
|
|
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (!fSquiggle)
|
|
hr = m_pRangeChecking->put_text(bstrPut);
|
|
else
|
|
hr = m_pRangeChecking->pasteHTML(bstrPut);
|
|
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
if (SUCCEEDED(hr))
|
|
hr = HrUpdateSelection();
|
|
|
|
SysFreeString(bstrGet);
|
|
SysFreeString(bstrPut);
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::GetSelection(IHTMLTxtRange **ppRange)
|
|
{
|
|
IHTMLSelectionObject* pSel = NULL;
|
|
IHTMLTxtRange *pTxtRange=0;
|
|
IDispatch *pID=0;
|
|
HRESULT hr=E_FAIL;
|
|
|
|
if (ppRange == NULL)
|
|
return TraceResult(E_INVALIDARG);
|
|
|
|
*ppRange = NULL;
|
|
|
|
if(m_pDoc)
|
|
{
|
|
m_pDoc->get_selection(&pSel);
|
|
if (pSel)
|
|
{
|
|
pSel->createRange(&pID);
|
|
if (pID)
|
|
{
|
|
hr = pID->QueryInterface(IID_IHTMLTxtRange, (LPVOID *)ppRange);
|
|
pID->Release();
|
|
}
|
|
pSel->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
HRESULT CSpell::HrRegisterKeyPressNotify(BOOL fRegister)
|
|
{
|
|
IConnectionPointContainer * pCPContainer=0;
|
|
IConnectionPoint * pCP=0;
|
|
HRESULT hr;
|
|
|
|
Assert(m_pDoc)
|
|
|
|
hr = m_pDoc->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&pCPContainer);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = pCPContainer->FindConnectionPoint(DIID_HTMLDocumentEvents, &pCP);
|
|
pCPContainer->Release();
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
if (fRegister)
|
|
{
|
|
Assert(0==m_dwCookieNotify);
|
|
hr = pCP->Advise(this, &m_dwCookieNotify);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
if (m_dwCookieNotify)
|
|
{
|
|
hr = pCP->Unadvise(m_dwCookieNotify);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
}
|
|
error:
|
|
ReleaseObj(pCP);
|
|
return hr;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
HRESULT CSpell::OnWMCommand(int id, IHTMLTxtRange *pTxtRange)
|
|
{
|
|
switch (id)
|
|
{
|
|
case idmSuggest0:
|
|
case idmSuggest1:
|
|
case idmSuggest2:
|
|
case idmSuggest3:
|
|
case idmSuggest4:
|
|
HrReplaceBySuggest(pTxtRange, id-idmSuggest0);
|
|
break;
|
|
case idmIgnore:
|
|
case idmIgnoreAll:
|
|
case idmAdd:
|
|
#ifdef BACKGROUNDSPELL
|
|
HrDeleteSquiggle(pTxtRange);
|
|
#endif // BACKGROUNDSPELL
|
|
break;
|
|
default:
|
|
return S_FALSE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT CSpell::HrUpdateSelection()
|
|
{
|
|
HRESULT hr;
|
|
VARIANT_BOOL fSuccess;
|
|
|
|
SafeRelease(m_pRangeSel);
|
|
m_pRangeSelEndDocEnd->duplicate(&m_pRangeSel);
|
|
if (!m_pRangeSel)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
hr = m_pRangeSel->setEndPoint((BSTR)c_bstr_EndToStart, m_pRangeSelEndDocEnd);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
hr = m_pRangeSel->setEndPoint((BSTR)c_bstr_StartToEnd, m_pRangeDocStartSelStart);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
|
|
SafeRelease(m_pRangeSelExpand);
|
|
m_pRangeSel->duplicate(&m_pRangeSelExpand);
|
|
if(!m_pRangeSelExpand)
|
|
{
|
|
hr = E_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
hr = m_pRangeSelExpand->expand((BSTR)c_bstr_Word, &fSuccess);
|
|
if(FAILED(hr))
|
|
goto error;
|
|
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
BOOL CSpell::FIgnoreNumber()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_IGNORENUMBER);
|
|
}
|
|
|
|
BOOL CSpell::FIgnoreUpper()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_IGNOREUPPER);
|
|
}
|
|
|
|
BOOL CSpell::FIgnoreDBCS()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_IGNOREDBCS);
|
|
}
|
|
|
|
BOOL CSpell::FIgnoreProtect()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_IGNOREPROTECT);
|
|
}
|
|
|
|
BOOL CSpell::FAlwaysSuggest()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_ALWAYSSUGGEST);
|
|
}
|
|
|
|
BOOL CSpell::FCheckOnSend()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_CHECKONSEND);
|
|
}
|
|
|
|
BOOL CSpell::FIgnoreURL()
|
|
{
|
|
return (m_dwOpt & MESPELLOPT_IGNOREURL);
|
|
}
|
|
|
|
|
|
UINT CSpell::GetCodePage()
|
|
{
|
|
UINT uCodePage;
|
|
TCHAR szBuf[cchEditBufferMax]={0};
|
|
|
|
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, szBuf, sizeof(szBuf)))
|
|
uCodePage = StrToInt(szBuf);
|
|
else
|
|
uCodePage = CP_ACP;
|
|
|
|
return uCodePage;
|
|
}
|
|
|
|
|
|
void DumpRange(IHTMLTxtRange *pRange)
|
|
{
|
|
#ifdef DEBUG
|
|
BSTR bstrGet=0;
|
|
|
|
if (!pRange)
|
|
return;
|
|
pRange->get_text(&bstrGet);
|
|
SysFreeString(bstrGet);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
BOOL FBadSpellChecker(LPSTR rgchBufDigit)
|
|
{
|
|
TCHAR rgchBufKey[cchMaxPathName];
|
|
TCHAR rgchBuf[cchMaxPathName];
|
|
TCHAR szMdr[cchMaxPathName];
|
|
LPSTR pszSpell;
|
|
|
|
wnsprintf(rgchBufKey, ARRAYSIZE(rgchBufKey), c_szRegSpellKeyDef, rgchBufDigit);
|
|
|
|
if (!GetSpellingPaths(rgchBufKey, rgchBuf, szMdr, sizeof(rgchBuf)/sizeof(TCHAR)))
|
|
return TRUE;
|
|
|
|
pszSpell = PathFindFileNameA(rgchBuf);
|
|
if (!pszSpell)
|
|
return TRUE;
|
|
|
|
if (lstrcmpi(pszSpell, "msspell.dll")==0 ||
|
|
lstrcmpi(pszSpell, "mssp32.dll")==0)
|
|
return TRUE;
|
|
|
|
// [email protected] - check that the dict exists (also check the spell dll
|
|
// for good measure) - 42208
|
|
|
|
// spell dll must exist
|
|
if (!PathFileExists(rgchBuf))
|
|
return TRUE;
|
|
|
|
// main dict must exist
|
|
if (!PathFileExists(szMdr))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#ifdef BACKGROUNDSPELL
|
|
CSpellStack::CSpellStack()
|
|
{
|
|
m_cRef = 1;
|
|
m_sp = -1;
|
|
ZeroMemory(&m_rgStack, sizeof(CCell)*MAX_SPELLSTACK);
|
|
}
|
|
|
|
|
|
CSpellStack::~CSpellStack()
|
|
{
|
|
while (m_sp>=0)
|
|
{
|
|
SafeRelease(m_rgStack[m_sp].pTextRange);
|
|
m_sp--;
|
|
}
|
|
}
|
|
|
|
|
|
ULONG CSpellStack::AddRef()
|
|
{
|
|
return ++m_cRef;
|
|
}
|
|
|
|
|
|
ULONG CSpellStack::Release()
|
|
{
|
|
if (--m_cRef==0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
return m_cRef;
|
|
}
|
|
|
|
HRESULT CSpellStack::HrGetRange(IHTMLTxtRange **ppTxtRange)
|
|
{
|
|
HRESULT hr;
|
|
|
|
Assert(ppTxtRange);
|
|
*ppTxtRange = 0;
|
|
if (m_sp < 0)
|
|
return E_FAIL;
|
|
|
|
*ppTxtRange = m_rgStack[m_sp].pTextRange;
|
|
if (*ppTxtRange)
|
|
(*ppTxtRange)->AddRef();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CSpellStack::push(IHTMLTxtRange *pTxtRange)
|
|
{
|
|
HRESULT hr;
|
|
BSTR bstr=0;
|
|
|
|
Assert(m_sp >= -1 && m_sp <= (MAX_SPELLSTACK-2));
|
|
|
|
if (pTxtRange == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
hr = pTxtRange->get_text(&bstr);
|
|
if (FAILED(hr) || bstr==NULL || *bstr==L'\0' || *bstr==L' ')
|
|
{
|
|
Assert(0);
|
|
goto error;
|
|
}
|
|
|
|
m_sp++;
|
|
m_rgStack[m_sp].pTextRange = pTxtRange;
|
|
pTxtRange->AddRef();
|
|
|
|
error:
|
|
SafeSysFreeString(bstr);
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CSpellStack::pop()
|
|
{
|
|
if (m_sp < 0)
|
|
return NOERROR;
|
|
|
|
Assert(m_sp>=0 && m_sp<=(MAX_SPELLSTACK-1));
|
|
|
|
SafeRelease(m_rgStack[m_sp].pTextRange);
|
|
m_sp--;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
BOOL CSpellStack::fEmpty()
|
|
{
|
|
Assert(m_sp>=-1 && m_sp<=(MAX_SPELLSTACK-1));
|
|
|
|
if (m_sp < 0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
#endif // BACKGROUNDSPELL
|
|
|
|
|
|
|
|
WORD GetWCharType(WCHAR wc)
|
|
{
|
|
BOOL fResult;
|
|
WORD wResult;
|
|
|
|
fResult = GetStringTypeExWrapW(CP_ACP, CT_CTYPE1, &wc, 1, &wResult);
|
|
if (FALSE == fResult)
|
|
return 0;
|
|
else
|
|
return wResult;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: OpenDirectory
|
|
|
|
SYNOPSIS: checks for existence of directory, if it doesn't exist
|
|
it is created
|
|
|
|
********************************************************************/
|
|
HRESULT OpenDirectory(TCHAR *szDir)
|
|
{
|
|
TCHAR *sz, ch;
|
|
HRESULT hr;
|
|
|
|
Assert(szDir != NULL);
|
|
hr = S_OK;
|
|
|
|
if (!CreateDirectory(szDir, NULL) && ERROR_ALREADY_EXISTS != GetLastError())
|
|
{
|
|
Assert(szDir[1] == TEXT(':'));
|
|
Assert(szDir[2] == TEXT('\\'));
|
|
|
|
sz = &szDir[3];
|
|
|
|
while (TRUE)
|
|
{
|
|
while (*sz != 0)
|
|
{
|
|
if (!IsDBCSLeadByte(*sz))
|
|
{
|
|
if (*sz == TEXT('\\'))
|
|
break;
|
|
}
|
|
sz = CharNext(sz);
|
|
}
|
|
ch = *sz;
|
|
*sz = 0;
|
|
if (!CreateDirectory(szDir, NULL))
|
|
{
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS)
|
|
{
|
|
hr = E_FAIL;
|
|
*sz = ch;
|
|
break;
|
|
}
|
|
}
|
|
*sz = ch;
|
|
if (*sz == 0)
|
|
break;
|
|
sz++;
|
|
}
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
HRESULT CSpell::_EnsureInited()
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
if (m_pMarkup == NULL)
|
|
{
|
|
hr = m_pDoc->QueryInterface(IID_IMarkupServices, (LPVOID *)&m_pMarkup);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
if (m_pBodyElem == NULL)
|
|
{
|
|
hr = HrGetBodyElement(m_pDoc, &m_pBodyElem);
|
|
if (FAILED(hr))
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
return hr;
|
|
}
|
|
|