. 그룹 ID

편집 그룹이란 한 묶음으로 처리해야 할 편집 동작의 집합이다. 하나의 목적을 위한 여러 개의 편집 동작들은 한꺼번에 취소/재실행해야 하며 그러기 위해서는 복수 개의 편집 동작이 한 묶음임을 표시할 수 있는 장치가 있어야 하는데 이것이 바로 편집 그룹이다. ApiEdit의 편집 레코드는 편집 동작 하나를 하나의 레코드로 기록하되 단 병합이라는 알고리즘을 통해 가급적이면 레코드 수를 줄이도록 하고 있다.

편집 그룹에는 이론적으로 무한개의 편집 동작이 포함될 수 있기 때문에 하나의 레코드로 편집 그룹을 구성할 수는 없다. 이동까지는 한 레코드로 처리했으나 다른 문자열로 바꾸는 대체는 현재 구조로 한 레코드에 기록되지 않는다. 왜냐하면 대체는 지워진 문자열과 새로 삽입된 문자열 두 개가 기록되어야 하는데 취소 레코드에는 문자열을 기록할 수 있는 멤버가 data 하나밖에 없기 때문이다.

모두 바꾸기는 같은 동작을 여러 번 반복하며 매크로는 순서가 있는 다른 동작을 반복하는데 이런 복잡한 편집 동작을 한 레코드에 저장하는 것은 여러 모로 볼 때 무리다. 그래서 각각의 편집 동작을 개별적인 레코드에 기록하되 이 레코드들을 그룹으로 묶어 줄 수 있어야 한다. 특히 매크로에서 그룹핑은 선택이 아니라 필수다. 매크로가 아무리 복잡한 일을 하더라도 사용자는 매크로 실행을 하나의 편집 동작으로 인식한다.

그룹핑을 위해서는 어떤 식으로든 레코드에 그룹 표시를 해야 한다. 표식이 있어야 이 레코드와 저 레코드가 하나의 묶음인지 알 수 있게 되며 그래야 두 레코드를 한꺼번에 취소/재실행할 수 있다. 그룹이 연속될 수도 있기 때문에 단순히 그룹에 속한다는 표식만 남겨서는 안되며 어떤 그룹에 속하는지 레코드의 소속을 정확하게 표시해야 한다. 예를 들어 취소 레코드에 BOOL bGroup이라는 플래그를 추가하고 이 값이 TRUE이면 그룹 레코드로 취급한다고 해보자.

 

레코드1

레코드2 그룹1

레코드3 그룹1

레코드4

레코드5 그룹2

레코드6 그룹2

레코드7

레코드1

레코드2 그룹1

레코드3 그룹1

레코드4 그룹2

레코드5 그룹2

레코드6 그룹2

레코드7

 

왼쪽 그림을 보면 두 개의 그룹이 있음을 알 수 있다. 하지만 오른쪽 그림처럼 두 그룹이 인접해 있을 때는 이 그룹이 통째로 하나인지 아니면 분리된 두 개인지를 구분할 수가 없다. 그래서 BOOL형으로 그룹 여부를 저장하는 것은 안되며 그룹의 소속을 표시할 수 있는 좀 더 정확한 방법을 사용해야 한다.

취소 레코드의 소속을 기록하는 방법에는 두 가지가 있다. 첫 번째는 그룹끼리 구분할 수 있는 고유한 그룹 ID를 부여함으로써 같은 그룹에 속하는 레코드들을 구분하는 방법이다. 그룹 ID는 계속 증가하는 정수값을 쓸 수도 있고 GUID같이 절대로 중복되지 않는 값을 쓸 수도 있다. 그룹 선두의 레코드 인덱스를 쓸 수 있을 것 같지만 상한선 제한에 의해 일정 용량이 되면 취소 레코드의 인덱스가 변경되는 기능이 있어서 선두 레코드의 인덱스는 그룹 ID로 부적합하다.

예를 들어 0~7까지의 레코드가 있고 4~7까지가 4번을 선두로 하는 그룹을 구성하고 있다고 하자. 이 상태에서 새로운 그룹을 구성하는 편집 레코드가 하나 더 삽입되었는데 이때 용량의 상한 조건에 걸리게 되면 레코드의 절반이 잘려 나갈 것이다. 이때의 상황을 그림으로 그려보면 다음과 같아진다.

 

새로 삽입된 레코드의 번호가 공교롭게도 이전 그룹의 선두인 4가 되면 레코드4이후부터 새로 작성되는 그룹과 기존 그룹의 ID가 일치하게 되어 버려 구분이 안된다. 물론 이것은 극단적으로 재수가 없는 경우이며 발생 빈도가 지극히 낮다. 하지만 이런 현상이 일어날 가능성을 알고 있는 상태에서 이 방법을 쓸 수는 없다. 만약 꼭 이 방법을 사용하고 싶다면 NextRecord에서 단순히 메모리 정리만 해서는 안되면 그룹을 구성하는 모든 레코드의 그룹 ID를 변경해야 한다.

두 번째 방법은 각 레코드에 그룹의 헤더, 그룹의 소속원 또는 단독 레코드인지 표식을 다는 방법을 쓸 수 있다. 그룹의 시작점에 대해 특별한 표식이 있으므로 그룹끼리 정확하게 구분이 되며 그룹 소속원의 개수를 쉽게 알 수 있다. 이 방법은 대화상자가 라디오 버튼끼리 그룹을 짓기 위해 WS_GROUP, WS_TABSTOP 스타일을 사용하는 방법과 동일하다.

두 방법 모두 논리적으로 별 문제가 없는데 여기서는 첫 번째 방법인 그룹 ID 방법을 채택하기로 했다. 그룹 ID로는 선두 레코드의 인덱스를 쓰되 이 값만으로는 부정확하므로 틱 카운트값을 조합하기로 한다. 틱 카운트는 순환하는 값이지만 순환 주기가 49일이나 되므로 중복 위험이 없고 안전한데다 운영체제가 항상 유지하고 있는 값이므로 쉽게 구할 수 있다. 그룹 ID의 상위 16비트에는 선두 레코드의 ID를 기록하고 하위 16비트에는 틱 카운트의 하위 16비트를 기록하도록 할 것이다.

조합되는 두 값의 상위 16비트는 버린다. 어차피 그룹 ID는 구분을 위한 값이므로 값 자체는 큰 의미가 없고 서로 중복되지만 않으면 된다. 그룹에 속하지 않는 레코드, 즉 단독 레코드는 그룹 ID 0의 값을 가진다.