. 선택영역 표시

선택영역이 있으면 일반 문자열과는 다른 색상으로 출력하여 이 부분이 선택되어 있음을 보여주어야 한다. 이 예제는 선택영역 출력을 위해 SelFore, SelBack 두 변수를 각각 전경색, 배경색으로 사용하고 있다. 아직 선택 관련 코드가 작성되어 있지 않으므로 임시로 OnCreate에서 선택영역을 다음과 같이 초기화한다.

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     SelStart=7;

     SelEnd=15;

 

     return TRUE;

}

 

7~15까지가 선택되어 있다고 치고 선택영역 표시 루틴을 우선 작성해보도록 하자. 선택영역을 표시하는 작업은 당연히 OnPaint가 담당해야겠지만 실제로 선택영역을 출력하는 책임은 한 줄을 출력하는 DrawLine에게 있다. DrawLine은 탭문자를 기준으로 조각을 나누어 출력하는데 이제 탭문자뿐만 아니라 선택영역의 경계도 조각을 나누는 기준이 된다. 7~15까지 선택되어 있는 상태에서 한 줄은 다음과 같이 조각이 나누어진다.

물론 여기에 탭문자까지 포함된다면 조각은 더 잘게 나누어질 것이다. 조각이란 한 번에 출력해야 할 문자열 덩어리이며 선택영역은 배경색과 전경색이 일반 문자열과 다르므로 분명히 따로 출력해야 하는 조각이다. DrawLine이 선택영역의 경계를 조각으로 나누고 각 조각에 대해 전경색과 배경색을 지정할 수 있도록 다음과 같이 코드를 수정한다.

 

int DrawLine(HDC hdc, int Line)

{

     ....

    BOOL bInSel;

    COLORREF fore, back;

    int SelFirst, SelSecond;

 

     GetLine(Line,s,e);

     if (s == -1)

          return 0;

 

    SelFirst=min(SelStart,SelEnd);

    SelSecond=max(SelStart,SelEnd);

 

     x=0-xPos;

     nowoff=s;

     for (;;) {

          for (len=0;;) {

               if (buf[nowoff+len] == ‘\t’) {

                   if (len == 0)

                        len=1;

               if (SelStart != SelEnd && nowoff >= SelFirst && nowoff < SelSecond) {

                   bInSel=TRUE;

               } else {

                   bInSel=FALSE;

               }

                   break;

               }

 

               if (nowoff+len == e) {

                if (SelStart != SelEnd && nowoff >= SelFirst && nowoff < SelSecond) {

                   bInSel=TRUE;

               } else {

                   bInSel=FALSE;

               }

                   break;

               }

 

            if (SelStart != SelEnd && len != 0 && nowoff+len == SelFirst) {

               bInSel=FALSE;

               break;

            }

            if (SelStart != SelEnd && len != 0 && nowoff+len == SelSecond) {

               bInSel=TRUE;

               break;

            }

 

               len++;

          }

 

        if (bInSel) {

            fore=SelFore;

            back=SelBack;

        } else {

            fore=RGB(0,0,0);

            back=GetSysColor(COLOR_WINDOW);

        }

 

        DrawSegment(hdc,x,Line*LineHeight-yPos,nowoff,len,(nowoff+len==e),fore,back);

 

          nowoff+=len;

          if (nowoff == e)

               return 1;

     }

}

 

각 조각에 대해 선택영역 안에 있는지 아닌지를 점검하는데 이때 범위 점검을 위해 SelStart, SelEnd를 바로 사용할 수는 없다. 왜냐하면 SelStart는 선택의 시작점이고 SelEnd는 선택의 끝점이지만 SelStart SelEnd보다 반드시 작다고 할 수 없기 때문이다. 왼쪽을 클릭한 후 오른쪽으로 드래그하면 SelStart가 더 앞쪽이지만 반대로 오른쪽을 클릭한 후 왼쪽으로 드래그하면 오히려 SelStart가 더 커진다.

그래서 임의의 오프셋 nPos가 선택영역 안인지 검사해보기 위해 SelStart <= nPos < SelEnd와 같은 부등식을 쓸 수 없다. SelStart SelEnd는 반드시 크기순으로 정렬되어야 하며 이 정렬된 결과를 저장할 변수가 SelFirst, SelSecond이다. SelFirst는 둘 중 작은 값을 가지며 SelSecond는 둘 중 큰 값을 가지므로 SelFirst는 반드시 SelSecond보다 크거나 같으며 따라서 SelFirst <= nPos < SelSecond 부등식으로 nPos가 선택영역 안인지 검사할 수 있다.

