. 시작 옵션

StartAction은 프로그램이 시작될 때 어떤 동작을 할 것인가를 지정하는 옵션이다. 아무것도 없는 빈 상태로 시작하거나 아니면 새 문서를 만들어 줄 수도 있으며 이전에 편집하던 문서를 열어 줄 수도 있다. 텍스트 편집기를 어떤 용도로 사용하는가에 따라 이 옵션을 적절하게 조정할 수 있는데 주로 문서 작성용으로 쓴다면 새 문서를 만드는 것이 좋겠고 문서편집용으로 쓴다면 편집하던 모든 파일을 다 열어주는 것이 좋을 것이다. 또 단순한 뷰어로만 사용한다면 빈 상태로 시작하는 것이 가장 깔끔하다.

StartAction의 디폴트값은 2로 되어 있으며 마지막 편집하던 문서를 기억했다가 다시 열어 준다. 그런데 아직까지 이 옵션이 적용되어 있지 않으므로 시작 직후에 New 함수를 호출하여 새 문서를 작성하도록 되어 있는데 이제 이 옵션을 적용해보자. 이 옵션을 구현하려면 먼저 편집하던 파일의 목록부터 저장해야 한다. 그래야 마지막 편집하던 파일을 다음 번 실행시에 열어줄 수가 있다.

편집하던 파일의 목록은 사용자가 의도적으로 만든 영구적인 설정값이 아니라 임시적인 정보에 불과하기 때문에 SOption에는 포함되지 않는다. 다음 번 실행을 위해 잠시 값을 저장해 둘 뿐이다. MRU나 마지막 실행 위치도 마찬가지 이유로 SOption에 포함시키지 않았다. 똑같이 레지스트리에 저장되지만 어떤 값은 SOption에 포함되고 어떤 값은 그렇지 않은데 어떤 차이점이 있을까? 이 두 부류의 정보들은 권장옵션 버튼을 클릭했을 때 기본값으로 돌아가느냐 아니냐가 다르다.

MRU나 편집파일의 목록은 권장옵션 버튼을 클릭했다고 해서 삭제하거나 다시 초기화할 필요가 없는 정보들이다. 사용자의 의도와 무관하게 저장되고 적당한 때가 되면 자동으로 삭제되는 임시 정보이기 때문에 SOption에 포함되지 않는 것이다. 그래서 편집하던 파일의 목록은 SOption::Save에서 저장되지 않으며 프로그램 종료 직전인 OnDestroy에서 직접 저장해야 한다.

 

void OnDestroy(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

    HWND hChild, hActive;

    TCHAR szKey[16];

    int nEditing;

    SInfo *pSi;

    BOOL bMax;

 

     Option.Save(KEY);

 

    nEditing=0;

    hChild=GetWindow(g_hMDIClient,GW_CHILD);

    while (hChild) {

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

        if (strncmp(pSi->NowFile,"이름없음",8)!=0) {

           wsprintf(szKey,"%d",++nEditing);

           SHRegWriteString(SHCU,KEY"Editing",szKey,pSi->NowFile);

        }

        hChild=GetWindow(hChild,GW_HWNDNEXT);

    }

    SHRegWriteInt(SHCU,KEY"Editing","Num",nEditing);

 

    SHRegWriteString(SHCU,KEY"Editing","Active","");

    hActive=(HWND)SendMessage(g_hMDIClient,WM_MDIGETACTIVE,0,(LPARAM)&bMax);

    if (hActive) {

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

        if (strncmp(pSi->NowFile,"이름없음",8)!=0) {

           SHRegWriteString(SHCU,KEY"Editing","Active",pSi->NowFile);

           SHRegWriteInt(SHCU,KEY"Editing","ActiveMax",bMax);

        }

    }

 

     if (arFont)

          free(arFont);

     PostQuitMessage(0);

     SavePosition(hWnd,KEY"Position");

     Mru.SaveMRU();

}

 

