. 설정 대화상자

스타일 색상을 저렇게 복잡하게 복사하고 주거니 받거니 하는 이유는 결국 사용자가 이 색상을 변경할 수 있는 기회를 제공하기 위해서였다. 분석기의 생성자에 있는 값을 바꿀 수는 없으므로 SOption::arStyle에 그 사본을 복사했고 이 값을 분석기가 선택될 때마다 전달하도록 해놓았으므로 이제 SOption::arStyle값을 사용자에게 보여주고 변경하도록 하기만 하면 된다.

문법 설정을 위한 대화상자 리소스는 이미 만들어져 있고 대화상자 프로시저도 비록 비어있지만 만들어져 있다. 이 빈 대화상자 프로시저에 코드를 작성하여 arStyle 배열의 상태를 보여주고 사용자가 원하는 색상으로 바꿀 수 있도록 해보자. 작성할 코드는 다음과 같다.

 

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

{

     HWND hCon;

     LVCOLUMN COL;

     LVITEM LI;

     int i,idx;

     LPDRAWITEMSTRUCT dis;

     HBRUSH hBrush, hOldBrush;

     static int mi,msubi;

     CHOOSECOLOR COLDLG;

     static COLORREF crTemp[16];

     int tx,ty;

     TCHAR *tstr;

     COLORREF OldBk, OldColor;

     DWORD OldMode;

     COLORREF fore,back;

     CParse *Parser;

 

     switch(iMessage)

     {

     case WM_INITDIALOG:

          hCon=GetDlgItem(hDlg,IDC_PARSER);

          SendMessage(hCon,CB_ADDSTRING,0,(LPARAM)"C/C++");

          SendMessage(hCon,CB_ADDSTRING,0,(LPARAM)"HTML");

          SendMessage(hCon,CB_ADDSTRING,0,(LPARAM)"SQL");

          SendMessage(hCon,CB_SETCURSEL,0,0);

 

          hCon=GetDlgItem(hDlg,IDC_STYLE);

          COL.mask=LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

          COL.fmt=LVCFMT_LEFT;

          COL.cx=100;

          COL.pszText="스타일";

          COL.iSubItem=0;

          ListView_InsertColumn(hCon,0,&COL);

 

          COL.cx=80;

          COL.pszText="글자색";

          COL.iSubItem=1;

          ListView_InsertColumn(hCon,1,&COL);

 

          COL.cx=80;

          COL.pszText="배경색";

          COL.iSubItem=2;

          ListView_InsertColumn(hCon,2,&COL);

          SendMessage(GetDlgItem(hDlg,IDC_DEFEXT),EM_LIMITTEXT,250,0);

          return TRUE;

     case WM_DRAWITEM:

          dis=(LPDRAWITEMSTRUCT)lParam;

          if (wParam==IDC_STYLE) {

              idx=SendDlgItemMessage(hDlg,IDC_PARSER,CB_GETCURSEL,0,0);

              fore=NewOption.arStyle[idx+1][dis->itemID].fore;

              back=NewOption.arStyle[idx+1][dis->itemID].back;

 

              if (fore != -1) {

                   OldColor=SetTextColor(dis->hDC,fore);

              }

              if (back != -1) {

                   OldBk=SetBkColor(dis->hDC, back);

              }

              tstr=NewOption.arStyle[idx+1][dis->itemID].name;

              OldMode=SetBkMode(dis->hDC,OPAQUE);

              TextOut(dis->hDC,dis->rcItem.left, dis->rcItem.top+3,

                   tstr,lstrlen(tstr));

              SetBkMode(dis->hDC,OldMode);

 

              if (fore != -1) {

                   hBrush=CreateSolidBrush(fore);

                   hOldBrush=(HBRUSH)SelectObject(dis->hDC,hBrush);

                   SetTextColor(dis->hDC,OldColor);

              }

              Rectangle(dis->hDC,dis->rcItem.left+105, dis->rcItem.top+1,

                   dis->rcItem.left+175, dis->rcItem.bottom-1);

              if (fore == -1) {

                   TextOut(dis->hDC,dis->rcItem.left+125, dis->rcItem.top+3,"기본",4);

              } else {

                   DeleteObject(SelectObject(dis->hDC,hOldBrush));

              }

 

              if (back != -1) {

                   hBrush=CreateSolidBrush(back);

                   hOldBrush=(HBRUSH)SelectObject(dis->hDC,hBrush);

                   SetBkColor(dis->hDC, OldBk);

              }

              Rectangle(dis->hDC,dis->rcItem.left+185, dis->rcItem.top+1,

                   dis->rcItem.left+255, dis->rcItem.bottom-1);

              if (back == -1) {

                   TextOut(dis->hDC,dis->rcItem.left+205, dis->rcItem.top+3,"기본",4);

              } else {

                   DeleteObject(SelectObject(dis->hDC,hOldBrush));

              }

          } else {

              if (dis->itemState & ODS_SELECTED) {

                   hBrush=CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));

                   OldBk=SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));

                   OldColor=SetTextColor(dis->hDC,GetSysColor(COLOR_HIGHLIGHTTEXT));

                   OldMode=SetBkMode(dis->hDC,TRANSPARENT);

              }

              else {

                   hBrush=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

              }

              FillRect(dis->hDC, &dis->rcItem, hBrush);

              DeleteObject(hBrush);

              hBrush=CreateSolidBrush(arPreColor[dis->itemID].Color);

              hOldBrush=(HBRUSH)SelectObject(dis->hDC,hBrush);

              if (dis->itemID > 1) {

                   Rectangle(dis->hDC,dis->rcItem.left+2,dis->rcItem.top+1,

                        dis->rcItem.left+23,dis->rcItem.bottom-1);

                   tx=dis->rcItem.left+30;

              } else {

                   tx=dis->rcItem.left+3;

              }

              ty=dis->rcItem.top+3;

              if (dis->itemID == 0) {

                   tstr="기본색";

              } else if (dis->itemID == 1) {

                   tstr="색상선택...";

              } else {

                   tstr=arPreColor[dis->itemID].Name;

              }

              TextOut(dis->hDC,tx,ty,tstr,lstrlen(tstr));

 

              if (dis->itemState & ODS_SELECTED) {

                   SetBkColor(dis->hDC, OldBk);

                   SetTextColor(dis->hDC,OldColor);

                   SetBkMode(dis->hDC,OldMode);

              }

              DeleteObject(SelectObject(dis->hDC,hOldBrush));

          }

          break;

     case WM_COMMAND:

          idx=SendDlgItemMessage(hDlg,IDC_PARSER,CB_GETCURSEL,0,0);

          if (LOWORD(wParam) > 1 && LOWORD(wParam) < sizeof(arPreColor)/sizeof(arPreColor[0])) {

              if (msubi==1) {

                   NewOption.arStyle[idx+1][mi].fore=arPreColor[LOWORD(wParam)].Color;

              } else {

                   NewOption.arStyle[idx+1][mi].back=arPreColor[LOWORD(wParam)].Color;

              }

              InvalidateRect(GetDlgItem(hDlg,IDC_STYLE),NULL,TRUE);

              PropSheet_Changed(GetParent(hDlg),hDlg);

              return TRUE;

          }

 

          switch (LOWORD(wParam))   {

          case 0:

              if (msubi==1) {

                   NewOption.arStyle[idx+1][mi].fore=-1;

              } else {

                   NewOption.arStyle[idx+1][mi].back=-1;

              }

              InvalidateRect(GetDlgItem(hDlg,IDC_STYLE),NULL,TRUE);

              PropSheet_Changed(GetParent(hDlg),hDlg);

              return TRUE;

          case 1:

              memset(&COLDLG, 0, sizeof(CHOOSECOLOR));

              COLDLG.lStructSize = sizeof(CHOOSECOLOR);

              COLDLG.hwndOwner=hDlg;

              COLDLG.lpCustColors=crTemp;

              COLDLG.Flags=CC_FULLOPEN;

              if (ChooseColor(&COLDLG)!=0) {

                   if (msubi==1) {

                        NewOption.arStyle[idx+1][mi].fore=COLDLG.rgbResult;

                   } else {

                        NewOption.arStyle[idx+1][mi].back=COLDLG.rgbResult;

                   }

                   InvalidateRect(GetDlgItem(hDlg,IDC_STYLE),NULL,TRUE);

                   PropSheet_Changed(GetParent(hDlg),hDlg);

              }

              return TRUE;

          case IDC_PARSER:

              switch (HIWORD(wParam)) {

              case CBN_SELCHANGE:

                   ListView_DeleteAllItems(GetDlgItem(hDlg,IDC_STYLE));

                   for (i=0;NewOption.arStyle[idx+1][i].fore!=-2;i++) {

                        LI.mask=LVIF_TEXT;

                        LI.iSubItem=0;

                        LI.iItem=i;

                        LI.pszText="zzz";

                        ListView_InsertItem(GetDlgItem(hDlg,IDC_STYLE),&LI);

                   }

                   bEditByCode=TRUE;

                   SetDlgItemText(hDlg,IDC_EXT,NewOption.arExt[idx+1]);

                   bEditByCode=FALSE;

 

                   switch (idx+1) {

                   case 1:

                        Parser=new CParseCpp;

                        break;

                   case 2:

                        Parser=new CParseHtml;

                        break;

                   case 3:

                        Parser=new CParseSql;

                        break;

                   }

 

                   SetDlgItemText(hDlg,IDC_PARDELI,Parser->GetInfo(1));

                   SetDlgItemText(hDlg,IDC_PARCOM,Parser->GetInfo(2));

                   SetDlgItemText(hDlg,IDC_PARCOMSTART,Parser->GetInfo(3));

                   SetDlgItemText(hDlg,IDC_PARCOMEND,Parser->GetInfo(4));

                   SetDlgItemText(hDlg,IDC_PARBLOCKSTART,Parser->GetInfo(5));

                   SetDlgItemText(hDlg,IDC_PARBLOCKEND,Parser->GetInfo(6));

                   delete Parser;

                   break;

              }

              return TRUE;

          case IDC_EXT:

              if (HIWORD(wParam) == EN_CHANGE) {

                   if (bEditByCode==FALSE) {

                        GetDlgItemText(hDlg,IDC_EXT,NewOption.arExt[idx+1],250);

                        PropSheet_Changed(GetParent(hDlg),hDlg);

                   }

              }

              return TRUE;

          case IDC_BTNDEFSTYLE:

              switch (idx+1) {

              case 1:

                   Parser=new CParseCpp;

                   break;

              case 2:

                   Parser=new CParseHtml;

                   break;

              case 3:

                   Parser=new CParseSql;

                   break;

              }

 

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

                   Parser->GetStyleColor(i,NewOption.arStyle[idx+1][i].fore,

                        NewOption.arStyle[idx+1][i].back);

                   if (NewOption.arStyle[idx+1][i].fore==-2)

                        break;

              }

              delete Parser;

              InvalidateRect(GetDlgItem(hDlg,IDC_STYLE),NULL,TRUE);

              PropSheet_Changed(GetParent(hDlg),hDlg);

              return TRUE;

          }

          break;

     case WM_MEASUREITEM:

          LPMEASUREITEMSTRUCT lpmis;

          lpmis = (LPMEASUREITEMSTRUCT)lParam;

          lpmis->itemWidth=80;

          lpmis->itemHeight=18;

          return TRUE;

     case WM_NOTIFY:

          LPNMHDR hdr;

          hdr=(LPNMHDR)lParam;

 

          if (hdr->hwndFrom == GetDlgItem(hDlg,IDC_STYLE)) {

              switch (hdr->code) {

              case NM_CLICK:

                   LVHITTESTINFO info;

                   HMENU hPopup;

                   POINT pt;

                   pt=info.pt=((LPNMITEMACTIVATE)lParam)->ptAction;

                   ListView_SubItemHitTest(hdr->hwndFrom,&info);

                   if (info.iItem !=-1 && info.iSubItem > 0) {

                        mi=info.iItem;

                        msubi=info.iSubItem;

                        hPopup=CreatePopupMenu();

                        AppendMenu(hPopup,MF_STRING | MF_OWNERDRAW,0,0);

                        AppendMenu(hPopup,MF_STRING | MF_OWNERDRAW,1,0);

                        idx=sizeof(arPreColor)/sizeof(arPreColor[0]);

                        for (i=3;i<idx;i++) {

                             if (i==(idx-1)/2+1+(idx%2 ? 0:1)) {

                                 AppendMenu(hPopup,MF_STRING | MF_OWNERDRAW | MF_MENUBARBREAK,i,0);

                             } else {

                                 AppendMenu(hPopup,MF_STRING | MF_OWNERDRAW,i,0);

                             }

                        }

 

                        ClientToScreen(hdr->hwndFrom,&pt);

                        TrackPopupMenu(hPopup, TPM_LEFTALIGN, pt.x,pt.y,

                             0, hDlg, NULL);

                        DestroyMenu(hPopup);

                   }

 

                   break;

              }

              return TRUE;

          }

 

          switch (hdr->code) {

          case PSN_SETACTIVE:

              g_StartPage=4;

               SendMessage(hDlg,WM_COMMAND,MAKEWPARAM(IDC_PARSER,CBN_SELCHANGE),

                   (LPARAM)GetDlgItem(hDlg,IDC_PARSER));

              return TRUE;

          case PSN_APPLY:

              ApplyNow();

              return TRUE;

          case PSN_KILLACTIVE:

              return TRUE;

          }

          break;

     }

     return FALSE;

}

 

