. 포맷팅영역

마진이 추가되면 윈도우의 작업영역 일부는 마진영역을 위해 할당되어야 한다. 이때 마진을 제외한 나머지 영역, 그러니까 실제로 텍스트를 보여주고 편집에 사용할 영역을 포맷팅영역(Formatting Area)이라고 한다. 포맷팅영역은 정렬, 출력, 마우스동작이 이루어지는 영역이므로 모든 편집코드는 이 영역 안에서 동작해야 한다.

ApiEdit6까지는 작업영역과 포맷팅영역이 완전히 일치하였기 때문에 GetClientRect 함수로 구한 작업영역을 그대로 포맷팅영역으로 사용했다. 하지만 마진이 추가되면 작업영역에서 마진의 폭만큼 빼준 영역만 포맷팅영역이 된다. 이 영역은 편집코드들이 수시로 참고해야 하므로 전역변수로 관리하는 것이 유리하다. 다음 네 개의 전역변수를 추가한다.

 

int MarginWidth;

COLORREF MarColor1, MarColor2;

RECT frt;

 

MarginWidth 변수는 이름 그대로 마진의 폭이며 두 개의 색상 변수는 마진을 그리는 데 사용할 색상값이다. frt가 바로 포맷팅영역인데 작업영역에서 마진의 폭을 제외한 나머지 영역을 차지하게 된다. 포맷팅영역을 초기화하고 갱신하는 일은 작업영역의 크기가 변경되는 시점인 OnSize에서 한다.

 

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

{

     if (state != SIZE_MINIMIZED) {

        GetClientRect(hWnd,&frt);

        frt.left += MarginWidth;

     ....

}

 

GetClientRect 함수로 작업영역을 먼저 구하고 마진의 폭만큼 왼쪽 변을 더했다. 마진은 오른쪽에 둘 수도 있고 왼쪽에 둘 수도 있는데 보통 왼쪽에 두므로 frt.left를 마진폭만큼 오른쪽으로 이동시키면 된다. OnCreate에서는 마진폭을 초기화한다.

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     MarginWidth=25;

     MarColor1=RGB(192,192,192);

     MarColor2=RGB(160,160,160);

     SendMessage(hWnd,WM_SIZE,0,0);

 

     return TRUE;

}

 

MarginWidth 25픽셀로 초기화했으며 색상은 회색 계통으로 두 개를 정의했다. 마진폭을 정한 후 곧바로 WM_SIZE 메시지를 보내 frt도 같이 초기화하도록 하였다. 왜 이 시점에서 OnSize호출이 필요한가 하면 초기화 함수인 UpdateLineInfo, UpdateScrollInfo frt를 먼저 사용해야 하기 때문이다. 그래서 OnCreate가 끝나기 전에 OnSize를 호출해야 하는데 OnSize에서는 frt 초기화뿐만 아니라 UpdateLineInfo, UpdateScrollInfo 함수도 같이 호출한다. 이제 OnCreate에서는 이 두 함수를 호출할 필요가 없어졌으므로 삭제하도록 하자.

포맷팅영역이 초기화되었으므로 이제 모든 편집코드는 작업영역 대신 frt를 대신 사용해야 한다. frt에는 마진폭이 이미 계산되어 있으므로 이 영역만 참고하면 편집코드는 마진의 존재에 대해 크게 신경쓰지 않아도 된다. 작업 대상 함수는 다음 9개이다.

 

OnKey, OnPaint, OnHScroll, OnVScroll, OnMouseMove,

GetLine, SetCaret, UpdateScrollInfo, Invalidate

 

수정해야 할 함수가 많기는 하지만 작업 절차는 아주 간단하다. 모든 함수의 RECT crt; 선언문과 GetClientRect 함수 호출문을 삭제하고 crt는 모조리 frt로 바꾸면 된다. 찾기, 바꾸기 기능을 사용하면 한 번에 코드를 수정할 수 있으므로 이런 작업이야 아주 쉬운 편이다. 이 작업 후에 crt라는 변수는 소스 전체를 통틀어 하나도 없어야 하며 GetClientRect 호출문은 OnSize에 하나만 있어야 한다. 마진을 만들었으니 화면에 보이도록 출력해보자. OnPaint를 또 한 번 대폭 수정해야 한다.

 

void OnPaint(HWND hWnd)

