. 미니 탐색기

파일창은 당근이 내장하고 있는 미니 탐색기라고 생각하면 된다. 메인 윈도우의 왼쪽 벽에 각 드라이브의 파일들을 보여주고 여기서 바로 파일을 열 수 있도록 하는 장치이다. 파일열기 대화상자는 속도가 너무 느리고 탐색기는 따로 열어야 하기 때문에 불편하다. 그래서 아예 편집기 내부에 디스크의 파일을 바로 선택할 수 있는 탐색기를 내장하도록 했다.

로컬 드라이브와 네트워크로 연결된 드라이브의 폴더, 파일 목록을 보여주는 것이 주된 기능이며 파일을 열거나 이름 변경, 삭제 등의 간단한 파일 관리 기능도 내장하고 있다. 더 정교하게 만들자면 파일과 관련된 많은 작업들을 할 수 있겠지만 텍스트 편집기 수준에서 꼭 필요하다고 판단되는 기능만 작성했다. 이 기능이 꼭 필요하다고 생각되지는 않지만 다른 편집기들도 파일창을 제공하므로 비슷한 스팩을 갖추기 위해 일단 기능을 구현해두었다. 디폴트 설정상태에서 이 창은 숨겨져 있다. 다음 함수들은 파일창을 구현하기 위한 보조 함수들이다.

 

BOOL IsDirEmpty(TCHAR *Path);

HTREEITEM FindInsAfter(HTREEITEM hParent, int iImage, TCHAR *Name);

int OnFindNode(TCHAR *Path,DWORD Attr,LPVOID pCustom);

void GetNodePath(HTREEITEM pNode, TCHAR *Path);

void InitTree();

void GetCurrentFolder(TCHAR *Path);

void GotoFolderInTree(TCHAR *Path);

void RefreshTree();

 

다음 코드는 파일창을 구현하기 위한 전체 소스이다. 앞에서 껍데기로 만들어 두었던 DGFileProc 함수를 다시 작성했으며 파일 목록 관리를 위한 관련 유틸리티 함수들이 많이 필요하다. 이 소스를 전부 다 입력하기는 어려우므로 CD-ROM의 소스에서 복사해 붙여넣고 코드를 분석해보기 바란다. 대부분 트리 뷰 컨트롤을 다루는 코드들이며 트리 뷰에 익숙하다면 아주 쉬운 코드이다.

 

enum {DGI_DRIVE, DGI_DRIVESEL, DGI_FOLDER, DGI_FOLDERSEL, DGI_FILE, DGI_FILESEL,

     DGI_PROJECT, DGI_PROJECTSEL, DGI_SUB, DGI_SUBSEL};

....

 

BOOL IsDirEmpty(TCHAR *Path)

{

     TCHAR SrchPath[MAX_PATH];

     WIN32_FIND_DATA wfd;

     HANDLE hSrch;

     BOOL nResult=TRUE;

     BOOL Result=FALSE;

     TCHAR *szFilter;

 

     lstrcpy(SrchPath, Path);

     lstrcat(SrchPath, "\\*.*");

     hSrch=FindFirstFile(SrchPath,&wfd);

     szFilter=(TCHAR *)SendMessage(hFilter,CB_GETITEMDATA,SendMessage(hFilter,CB_GETCURSEL,0,0),0);

 

     while (nResult) {

          if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

              if (wfd.cFileName[0]!=‘.’) {

                   Result=TRUE;

                   break;

              }

          } else {

              if (IsMatch(wfd.cFileName,szFilter)) {

                   Result=TRUE;

                   break;

              }

          }

          nResult=FindNextFile(hSrch,&wfd);

     }

     FindClose(hSrch);

     return Result;

}

 

HTREEITEM FindInsAfter(HTREEITEM hParent, int iImage, TCHAR *Name)

