IME 메시지를 처리하도록 수정하는 방법은 CodeWiz님의 방법을 거의 그대로 사용했으며, 티끌만큼 수정했습니다.
그런데도, 굳이 여기에 적은 이유는 아래와 같습니다.
수정과정은 역시 파일 단위로 적겠습니다.
수정 대상 파일은 3개(ScintillaWin.cxx, Editor.h, Editor.cxx)인데, 모두 Scintilla의 파일들입니다.
1. ScintillaWin.cxx
WM_IME_COMPOSITION 메시지를 처리하기 위해 관련된 메쏘드를 수정하거나 추가해야 합니다.
수정할 메쏘드와 추가할 메쏘드는 다음과 같습니다.
HandleComposition()은 아래와 같이 수정합니다.
UpdateSystemCaret()은 아래와 같이 수정합니다.
CreateSystemCaret()은 아래와 같이 수정합니다.
DestroySystemCaret()은 아래와 같이 수정합니다.
HasCaretSizeChanged()은 아래와 같이 수정합니다.
다음, 추가해야할 메쏘드 4개를 ScintillaWin.cxx 파일 맨 마지막에 추가합니다.
마지막으로, ScintillaWin.cxx에서 추가할 메쏘드를 선언해줍니다.
수정할 메쏘드와 추가할 메쏘드는 다음과 같습니다.
수정: HandleComposition(), UpdateSystemCaret(), CreateSystemCaret(), DestroySystemCaret(), HasCaretSizeChanged()
추가: GetCompositionString(), AddImeCompositionString(), MoveCompositionWindow(), GetCaretSize()
추가: GetCompositionString(), AddImeCompositionString(), MoveCompositionWindow(), GetCaretSize()
HandleComposition()은 아래와 같이 수정합니다.
sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
// Digital Mars compiler does not include Imm library
return 0;
#else
if(lParam & GCS_COMPSTR)
{
if(inComposition)
DelCharBack(false);
if(AddImeCompositionString(GCS_COMPSTR) == 0)
inComposition = FALSE;
else
inComposition = TRUE;
}
else if(lParam & GCS_RESULTSTR)
{
if(inComposition)
DelCharBack(false);
AddImeCompositionString(GCS_RESULTSTR);
inComposition = FALSE;
}
MoveCompositionWindow();
UpdateSystemCaret();
return 0;
#endif
}
UpdateSystemCaret()은 아래와 같이 수정합니다.
void ScintillaWin::UpdateSystemCaret()
{
if (hasFocus)
{
if (HasCaretSizeChanged())
{
DestroySystemCaret();
CreateSystemCaret();
}
if(inComposition)
{
Point pos = LocationFromPosition(currentPos-1);
::SetCaretPos(pos.x-sysCaretWidth, pos.y);
}
else
{
Point pos = LocationFromPosition(currentPos);
::SetCaretPos(pos.x, pos.y);
}
}
}
CreateSystemCaret()은 아래와 같이 수정합니다.
BOOL ScintillaWin::CreateSystemCaret()
{
Point cs = GetCaretSize();
sysCaretHeight = cs.y;
sysCaretWidth = cs.x;
BOOL retval = ::CreateCaret(MainHWND()
, NULL
, sysCaretWidth
, sysCaretHeight);
::ShowCaret(MainHWND());
return retval;
}
DestroySystemCaret()은 아래와 같이 수정합니다.
BOOL ScintillaWin::DestroySystemCaret()
{
::HideCaret(MainHWND());
return ::DestroyCaret();
}
HasCaretSizeChanged()은 아래와 같이 수정합니다.
bool ScintillaWin::HasCaretSizeChanged()
{
Point cs = GetCaretSize();
if(cs.x != sysCaretWidth || cs.y != sysCaretHeight)
return true;
return false;
}
다음, 추가해야할 메쏘드 4개를 ScintillaWin.cxx 파일 맨 마지막에 추가합니다.
LONG ScintillaWin::GetCompositionString(DWORD index, LPVOID buf, DWORD len)
{
LONG bytes = 0;
HIMC hIMC = ::ImmGetContext(MainHWND());
if (hIMC)
{
bytes = ::ImmGetCompositionStringW(hIMC, index, buf, len);
::ImmReleaseContext(MainHWND(), hIMC);
}
return bytes;
}
LONG ScintillaWin::AddImeCompositionString(DWORD index)
{
const int maxImeBuf = 200;
wchar_t wcs[maxImeBuf];
LONG bytes = GetCompositionString(index, wcs, (maxImeBuf-1)*2);
if(bytes == 0)
return bytes;
LONG wides = bytes / 2;
if (IsUnicodeMode()) {
char utfval[maxImeBuf * 3];
unsigned int len = UTF8Length(wcs, wides);
UTF8FromUTF16(wcs, wides, utfval, len);
utfval[len] = '\0';
AddCharUTF(utfval, len);
} else {
char dbcsval[maxImeBuf * 2];
int size = ::WideCharToMultiByte(InputCodePage()
, 0
, wcs
, wides
, dbcsval
, sizeof(dbcsval) - 1
, 0
, 0);
AddCharUTF(dbcsval, size);
}
return bytes;
}
BOOL ScintillaWin::MoveCompositionWindow()
{
HIMC hIMC = ::ImmGetContext(MainHWND());
if(!hIMC)
return FALSE;
Point pos = LocationFromPosition(currentPos);
COMPOSITIONFORM CompForm;
CompForm.dwStyle = CFS_POINT;
CompForm.ptCurrentPos.x = pos.x;
CompForm.ptCurrentPos.y = pos.y;
::ImmSetCompositionWindow(hIMC, &CompForm);
::ImmReleaseContext(MainHWND(), hIMC);
return TRUE;
}
Point ScintillaWin::GetCaretSize()
{
Point cs;
if(inComposition)
{
Point end = LocationFromPosition(currentPos);
Point start = LocationFromPosition(currentPos-2);
cs.x = end.x - start.x;
cs.y = vs.lineHeight;
}
else if(inOverstrike)
{
cs.y = vs.lineHeight;
Point end = LocationFromPosition(currentPos+1);
Point start = LocationFromPosition(currentPos);
cs.x = end.x - start.x;
if(cs.x <= 0)
{
cs.x = vs.aveCharWidth;
}
}
else
{
cs.y = vs.lineHeight;
cs.x = vs.caretWidth;
if (cs.x == 0)
cs.x = 1;
}
return cs;
}
마지막으로, ScintillaWin.cxx에서 추가할 메쏘드를 선언해줍니다.
class ScintillaWin :
public ScintillaBase {
public:
LONG GetCompositionString(DWORD index, LPVOID buf, DWORD len);
LONG AddImeCompositionString(DWORD index);
BOOL MoveCompositionWindow();
Point GetCaretSize();
2. Editor.h
IME 조합중인지 여부를 저장하는 변수를 선언합니다.
class Editor : public DocWatcher {
public:
bool inComposition;
3. Editor.cxx
IME 조합여부를 처리하는 부분을 추가합니다.
Editor의 선언부와 AddCharUTF()에 조금의 코드를 추가하면 됩니다.
선언부에서는 아래와 같이 초기화 작업만 해주면 됩니다.
다음, void AddCharUTF(char *s, unsigned int len, bool treatAsDBCS) 메쏘드에서
마지막으로, void Paint(Surface *surfaceWindow, PRectangle rcArea) 메쏘드에서 캐럿을 그려주는 부분을 찾아 주석처리합니다.
아래의
Editor의 선언부와 AddCharUTF()에 조금의 코드를 추가하면 됩니다.
선언부에서는 아래와 같이 초기화 작업만 해주면 됩니다.
Editor::Editor() {
inComposition = false;
다음, void AddCharUTF(char *s, unsigned int len, bool treatAsDBCS) 메쏘드에서
if (inOverstrike && !wasSelection && !RangeContainsProtected(currentPos, currentPos + 1)) {를 찾아서
if (!inComposition && inOverstrike && !wasSelection && !RangeContainsProtected(currentPos, currentPos + 1)) {로 수정하면 됩니다.
마지막으로, void Paint(Surface *surfaceWindow, PRectangle rcArea) 메쏘드에서 캐럿을 그려주는 부분을 찾아 주석처리합니다.
아래의
// Draw the Caret를 찾아서
if (lineDoc == lineCaret) {
// Draw the Caret로 바꿔주면 됩니다.
if (false && lineDoc == lineCaret) {
여기까지 수정된 것만으로 자연스러운 한글 입출력은 가능합니다.
하지만, undo/redo에서 문제가 발생합니다.
(Scintilla/Notepad2의 undo/redo 구조때문입니다)
다음 포스팅에서는 undo/redo를 자연스럽게 수정하는 방법을 소개하겠습니다.
trackback from: 메모장2 (3.0.20)
답글삭제메모장2가 2.1.19에서 3.0.20으로 껑충 버전업한 것을 발견했다.이전 게시물에서도 언급했듯이 메모장2는 Florian Balmer가 공개하는 것 말고도 변종 프로젝트가 상당히 많다. 우연히 공식홈에 링크된 수정버전을 살펴봤는데 몇가지 유용한 패치가 되어있길래 소개한다.이번 버전의 한글화를 하면서 살펴보니 이전버전에 비해 달라진 점이 참 적었다. 그런데 왜 버전넘버는 1씩이나 올라간거냐-_- (누가 IME 패치도 좀 해줬으면 좋겠는데...;)...
고생하셨습니당... ^^
답글삭제많은 분들한테 좋은 자료가 될 듯 하네요...
@codewiz - 2008/12/01 11:38
답글삭제슬쩍 업어온 것 같아 고마우면서도 죄송하다능~
도움 감사드립니다. ^^;;;