/*
Copyright: 2000
Author: Matthew T Gullett
Email: gullettm@yahoo.com
Name: CFPSSpellingEditCtrl
Part of: Spell Checking Engine
Requires:
DESCRIPTION
----------------------------------------------------------
This class is designed to implement a self spell-checking
edit control. It utilized the FPS Spell Checking Engine
to provide dictionary and suggestion support.
INFO:
----------------------------------------------------------
This class is provided -as is-. No warranty as to the
function or performance of this class is provided either
written or implied.
You may freely use this code and modify it as necessary,
as long as this header is unmodified and credit is given
to the author in the application(s) in which it is
incorporated.
*/
#include "stdafx.h"
#include "FPSSpellChecker.h"
#include "FPSSpellingEditCtrl.h"
#include "FPSSpellCheckEngine.h"
#include "DlgSpellChecker.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// define the maximum # of suggestions to display on popup-menu
#define CFPSSpellingEditCtrl_MAX_SUGGEST 10
// here, I define the COMMAND IDs used in the context menu for
// selecting a desired suggestion.
#define APSTUDIO_INVOKED
#undef APSTUDIO_READONLY_SYMBOLS
#include "resource.h"
#define ID_SPELL_WORD1 _APS_NEXT_COMMAND_VALUE+1
#define ID_SPELL_WORD2 ID_SPELL_WORD1+1
#define ID_SPELL_WORD3 ID_SPELL_WORD1+2
#define ID_SPELL_WORD4 ID_SPELL_WORD1+3
#define ID_SPELL_WORD5 ID_SPELL_WORD1+4
#define ID_SPELL_WORD6 ID_SPELL_WORD1+5
#define ID_SPELL_WORD7 ID_SPELL_WORD1+6
#define ID_SPELL_WORD8 ID_SPELL_WORD1+7
#define ID_SPELL_WORD9 ID_SPELL_WORD1+8
#define ID_SPELL_WORD10 ID_SPELL_WORD1+9
#define ID_SPELL_IGNORE _APS_NEXT_COMMAND_VALUE+21
#define ID_SPELL_IGNORE_ALL _APS_NEXT_COMMAND_VALUE+22
#define ID_SPELL_ADD _APS_NEXT_COMMAND_VALUE+23
#define APSTUDIO_READONLY_SYMBOLS
#undef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
// CFPSSpellingEditCtrl
CFPSSpellingEditCtrl::CFPSSpellingEditCtrl()
{
m_bErrorsDrawn = FALSE;
m_dwLastTick = 0;
m_uiTimerID = 0;
m_pCurrentSel = NULL;
}
CFPSSpellingEditCtrl::~CFPSSpellingEditCtrl()
{
ClearSpellingErrors();
}
CFPSSpellCheckEngine* CFPSSpellingEditCtrl::m_pEngine = NULL;
FPSSPELLEDIT_HOTKEY CFPSSpellingEditCtrl::m_HotKey;
BEGIN_MESSAGE_MAP(CFPSSpellingEditCtrl, CEdit)
//{{AFX_MSG_MAP(CFPSSpellingEditCtrl)
ON_WM_PAINT()
ON_CONTROL_REFLECT(EN_CHANGE, OnChange)
ON_WM_VSCROLL()
ON_WM_HSCROLL()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_DESTROY()
ON_WM_TIMER()
ON_WM_KEYDOWN()
ON_WM_RBUTTONUP()
ON_CONTROL_REFLECT(EN_UPDATE, OnUpdate)
ON_WM_CREATE()
ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
ON_COMMAND_RANGE(ID_SPELL_WORD1, ID_SPELL_WORD10, ReplaceSpellingError)
ON_COMMAND(ID_EDIT_UNDO, Undo)
ON_COMMAND(ID_EDIT_CUT, Cut)
ON_COMMAND(ID_EDIT_COPY, Copy)
ON_COMMAND(ID_EDIT_PASTE, Paste)
ON_COMMAND(ID_EDIT_SELECT_ALL, SelectAll)
ON_COMMAND(ID_SPELL_ADD, OnAddWord)
ON_COMMAND(ID_SPELL_IGNORE, OnIgnoreWord)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CFPSSpellingEditCtrl message handlers
void CFPSSpellingEditCtrl::OnPaint()
{
CEdit::OnPaint();
if (m_bErrorsDrawn)
RedrawSpellingErrors();
}
void CFPSSpellingEditCtrl::OnChange()
{
InvalidateSpellCheck();
}
void CFPSSpellingEditCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if (m_bErrorsDrawn)
{
m_bErrorsDrawn = FALSE;
RedrawWindow();
CEdit::OnVScroll(nSBCode, nPos, pScrollBar);
m_bErrorsDrawn = TRUE;
RedrawSpellingErrors();
}
else
{
CEdit::OnVScroll(nSBCode, nPos, pScrollBar);
}
}
void CFPSSpellingEditCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if (m_bErrorsDrawn)
{
CEdit::OnHScroll(nSBCode, nPos, pScrollBar);
m_bErrorsDrawn = TRUE;
RedrawSpellingErrors();
}
else
{
CEdit::OnHScroll(nSBCode, nPos, pScrollBar);
}
}
void CFPSSpellingEditCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
CEdit::OnLButtonDown(nFlags, point);
if (m_bErrorsDrawn)
RedrawSpellingErrors();
}
void CFPSSpellingEditCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
CEdit::OnLButtonUp(nFlags, point);
if (m_bErrorsDrawn)
RedrawSpellingErrors();
}
void CFPSSpellingEditCtrl::RedrawSpellingErrors()
{
if (IsPaintErrorsOK())
{
CClientDC dc(this);
CString strText;
int iLine = GetFirstVisibleLine();
int iChar = LineIndex(iLine);
int iLineLen = LineLength(iLine);
int iLineCount = GetLineCount();
CRect ClientRect;
BOOL bContinue = TRUE;
ClearSpellingErrors();
GetWindowText(strText);
GetClientRect(ClientRect);
while (bContinue)
{
if (iLineLen > 0 && iChar != -1)
RedrawSpellingErrors(iLine, iLineLen, iChar, strText, dc);
iLine++;
if (iLine > iLineCount)
{
bContinue = FALSE;
}
else
{
iLineLen = LineLength(iLine);
iChar = LineIndex(iLine);
POINT pt = PosFromChar(iChar);
if (pt.y > ClientRect.Height())
bContinue = FALSE;
if (iLine >= iLineCount)
bContinue = FALSE;
}
}
}
}
void CFPSSpellingEditCtrl::RedrawSpellingErrors(int iLine, int iLineLen, int iChar, LPCSTR lpszText, CClientDC& dc)
{
ASSERT(lpszText);
ASSERT(AfxIsValidString(lpszText));
ASSERT(iLine >= 0);
ASSERT(iLineLen >= 0);
ASSERT(iChar >= 0);
CString strWord;
int iLineBegins = -1;
char cThisChar = 0;
int iCharPos = iChar;
// extract words from line
while (lpszText[iCharPos] != 0 && lpszText[iCharPos] != '\r' && lpszText[iCharPos] != '\n')
{
cThisChar = lpszText[iCharPos];
if (IsWordBreak(cThisChar))
{
strWord.TrimLeft();
strWord.TrimRight();
if (strWord != "")
DrawSpellingError(strWord, iLineBegins, dc);
strWord = "";
iLineBegins = -1;
}
else
{
strWord += lpszText[iCharPos];
if (iLineBegins == -1 && strWord != "")
iLineBegins = iCharPos;
}
iCharPos++;
}
if (strWord != "")
{
strWord.TrimLeft();
strWord.TrimRight();
if (strWord != "")
DrawSpellingError(strWord, iLineBegins, dc);
}
}
void CFPSSpellingEditCtrl::DrawSpellingError(LPCSTR lpszWord, int iChar, CClientDC& dc)
{
ASSERT(lpszWord);
ASSERT(AfxIsValidString(lpszWord));
ASSERT(iChar >= 0);
ASSERT(m_pEngine);
CStringList Suggestions;
if (!m_pEngine)
return;
if (m_pEngine->FindWord(lpszWord, Suggestions, FALSE) || (lstrlen(lpszWord) == 0 && IsVowel(lpszWord[0])))
return;
CFont* pFont = GetFont();
CFont* pOldFont = NULL;
CBrush* pNewBrush = NULL;
CBrush* pOldBrush = NULL;
CPen* pNewPen = NULL;
CPen* pOldPen = NULL;
CSize szWord;
int iY = 0;
pOldFont = dc.SelectObject(pFont);
ASSERT(pOldFont);
try
{
pNewBrush = new CBrush;
}
catch(...)
{
pNewBrush = NULL;
}
ASSERT(pNewBrush);
VERIFY(pNewBrush->CreateSolidBrush(RGB(255,0,0)));
try
{
pNewPen = new CPen;
}
catch(...)
{
pNewPen = NULL;
}
ASSERT(pNewPen);
VERIFY(pNewPen->CreatePen(PS_SOLID, 1, RGB(255,0,0)));
szWord = dc.GetTextExtent(lpszWord);
pOldBrush = dc.SelectObject(pNewBrush);
pOldPen = dc.SelectObject(pNewPen);
CPoint pt = PosFromChar(iChar);
iY = pt.y + szWord.cy;
DrawSquigly(dc, pt.x, szWord.cx, iY);
dc.SelectObject(pOldFont);
dc.SelectObject(pOldBrush);
dc.SelectObject(pOldPen);
try
{
delete pNewPen;
}
catch(...)
{
ASSERT(FALSE);
}
pNewPen = NULL;
try
{
delete pNewBrush;
}
catch(...)
{
ASSERT(FALSE);
}
pNewPen = NULL;
// create a new spelling error record
FPSSPELLEDIT_ERRORS* pNewError = NULL;
try
{
pNewError = new FPSSPELLEDIT_ERRORS;
}
catch(...)
{
pNewError = NULL;
}
ASSERT(pNewError);
m_SpellingErrors.AddTail(pNewError);
pNewError->strWord = lpszWord;
pNewError->rctArea.SetRect(pt.x, pt.y, pt.x + szWord.cx, iY);
pNewError->iChar = iChar;
}
void CFPSSpellingEditCtrl::DrawSquigly(CDC &dc, int iLeftX, int iWidth, int iY)
{
int iCurrentY = iY;
int iCurrentX = iLeftX;
CRect ClientRect;
GetClientRect(ClientRect);
while (iCurrentX <= iLeftX + iWidth)
{
dc.MoveTo(iCurrentX, iY);
if (iCurrentX+2 <= iLeftX + iWidth && iY+2 < ClientRect.Width())
dc.LineTo(iCurrentX+2, iY+2);
if (iCurrentX+4 <= iLeftX + iWidth && iY+2 < ClientRect.Width())
dc.LineTo(iCurrentX+4, iY);
iCurrentX += 3;
}
}
void CFPSSpellingEditCtrl::ClearSpellingErrors()
{
FPSSPELLEDIT_ERRORS* pEntry = NULL;
POSITION Pos = NULL;
Pos = m_SpellingErrors.GetHeadPosition();
while (Pos)
{
pEntry = m_SpellingErrors.GetNext(Pos);
ASSERT(pEntry);
try
{
delete pEntry;
}
catch(...)
{
ASSERT(FALSE);
}
pEntry = NULL;
}
m_SpellingErrors.RemoveAll();
}
void CFPSSpellingEditCtrl::OnDestroy()
{
CEdit::OnDestroy();
KillTimer(1);
KillTimer(m_uiTimerID);
}
void CFPSSpellingEditCtrl::OnTimer(UINT nIDEvent)
{
KillTimer(1);
KillTimer(m_uiTimerID);
// if timer event is the spell checker event, check it now
if (nIDEvent == m_uiTimerID)
{
DWORD dwCount = GetTickCount() - m_dwLastTick;
if (dwCount > FPSSPELLEDIT_SPELL_CHECK_WAIT && !m_bErrorsDrawn)
{
RedrawSpellingErrors();
m_bErrorsDrawn = TRUE;
}
}
m_uiTimerID = SetTimer(1, 10, NULL);
CEdit::OnTimer(nIDEvent);
}
void CFPSSpellingEditCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
InvalidateSpellCheck();
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
void CFPSSpellingEditCtrl::InvalidateSpellCheck()
{
if (m_bErrorsDrawn)
{
m_SpellingMatches.RemoveAll();
m_pCurrentSel = NULL;
m_bErrorsDrawn = FALSE;
RedrawWindow();
UpdateWindow();
}
m_dwLastTick = ::GetTickCount();
}
BOOL CFPSSpellingEditCtrl::PreTranslateMessage(MSG* pMsg)
{
ASSERT(pMsg);
// create our timer when appropriate
if (::IsWindow(GetSafeHwnd()) && m_uiTimerID == 0)
m_uiTimerID = SetTimer(1, 10, NULL);
// check for Spell Check Hot Key
if (IsHotKey(pMsg, m_HotKey))
{
CheckSpelling();
return TRUE;
}
return CEdit::PreTranslateMessage(pMsg);
}
FPSSPELLEDIT_ERRORS* CFPSSpellingEditCtrl::FindError(CPoint pt)
{
FPSSPELLEDIT_ERRORS* pEntry;
POSITION Pos;
BOOL bFound = FALSE;
Pos = m_SpellingErrors.GetHeadPosition();
while (Pos && !bFound)
{
pEntry = m_SpellingErrors.GetNext(Pos);
ASSERT(pEntry);
if (pEntry->rctArea.PtInRect(pt))
bFound = TRUE;
}
if (bFound)
return pEntry;
return NULL;
}
void CFPSSpellingEditCtrl::OnRButtonUp(UINT nFlags, CPoint point)
{
ASSERT(m_pEngine);
FPSSPELLEDIT_ERRORS* pError = FindError(point);
if (pError)
{
m_pCurrentSel = pError;
int iPos = 0;
POSITION Pos;
CStringList Matches;
CString strMatch;
HANDLE hClip = ::GetClipboardData(CF_TEXT);
if (m_pEngine)
m_pEngine->FindWord(pError->strWord, Matches, TRUE);
SortMatches(pError->strWord, Matches);
m_SpellingMatches.RemoveAll();
m_SpellingMatches.AddTail(&Matches);
CMenu* pMenu = NULL;
try
{
pMenu = new CMenu;
}
catch(...)
{
pMenu = NULL;
}
ASSERT(pMenu);
VERIFY(pMenu->CreatePopupMenu());
Pos = Matches.GetHeadPosition();
while (Pos && iPos < CFPSSpellingEditCtrl_MAX_SUGGEST)
{
strMatch = Matches.GetNext(Pos);
VERIFY(pMenu->AppendMenu(MF_STRING, ID_SPELL_WORD1+iPos, strMatch));
iPos++;
}
if (Matches.GetCount() == 0)
VERIFY(pMenu->AppendMenu(MF_STRING, 0, "No Suggestions"));
VERIFY(pMenu->AppendMenu(MF_SEPARATOR, 0, ""));
VERIFY(pMenu->AppendMenu(MF_STRING, ID_SPELL_ADD, "Add"));
VERIFY(pMenu->AppendMenu(MF_STRING, ID_SPELL_IGNORE, "Ignore"));
VERIFY(pMenu->AppendMenu(MF_SEPARATOR, 0, ""));
if (CanUndo())
VERIFY(pMenu->AppendMenu(MF_STRING, ID_EDIT_UNDO, "Undo"));
if (IsSelection() || hClip)
{
VERIFY(pMenu->AppendMenu(MF_SEPARATOR, 0, ""));
VERIFY(pMenu->AppendMenu(MF_STRING, ID_EDIT_CUT, "Cut"));
}
if (IsSelection())
VERIFY(pMenu->AppendMenu(MF_STRING, ID_EDIT_COPY, "Copy"));
if (hClip)
VERIFY(pMenu->AppendMenu(MF_STRING, ID_EDIT_PASTE, "Paste"));
VERIFY(pMenu->AppendMenu(MF_SEPARATOR, 0, ""));
VERIFY(pMenu->AppendMenu(MF_STRING, ID_EDIT_SELECT_ALL, "Select All"));
if (!hClip)
::CloseHandle(hClip);
ClientToScreen(&point);
pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON,
point.x, point.y,
this);
try
{
delete pMenu;
}
catch(...)
{
ASSERT(FALSE);
TRACE("CFPSSpellingEditCtrl::OnRButtonUp failed to destroy menu resource\n");
pMenu = FALSE;
}
}
else
{
CEdit::OnRButtonUp(nFlags, point);
}
}
BOOL CFPSSpellingEditCtrl::IsSelection()
{
BOOL bReturn = FALSE;
int iStart = 0;
int iEnd = 0;
GetSel(iStart, iEnd);
if (iStart != iEnd)
bReturn = TRUE;
return bReturn;
}
void CFPSSpellingEditCtrl::ReplaceSpellingError(UINT uid)
{
if (m_pCurrentSel)
{
int iWord = uid - ID_SPELL_WORD1 - 1;
CString strReplaceWord = GetReplaceWord(iWord);
SetSel(m_pCurrentSel->iChar, m_pCurrentSel->iChar + m_pCurrentSel->strWord.GetLength());
ReplaceSel(strReplaceWord, TRUE);
m_bErrorsDrawn = TRUE;
RedrawWindow();
}
}
void CFPSSpellingEditCtrl::SelectAll()
{
CString strText;
GetWindowText(strText);
SetSel(0, strText.GetLength());
m_bErrorsDrawn = FALSE;
RedrawWindow();
m_bErrorsDrawn = TRUE;
RedrawSpellingErrors();
}
CString CFPSSpellingEditCtrl::GetReplaceWord(int iPos)
{
ASSERT(iPos >= 0);
int iCurrent = 0;
POSITION Pos;
CString strTemp;
Pos = m_SpellingMatches.GetHeadPosition();
while (Pos && iCurrent < iPos)
{
strTemp = m_SpellingMatches.GetNext(Pos);
iCurrent++;
}
if (iCurrent == iPos)
return strTemp;
return "";
}
void CFPSSpellingEditCtrl::OnUpdate()
{
InvalidateSpellCheck();
}
int CFPSSpellingEditCtrl::InitSpellingEngine(LPCSTR lpszConfigFile)
{
if (lpszConfigFile)
ASSERT(AfxIsValidString(lpszConfigFile));
int iReturn = FPSSPELLCHECK_ERROR_NONE;
if (m_pEngine)
{
try
{
delete m_pEngine;
}
catch(...)
{
ASSERT(FALSE);
}
}
m_pEngine = NULL;
try
{
m_pEngine = new CFPSSpellCheckEngine;
}
catch(...)
{
iReturn = FPSSPELLCHECK_ERROR_MEMORY;
m_pEngine = NULL;
}
ASSERT(m_pEngine);
if (m_pEngine)
iReturn = m_pEngine->InitEngine(lpszConfigFile);
m_HotKey.bAlt = FALSE;
m_HotKey.bControl = FALSE;
m_HotKey.bShift = FALSE;
m_HotKey.uiKey = VK_F7;
return iReturn;
}
BOOL CFPSSpellingEditCtrl::IsPaintErrorsOK()
{
BOOL bReturn = TRUE;
BYTE bpKeyState[255];
memset(bpKeyState, 0, 255);
::GetKeyboardState(bpKeyState);
for (int iPos = 0; iPos < 255; iPos++)
{
if (HIBYTE(bpKeyState[iPos]) > 0)
bReturn = FALSE;
}
short sL = ::GetAsyncKeyState(VK_LBUTTON);
short sM = ::GetAsyncKeyState(VK_MBUTTON);
short sR = ::GetAsyncKeyState(VK_RBUTTON);
if (HIBYTE(sL) > 0 || HIBYTE(sM) > 0 || HIBYTE(sR) > 0)
bReturn = FALSE;
if (!m_pEngine->GetOptions().CheckWhileTyping())
bReturn = FALSE;
return bReturn;
}
void CFPSSpellingEditCtrl::OnAddWord()
{
if (m_pCurrentSel)
{
ASSERT(m_pEngine);
ASSERT(m_pEngine->GetUserDic());
m_pEngine->GetUserDic()->AddWord(m_pCurrentSel->strWord);
m_bErrorsDrawn = TRUE;
RedrawWindow();
}
}
void CFPSSpellingEditCtrl::OnIgnoreWord()
{
if (m_pCurrentSel)
{
m_pEngine->IgnoreWord(m_pCurrentSel->strWord);
m_bErrorsDrawn = TRUE;
RedrawWindow();
}
}
void CFPSSpellingEditCtrl::SetHotKey(BOOL bShift, BOOL bControl, BOOL bAlt, UINT uiKey)
{
// set the hot key to respond to
m_HotKey.bAlt = bAlt;
m_HotKey.bControl = bControl;
m_HotKey.bShift = bShift;
m_HotKey.uiKey = uiKey;
}
void CFPSSpellingEditCtrl::CheckSpelling()
{
// call the support function from CDlgSpellChecker
CheckSpellingEdit(m_pEngine, this);
}
BOOL CFPSSpellingEditCtrl::IsHotKey(MSG *pMsg, FPSSPELLEDIT_HOTKEY &HotKey)
{
ASSERT(pMsg);
BOOL bReturn = FALSE;
// if WM_KEYDOWN and correct KEY
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == HotKey.uiKey)
{
// check the states of the SHIFT, CONTROL and ALT
// keys
short sShift = HIBYTE(::GetKeyState(VK_SHIFT));
short sControl = HIBYTE(::GetKeyState(VK_CONTROL));
short sAlt = HIBYTE(::GetKeyState(VK_MENU));
BOOL bShift = FALSE;
BOOL bControl = FALSE;
BOOL bAlt = FALSE;
if (sShift == 1 && HotKey.bShift) bShift = TRUE; if (sShift == 0 && !HotKey.bShift) bShift = TRUE;
if (sControl == 1 && HotKey.bControl) bControl = TRUE; if (sControl == 0 && !HotKey.bControl) bControl = TRUE;
if (sAlt == 1 && HotKey.bAlt) bAlt = TRUE; if (sAlt == 0 && !HotKey.bAlt) bAlt = TRUE;
if (bShift && bControl && bAlt)
bReturn = TRUE;
}
return bReturn;
}
void CFPSSpellingEditCtrl::Terminate()
{
if (m_pEngine)
{
ASSERT(m_pEngine->GetUserDic());
m_pEngine->GetUserDic()->Save(m_pEngine->GetOptions().GetUserDic());
try
{
delete m_pEngine;
}
catch(...)
{
ASSERT(FALSE);
TRACE("CFPSSpellingEditCtrl::Terminate() delete m_pEngine failed\n");
m_pEngine = NULL;
}
m_pEngine = NULL;
}
}
void CFPSSpellingEditCtrl::AttachEdit(CWnd *pWnd, UINT uiControlID)
{
ASSERT(!IsWindow(GetSafeHwnd()));
ASSERT(pWnd);
ASSERT(::IsWindow(pWnd->GetSafeHwnd()));
CWnd* pEdit = pWnd->GetDlgItem(uiControlID);
ASSERT(pEdit);
ASSERT(::IsWindow(pEdit->GetSafeHwnd()));
SubclassWindow(pEdit->GetSafeHwnd());
}
BOOL CFPSSpellingEditCtrl::OnEraseBkgnd(CDC* pDC)
{
return CWnd::OnEraseBkgnd(pDC);
}