.텍스트 삽입

다음은 가장 복잡한 객체인 텍스트를 삽입해 보자. 외부 파일에서 읽어 오는 비트맵이나 메타와는 달리 텍스트는 사용자로부터 직접 문자열을 입력받아야 하므로 복잡하다. 문자열을 입력받기 위해서는 에디트 컨트롤을 사용해야 하는데 모달 대화상자를 열어서 입력받는 방법이 가장 편리하다. 리소스에는 이미 대화상자가 작성되어 있으므로 코드만 추가하면 된다.

중앙의 에디트 박스에 전달된 텍스트를 보여 주고 사용자가 편집할 있도록 하며 확인 버튼을 누르면 편집 결과를 다시 리턴해야 한다. 편집 대상 문자열은 대화상자의 파라미터로 전달되어야 하는데 고정 길이의 문자열을 전달한다면 TCHAR *형을 넘기고 대화상자는 결과를 다시 버퍼에 저장하면 것이다. 그러나 프로젝트의 경우 사용자가 입력할 있는 문자열의 길이에 제한이 없기 때문에 대화상자가 에디트 컨트롤에 입력된 길이만큼 메모리를 동적으로 재할당하여 편집 결과를 리턴해야 한다.

그래서 대화상자는 파라미터로 TCHAR * 받아서는 안되면 동적 할당이 가능한 TCHAR ** 받아야 한다. 그래야 전달된 포인터가 가리키는 곳에 있는 TCHAR *형의 변수를 원하는 크기대로 마음대로 늘리거나 줄일 있다. 대화상자 프로시저는 다음과 같이 작성한다.

 

BOOL CALLBACK TextDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   static TCHAR **ppText;

   int len;

 

   switch(iMessage) {

   case WM_INITDIALOG:

      ppText=(TCHAR **)lParam;

      if (*ppText) {

          SetDlgItemText(hDlg,IDC_EDSTR,*ppText);

      }

      return TRUE;

   case WM_COMMAND:

      switch (LOWORD(wParam)) {

      case IDOK:

          len=GetWindowTextLength(GetDlgItem(hDlg,IDC_EDSTR));

          *ppText=(TCHAR *)realloc(*ppText,len+1);

          GetDlgItemText(hDlg,IDC_EDSTR,*ppText,len+1);

          EndDialog(hDlg,IDOK);

          return TRUE;

      case IDCANCEL:

          EndDialog(hDlg,IDCANCEL);

          return TRUE;

      }

      break;

   }

   return FALSE;

}

 

WM_INITDIALOG에서 lParam으로 전달받은 문자열 버퍼의 포인터를 ppText 대입해 두고 버퍼의 내용인 *ppText 에디트 박스에 출력했다. *ppText NULL 경우, 새로 생성되는 객체인 경우는 출력할 내용이 없으므로 에디트를 비워 둔다. 사용자가 편집을 마치고 확인 버튼을 누르면 편집된 길이를 len 조사하고 *ppText 길이에 맞게 재할당한다. 편집된 문자열이 버퍼 크기보다 크면 버퍼가 확장될 것이고 작다면 축소될 것이다. 길이를 조정한 *ppText 에디트의 내용을 복사한다.

텍스트 객체를 삽입할 시점은 역시 OnLButtonDown이다. 함수의 선두에 다음 코드를 작성하여 NowTool DT_TEXT 텍스트 편집 대화상자를 호출하여 사용자로부터 텍스트를 입력받아 텍스트 객체를 배열에 추가한다. 비트맵이나 메타와 마찬가지로 텍스트 삽입 코드의 순서도 중요하지 않다.

 

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

