. 그룹의 작성

편집 그룹을 만들 준비가 되었으므로 이제 실제로 그룹을 만들어보자. 두 개의 편집 동작을 그룹으로 묶으려면 다음과 같이 코드를 작성하면 된다.

 

StartUndoGroup();

편집1

편집2

EndUndoGroup();

편집3

 

StartUndoGroup이 그룹 ID를 작성하여 URtick에 저장해놓을 것이다. 편집 동작을 기록하는 URInsert, URDelete 등의 함수는 이 ID를 취소 레코드의 tick 멤버에 저장함으로써 같은 그룹임을 표시한다. EndUndoGroup 함수에 의해 URtick 0이 되며 편집3 tick값은 0이 되어 단독 레코드로 기록된다. 그룹으로 묶고자 하는 부분의 시작과 끝에 Start(End)UndoGroup 함수만 불러 주면 되므로 사용방법은 아주 간단한 편이다.

현재까지의 편집기능에서 그룹을 구성하는 편집 동작은 선택 상태가 있는 상태에서 문자열을 입력할 때뿐이다. 이때는 문자열 대체가 발생하는데 한 레코드에 기록할 수 없으므로 두 개의 레코드에 각각 편집 동작을 기록하고 그룹으로 묶어야 한다. 3군데를 수정해야 하는데 우선 붙여넣기 코드를 수정해보자. IDM_AE_PASTE 메시지를 받았을 때 선택영역이 있다면 선택을 지우는 편집 동작과 문자열을 붙여넣는 편집 동작이 그룹을 구성한다. 선택영역이 없는 상태에서 붙여넣을 때는 그룹을 구성하지 않는다. 코드를 다음과 같이 수정한다.

 

void CApiEdit::OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify)

{

     ....

     case IDM_AE_PASTE:

        BOOL bPrevSel;

          if (IsClipboardFormatAvailable(CF_TEXT) && bReadOnly==FALSE) {

           if (SelStart != SelEnd) {

               StartUndoGroup();

           }

           bPrevSel=DeleteSelection();

              OpenClipboard(hWnd);

              hmem=GetClipboardData(CF_TEXT);

              ptr=(TCHAR *)GlobalLock(hmem);

              Insert(off,ptr);

           if (bPrevSel) {

               EndUndoGroup();

           }

              GlobalUnlock(hmem);

              CloseClipboard();

              Invalidate(FindParaStart(off));

              off += lstrlen(ptr);

              SetCaret();

          }

          return;

 

DeleteSelection 함수는 선택영역을 삭제하고 선택영역의 유무를 리턴하지만 삭제 동작부터 그룹으로 묶어야 하므로 DeleteSelection을 호출하기 전에 그룹 포함여부를 먼저 결정해야 한다. SelStart SelEnd가 다르다면 선택영역이 있으므로 StartUndoGroup으로 그룹을 시작한 후 DeleteSelection을 호출하였다. StartUndoGroup URtick에 그룹 ID를 작성해놓을 것이며 DeleteSelection->Delete->URDelete순으로 호출되어 URtick이 취소 레코드의 그룹 ID에 기록된다.

선택영역을 삭제한 후 클립보드의 텍스트를 붙여넣는 Insert 호출도 같은 그룹이므로 URInsert에서 URtick에 그룹 ID를 기록할 것이다. 이렇게 해서 삭제와 삽입 두 개의 편집 동작이 하나의 그룹 ID로 그룹에 포함되었다. EndUndoGroup에 의해 그룹은 해제되고 이후부터의 편집 동작은 단독 레코드가 된다.

영문자와 숫자가 입력되는 OnChar에도 동일한 처리를 한다. 선택영역이 있을 때 문자가 입력되면 이때도 선택 삭제와 문자입력이 하나의 그룹이 된다. 다음과 같이 코드를 수정하며 앞에서 작성한 붙여넣기의 경우와 동일한 코드이다.

 

void CApiEdit::OnChar(HWND hWnd, TCHAR ch, int cRepeat)

{

     ....

    if (SelStart != SelEnd) {

        StartUndoGroup();

    }

     bPrevSel=DeleteSelection();

     for (i=0;i<cRepeat;i++) {

          if (bOvr && bPrevSel==FALSE && ch!=‘\r’) {

              if (IsDBCS(buf,off)) {

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

                        Delete(off,2);

                   }

              } else {

                   Delete(off,1);

              }

          }

          Insert(off,szChar);

          off+=lstrlen(szChar);

     }

    if (bPrevSel) {

        EndUndoGroup();

    }

     bComp=FALSE;

     Invalidate(FindParaStart(off-lstrlen(szChar)));

     SetCaret();

}

 

