Scintilla IME 패치

@codemaru · April 25, 2007 · 8 min read

IME 패치를할 때 가장 먼저 해야 하는 것은 WM_IME_COMPOSITION 메시지를 처리하는 겁니다. Scintilla의 경우 메시지 핸들러 HandleComposition입니다. 기본적인 처리는 되어 있는데 GCS_COMPSTR을 처리해주지 않아서 조합 과정이 보여지지 않습니다. 간단하게 GCS_COMPSTR을 추가해 주면 됩니다. IME와 관련된 내용은 http://www.winapi.co.kr의 당근 프로젝트 설명 문서를 참고하시면 됩니다. 아래 내용을 Scintilla의 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);  
        UTF8FromUCS2(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;  
}  
 
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  
}
```다음으로 할 일은 커서를 조정하는 일 입니다. Scintilla의 경우 자체적으로 커서를 그리는 부분이 있고 Win32 API를 사용하는 부분도 있습니다. 하지만 실제로는 자체 커서만을 사용합니다. Win32 커서를 다루는 부분이 조금 이상하게 되어 있습니다. 위의 함수들과 마찬가지로 아래 함수들도 ScintillaWin.cxx에 추가해 주면 됩니다. 없는 함수들은 마찬가지로 클래스 선언에 추가해 주세요. 같은 파일에 있습니다. 한글일 때 커서 크기를 크게 해주는 것만 신경쓰면 됩니다.  
소스 보기...

```cpp
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);  
        }  
    }  
}  
 
BOOL ScintillaWin::CreateSystemCaret()   
{  
 
    Point cs = GetCaretSize();  
 
    sysCaretHeight = cs.y;  
    sysCaretWidth = cs.x;  
      
    BOOL retval = ::CreateCaret(MainHWND()  
                                , NULL  
                                , sysCaretWidth  
                                , sysCaretHeight);  
 
    ::ShowCaret(MainHWND());  
    return retval;  
}  
 
BOOL ScintillaWin::DestroySystemCaret()   
{  
    ::HideCaret(MainHWND());  
    return ::DestroyCaret();  
}  
   
bool ScintillaWin::HasCaretSizeChanged()   
{  
    Point cs = GetCaretSize();  
    if(cs.x != sysCaretWidth || cs.y != sysCaretHeight)  
        return true;  
    return false;  
}  
 
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;  
}
```이제는 자잘한 작업만 남았습니다. Editor.h에 다음 변수를 추가하세요. 이 변수를 ScintillaWIn에 추가하지 않는 이유는 아래 나옵니다.  
 
```cpp
bool inComposition;

Editor.cxx의 생성자에 아래 코드를 추가해 주세요.

inComposition = false;

Editor.cxx의 AddCharUTF 부분의 if문을 아래와 같이 변경해 주세요. !inComposition 조건만 추가해 주면 됩니다. 이건 오버라이트 모드와 관련된 부분입니다. 오버라이트 상태가 되면 기존 글을 지우고 그 위치에 새로 입력한 글을 추가해주죠. 그런데 한글 조합중일때는 여러 글자가 추가되기 때문에 조합 과정에서 글자가 계속 지워지는 현상이 발생합니다. 그래서 조합이 아닌 상태일때만 지워주도록 해주는 겁니다.

void Editor::AddCharUTF(char \*s, unsigned int len, bool treatAsDBCS) {  
    bool wasSelection = currentPos != anchor;  
    ClearSelection();  
    bool charReplaceAction = false;  
    if (!inComposition && inOverstrike && !wasSelection ...

이제 마지막 작업만 해주면 됩니다. Editor.cxx에서 Draw the Caret을 찾아서 해당 블록을 주석 처리해 줍니다. 이 부분은 자체적으로 커서를 그리는 부분인데 우리는 API를 사용해서 그리기 때문에 굳이 필요가 없습니다. 적당한 전처리기를 사용해서 윈도우일때만 안그리도록 해주면 되겠습니다.

#if 0  
// Draw the Caret  
if (lineDoc == lineCaret) {  
int offset = Platform::Minimum(posCaret - rangeLine.start, ll->maxLineLength);  
  
// ... 중략 ...  
  
            surface->FillRectangle(rcCaret, vs.caretcolour.allocated);  
        }  
    }  
}  
#enif

아래는 수정된 세 개의 파일입니다.
scintilla_ime.zip

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중