. 현재행 표시

현재행이란 캐럿이 있는 위치, 즉 편집을 하고 있는 화면상의 위치를 말한다. 캐럿은 계속 깜박거려 눈에 잘 띄도록 되어 있지만 크기가 별로 크지 않기 때문에 한눈에 위치가 잘 파악되지 않는 경향이 있다. 특히 시력이 좋지 않은 사람에게 이 문제는 아주 심각하다. 현재행 표시 기능은 캐럿이 있는 줄 전체를 강조하여 어디쯤 편집하고 있는지를 분명하게 보여주는 기능이다. 개인적으로 별로 좋아하지도 않고 꼭 필요하다고도 생각하지 않지만 대부분의 편집기들이 이 기능을 지원하고 있으므로 ApiEdit도 이 기능을 만들어보자.

현재행을 강조할 때는 보통 사각의 박스를 보여주거나 아니면 배경색을 다른 색으로 칠해 눈에 잘 띄도록 하는데 취향에 따라 강조 방식을 선택할 수 있도록 한다. , 현재행을 보여줄 것인가 아닌가 뿐만 아니라 어떤 식으로 보여줄 것인가를 선택할 수 있다. 그래서 변수는 BOOL형이 아닌 정수형으로 선언하였다.

 

int nShowCurLine;

COLORREF CurColor;

 

이 변수의 값에 따라 현재행을 강조하는 방식이 달라지며 다음 5가지 방식이 가능하다. 물론 필요하면 타입을 얼마든지 더 늘릴 수 있다.

 

현재행 강조

0

강조하지 않는다.

1

현재행을 둘러싸는 사각 프레임을 보여준다.

2

현재행 아래쪽에 밑줄을 보여준다.

3

배경색을 칠한다.

4

배경색을 칠하고 사각 프레임으로 감싼다.

5

마진에 삼각형 표시로 현재행을 표시한다.

 

적당한 디폴트값을 선택해야 하는데 일단 이 기능이 있다는 것을 보여 줘야 하므로 3번 배경색을 칠하는 것으로 초기화하도록 하자. 배경색상은 눈에 잘 띄는 노란색으로 초기화하였다.

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     nShowCurLine=3;

     CurColor=RGB(255,255,0);

     ....

 

이 변수값은 한 줄을 출력하는 DrawLine의 동작에 많은 영향을 미치게 된다. 줄의 텍스트만 출력할 게 아니라 현재 줄도 같이 보여 줘야 하기 때문이다. DrawLine의 코드가 많이 확장되어야 한다.

 

int DrawLine(HDC hdc, int Line)

{

     ....

    int r,c;

    BOOL bCurLine=FALSE;

    RECT lrt;

    HBRUSH hCurLine;

    int toff;

    POINT tri[]={{1,1},{1,0},{0,0},{1,1}};

 

     if (pLine[Line].Start == -1)

          return 0;

 

    if (bComp) {

        toff=off-2;

    } else {

        toff=off;

    }

    GetRCFromOff(toff,r,c);

    if (r==Line) {

        bCurLine=TRUE;

    }

 

    if (bCurLine && (nShowCurLine == 3 || nShowCurLine == 4)) {

        SetRect(&lrt,frt.left,0,frt.right,FontHeight);

        hCurLine=CreateSolidBrush(CurColor);

        FillRect(hdc,&lrt,hCurLine);

        DeleteObject(hCurLine);

    }

 

     SelFirst=min(SelStart,SelEnd);

     SelSecond=max(SelStart,SelEnd);

 

     x=MarginWidth-xPos;

     nowoff=pLine[Line].Start;

     for (;;) {

              ....

          if (bInSel && (GetFocus()==hWndMain || HideSelType!=0)) {

              fore=SelFore;

              back=SelBack;

          } else {

              fore=RGB(0,0,0);

           if ((nShowCurLine==3 || nShowCurLine==4) && bCurLine) {

               back=CurColor;

           } else {

                   back=GetSysColor(COLOR_WINDOW);

           }

          }

 

          DrawSegment(hdc,x,0,nowoff,len,(nowoff+len==pLine[Line].End),fore,back);

 

          nowoff+=len;

          if (nowoff == pLine[Line].End)

              break;

     }

 

    if ((nShowCurLine==1 || nShowCurLine==2 || nShowCurLine==4) && bCurLine) {

        if (nShowCurLine == 1 || nShowCurLine == 4) {

           SetRect(&lrt,frt.left-1,0,frt.right+1,FontHeight);

        } else {

           SetRect(&lrt,frt.left-1,FontHeight,frt.right+1,FontHeight);

        }

        SetTextColor(hdc,RGB(0,0,0));

        SetBkColor(hdc,GetSysColor(COLOR_WINDOW));

        DrawFocusRect(hdc,&lrt);

    }

 

     if (MarginWidth != 0) {

          ....

        if (nShowCurLine == 5 && bCurLine) {

           OldBrush=(HBRUSH)SelectObject(hdc,GetStockObject(BLACK_BRUSH));

           tri[1].y=FontHeight-1-FontHeight % 2;

           tri[2].x=FontHeight/2;

           tri[2].y=FontHeight/2;

           Polygon(hdc,tri,4);

           SelectObject(hdc,OldBrush);

        }

     }

     return 1;

}

 

