. bUseLineEnd

앞에서도 말했지만 컨트롤의 옵션은 많을수록 좋다. 옵션이 너무 많고 의미가 어려워 사용자들에게 혼란을 초래할 정도라면 이 컨트롤을 사용하는 호스트에서 꼭 필요한 옵션만 선별적으로 사용할 수 있으므로 많다고 해서 문제가 될 것은 없다. 사용하지 않는 옵션은 적당히 디폴트를 취하면 최종 사용자에게는 없는 것처럼 보이게 만들 수 있다. 옵션이 많다는 것은 그만큼 컨트롤의 모양이나 기능이 변화무쌍하게 바뀔 수 있다는 뜻이며 이는 곧 컨트롤의 활용성이 높다는 얘기이다.

옵션이 많아지면 컨트롤의 내부 코드에서 일일이 조건 점검을 해야 하기 때문에 코드의 크기가 늘어나기는 하지만 속도에는 거의 영향을 주지 않는다. 왜냐하면 옵션이 적용되는 시점은 대체로 반복적인 루프 내부가 아니기 때문이다. ApiEdit는 이미 충분한 수의 옵션을 가지고 있지만 좀 더 많이 만들어 보도록 하자. 조금이라도 변화를 줄 수 있고 사용자의 기호에 따라 바뀔 수 있는 값이라면 고정값을 사용하지 않고 최대한 옵션에 따라 동작하도록 만들 것이다.

옵션을 기억할 변수를 선언하고 이 변수값에 따라 동작하도록 코드를 수정하면 되므로 별로 복잡하지도 않다. 이 장에서는 7개의 옵션을 추가할 것이다. 다음 멤버변수들을 CApiEdit 클래스에 선언한다. 그리고 외부에서 이 변수들의 값을 조사하거나 변경할 수 있도록 Get/Set 함수도 제공해야 한다.

 

class CApiEdit

{

     ....

     BOOL bUseLineEnd;

     BOOL bAllowDrag;

     int CaretWidth;

     BOOL bHideCurLine;

     BOOL bCalcTabWithAvg;

     int RightWrap;

     int ColMark;

 

public:

     ....

     BOOL GetUseLineEnd() { return bUseLineEnd; }

     void SetUseLineEnd(BOOL aUseLineEnd);

     BOOL GetAllowDrag() { return bAllowDrag; }

     void SetAllowDrag(BOOL aAllowDrag);

     int GetCaretWidth() { return CaretWidth; }

     void SetCaretWidth(int aCaretWidth);

     BOOL GetHideCurLine() { return bHideCurLine; }

     void SetHideCurLine(BOOL aHideCurLine);

     BOOL GetCalcTabWithAvg() { return bCalcTabWithAvg; }

     void SetCalcTabWithAvg(int aCalcTabWithAvg);

     int GetRightWrap() { return RightWrap; }

     void SetRightWrap(int aRightWrap);

     int GetColMark() { return ColMark; }

     void SetColMark(int aColMark);

 

각 변수들의 의미는 이름에 잘 설명되어 있는데 일단 이름으로부터 의미를 유추해보도록 하자. 개별적으로 이 옵션들을 적용하면서 의미에 대해 상세하게 연구해 볼 것이다. 이 변수들은 SetDefaultSetting 에서 다음과 같이 초기화된다.

 

void CApiEdit::SetDefaultSetting()

{

    bCalcTabWithAvg=TRUE;

     logfont.lfHeight=0;

     SetFont(&logfont);

     ....

    bUseLineEnd=TRUE;

    bAllowDrag=TRUE;

    CaretWidth=2;

    bHideCurLine=TRUE;

    RightWrap=0;

    ColMark=0;

}

 

이 함수에서 대입한 값이 곧 컨트롤의 디폴트 설정값이다. bCalcTabWidth 변수는 SetFont 함수에서 사용하므로 이 함수 호출 전에 초기화해야 한다. Get 함수들은 값을 읽어주기만 하므로 모두 인라인으로 작성했으면 Set 함수는 다음과 같이 작성한다. 옵션에 따라 값만 바꿔주면 되는 것도 있고 화면을 다시 그리거나 캐럿을 옮겨야 하는 것도 있지만 앞에서 만들었던 글꼴이나 줄간 옵션처럼 복잡한 것은 없다.

 

void CApiEdit::SetUseLineEnd(BOOL aUseLineEnd)

{

     bUseLineEnd=aUseLineEnd;

}

 

void CApiEdit::SetAllowDrag(BOOL aAllowDrag)

{

     bAllowDrag=aAllowDrag;

}

 

void CApiEdit::SetCaretWidth(int aCaretWidth)

{

     CaretWidth=aCaretWidth;

     SetCaret();

}

 

void CApiEdit::SetHideCurLine(BOOL aHideCurLine)

{

     bHideCurLine=aHideCurLine;

     Invalidate(-1);

}

 

void CApiEdit::SetCalcTabWithAvg(int aCalcTabWithAvg)

{

     bCalcTabWithAvg=aCalcTabWithAvg;

     SetTabWidth(TabWidth);

}

 

void CApiEdit::SetRightWrap(int aRightWrap)

{

     RightWrap=aRightWrap;

     SetWrap(nWrap);

}

 

void CApiEdit::SetColMark(int aColMark)

{

     ColMark=aColMark;

     Invalidate(-1);

}

 

설정상태를 기억할 변수와 액세스 함수를 먼저 작성했다. 각 옵션에 대해 개별적으로 적용 코드를 순서대로 작성해보자. 먼저 bUseLineEnd에 대한 코드부터 작성한다. 이 변수는 자동개행된 줄에서 좌우이동시 bLineEnd값을 변경할 것인가 아닌가를 지정하며 TRUE이면 사용하고 그렇지 않으면 이 값을 항상 FALSE로 유지한다.

bLineEnd는 멀리 ApiEdit2에서 자동개행된 줄의 처음과 끝을 구분하기 위해 만들었던 변수이며 이 변수를 관리하는 코드가 나름대로 복잡했었다. 지금 이 단계에서 ? bLineEnd가 뭐지? 처음 보는 변수 같은데라고 한다면 곤란하다. 생각이 안 난다면 ApiEdit2 코드를 다시 한 번 더 보고 오기 바란다.

bLineEnd는 자동개행된 줄에서 캐럿이동을 매끄럽게 처리하기 위해 도입된 것이며 자동개행을 하는 편집기는 이 처리를 꼭 할 필요가 있다고 생각한다. 하지만 다른 편집기들이 이런 식으로 캐럿을 이동하지 않는 경우가 있기 때문에 이미 쓰던 편집기에 익숙한 사람들은 ApiEdit의 캐럿 처리 방식이 마음에 들지 않을 수도 있다. 논리적으로 따지기 전에 사용자가 불편하다고 느낀다면 바꿀 수 있어야 하므로 좌우이동시 bLineEnd를 적용할 것인가 아닌가를 옵션으로 제어할 수 있도록 하기 위해 bUseLineEnd 옵션이 추가되었다.

bLineEnd 처리가 복잡하기 때문에 이 옵션을 처리하는 코드도 굉장히 복잡할 것 같지만 생각보다 훨씬 더 간단하다. bLineEnd를 무력화시키기만 하면 되며 조건문만 조금 손보면 이 옵션을 적용할 수 있다. OnKey 함수의 좌우이동 코드에 다음 조건을 추가한다.

 