범위 체크를 쉽게 하기 위해 이런 식으로 선택영역 범위를 크기 순으로 정렬하는데 이 과정을 정규화(Normalize)라고 한다. 정규화란 임의의 정보를 좀 더 다루기 쉽게 규격화함으로써 코드를 간단하게 하는 기법이며 이후 선택영역을 다루는 루틴에서 자주 등장할 것이다. 아주 사소한 기법인 것 같지만 정규화의 효과는 결코 무시할 수 없을 정도로 대단하므로 애써 기억해놓을 가치가 있는 기법이다.

각 조각을 나눌 때 이 조각이 선택영역 안인지 조사하여 bInSel 변수에 그 결과를 대입하는데 조각의 종류에 따라 bInSel을 조사하는 방법이 조금씩 다르다. 각 조건문에서 bInSel TRUE가 되기 위해서는 당연히 선택영역이 있어야 하므로 SelStart != SelEnd 조건문은 공통적으로 포함되어 있다.

탭과 일반 문자열의 경우 nowoff 즉 조각의 첫 부분이 선택영역에 포함되어 있는지를 보면 된다. 같은 조각 내에서는 모든 문자의 속성이 같으므로 대표적으로 조각의 첫 위치만 범위 점검을 하였다. 선택의 시작점을 만난 경우, 즉 일반 문자열이다가 선택이 시작되는 시점이라면 무조건 비선택 상태이다. 반대로 선택의 끝 점을 만난 경우는 선택영역이 끝나는 시점이므로 무조건 선택 상태이다.

이 조건문에서 len!=0이라는 조건이 필요한 이유는 줄의 처음부터 선택되어 있는 경우 또는 줄의 처음에서 선택이 끝난 경우는 아직 조각을 나눌 필요가 없기 때문이다. break해 봐야 길이가 0이므로 출력할 조각도 없다. 그래서 조각의 길이가 0인 상태에서는 선택영역의 경계를 무시한다.

루프에서 탭과 선택영역의 경계를 기준으로 조각을 나누어 주고 bInSel에 조각의 선택 여부를 조사하면 이제 전경색과 배경색을 결정한 후 DrawSegment를 호출하면 된다. bInSel TRUE이면 SelFore, SelBack을 선택하고 그렇지 않다면 검정색과 윈도우 배경색을 선택하면 된다. DrawSegment는 조각을 출력할 때 색상을 적용해야 하므로 두 개의 인수를 추가로 전달받는다.

 

void DrawSegment(HDC hdc, int &x, int y, int SegOff, int len, BOOL ignoreX,

     COLORREF fore, COLORREF back)

{

     int docx;

    int oldx;

    RECT rt;

    HBRUSH Brush;

 

     if (buf[SegOff] == ‘\t’) {

        oldx=x;

          docx=x+xPos;

          docx=(docx/TabSize+1)*TabSize;

          x=docx-xPos;

        SetRect(&rt,oldx,y,x+1,y+FontHeight);

        Brush=CreateSolidBrush(back);

        FillRect(hdc,&rt,Brush);

        DeleteObject(Brush);

     } else {

        SetTextColor(hdc,fore);

        SetBkColor(hdc,back);

          TextOut(hdc,x,y,buf+SegOff,len);

          if (ignoreX == FALSE) {

               x+=GetCharWidth(hdc,buf+SegOff,len);

          }

     }

}

 

전달받은 색상값은 각각 SetTextColor, SetBkColor 함수의 인수로 사용되었다. 탭문자도 선택영역 안에 있을 때는 배경색을 가져야 하며 이제 더 이상 여백이 아니다. 그래서 탭이 차지하는 사각 영역을 계산한 후 전달된 배경색으로 사각형을 채워 주었다.

여기까지 실습한 후 실행해보아라. 아직 선택영역을 마우스로 바꾸는 기능은 없지만 두 번째 단어가 선택된 모습을 보여줄 것이다. 이제 마우스든 키보드든 SelStart SelEnd만 잘 조정하면 선택영역을 만들 수 있다. 다음 실습으로 넘어가기 전에 OnCreate에 작성했던 SelStart SelEnd의 임시값은 제거하도록 하자.