{

     HTREEITEM Node;

     TVITEM TV;

     TCHAR Text[MAX_PATH];

 

     Node=TreeView_GetNextItem(hFileTree,hParent,TVGN_CHILD);

     if (Node==NULL) {

          return TVI_FIRST;

     }

     if (iImage == DGI_FILE) {

          for (;;) {

              TV.mask=TVIF_IMAGE;

              TV.hItem=Node;

               TreeView_GetItem(hFileTree,&TV);

              if (TV.iImage== DGI_FILE) {

                   break;

              }

              Node=TreeView_GetNextSibling(hFileTree,Node);

              if (Node==NULL) {

                   return TVI_LAST;

              }

          }

     }

 

     for (;;) {

          TV.mask=TVIF_TEXT | TVIF_IMAGE;

          TV.hItem=Node;

          TV.pszText=Text;

          TV.cchTextMax=MAX_PATH;

          TreeView_GetItem(hFileTree,&TV);

          if (iImage==DGI_FOLDER && TV.iImage==DGI_FILE) {

              break;

          }

          if (lstrcmpi(Name,Text) < 0) {

              break;

          }

          Node=TreeView_GetNextSibling(hFileTree,Node);

          if (Node==NULL) {

              return TVI_LAST;

          }

     }

 

     Node=TreeView_GetPrevSibling(hFileTree,Node);

     if (Node==NULL) {

          return TVI_FIRST;

     } else {

          return Node;

     }

}

 

int OnFindNode(TCHAR *Path,DWORD Attr,LPVOID pCustom)

{

     TVINSERTSTRUCT TI;

     TVITEM TV;

     HTREEITEM hParent=(HTREEITEM)pCustom;

     HTREEITEM Node;

     TCHAR Ext[_MAX_EXT];

     TCHAR Name[MAX_PATH];

 

     _splitpath(Path,NULL,NULL,Name,Ext);

     lstrcat(Name,Ext);

 

     TI.hParent=hParent;

     TI.item.mask=TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;

     if (Attr & FILE_ATTRIBUTE_DIRECTORY) {

          if  (Name[0]==‘.’) {

              return 0;

          }

          TI.item.iImage=DGI_FOLDER;

          TI.item.iSelectedImage=DGI_FOLDERSEL;

     } else {

          TI.item.iImage=DGI_FILE;

          TI.item.iSelectedImage=DGI_FILESEL;

     }

     TI.hInsertAfter=FindInsAfter(hParent,TI.item.iImage,Name);

     TI.item.pszText=Name;

     Node=TreeView_InsertItem(hFileTree,&TI);

 

     if ((Attr & FILE_ATTRIBUTE_DIRECTORY) && IsDirEmpty(Path)) {

          TV.mask=TVIF_CHILDREN;

          TV.hItem=Node;

          TV.cChildren=1;

 

          TreeView_SetItem(hFileTree,&TV);

     }

     return 0;

}

 

void GetNodePath(HTREEITEM pNode, TCHAR *Path)

{

     HTREEITEM tNode;

     TVITEM TV;

     TCHAR Text[MAX_PATH];

     TCHAR szTemp[MAX_PATH];

 

     TV.mask=TVIF_TEXT;

     TV.pszText=Text;

     TV.cchTextMax=MAX_PATH;

     Path[0]=0;

 

     for (tNode=pNode;tNode!=NULL;) {

          TV.hItem=tNode;

          TreeView_GetItem(hFileTree,&TV);

 

          if (TreeView_GetParent(hFileTree,tNode)==NULL) {

              Text[1]=‘:’;

              Text[2]=0;

              wsprintf(szTemp,"%s%s",Text,Path);

          } else {

              wsprintf(szTemp,"\\%s%s",Text,Path);

          }

          lstrcpy(Path,szTemp);

          tNode=TreeView_GetParent(hFileTree,tNode);

     }

}

 

void InitTree()

{

     TVINSERTSTRUCT TI;

     TVITEM TV;

     HTREEITEM Node;

     TCHAR c;

     TCHAR Caption[MAX_PATH];

     UINT DriveType;

 

     TreeView_DeleteAllItems(hFileTree);

 

     TI.hParent=0;

     TI.hInsertAfter=TVI_LAST;

     TI.item.mask=TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;

     TI.item.iImage=DGI_DRIVE;

     TI.item.iSelectedImage=DGI_DRIVESEL;

     for (c=‘C’;c<=‘Z’;c++) {

          wsprintf(Caption,"%c:\\",c);

          DriveType=GetDriveType(Caption);

          if (DriveType==DRIVE_FIXED || DriveType==DRIVE_REMOTE) {

              wsprintf(Caption,"%c 드라이브",c);

              TI.item.pszText=Caption;

              Node=TreeView_InsertItem(hFileTree,&TI);

 

              TV.mask=TVIF_CHILDREN;

              TV.hItem=Node;

              TV.cChildren=1;

              TreeView_SetItem(hFileTree,&TV);

          }

     }

}

 

