. 상하 이동

상하이동도 별로 특별한 것은 없으므로 바로 코드를 작성하도록 하자. 두 코드는 유사하므로 VK_UP의 코드만 중점적으로 분석해보면 된다.

 

     case VK_UP:

        if (bControl && bShift)

            return;

          GetRCFromOff(off,r,c);

          if (r > 0) {

            if (bControl) {

               SendMessage(hWnd, WM_VSCROLL, SB_LINEUP, 0L);

               if (r != (crt.bottom+yPos)/LineHeight)

                   return;

            }

            OldOff=off;

               r--;

               off=GetXPosOnLine(r,PrevX);

            if (bShift) {

               ExpandSelection(OldOff,off);

            } else {

               if (SelStart != SelEnd) {

                   off=min(SelStart, SelEnd);

                   ClearSelection();

                   SetCaret();

                   SendMessage(hWnd,WM_KEYDOWN,VK_UP,(LPARAM)0);

               }

            }

               SetCaret(FALSE);

          }

 

        if (!bShift) {

            ClearSelection();

        }

          return;

     case VK_DOWN:

        if (bControl && bShift)

            return;

          GetRCFromOff(off,r,c);

        if (bControl) {

            SendMessage(hWnd, WM_VSCROLL, SB_LINEDOWN, 0L);

            if (r != yPos/LineHeight-1) {

               return;

            }

        }

          if (r < GetRowCount()-1) {

            OldOff=off;

               r++;

               off=GetXPosOnLine(r,PrevX);

            if (bShift) {

               ExpandSelection(OldOff,off);

            } else {

               if (SelStart != SelEnd) {

                   off=max(SelStart, SelEnd);

                   ClearSelection();

                   SetCaret();

                   SendMessage(hWnd,WM_KEYDOWN,VK_DOWN,(LPARAM)0);

               }

            }

               SetCaret(FALSE);

          }

 

        if (!bShift) {

            ClearSelection();

        }

          return;

 

<Shift>키에 대한 처리는 좌우이동과 완전히 동일하다. 이동전의 위치를 OldOff에 저장해두고 이동한 위치 off까지 선택을 확장하면 된다. <Shift>키를 누른 채로 아래위로 이동하면 줄단위로 선택이 확장될 것이다. 그러나 <Shift>키를 누르지 않았을 때 선택영역을 푸는 방식은 상당히 다르다. 좌우이동시는 블록의 왼쪽이나 오른쪽으로 캐럿을 옮겨주기만 했으나 상하이동시는 블록의 처음과 끝으로 캐럿을 옮긴 후 다시 아래위로 한 번 더 이동한다. 즉 선택을 풀면서 상하이동도 같이 하는 것이다.

좌우이동과는 달리 상하이동시는 왜 이렇게 처리하도록 했는가 하면 한 줄 안에서만 선택이 되어 있는 경우 블록의 처음이나 끝으로 이동하고 가만히 있으면 사용자의 상하이동 명령을 무시한 듯한 느낌이 들기 때문이다. 좌우이동은 원래 수평이동이기 때문에 굳이 캐럿이 블록 밖으로 움직이지 않아도 이상하지 않지만 상하이동의 경우는 선택을 풀고 난 다음에도 상하이동을 하는 것이 더 좋다.

그래서 <Shift>키가 눌러지지 않았고 선택영역이 있을 때 VK_UP을 한 번 더 보내 위로 이동하도록 하였다. VK_UP VK_UP을 실행하는 꼴인데 그 전에 세 가지 처리가 필요하다. 우선 off는 블록의 처음으로 이동시켜 블록 처음에서 위로 이동하도록 한다. ClearSelection 함수를 불러 준 이유는 무한루프를 방지하기 위해서인데 계속 선택영역이 남아 있으면 VK_UP이 무한 번 호출되기 때문에 다음 번 호출에서 끝을 내기 위해서이다. SetCaret 호출이 필요한 이유는 변경된 off로부터 PrevX를 갱신하기 위해서이다. VK_UP PrevX를 기준으로 수직 대응 위치를 찾으므로 PrevX가 제대로 설정되어 있어야 한다.