레지스트리의 Editing 서브키 아래에 편집하던 파일에 대한 모든 정보를 저장하되 단, 디스크에 저장되지 않은 파일(이름없음 n)은 다음 번 실행시에 열 수가 없으므로 저장하지 않는다. 모든 차일드를 순회하면서 각 창의 편집파일 경로를 1,2,3,4,... 키에 저장하였다. 모든 파일을 저장한 후 Num키에 저장한 파일의 개수를 기록해두었다.

활성창에 대한 경로는 별도로 한 번 더 기록하여 다음 번 실행시 편집하던 파일에 포커스를 줄 수 있도록 하였다. Active가 편집중인 파일의 경로이며 활성창은 특별히 최대화 상태도 같이 저장한다. 그래야 활성창의 상태까지 원래 상태 그대로 복구할 수 있다.

편집파일의 목록을 저장하는 동작은 StartAction 옵션과는 상관이 없다. 이 옵션이 어떤 값을 가지더라도 일단은 모든 파일의 목록을 완전히 저장한다. StartAction 옵션은 이렇게 저장된 값 중 어떤 값을 사용할 것인가를 지정할 뿐이지 어떤 값을 저장할 것인가에 대해서는 상관하지 않는다. 다음 실행시에 불필요할지도 모르는 정보를 저장하는 셈이지만 꼭 필요한 정보만 저장하자면 조건 판단을 한 번 더 해야 하므로 오히려 더 복잡해진다.

이렇게 저장된 정보는 다음 번 실행시에 읽혀지는데 OnCreate에서 읽지 않고 OnTimer에서 읽는다. 그 이유는 Dangeun1을 만들 때 이미 설명했지만 초기화가 완전히 완료되어야 다음 동작을 할 수 있기 때문이다. OnTimer에 꽤 많은 코드가 작성되어야 한다.

 

void OnTimer(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

    int i;

    HWND hChild;

    TCHAR szKey[16];

    int nEditing;

    TCHAR Path[MAX_PATH];

    BOOL bOldMaxFirstChild;

 

     switch (wParam) {

     case 100:

          KillTimer(hWnd,100);

        bOldMaxFirstChild=Option.bMaxFirstChild;

        if (SHRegReadInt(SHCU,KEY"Editing","ActiveMax",1)==1) {

           Option.bMaxFirstChild=TRUE;

        } else {

           Option.bMaxFirstChild=FALSE;

        }

 

        switch (Option.StartAction)

        {

        case 0:

           break;

        case 1:

           New();

           break;

        case 2:

           nEditing=SHRegReadInt(SHCU,KEY"Editing","Num",0);

           SHRegReadString(SHCU,KEY"Editing","Active","",Path,MAX_PATH);

           if (nEditing && lstrlen(Path) != 0) {

               OpenFromFile(Path);

           } else {

               New();

           }

           break;

        case 3:

           nEditing=SHRegReadInt(SHCU,KEY"Editing","Num",0);

           for (i=1;i<=nEditing;i++) {

               wsprintf(szKey,"%d",i);

               SHRegReadString(SHCU,KEY"Editing",szKey,"",Path,MAX_PATH);

               OpenFromFile(Path);

           }

           SHRegReadString(SHCU,KEY"Editing","Active","",Path,MAX_PATH);

           if (lstrlen(Path)!= 0) {

               hChild=FindChildWithFile(Path);

               SendMessage(g_hMDIClient,WM_MDIACTIVATE,(WPARAM)hChild,0);

           }

           break;

        }

        Option.bMaxFirstChild=bOldMaxFirstChild;

          break;

     }

}

 

StartAction 옵션에 따라 4가지 분기를 하고 있다. 0이면 아무 것도 하지 않고 1이면 New 함수를 호출하여 새 문서를 만들어 준다. 2는 마지막 편집하던 파일 중 활성화 되어 있던 파일 하나만 다시 읽어온다. 3은 편집하던 모든 파일을 읽어오고 마지막 편집하던 파일을 활성화시킨다. 0 1번은 아주 쉽게 이해가 될 것이고 2 3번도 유사하다.