{

     HDC hdc, hMemDC;

     PAINTSTRUCT ps;

     int l;

     int s,e,t;

     HBRUSH hBrush;

     HBITMAP OldBitmap;

     RECT lrt;

    HBRUSH hMarBrush;

    RECT mrt;

    HPEN hMarPen,OldPen;

 

     hdc=BeginPaint(hWnd,&ps);

 

     t=yPos/LineHeight;

     s=(yPos+ps.rcPaint.top)/LineHeight;

     e=(yPos+ps.rcPaint.bottom-1)/LineHeight;

     e=min(e,TotalLine-1);

 

     hMemDC=CreateCompatibleDC(hdc);

     if (hBit == NULL) {

          hBit=CreateCompatibleBitmap(hdc,frt.right,LineHeight);

     }

     OldBitmap=(HBITMAP)SelectObject(hMemDC,hBit);

 

     hBrush=GetSysColorBrush(COLOR_WINDOW);

     SetRect(&lrt,MarginWidth-1,0,frt.right,LineHeight);

 

     for (l=s;l<=e;l++) {

          FillRect(hMemDC,&lrt,hBrush);

          DrawLine(hMemDC,l);

          BitBlt(hdc,0,(l-t)*LineHeight,frt.right,(l-t)*LineHeight+LineHeight,

              hMemDC,0,0,SRCCOPY);

     }

 

    if (MarginWidth != 0) {

        hMarBrush=CreateSolidBrush(MarColor1);

        SetRect(&mrt,0,(l-t)*LineHeight,MarginWidth-1,frt.bottom);

        FillRect(hdc,&mrt,hMarBrush);

        hMarPen=CreatePen(PS_SOLID,1,MarColor2);

        OldPen=(HPEN)SelectObject(hdc,hMarPen);

        MoveToEx(hdc,MarginWidth-2,(l-t)*LineHeight,NULL);

        LineTo(hdc,MarginWidth-2,frt.bottom);

 

        SelectObject(hdc,OldPen);

        DeleteObject(hMarPen);

        DeleteObject(hMarBrush);

    }

     SetRect(&lrt,MarginWidth-1,(l-t)*LineHeight,frt.right,frt.bottom);

     FillRect(hdc,&lrt,hBrush);

 

     DeleteObject(hBrush);

     SelectObject(hMemDC,OldBitmap);

     DeleteDC(hMemDC);

     EndPaint(hWnd,&ps);

}

 

몇 개의 지역변수가 추가되었는데 hMarBrush, hMarPen은 마진 채색에 사용할 브러시와 펜의 핸들이며 mrt는 마진의 영역이다. 텍스트가 출력되어야 할 영역인 lrt의 왼쪽 좌표는 마진의 폭과 같아져야 하나 1픽셀만큼 여유를 두어 마진과 텍스트가 너무 붙어 보이지 않도록 하였다. 작업영역의 왼쪽에 마진폭만큼 자리가 비워지고 그 오른쪽에 텍스트가 출력된다. 각 줄에 대해 DrawLine을 호출하면 이 함수 내부에서 마진과 텍스트를 비트맵에 같이 출력하도록 할 것이다.

모든 줄의 출력이 끝난 후 아래쪽 여백에 대해서도 마진을 직접 그려야 한다. 마진폭에서 2픽셀을 뺀만큼 MarColor1로 채우고 그 오른쪽에 MarColor2로 한 줄 선을 그어 주었다. 단순한 그래픽 출력문일 뿐이다. 각 줄의 마진영역은 DrawLine 함수가 비트맵에 그려 준다.

 

int DrawLine(HDC hdc, int Line)

{

    HBRUSH hMarBrush;

    HPEN hMarPen,OldPen;

    RECT mrt;

     ....

          DrawSegment(hdc,x,0,nowoff,len,

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

 

          nowoff+=len;

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

           break;

     }

 

    if (MarginWidth != 0) {

        hMarBrush=CreateSolidBrush(MarColor1);

        SetRect(&mrt,0,0,MarginWidth-1,LineHeight);

        FillRect(hdc,&mrt,hMarBrush);

        hMarPen=CreatePen(PS_SOLID,1,MarColor2);

        OldPen=(HPEN)SelectObject(hdc,hMarPen);

        MoveToEx(hdc,MarginWidth-2,0,NULL);

        LineTo(hdc,MarginWidth-2,LineHeight);

 

        SelectObject(hdc,OldPen);

        DeleteObject(hMarPen);

        DeleteObject(hMarBrush);

    }

    return 1;

}

 

줄 끝을 만났을 때 그냥 리턴하는 것이 아니라 일단 루프를 빠져 나와 마진을 그린 후 리턴해야 한다. 마진은 텍스트를 출력하기 전에 그려도 되고 텍스트를 먼저 출력한 후 그려도 되는데 수평으로 스크롤될 때 텍스트로 인해 마진영역이 지워지는 것을 따로 처리하지 않기 위해 텍스트를 완전히 출력한 후 마진을 따로 그리는 것이 좋다. 텍스트가 설사 마진영역에 출력되더라도 마진을 다시 그리므로 항상 마진이 텍스트보다 더 위에 있게 된다. DrawLine의 마진 출력문도 OnPaint에서와 거의 동일하다. 여기까지 수정한 후 실행해보자.

왼쪽에 옅은 회색의 마진이 나타나기는 하는데 아직 나머지 코드들이 수정되지 않았기 때문에 문장이 마진에 의해 가려지고 캐럿이 마진 안에서 놀고 있다. 정렬함수와 출력함수 등이 마진을 제대로 인식하도록 하는 작업이 필요하다.