. 취소

취소 레코드가 완벽하게 기록되어 있으면 취소/재실행 코드를 만드는 것은 아주 쉽다. 레코드 내용을 읽어 기록된 편집 동작을 거꾸로 실행하기만 하면 된다. 편집에 관련된 모든 서브루틴이 이미 완비되어 있으므로 전혀 어려울 것이 없다. 취소 함수인 Undo를 바로 작성해보자. 코드는 다음과 같이 좀 길어 보이지만 단순한 문장들뿐이다.

 

void CApiEdit::Undo()

{

     int from, to, len, dest;

 

     if (pUR[nowur].action == UR_NONE || pUR[nowur].status==UR_CANCELED) {

          nowur--;

     }

 

     SelStart=SelEnd=0;

 

     switch (pUR[nowur].action) {

     case UR_INSERT:

          Delete(pUR[nowur].pos,lstrlen(pUR[nowur].data),FALSE);

          off=pUR[nowur].pos;

          SetCaret();

          Invalidate(-1);

          break;

     case UR_DELETE:

          Insert(pUR[nowur].pos,pUR[nowur].data,FALSE);

          off=pUR[nowur].pos+lstrlen(pUR[nowur].data);

          SetCaret();

          Invalidate(-1);

          break;

     case UR_MOVE:

          from=pUR[nowur].pos;

          to=pUR[nowur].dest[0];

          len=pUR[nowur].dest[1];

          if (from > to) {

              dest=from+len;

              CopyString(FALSE,to,dest,len,FALSE);

          } else {

              CopyString(FALSE,to-len,from,len,FALSE);

          }

          off=from+len;

          SetCaret();

          Invalidate(-1);

          break;

     }

 

     pUR[nowur].status=UR_CANCELED;

}

 

먼저 취소 대상이 되는 레코드를 찾아야 한다. 통상 현재 작성중인 레코드가 취소 대상이 되지만 액션이 정의되어 있지 않거나 이미 취소된 레코드인 경우는 이전 레코드가 취소 대상이 된다. 그래서 nowur 1 감소시키는데 이때 nowur이 음수가 될 가능성은 없다. 왜냐하면 nowur 0인 상태에서 액션이 없거나 취소된 레코드이면 CanUndo 함수가 FALSE를 리턴하여 이 함수가 호출되지 않도록 하기 때문이다.

취소하기 직전에 선택영역을 제거하기 위해 SelStart SelEnd는 모두 0으로 만든다. 취소나 재실행은 사용자의 조작이 아닌 기록된 내용을 참조하여 문서가 변경되는 동작이기 때문에 선택영역을 그대로 두면 취소/재실행에 의해 엉뚱한 곳을 선택할 위험이 있다. 또한 캐럿은 항상 선택영역의 끝인 SelEnd에 있어야 하는데 취소/재실행에 의해 이 조건을 만족시키지 못하는 상태가 될 수도 있으며 만에 하나라도 캐럿이 한글의 경계에 걸치게 되면 문제가 발생하므로 아예 선택영역은 없애는 것이 안전하다.

사용자들도 편집을 취소할 때 선택영역이 그대로 남아 있기를 기대하지는 않는다. 만약 ApiEdit가 선택과 선택의 해제 동작까지 편집 동작으로 간주하여 기록한다면 문제는 달라지겠지만 선택과 이동은 애초 설계할 때부터 편집 동작에서 제외되었다.

switch문은 취소 레코드의 action 값을 읽어 각 액션별로 취소를 구현한다. action UR_INSERT일 때 즉 삽입 레코드일 때는 이 레코드의 기록 내용대로 삽입된 문자열을 다시 삭제한다. 삭제를 위해 Delete 함수를 호출하되 삽입된 문자열의 위치와 길이를 인수로 전달하면 된다. 이때 취소를 위한 삭제는 다시 취소 레코드에 기록되지 말아야 하므로 Delete 함수의 bRec 인수는 반드시 FALSE여야 한다. 문자열을 삭제한 후 캐럿은 삽입되기 전의 위치로 옮겨 주고 화면은 다시 그린다. 아주 감쪽같이 삽입된 문자열이 사라질 것이다.

삭제도 거의 동일한 코드로 구성되어 있다. 삭제 동작을 취소하기 위해 Insert 함수를 호출하여 삭제된 문자열을 다시 삽입해 넣되 이때도 bRec인수는 반드시 FALSE여야 한다. 캐럿은 다시 삽입된 문자열의 끝으로 가도록 했다.

이동은 이동 방향이 두 가지로 구분되기 때문에 조금 복잡하다. 원래 위치와 이동된 위치, 길이를 dest 멤버에서 조사하고 이 값의 대소 비교에 의해 앞으로의 이동인가 뒤로의 이동인가에 따라 적절한 인수로 CopyString 함수를 호출하면 된다. 캐럿은 이동하기 전의 문자열 마지막으로 보낸다.

레코드의 동작을 취소한 후는 status UR_CANCELED로 바꿔 이 레코드가 취소된 레코드임을 표시해놓는다.