notepad2 컴파일 삽질기 8 : 한글 IME 패치

by BLUEnLIVE | 2010/03/04 21:32

notepad2가 4.1.24로 업데이트 되었다. 이전 버전인 4.0.23으로 충분히 완벽해졌다고 생각했는데, Florian Balmer 님께선 아직 업뎃이 고픈가...


이전 포스트에서 얘기한 notepad2 4.1.24의 한글 IME 패치방법을 설명하기에 앞서, 몇 가지 작업을 해야한다.


0. 들어가기 전에

다음과 같은 내용은 모두 준비가 되어있다고 가정하고 설명함.

a. Visual C++ v6.0 및 Service Pack 6 설치
b. Platform SDK 설치 (Windows® Server 2003 R2 Platform SDK ISO Download)
c. Scintilla 2.03


1. Visual C++ 6.0을 위한 dsp 파일 생성

notepad2의 소스에는 VC6을 위한 dsp 파일이 없다. VC7을 기준으로 만들어졌기 때문이다.

얼마전까지(정확히는 4.0.22-beta5 까지) notepad2의 기능추가 패치를 제작하시던 Kai Liu님이 notepad2 패치 작업을 중단했는데, Kai Liu 님의 패치 중에 VC6을 위한 dsp 파일 생성이 포함되어 있었다.
따라서, VC6에서 notepad2를 컴파일하려면 dsp 파일을 다시 만들어야 한다.

아래 파일을 다운받아 압축을 푼 뒤에 notepad2 소스의 루트 폴더에 저장한다.



2. Edit.c 버그 수정

EditTitleCase() 함수에서 아래와 같은 부분을 찾는다.
if (!IsCharAlphaNumericW(pszTextW[i]) && !StrChr(L"'?,pszTextW[i])) {

이 부분을 아래와 같이 수정한다.
if (!IsCharAlphaNumericW(pszTextW[i]) && !StrChr(L"'",pszTextW[i])) {


이건 그냥 notepad2의 버그다. 제작자 Balmer 님께 문의를 했는데, 패치할 의지가 없다는 답장을 받았다. 헐~


3. ScintillaWin.cxx 수정 #1

짜잔~ 이제야 한글 IME 패치를 할 차례가 되었다.
수정할 곳은 이전 패치과 같이 두 군데다. 그 중 첫번째는 WndProc().

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);


4. ScintillaWin.cxx 수정 #2

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 = sel.Count()-1;
            int selBegin = 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
}

이렇게 수정하면 아래와 같이 언제나 정상적으로 한글 입력이 가능한 notepad2를 만날 수 있다.

쿠헬헬~