Notepad2-mod 한글 IME 패치

2010/03/04 - [컴퓨터야그/notepad2] - notepad2 컴파일 삽질기 8 : 한글 IME 패치


Notepad2-mod에서 한글을 정상적으로 입력하려면 (아는 분들은 다 아시다시피) IME 처리부분을 수정해야 한다.
그런데, 기존에 공개한 패치는 최신 버전의 WDK에서는 정상적으로 컴파일되지 않는다.

사실 크게 수정된 것은 아니지만, 다소 포스팅을 정리할 필요가 있어 다시 포스팅한다.
코드 자체는 크게 바뀐 게 없고, 명확성을 기하기 위한 타입캐스팅이 추가되었다.

수정 대상은 scintilla\win32\ScintillaWin.cxx 하나다.


1. 수정#1: WndProc()

우선 sptr_t ScintillaWin::WndProc()를 찾는다.
이 함수는 하나의 switch-case로 되어있는데, 여기서 case WM_IME_STARTCOMPOSITION:를 찾는다.

이 부분을 아래와 같이 수정한다. 지난 패치와 완전히 동일하다.

  case WM_IME_STARTCOMPOSITION:     // dbcs
      ImeStartComposition();
      // added from here-------------------------------------------------
      if (LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN)) {
        // if the current IME is the Korean IME, do not show the default IME window
        return 0;
      }
      // added to here-------------------------------------------------
      return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);


2. 수정#2: sptr_t ScintillaWin::HandleComposition()

다음으로, sptr_t ScintillaWin::HandleComposition()를 찾아 아래 내용으로 대체한다.
지난 패치에 비해 아주 깨알같이 달라졌다.

 sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
// Digital Mars compiler does not include Imm library
return 0;
#else
static int cs = -1;
static int undo = -1;
static bool comp = false;
static bool bEndOfLine, bOverstrike;
static bool wasSelection = false;
bool bKoreanIME = LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN);

