. 읽기전용

텍스트 편집기는 텍스트를 편집하는 용도 외에도 한 가지 용도를 더 가지고 있는데 텍스트 내용을 보여주는 뷰어로도 많이 활용된다. 텍스트를 편집하려면 어차피 내용을 보여주어야 하므로 편집기는 곧 뷰어를 겸하고 있다. 하지만 어떤 경우는 오로지 뷰어로만 동작해야 할 경우가 있는데 변경할 수 없는 읽기전용 문서를 편집할 때나 로컬에 존재하지 않는 네트워크상의 텍스트 파일을 다운로드받아 보여줄 때는 편집해 봐야 저장할 대상이 없으므로 뷰어로만 동작해야 한다.

편집기가 뷰어가 되는 방법은 아주 간단하다. 편집을 금지시켜 문서를 읽기전용으로 만들면 된다. 플래그를 하나 두고 이 플래그의 값에 따라 문서를 읽기전용으로 만들 수 있도록 해보자. 새로운 기능이 추가되므로 ApiEdit9 프로젝트를 새로 만들고 OnCreate에 있는 샘플 텍스트는 삭제하도록 하자. 그리고 다음 전역변수를 선언하고 초기화한다.

 

BOOL bReadOnly;

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     bReadOnly=FALSE;

 

일단은 FALSE로 초기화하여 편집이 가능하도록 했는데 이 변수가 TRUE값을 가지면 읽기전용으로 동작하면 된다. 이 변수의 값은 ApiEdit가 내부적으로 변경할 필요가 없으며 외부에서 주어진다. 예를 들어 파일을 읽었는데 읽기전용 속성을 가지고 있다면 이 플래그를 TRUE로 바꾸게 될 것이다. ApiEdit는 이 플래그의 값을 참조만 하며 변수값에 따라 문서를 변경하는 모든 코드를 막아주면 된다.

문서에 변경이 가해지는 부분은 꽤 많이 있는데 일단은 버퍼에 직접 문자열을 삽입, 삭제하는 Insert, Delete가 가장 우선적으로 수정되어야 한다.

 

 

void Insert(int nPos, TCHAR *str)

{

     if (bReadOnly)

          return;

     ....

 

void Delete(int nPos, int nCount)

{

     if (bReadOnly)

          return;

     ....

 

함수의 가장 첫 부분에서 bReadOnly 변수값을 보고 이 값이 TRUE이면 아무 것도 하지 않고 그냥 리턴하면 된다. 이 두 함수를 막아버리면 프로그램 내의 어떠한 코드도 버퍼를 변경할 수 없게 된다. 이로써 모든 처리가 끝나면 얼마나 간단하겠는가만 더 신경써야 할 곳이 많다. 직접 문자를 입력받는 OnChar, OnImeComposition 함수도 읽기전용일 때는 아무 것도 하지 않도록 해야 한다.

 

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

{

     ....

     if ((ch < ‘ ‘ && ch != ‘\r’ && ch != ‘\t’) || ch==127)

          return;

    

     if (bReadOnly)

          return;

     ....

 

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

{

     ....

     if (bReadOnly)

          return 0;

     ....

 

OnChar의 경우 단축키 점검 다음에 읽기전용 점검을 해야 한다. 읽기전용이더라도 복사, 전체 선택 등은 가능해야 하기 때문이다. 이 두 함수에서 굳이 bReadOnly를 점검하지 않아도 Insert, Delete가 버퍼를 변경하지 않기 때문에 문서 내용이 편집되지는 않는다. 하지만 이 함수들은 실패 사실을 모르고 성공했다고 가정하고 다음 동작을 하게 된다. 문자가 삽입된 위치를 무효화시키고 다음 문자 위치로 캐럿을 옮기려고 하는데 이때 버퍼 내용이 편집되지 않았기 때문에 엉뚱한 동작을 할 위험이 있다.

예를 들어 대한민국자 앞에서 영문 a를 입력했다고 해보자. 그러면 이 함수들은 a가 입력되었다고 생각하고 a자 뒤로 캐럿을 보내려고 하는데 버퍼는 변경되지 않았기 때문에 캐럿은 자의 중간으로 가려고 한다. 이 위치는 한글 코드의 중간이기 때문에 대응되는 캐럿 위치가 없으며 무한루프에 빠져 프로그램은 예외를 일으키고 죽게 된다. 그래서 아예 원천적으로 코드 실행을 막아야 한다. 문자를 삭제하는 VK_DELETE, VK_BACK도 막아야 한다.

 

     case VK_DELETE:

          if (bReadOnly)

              return;

     ....

     case VK_BACK:

          if (bReadOnly)

              return;

     ....

 

이 두 키도 문자를 실제 삭제하지는 못하지만 삭제했다고 가정하고 캐럿 위치를 옮기기 때문에 읽기전용일 때는 아예 키입력 자체를 무시해야 한다. 클립보드 관련 코드도 수정한다. 복사 동작은 문서에 전혀 영향을 주지 않기 때문에 읽기전용이라도 허용해야 한다. 반면 잘라내기나 붙여넣기는 허용할 수 없다. 이 기능들도 문서 내용을 바꾸지는 못하지만 캐럿을 이동시키는 코드가 있어 금지해야 한다.

 

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

{

     switch (id) {

     case IDM_AE_CUT:

          if (SelStart != SelEnd && bReadOnly==FALSE) {

     ....

     case IDM_AE_PASTE:

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

     ....

 

잘라내기, 붙여넣기를 할 수 있는 조건에 bReadOnly FALSE여야 한다는 조건이 하나 더 추가되었다. 잘라내기를 할 때는 마치 선택영역이 없는 것처럼 만들어버리고, 붙여넣기를 할 때는 클립보드에 텍스트 데이터가 없는 것처럼 만들어 버렸다. 그래서 읽기전용 상태에서는 두 동작 모두 금지된다. 클립보드 관련 메뉴도 읽기전용일 때는 사용금지시키도록 OnInitMenu 함수를 수정하는 것이 정확하겠지만 굳이 그렇게까지는 하지 않았다. 어차피 메뉴를 선택해도 아무 일도 일어나지 않는다.

마우스 액션은 대부분 선택영역을 만드는 코드들로 구성되어 있는데 읽기전용 상태에서도 선택은 할 수 있다. 선택 자체가 문서를 변경하지 않으며 복사를 허용하기 때문에 선택도 당연히 허용되어야 한다. 하지만 문자열 드래그 기능은 문서 내용을 변경하기 때문에 막아야 한다. 다른 코드는 그대로 두고 bDragSel 모드로만 들어가지 못하도록 하면 드래그 동작을 시작할 수 없으므로 문서변경을 쉽게 막을 수 있다.

 

void OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)

{

     ....

     if (IsInSelection(x+xPos, y+yPos) && bReadOnly==FALSE) {

     ....

 

문자열 드래그를 시작할 조건에 읽기전용이 아니어야 한다는 조건이 하나 더 추가되었다. bReadOnly TRUE로 바꾸어 놓고 편집이 되는지 테스트해보아라. 다 막아 버렸기 때문에 물론 편집이 안된다. 이런 간단한 처리를 위해 여기 저기 코드를 많이 손 대야 하는데 만약 Insert Delete void형으로 만들지 않고 삽입, 삭제 성공 여부를 리턴하도록 했다면 다른 코드가 조금 더 간단해졌을지도 모른다. 하지만 이 함수들의 리턴값을 점검하는 코드나 bReadOnly를 직접 점검하는 코드나 결국 양은 비슷하다.