. 키보드 스크롤

스크롤 바로의 스크롤 코드는 다 작성했다. 하지만 아직 키보드로는 스크롤을 할 수 없다. 제일 아래줄에 캐럿을 두고 한 칸 아래로 내려가면 캐럿만 내려갈 뿐 스크롤은 되지 않는다. 캐럿이 화면 아래로 숨어버리는 것이다.

 

그래서 캐럿 위치가 변경되면 이 캐럿이 보일 수 있도록 화면도 같이 스크롤해야 한다. 키보드에 의해 스크롤이 발생하므로 OnKey 함수에서 처리해야 할 것 같지만 그렇지 않다. 키보드에 의해 캐럿이 이동될 때마다 스크롤되는 것이 아니라 캐럿이 화면을 벗어날 때만 스크롤되어야 하므로 이 작업은 캐럿 위치를 변경하는 SetCaret에서 하는 것이 훨씬 더 효율적이다. 코드를 보기 전에 이 함수의 동작을 그림으로 먼저 설명해보았다.

만약 캐럿이 오른쪽 바깥으로 벗어났다면 오른쪽으로 문서를 스크롤시켜 새 위치가 화면의 중앙이 되도록 한다. 또한 캐럿이 위쪽으로 벗어났으면 위로 스크롤하여 캐럿이 첫 번째 줄에 보이도록 해야 하며 반대로 아래쪽으로 벗어나면 마지막 줄에 캐럿이 있도록 해야 한다. 이 동작을 코드로 옮긴 결과는 다음과 같다. 키보드 스크롤을 지원하기 위해 SetCaret 함수에 인수가 하나 더 추가되었다.

 

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

{

     HDC hdc;

     int toff;

     int caretwidth;

     int x,y;

    RECT crt;

    int ty;

    BOOL bScroll=FALSE;

 

     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);

 

     GetXYFromOff(toff,x,y);

    if (bScrollToCaret) {

        GetClientRect(hWndMain,&crt);

        if (nWrap==0) {

            if ((x+caretwidth > xPos+crt.right) || (x < xPos)) {

               xPos=max(0,x-crt.right/2);

               bScroll=TRUE;

            }

        }

 

        if (y < yPos) {

            yPos=y;

            bScroll=TRUE;

        }

 

        if (y+FontHeight > yPos+crt.bottom) {

            ty=(crt.bottom-FontHeight)/LineHeight*LineHeight;

            yPos=y-ty;

            bScroll=TRUE;

        }

 

        if (bScroll == TRUE) {

            SetScrollPos(hWndMain, SB_HORZ, xPos, TRUE);

            SetScrollPos(hWndMain, SB_VERT, yPos, TRUE);

            InvalidateRect(hWndMain,NULL,TRUE);

        }

    }

 

    SetCaretPos(x-xPos,y-yPos);

     ReleaseDC(hWndMain,hdc);

     if (bUpdatePrevX) {

          PrevX=x;

     }

}

 

if (bScrollToCaret) 블록 아래의 조건문들이 이런 처리를 하고 있는데 다 비슷한 코드이므로 대표적으로 아래쪽으로 벗어난 경우인 세 번째 조건문만 분석해보도록 하자. 이 조건문에서 y SetCaret이 조사한 캐럿의 새 위치이며 이 값에 FontHeight를 더하면 캐럿의 발가락 좌표가 계산된다. 이 좌표가 현재 스크롤된 상태에서 작업영역의 바닥(yPos+crt.bottom)보다 더 아래쪽에 있다면 강제로 이 줄을 위로 올려준다.

이때 스크롤된 후의 위치는 작업영역의 바닥에서 폰트의 높이만큼 위쪽이되 단 첫 줄이 잘리지 않도록 하기 위해 줄간의 배수가 되도록 조정해야 한다.

나머지 조건문들도 다 비슷하므로 직접 분석해보기 바란다. 수평좌표를 비교할 때는 nWrap 0인 경우만 비교해보면 되는데 자동개행 상태에서는 어떤 경우라도 수평으로 스크롤되지 말아야 한다.

캐럿이 문서 밖을 벗어났는지는 캐럿을 옮길 때마다 점검해야 한다. , 예외가 있는데 OnSetFocus에서는 이 처리를 할 필요가 없다. 스크롤바로 문서의 아래쪽 부분을 스크롤해서 읽다가 다른 프로그램을 잠시 사용한 후 다시 편집기로 돌아왔는데 캐럿 위치로 스크롤을 하면 안된다. 사용자가 읽고 있던 위치는 일부러 캐럿을 옮기지 않는 한은 유지하는 것이 옳다. 그래서 SetCaret의 원형에 새로운 디폴트 인수 bScrollToCaret이 하나 더 추가되었으며 OnSetFocus에서는 다음과 같이 SetCaret의 인수를 바꿔야 한다.

 

void OnSetFocus(HWND hWnd, HWND hwndOldFocus)

{

    SetCaret(FALSE,FALSE);

}

 

추가된 인수의 디폴트값을 주었으므로 그 외 나머지 SetCaret을 호출하는 부분은 전혀 건드리지 않아도 된다. 이제 실행해보면 캐럿이 화면을 벗어날 때 자연스럽게 스크롤될 것이다. 키보드 스크롤은 사실 캐럿 스크롤이라고 이름을 붙이는 것이 더 적절한 것 같다.