. 캐럿 처리

앞에서 만들었던 Ime1 예제는 한글을 잘 입력받기는 하지만 무조건 입력만 받을 수 있을 뿐 위치를 옮긴다거나 삽입, 삭제가 불가능하다. 다만 조립중인 한글에 대해서만 <BS>키로 음소를 삭제할 수 있는데 이것은 Ime1이 처리하는 것이 아니라 디폴트 IME <BS>키를 특별히 취급하기 때문이다. 이 예제를 확장하여 문장을 자유롭게 편집할 수 있는 에디트 컨트롤을 만들어 보자.

편집 위치를 옮기고 원하는 위치의 문자를 삭제하거나 또는 삽입하려면 먼저 현재 위치가 어디인지를 보여줄 방법이 있어야 한다. 윈도우즈에서는 이런 용도로 캐럿이라는 좋은 장치를 제공하므로 캐럿을 달아 보도록 하자. 다음 단계를 따라 예제의 코드를 조금씩 수정한다.

 

 먼저 프로젝트를 만든다. Ime1 프로젝트를 복사하여 Ime2 프로젝트를 만들기만 하면 된다. 그대로 사본을 떴으므로 imm32.lib를 다시 연결하거나 할 필요는 없다. Ime2 예제를 만든 후 윈도우 클래스 이름만 다음과 같이 변경한다.

 

LPCTSTR lpszClass=TEXT("Ime2");

 

전체적인 완성 코드는 보이지 않고 실습 과정을 보이므로 반드시 프로젝트를 만든 후 실습해보기 바란다. 그래야 어떤 코드에 의해 어떤 기능이 추가되는지 살펴 보고 또한 문제점들을 직접 확인하면서 풀어나갈 수 있다.

 캐럿은 현재 편집중인 위치가 어디인가를 보여주는 것이며 문자의 삽입, 삭제 동작은 항상 현재 캐럿 위치에서 일어난다. 현재 위치가 어디인가는 곧 문서 버퍼인 buf에서의 오프셋으로 표현되는데 이 위치를 기억하기 위해 off 전역변수를 추가하였다.

 

#include <imm.h>

TCHAR *buf;

BOOL bComp;

int off;

int FontHeight;

 

