. 기록 시점

취소 레코드는 사용자의 삽입, 삭제, 이동동작을 기록하며 그 시점은 각 명령에 대응되는 함수인 Insert, Delete, CopyString 함수이다. 이 함수들에서 레코드 기록 함수들을 호출하게 되는데 예를 들어 Insert 함수는 방금 문자열이 삽입되었음을 레코드 기록 함수에게 알려야 한다. 레코드 기록 함수를 작성하기 전에 먼저 이 함수들이 언제 레코드를 기록할 것인지를 결정할 수 있도록 수정할 필요가 있다.

이 세 함수들은 문서를 직접 변경시키는 동작을 하지만 그렇다고 해서 항상 취소 레코드에 편집 동작을 기록하는 것은 아니다. 사용자가 직접 편집을 하고 있을 때, 문서 내용이 실제로 변경될 때만 편집 동작을 기록해야 한다. Insert, Delete, CopyString에서 문서 내용을 변경하더라도 취소 레코드는 작성하지 않아야 할 경우가 있는데 이를 위해 레코드 기록 여부를 전달 받는 bRec 인수를 추가하도록 하자. 원형을 다음과 같이 수정한다.

 

class CApiEdit

{

     ....

     void Insert(int nPos, TCHAR *str, BOOL bRec=TRUE);

     void Delete(int nPos, int nCount, BOOL bRec=TRUE);

     ....

     void CopyString(BOOL bCopy, int from, int &to, int len, BOOL bRec=TRUE);

 

모두 bRec이라는 인수를 추가했으며 이 인수의 디폴트값은 TRUE로 설정하여 기존 코드가 영향을 받지 않도록 하였다. 레코드를 기록하지 말아야 할 시점에서 이 함수들을 부를 때는 bRec FALSE를 전달해야 한다. 그렇다면 어떤 상황이 편집 동작을 기록하지 말아야 할 때인지 알아보도록 하자.

첫 번째는 SetText 함수에서 문서 텍스트를 대입 받을 때이다. 호스트의 입장에서 본다면 파일을 열어서 ApiEdit에 텍스트를 전달하는 시점인데 이 때는 편집을 시작하기 직전이므로 텍스트 대입 자체를 기록할 필요는 없다. 파일을 읽는 동작 자체는 컨트롤의 입장에서는 취소 대상이 아니므로 이 함수에서 호출하는 Insert 함수의 bRec FALSE가 되어야 한다.

 

void CApiEdit::SetText(TCHAR *TextBuf)

{

     InitDoc();

    Insert(0,TextBuf,FALSE);

}

 

두 번째는 문자열을 이동시키는 CopyString 함수에서 호출하는 Insert, Delete 문이다. 문자열 이동은 삭제 후 삽입과 같은데 이 두 동작은 묶어서 이동으로 정의되므로 따로 취소 레코드를 기록할 필요가 없다. UndoRecord는 이미 이동동작을 위해 UR_MOVE 액션을 따로 정의해놓았기 때문에 이 함수가 호출하는 Insert, Delete 함수의 bRec FALSE여야 한다.

 

void CApiEdit::CopyString(BOOL bCopy, int from, int &to, int len, BOOL bRec/*=TRUE*/)

{

     TCHAR *t;

     int orito=to;

 

     t=(TCHAR *)malloc(len+1);

     lstrcpyn(t,buf+from,len+1);

    Insert(to,t,FALSE);

 

     if (bCopy==FALSE) {

          if (to > from) {

           Delete(from,len,FALSE);

              to-=len;

          } else {

           Delete(from+len,len,FALSE);

          }

     }

     free(t);

}

 

취소 레코드를 기록하지 않는 세 번째 경우는 한글을 조립하는 OnImeComposition 함수이다. 조립중인 문자를 변경하기 위해 삭제, 삽입을 연거푸 호출하는데 이때는 문서가 바뀌기는 하지만 아직 글자가 완성된 것이 아니므로 취소 레코드에 기록하기에는 너무 이르다. 예를 들어 자 상태에서 ㄴ이 입력되어 자가 될 때 를 삭제하고 을 다시 삽입하는데 이 두 동작은 따로 기록할 필요가 없다. 이 함수를 다음과 같이 수정한다.

 

LRESULT CApiEdit::OnImeComposition(HWND hWnd, WPARAM wParam, LPARAM lParam)

{

     ....

              if (bComp) {

                   off-=2;

               Delete(off,2,FALSE);

                   bNewIns=FALSE;

              }

              if (len == 0) {

                   bComp=FALSE;

              } else {

                   bComp=TRUE;

              }

 

              if (bOvr && bNewIns && bPrevSel==FALSE) {

                   if (IsDBCS(buf,off)) {

                        if (buf[off] != ‘\r’) {

                            Delete(off,2);

                        }

                   } else {

                        Delete(off,1);

                   }

              }

 

           Insert(off,szComp,FALSE);

              off+=len;

          }

     ....

}

 

조립중의 삭제 동작과 조립 후의 삽입 동작에 대해서는 취소 레코드를 기록하지 않도록 하였다. 이 함수는 조립중인 한글만 다루므로 따로 기록하지 않더라도 글자가 완성될 때마다 OnImeChar에서 완성된 글자에 대해 취소 레코드를 제대로 작성하게 된다.

덮어쓰기 모드일 때는 취소 레코드를 기록하도록 했는데 덮어쓰기는 삭제 후 삽입으로 정의되어 있고 이 정의에 의해 Delete를 호출할 때 취소 레코드를 기록한다. 따라서 덮어 쓰기 모드에서는 한글자 입력할 때마다 삭제 레코드와 삽입 레코드가 각각 하나씩 생성되어 별로 바람직하지 않다. 대체 액션을 따로 정의한다면 덮어쓰기를 한 번의 액션으로 처리할 수도 있지만 너무 복잡해지는데다 다행히 덮어쓰기 모드는 잘 사용되지 않으므로 이대로 두기로 했다. 보기에 좀 안 좋을 뿐이지 기능상의 문제는 없다.

이 세 경우에 취소 레코드를 왜 기록하지 말아야 하는지는 어렵지 않게 이해가 될 것이다. 이외에 레코드 기록을 하지 말아야 할 가장 중요한 시점이 있는데 바로 취소/재실행할 때이다. 삽입 레코드를 취소하면 삭제인데 이때 호출되는 Delete 함수는 bRec FALSE로 줘야 한다. 삽입을 취소하고자 삭제를 하는데 이 삭제 동작이 또 기록된다면 취소 레코드는 무한히 커지기만 할 것이다. 재실행할 때도 마찬가지로 취소 레코드를 기록하지 말아야 한다. 취소/재실행시의 취소 레코드는 기록 대상이 아니라 단순히 사용 대상일 뿐이다.

Insert, Delete, CopyString 함수가 적절한 시기에 취소 레코드를 작성할 수 있도록 준비만 했으며 아직 레코드를 작성하는 코드는 만들지 않았다. 잠시 후 취소 레코드를 기록하는 함수를 작성한 후 편집시에 이 함수들을 호출하도록 다시 수정할 것이다.