. 덮어쓰기 모드

덮어쓰기 모드는 캐럿 위치의 문자를 삭제하고 새로 입력한 문자를 그 위에 대체하는 입력 방법이다. 보통은 새로 입력한 문자가 삽입되는 방식으로 편집을 하고 문자열을 대체할 때는 삽입 후 삭제하는 것이 일반적이다. 하지만 많은 내용을 한꺼번에 변경할 때는 덮어쓰기 모드에서 작업하는 것이 편리하다. 덮어쓰기 모드 지원을 위해 전역변수를 선언하고 이 변수를 초기화한다.

 

BOOL bOvr;

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     bOvr=FALSE;

 

     return TRUE;

}

 

디폴트 모드는 삽입 모드로 시작되므로 bOvr변수의 초기값은 FALSE이다. 이 변수의 값은 편집중에 언제든지 Ins키를 눌러 변경할 수 있다. VK_INSERT에서 이 변수를 토글시키도록 코드를 작성한다.

 

     case VK_INSERT:

          if (bShift) {

              SendMessage(hWnd,WM_COMMAND,MAKEWPARAM(IDM_PASTE,0),0);

          } else if (bControl) {

              SendMessage(hWnd,WM_COMMAND,MAKEWPARAM(IDM_COPY,0),0);

        } else {

           bOvr = !bOvr;

           SetCaret();

        }

          return;

 

bOvr변수를 바꾸어 놓으면 이후 편집코드에서 이 변수값에 따라 문자열을 삽입하는 방법이 달라진다. 덮어쓰기 모드에서 캐럿은 문자들 사이에 위치하지 않으며 다음 삭제될 문자를 가리키도록 변경되어야 하므로 SetCaret을 호출하여 캐럿 모양을 변경하도록 하였다. 이 변수값에 영향을 받는 부분은 네 군데가 있다. 우선 영문자를 입력받는 OnChar 함수를 수정하도록 하자.

 

void OnChar(HWND hWnd, TCHAR ch, int cRepeat)

{

    BOOL bPrevSel;

     ....

    bPrevSel=DeleteSelection();

    for (i=0;i<cRepeat;i++) {

        if (bOvr && bPrevSel==FALSE && ch!=‘\r’) {

           if (IsDBCS(off)) {

               if (buf[off] != ‘\r’) {

                   Delete(off,2);

               }

           } else {

               Delete(off,1);

           }

        }

          Insert(off,szChar);

          off+=lstrlen(szChar);

     }

     bComp=FALSE;

     Invalidate(FindParaStart(off-lstrlen(szChar)));

     SetCaret();

}

 

덮어쓰기 모드이면 현재 캐럿 위치의 문자를 먼저 삭제한 후 새로 입력된 문자를 삽입하도록 하였다. 삭제 후 삽입하므로 덮어쓰기를 하는 것과 동일하다. 그러나 덮어 쓴다고 해서 buf의 문자코드를 직접 바로 교체해서는 안되는데 왜냐하면 삭제될 문자와 새로 삽입될 문자의 길이가 항상 같지 않기 때문이다. 영문자가 한글에 의해 덮이거나 반대로 한글이 영문자에 의해 덮일 때 문서 길이에 변화가 생기게 되고 따라서 문서를 재정렬해야 한다. 이런 처리와 문서변경의 일관성을 위해 삭제 후 삽입 동작은 반드시 Delete Insert의 연속 호출이어야 한다.

여기에 몇 가지 조건이 들어가는데 선택영역이 있었을 때는 현재 캐럿 위치의 문자를 삭제하는 것이 아니라 선택영역이 대신 삭제되므로 캐럿 위치의 문자를 삭제할 필요가 없다. 그래서 DeleteSelection 함수의 처리 결과를 bPrevSel로 대입받았다가 이 값이 TRUE이면 설사 덮어쓰기 모드이더라도 평상시와 마찬가지로 삽입만 한다.

개행코드는 덮어쓰기 모드에서 특별하게 관리되는 대상이다. 왜냐하면 덮어쓰기란 입력되어 있는 문자를 대상으로 하는 것이지 문단 구성을 바꾸어서는 안되기 때문이다. 덮어쓰기 모드에서도 엔터 입력은 여전히 삽입과 동일하여 <Enter>키를 치면 문단이 끝나며 다음 줄로 강제 개행되어야 한다. 또한 삭제할 대상이 개행코드라면 삭제하지 말고 계속 삽입하여 줄 뒤에 문자를 연이어 입력하도록 한다. 즉 개행코드를 입력할 때와 현재 캐럿 위치가 개행코드인 경우 모두 예외처리해야 한다.

