. 상태란 나누기

상태란에 보여줄 정보의 종류가 많으면 몇 개의 칸으로 나눈 후 각 칸에 서로 다른 여러 개의 정보를 출력할 수 있다. 이때 상태란의 분할된 각 칸을 파트라고 하며 원하는 수만큼 원하는 폭으로 나눌 수 있다. 다음 함수는 상태란을 여러 파트로 분할한다.

 

void SetStatusPart()

{

     int SBPart[6];

     RECT crt;

     HWND hActive;

     hActive=(HWND)SendMessage(g_hMDIClient,WM_MDIGETACTIVE,0,NULL);

 

     GetClientRect(g_hFrameWnd,&crt);

     if (g_ChildNum) {

          SBPart[0]=max(200,crt.right-430);

          SBPart[1]=SBPart[0]+70;

          SBPart[2]=SBPart[1]+130;

          SBPart[3]=SBPart[2]+80;

          SBPart[4]=SBPart[3]+70;

          SBPart[5]=SBPart[4]+80;

 

          SendMessage(hStatus, SB_SETPARTS, 6, (LPARAM)SBPart);

     } else {

          SBPart[0]=crt.right;

          SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)SBPart);

     }

}

 

활성 차일드가 있으면 6개의 파트를 나누어 각 파트에 문서에 대한 여러 가지 정보를 보여주도록 한다. 각 파트는 보여줄 정보의 길이에 따라 적당한 길이를 갖도록 했으며 첫 번째 파트는 최소한 200픽셀의 폭을 가지도록 하였다. 활성창이 없으면 보여줄 정보가 없으므로 한 개의 파트만 나눈다. 이 함수는 DGChildProc에서 첫 차일드를 생성할 때나 또는 마지막 차일드를 파괴할 때 호출된다.

 

LRESULT CALLBACK DGChildProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     SInfo *pSi;

     int idx;

 

     switch(iMessage) {

     case WM_CREATE:

          g_ChildNum++;

          if (g_ChildNum == 1) {

              SendMessage(g_hMDIClient,WM_MDISETMENU,(WPARAM)hMenu1,

                   (LPARAM)GetSubMenu(hMenu1,5));

              Mru.ChangeMenu(GetSubMenu(GetSubMenu(hMenu1,0),8));

              DrawMenuBar(g_hFrameWnd);

           if (Option.bShowStatus) {

               SetStatusPart();

           }

          }

          ....

     case WM_DESTROY:

          pSi=(SInfo *)GetWindowLong(hWnd,0);

          delete pSi;

          g_ChildNum--;

          if (g_ChildNum==0) {

              SendMessage(g_hMDIClient,WM_MDISETMENU,(WPARAM)hMenu2,NULL);

              Mru.ChangeMenu(GetSubMenu(GetSubMenu(hMenu2,0),3));

              DrawMenuBar(g_hFrameWnd);

           if (Option.bShowStatus) {

               SetStatusPart();

           }

          }

          ....

 

물론 상태란이 보이는 상태일 때만 SetStatusPart를 호출하며 상태란이 보이지 않는다면 굳이 이 함수를 호출할 필요가 없다. 활성창의 유무에 따라 상태란의 파트 구성이 달라질 것이다. 메인 윈도우의 크기가 변경되면 각 파트의 크기도 같이 조정되어야 하므로 Relayout 함수에서도 파트의 크기를 재조정해야 한다.

 

void Relayout()

{

     ....

     if (Option.bShowStatus) {

          SendMessage(hStatus,WM_SIZE,0,0);

          ShowWindow(hStatus,SW_SHOW);

        SetStatusPart();

     } else {

          ShowWindow(hStatus,SW_HIDE);

     }

 

다음 함수는 이렇게 나누어진 각 파트에 조사된 정보를 출력한다. 0번 파트는 특별히 출력할 문자열을 따로 인수로 전달받을 수 있다. 만약 특별한 문자열이 전달되지 않으면 디폴트 문자열인 당근 편집기입니다가 대신 출력된다.

 

void SetStatusText(int mask,LPCTSTR Mes/*=NULL*/)

{

     TCHAR Text[128];

     HWND hActive;

     SInfo *pSi;

     int byte;

 

     hActive=(HWND)SendMessage(g_hMDIClient,WM_MDIGETACTIVE,0,NULL);

     if (hActive==NULL) {

          return;

     }

     pSi=(SInfo *)GetWindowLong(hActive,0);

 

     if (mask & 1) {

          if (Mes==NULL) {

              SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)"당근 편집기입니다");

          } else {

              SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)Mes);

          }

     }

 

     if (mask & 2) {

          wsprintf(Text, "%d %d",pSi->Ae.GetInfoFromOff(2)+1,pSi->Ae.GetInfoFromOff(4)+1);

          SendMessage(hStatus, SB_SETTEXT, 1, (LPARAM)Text);

     }

 

     if (mask & 4) {

          byte=pSi->Ae.GetTextLength();

          if (byte < 10240) {

              wsprintf(Text, " %d %d 바이트",pSi->Ae.GetDocInfo(0)+1,byte);

          } else {

              wsprintf(Text, " %d %d K바이트",pSi->Ae.GetDocInfo(0)+1,byte/1024);

          }

          SendMessage(hStatus, SB_SETTEXT, 2, (LPARAM)Text);

     }

 

     if (mask & 8) {

          if (pSi->Ae.GetModified()) {

              lstrcpy(Text,"편집되었음");

          } else {

              lstrcpy(Text,"미편집");

          }

          SendMessage(hStatus, SB_SETTEXT, 3, (LPARAM)Text);

     }

    

     if (mask & 0x10) {

          lstrcpy(Text,"PC 포맷");

          SendMessage(hStatus, SB_SETTEXT, 4, (LPARAM)Text);

     }

 

     if (mask & 0x20) {

          if (pSi->Ae.GetInsMode()) {

              lstrcpy(Text,"겹침모드");

          } else {

              lstrcpy(Text,"삽입모드");

          }

          SendMessage(hStatus, SB_SETTEXT, 5, (LPARAM)Text);

     }

}

 