void GetCurrentFolder(TCHAR *Path)

{

     TVITEM TV;

     HTREEITEM Node;

 

     Node=TreeView_GetSelection(hFileTree);

     if (Node==NULL) {

          lstrcpy(Path,"");

          return;

     }

     TV.mask=TVIF_IMAGE;

     TV.hItem=Node;

     TreeView_GetItem(hFileTree,&TV);

     if (TV.iImage == DGI_FILE) {

          Node=TreeView_GetParent(hFileTree,Node);

     }

 

     GetNodePath(Node,Path);

}

 

void GotoFolderInTree(TCHAR *Path)

{

     TVITEM TV;

     HTREEITEM Node;

     TCHAR Text[MAX_PATH];

     TCHAR Part[MAX_PATH];

     TCHAR *p,*p2;

 

     if (lstrlen(Path)==0) {

          return;

     }

     Node=TreeView_GetRoot(hFileTree);

     p=Path;

     for (;;) {

          if (p==Path) {

              wsprintf(Part,"%c 드라이브",Path[0]);

          } else {

              p2=strchr(p,’\\’);

              if (p2==NULL) {

                   lstrcpy(Part,p);

              } else {

                   lstrcpyn(Part,p,p2-p+1);

              }

          }

          for (;;) {

              TV.mask=TVIF_TEXT;

              TV.pszText=Text;

              TV.cchTextMax=MAX_PATH;

              TV.hItem=Node;

              TreeView_GetItem(hFileTree,&TV);

              if (lstrcmpi(Text,Part)==0) {

                   break;

              }

              Node=TreeView_GetNextSibling(hFileTree,Node);

              if (Node==NULL) {

                   return;

              }

          }

          TreeView_Expand(hFileTree,Node,TVE_EXPAND);

          p=strchr(p,’\\’);

          if (p==NULL) {

              break;

          }

          p++;

          Node=TreeView_GetChild(hFileTree,Node);

     }

     if (Node) {

          TreeView_Select(hFileTree,Node,TVGN_CARET);

          TreeView_EnsureVisible(hFileTree,Node);

     }

}

 

void RefreshTree()

{

     TCHAR LastFolder[MAX_PATH];

 

     GetCurrentFolder(LastFolder);

     InitTree();

     GotoFolderInTree(LastFolder);

}

 