마지막 활성화된 창을 읽어올 때는 최대화 상태를 조사하여 그대로 복구한다. 즉 최대화 상태에서 작업중이었다면 최대화 상태로 열어주고 그렇지 않다면 노말 상태로 열어준다. 레지스트리에 저장된 작업자의 이전 상태는 bMaxFirstChild 옵션보다 더 우선권을 가지는 것이 논리적으로 합당하다. bMaxFirstChild 옵션은 새로 창을 만들 때 최대화 상태를 지정하는 것이지 이전 편집파일을 복구할 때는 적용하지 말아야 한다.

그래서 전역변수 Option.bMaxFirstChild 값을 잠시 보관해놓고 이 값을 레지스트리에 저장된 활성창 최대화 값으로 변경한 상태로 창을 만든 후 다시 복구했다. 창을 만드는 주체인 New 함수를 감쪽같이 속이는 것이다. 전역변수를 잠시 다른 값으로 바꾸는 이런 이상한 방법이 무척 못 마땅해 보일 것이며 사실 나도 별로 마음에 들지는 않는다. 이 방법 대신 New 함수에게 무조건 최대화하라는 명령을 별도의 인수로 전달하는 방법을 쓸 수도 있다. 그러자면 New 함수에게 이 인수를 전달하기 위해 OpenFromFile부터 수정해야 하는데 이는 무척 번거로운 일이며 설계 이론상으로도 OpenFromFile이 직접 쓰지 않는 통과 인수를 두는 것은 별로 좋은 방법이 아니다.

또는 일단 창을 생성한 후 WM_MDIMAXIMIZE 메시지를 보내 방금 생성한 활성창을 최대화할 수도 있으나 애니메이션이 일어나 느리고 보기에 좋지 않다. 소프트웨어 공학 이론에 따라 정석대로 코드를 작성할 수도 있지만 실전에서는 저런 지저분해보이는 코드가 오히려 더 성능이 좋을 수도 있다.

StartAction이 적용되는 시점은 프로그램이 초기화를 완료했을 때인 OnTimer 뿐이다. 따라서 ApplyNow는 이 옵션이 변경되었다고 해서 어떤 조치를 할 필요가 없다. Option=NewOption 대입문으로 옵션의 현재 상태만 저장해두면 SOption::Save가 이 변경상태를 레지스트리에 저장하고 다음 실행시 SOption::Load가 이 값을 읽고 OnTimer에 의해 적용된다.

StartAction으로 가능한 값은 0~3까지 모두 4가지가 있는데 이 프로그램은 정수값을 직접 사용하고 있다. 기억의 용이함을 위해 각각의 값에 대해 #define으로 매크로 상수를 정의하거나 아니면 열거형으로 정의하는 것이 더 권장되는 바다. 하지만 이 변수의 값은 0~3까지 갈수록 강도가 증가하는 성질이 있어 직관적으로 이해하기 쉬운데다 OnTimer 한 군데에서만 참조하고 있기 때문에 상수를 바로 사용했다.

한 번만 쓸 상수를 위해 매크로를 정의하는 방식을 개인적으로 좋아하지 않는데다 천성적으로 문자열보다 숫자를 더 잘 기억하기 때문이다. 매크로가 오히려 더 혼란스러울 수도 있다고 스스로 정당화하고는 있지만 솔직히 별로 좋은 습관은 아니므로 배우지는 말기 바란다. 정석대로 하자면 열거형을 쓰는 것이 가장 깔끔하다. 이런 코딩 스타일은 개발자마다 백인백색으로 다르며 분명히 옳고 그름의 문제가 아니지만 그래도 많은 지적을 받곤한다. 그래서 누구든지 자기 소스를 남에게 보여준다는 것은 심적으로 엄청나게 부담스러운 일인 것 같다.