마지막으로 선택 상태에서 한글을 입력할 때도 그룹을 구성하는데 이때는 상황이 좀 다르다. 그룹의 시작은 조립 시작 시점인 OnImeComposition에서 하는데 그룹의 끝은 한 글자가 완성되는 시점인 OnImeChar에서 한다. 그룹을 시작하는 함수와 끝내는 함수가 분리되어 있기 때문에 두 함수간의 통신을 위한 전역변수가 필요하며 bPrevCompGroup 변수가 이 역할을 한다.

 

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

{

     ....

     if (bComp) {

          URInsert(off-lstrlen(szChar),szChar);

          buf[off-2]=szChar[0];

          buf[off-1]=szChar[1];

        if (bPrevCompoGroup) {

            EndUndoGroup();

           bPrevCompoGroup=FALSE;

        }

     ....

}

 

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

{

     ....

    if (SelStart != SelEnd) {

        StartUndoGroup();

        bPrevCompoGroup=TRUE;

    }

     bPrevSel=DeleteSelection();

     hImc=ImmGetContext(hWnd);

     ....

 

조립을 시작할 때 선택영역이 있으면 그룹을 시작하고 bPrevCompGroup TRUE로 만들어 놓는다. 한글자 조립이 끝나는 시점인 OnImeChar에서는 이 변수가 TRUE이면 그룹을 끝낸다. OnImeComposition DeleteSelection 호출과 OnImeChar URInsert가 한 그룹이 되는 것이다. 이후 추가로 조립되는 글자는 URInsert의 병합 알고리즘에 의해 같은 그룹에 속하게 된다. 시작과 끝이 분리되어 있어 별도의 전역변수가 필요하고 조금 복잡하다.

ApiEdit는 아직까지 많은 편집기능을 제공하지 않기 때문에 편집 그룹이 중첩되는 실제 예는 없다. 검색 기능이 작성되면 모두 바꾸기에서 실제 예를 볼 수 있을 것이며 차후에 매크로 기능이 추가되면 다중 중첩되기도 한다. 실례가 없으므로 여기서는 바꾸기 명령을 포함한 매크로를 가정하고 편집기록이 어떻게 중첩되는지 구경만 해보도록 하자.

 

 

URnest

URtick

StartUndoGroup

1

123456

                  편집1

1

123456

                  편집2

1

123456

                  StartUndoGroup

2

123456

                                   삭제

2

123456

                                   삽입

2

123456

                  EndUndoGroup

1

123456

                  편집3

1

123456

EndUndoGroup

1

123456

편집4

0

0

 

매크로가 실행을 시작할 때 StartUndoGroup 함수를 호출하여 매크로내의 모든 명령이 같은 그룹임을 기록해 둔다. 이때 URnest 1이 되어 새로 그룹이 시작되었음을 기억하며 URtick에는 선두 레코드의 인덱스와 틱값으로 그룹 ID가 작성된다. 실제로 어떤 값이 작성될지는 알 수 없지만 편의상 123456이라는 ID가 부여되었다고 하자. 그룹에 속한 편집1,편집2 레코드는 tick값으로 123456을 기록할 것이다.

매크로 내에서의 바꾸기 명령도 그룹을 구성하므로 StartUndoGroup을 호출한다. 이때 URnest가 이미 1이므로 그룹 ID를 변경하지는 않으며 URnest 2로 증가한다. 바꾸기 명령을 구성하는 삭제, 삽입은 여전히 최초의 그룹 ID 123456값을 가질 것이다. EndUndGroup 함수는 바꾸기 편집 그룹을 종료한다. URnest 1 감소시켜 하나의 그룹을 끝내지만 그래도 URnest는 아직 1이라는 값을 가지고 있다. 즉 그룹이 완전히 끝나지 않은 것이며 URtick은 값을 유지한다. 매크로에 속한 마지막 명령인 편집3 레코드도 역시 같은 tick값을 가진다.

다시 한 번 EndUndoGroup 함수가 호출되면 URnest 0이 되며 이때 URtick은 비로소 0으로 바뀐다. 그룹의 바깥에 있는 편집4 레코드의 tick값으로는 0이 기록되며 그룹에 속하지 않는 단독 레코드이다. 이상이 편집 그룹이 2중 중첩된 경우의 동작 과정인데 3, 4중으로 중첩해도 동작방식은 동일하다. 중첩 처리의 핵심은 URnest가 기억하는 중첩 레벨이다.