LRESULT CALLBACK DGFileProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     TVITEM TV;

     HTREEITEM Node;

     TCHAR Path[MAX_PATH];

     TCHAR *szFilter;

     int idx;

     HMENU hMenu, hPopup;

     LPNMHDR hdr;

     LPNMTREEVIEW ntv;

     LPNMTVDISPINFO ndi;

     TCHAR NewPath[MAX_PATH];

     TCHAR Drive[_MAX_DRIVE];

     TCHAR Dir[_MAX_DIR];

     HTREEITEM tNode, tNode2;

 

     switch(iMessage) {

     case WM_CREATE:

          hFilter=CreateWindow("combobox",NULL,WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST,

              0,0,0,0,hWnd,(HMENU)0,g_hInst,NULL);

 

          SendMessage(hFilter,WM_SETFONT,(WPARAM)hGul9,(LPARAM)FALSE);

 

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"모든 텍스트 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.txt;*.c;*.cpp;*.h;*.hpp;"

              "*.html;*.htm;*.asp;*.asa;*.php;*.php3;*.php4;*.shtml;*.sql;*.ini;"

              "*.log;*.pas;*.java;*.xml");

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"모든 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.*");

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"텍스트 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.txt");

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"C/C++ 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.c;*.cpp;*.h;*.hpp;*.inl");

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"HTML 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.html;*.htm;*.asp;*.asa;*.php;*.php3;*.php4;*.shtml");

          idx=SendMessage(hFilter,CB_ADDSTRING,0,(LPARAM)"SQL 파일");

          SendMessage(hFilter,CB_SETITEMDATA,idx,(LPARAM)"*.sql");

          SendMessage(hFilter,CB_SETCURSEL,Option.FilterIndex,0);

 

          hFileTree=CreateWindow(WC_TREEVIEW, "", WS_CHILD | WS_VISIBLE |

              TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | TVS_SHOWSELALWAYS,

              0,0,0,0,hWnd,(HMENU)1,g_hInst,NULL);

 

          hFileImg=ImageList_LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_FILES), 16, 1, RGB(255,255,255));

          SendMessage(hFileTree, TVM_SETIMAGELIST, (WPARAM)TVSIL_NORMAL, (LPARAM)hFileImg);

 

          InitTree();

          return 0;

     case WM_COMMAND:

          switch (LOWORD(wParam)) {

          case 0:

              switch (HIWORD(wParam)) {

              case CBN_SELCHANGE:

                   Option.FilterIndex=SendMessage(hFilter,CB_GETCURSEL,0,0);

                   RefreshTree();

                   break;

              }

              break;

          case IDM_FILEWND_CLOSE:

              Option.bShowFileWnd=FALSE;

              Relayout();

              break;

          case IDM_FILEWND_REFRESH:

              RefreshTree();

              break;

          case IDM_FILEWND_HIDDEN:

              Option.bShowHidden=!Option.bShowHidden;

              RefreshTree();

              break;

          case IDM_FILEWND_BROWSE:

              Option.bBrowseMode=!Option.bBrowseMode;

              break;

          case IDM_FILEWND_DELETE:

              Node=TreeView_GetSelection(hFileTree);

              GetNodePath(Node,Path);

              Path[lstrlen(Path)+1]=0;

              SHFILEOPSTRUCT sf;

              sf.hwnd=hWnd;

              sf.wFunc=FO_DELETE;

              sf.pFrom=Path;

              sf.pTo=NULL;

              sf.fFlags=FOF_ALLOWUNDO;

              sf.lpszProgressTitle="";

              if (SHFileOperation(&sf)==0) {

                    if (sf.fAnyOperationsAborted == FALSE) {

                        TreeView_DeleteItem(hFileTree,Node);

                   }

              }

              break;

          case IDM_FILEWND_RENAME:

              Node=TreeView_GetSelection(hFileTree);

              TreeView_EditLabel(hFileTree,Node);

              break;

          }

          return 0;

     case WM_NOTIFY:

          hdr=(LPNMHDR)lParam;

          ntv=(LPNMTREEVIEW)lParam;

          ndi=(LPNMTVDISPINFO)lParam;

          if (hdr->hwndFrom == hFileTree) {

              switch (hdr->code) {

              case TVN_ITEMEXPANDING:

                   if (ntv->action==TVE_EXPAND) {

                        if (TreeView_GetChild(hFileTree,ntv->itemNew.hItem)==NULL) {

                             GetNodePath(ntv->itemNew.hItem,Path);

                             szFilter=(TCHAR *)SendMessage(hFilter,CB_GETITEMDATA,

                                 SendMessage(hFilter,CB_GETCURSEL,0,0),0);

                             bContFIF=TRUE;

                             FindInFiles(Path,szFilter,Option.bShowHidden ? FIF_INCHID:0,

                                  OnFindNode,ntv->itemNew.hItem);

                        }

                   }

                   break;

              case TVN_SELCHANGED:

                   if (Option.bBrowseMode) {

                        Node=ntv->itemNew.hItem;

                        TV.mask=TVIF_IMAGE;

                        TV.hItem=Node;

                        TreeView_GetItem(hFileTree,&TV);

                        if (TV.iImage==DGI_FILE) {

                             GetNodePath(Node,Path);

                             OpenFromFile(Path,FALSE,TRUE);

                        }

                   }

                   break;

              case NM_DBLCLK:

                   Node=TreeView_GetSelection(hFileTree);

                   if (Node) {

                        TV.mask=TVIF_IMAGE;

                        TV.hItem=Node;

                        TreeView_GetItem(hFileTree,&TV);

                        if (TV.iImage==DGI_FILE) {

                             GetNodePath(Node,Path);

                             OpenFromFile(Path);

                        }

                   }

                   break;

              case TVN_BEGINLABELEDIT:

                   for (tNode=ndi->item.hItem;tNode2=TreeView_GetParent(hFileTree,tNode);tNode=tNode2);

                   TV.mask=TVIF_IMAGE;

                    TV.hItem=tNode;

                   TreeView_GetItem(hFileTree,&TV);

                   if (tNode==ndi->item.hItem || TV.iImage != DGI_DRIVE) {

                        return TRUE;

                   } else {

                        return FALSE;

                   }

              case TVN_ENDLABELEDIT:

                   ndi=(LPNMTVDISPINFO)lParam;

                   if (ndi->item.pszText == NULL)

                        return FALSE;

                   GetNodePath(ndi->item.hItem,Path);

                   _splitpath(Path,Drive,Dir,NULL,NULL);

                   wsprintf(NewPath,"%s%s%s",Drive,Dir,ndi->item.pszText);

                   if (MoveFile(Path,NewPath)==FALSE) {

                        return FALSE;

                   }

                   return TRUE;

              case TVN_DELETEITEM:

                   if (ntv->itemOld.lParam) {

                        free((LPVOID)ntv->itemOld.lParam);

                   }

                   break;

              }

          }

          return 0;

     case WM_CONTEXTMENU:

          POINT pt;

          hMenu=LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_POPUP));

          hPopup=GetSubMenu(hMenu, 1);

 

          TVHITTESTINFO ht;

          ht.pt.x=LOWORD(lParam);

          ht.pt.y=HIWORD(lParam);

          ScreenToClient(hWnd,&ht.pt);

          TreeView_HitTest(hFileTree,&ht);

          TreeView_SelectItem(hFileTree,ht.hItem);

 

          if (Option.bShowHidden) {

              CheckMenuItem(hMenu, IDM_FILEWND_HIDDEN, MF_BYCOMMAND | MF_CHECKED);

          } else {

              CheckMenuItem(hMenu, IDM_FILEWND_HIDDEN, MF_BYCOMMAND | MF_UNCHECKED);

          }

 

          if (Option.bBrowseMode) {

              CheckMenuItem(hMenu, IDM_FILEWND_BROWSE, MF_BYCOMMAND | MF_CHECKED);

          } else {

              CheckMenuItem(hMenu, IDM_FILEWND_BROWSE, MF_BYCOMMAND | MF_UNCHECKED);

          }

 

          if (TreeView_GetParent(hFileTree,ht.hItem)==NULL) {

              EnableMenuItem(hMenu, IDM_FILEWND_DELETE, MF_BYCOMMAND | MF_GRAYED);

              EnableMenuItem(hMenu, IDM_FILEWND_RENAME, MF_BYCOMMAND | MF_GRAYED);

          } else {

              EnableMenuItem(hMenu, IDM_FILEWND_DELETE, MF_BYCOMMAND | MF_ENABLED);

              EnableMenuItem(hMenu, IDM_FILEWND_RENAME, MF_BYCOMMAND | MF_ENABLED);

          }

 

          if (LOWORD(lParam)==65535) {

              pt.x=20;

              pt.y=20;

              ClientToScreen(hWnd,&pt);

              lParam=MAKELPARAM(pt.x,pt.y);

          }

          TrackPopupMenu(hPopup, TPM_LEFTALIGN, LOWORD(lParam), HIWORD(lParam),

              0, hWnd, NULL);

          DestroyMenu(hMenu);

          return 0;

     case WM_SIZE:

          MoveWindow(hFileTree,0,0,LOWORD(lParam),HIWORD(lParam)-26,TRUE);

          MoveWindow(hFilter,0,HIWORD(lParam)-23,LOWORD(lParam),350,TRUE);

          return 0;

     case WM_DESTROY:

          ImageList_Destroy(hFileImg);

          return 0;

     }

     return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