문법 설정상태를 보여주는 리스트 뷰 컨트롤과 색상을 선택하는 메뉴가 모두 오너 드로우로 작성되어 있다. 그러다 보니 코드가 좀 길어졌는데 따로 분석해 볼만큼 어려운 구문은 없으므로 자세한 분석은 생략한다. 실행중의 모습은 다음과 같다.

이 대화상자를 통해 분석기의 스타일 색상과 연결된 확장자를 변경할 수 있으며 구분자나 기타 문법 정보를 확인할 수 있다. 직관적으로 이해할 수 있는 UI를 설계하고자 했으며 색상을 쉽게 바꿀 수 있도록 터치수를 낮추는데 주안점을 두었다. 나름대로 노력했지만 사용자들이 어떻게 평가할지는 알 수 없는 노릇이다.

이 대화상자에서 색상이나 확장자를 변경한 후 적용 버튼을 클릭하면 즉시 변경 사항을 반영해야 한다. ApplyNow에 다음 코드를 작성하여 문법 설정 중 변경된 것을 찾아 적용하도록 하자.

 

void ApplyNow()

{

     HWND hChild;

     SInfo *pSi;

     HDC hdc;

     LOGFONT tFont;

    BOOL bChangeStyle;

 

    if (memcmp(Option.arStyle,NewOption.arStyle,sizeof(NewOption.arStyle))!=0) {

        bChangeStyle=TRUE;

    } else {

        bChangeStyle=FALSE;

    }

 

     ....

     hChild=GetWindow(g_hMDIClient,GW_CHILD);

     while (hChild) {

          ....

        if (bChangeStyle) {

           NewOption.SetStyleColor(pSi->Ae.GetParser());

           pSi->Ae.Invalidate(-1);

        }

 

          hChild=GetWindow(hChild,GW_HWNDNEXT);

     }

 

     Option=NewOption;

}

 

