. 선택영역 확장

반전 선택법으로 선택영역을 표시할 때도 선택을 만드는 방법은 거의 동일하다. 다만 다시 그리지 않기 때문에 마우스의 이동에 의해 추가로 선택된 영역만 따로 반전시켜야 한다. 기존에 선택되어 있는 반전 블록을 그대로 두고 추가 영역만 구해 반전시키면 되는데 이 처리는 간단한 수학식 하나로 해결된다. 다음 함수가 추가 선택영역을 반전시켜 준다.

 

void InvertNewSel(HDC hdc, int s, int e1, int e2)

{

     HRGN rgn1, rgn2, rgnOR, rgnAND, rgnDIFF;

     MakeSelRgn(s, e1, rgn1);

     MakeSelRgn(s, e2, rgn2);

 

     rgnOR=CreateRectRgn(0,0,0,0);

     rgnAND=CreateRectRgn(0,0,0,0);

     rgnDIFF=CreateRectRgn(0,0,0,0);

     CombineRgn(rgnOR, rgn1, rgn2, RGN_OR);

     CombineRgn(rgnAND, rgn1, rgn2, RGN_AND);

     CombineRgn(rgnDIFF, rgnOR, rgnAND, RGN_DIFF);

 

     InvertRgn(hdc,rgnDIFF);

 

     DeleteObject(rgnOR);

     DeleteObject(rgnAND);

     DeleteObject(rgnDIFF);

     DeleteObject(rgn1);

     DeleteObject(rgn2);

}

 

기존 선택영역을 A라고 하고 새로 선택된 영역을 B라고 할 때 추가로 반전시켜야 할 영역은 A B의 합집합에서 A B의 교집합을 뺀 영역이다. InvertNewSel 함수는 선택 시작 점 s와 이전의 선택 끝점 e1, 새 선택 끝점 e2를 인수로 전달 받아 두 선택영역의 리전을 구한 후 공식에 따라 새로 반전시킬 리전을 구한다. 그리고 InvertRgn 함수로 이 리전을 반전시킨다.

이 공식은 선택영역이 확장될 때뿐만 아니라 선택영역이 축소될 때도 그대로 적용된다. 왜냐하면 반전된 영역을 다시 반전시키면 원래대로 돌아오기 때문이다. , 이 함수는 두 선택영역으로부터 확장되어야 할 부분, 또는 축소되어야 할 부분을 계산해서 그만큼을 반전시켜 준다. 마우스 메시지를 처리하는 코드는 다시 그리는 방법과 거의 유사하다.

 

void OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)

{

     HDC hdc;

 

     UpdateWindow(hWnd);

     hdc=GetDC(hWnd);

     InvertSelect(hdc);

     ReleaseDC(hWnd,hdc);

     SelStart=SelEnd=0;

 

     off=GetOffFromXY(x+xPos,y+yPos);

     SelStart=SelEnd=off;

     SetCapture(hWnd);

     bCapture=TRUE;

     SetCaret();

}

 

void OnMouseMove(HWND hWnd, int x, int y, UINT keyFlags)

{

     HDC hdc;

     int OldSelEnd;

     BOOL bInstTimer;

     RECT crt;

     int r,c;

     int s,e;

 

     if (bCapture == FALSE)

          return;

 

     OldSelEnd=SelEnd;

     off=SelEnd=GetOffFromXY(x+xPos,y+yPos);

 

     hdc=GetDC(hWnd);

     if (OldSelEnd != SelEnd) {

          InvertNewSel(hdc,SelStart,OldSelEnd,SelEnd);

     }

     ReleaseDC(hWnd,hdc);

 

     bInstTimer=FALSE;

     GetClientRect(hWnd,&crt);

     if (y>crt.bottom) {

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

          bInstTimer=TRUE;

     }

     if (y <0) {

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

          bInstTimer=TRUE;

     }

 

     if (nWrap == 0) {

          GetRCFromOff(SelEnd,r,c);

          GetLine(r,s,e);

          if (x>crt.right && SelEnd != e) {

               SendMessage(hWnd, WM_HSCROLL, SB_LINERIGHT, 0L);

               bInstTimer=TRUE;

          }

          if (x<0 && SelEnd != s) {

               SendMessage(hWnd, WM_HSCROLL, SB_LINELEFT, 0L);

               bInstTimer=TRUE;

          }

     }

 

     if (bInstTimer==TRUE) {

          SetTimer(hWnd, 1, 200, NULL);

     } else {

          KillTimer(hWnd, 1);

     }

     SetCaret();

}

 

void OnLButtonUp(HWND hWnd, int x, int y, UINT keyFlags)

{

     bCapture=FALSE;

     ReleaseCapture();

     KillTimer(hWnd,1);

}

 

void OnTimer(HWND hWnd, UINT id)

{

     POINT pt;

 

     switch (id) {

     case 1:

          GetCursorPos(&pt);

          ScreenToClient(hWnd, &pt);

          SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y));

          break;

     }

}

 

OnLButtonUp, OnTimer는 다시 그리는 방법과 완전히 동일하며 OnLButtonDown, OnMouseMove에서 선택을 만드는 방법만 조금 다르다. 이전 선택의 끝점을 OldSelEnd에 보관해놓고 새로운 선택 끝점과 함께 InvertNewSel 함수를 호출하면 된다. 선택영역이 바뀌었더라도 화면을 다시 그릴 필요는 없기 때문에 InvalidateRect 함수를 호출할 필요는 없다. 타이머를 사용하여 스크롤시키는 방법도 완전히 동일하다.

선택영역을 통째로 반전시키기 때문에 줄간의 사이나 줄의 뒷부분까지 같이 선택되는 점이 조금 다르며 화면을 다시 그리지 않기 때문에 깜박임이 전혀 없다. 이 예제의 경우 아직 최적화가 되지 않았기 때문에 스크롤 속도가 느리지만 스크롤만 빨리 된다면 반전 선택법은 굉장히 빠른 속도로 동작한다. 예문이 전부 보일 정도로 윈도우를 크게 만들어서 테스트해보면 마우스를 아무리 빨리 움직여도 선택영역이 마우스를 잘 따라다니는 것을 확인할 수 있다.