2025-11-27 16:46:48 +09:00

656 lines
14 KiB
C++

// ==========================================================================
// Class Specification : COXAutoComplete
// ==========================================================================
// Header file : OXAutoComplete.cpp
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
//
// //////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "OXAutoComplete.h"
#include "OXRegistryValFile.h"
#include "UTB64Bit.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#pragma warning(disable : 4355)
//////////////////////////////////////////////////////////////////////
// COXAutoComplete
//////////////////////////////////////////////////////////////////////
COXAutoComplete* COXAutoComplete::m_pThis=NULL;
COXAutoComplete::COXAutoComplete(HWND hParentWnd) :
m_lstBox(this), m_hParent(hParentWnd)
{
m_pThis=this;
m_hkMsg=::SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,NULL,::GetCurrentThreadId());
ASSERT(m_hkMsg);
m_hkKbrd=::SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,NULL,::GetCurrentThreadId());
ASSERT(m_hkKbrd);
m_hAttached=NULL;
m_bUpdate=FALSE;
}
COXAutoComplete::~COXAutoComplete()
{
Detach();
::UnhookWindowsHookEx(m_hkMsg);
::UnhookWindowsHookEx(m_hkKbrd);
if (::IsWindow(m_lstBox.GetSafeHwnd()))
m_lstBox.DestroyWindow();
}
BOOL COXAutoComplete::Attach(CWnd *pWnd, LPCTSTR lpszStorageName, DWORD dwOptions)
{
return Attach(pWnd->GetSafeHwnd(),lpszStorageName, dwOptions);
}
BOOL COXAutoComplete::Attach(HWND hWnd, LPCTSTR lpszStorageName, DWORD dwOptions)
{
ASSERT(hWnd);
if (!m_hkMsg || !m_hkKbrd)
return FALSE;
if (::IsWindow(hWnd))
{
if (!m_hParent)
m_hParent=::GetParent(hWnd);
COXAutoStorage* pStorage;
if (m_mpStorage.Lookup(hWnd,pStorage))
Detach(hWnd);
pStorage=NULL;
//try to find COXAutoStorage
POSITION pos=m_mpStorage.GetStartPosition();
while (pos)
{
HWND hwnd;
COXAutoStorage* pMappedStorage=NULL;
m_mpStorage.GetNextAssoc(pos, hwnd, pMappedStorage);
if (pMappedStorage && pMappedStorage->GetName()==lpszStorageName)
{
pStorage=pMappedStorage;
break;
}
}
if (pStorage)
{
m_mpStorage.SetAt(hWnd,pStorage);
m_mpOptions.SetAt(hWnd,dwOptions);
return TRUE;
}
pStorage=new COXAutoStorage(lpszStorageName);
m_mpStorage.SetAt(hWnd,pStorage);
m_mpOptions.SetAt(hWnd,dwOptions);
return TRUE;
}
return FALSE;
}
LRESULT CALLBACK COXAutoComplete::CallWndProc(int nCode,
WPARAM wParam,
LPARAM lParam )
{
#if defined (_WINDLL)
#if defined (_AFXDLL)
AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif
if (nCode<0)
{
return ::CallNextHookEx(m_pThis->m_hkMsg,nCode,wParam,lParam);
}
CWPSTRUCT* pSt=(CWPSTRUCT*) lParam;
COXAutoStorage* pStorage=NULL;
ASSERT(m_pThis);
if (!m_pThis->m_mpStorage.Lookup(pSt->hwnd,pStorage))
{
return ::CallNextHookEx(m_pThis->m_hkMsg,nCode,wParam,lParam);
}
switch (pSt->message)
{
case WM_SETTEXT:
case WM_PASTE:
case WM_CUT:
{
if (!m_pThis->m_bUpdate)
{
CString sText;
CWnd* pWnd=CWnd::FromHandle(pSt->hwnd);
pWnd->GetWindowText(sText);
m_pThis->OnContentsChange(pWnd->m_hWnd,sText);
}
}
break;
case WM_KILLFOCUS:
m_pThis->Hide();
}
return ::CallNextHookEx(m_pThis->m_hkMsg,nCode,wParam,lParam);
}
void COXAutoComplete::Detach(CWnd *pWnd)
{
Detach(pWnd->GetSafeHwnd());
}
void COXAutoComplete::Detach(HWND hWnd)
{
if (hWnd)
{
COXAutoStorage* pStorage;
if (m_mpStorage.Lookup(hWnd,pStorage))
{
m_mpStorage.RemoveKey(hWnd);
m_mpOptions.RemoveKey(hWnd);
}
else
return;
//try to find out who else is using this storage,
//if no one is using, delete it
COXAutoStorage* pTest=NULL;
POSITION pos=m_mpStorage.GetStartPosition();
while (pos)
{
HWND hwnd;
m_mpStorage.GetNextAssoc(pos,hwnd,pTest);
if (pTest==pStorage)
return;
}
delete pStorage;
return;
}
//detach all storages
POSITION pos=m_mpStorage.GetStartPosition();
//cannot delete directly because the same storage can be mapped
//to different windows
CMap<COXAutoStorage*,COXAutoStorage*,DWORD,DWORD> mpToDelete;
while (pos)
{
HWND hwnd;
COXAutoStorage* pStorage=NULL;
m_mpStorage.GetNextAssoc(pos,hwnd,pStorage);
mpToDelete.SetAt(pStorage,NULL);
}
//delete all objects
pos=mpToDelete.GetStartPosition();
while (pos)
{
COXAutoStorage* pStorage;
DWORD dwNULL;
mpToDelete.GetNextAssoc(pos,pStorage,dwNULL);
delete pStorage;
}
mpToDelete.RemoveAll();
m_mpStorage.RemoveAll();
m_mpOptions.RemoveAll();
}
LRESULT COXAutoComplete::KeyboardProc(int code,
WPARAM wParam, LPARAM lParam)
{
ASSERT(m_pThis);
if (code<0)
return ::CallNextHookEx(m_pThis->m_hkKbrd,code,wParam,lParam);
else
{
CString sText;
CWnd* pWnd=CWnd::GetFocus();
COXAutoStorage* pStorage;
_AFX_THREAD_STATE* pThreadState=AfxGetThreadState();
if(pThreadState->m_hTrackingWindow==NULL && pWnd!=NULL &&
m_pThis->m_mpStorage.Lookup(pWnd->m_hWnd,pStorage))
{
if (lParam<0)//key is pressed
{
switch (wParam)
{
case VK_NEXT:
case VK_PRIOR:
case VK_UP:
case VK_DOWN:
m_pThis->ChangeSel(PtrToInt(wParam));
return TRUE;
break;
default:
if (wParam>VK_HELP)
{
pWnd->GetWindowText(sText);
m_pThis->OnContentsChange(pWnd->m_hWnd,sText);
}
}
}
else
{
switch (wParam)
{
case VK_NEXT:
case VK_PRIOR:
case VK_UP:
case VK_DOWN:
return TRUE;
}
}
}
}
return ::CallNextHookEx(m_pThis->m_hkKbrd,code,wParam,lParam);
}
BOOL COXAutoComplete::OnContentsChange(HWND hwnd, CString sNewText)
{
COXAutoStorage* pStorage;
if (!m_mpStorage.Lookup(hwnd,pStorage))
return FALSE;
DWORD dwOptions;
VERIFY(m_mpOptions.Lookup(hwnd,dwOptions));
if (!dwOptions)
return FALSE;
CStringArray arsStrings;
ASSERT(pStorage);
UINT nCount=pStorage->GetMatchedStrings(sNewText, arsStrings);
if (nCount)
{
CWnd* pWnd=CWnd::FromHandle(hwnd);
if (dwOptions & OX_AUTOCOMPLETE_APPEND)
{
CString sText=arsStrings.GetAt(0);
int nLength=pWnd->GetWindowTextLength();
if (nLength<=sText.GetLength())
{
m_bUpdate=TRUE;
pWnd->SetWindowText(sText);
pWnd->SendMessage(EM_SETSEL,nLength,-1);
m_bUpdate=FALSE;
}
}
if (dwOptions & OX_AUTOCOMPLETE_LIST)
{
m_lstBox.m_bDraw=FALSE;
if (hwnd!=m_hAttached && m_lstBox.GetSafeHwnd())
m_lstBox.DestroyWindow();
//calculate height of the listbox
CDC* pDC=pWnd->GetDC();
SIZE sz;
sz.cx=sz.cy=0;
int nHeight=0;
int n=0;
for (n=0;n<arsStrings.GetSize();n++)
{
CString sText=arsStrings.GetAt(n);
VERIFY(::GetTextExtentPoint32(pDC->m_hDC,
(LPCTSTR) sText, sText.GetLength(),&sz));
if ((sz.cy+nHeight)<(OX_AUTOCOMPLETE_HEIGHTDEFAULTMAX-1))
nHeight+=sz.cy;
else
break;
}
nHeight+=2;
CRect rctWnd, rctWArea;
pWnd->GetWindowRect(&rctWnd);
//calculate vertical position of the listbox
VERIFY(::SystemParametersInfo(SPI_GETWORKAREA,NULL,&rctWArea,NULL));
if (rctWArea.bottom-rctWnd.bottom<nHeight)
{
//on the top of the window
rctWnd.bottom=rctWnd.top;
rctWnd.top-=nHeight;
}
else
{
rctWnd.top=rctWnd.bottom;
rctWnd.bottom+=nHeight;
}
m_lstBox.m_nHeight=sz.cy;
m_lstBox.m_nWidth=rctWnd.Width();
CWnd* pParent=CWnd::FromHandle(m_hParent);
pParent->ScreenToClient(rctWnd);
if (!::IsWindow(m_lstBox.GetSafeHwnd()))
{
VERIFY(m_lstBox.Create(WS_CHILD | WS_BORDER
| LBS_OWNERDRAWFIXED | WS_VSCROLL | LBS_HASSTRINGS,
rctWnd,pParent,
OX_AUTOCOMPLETE_IDC_LIST));
m_lstBox.ModifyStyleEx(0, WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
m_lstBox.SetFont(pWnd->GetFont());
m_hAttached=hwnd;
}
else
{
CRect rctAct;
m_lstBox.GetWindowRect(&rctAct);
if (rctAct!=rctWnd)
m_lstBox.MoveWindow(&rctWnd);
m_lstBox.ResetContent();
}
for (n=0;n<arsStrings.GetSize();n++)
{
m_lstBox.AddString(arsStrings.GetAt(n));
}
if (!m_lstBox.IsWindowVisible())
m_lstBox.ShowWindow(SW_SHOW);
m_lstBox.m_bDraw=TRUE;
m_lstBox.SetWindowPos(&CWnd::wndTopMost,0,0,0,0,SWP_NOMOVE);
}
}
else
if (::IsWindow(m_lstBox.GetSafeHwnd()))
m_lstBox.ShowWindow(SW_HIDE);
return TRUE;
}
void COXAutoComplete::Complete(HWND hWnd)
{
COXAutoStorage* pStorage;
if (!m_mpStorage.Lookup(hWnd,pStorage))
return;
ASSERT(pStorage);
CWnd* pWnd=CWnd::FromHandlePermanent(hWnd);
CString sText;
pWnd->GetWindowText(sText);
if (!sText.IsEmpty())
{
pStorage->AddString(sText);
}
::SendMessage(m_hAttached,EM_SETSEL,(WPARAM)-1,(LPARAM)-1);
}
void COXAutoComplete::Hide()
{
if (::IsWindow(m_lstBox.GetSafeHwnd()))
m_lstBox.ShowWindow(SW_HIDE);
::SendMessage(m_hAttached,EM_SETSEL,(WPARAM)-1,(LPARAM)-1);
}
void COXAutoComplete::ChangeSel(int nKey)
{
if (::IsWindow(m_lstBox.GetSafeHwnd()))
{
int nSel=m_lstBox.GetCurSel();
if (nSel==-1)
{
m_lstBox.SetCurSel(0);
}
else
{
CRect rct;
m_lstBox.GetClientRect(rct);
int nCount=rct.Height()/m_lstBox.GetItemHeight(nSel);
switch (nKey)
{
case VK_UP:
if (nSel>0)
m_lstBox.SetCurSel(nSel-1);
break;
case VK_DOWN:
if (nSel<(m_lstBox.GetCount()-1))
m_lstBox.SetCurSel(nSel+1);
break;
case VK_NEXT:
if ((nSel+nCount)<m_lstBox.GetCount())
m_lstBox.SetCurSel(nSel+nCount-1);
else
m_lstBox.SetCurSel(m_lstBox.GetCount()-1);
break;
case VK_PRIOR:
if ((nSel-nCount+1)>0)
m_lstBox.SetCurSel(nSel-nCount+1);
else
m_lstBox.SetCurSel(0);
}
}
int nNewSel=m_lstBox.GetCurSel();
if (nNewSel!=nSel)
{
int nStartSel;
::SendMessage(m_hAttached,EM_GETSEL, (WPARAM) &nStartSel,NULL);
CString sText;
m_lstBox.GetText(nNewSel,sText);
m_bUpdate=TRUE;
::SendMessage(m_hAttached,WM_SETTEXT,NULL, (LPARAM) (LPCTSTR) sText);
m_bUpdate=FALSE;
::SendMessage(m_hAttached,EM_SETSEL, nStartSel, -1);
}
}
}
void COXAutoComplete::SetDepth(UINT nDepth, HWND hWnd)
{
COXAutoStorage* pStorage=NULL;
if (m_mpStorage.Lookup(hWnd,pStorage))
{
ASSERT(pStorage);
pStorage->SetDepth(nDepth);
}
}
int COXAutoComplete::GetDepth(HWND hWnd)
{
int nRet=-1;
COXAutoStorage* pStorage=NULL;
if (m_mpStorage.Lookup(hWnd,pStorage))
{
ASSERT(pStorage);
nRet=pStorage->GetDepth();
}
return nRet;
}
COXAutoStorage* COXAutoComplete::GetStorage(HWND hWnd)
{
COXAutoStorage* pStorage=NULL;
if (m_mpStorage.Lookup(hWnd,pStorage))
return pStorage;
else
return NULL;
}
//////////////////////////////////////////////////////////////////////
// COXAutoStorage
//////////////////////////////////////////////////////////////////////
COXAutoStorage::COXAutoStorage(LPCTSTR lpszName,UINT nDepth) :
m_nDepth(nDepth)
{
if (lpszName && *lpszName)
m_sName=lpszName;
else
m_sName=OX_AUTOCOMPLETE_NAMEDEFAULT;
VERIFY(Load());
}
COXAutoStorage::~COXAutoStorage()
{
Save();
}
UINT COXAutoStorage::GetMatchedStrings(CString sText, CStringArray& arsStrings)
{
UINT nRslt=0;
for (int n=0; n<m_arsContents.GetSize();n++)
{
if (m_arsContents.GetAt(n).Find(sText)==0)
{
nRslt++;
arsStrings.Add(m_arsContents.GetAt(n));
}
}
return nRslt;
}
BOOL COXAutoStorage::AddString(CString sText)
{
if (sText.IsEmpty())
return FALSE;
ASSERT((UINT) m_arsContents.GetSize()<=m_nDepth);
//try to find the same string in the storage
for (int n=0;n<m_arsContents.GetSize();n++)
{
if (sText==m_arsContents.GetAt(n))
{
m_arsContents.RemoveAt(n);
m_arsContents.InsertAt(0,sText);
return TRUE;
}
}
m_arsContents.InsertAt(0,sText);
if ((UINT) m_arsContents.GetSize()>=m_nDepth)
m_arsContents.RemoveAt(m_nDepth);
return TRUE;
}
BOOL COXAutoStorage::Load()
{
CString sApp=AfxGetAppName();
sApp+=_T("\\Autocomplete");
COXRegistryValFile reg;
long lErr;
if (!reg.Open(HKEY_CURRENT_USER,sApp,m_sName,lErr))
return FALSE;
DWORD dwLength=(DWORD)reg.GetLength();
if (!dwLength)
return TRUE;
BYTE* pBuffer=new BYTE[dwLength+2];
::ZeroMemory(pBuffer,dwLength+2);
if (reg.Read(pBuffer,dwLength)!=dwLength)
{
delete []pBuffer;
reg.Close();
return FALSE;
}
reg.Close();
DWORD dwVersion=*((DWORD*) pBuffer);
if (dwVersion!=OX_AUTOCOMPLETE_VERSION)
{
delete []pBuffer;
return FALSE;
}
TCHAR* pChar=(TCHAR*) (pBuffer+4);
CString sText=pChar;
delete []pBuffer;
int nFind=sText.Find(_T("\r\n"));
while (nFind!=-1)
{
CString sString=sText.Left(nFind);
m_arsContents.Add(sString);
sText=sText.Right(sText.GetLength()-nFind-2);
nFind=sText.Find(_T("\r\n"));
}
while((UINT) m_arsContents.GetSize()>m_nDepth)
m_arsContents.RemoveAt(m_arsContents.GetSize()-1);
return TRUE;
}
BOOL COXAutoStorage::Save()
{
CString sApp=AfxGetAppName();
sApp+=_T("\\Autocomplete");
COXRegistryValFile reg;
long lErr;
if (!reg.Open(HKEY_CURRENT_USER,sApp,m_sName,lErr))
return FALSE;
reg.SetLength(0);
DWORD dwVersion=OX_AUTOCOMPLETE_VERSION;
reg.Write(&dwVersion, 4);
for (int i=0; i<m_arsContents.GetSize(); i++)
{
CString sString=m_arsContents.GetAt(i)+_T("\r\n");
reg.Write(sString, sString.GetLength());
}
reg.Close();
return TRUE;
}
void COXAutoStorage::SetDepth(UINT nDepth)
{
m_nDepth=nDepth;
}
UINT COXAutoStorage::GetDepth()
{
return m_nDepth;
}
void COXAutoComplete::SetParent(HWND hParentWnd)
{
m_hParent=hParentWnd;
}