완전히 독립된 기능을 구현하는 코드들이므로 기존의 코드는 거의 수정할 필요가 없다. , 프로그램을 종료할 때 최후 선택된 폴더의 위치를 저장하고 다시 읽어오는 코드만 작성하면 된다. OnDestroy에 다음 코드를 추가한다.

 

void OnDestroy(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     TCHAR LastFolder[MAX_PATH];

     ....

     GetCurrentFolder(LastFolder);

     SHRegWriteString(SHCU,KEY"Setting","LastFolder",LastFolder);

}

 

GetCurrentFolder 함수로 파일창에 선택된 마지막 폴더의 위치를 레지스트리에 저장해두었다. 이 경로는 초기화가 완료된 시점인 OnTimer에서 다시 읽혀진다.

 

void OnTimer(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     ....

     switch (wParam) {

     case 100:

          ....

        SHRegReadString(SHCU,KEY"Setting","LastFolder","",Path,MAX_PATH);

        GotoFolderInTree(Path);

          break;

     }

}

 

레지스트리에 저장된 최후 폴더 위치를 읽어 그 위치를 다시 찾아가도록 했다. 이 코드는 파일창의 WM_INITDIALOG에 있어도 상관없다. 그러나 다음에 작성할 프로젝트 기능을 지원하기 위해 OnTimer에 미리 작성해두었다.