개행코드와 마찬가지로 또 특수하게 관리되어야 할 코드가 있는데 바로 문서 끝을 나타내는 NULL문자이다. 아무리 덮어쓰기 모드라 하더라도 이 문자는 문서 자체가 아니기 때문에 다른 문자로 바꿀 수 없다. NULL문자가 사라지면 ApiEdit는 문서 끝을 찾지 못하고 운명하게 된다. 그러나 위 코드를 보면 알겠지만 NULL문자에 대해서는 어떠한 예외 처리도 하지 않고 있는데 그래도 NULL문자는 절대로 지워지지 않는다. 왜냐하면 Delete 함수가 삭제될 문자의 범위 점검을 철저하게 하고 있기 때문이다. Delete 함수의 선두에서 삭제할 문자 길이가 0이상인지, 삭제 후에도 NULL문자가 안 지워지는지 항상 감시하고 있다.

OnImeChar 함수도 거의 동일한 방법으로 수정한다. 문자를 삽입하기 전에 캐럿 위치에 있는 문자를 삭제하되 개행코드는 삭제하지 않는다. 이 함수가 호출될 때는 선택영역이 항상 없으며 개행코드가 입력되지 않기 때문에 OnChar 함수보다는 조금 더 간단하다.

 

LRESULT OnImeChar(HWND hWnd, WPARAM wParam, LPARAM lParam)

{

     ....

     if (bComp) {

          off-=2;

          Delete(off,2);

     }

    if (bOvr && bComp==FALSE) {

        if (IsDBCS(off)) {

           if (buf[off] != ‘\r’) {

               Delete(off,2);

           }

        } else {

           Delete(off,1);

        }

    }

     Insert(off,szChar);

     off+=lstrlen(szChar);

     bComp=FALSE;

     Invalidate(FindParaStart(off-lstrlen(szChar)));

     SetCaret();

     return 0;

}

 

덮어쓰기 모드이고 한글조립중이 아니라면 일단 현재 위치의 문자를 삭제한다. 이때도 엔터코드는 삭제하지 않도록 주의해야 한다. OnImeCompostion 함수는 조금 복잡하다.

 

LRESULT OnImeComposition(HWND hWnd, WPARAM wParam, LPARAM lParam)

{

     ....

    BOOL bPrevSel;

    BOOL bNewIns=TRUE;

 

     if (bReadOnly)

          return 0;

 

    bPrevSel=DeleteSelection();

     hImc=ImmGetContext(hWnd);

     if (lParam & GCS_COMPSTR) {

          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;

              Delete(off,2);

           bNewIns=FALSE;

          }

          if (len == 0) {

              bComp=FALSE;

          } else {

              bComp=TRUE;

          }

 

        if (bOvr && bNewIns && bPrevSel==FALSE) {

           if (IsDBCS(off)) {

               if (buf[off] != ‘\r’) {

                   Delete(off,2);

               }

           } else {

               Delete(off,1);

           }

        }

 

          Insert(off,szComp);

          off+=len;

          ImmReleaseContext(hWnd,hImc);

          free(szComp);

          Invalidate(FindParaStart(off-len));

          SetCaret();

     }

 

     return DefWindowProc(hWnd,WM_IME_COMPOSITION,wParam,lParam);

}

 

덮어쓰기 모드일 때 현재 문자를 삭제하되 단, 한글을 조립중일 때는 이미 삭제를 했기 때문에 다시 삭제를 하지 않도록 한다. bNewIns 변수는 새로 문자가 삽입된 것인지 아닌지를 가리키는 변수인데 조립중에 새 음소가 입력되었다면 이 변수가 TRUE가 되고 조립중에 문자가 대체되었으면 초기값인 FALSE를 그대로 가진다.

상기의 세 가지 입력 함수가 덮어쓰기에 대한 처리를 처리하고 있으며 마지막으로 SetCaret에서 덮어쓰기 모드일 때 캐럿의 모양을 변경하면 된다.

 

void SetCaret(BOOL bUpdatePrevX/*=TRUE*/, BOOL bScrollToCaret/*=TRUE*/)

{

     ....

     if (bComp) {

          toff=off-2;

          caretwidth=GetCharWidth(buf+toff,2);

     } else {

          toff=off;

          caretwidth=2;

     }

 

    if (bOvr) {

        if (IsDBCS(toff)) {

           if (buf[toff] == ‘\r’) {

               caretwidth=arChWidth[‘ ‘];

           } else {

               caretwidth=GetCharWidth(buf+off,2);

           }

        } else {

           caretwidth=GetCharWidth(buf+off,1);

        }

    }

     CreateCaret(hWndMain,NULL,caretwidth,FontHeight);

     ShowCaret(hWndMain);

     ....

 

덮어쓰기 모드이면 캐럿의 폭을 캐럿 위치의 문자폭만큼 확장해 이 문자가 다음에 덮어 쓰여질 문자라는 것을 보여준다. 단 개행코드는 DBCS이지만 너무 폭이 넓으면 보기 좋지 않으므로 공백과 같은 폭을 가지도록 했다. 캐럿이 문서 끝에 있을 때는 NULL문자의 폭이 캐럿의 폭이 되는데 PrepareCharWidth 함수에서 이 경우의 처리를 위해 NULL문자의 폭을 공백폭과 같도록 해두었다.