     case VK_LEFT:

          if (off > 0) {

              GetRCFromOff(off,r,c);

 

              OldOff=off;

              if (bControl) {

                   off=GetPrevWord(off);

              } else {

                   if (off==pLine[r].Start) {

                        if (buf[GetPrevOff(off)]==‘\r’ || bUseLineEnd==FALSE) {

                            off=GetPrevOff(off);

                            bLineEnd=FALSE;

                        } else {

                            bLineEnd=TRUE;

                        }

          ....

     case VK_RIGHT:

          if (off < (int)doclen) {

              GetRCFromOff(off,r,c);

 

              OldOff=off;

              if (bControl) {

                   off=GetNextWord(off);

              } else {

                   if (off==pLine[r].End) {

                        if (buf[pLine[r].End]==‘\r’) {

                            off=GetNextOff(off);

                        }

                        bLineEnd=FALSE;

                   } else {

                        off=GetNextOff(off);

                        if (off==pLine[r].End && buf[off]!=‘\r’ && bUseLineEnd) {

                            bLineEnd=TRUE;

                        } else {

                            bLineEnd=FALSE;

                        }

                   }

              }

          ....

 

bUseLineEnd FALSE인 상태에서는 bLineEnd TRUE가 되지 않도록 하면 된다. 먼저 왼쪽 이동의 경우를 보자. 왼쪽으로 이동할 때는 현재 위치가 줄의 처음이고 바로 앞의 문자가 개행코드가 아닐 때만 bLineEnd TRUE가 된다. 즉 자동개행된 줄의 선두에 있을 때 이전 줄의 마지막 위치로 옮기기 위해 오프셋은 그대로 두고 bLineEnd TRUE로 변경했다. 이 코드에서 bUseLineEnd FALSE인 조건을 OR로 추가하면 앞 문자가 무조건 개행코드인 상태와 같아지므로 bLineEnd는 결코 TRUE가 될 수 없게 된다.

오른쪽 이동도 비슷하다. 한 칸 오른쪽으로 이동한 결과 줄 끝이고 여기가 개행코드가 아니면 자동개행된 줄의 끝이므로 bLineEnd TRUE로 변경하는데 이 조건에 bUseLineEnd TRUE여야 한다는 조건이 하나 더 들어갔다. 따라서 bLineEnd TRUE가 되기 위해서는 bUseLineEnd 옵션이 반드시 선택되어 있어야 하며 bUseLineEnd FALSE인 상태에서는 bLineEnd TRUE로 바뀔 수가 없다.

bUseLineEnd 옵션은 자동개행된 줄에서의 좌우이동에만 관여할 뿐이며 그 나머지 캐럿이동에 대해서는 적용되지 않는다. bUseLineEnd FALSE이더라도 bLineEnd는 여전히 TRUE가 될 수 있는데 자동개행된 줄의 처음과 끝은 어떤 식으로든지 구분을 해야 하기 때문이다. 자동개행된 줄에서 End키를 누르면 bLineEnd TRUE가 되어야 줄 끝으로 이동할 수 있다.

VK_END, GetXPosOnLine, GetOffFromXY 등에서도 bLineEnd를 변경하는데 이 코드는 그대로 남겨 두도록 하자. 이 함수들의 bLineEnd 조작은 bUseLineEnd 옵션과는 상관없이 항상 동작해야 한다. 사실 bLineEnd는 실습의 초반부터 나온 것이지만 이해하기 쉬운 개념은 아닌 것 같으며 따라서 bUseLineEnd도 조금 까다롭다. bLineEnd를 제대로 알고 있다면 bUseLineEnd 옵션은 사실 무척 간단한 옵션이다.