다른 옵션과 마찬가지로 스타일 색상도 변경되었을 때만 적용해야 한다. 그런데 스타일 색상은 단순 변수가 아니라 2차원 배열이기 때문에 변경 여부를 쉽게 판단하기가 어렵다. 그래서 적용하기에 앞서 Option NewOption arStyle 배열을 2진 차원에서 메모리 비교하여 변경했는지 아닌지만 먼저 조사한다. 사본을 만들 때 그대로라면 변경되지 않은 것이고 단 1비트라도 바뀐 것이 있다면 사용자가 문법 설정을 변경한 것이다.

문법 설정이 변경되었으면 이미 생성되어 있는 모든 CApiEdit 객체에게 변경된 스타일 정보를 다시 가르쳐 주도록 하였다. , 이때 모든 분석기에 대한 스타일을 가르쳐 줄 필요는 없고 현재 사용중인 문법에 대한 스타일만 갱신하면 된다. HTML 문서를 편집하고 있는 차일드에게 C/C++ 문법 설정의 변경은 당장 필요한 것이 아니다.

앞으로 생성될 창에 대해서는 별도로 스타일을 가르쳐 주지 않아도 되므로 SetSetting 함수에는 아무 코드도 필요없다. 차일드가 생성될 때 SelectParser 함수가 스타일 정보를 매번 가르쳐 주고 있다. 확장자 연결 상태도 다음 번 SelectParser 호출시에 적용되므로 별도로 적용 코드를 작성할 필요가 없다.

이상으로 문법강조에 대한 실습을 마친다. 마지막으로 CApiEdit::SetDefaultSetting SetParser(1) SetParser(0)으로 수정하여 기본 분석기를 디폴트 분석기로 사용하도록 한다.