<Ctrl>키와 상하이동키는 제자리 스크롤 기능을 가지는데 캐럿은 그 자리에 두고 아래위로 스크롤하는 것이다. 스크롤 기능은 이미 다 작성되어 있으므로 스크롤 메시지를 보내주기만 하면 공짜로 기능을 사용할 수 있다. <Ctrl>키를 누른 채로 위로 이동하면 위로 스크롤만 되며 현재 위치는 변하지 않는다. 이 키 조합은 현재 편집 위치를 유지한 채로 화면의 좀 더 위쪽을 볼 수 있도록 한다.

스크롤 기능과 거의 동일하되 단 캐럿이 화면을 벗어나지 않도록 해야 한다. 캐럿이 화면의 제일 아래줄에 있는 상태에서 위로 스크롤하면 캐럿은 아래로 내려가 버릴 것이다. 그래서 스크롤한 후에 캐럿이 화면 제일 아래줄에 있는지 보고 그렇다면 return하지 않고 아래로 내려와 한 줄 위로 이동하도록 하였다.

아래로 이동할 때도 마찬가지 처리를 한다. 스크롤한 후에 캐럿이 화면상의 첫 줄보다 한 줄 더 작은 위치라면, 즉 화면 위의 안 보이는 부분으로 가 버렸다면 아래로 한 칸 내려 주도록 해야 한다. 그 외의 경우는 커서이동을 하지 않고 스크롤만 한 후 곧바로 return한다. 상하이동은 <Ctrl>키와 <Shift>키를 같이 누르는 것에 대해서는 기능을 전혀 정의하지 않고 있다. 그래서 이 키조합은 무시하였다. 다음은 <PgUp>, <PgDn>의 코드이다.

 

     case VK_PRIOR:

          GetRCFromOff(off,r,c);

          oldr=r;

          r-=crt.bottom/LineHeight;

          r=max(r,0);

          yPos=yPos-(oldr-r)*LineHeight;

          yPos=max(yPos,0);

          InvalidateRect(hWnd,NULL,TRUE);

          SetScrollPos(hWnd, SB_VERT, yPos, TRUE);

 

        OldOff=off;

          off=GetXPosOnLine(r,PrevX);

        if (bShift) {

            ExpandSelection(OldOff,off);

        } else {

            ClearSelection();

        }

          SetCaret(FALSE);

          return;

     case VK_NEXT:

          GetRCFromOff(off,r,c);

          oldr=r;

          r+=crt.bottom/LineHeight;

          r=min(r,GetRowCount()-1);

          yPos=yPos+(r-oldr)*LineHeight;

          yPos=max(0,min(yPos,yMax-(crt.bottom/LineHeight)*LineHeight));

          InvalidateRect(hWnd,NULL,TRUE);

          SetScrollPos(hWnd, SB_VERT, yPos, TRUE);

 

        OldOff=off;

          off=GetXPosOnLine(r,PrevX);

        if (bShift) {

            ExpandSelection(OldOff,off);

        } else {

            ClearSelection();

        }

          SetCaret(FALSE);

          return;

 

<Shift>키에 대한 처리가 추가되어 <Shift>키를 누른 채로 이동하면 페이지단위로 선택을 확장한다. <Ctrl>키에 대해서는 처리하고 있지 않으나 일부러 막지도 않았으므로 <Ctrl+PgUp><PgUp>과 완전히 동일하다. 필요하다면 현재 보이는 화면상의 처음이나 이전 문단으로 이동 등의 기능을 부여할 수도 있다.

 

이상, 여기까지 키보드로 블록을 선택하는 방법과 덤으로 <Ctrl>키에 대한 처리까지 같이 구현해보았다. 이 실습에서 ExpandSelection이라는 함수를 만들어 아주 요긴하게 사용했는데 이 함수를 만들지 않았다면 각 키의 처리 루틴이 무척 길어졌을 것이다. 이런 함수는 미리 만들어 놓고 사용하는 것이 아니라 반대로 코드를 다 작성한 후 공통된 부분을 뽑아내서 만든다.

각 키 처리 루틴을 만들어 놓고 보니 계속 유사한 코드가 반복되길래 이 코드들을 잘 정리해서 재활용이 쉽도록 만든 함수가 바로 ExpandSelection 함수이다. 처음부터 이런 함수의 필요성을 인지하기는 웬만큼 경험이 많아도 사실 무척 어렵다. , 개발과정이란 필요한 함수를 다 만들어 놓고 시작하는 것이 아니라 일단 코딩을 하다가 반복되는 루틴을 함수로 분리하면서 점점 완성되어가는 것이다.