. 자동개행 지원

여기까지 자동개행 기능의 핵심 함수인 GetLineSub 함수를 작성했다. 줄의 끝을 제대로 찾을 수 있게 되었으므로 이제 거의 다 만든 거나 다름없고 몇몇 관련 함수들만 고치면 된다. 줄 끝을 찾기만 한다고 해서 자동개행이 되는 것이 아니라 다른 함수들도 GetLineSub가 찾아놓은 줄 끝 정보를 잘 이용하도록 수정되어야 한다.

GetLine 함수 수정

먼저 GetLine 함수의 p+=2를 다음 코드로 수정한다.

 

void GetLine(int Line, int &s, int &e)

{

     ....

          if (*p == ‘\r’) {

               p+=2;

          }

 

p+=2는 줄의 끝을 찾은 후에 다음 줄의 처음으로 이동하기 위해 \r\n을 건너 뛰는 코드이다. 그러나 자동개행이 되는 지금은 무조건 두 칸 뒤로 이동해서는 안되면 개행코드에 의해 강제 개행된 경우만 이동해야 한다. 자동개행된 경우는 아직 물리적인 줄이 완전히 끝나지 않았으며 바로 뒤에 문자가 계속 있다.

자동개행된 경우 앞 줄의 끝은 다음 줄의 시작과 같은 위치가 된다. 그래서 이 경우는 p+=2를 실행하지 말아야 한다.

GetRCFromOff 함수 수정

오프셋으로부터 행렬을 찾는 함수도 다음과 같이 수정되어야 한다.

 

void GetRCFromOff(int nPos, int &r, int &c)

{

     int s,e;

     TCHAR *p=buf;

     r=0;

 

     if (nWrap==0) {

          while (p-buf != nPos) {

               if (*p==‘\r’)

                   r++;

               p++;

          }

          GetLine(r,s,e);

     } else {

          for (;;r++) {

               GetLine(r,s,e);

               if (nPos >= s && nPos < e)

                   break;

 

               if (nPos == e) {

                   if (buf[e] == 0 || buf[e] == ‘\r’) {

                        break;

                   }

               }

          }

     }

 

     c=nPos-s;

}

 

자동개행을 하지 않을 경우의 코드는 이전과 동일하며 엔터코드의 개수를 세면 줄번호를 구할 수 있다. 그러나 자동개행 상태에서는 한 줄이 화면상에 여러 줄로 표시될 수 있기 때문에 엔터코드를 세는 것만으로는 줄번호를 구할 수 없다.

GetLine으로 줄의 범위를 검사하고 nPos가 이 범위안에 있을 때까지 계속 줄을 증가시키면 nPos가 몇 번째 줄에 있는지 알 수 있다. GetLine이 자동개행 상태를 고려하여 줄의 범위를 알아서 조사하기 때문이다. , 개행코드와 문서 끝은 GetLine이 줄의 범위에서 제외하므로 따로 검사해야 한다. 이 검사를 생략하면 nPos가 엔터코드일 경우 이 줄은 영원히 못 찾게 될 것이다.

그러나 이 검사를 매번 수행할 필요는 없다. 왜냐하면 개행코드와 문서의 끝은 줄의 끝 위치인 e에서만 나타날 수 있기 때문이다. 줄의 중간에 개행코드가 있다면 그것은 정렬루틴이 잘못되었다는 뜻이다. nPos e인 경우만 검사하면 되며 이 검사로 인한 속도상의 불이익은 거의 없다.

이 함수는 자동개행 상태인 경우와 그렇지 않은 경우의 코드를 따로 실행하고 있다. 사실 자동개행 상태가 아닌 경우라도 자동개행된 경우의 코드로 줄번호를 조사할 수 있다. GetLine이 이미 자동개행 상태를 고려하여 줄 범위를 조사하기 때문이다. 그러나 굳이 이 두 코드를 따로 두는 이유는 실행속도를 조금이라도 향상시키기 위해서이다. 자동개행 상태가 아닌 경우는 굳이 느려터진 GetLine 함수를 부를 필요없이 엔터코드의 개수만 세어 보면 된다.

삭제 코드 수정

<Del>키로 현재 위치의 문자를 삭제할 때 오프셋은 변경되지 않는다. 문자를 삭제해도 오프셋은 여전히 그 자리에 머무르게 되며 따라서 캐럿을 옮길 필요도 없다. 그래서 VK_DELETE 처리코드에서는 SetCaret을 호출하지 않았다. 반면 <BS>키는 앞쪽 문자를 삭제하기 때문에 오프셋이 이전 문자로 이동하게 되고 캐럿도 옮겨주어야 한다.

그러나 자동개행 기능이 들어감으로써 이런 사정이 달라지게 된다. <Del>키를 누를 때 오프셋이 변하지 않는 것은 여전하지만 같은 오프셋이라도 정렬 상태에 따라 캐럿의 위치는 달라질 수 있기 때문이다. 다음 그림을 보자.

 

위에 한 줄이 있고 개행하지 않은 상태에서 abcde를 입력했는데 이 단어가 윗줄의 오른쪽 여백에 들어갈 수 없기 때문에 아래줄로 자동개행되었다. c문자 위치에 캐럿을 두고 <Del>키를 누르면 이 문자가 삭제되면서 abde가 되고 이 단어는 위쪽 여백에 들어갈 수 있게 된다. 이때 캐럿을 옮겨주지 않으면 캐럿이 틀린 위치를 가리키게 된다. 문자열은 정렬 기능에 의해 위로 올라갔는데 캐럿은 문자를 따라 가지 못하는 것이다.

자동개행 기능에 의해 문자 삭제시 정렬이 바뀔 수 있고 따라서 VK_DELETE에서도 캐럿을 옮겨야 한다. <BS>키도 물론 캐럿을 옮겨야 하지만 VK_DELETE에서 캐럿을 옮겨주기 때문에 VK_BACK에서는 SetCaret을 호출할 필요가 없다. VK_BACK에 있는 SetCaret문을 VK_DELETE로 옮겼다.

 

void OnKey(HWND hWnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)

{

     ....

     case VK_DELETE:

          if (IsDBCS(buf,off)) {

               Delete(off, 2);

          } else {

               Delete(off, 1);

          }

          InvalidateRect(hWnd,NULL,TRUE);

        SetCaret();

          return;

     case VK_BACK:

          if (off == 0)

               return;

          off=GetPrevOff(off);

          SendMessage(hWnd,WM_KEYDOWN,VK_DELETE,0);

          return;

 

캐럿 위치 변경

자동개행과 관련된 마지막 작업은 윈도우의 크기를 바꿀 때 캐럿의 위치를 다시 계산하는 것이다. 자동개행 상태에서 윈도우의 폭이 바뀌면 전체적으로 문서가 재정렬될 것이고 따라서 캐럿의 위치도 새로 정렬된 곳으로 옮겨야 한다. OnSize에서 SetCaret 함수만 호출하면 나머지 처리는 SetCaret이 알아서 한다.

 

void OnSize(HWND hWnd, UINT state, int cx, int cy)

{

     if (state != SIZE_MINIMIZED) {

          if (GetFocus()==hWnd) {

               SetCaret();

          }

     }

}

 

, 윈도우가 최소화 될 때는 SetCaret을 부르지 말아야 한다. 왜냐하면 최소화시 윈도우의 폭은 0이 되고 이 상태에서는 어떤 정렬도 실패한다. 폭이 0이므로 단 한문자도 집어 넣을 수 없기 때문이다. 이때는 그냥 아무 것도 않고 가만히 있으면 된다. 또한 이 윈도우가 포커스를 가지고 있을 때만 캐럿의 위치를 조정할 필요가 있다. 포커스가 없으면 캐럿이 보이지 않아야 하므로 SetCaret을 호출하지 말아야 한다.

조건 판단의 순서

GetLineSub 함수는 처음 봐서 바로 이해가 될 만큼 간단하지는 않다. 그러나 결과를 분석해보면 이해하기 어려운 정도는 아니다. 결과는 이해했다 치고 이 함수 제작 과정에서 어떤 함정들이 있었는지 구경해보도록 하자.

루프의 탈출 조건은 앞에서 살펴 봤듯이 세 가지가 있는데 대등한 조건문 같지만 이 세 조건을 점검하는 순서에 의미가 있다. \r, \0, 폭넘침 순서대로 점검을 하는데 이 순서를 폭넘침, \r, \0로 바꾸어 보도록 하자. 그리고 다음 문장을 입력해보아라. 증세를 정확하게 확인하기 위해서는 윈도우 폭을 초기값인 300으로 맞춰 놓아야 하며 디폴트 시스템 폰트를 사용해야 한다.

 

정렬 테스트 가나다라마바사아자차카타. 이상하다.

 

자까지 입력한 후 마침표를 찍으면 이 마침표가 아래줄로 내려간다. 이 상태에서 공백을 입력하면 마침표와 공백이 다시 위로 올라간다.

 

한 번 내려간 글자가 위로 다시 올라가는 것은 뭔가 정렬 코드가 제대로 되어 있지 않기 때문이다. 조건 점검의 순서를 바꾸면 왜 이런 현상이 일어나는지는 퀴즈로 남겨 두기로 한다. 디버거로 디버깅해보면 그 이유를 알 수 있을 것이다.

여기까지 코드를 작성한 후 실행해보면 자동개행 기능이 잘 동작할 것이다. 편집창의 오른쪽 끝에 닿으면 다음 줄로 알아서 내려가고 윈도우 크기가 바뀌면 문서 전체가 재정렬되어 모양이 완전히 바뀌게 될 것이다.