if (bKoreanIME && (lParam & GCS_COMPSTR)) {
HIMC hIMC = ::ImmGetContext(MainHWND());
if (hIMC) {
const int maxLenInputIME = 200;
wchar_t wcs[maxLenInputIME];
LONG bytes = ::ImmGetCompositionStringW(hIMC, GCS_COMPSTR, wcs, (maxLenInputIME-1)*2);
int wides = bytes / 2;

int lastitem = (int)(sel.Count()-1);
int selBegin = (int)(sel.Range(lastitem).End().Position());
int selEnd = selBegin;

if (bytes) {
//comp==false 이면 최초 진입
//undo를 마비시키기 전에 삭제할 글자/블럭 삭제
if (!comp)
{
FilterSelections();

{
UndoGroup ug(pdoc, (sel.Count() > 1) || !sel.Empty() || inOverstrike);
for (size_t r=0; r<sel.Count(); r++) {
if (!RangeContainsProtected(sel.Range(r).Start().Position(),
sel.Range(r).End().Position())) {
selBegin = sel.Range(r).Start().Position();
if (!sel.Range(r).Empty()) {
if (sel.Range(r).Length()) {
pdoc->DeleteChars(selBegin, sel.Range(r).Length());
sel.Range(r).ClearVirtualSpace();
} else {
// Range is all virtual so collapse to start of virtual space
sel.Range(r).MinimizeVirtualSpace();
}
} else if (inOverstrike) {
if (selBegin < pdoc->Length()) {
if (!IsEOLChar(pdoc->CharAt(selBegin))) {
pdoc->DelChar(selBegin);
sel.Range(r).ClearVirtualSpace();
}
}
}
selBegin = InsertSpace(selBegin, sel.Range(r).caret.VirtualSpace());

sel.Range(r).ClearVirtualSpace();
// If in wrap mode rewrap current line so EnsureCaretVisible has accurate information
if (wrapState != eWrapNone) {
AutoSurface surface(this);
if (surface) {
WrapOneLine(surface, pdoc->LineFromPosition(selBegin));
}
}
}
}

}

bOverstrike = inOverstrike;
}

if (cs < 0 && !bOverstrike) {
cs = vs.caretStyle;
vs.caretStyle = CARETSTYLE_BLOCK;
}

if (undo < 0) {
undo = pdoc->IsCollectingUndo()?1:0;
pdoc->SetUndoCollection(false);
}

if (!comp) {
comp = true;
} else {
DelChar();
}
} else {
//조합 중 조합중인 글자를 다 지운 경우
if (cs >= 0) {
vs.caretStyle = cs;
cs = -1;
}

if (comp) {
comp = false;

DelChar();
}

if (undo >= 0) {
pdoc->SetUndoCollection(undo==1);
undo = -1;
}
}

MovePositionTo(selBegin);

inOverstrike = false;
if (IsUnicodeMode()) {
char utfval[maxLenInputIME * 3];
unsigned int len = UTF8Length(wcs, wides);
UTF8FromUTF16(wcs, wides, utfval, len);
utfval[len] = '\0';
AddCharUTF(utfval, len);
} else {
char dbcsval[maxLenInputIME * 2];
int size = ::WideCharToMultiByte(InputCodePage(),
0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
for (int i=0; i<size; i++) {
AddChar(dbcsval[i]);
}
}
inOverstrike = bOverstrike;

MovePositionTo(selBegin);
}
}

if (lParam & GCS_RESULTSTR) {
//앞의 if문 6개는 모두 한글 입력기에서만 동작
//다른 언어 IME에서는 패스
if (comp) {
comp = false;
DelChar();
}

if (cs >= 0) {
vs.caretStyle = cs;
cs = -1;
}
if (undo >= 0){
pdoc->SetUndoCollection(undo==1);
undo = -1;
}

//덮어쓰기에서 한글 조합 중 마지막에 숫자나 기호를 붙인 경우 한 글자 더 삭제(가9)
bool bKoreanPlusOneMore = false;
if (bKoreanIME && bOverstrike)
{
HIMC hIMC = ::ImmGetContext(MainHWND());
if (hIMC) {
const int maxLenInputIME = 200;
wchar_t wcs[maxLenInputIME];
LONG bytes = ::ImmGetCompositionStringW(hIMC,
GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
int wides = bytes / 2;

char dbcsval[maxLenInputIME * 2];
int size = ::WideCharToMultiByte(InputCodePage(),
0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);

bKoreanPlusOneMore = (size==3);
::ImmReleaseContext(MainHWND(), hIMC);
}
}
if (bKoreanPlusOneMore) DelChar();

if (bKoreanIME) inOverstrike = false;

HIMC hIMC = ::ImmGetContext(MainHWND());
if (hIMC) {
const int maxLenInputIME = 200;
wchar_t wcs[maxLenInputIME];
LONG bytes = ::ImmGetCompositionStringW(hIMC,
GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
int wides = bytes / 2;

if (IsUnicodeMode()) {
char utfval[maxLenInputIME * 3];
unsigned int len = UTF8Length(wcs, wides);
UTF8FromUTF16(wcs, wides, utfval, len);
utfval[len] = '\0';
AddCharUTF(utfval, len);
} else {
char dbcsval[maxLenInputIME * 2];
int size = ::WideCharToMultiByte(InputCodePage(),
0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
for (int i=0; i<size; i++)
AddChar(dbcsval[i]);
}

// Set new position after converted
Point pos = LocationFromPosition(sel.Range(0).End().Position());
COMPOSITIONFORM CompForm;
CompForm.dwStyle = CFS_POINT;
CompForm.ptCurrentPos.x = pos.x;
CompForm.ptCurrentPos.y = pos.y;
::ImmSetCompositionWindow(hIMC, &CompForm);

::ImmReleaseContext(MainHWND(), hIMC);
}
if (bKoreanIME) inOverstrike = bOverstrike;

return 0;
}

return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam);
#endif
}


이렇게 수정하면 아래와 같이 정상적으로 한글을 입력할 수 있다.