. 상하 이동

여러 줄을 편집할 수 있게 되었으니 이제 당연히 아래위로도 이동할 수 있어야 한다. 아래쪽이나 위쪽이나 논리는 비슷하므로 아래쪽 이동을 중점적으로 살펴 보도록 하자. 개념적으로 한 칸 아래로 내려 간다는 것은 아주 쉽다. 줄번호만 하나 증가시켜 주고 열번호는 그대로 유지하면 된다.

왼쪽 그림에서 첫 번째 줄 세 번째 칸 자가 현재 위치라면 두 번째 줄 세 번째 칸인 자로 이동하면 되는 것이다. 그러나 문제가 있다. 오른쪽 그림처럼 바로 아래 줄에 대응되는 칸이 없는 경우가 있을 수 있는데 이때는 아래 줄 끝으로 이동해야 한다. 메모장이나 다른 편집기의 동작을 보면 이런 처리를 쉽게 확인할 수 있다. 다행히 이 처리는 이미 GetOffFromRC에서 하고 있으므로 상하이동 코드는 더 이상 이 문제를 신경쓰지 않아도 된다.

상하이동 둘 다 범위 점검은 꼭 해야 한다. 제일 첫 줄에서는 더 이상 올라갈 곳이 없고 제일 아래줄에서는 더 내려갈 곳이 없다. 첫 줄인가는 GetRCFromOff로 행열을 조사한 후 r 0인지 보면 되므로 간단하다. 하지만 마지막 줄인지는 아직 알 수 있는 방법이 없으므로 문서의 줄 길이를 조사하는 함수 GetRowCount 함수를 만들도록 하자.

 

int GetRowCount()

{

     int l,s,e;

 

     for (l=0;;l++) {

          GetLine(l,s,e);

          if (s==-1)

               break;

     }

 

     return l;

}

 

GetLine을 반복적으로 호출하다가 s -1이 되는 시점에서 탈출하면 문서의 줄 수를 구할 수 있다. 이 함수로 조사하는 값은 문서의 줄 수이지 마지막 끝 줄이 아니다. 끝 줄은 줄 수에서 1을 빼야 한다. 오프셋 값과 행열값을 변환하는 함수, 문서의 줄 수를 구하는 함수들을 이미 만들어 두었으므로 이제 상하이동은 거저 먹기다. 아래, 위로 이동하는 절차를 정리해보았다.

 

아래쪽 이동

위쪽 이동

현재 행렬값 구함

r 1증가시킴. 아래줄로

대응되는 오프셋을 구함

캐럿을 위치로 이동시킴

 

, 현재 줄이 제일 줄인 경우는 제외

현재 행렬값 구함

r 1감소시킨. 윗줄로

대응되는 오프셋을 구함

캐럿을 위치로 옮김

 

, 현재 줄이 줄인 경우는 제외

 

지극히 상식적이므로 쉽게 이해될 것이며 이 절차를 그대로 함수의 코드로 옮기기만 하면 된다. OnKey 함수에 다음 코드를 작성하자.

 

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

{

    int r,c;

 

     if (fDown==FALSE)

          return;

 

     switch (vk)

     {

     ....

    case VK_UP:

        GetRCFromOff(off,r,c);

        if (r > 0) {

            r--;

            off=GetOffFromRC(r,c);

            SetCaret();

        }

        return;

    case VK_DOWN:

        GetRCFromOff(off,r,c);

        if (r < GetRowCount()-1) {

            r++;

            off=GetOffFromRC(r,c);

            SetCaret();

        }

        return;

 

그런데 이렇게 단순한 방법으로 아래위로 이동하면 다음 그림처럼 한글의 중간에 캐럿이 위치할 수도 있다. 다음 그림은 첫 번째 줄의 자 다음 위치에서 아래로 이동한 결과이다.

자 다음의 공백 위치는 열번호가 6번이다. 그런데 두 번째 줄의 6열은 자의 중간에 걸치게 된다. GetOffFromRC가 글자의 경계인지는 보지 않고 단순히 시작위치에 열번호만 더해서 오프셋을 구하기 때문이다. 이 버그는 현재 구조로는 고치기 어렵고 바로 다음 번 예제에서 수정할 것이다. 일단은 그냥 넘어가도록 하자.

여러 줄을 편집할 수 있게 되었더라도 좌우이동 코드는 건드릴 필요가 없다. 왜냐하면  IsDBCS 함수가 Enter 코드를 인식하도록 수정되었고 GetNextOff, GetPrevOff 함수가 개행코드의 길이만큼 잘 이동하고 있기 때문이다. 줄의 처음과 끝으로 이동하는 Home, End의 코드는 수정되어야 한다. 한 줄만 있을 때는 무조건 버퍼의 처음, 또는 버퍼의 끝으로 이동하면 되지만 여러 줄일 때는 현재 줄의 처음과 끝으로 이동해야 한다.

 

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

{

     ....

     case VK_HOME:

          GetRCFromOff(off,r,c);

          off=GetOffFromRC(r,0);

          SetCaret();

          return;

     case VK_END:

          GetRCFromOff(off,r,c);

          off=GetOffFromRC(r,1000000);

          SetCaret();

          return;

 

Home의 경우 현재 줄번호를 구한 후 GetOffFromRC(r,0)을 호출하면 줄의 처음 위치로 이동할 것이다. End의 경우 줄 끝의 열번호를 정확히 조사해서 이동하는 것보다는 GetOffFromRC(r,1000000)으로 제일 뒤쪽으로 이동하도록 하였다. 이때 1000000이라는 열번호는 특별한 의미가 있는 것이 아니라 단순히 충분히 큰 값을 의미한다. GetOffFromRC는 열번호가 줄의 끝보다 더 클 경우 줄 끝의 오프셋을 찾아주기 때문에 이렇게 해도 전혀 무리가 없다.

 

이상으로 여러 줄 편집이 가능한 ApiEdit1 예제의 작성을 마친다. 아직 편집기라고 할만한 기능들을 별로 가지고 있지 않지만 이 예제는 앞으로 만들 예제들의 원형이므로 적어도 이 예제의 코드는 완벽하게 이해하고 넘어가야 다음 실습을 하는데 불편함이 없다. 좀 시간이 걸리더라도 함수 하나 하나를 스스로 분석해보고 정리한 후 넘어가도록 하자.

특히 이 예제에서 눈여겨 볼 것은 편집기 제작에 필요한 유틸리티 함수 집합을 정의했다는 점이다. 이 함수들은 앞으로 기능을 확장하는 주요한 도구가 되며 또한 그 자체가 확장의 대상이기도 하다. 함수의 코드 자체도 중요하지만 그 보다는 왜 이런 함수들이 필요한지를 이해하고 함수의 입력과 출력을 어떻게 설계해놓았는지를 살펴보는 것이 더 중요하다.