{

   int TempSel;

   int nHit;

   TCHAR *pText;

 

   if (NowTool==DT_TEXT) {

      pText=NULL;

      if (DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_TEXT),hWnd,

          TextDlgProc,(LPARAM)&pText) == IDOK) {

          if (lstrlen(pText) == 0) {

             free(pText);

          } else {

             AppendObject(DT_TEXT,LOWORD(lParam),HIWORD(lParam),

                LOWORD(lParam)+200,HIWORD(lParam)+50);

             arObj[arNum-1]->Text=pText;

             arObj[arNum-1]->Len=lstrlen(pText)+1;

             arObj[arNum-1]->PlaneColor=-1;

             InvalidateRect(hWndMain,NULL,TRUE);

          }

          NowTool=DT_SELECT;

          NowSel=arNum-1;

      }

      return 0;

   }

   ....

 

텍스트 객체를 새로 추가하는 것이므로 대화상자의 파라미터로 넘겨지는 포인터는 NULL 초기화된 값이어야 한다. pText 편집된 텍스트를 저장할 배열인데 포인터를 NULL 초기화하고 포인터 변수의 번지인 &pText 대화상자로 전달한다. 대화상자는 사용자로부터 입력을 받은 pText 할당하여 편집 결과를 복사할 것이다. 편집이 완료되면 AppendObject 텍스트 객체를 추가하고 Text멤버는 대화상자가 할당한 pText 대입하고 길이는 문자분까지 고려해서 문자열 길이+1 설정한다. 온전한 문자열이 되려면 항상 종료 문자가 제일 뒤에 있어야 한다.

만약 대화상자에서 문자열을 하나도 입력하지 않고 곧바로 확인 버튼을 눌렀다면 이때는 텍스트 객체가 되므로 객체를 생성해서는 안된다. 어차피 생성해 봐야 보이지도 않을 것이다. 텍스트 객체를 추가한 화면을 무효화하고 선택 모드로 설정하며 방금 삽입된 텍스트 객체를 선택한다. DObject 구조체에는 텍스트의 속성에 관련된 몇가지 설정값들이 있는데 값을 초기화하는 작업은 AppendObject에서 한다.

 

BOOL AppendObject(DTool Type,int x1,int y1,int x2,int y2)

{

   ....

   arObj[arNum]->LineWidth=Opt.LineWidth;

   arObj[arNum]->LineColor=Opt.LineColor;

   arObj[arNum]->PlaneColor=Opt.PlaneColor;

   arObj[arNum]->FontColor=Opt.FontColor;

   arObj[arNum]->FontSize=Opt.FontSize;

   lstrcpy(arObj[arNum]->FontFace,Opt.FontFace);

   arNum++;

   return TRUE;

}

 

전역 옵션에 기억되어 있는 글꼴 관련 설정을 새로 삽입되는 텍스트 객체에도 그대로 적용해야 한다. 텍스트가 다른 객체와 조금 다른 점이라면 면의 색상이 항상 투명이라는 점이다. AppendObject에서 PlaneColor 전역 옵션으로 대입하지만 OnLButtonDown에서 값을 다시 -1 바꾸어 전역 옵션을 따르지 않도록 예외 처리를 했다. 텍스트는 문자열일 뿐이므로 배경 색상이 투명한 것이 보통이다. 물론 처음 생성할 때만 투명하며 일단 생성된 사용자가 명시적으로 배경색을 수는 있다. OnPaint에서는 배열에 삽입된 텍스트를 그린다.

 

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