일단 이 함수는 지금 출력하는 Line 줄이 현재 캐럿이 있는 줄인지를 먼저 판단해야 한다. 이 함수는 출력 대상이 되는 줄번호만 받을 뿐이지 이 줄이 현재 줄인지 아닌지는 전달받지 않기 때문이다. 현재 캐럿 위치는 일반적으로 전역변수 off가 가리키는 위치이되 단 한글을 조립하고 있는 중에는 2칸 앞쪽의 오프셋이 현재 캐럿 위치가 된다. 이 위치의 줄 수와 Line 줄이 맞으면 지금 출력하는 줄이 현재 줄이며 이때 bCurLine 변수를 TRUE로 바꾼다. 만약 현재 줄이 아니거나 nShowCurLine 0이라면 아무것도 할 필요가 없으며 텍스트만 제대로 출력하면 된다.

nShowCurLine 1이나 2인 경우의 처리는 모든 텍스트를 다 출력하고 난 다음에 한다. 사각 프레임을 출력할 때는 DrawFocusRect 함수를 사용하는데 이 함수는 DC에 설정된 텍스트의 전경, 배경색을 사용하므로 검정색과 배경색으로 된 점선을 긋기 위해 전경, 배경색을 모두 설정해야 한다. nShowCurLine값에 따라 프레임을 출력할 영역이 달라지는데 1이면 아래쪽에 1픽셀 두께로, 2면 줄 전체를 감싸는 크기대로 출력하였다. 프레임의 수평위치는 포맷팅영역보다 한 픽셀씩 바깥쪽으로 되어 있는데 왜냐하면 수평스크롤시에 프레임이 같이 스크롤되는 것을 방지하기 위해서이다. 마진의 오른쪽에 한 픽셀 여유분이 있으므로 프레임이 포맷팅영역을 약간 벗어나도 별 상관이 없다.

nShowCurLine 3인 경우는 배경색을 칠하는데 문자가 없는 영역에도 배경색이 있어야 하므로 텍스트 출력 전에 일단 배경색을 깔아 두어야 한다. 그리고 텍스트를 출력할 때 선택영역이 아닌 조각의 배경색을 노란색으로 바꾸어 줌으로써 줄 전체가 노란 배경을 가지도록 했다. 물론 선택영역은 배경색을 무시하고 선택영역의 배경색을 사용해야 한다. nShowCurLine 4인 경우는 3인 경우와 2인 경우의 조합이므로 텍스트 출력 전에 배경을 깔고 출력 후에 프레임도 그려야 한다.

nShowCurLine 5인 경우는 포맷팅영역이 아니라 마진영역에 현재 행을 표시하므로 if (MarginWidth != 0) 블록 안에 코드를 작성해야 한다. 현재행 표시는 줄번호나 북마크 표시보다도 더 위에 있어야 하므로 이 블록의 가장 아래쪽에 있는 것이 좋다. tri 배열은 삼각형의 세 꼭지점 좌표이되 아래쪽과 오른쪽 꼭지점은 폰트 크기에 따라 변하므로 일단 0으로 초기화하고 삼각형을 그릴 때 좌표값을 조정해야 한다. 특히 아래 점 tri[1].y 값은 FontHeight가 홀수일 때는 1을 더 빼주어야 예쁜 삼각형이 그려진다.

캐럿은 문서에 포함되어 있는 것이 아니므로 캐럿의 이동으로 인해 화면을 다시 그리지는 않는다. 하지만 현재행 강조 기능이 들어가면 캐럿이 이동할 때마다 이동 전의 행과 이동 후의 행을 다시 그려야 할 필요가 있다. SetCaret에서 캐럿의 y 좌표가 변경되면 무효화하여 다시 그리도록 해야 한다. 수평으로 이동할 때는 굳이 다시 그릴 필요가 없다.

 

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

{

     POINT OldPos;

     RECT InvRt;

 

     GetCaretPos(&OldPos);

     ....

 

     if (nShowCurLine != 0 && OldPos.y != y-yPos) {

          InvRt.left=0;

          InvRt.right=frt.right;

          InvRt.top=min(OldPos.y,y-yPos);

          InvRt.bottom=max(OldPos.y,y-yPos)+LineHeight;

          InvalidateRect(hWndMain,&InvRt,FALSE);

     }

}

 

캐럿을 이동하기 전에 OldPos에 캐럿의 좌표를 조사해놓고 캐럿이동 후 OldPos y 좌표가 이동한 후의 y 좌표와 다르면, 즉 캐럿의 수직좌표가 바뀌었으면 작업영역을 무효화하여 다시 그려야 한다.

이때 다시 그려야 할 영역은 이동하기 전의 캐럿 위치에서부터 이동한 후의 캐럿 위치까지이며 그 외의 영역은 굳이 다시 그릴 필요가 없다. 예를 들어 3번 줄에서 4번 줄로 내려왔다면 3번 줄에 있는 현재행 표시는 지우고 4번 줄에 다시 현재행을 표시하면 된다. 무효영역을 최소화하기 위해 이동 전의 y 좌표와 이동 후의 y 좌표 사이만 무효화하였다.

작업영역을 무효화시키는 Invalidate라는 함수를 이미 만들어 두었지만 SetCaret은 이 함수를 사용하지 않고 무효영역을 계산하는 코드를 작성하였다. 왜냐하면 SetCaret은 화면 좌표를 직접 다루며 오프셋을 다루고 있지 않기 때문이다. 캐럿이 이동하기 전의 오프셋 좌표를 모르기 때문에 Invalidate 함수를 사용할 수 없으며 그래서 직접 무효영역을 계산하였다. 각 타입 별로 현재행은 다음과 같이 표시된다.

  

프레임을 보여주는 1번이 가장 깔끔한 것 같고 3번이 가장 눈에 잘 띈다. 5번도 그럭저럭 괜찮아 보이는데 이런 타입은 얼마든지 더 추가할 수 있다.