캐럿의 높이는 현재 선택된 폰트의 높이가 되는데 이 값을 기억하기 위해 FontHeight라는 전역변수도 추가하였다. 이 두 변수는 WM_CREATE에서 초기화되는데 off의 초기값은 0이고 FontHeight의 초기값은 폰트 메트릭스로 조사한 폰트 높이이다.

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

    TEXTMETRIC tm;

     ...

     switch(iMessage) {

     case WM_CREATE:

          bComp=FALSE;

          buf=(char *)malloc(65536);

          memset(buf,0,65536);

        hWndMain=hWnd;

        off=0;

        hdc=GetDC(hWnd);

        GetTextMetrics(hdc,&tm);

        FontHeight=tm.tmHeight;

        ReleaseDC(hWnd,hdc);

          return 0;

 

메인 윈도우의 핸들인 hWndMain도 미리 구해놓았다. 통상 WinMain에서 메인 윈도우 생성 후 이 전역변수값을 대입하는데 이 예제의 경우 윈도우가 생성될 때 보내지는 WM_SETFOCUS에서 이 핸들을 참조하므로 미리 대입되어 있어야 한다.

 캐럿의 위치는 문서편집중에 수시로 변경되어야 한다. 그래서 WndProc에서 캐럿의 위치를 일일이 옮겨주는 것보다는 캐럿의 모양과 위치를 변경하는 SetCaret 일반함수를 만들고 필요할 때 이 함수를 호출하도록 하였다. 다음 두 함수를 WndProc 이전에 입력한다.

 

int GetCharWidth(HDC hdc, TCHAR *ch, int len)

{

     SIZE sz;

     GetTextExtentPoint32(hdc, ch, len, &sz);

     return sz.cx;

}

 

void SetCaret()

{

     SIZE sz;

     HDC hdc;

     int toff;

     int caretwidth;

 

     hdc=GetDC(hWndMain);

     if (bComp) {

          toff=off-2;

          caretwidth=GetCharWidth(hdc,buf+toff,2);

     } else {

          toff=off;

          caretwidth=2;

     }

     CreateCaret(hWndMain,NULL,caretwidth,FontHeight);

     ShowCaret(hWndMain);

 

     GetTextExtentPoint32(hdc,buf,toff,&sz);

     SetCaretPos(sz.cx,0);

     ReleaseDC(hWndMain,hdc);

}

 

GetCharWidth 함수는 ch 번지에 있는 길이 len 문자의 폭을 구하는데 가변폭 글꼴의 경우 각 문자마다 폭이 달라질 수 있으므로 캐럿도 현재 위치의 문자폭에 맞게 달라져야 한다. 문자열의 길이를 계산할 때는 GetTextExtentPoint32 함수가 사용된다. 글꼴은 문자마다 폭이 다르지만 높이는 일정하므로 높이는 따로 구하지 않으며 WM_CREATE에서 딱 한 번만 조사하여 FontHeight 전역변수에 대입해두었다.

SetCaret 함수는 현재 상태에 따라 캐럿의 모양과 위치를 결정한다. 캐럿의 모양은 한글을 입력중일 때 사각형 모양이 되며 그 외의 경우는 2픽셀 두께를 가지는데 메모장을 보면 이렇게 되어 있다. 한글 입력중인가 아닌가는 bComp 변수를 참조하면 쉽게 알 수 있으며 이 변수값에 따라 캐럿의 폭과 높이를 결정한다. 캐럿의 높이는 항상 폰트의 높이와 같으며 폭은 2픽셀 또는 현재 위치에 있는 문자의 폭과 같다. 캐럿의 모양이 결정되면 CreateCaret 함수로 캐럿을 만든다. 이 함수는 기존에 만들어져 있는 캐럿을 파괴한 후 새 캐럿을 만듦으로 일부러 DestroyCaret을 호출할 필요는 없다.

 

캐럿의 위치는 off 변수가 가리키는 현재 위치까지의 문자열 폭을 구하면 된다. , 한글 입력중일 때는 현재 위치보다 두 칸 더 앞으로 이동한 위치에 캐럿을 놓아야 한다. 그래야 입력중인 한글 음절이 캐럿으로 감싸진다.

 캐럿은 시스템에 전역적으로 하나밖에 없는 자원이므로 포커스를 받았을 때만 생성하고 그렇지 않을 경우는 파괴해야 한다. 다음 두 메시지를 처리하면 된다.

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     ....

     case WM_SETFOCUS:

          SetCaret();

          return 0;

     case WM_KILLFOCUS:

          DestroyCaret();

          return 0;

 

포커스를 받았을 때 SetCaret 함수를 호출하여 캐럿을 현재 위치에 적당한 모양으로 보이도록 하였으며 포커스를 잃었을 때 DestroyCaret으로 캐럿을 파괴하였다.

 캐럿의 모양과 위치를 변경하는 함수가 제대로 동작하기 위해서는 문자가 입력될 때 off 변수가 현재 위치를 정확하게 가리키도록 관리해야 한다. 영문자가 입력될 때인 WM_CHAR 메시지에 다음 코드를 추가한다.

 

     case WM_CHAR:

          szChar[0]=(BYTE)wParam;

          szChar[1]=0;

          for (i=0;i<LOWORD(lParam);i++) {

               lstrcat(buf,szChar);

            off+=lstrlen(szChar);

          }

          bComp=FALSE;

          InvalidateRect(hWnd,NULL,TRUE);

        SetCaret();

          return 0;

 

입력된 문자 길이만큼 off를 증가시키고 새 off 값으로 캐럿 위치를 갱신하기 위해 SetCaret 함수를 호출하였다. 다음은 한글이 조립될 때 캐럿의 위치를 옮겨보자.

 

     case WM_IME_COMPOSITION:

          if (lParam & GCS_COMPSTR) {

               hImc=ImmGetContext(hWnd);

               len=ImmGetCompositionString(hImc,GCS_COMPSTR,NULL,0);

               szComp=(TCHAR *)malloc(len+1);

               ImmGetCompositionString(hImc,GCS_COMPSTR,szComp,len);

               szComp[len]=0;

               if (bComp) {

               off-=2;

                   buf[lstrlen(buf)-2]=0;

               }

               if (len == 0) {

                   bComp=FALSE;

               } else {

                   bComp=TRUE;

               }

               lstrcat(buf,szComp);

            off+=len;

               ImmReleaseContext(hWnd,hImc);

               free(szComp);

               InvalidateRect(hWnd,NULL,TRUE);

            SetCaret();

          }

          break;

     case WM_IME_CHAR:

          if (IsDBCSLeadByte((BYTE)(wParam >> 8))) {

               szChar[0]=HIBYTE(LOWORD(wParam));

               szChar[1]=LOBYTE(LOWORD(wParam));

               szChar[2]=0;

          } else {

               szChar[0]=(BYTE)wParam;

               szChar[1]=0;

          }

          if (bComp) {

            off-=2;

               buf[lstrlen(buf)-2]=0;

          }

          lstrcat(buf,szChar);

        off+=lstrlen(szChar);

          bComp=FALSE;

          InvalidateRect(hWnd,NULL,TRUE);

        SetCaret();

 

한 음절이 입력된 후 off len만큼 증가시키도록 하였다. , 조립중인 문자를 삭제할 때는 오히려 off 2 감소시켜야 한다. 그래서 한글이 조립될 때는 off 2 감소했다가 다시 2 증가하므로 캐럿이 그 자리에 머물러 있게 되며 음절이 완성되면 한 칸 오른쪽으로 이동한다. 여기까지 코드를 컴파일한 후 예제를 실행해보자.

실행해보면 문자를 입력할 때 현재 위치에 캐럿이 나타날 것이며 가만히 있을 때는 캐럿이 혼자 깜박거리고 있을 것이다. 기능상 더 나아진 것은 없지만 캐럿이 보이니 문자가 삽입될 위치를 확실하게 보여줄 수 있으며 좀 멋있어 보이기도 한다.