인수 mask는 어떤 파트에 정보를 출력할 것인가를 지정하는데 하위 비트부터 각 파트에 대응된다. 0번 비트가 1이면 파트 0에 문자열이 출력되며 1번 비트가 1이면 파트 1에 정보가 출력된다. 모든 파트에 문자열을 출력하려면 0xffff를 마스크로 지정하면 된다. 당근은 상태란에 출력할 정보 조사를 위해 ApiEdit의 각종 함수를 적절히 호출한다. 각 파트에 어떤 정보들이 어떻게 출력되는지 보자.

파트0은 인수로 전달된 Mes 문자열이 출력되는데 현재 상태에 대한 도움말이나 작업 결과를 출력할 목적으로 사용된다. 파트1은 현재 캐럿 위치를 보여주는데 ApiEdit가 조사하는 모든 정보는 Zero Base이고 사용자에게 보여지는 숫자는 One Base이므로 조사된 위치값에 1을 더해야 한다.

파트 2에는 문서의 크기를 줄단위와 바이트 단위로 보여주는데 문서 크기가 10240바이트를 넘을 경우 킬로 바이트 단위로 보여주도록 했다. 파트 3에는 문서의 편집 여부를, 파트 4에는 문서의 포맷을 보여주고 파트 5에는 덮어쓰기 모드의 현재 상태를 보여준다.

상태란의 정보가 변경될 때는 호스트가 통지 메시지를 받았을 때인데 전달받은 통지 메시지의 종류에 따라 수정해야 할 정보의 종류(마스크)가 다르다. 예를 들어 AEN_MOVE 메시지를 받았을 때는 1번 파트의 캐럿 위치만 갱신하면 되므로 마스크로 0x2를 전달하여 꼭 필요한 정보만 갱신하도록 했다. 활성 차일드가 변경될 때는 모든 정보가 한꺼번에 변경되므로 마스크로 0xffff를 전달하여 모든 파트의 텍스트를 갱신하도록 한다.

 

LRESULT CALLBACK DGChildProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     ....

    case WM_COMMAND:

        switch (LOWORD(wParam)) {

        case 1:

           switch (HIWORD(wParam)) {

           case AEN_MOVE:

               SetStatusText(0x2);

               break;

           case AEN_CHANGE:

               SetStatusText(0x1c);

               break;

           case AEN_INSMODE:

               SetStatusText(0x20);

               break;

           case AEN_CHGMODI:

               SetStatusText(0x8);

               break;

           }

           break;

        }

        return 0;

     case WM_SIZE:

          pSi=(SInfo *)GetWindowLong(hWnd,0);

          MoveWindow(pSi->Ae.hWnd,0,0,LOWORD(lParam),HIWORD(lParam),TRUE);

          break;

     case WM_SETFOCUS:

          pSi=(SInfo *)GetWindowLong(hWnd,0);

          SetFocus(pSi->Ae.hWnd);

        SetStatusText(0xffff);

          return 0;

 

상태란에 정보가 어떻게 조사되고 출력되는지 어렵지 않게 분석될 것이다. 이 시점에서 간단한 퀴즈를 하나 풀어 보자. SetStatusText 함수에서 hActive NULL인 것은 왜 점검해야 할까? 이 함수는 DGChildProc에서만 호출하는데 DGChildProc이 실행중이라는 것은 이미 hActive가 있다는 뜻이므로 굳이 활성창이 있는지를 점검할 필요는 없지 않겠는가?

그럴 것 같지만 아주 특수한 경우 즉, 차일드가 생성되는 시점에서 통지 메시지가 전달될 때가 문제가 된다. 아직 활성창이 덜 만들어진 상태에서 이 함수가 호출될 수도 있는데 그대로 두면 죽는다. SetStatusText 함수에서 hActive를 점검하는 목적은 이 창이 완전히 다 만들어진 것인지를 점검하는 것이다.