{

   ....

   HFONT hFont,hOldFont;

   int FontHeight;

 

   SetBkMode(hMemDC,TRANSPARENT);

   for (idx=0;idx<arNum;idx++) {

      ....

      case DT_TEXT:

          FillRect(hMemDC,&arObj[idx]->rt,hBrush);

          FontHeight=arObj[idx]->FontSize*GetDeviceCaps(hMemDC,LOGPIXELSY)/72;

          hFont=CreateFont(FontHeight,0,0,0,0,0,0,0,HANGEUL_CHARSET,3,2,1,

             VARIABLE_PITCH | FF_ROMAN,arObj[idx]->FontFace);

          hOldFont=(HFONT)SelectObject(hMemDC,hFont);

          if (arObj[idx]->FontColor != (COLORREF)-1) {

             SetTextColor(hMemDC,arObj[idx]->FontColor);

             DrawText(hMemDC,arObj[idx]->Text,-1,&arObj[idx]->rt,DT_WORDBREAK);

          }

          DeleteObject(SelectObject(hMemDC,hOldFont));

          break;

 

도형 출력 루프에 들어가기 전에 배경 모드를 미리 투명으로 바꾸어 둔다. DT_TEXT 객체를 만나면 속성에 기록된 대로 글꼴을 생성하고 글꼴색상을 변경한 DrawText 객체의 영역에 텍스트를 출력했다. 옵션에 기록되는 글꼴의 크기는 포인트 단위이므로 단위를 픽셀 단위로 변환해야 한다. 포인트값을 화면 해상도와 곱하고 72 나누면 폰트의 픽셀 단위를 구할 있다. 참고로 코드가 폰트를 다루는 방법은 정확하지 못한데 속성에 폰트의 이름만 지정되므로 문자셋이나 피치 정보를 수가 없다. 문제는 잠시 후에 다시 점검해 보도록 하자.

다음은 이미 작성된 텍스트 객체의 문자열을 수정하는 기능을 넣어 보자. 비트맵은 외부 파일에서 읽어 왔으므로 편집까지 필요가 없지만 텍스트는 사용자가 입력한 것이므로 원할 경우 내용을 편집할 있어야 한다. 편집 명령은 마우스 더블클릭으로 받아들이도록 하자. 캔버스의 윈도우 클래스는 더블 클릭을 받아들일 있도록 CS_DBLCLKS 스타일을 이미 부여해 놓았다.

 

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

{

   int TempSel;

   TCHAR *pText;

 

   TempSel=FindObject(LOWORD(lParam),HIWORD(lParam));

   if (TempSel == -1) {

      return 0;

   }

   if (arObj[TempSel]->Type == DT_TEXT) {

      pText=arObj[TempSel]->Text;

      if (DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_TEXT),hWnd,

          TextDlgProc,(LPARAM)&pText) == IDOK) {

          if (lstrlen(pText) == 0) {

             DelObject(TempSel);

          } else {

             arObj[arNum-1]->Text=pText;

             arObj[arNum-1]->Len=lstrlen(pText)+1;

          }

          InvalidateRect(hWndMain,NULL,TRUE);

      }

   }

   return 0;

}

 

편집하기 전에 더블클릭한 곳의 객체를 조사해서 텍스트 객체인지를 먼저 점검해야 한다. 만약 여백을 더블클릭했다거나 텍스트가 아닌 객체를 더블클릭했다면 아무 것도 필요가 없다. 선택된 텍스트 객체의 Text멤버를 pText 대입한 pText 번지를 편집 대화상자의 파라미터로 넘겨 편집하도록 했다. 편집이 완료되면 기존 텍스트가 지워졌는지 보고 만약 텍스트를 모두 지웠다면 이를 객체 삭제 명령으로 인식하여 삭제하도록 했다. 만약 처리를 하지 않으면 텍스트도 없는 객체가 존재하게 것이다.

객체가 아니라면 편집된 결과인 pText 다시 Text멤버에 대입하고 길이도 다시 조사한다. 대화상자를 호출할 Text pText 대입했지만 대화상자가 리턴된 pText 여전히 같은 번지를 가리킨다고 없으므로 Text 멤버는 재할당된 번지를 다시 대입받아야 한다. 텍스트 객체가 새로 생성될 때와 이미 생성된 객체를 편집할 대화상자 프로시저로 전달되는 값을 비교해 보자.

새로 생성할 때는 NULL값을 가지는, 할당되지 않은 포인터를 가지는 문자형 포인터가 전달되며 이때 대화상자는 포인터를 새로 할당한다. 편집시에는 편집 대상 텍스트를 가리키는 포인터의 번지가 전달되며 대화상자는 포인터가 가리키는 곳의 포인터가 가리키는 문자열로 에디트를 초기화하고 편집된 결과만큼 재할당까지 해서 다시 복사한다.