From 07ce6c2516bf1fb8cc14d3bee897b39953382466 Mon Sep 17 00:00:00 2001 From: Don Ho Date: Sat, 23 Jan 2016 02:24:37 +0100 Subject: [PATCH] New feature: File browser --- PowerEditor/src/MISC/Common/Common.cpp | 10 +- PowerEditor/src/MISC/Common/Common.h | 2 +- .../Process/{Process.cpp => Processus.cpp} | 2 +- .../MISC/Process/{Process.h => Processus.h} | 2 + PowerEditor/src/Notepad_plus.cpp | 131 +- PowerEditor/src/Notepad_plus.h | 13 +- PowerEditor/src/NppIO.cpp | 123 ++ .../WinControls/FileBrowser/fileBrowser.cpp | 1232 +++++++++++++++++ .../src/WinControls/FileBrowser/fileBrowser.h | 191 +++ .../WinControls/FileBrowser/fileBrowser.rc | 52 + .../WinControls/FileBrowser/fileBrowser_rc.h | 51 + .../ReadDirectoryChanges.cpp | 112 ++ .../ReadDirectoryChanges.h | 164 +++ .../ReadDirectoryChangesPrivate.cpp | 178 +++ .../ReadDirectoryChangesPrivate.h | 177 +++ .../ReadDirectoryChanges/ThreadSafeQueue.h | 116 ++ .../ReadDirectoryChanges/targetver.h | 8 + PowerEditor/src/resource.h | 2 + PowerEditor/src/winmain.cpp | 2 +- PowerEditor/visual.net/notepadPlus.vcxproj | 19 +- 20 files changed, 2555 insertions(+), 32 deletions(-) rename PowerEditor/src/MISC/Process/{Process.cpp => Processus.cpp} (96%) rename PowerEditor/src/MISC/Process/{Process.h => Processus.h} (95%) create mode 100644 PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp create mode 100644 PowerEditor/src/WinControls/FileBrowser/fileBrowser.h create mode 100644 PowerEditor/src/WinControls/FileBrowser/fileBrowser.rc create mode 100644 PowerEditor/src/WinControls/FileBrowser/fileBrowser_rc.h create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.cpp create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.h create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.cpp create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.h create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/ThreadSafeQueue.h create mode 100644 PowerEditor/src/WinControls/ReadDirectoryChanges/targetver.h diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index 7f2a3b24..7ee055a8 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -885,5 +885,13 @@ bool str2Clipboard(const generic_string &str2cpy, HWND hwnd) return true; } - +bool matchInList(const TCHAR *fileName, const std::vector & patterns) +{ + for (size_t i = 0, len = patterns.size(); i < len; ++i) + { + if (PathMatchSpec(fileName, patterns[i].c_str())) + return true; + } + return false; +} diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index 3f0646d0..94d04cb0 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -88,7 +88,7 @@ generic_string BuildMenuFileName(int filenameLen, unsigned int pos, const generi std::string getFileContent(const TCHAR *file2read); generic_string relativeFilePathToFullFilePath(const TCHAR *relativeFilePath); void writeFileContent(const TCHAR *file2write, const char *content2write); - +bool matchInList(const TCHAR *fileName, const std::vector & patterns); class WcharMbcsConvertor final { diff --git a/PowerEditor/src/MISC/Process/Process.cpp b/PowerEditor/src/MISC/Process/Processus.cpp similarity index 96% rename from PowerEditor/src/MISC/Process/Process.cpp rename to PowerEditor/src/MISC/Process/Processus.cpp index a224eece..af1c44ec 100644 --- a/PowerEditor/src/MISC/Process/Process.cpp +++ b/PowerEditor/src/MISC/Process/Processus.cpp @@ -27,7 +27,7 @@ #include "Parameters.h" -#include "Process.h" +#include "Processus.h" void Process::run() diff --git a/PowerEditor/src/MISC/Process/Process.h b/PowerEditor/src/MISC/Process/Processus.h similarity index 95% rename from PowerEditor/src/MISC/Process/Process.h rename to PowerEditor/src/MISC/Process/Processus.h index 518bfe87..62cee103 100644 --- a/PowerEditor/src/MISC/Process/Process.h +++ b/PowerEditor/src/MISC/Process/Processus.h @@ -29,6 +29,8 @@ #ifndef PROCESSUS_H #define PROCESSUS_H +#include "common.h" + enum progType {WIN32_PROG, CONSOLE_PROG}; class Process diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index 9394bc9a..455371e2 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -47,6 +47,7 @@ #include "ProjectPanel.h" #include "documentMap.h" #include "functionListPanel.h" +#include "fileBrowser.h" #include "LongRunningOperation.h" using namespace std; @@ -193,6 +194,7 @@ Notepad_plus::~Notepad_plus() delete _pProjectPanel_3; delete _pDocMap; delete _pFuncList; + delete _pFileBrowser; } LRESULT Notepad_plus::init(HWND hwnd) @@ -1090,16 +1092,6 @@ bool Notepad_plus::replaceInOpenedFiles() { return true; } -bool Notepad_plus::matchInList(const TCHAR *fileName, const vector & patterns) -{ - for (size_t i = 0, len = patterns.size() ; i < len ; ++i) - { - if (PathMatchSpec(fileName, patterns[i].c_str())) - return true; - } - return false; -} - void Notepad_plus::wsTabConvert(spaceTab whichWay) { @@ -1435,7 +1427,6 @@ void Notepad_plus::getMatchedFileNames(const TCHAR *dir, const vector folderPaths; + vector filePaths; + for (int i = 0; i < filesDropped; ++i) { TCHAR pathDropped[MAX_PATH]; ::DragQueryFile(hdrop, i, pathDropped, MAX_PATH); - BufferID test = doOpen(pathDropped); - if (test != BUFFER_INVALID) - lastOpened = test; - //setLangStatus(_pEditView->getCurrentDocType()); + if (::PathIsDirectory(pathDropped)) + { + size_t len = lstrlen(pathDropped); + if (len > 0 && pathDropped[len - 1] != TCHAR('\\')) + { + pathDropped[len] = TCHAR('\\'); + pathDropped[len + 1] = TCHAR('\0'); + } + folderPaths.push_back(pathDropped); + } + else + { + filePaths.push_back(pathDropped); + } } - if (lastOpened != BUFFER_INVALID) { + + bool isOldMode = false; + + if (isOldMode || folderPaths.size() == 0) // old mode or new mode + only files + { + + BufferID lastOpened = BUFFER_INVALID; + for (int i = 0; i < filesDropped; ++i) + { + TCHAR pathDropped[MAX_PATH]; + ::DragQueryFile(hdrop, i, pathDropped, MAX_PATH); + BufferID test = doOpen(pathDropped); + if (test != BUFFER_INVALID) + lastOpened = test; + } + if (lastOpened != BUFFER_INVALID) { + switchToFile(lastOpened); + } + } + else if (not isOldMode && (folderPaths.size() != 0 && filePaths.size() != 0)) // new mode && both folders & files + { + // display error & do nothing + } + else if (not isOldMode && (folderPaths.size() != 0 && filePaths.size() == 0)) // new mode && only folders + { + launchFileBrowser(); + + // process new mode + + for (int i = 0; i < filesDropped; ++i) + { + _pFileBrowser->addRootFolder(folderPaths[i]); + } + + /* + for (int i = 0; i < filesDropped; ++i) + { + if (not _pFileBrowser->isAlreadyExist(folderPaths[i])) + { + vector patterns2Match; + patterns2Match.push_back(TEXT("*.*")); + + FolderInfo directoryStructure; + getDirectoryStructure(folderPaths[i].c_str(), patterns2Match, directoryStructure, true, false); + _pFileBrowser->setDirectoryStructure(directoryStructure); + } + int j = 0; + j++; + } + */ + } + + if (lastOpened != BUFFER_INVALID) + { switchToFile(lastOpened); } ::DragFinish(hdrop); @@ -4607,6 +4664,7 @@ void Notepad_plus::notifyBufferChanged(Buffer * buffer, int mask) // Then we ask user to update didDialog = true; + if (doReloadOrNot(buffer->getFullPathName(), buffer->isDirty()) != IDYES) break; //abort } @@ -5294,6 +5352,45 @@ void Notepad_plus::launchAnsiCharPanel() _pAnsiCharPanel->display(); } +void Notepad_plus::launchFileBrowser() +{ + if (!_pFileBrowser) + { + _pFileBrowser = new FileBrowser; + _pFileBrowser->init(_pPublicInterface->getHinst(), _pPublicInterface->getHSelf()); + + tTbData data; + memset(&data, 0, sizeof(data)); + _pFileBrowser->create(&data); + data.pszName = TEXT("ST"); + + ::SendMessage(_pPublicInterface->getHSelf(), NPPM_MODELESSDIALOG, MODELESSDIALOGREMOVE, (WPARAM)_pFileBrowser->getHSelf()); + // define the default docking behaviour + data.uMask = DWS_DF_CONT_LEFT | DWS_ICONTAB; + data.hIconTab = (HICON)::LoadImage(_pPublicInterface->getHinst(), MAKEINTRESOURCE(IDR_PROJECTPANEL_ICO), IMAGE_ICON, 14, 14, LR_LOADMAP3DCOLORS | LR_LOADTRANSPARENT); + data.pszModuleName = NPP_INTERNAL_FUCTION_STR; + + NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance())->getNativeLangSpeaker(); + generic_string title_temp = pNativeSpeaker->getAttrNameStr(PM_PROJECTPANELTITLE, "FileBrowser", "PanelTitle"); + + static TCHAR title[32]; + if (title_temp.length() < 32) + { + lstrcpy(title, title_temp.c_str()); + data.pszName = title; + } + ::SendMessage(_pPublicInterface->getHSelf(), NPPM_DMMREGASDCKDLG, 0, (LPARAM)&data); + + COLORREF fgColor = (NppParameters::getInstance())->getCurrentDefaultFgColor(); + COLORREF bgColor = (NppParameters::getInstance())->getCurrentDefaultBgColor(); + + _pFileBrowser->setBackgroundColor(bgColor); + _pFileBrowser->setForegroundColor(fgColor); + } + + _pFileBrowser->display(); +} + void Notepad_plus::launchProjectPanel(int cmdID, ProjectPanel ** pProjPanel, int panelID) { diff --git a/PowerEditor/src/Notepad_plus.h b/PowerEditor/src/Notepad_plus.h index ef8e1682..f55b5bb6 100644 --- a/PowerEditor/src/Notepad_plus.h +++ b/PowerEditor/src/Notepad_plus.h @@ -105,7 +105,7 @@ #endif //DOCKINGMANAGER_H #ifndef PROCESSUS_H -#include "Process.h" +#include "Processus.h" #endif //PROCESSUS_H #ifndef AUTOCOMPLETION_H @@ -195,9 +195,7 @@ class VerticalFileSwitcher; class ProjectPanel; class DocumentMap; class FunctionListPanel; - - - +class FileBrowser; class Notepad_plus final @@ -421,6 +419,8 @@ private: ProjectPanel* _pProjectPanel_2 = nullptr; ProjectPanel* _pProjectPanel_3 = nullptr; + FileBrowser* _pFileBrowser = nullptr; + DocumentMap* _pDocMap = nullptr; FunctionListPanel* _pFuncList = nullptr; @@ -589,9 +589,7 @@ private: bool findInOpenedFiles(); bool findInCurrentFile(); - bool matchInList(const TCHAR *fileName, const std::vector & patterns); void getMatchedFileNames(const TCHAR *dir, const std::vector & patterns, std::vector & fileNames, bool isRecursive, bool isInHiddenDir); - void doSynScorll(HWND hW); void setWorkingDir(const TCHAR *dir); bool str2Cliboard(const generic_string & str2cpy); @@ -623,6 +621,7 @@ private: void launchProjectPanel(int cmdID, ProjectPanel ** pProjPanel, int panelID); void launchDocMap(); void launchFunctionList(); + void launchFileBrowser(); void showAllQuotes() const; static DWORD WINAPI threadTextPlayer(void *text2display); static DWORD WINAPI threadTextTroller(void *params); @@ -640,6 +639,8 @@ private: } static DWORD WINAPI backupDocument(void *params); + //static DWORD WINAPI monitorFileOnChange(void * params); + //static DWORD WINAPI monitorDirectoryOnChange(void * params); }; diff --git a/PowerEditor/src/NppIO.cpp b/PowerEditor/src/NppIO.cpp index d5190957..350a6a5b 100644 --- a/PowerEditor/src/NppIO.cpp +++ b/PowerEditor/src/NppIO.cpp @@ -37,8 +37,117 @@ using namespace std; +/* +struct monitorFileParams { + WCHAR _fullFilePath[MAX_PATH]; +}; + +DWORD WINAPI Notepad_plus::monitorFileOnChange(void * params) +{ + monitorFileParams *mfp = (monitorFileParams *)params; + + //Le répertoire à surveiller : + WCHAR folderToMonitor[MAX_PATH]; + //::MessageBoxW(NULL, mfp->_fullFilePath, TEXT("PATH AFTER thread"), MB_OK); + lstrcpy(folderToMonitor, mfp->_fullFilePath); + //::PathRemoveFileSpecW(folderToMonitor); + + HANDLE hDirectory = ::CreateFile(folderToMonitor, + FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + // buffer qui va récuppérer les informations de mise à jour + const int MAX_BUFFER = 1024; + BYTE buffer[MAX_BUFFER]; + DWORD nombreDeByteRetournes = 0; + + bool cond = true; + while (cond) + { + ::Sleep(1000); + // surveille un changement dans le répertoire : il attend tant que rien ne se passe : c’est synchrone. + BOOL res = ReadDirectoryChangesW(hDirectory, buffer, MAX_BUFFER, + TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE, &nombreDeByteRetournes, NULL, NULL); + + if (res == FALSE) + continue; + + // puis on transforme le buffer pour être lisible. + FILE_NOTIFY_INFORMATION *notifyInfo = (FILE_NOTIFY_INFORMATION *)buffer; + + wchar_t fn[MAX_PATH]; + memset(fn, 0, MAX_PATH*sizeof(wchar_t)); + if (notifyInfo->Action != FILE_ACTION_MODIFIED) + continue; + + // affiche le fichier qui a été modifié. + if (notifyInfo->FileNameLength <= 0) + continue; + + TCHAR str2Display[512]; + generic_strncpy(fn, notifyInfo->FileName, notifyInfo->FileNameLength / sizeof(wchar_t)); + generic_sprintf(str2Display, TEXT("offset : %d\raction : %d\rfn len : %d\rfn : %s"), notifyInfo->NextEntryOffset, notifyInfo->Action, notifyInfo->FileNameLength, notifyInfo->FileName); + // on peut vérifier avec ceci : + //printInt(notifyInfo->NextEntryOffset); + //printInt(notifyInfo->FileNameLength); + MessageBox(NULL, str2Display, TEXT("name"), MB_OK); + } + return TRUE; +} + +DWORD WINAPI Notepad_plus::monitorDirectoryOnChange(void * params) +{ + monitorFileParams *mfp = (monitorFileParams *)params; + + //Le répertoire à surveiller : + WCHAR folderToMonitor[MAX_PATH]; + //::MessageBoxW(NULL, mfp->_fullFilePath, TEXT("PATH AFTER thread"), MB_OK); + lstrcpy(folderToMonitor, mfp->_fullFilePath); + + + //::PathRemoveFileSpecW(folderToMonitor); + + HANDLE hDirectory = ::CreateFile(folderToMonitor, + FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + + // buffer qui va récuppérer les informations de mise à jour + const int MAX_BUFFER = 1024; + BYTE buffer[MAX_BUFFER]; + DWORD nombreDeByteRetournes = 0; + + bool cond = true; + while (cond) + { + ::Sleep(1000); + // surveille un changement dans le répertoire : il attend tant que rien ne se passe : c’est synchrone. + ReadDirectoryChangesW(hDirectory, buffer, MAX_BUFFER, + TRUE, FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &nombreDeByteRetournes, NULL, NULL); + // puis on transforme le buffer pour être lisible. + FILE_NOTIFY_INFORMATION *notifyInfo = (FILE_NOTIFY_INFORMATION *)buffer; + + wchar_t fn[MAX_PATH]; + memset(fn, 0, MAX_PATH*sizeof(wchar_t)); + + //if (notifyInfo->Action != FILE_ACTION_MODIFIED) + if (notifyInfo->Action != FILE_ACTION_ADDED && notifyInfo->Action != FILE_ACTION_REMOVED && notifyInfo->Action != FILE_ACTION_RENAMED_OLD_NAME && notifyInfo->Action != FILE_ACTION_RENAMED_NEW_NAME) + continue; + + // affiche le fichier qui a été modifié. + //if (notifyInfo->FileNameLength <= 0) + //continue; + + generic_strncpy(fn, notifyInfo->FileName, notifyInfo->FileNameLength / sizeof(wchar_t)); + + // on peut vérifier avec ceci : + //printInt(notifyInfo->FileNameLength); + MessageBox(NULL, fn, TEXT("name"), MB_OK); + } + return TRUE; +} +*/ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, bool isReadOnly, int encoding, const TCHAR *backupFileName, time_t fileNameTimestamp) { @@ -256,6 +365,20 @@ BufferID Notepad_plus::doOpen(const generic_string& fileName, bool isRecursive, _pluginsManager.notify(&scnN); if (_pFileSwitcherPanel) _pFileSwitcherPanel->newItem(buf, currentView()); + + /* + if (::PathFileExists(longFileName)) + { + // Thread to + monitorFileParams *params = new monitorFileParams; + lstrcpy(params->_fullFilePath, longFileName); + //::MessageBoxW(NULL, params._fullFilePath, TEXT("PATH b4 thread"), MB_OK); + //HANDLE hThread = ::CreateThread(NULL, 0, monitorFileOnChange, params, 0, NULL); + HANDLE hThread = ::CreateThread(NULL, 0, monitorFileOnChange, params, 0, NULL); + ::CloseHandle(hThread); + } + */ + } else { diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp new file mode 100644 index 00000000..91bf3e74 --- /dev/null +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.cpp @@ -0,0 +1,1232 @@ +// This file is part of Notepad++ project +// Copyright (C)2003 Don HO +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +#include "fileBrowser.h" +#include "resource.h" +#include "tinyxml.h" +#include "FileDialog.h" +#include "localization.h" +#include "Parameters.h" + +#define CX_BITMAP 16 +#define CY_BITMAP 16 + +#define INDEX_OPEN_ROOT 0 +#define INDEX_CLOSE_ROOT 1 +#define INDEX_OPEN_NODE 3 +#define INDEX_CLOSED_NODE 4 +#define INDEX_LEAF 5 +#define INDEX_LEAF_INVALID 6 + +#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) +#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) + +FileBrowser::~FileBrowser() +{ + for (size_t i = 0; i < _folderUpdaters.size(); ++i) + { + _folderUpdaters[i].stopWatcher(); + } +} + +INT_PTR CALLBACK FileBrowser::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG : + { + FileBrowser::initMenus(); + + // Create toolbar menu + int style = WS_CHILD | WS_VISIBLE | CCS_ADJUSTABLE | TBSTYLE_AUTOSIZE | TBSTYLE_FLAT | TBSTYLE_LIST; + _hToolbarMenu = CreateWindowEx(0,TOOLBARCLASSNAME,NULL, style, + 0,0,0,0,_hSelf,(HMENU)0, _hInst, NULL); + TBBUTTON tbButtons[2]; + + NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance())->getNativeLangSpeaker(); + generic_string workspace_entry = pNativeSpeaker->getProjectPanelLangMenuStr("Entries", 0, PM_WORKSPACEMENUENTRY); + generic_string edit_entry = pNativeSpeaker->getProjectPanelLangMenuStr("Entries", 1, PM_EDITMENUENTRY); + + tbButtons[0].idCommand = IDB_FILEBROWSER_BTN; + tbButtons[0].iBitmap = I_IMAGENONE; + tbButtons[0].fsState = TBSTATE_ENABLED; + tbButtons[0].fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + tbButtons[0].iString = (INT_PTR)workspace_entry.c_str(); + + tbButtons[1].idCommand = IDB_FILEBROWSER_EDIT_BTN; + tbButtons[1].iBitmap = I_IMAGENONE; + tbButtons[1].fsState = TBSTATE_ENABLED; + tbButtons[1].fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + tbButtons[1].iString = (INT_PTR)edit_entry.c_str(); + + SendMessage(_hToolbarMenu, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0); + SendMessage(_hToolbarMenu, TB_ADDBUTTONS, (WPARAM)sizeof(tbButtons) / sizeof(TBBUTTON), (LPARAM)&tbButtons); + SendMessage(_hToolbarMenu, TB_AUTOSIZE, 0, 0); + ShowWindow(_hToolbarMenu, SW_SHOW); + + _treeView.init(_hInst, _hSelf, ID_FILEBROWSERTREEVIEW); + + setImageList(IDI_PROJECT_WORKSPACE, IDI_PROJECT_WORKSPACEDIRTY, IDI_PROJECT_PROJECT, IDI_PROJECT_FOLDEROPEN, IDI_PROJECT_FOLDERCLOSE, IDI_PROJECT_FILE, IDI_PROJECT_FILEINVALID); + _treeView.addCanNotDropInList(INDEX_LEAF); + _treeView.addCanNotDropInList(INDEX_LEAF_INVALID); + + //_treeView.addCanNotDragOutList(INDEX_CLEAN_ROOT); + //_treeView.addCanNotDragOutList(INDEX_DIRTY_ROOT); + //_treeView.addCanNotDragOutList(INDEX_PROJECT); + + _treeView.display(); + + /* + if (!openWorkSpace(_workSpaceFilePath.c_str())) + newWorkSpace(); + */ + + return TRUE; + } + + + case WM_MOUSEMOVE: + if (_treeView.isDragging()) + _treeView.dragItem(_hSelf, LOWORD(lParam), HIWORD(lParam)); + break; + case WM_LBUTTONUP: + if (_treeView.isDragging()) + if (_treeView.dropItem()) + { + + } + break; + + case WM_NOTIFY: + { + notified((LPNMHDR)lParam); + } + return TRUE; + + case WM_SIZE: + { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + RECT toolbarMenuRect; + ::GetClientRect(_hToolbarMenu, &toolbarMenuRect); + + ::MoveWindow(_hToolbarMenu, 0, 0, width, toolbarMenuRect.bottom, TRUE); + + HWND hwnd = _treeView.getHSelf(); + if (hwnd) + ::MoveWindow(hwnd, 0, toolbarMenuRect.bottom + 2, width, height - toolbarMenuRect.bottom - 2, TRUE); + break; + } + + case WM_CONTEXTMENU: + if (!_treeView.isDragging()) + showContextMenu(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + return TRUE; + + case WM_COMMAND: + { + popupMenuCmd(LOWORD(wParam)); + break; + } + + case WM_DESTROY: + { + _treeView.destroy(); + destroyMenus(); + ::DestroyWindow(_hToolbarMenu); + break; + } + case WM_KEYDOWN: + //if (wParam == VK_F2) + { + ::MessageBoxA(NULL,"vkF2","",MB_OK); + } + break; + + default : + return DockingDlgInterface::run_dlgProc(message, wParam, lParam); + } + return DockingDlgInterface::run_dlgProc(message, wParam, lParam); +} + +void FileBrowser::initMenus() +{ + _hWorkSpaceMenu = ::CreatePopupMenu(); + + NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance())->getNativeLangSpeaker(); + + generic_string edit_moveup = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_MOVEUP, PM_MOVEUPENTRY); + generic_string edit_movedown = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_MOVEDOWN, PM_MOVEDOWNENTRY); + generic_string edit_rename = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_RENAME, PM_EDITRENAME); + generic_string edit_addfolder = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_NEWFOLDER, PM_EDITNEWFOLDER); + generic_string edit_addfiles = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_ADDFILES, PM_EDITADDFILES); + //generic_string edit_addfilesRecursive = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_ADDFILESRECUSIVELY, PM_EDITADDFILESRECUSIVELY); + generic_string edit_remove = pNativeSpeaker->getProjectPanelLangMenuStr("ProjectMenu", IDM_FILEBROWSER_DELETEFOLDER, PM_EDITREMOVE); + + _hProjectMenu = ::CreatePopupMenu(); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEUP, edit_moveup.c_str()); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEDOWN, edit_movedown.c_str()); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, UINT(-1), 0); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_RENAME, edit_rename.c_str()); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_NEWFOLDER, edit_addfolder.c_str()); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_ADDFILES, edit_addfiles.c_str()); + //::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_ADDFILESRECUSIVELY, edit_addfilesRecursive.c_str()); + ::InsertMenu(_hProjectMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_DELETEFOLDER, edit_remove.c_str()); + + edit_moveup = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_MOVEUP, PM_MOVEUPENTRY); + edit_movedown = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_MOVEDOWN, PM_MOVEDOWNENTRY); + edit_rename = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_RENAME, PM_EDITRENAME); + edit_addfolder = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_NEWFOLDER, PM_EDITNEWFOLDER); + edit_addfiles = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_ADDFILES, PM_EDITADDFILES); + //edit_addfilesRecursive = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_ADDFILESRECUSIVELY, PM_EDITADDFILESRECUSIVELY); + edit_remove = pNativeSpeaker->getProjectPanelLangMenuStr("FolderMenu", IDM_FILEBROWSER_DELETEFOLDER, PM_EDITREMOVE); + + _hFolderMenu = ::CreatePopupMenu(); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEUP, edit_moveup.c_str()); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEDOWN, edit_movedown.c_str()); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, UINT(-1), 0); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_RENAME, edit_rename.c_str()); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_NEWFOLDER, edit_addfolder.c_str()); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_ADDFILES, edit_addfiles.c_str()); + //::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_ADDFILESRECUSIVELY, edit_addfilesRecursive.c_str()); + ::InsertMenu(_hFolderMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_DELETEFOLDER, edit_remove.c_str()); + + edit_moveup = pNativeSpeaker->getProjectPanelLangMenuStr("FileMenu", IDM_FILEBROWSER_MOVEUP, PM_MOVEUPENTRY); + edit_movedown = pNativeSpeaker->getProjectPanelLangMenuStr("FileMenu", IDM_FILEBROWSER_MOVEDOWN, PM_MOVEDOWNENTRY); + edit_rename = pNativeSpeaker->getProjectPanelLangMenuStr("FileMenu", IDM_FILEBROWSER_RENAME, PM_EDITRENAME); + edit_remove = pNativeSpeaker->getProjectPanelLangMenuStr("FileMenu", IDM_FILEBROWSER_DELETEFILE, PM_EDITREMOVE); + generic_string edit_modifyfile = pNativeSpeaker->getProjectPanelLangMenuStr("FileMenu", IDM_FILEBROWSER_MODIFYFILEPATH, PM_EDITMODIFYFILE); + + _hFileMenu = ::CreatePopupMenu(); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEUP, edit_moveup.c_str()); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MOVEDOWN, edit_movedown.c_str()); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, UINT(-1), 0); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_RENAME, edit_rename.c_str()); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_DELETEFILE, edit_remove.c_str()); + ::InsertMenu(_hFileMenu, 0, MF_BYCOMMAND, IDM_FILEBROWSER_MODIFYFILEPATH, edit_modifyfile.c_str()); +} + + +BOOL FileBrowser::setImageList(int root_clean_id, int root_dirty_id, int project_id, int open_node_id, int closed_node_id, int leaf_id, int ivalid_leaf_id) +{ + HBITMAP hbmp; + COLORREF maskColour = RGB(192, 192, 192); + const int nbBitmaps = 7; + + // Creation of image list + if ((_hImaLst = ImageList_Create(CX_BITMAP, CY_BITMAP, ILC_COLOR32 | ILC_MASK, nbBitmaps, 0)) == NULL) + return FALSE; + + // Add the bmp in the list + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(root_clean_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(root_dirty_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(project_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(open_node_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(closed_node_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(leaf_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + hbmp = LoadBitmap(_hInst, MAKEINTRESOURCE(ivalid_leaf_id)); + if (hbmp == NULL) + return FALSE; + ImageList_AddMasked(_hImaLst, hbmp, maskColour); + DeleteObject(hbmp); + + if (ImageList_GetImageCount(_hImaLst) < nbBitmaps) + return FALSE; + + // Set image list to the tree view + TreeView_SetImageList(_treeView.getHSelf(), _hImaLst, TVSIL_NORMAL); + + return TRUE; +} + + +void FileBrowser::destroyMenus() +{ + ::DestroyMenu(_hWorkSpaceMenu); + ::DestroyMenu(_hProjectMenu); + ::DestroyMenu(_hFolderMenu); + ::DestroyMenu(_hFileMenu); +} + +void FileBrowser::buildProjectXml(TiXmlNode *node, HTREEITEM hItem, const TCHAR* fn2write) +{ + TCHAR textBuffer[MAX_PATH]; + TVITEM tvItem; + tvItem.mask = TVIF_TEXT | TVIF_PARAM; + tvItem.pszText = textBuffer; + tvItem.cchTextMax = MAX_PATH; + + for (HTREEITEM hItemNode = _treeView.getChildFrom(hItem); + hItemNode != NULL; + hItemNode = _treeView.getNextSibling(hItemNode)) + { + tvItem.hItem = hItemNode; + SendMessage(_treeView.getHSelf(), TVM_GETITEM, 0,(LPARAM)&tvItem); + if (tvItem.lParam != NULL) + { + generic_string *fn = (generic_string *)tvItem.lParam; + generic_string newFn = getRelativePath(*fn, fn2write); + TiXmlNode *fileLeaf = node->InsertEndChild(TiXmlElement(TEXT("File"))); + fileLeaf->ToElement()->SetAttribute(TEXT("name"), newFn.c_str()); + } + else + { + TiXmlNode *folderNode = node->InsertEndChild(TiXmlElement(TEXT("Folder"))); + folderNode->ToElement()->SetAttribute(TEXT("name"), tvItem.pszText); + buildProjectXml(folderNode, hItemNode, fn2write); + } + } +} + +generic_string FileBrowser::getRelativePath(const generic_string & filePath, const TCHAR *workSpaceFileName) +{ + TCHAR wsfn[MAX_PATH]; + lstrcpy(wsfn, workSpaceFileName); + ::PathRemoveFileSpec(wsfn); + + size_t pos_found = filePath.find(wsfn); + if (pos_found == generic_string::npos) + return filePath; + const TCHAR *relativeFile = filePath.c_str() + lstrlen(wsfn); + if (relativeFile[0] == '\\') + ++relativeFile; + return relativeFile; +} + +void FileBrowser::openSelectFile() +{ + TVITEM tvItem; + tvItem.mask = TVIF_PARAM; + tvItem.hItem = _treeView.getSelection(); + ::SendMessage(_treeView.getHSelf(), TVM_GETITEM, 0,(LPARAM)&tvItem); + + BrowserNodeType nType = getNodeType(tvItem.hItem); + generic_string *fn = (generic_string *)tvItem.lParam; + if (nType == browserNodeType_file && fn) + { + tvItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; + if (::PathFileExists(fn->c_str())) + { + ::SendMessage(_hParent, NPPM_DOOPEN, 0, (LPARAM)(fn->c_str())); + tvItem.iImage = INDEX_LEAF; + tvItem.iSelectedImage = INDEX_LEAF; + } + else + { + tvItem.iImage = INDEX_LEAF_INVALID; + tvItem.iSelectedImage = INDEX_LEAF_INVALID; + } + TreeView_SetItem(_treeView.getHSelf(), &tvItem); + } +} + + +void FileBrowser::notified(LPNMHDR notification) +{ + if ((notification->hwndFrom == _treeView.getHSelf())) + { + TCHAR textBuffer[MAX_PATH]; + TVITEM tvItem; + tvItem.mask = TVIF_TEXT | TVIF_PARAM; + tvItem.pszText = textBuffer; + tvItem.cchTextMax = MAX_PATH; + + switch (notification->code) + { + case NM_DBLCLK: + { + openSelectFile(); + } + break; + + case TVN_ENDLABELEDIT: + { + LPNMTVDISPINFO tvnotif = (LPNMTVDISPINFO)notification; + if (!tvnotif->item.pszText) + return; + if (getNodeType(tvnotif->item.hItem) == browserNodeType_root) + return; + + // Processing for only File case + if (tvnotif->item.lParam) + { + // Get the old label + tvItem.hItem = _treeView.getSelection(); + ::SendMessage(_treeView.getHSelf(), TVM_GETITEM, 0,(LPARAM)&tvItem); + size_t len = lstrlen(tvItem.pszText); + + // Find the position of old label in File path + generic_string *filePath = (generic_string *)tvnotif->item.lParam; + size_t found = filePath->rfind(tvItem.pszText); + + // If found the old label, replace it with the modified one + if (found != generic_string::npos) + filePath->replace(found, len, tvnotif->item.pszText); + + // Check the validity of modified file path + tvItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; + if (::PathFileExists(filePath->c_str())) + { + tvItem.iImage = INDEX_LEAF; + tvItem.iSelectedImage = INDEX_LEAF; + } + else + { + tvItem.iImage = INDEX_LEAF_INVALID; + tvItem.iSelectedImage = INDEX_LEAF_INVALID; + } + TreeView_SetItem(_treeView.getHSelf(), &tvItem); + } + + // For File, Folder and Project + ::SendMessage(_treeView.getHSelf(), TVM_SETITEM, 0,(LPARAM)(&(tvnotif->item))); + } + break; + + case TVN_KEYDOWN: + { + //tvItem.hItem = _treeView.getSelection(); + //::SendMessage(_treeView.getHSelf(), TVM_GETITEM, 0,(LPARAM)&tvItem); + LPNMTVKEYDOWN ptvkd = (LPNMTVKEYDOWN)notification; + + if (ptvkd->wVKey == VK_DELETE) + { + HTREEITEM hItem = _treeView.getSelection(); + BrowserNodeType nType = getNodeType(hItem); + if (nType == browserNodeType_folder) + popupMenuCmd(IDM_FILEBROWSER_DELETEFOLDER); + else if (nType == browserNodeType_file) + popupMenuCmd(IDM_FILEBROWSER_DELETEFILE); + } + else if (ptvkd->wVKey == VK_RETURN) + { + HTREEITEM hItem = _treeView.getSelection(); + BrowserNodeType nType = getNodeType(hItem); + if (nType == browserNodeType_file) + openSelectFile(); + else + _treeView.toggleExpandCollapse(hItem); + } + else if (ptvkd->wVKey == VK_UP) + { + if (0x80 & GetKeyState(VK_CONTROL)) + { + popupMenuCmd(IDM_FILEBROWSER_MOVEUP); + } + } + else if (ptvkd->wVKey == VK_DOWN) + { + if (0x80 & GetKeyState(VK_CONTROL)) + { + popupMenuCmd(IDM_FILEBROWSER_MOVEDOWN); + } + } + else if (ptvkd->wVKey == VK_F2) + popupMenuCmd(IDM_FILEBROWSER_RENAME); + + } + break; + + case TVN_ITEMEXPANDED: + { + LPNMTREEVIEW nmtv = (LPNMTREEVIEW)notification; + tvItem.hItem = nmtv->itemNew.hItem; + tvItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; + + if (getNodeType(nmtv->itemNew.hItem) == browserNodeType_folder) + { + if (nmtv->action == TVE_COLLAPSE) + { + _treeView.setItemImage(nmtv->itemNew.hItem, INDEX_CLOSED_NODE, INDEX_CLOSED_NODE); + } + else if (nmtv->action == TVE_EXPAND) + { + _treeView.setItemImage(nmtv->itemNew.hItem, INDEX_OPEN_NODE, INDEX_OPEN_NODE); + } + } + } + break; + + case TVN_BEGINDRAG: + { + //printStr(TEXT("hello")); + _treeView.beginDrag((LPNMTREEVIEW)notification); + + } + break; + } + } +} + +BrowserNodeType FileBrowser::getNodeType(HTREEITEM hItem) +{ + TVITEM tvItem; + tvItem.hItem = hItem; + tvItem.mask = TVIF_IMAGE | TVIF_PARAM; + SendMessage(_treeView.getHSelf(), TVM_GETITEM, 0,(LPARAM)&tvItem); + + // Root + if (tvItem.iImage == INDEX_CLOSE_ROOT || tvItem.iImage == INDEX_OPEN_ROOT) + { + return browserNodeType_root; + } + // Folder + else if (tvItem.lParam == NULL) + { + return browserNodeType_folder; + } + // File + else + { + return browserNodeType_file; + } +} + +void FileBrowser::showContextMenu(int x, int y) +{ + TVHITTESTINFO tvHitInfo; + HTREEITEM hTreeItem; + + // Detect if the given position is on the element TVITEM + tvHitInfo.pt.x = x; + tvHitInfo.pt.y = y; + tvHitInfo.flags = 0; + ScreenToClient(_treeView.getHSelf(), &(tvHitInfo.pt)); + hTreeItem = TreeView_HitTest(_treeView.getHSelf(), &tvHitInfo); + + if (tvHitInfo.hItem != NULL) + { + // Make item selected + _treeView.selectItem(tvHitInfo.hItem); + + // get clicked item type + BrowserNodeType nodeType = getNodeType(tvHitInfo.hItem); + HMENU hMenu = NULL; + if (nodeType == browserNodeType_root) + hMenu = _hWorkSpaceMenu; + else if (nodeType == browserNodeType_folder) + hMenu = _hFolderMenu; + else //nodeType_file + hMenu = _hFileMenu; + TrackPopupMenu(hMenu, TPM_LEFTALIGN, x, y, 0, _hSelf, NULL); + } +} + +POINT FileBrowser::getMenuDisplayPoint(int iButton) +{ + POINT p; + RECT btnRect; + SendMessage(_hToolbarMenu, TB_GETITEMRECT, iButton, (LPARAM)&btnRect); + + p.x = btnRect.left; + p.y = btnRect.top + btnRect.bottom; + ClientToScreen(_hToolbarMenu, &p); + return p; +} + +HTREEITEM FileBrowser::addFolder(HTREEITEM hTreeItem, const TCHAR *folderName) +{ + HTREEITEM addedItem = _treeView.addItem(folderName, hTreeItem, INDEX_CLOSED_NODE); + + TreeView_Expand(_treeView.getHSelf(), hTreeItem, TVE_EXPAND); + TreeView_EditLabel(_treeView.getHSelf(), addedItem); + if (getNodeType(hTreeItem) == browserNodeType_folder) + _treeView.setItemImage(hTreeItem, INDEX_OPEN_NODE, INDEX_OPEN_NODE); + + return addedItem; +} + + +void FileBrowser::popupMenuCmd(int cmdID) +{ + // get selected item handle + HTREEITEM hTreeItem = _treeView.getSelection(); + if (!hTreeItem) + return; + + switch (cmdID) + { + // + // Toolbar menu buttons + // + case IDB_FILEBROWSER_BTN: + { + POINT p = getMenuDisplayPoint(0); + TrackPopupMenu(_hWorkSpaceMenu, TPM_LEFTALIGN, p.x, p.y, 0, _hSelf, NULL); + } + break; + + case IDB_FILEBROWSER_EDIT_BTN: + { + POINT p = getMenuDisplayPoint(1); + HMENU hMenu = NULL; + BrowserNodeType nodeType = getNodeType(hTreeItem); + + if (nodeType == browserNodeType_folder) + hMenu = _hFolderMenu; + else if (nodeType == browserNodeType_file) + hMenu = _hFileMenu; + if (hMenu) + TrackPopupMenu(hMenu, TPM_LEFTALIGN, p.x, p.y, 0, _hSelf, NULL); + } + break; + + // + // Toolbar menu commands + // + + case IDM_FILEBROWSER_RENAME : + TreeView_EditLabel(_treeView.getHSelf(), hTreeItem); + break; + + case IDM_FILEBROWSER_NEWFOLDER : + { + NativeLangSpeaker *pNativeSpeaker = (NppParameters::getInstance())->getNativeLangSpeaker(); + generic_string newFolderLabel = pNativeSpeaker->getAttrNameStr(PM_NEWFOLDERNAME, "ProjectManager", "NewFolderName"); + addFolder(hTreeItem, newFolderLabel.c_str()); + } + break; + + case IDM_FILEBROWSER_MOVEDOWN : + { + _treeView.moveDown(hTreeItem); + } + break; + + case IDM_FILEBROWSER_MOVEUP : + { + _treeView.moveUp(hTreeItem); + } + break; + + case IDM_FILEBROWSER_ADDFILES : + { + addFiles(hTreeItem); + if (getNodeType(hTreeItem) == browserNodeType_folder) + _treeView.setItemImage(hTreeItem, INDEX_OPEN_NODE, INDEX_OPEN_NODE); + } + break; + + case IDM_FILEBROWSER_DELETEFOLDER : + { + HTREEITEM parent = _treeView.getParent(hTreeItem); + + if (_treeView.getChildFrom(hTreeItem) != NULL) + { + TCHAR str2display[MAX_PATH] = TEXT("All the sub-items will be removed.\rAre you sure you want to remove this folder from the project?"); + if (::MessageBox(_hSelf, str2display, TEXT("Remove folder from project"), MB_YESNO) == IDYES) + { + _treeView.removeItem(hTreeItem); + } + } + else + { + _treeView.removeItem(hTreeItem); + } + if (getNodeType(parent) == browserNodeType_folder) + _treeView.setItemImage(parent, INDEX_CLOSED_NODE, INDEX_CLOSED_NODE); + } + break; + + case IDM_FILEBROWSER_DELETEFILE : + { + HTREEITEM parent = _treeView.getParent(hTreeItem); + + TCHAR str2display[MAX_PATH] = TEXT("Are you sure you want to remove this file from the project?"); + if (::MessageBox(_hSelf, str2display, TEXT("Remove file from project"), MB_YESNO) == IDYES) + { + _treeView.removeItem(hTreeItem); + if (getNodeType(parent) == browserNodeType_folder) + _treeView.setItemImage(parent, INDEX_CLOSED_NODE, INDEX_CLOSED_NODE); + } + } + break; + } +} + +void FileBrowser::addFiles(HTREEITEM hTreeItem) +{ + FileDialog fDlg(_hSelf, ::GetModuleHandle(NULL)); + fDlg.setExtFilter(TEXT("All types"), TEXT(".*"), NULL); + + if (stringVector *pfns = fDlg.doOpenMultiFilesDlg()) + { + size_t sz = pfns->size(); + for (size_t i = 0 ; i < sz ; ++i) + { + TCHAR *strValueLabel = ::PathFindFileName(pfns->at(i).c_str()); + _treeView.addItem(strValueLabel, hTreeItem, INDEX_LEAF, pfns->at(i).c_str()); + } + _treeView.expand(hTreeItem); + } +} + +void FileBrowser::recursiveAddFilesFrom(const TCHAR *folderPath, HTREEITEM hTreeItem) +{ + bool isRecursive = true; + bool isInHiddenDir = false; + generic_string dirFilter(folderPath); + if (folderPath[lstrlen(folderPath)-1] != '\\') + dirFilter += TEXT("\\"); + + dirFilter += TEXT("*.*"); + WIN32_FIND_DATA foundData; + std::vector files; + + HANDLE hFile = ::FindFirstFile(dirFilter.c_str(), &foundData); + + do { + if (hFile == INVALID_HANDLE_VALUE) + break; + + if (foundData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (!isInHiddenDir && (foundData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) + { + // do nothing + } + else if (isRecursive) + { + if ((lstrcmp(foundData.cFileName, TEXT("."))) && (lstrcmp(foundData.cFileName, TEXT("..")))) + { + generic_string pathDir(folderPath); + if (folderPath[lstrlen(folderPath)-1] != '\\') + pathDir += TEXT("\\"); + pathDir += foundData.cFileName; + pathDir += TEXT("\\"); + HTREEITEM addedItem = addFolder(hTreeItem, foundData.cFileName); + recursiveAddFilesFrom(pathDir.c_str(), addedItem); + } + } + } + else + { + files.push_back(foundData.cFileName); + } + } while (::FindNextFile(hFile, &foundData)); + + for (size_t i = 0, len = files.size() ; i < len ; ++i) + { + generic_string pathFile(folderPath); + if (folderPath[lstrlen(folderPath)-1] != '\\') + pathFile += TEXT("\\"); + pathFile += files[i]; + _treeView.addItem(files[i].c_str(), hTreeItem, INDEX_LEAF, pathFile.c_str()); + } + + ::FindClose(hFile); +} + + + +void FileBrowser::getDirectoryStructure(const TCHAR *dir, const std::vector & patterns, FolderInfo & directoryStructure, bool isRecursive, bool isInHiddenDir) +{ + directoryStructure.setPath(dir); + + generic_string dirFilter(dir); + dirFilter += TEXT("*.*"); + WIN32_FIND_DATA foundData; + + HANDLE hFile = ::FindFirstFile(dirFilter.c_str(), &foundData); + + if (hFile != INVALID_HANDLE_VALUE) + { + + if (foundData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (!isInHiddenDir && (foundData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) + { + // do nothing + } + else if (isRecursive) + { + if ((lstrcmp(foundData.cFileName, TEXT("."))) && (lstrcmp(foundData.cFileName, TEXT("..")))) + { + generic_string pathDir(dir); + pathDir += foundData.cFileName; + pathDir += TEXT("\\"); + + FolderInfo subDirectoryStructure; + getDirectoryStructure(pathDir.c_str(), patterns, subDirectoryStructure, isRecursive, isInHiddenDir); + directoryStructure.addSubFolder(subDirectoryStructure); + } + } + } + else + { + if (matchInList(foundData.cFileName, patterns)) + { + generic_string pathFile(dir); + pathFile += foundData.cFileName; + directoryStructure.addFile(pathFile.c_str()); + } + } + } + while (::FindNextFile(hFile, &foundData)) + { + if (foundData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (!isInHiddenDir && (foundData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)) + { + // do nothing + } + else if (isRecursive) + { + if ((lstrcmp(foundData.cFileName, TEXT("."))) && (lstrcmp(foundData.cFileName, TEXT("..")))) + { + generic_string pathDir(dir); + pathDir += foundData.cFileName; + pathDir += TEXT("\\"); + + FolderInfo subDirectoryStructure; + getDirectoryStructure(pathDir.c_str(), patterns, subDirectoryStructure, isRecursive, isInHiddenDir); + directoryStructure.addSubFolder(subDirectoryStructure); + } + } + } + else + { + if (matchInList(foundData.cFileName, patterns)) + { + generic_string pathFile(dir); + pathFile += foundData.cFileName; + directoryStructure.addFile(pathFile.c_str()); + } + } + } + ::FindClose(hFile); +} + +void FileBrowser::addRootFolder(generic_string rootFolderPath) +{ + for (size_t i = 0; i < _folderUpdaters.size(); ++i) + { + if (_folderUpdaters[i]._rootFolder._path == rootFolderPath) + return; + } + + std::vector patterns2Match; + patterns2Match.push_back(TEXT("*.*")); + + FolderInfo directoryStructure; + getDirectoryStructure(rootFolderPath.c_str(), patterns2Match, directoryStructure, true, false); + HTREEITEM hRootItem = createFolderItemsFromDirStruct(nullptr, directoryStructure); + _treeView.expand(hRootItem); + _folderUpdaters.push_back(FolderUpdater(directoryStructure, _hSelf)); + _folderUpdaters[_folderUpdaters.size() - 1].startWatcher(); +} + +HTREEITEM FileBrowser::createFolderItemsFromDirStruct(HTREEITEM hParentItem, const FolderInfo & directoryStructure) +{ + TCHAR rootPath[MAX_PATH]; + lstrcpy(rootPath, directoryStructure._path.c_str()); + size_t len = lstrlen(rootPath); + if (rootPath[len-1] == '\\') + rootPath[len-1] = '\0'; + + TCHAR *rootName = ::PathFindFileName(rootPath); + HTREEITEM hFolderItem = nullptr; + if (hParentItem == nullptr) + { + hFolderItem = _treeView.addItem(rootName, TVI_ROOT, INDEX_CLOSED_NODE); + } + else + { + hFolderItem = addFolder(hParentItem, rootName); + } + + for (size_t i = 0; i < directoryStructure._subFolders.size(); ++i) + { + createFolderItemsFromDirStruct(hFolderItem, directoryStructure._subFolders[i]); + } + + for (size_t i = 0; i < directoryStructure._files.size(); ++i) + { + TCHAR filePath[MAX_PATH]; + lstrcpy(filePath, directoryStructure._files[i]._path.c_str()); + TCHAR *fileName = ::PathFindFileName(filePath); + _treeView.addItem(fileName, hFolderItem, INDEX_LEAF, directoryStructure._files[i]._path.c_str()); + } + _treeView.fold(hParentItem); + + return hFolderItem; +} + +bool FolderInfo::compare(const FolderInfo & struct2compare, std::vector & result) +{ + if (_contentHash == struct2compare._contentHash) + { + if (_path == struct2compare._path) // Everything is fine + { + return true; + } + else // folder is renamed + { + // put result in the vector + changeInfo info; + info._action = info.rename; + info._fullFilePath = struct2compare._path; + return false; + } + } + else // sub-folders or files/sub-files deleted or renamed + { + if (_path != struct2compare._path) // both content and path are different, stop to compare + { + return true; // everything could be fine + } + + //check folder (maybe go deeper) + for (size_t i = 0; i < _subFolders.size(); ++i) + { + bool isFound = false; + for (size_t j = 0; j < struct2compare._subFolders.size(); ++j) + { + if ((_subFolders[i]._path == struct2compare._subFolders[j]._path) && + (_subFolders[i]._contentHash == struct2compare._subFolders[j]._contentHash)) + { + isFound = true; + } + else + { + if ((_subFolders[i]._path != struct2compare._subFolders[j]._path) && + (_subFolders[i]._contentHash == struct2compare._subFolders[j]._contentHash)) // rename + { + changeInfo info; + info._action = info.rename; + info._fullFilePath = struct2compare._path; + } + else if ((_subFolders[i]._path == struct2compare._subFolders[j]._path) && + (_subFolders[i]._contentHash != struct2compare._subFolders[j]._contentHash)) // problem of sub-files or sub-folders. go deeper + { + _subFolders[i].compare(struct2compare._subFolders[j], result); + } + } + } + if (not isFound) // folder is deleted + { + // put result in the vector + changeInfo info; + info._action = info.remove; + info._fullFilePath = _subFolders[i]._path; + result.push_back(info); + } + } + + //check files + for (size_t i = 0; i < _files.size(); ++i) + { + bool isFound = false; + for (size_t j = 0; not isFound && j < struct2compare._files.size(); ++j) + { + if (_files[i]._path == struct2compare._files[j]._path) + isFound = true; + } + + if (not isFound) // file is deleted + { + // put result in the vector + changeInfo info; + info._action = info.remove; + info._fullFilePath = _files[i]._path; + result.push_back(info); + } + } + /* + for (size_t i = 0; i < struct2compare._files.size(); ++i) + { + bool isFound = false; + for (size_t j = 0; not isFound && j < _files.size(); ++j) + { + if (_files[i]._path == struct2compare._files[j]._path) + isFound = true; + } + + if (not isFound) // file is deleted + { + // put result in the vector + changeInfo info; + info._action = info.add; + info._fullFilePath = struct2compare._files[i]._path; + result.push_back(info); + } + } + */ + return true; + } +} + +bool FolderInfo::makeDiff(FolderInfo & struct1, FolderInfo & struct2, std::vector result) +{ + if (struct1._contentHash == struct2._contentHash) + { + if (struct1._path == struct2._path) // Everything is fine + { + // remove both + return true; + } + else // folder is renamed + { + // put result in the vector + changeInfo info; + info._action = info.rename; + info._fullFilePath = struct2._path; + result.push_back(info); + return false; + } + } + else // sub-folders or files/sub-files deleted or renamed + { + if (struct1._path != struct2._path) // both content and path are different, stop to compare + { + return true; // everything could be fine + } + + //check folder (maybe go deeper) + for (int i = struct1._subFolders.size(); i >= 0; --i) + { + bool isFound = false; + for (int j = struct2._subFolders.size(); not isFound && j >= 0; --j) + { + if ((struct1._subFolders[i]._path == struct2._subFolders[j]._path) && + (struct1._subFolders[i]._contentHash == struct2._subFolders[j]._contentHash)) + { + struct1._subFolders.erase(struct1._subFolders.begin() + i); + struct2._subFolders.erase(struct2._subFolders.begin() + j); + isFound = true; + } + else + { + if ((struct1._subFolders[i]._path != struct2._subFolders[j]._path) && + (struct1._subFolders[i]._contentHash == struct2._subFolders[j]._contentHash)) // rename + { + changeInfo info; + info._action = info.rename; + info._fullFilePath = struct2._path; + result.push_back(info); + } + else if ((struct1._subFolders[i]._path == struct2._subFolders[j]._path) && + (struct1._subFolders[i]._contentHash != struct2._subFolders[j]._contentHash)) // problem of sub-files or sub-folders. go deeper + { + makeDiff(struct1._subFolders[i], struct2._subFolders[j], result); + } + } + } + if (not isFound) // folder is deleted + { + // put result in the vector + changeInfo info; + info._action = info.remove; + info._fullFilePath = struct1._subFolders[i]._path; + result.push_back(info); + } + } + + //check files + for (int i = struct1._files.size(); i >= 0; --i) + { + bool isFound = false; + for (int j = struct2._files.size(); not isFound && j >= 0; --j) + { + if (struct1._files[i]._path == struct2._files[j]._path) + { + struct1._subFolders.erase(struct1._subFolders.begin() + i); + struct2._subFolders.erase(struct2._subFolders.begin() + j); + isFound = true; + } + } + + if (not isFound) // file is deleted + { + // put result in the vector + changeInfo info; + info._action = info.remove; + info._fullFilePath = struct1._files[i]._path; + result.push_back(info); + } + } + return true; + } +} + +generic_string FolderInfo::getLabel() +{ + return ::PathFindFileName(_path.c_str()); +} + +void FolderUpdater::startWatcher() +{ + _mutex = ::CreateMutex(nullptr, false, nullptr); + _watchThreadHandle = ::CreateThread(NULL, 0, watching, this, 0, NULL); +} + +void FolderUpdater::stopWatcher() +{ + _toBeContinued = false; + ReleaseMutex(_mutex); + ::CloseHandle(_watchThreadHandle); +} + +bool FolderUpdater::updateTree(changeInfo changeInfo) +{ + // Search the roots : the root path is the prefix of file/folder in changeInfo + + // found: remove prefix of file/folder in changeInfo, splite the remained path + + // search recursively according indication (file or folder) + + return true; +} + +DWORD WINAPI FolderUpdater::watching(void *params) +{ + FolderUpdater *thisFolderUpdater = (FolderUpdater *)params; + const TCHAR *lpDir = (thisFolderUpdater->_rootFolder)._path.c_str(); + DWORD dwWaitStatus; + HANDLE dwChangeHandles[3]; + + // Watch the directory for file creation and deletion. + dwChangeHandles[0] = FindFirstChangeNotification( + lpDir, // directory to watch + TRUE, // do watch subtree + FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes + + if (dwChangeHandles[0] == INVALID_HANDLE_VALUE) + { + printStr(TEXT("\n ERROR: FindFirstChangeNotification function failed.\n")); + //ExitProcess(GetLastError()); + return 1; + } + + // Watch the subtree for directory creation and deletion. + dwChangeHandles[1] = FindFirstChangeNotification( + lpDir, // directory to watch + TRUE, // watch the subtree + FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir name changes + + if (dwChangeHandles[1] == INVALID_HANDLE_VALUE) + { + printStr(TEXT("\n ERROR: FindFirstChangeNotification function failed.\n")); + return 1; + } + + dwChangeHandles[2] = thisFolderUpdater->_mutex; + + + // Make a final validation check on our handles. + if ((dwChangeHandles[0] == NULL) || (dwChangeHandles[1] == NULL)) + { + printStr(TEXT("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n")); + //ExitProcess(GetLastError()); + return 1; + } + + // Change notification is set. Now wait on both notification + // handles and refresh accordingly. + + while (thisFolderUpdater->_toBeContinued) + { + // Wait for notification. + dwWaitStatus = WaitForMultipleObjects(3, dwChangeHandles, FALSE, INFINITE); + + switch (dwWaitStatus) + { + case WAIT_OBJECT_0: + + // A file was created, renamed, or deleted in the directory. + // Refresh this directory and restart the notification. + printStr(lpDir); + //PostMessage() + if (FindNextChangeNotification(dwChangeHandles[0]) == FALSE) + { + printStr(TEXT("\n ERROR: FindNextChangeNotification function failed.\n")); + return 1; + } + break; + + case WAIT_OBJECT_0 + 1: + + // A directory was created, renamed, or deleted. + // Refresh the tree and restart the notification. + printStr(TEXT("A directory was created, renamed, or deleted.")); + if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE) + { + printStr(TEXT("\n ERROR: FindNextChangeNotification function failed.\n")); + return 1; + } + break; + + case WAIT_TIMEOUT: + + // A timeout occurred, this would happen if some value other + // than INFINITE is used in the Wait call and no changes occur. + // In a single-threaded environment you might not want an + // INFINITE wait. + + printStr(TEXT("\nNo changes in the timeout period.\n")); + break; + + default: + printStr(TEXT("\n ERROR: Unhandled dwWaitStatus.\n")); + return 1; + break; + } + } + printStr(TEXT("youpi!")); + return 0; +} diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h new file mode 100644 index 00000000..074030cd --- /dev/null +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h @@ -0,0 +1,191 @@ +// This file is part of Notepad++ project +// Copyright (C)2003 Don HO +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +#ifndef FILEBROWSER_H +#define FILEBROWSER_H + +//#include +#ifndef DOCKINGDLGINTERFACE_H +#include "DockingDlgInterface.h" +#endif //DOCKINGDLGINTERFACE_H + +#include "TreeView.h" +#include "fileBrowser_rc.h" + +#define PM_NEWFOLDERNAME TEXT("Folder Name") +#define PM_NEWPROJECTNAME TEXT("Project Name") + +#define PM_SAVEWORKSPACE TEXT("Save") +#define PM_SAVEASWORKSPACE TEXT("Save As...") +#define PM_SAVEACOPYASWORKSPACE TEXT("Save a Copy As...") +#define PM_NEWPROJECTWORKSPACE TEXT("Add New Project") + +#define PM_EDITRENAME TEXT("Rename") +#define PM_EDITNEWFOLDER TEXT("Add Folder") +#define PM_EDITADDFILES TEXT("Add Files...") +#define PM_EDITADDFILESRECUSIVELY TEXT("Add Files from Directory...") +#define PM_EDITREMOVE TEXT("Remove\tDEL") +#define PM_EDITMODIFYFILE TEXT("Modify File Path") + +#define PM_WORKSPACEMENUENTRY TEXT("Workspace") +#define PM_EDITMENUENTRY TEXT("Edit") + +#define PM_MOVEUPENTRY TEXT("Move Up\tCtrl+Up") +#define PM_MOVEDOWNENTRY TEXT("Move Down\tCtrl+Down") + +class TiXmlNode; + +class changeInfo final +{ +friend class FolderInfo; +public: + enum folderChangeAction{ + add, remove, rename + }; + +private: + bool isFile; // true: file, false: folder + generic_string _fullFilePath; + std::vector _relativePath; + folderChangeAction _action; +}; + +class FileInfo final +{ +friend class FileBrowser; +friend class FolderInfo; + +public: + FileInfo(const generic_string & fn) { _path = fn; }; + generic_string getLabel() { return ::PathFindFileName(_path.c_str()); }; + +private: + generic_string _path; +}; + +class FolderInfo final +{ +friend class FileBrowser; +friend class FolderUpdater; + +public: + void setPath(generic_string dn) { _path = dn; }; + void addFile(generic_string fn) { _files.push_back(FileInfo(fn)); }; + void addSubFolder(FolderInfo subDirectoryStructure) { _subFolders.push_back(subDirectoryStructure); }; + bool compare(const FolderInfo & struct2compare, std::vector & result); + static bool makeDiff(FolderInfo & struct1, FolderInfo & struct2static, std::vector result); + generic_string getLabel(); + +private: + std::vector _subFolders; + std::vector _files; + generic_string _path; + generic_string _contentHash; +}; + +enum BrowserNodeType { + browserNodeType_root = 0, browserNodeType_folder = 2, browserNodeType_file = 3 +}; + +class FolderUpdater { +friend class FileBrowser; +public: + FolderUpdater(FolderInfo fi, HWND hFileBrowser) : _rootFolder(fi), _hFileBrowser(hFileBrowser) {}; + ~FolderUpdater() {}; + bool updateTree(changeInfo changeInfo); // postMessage to FileBrowser to upgrade GUI + + void startWatcher(); + void stopWatcher(); + + +private: + FolderInfo _rootFolder; + HWND _hFileBrowser = nullptr; + bool _toBeContinued = true; + HANDLE _watchThreadHandle = nullptr; + HANDLE _mutex = nullptr; + + static DWORD WINAPI watching(void *param); +}; + +class FileBrowser : public DockingDlgInterface { +public: + FileBrowser(): DockingDlgInterface(IDD_FILEBROWSER) {}; + ~FileBrowser(); + void init(HINSTANCE hInst, HWND hPere) { + DockingDlgInterface::init(hInst, hPere); + } + + virtual void display(bool toShow = true) const { + DockingDlgInterface::display(toShow); + }; + + void setParent(HWND parent2set){ + _hParent = parent2set; + }; + + virtual void setBackgroundColor(COLORREF bgColour) { + TreeView_SetBkColor(_treeView.getHSelf(), bgColour); + }; + virtual void setForegroundColor(COLORREF fgColour) { + TreeView_SetTextColor(_treeView.getHSelf(), fgColour); + }; + + void addRootFolder(generic_string); + +protected: + TreeView _treeView; + HIMAGELIST _hImaLst; + HWND _hToolbarMenu = NULL; + HMENU _hWorkSpaceMenu = NULL; + HMENU _hProjectMenu = NULL; + HMENU _hFolderMenu = NULL; + HMENU _hFileMenu = NULL; + std::vector _folderUpdaters; + + void initMenus(); + void destroyMenus(); + BOOL setImageList(int root_clean_id, int root_dirty_id, int project_id, int open_node_id, int closed_node_id, int leaf_id, int ivalid_leaf_id); + void addFiles(HTREEITEM hTreeItem); + void recursiveAddFilesFrom(const TCHAR *folderPath, HTREEITEM hTreeItem); + HTREEITEM addFolder(HTREEITEM hTreeItem, const TCHAR *folderName); + + generic_string getRelativePath(const generic_string & fn, const TCHAR *workSpaceFileName); + void buildProjectXml(TiXmlNode *root, HTREEITEM hItem, const TCHAR* fn2write); + BrowserNodeType getNodeType(HTREEITEM hItem); + void popupMenuCmd(int cmdID); + POINT getMenuDisplayPoint(int iButton); + virtual INT_PTR CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam); + void notified(LPNMHDR notification); + void showContextMenu(int x, int y); + void openSelectFile(); + void getDirectoryStructure(const TCHAR *dir, const std::vector & patterns, FolderInfo & directoryStructure, bool isRecursive, bool isInHiddenDir); + HTREEITEM createFolderItemsFromDirStruct(HTREEITEM hParentItem, const FolderInfo & directoryStructure); +}; + +#endif // FILEBROWSER_H diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.rc b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.rc new file mode 100644 index 00000000..0de709c8 --- /dev/null +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.rc @@ -0,0 +1,52 @@ +// This file is part of Notepad++ project +// Copyright (C)2003 Don HO +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +#include +#include "FileBrowser_rc.h" + +IDD_FILEBROWSER DIALOGEX 26, 41, 142, 324 +STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW | WS_EX_WINDOWEDGE +CAPTION "File Browser" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + //CONTROL "",ID_PROJECTTREEVIEW,"SysTreeView32", TVS_HASBUTTONS | TVS_EDITLABELS | TVS_INFOTIP | TVS_HASLINES | WS_BORDER | WS_HSCROLL | WS_TABSTOP,7,7,172,93 +END + +/* +IDD_FILERELOCALIZER_DIALOG DIALOGEX 0, 0, 350, 48 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW +CAPTION "Change file full path name" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,235,27,50,14 + PUSHBUTTON "Cancel",IDCANCEL,290,27,50,14 + EDITTEXT IDC_EDIT_FILEFULLPATHNAME,7,7,335,14,ES_AUTOHSCROLL +END +*/ diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser_rc.h b/PowerEditor/src/WinControls/FileBrowser/fileBrowser_rc.h new file mode 100644 index 00000000..5a0ec619 --- /dev/null +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser_rc.h @@ -0,0 +1,51 @@ +// This file is part of Notepad++ project +// Copyright (C)2003 Don HO +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +#ifndef FILEBROWSER_RC_H +#define FILEBROWSER_RC_H + +#define IDD_FILEBROWSER 3500 + +#define IDD_FILEBROWSER_MENU (IDD_FILEBROWSER + 10) + #define IDM_FILEBROWSER_RENAME (IDD_FILEBROWSER_MENU + 1) + #define IDM_FILEBROWSER_NEWFOLDER (IDD_FILEBROWSER_MENU + 2) + #define IDM_FILEBROWSER_ADDFILES (IDD_FILEBROWSER_MENU + 3) + #define IDM_FILEBROWSER_DELETEFOLDER (IDD_FILEBROWSER_MENU + 4) + #define IDM_FILEBROWSER_DELETEFILE (IDD_FILEBROWSER_MENU + 5) + #define IDM_FILEBROWSER_MODIFYFILEPATH (IDD_FILEBROWSER_MENU + 6) + #define IDM_FILEBROWSER_MOVEUP (IDD_FILEBROWSER_MENU + 8) + #define IDM_FILEBROWSER_MOVEDOWN (IDD_FILEBROWSER_MENU + 9) + +#define IDD_FILEBROWSER_CTRL (IDD_FILEBROWSER + 30) + #define ID_FILEBROWSERTREEVIEW (IDD_FILEBROWSER_CTRL + 1) + #define IDB_FILEBROWSER_BTN (IDD_FILEBROWSER_CTRL + 2) +#define IDB_FILEBROWSER_EDIT_BTN (IDD_FILEBROWSER_CTRL + 3) + + +#endif // FILEBROWSER_RC_H + diff --git a/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.cpp b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.cpp new file mode 100644 index 00000000..8467f6f9 --- /dev/null +++ b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.cpp @@ -0,0 +1,112 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +#include "ReadDirectoryChanges.h" +#include "ReadDirectoryChangesPrivate.h" + +using namespace ReadDirectoryChangesPrivate; + +/////////////////////////////////////////////////////////////////////////// +// CReadDirectoryChanges + +CReadDirectoryChanges::CReadDirectoryChanges(int nMaxCount) + : m_Notifications(nMaxCount) +{ + m_hThread = NULL; + m_dwThreadId= 0; + m_pServer = new CReadChangesServer(this); +} + +CReadDirectoryChanges::~CReadDirectoryChanges() +{ + Terminate(); + delete m_pServer; +} + +void CReadDirectoryChanges::Init() +{ + // + // Kick off the worker thread, which will be + // managed by CReadChangesServer. + // + m_hThread = (HANDLE)_beginthreadex(NULL, + 0, + CReadChangesServer::ThreadStartProc, + m_pServer, + 0, + &m_dwThreadId + ); +} + +void CReadDirectoryChanges::Terminate() +{ + if (m_hThread) + { + ::QueueUserAPC(CReadChangesServer::TerminateProc, m_hThread, (ULONG_PTR)m_pServer); + ::WaitForSingleObjectEx(m_hThread, 10000, true); + ::CloseHandle(m_hThread); + + m_hThread = NULL; + m_dwThreadId = 0; + } +} + +void CReadDirectoryChanges::AddDirectory( LPCTSTR szDirectory, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize ) +{ + if (!m_hThread) + Init(); + + CReadChangesRequest* pRequest = new CReadChangesRequest(m_pServer, szDirectory, bWatchSubtree, dwNotifyFilter, dwBufferSize); + QueueUserAPC(CReadChangesServer::AddDirectoryProc, m_hThread, (ULONG_PTR)pRequest); +} + +void CReadDirectoryChanges::Push(DWORD dwAction, CStringW& wstrFilename) +{ + TDirectoryChangeNotification dirChangeNotif = TDirectoryChangeNotification(dwAction, wstrFilename); + m_Notifications.push(dirChangeNotif); +} + +bool CReadDirectoryChanges::Pop(DWORD& dwAction, CStringW& wstrFilename) +{ + TDirectoryChangeNotification pair; + if (!m_Notifications.pop(pair)) + return false; + + dwAction = pair.first; + wstrFilename = pair.second; + + return true; +} + +bool CReadDirectoryChanges::CheckOverflow() +{ + bool b = m_Notifications.overflow(); + if (b) + m_Notifications.clear(); + return b; +} diff --git a/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.h b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.h new file mode 100644 index 00000000..33fad8d8 --- /dev/null +++ b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChanges.h @@ -0,0 +1,164 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + + +#pragma once + +#define _CRT_SECURE_NO_DEPRECATE + +#include "targetver.h" + +#include + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +#include + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +#include +#include + +#include +#include + +using namespace std; + +#include "ThreadSafeQueue.h" + +typedef pair TDirectoryChangeNotification; + +namespace ReadDirectoryChangesPrivate +{ + class CReadChangesServer; +} + +/////////////////////////////////////////////////////////////////////////// + + +/// +/// Track changes to filesystem directories and report them +/// to the caller via a thread-safe queue. +/// +/// +/// +/// This sample code is based on my blog entry titled, "Understanding ReadDirectoryChangesW" +/// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +/// +/// All functions in CReadDirectoryChangesServer run in +/// the context of the calling thread. +/// +/// +/// CReadDirectoryChanges changes; +/// changes.AddDirectory(_T("C:\\"), false, dwNotificationFlags); +/// +/// const HANDLE handles[] = { hStopEvent, changes.GetWaitHandle() }; +/// +/// while (!bTerminate) +/// { +/// ::MsgWaitForMultipleObjectsEx( +/// _countof(handles), +/// handles, +/// INFINITE, +/// QS_ALLINPUT, +/// MWMO_INPUTAVAILABLE | MWMO_ALERTABLE); +/// switch (rc) +/// { +/// case WAIT_OBJECT_0 + 0: +/// bTerminate = true; +/// break; +/// case WAIT_OBJECT_0 + 1: +/// // We've received a notification in the queue. +/// { +/// DWORD dwAction; +/// CStringW wstrFilename; +/// changes.Pop(dwAction, wstrFilename); +/// wprintf(L"%s %s\n", ExplainAction(dwAction), wstrFilename); +/// } +/// break; +/// case WAIT_OBJECT_0 + _countof(handles): +/// // Get and dispatch message +/// break; +/// case WAIT_IO_COMPLETION: +/// // APC complete.No action needed. +/// break; +/// } +/// } +/// +/// +class CReadDirectoryChanges +{ +public: + CReadDirectoryChanges(int nMaxChanges=1000); + ~CReadDirectoryChanges(); + + void Init(); + void Terminate(); + + /// + /// Add a new directory to be monitored. + /// + /// Directory to monitor. + /// True to also monitor subdirectories. + /// The types of file system events to monitor, such as FILE_NOTIFY_CHANGE_ATTRIBUTES. + /// The size of the buffer used for overlapped I/O. + /// + /// + /// This function will make an APC call to the worker thread to issue a new + /// ReadDirectoryChangesW call for the given directory with the given flags. + /// + /// + void AddDirectory( LPCTSTR wszDirectory, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize=16384 ); + + /// + /// Return a handle for the Win32 Wait... functions that will be + /// signaled when there is a queue entry. + /// + HANDLE GetWaitHandle() { return m_Notifications.GetWaitHandle(); } + + bool Pop(DWORD& dwAction, CStringW& wstrFilename); + + // "Push" is for usage by ReadChangesRequest. Not intended for external usage. + void Push(DWORD dwAction, CStringW& wstrFilename); + + // Check if the queue overflowed. If so, clear it and return true. + bool CheckOverflow(); + + unsigned int GetThreadId() { return m_dwThreadId; } + +protected: + ReadDirectoryChangesPrivate::CReadChangesServer* m_pServer; + + HANDLE m_hThread; + + unsigned int m_dwThreadId; + + CThreadSafeQueue m_Notifications; +}; diff --git a/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.cpp b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.cpp new file mode 100644 index 00000000..a0fc9edd --- /dev/null +++ b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.cpp @@ -0,0 +1,178 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +#include "ReadDirectoryChanges.h" +#include "ReadDirectoryChangesPrivate.h" + + +// The namespace is a convenience to emphasize that these are internals +// interfaces. The namespace can be safely removed if you need to. +namespace ReadDirectoryChangesPrivate +{ + +/////////////////////////////////////////////////////////////////////////// +// CReadChangesRequest + +CReadChangesRequest::CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size) +{ + m_pServer = pServer; + m_dwFilterFlags = dw; + m_bIncludeChildren = b; + m_wstrDirectory = sz; + m_hDirectory = 0; + + ::ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); + + // The hEvent member is not used when there is a completion + // function, so it's ok to use it to point to the object. + m_Overlapped.hEvent = this; + + m_Buffer.resize(size); + m_BackupBuffer.resize(size); +} + + +CReadChangesRequest::~CReadChangesRequest() +{ + // RequestTermination() must have been called successfully. + _ASSERTE(m_hDirectory == NULL); +} + + +bool CReadChangesRequest::OpenDirectory() +{ + // Allow this routine to be called redundantly. + if (m_hDirectory) + return true; + + m_hDirectory = ::CreateFileW( + m_wstrDirectory, // pointer to the file name + FILE_LIST_DIRECTORY, // access (read/write) mode + FILE_SHARE_READ // share mode + | FILE_SHARE_WRITE + | FILE_SHARE_DELETE, + NULL, // security descriptor + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS // file attributes + | FILE_FLAG_OVERLAPPED, + NULL); // file with attributes to copy + + if (m_hDirectory == INVALID_HANDLE_VALUE) + { + return false; + } + + return true; +} + +void CReadChangesRequest::BeginRead() +{ + DWORD dwBytes=0; + + // This call needs to be reissued after every APC. + ::ReadDirectoryChangesW( + m_hDirectory, // handle to directory + &m_Buffer[0], // read results buffer + m_Buffer.size(), // length of buffer + m_bIncludeChildren, // monitoring option + m_dwFilterFlags, // filter conditions + &dwBytes, // bytes returned + &m_Overlapped, // overlapped buffer + &NotificationCompletion); // completion routine +} + +//static +VOID CALLBACK CReadChangesRequest::NotificationCompletion( + DWORD dwErrorCode, // completion code + DWORD dwNumberOfBytesTransfered, // number of bytes transferred + LPOVERLAPPED lpOverlapped) // I/O information buffer +{ + CReadChangesRequest* pBlock = (CReadChangesRequest*)lpOverlapped->hEvent; + + if (dwErrorCode == ERROR_OPERATION_ABORTED) + { + ::InterlockedDecrement(&pBlock->m_pServer->m_nOutstandingRequests); + delete pBlock; + return; + } + + // Can't use sizeof(FILE_NOTIFY_INFORMATION) because + // the structure is padded to 16 bytes. + _ASSERTE(dwNumberOfBytesTransfered >= offsetof(FILE_NOTIFY_INFORMATION, FileName) + sizeof(WCHAR)); + + // This might mean overflow? Not sure. + if(!dwNumberOfBytesTransfered) + return; + + pBlock->BackupBuffer(dwNumberOfBytesTransfered); + + // Get the new read issued as fast as possible. The documentation + // says that the original OVERLAPPED structure will not be used + // again once the completion routine is called. + pBlock->BeginRead(); + + pBlock->ProcessNotification(); +} + +void CReadChangesRequest::ProcessNotification() +{ + BYTE* pBase = m_BackupBuffer.data(); + + for (;;) + { + FILE_NOTIFY_INFORMATION& fni = (FILE_NOTIFY_INFORMATION&)*pBase; + + CStringW wstrFilename(fni.FileName, fni.FileNameLength/sizeof(wchar_t)); + // Handle a trailing backslash, such as for a root directory. + if (wstrFilename.Right(1) != L"\\") + wstrFilename = m_wstrDirectory + L"\\" + wstrFilename; + else + wstrFilename = m_wstrDirectory + wstrFilename; + + // If it could be a short filename, expand it. + LPCWSTR wszFilename = PathFindFileNameW(wstrFilename); + int len = lstrlenW(wszFilename); + // The maximum length of an 8.3 filename is twelve, including the dot. + if (len <= 12 && wcschr(wszFilename, L'~')) + { + // Convert to the long filename form. Unfortunately, this + // does not work for deletions, so it's an imperfect fix. + wchar_t wbuf[MAX_PATH]; + if (::GetLongPathNameW(wstrFilename, wbuf, _countof(wbuf)) > 0) + wstrFilename = wbuf; + } + + m_pServer->m_pBase->Push(fni.Action, wstrFilename); + + if (!fni.NextEntryOffset) + break; + pBase += fni.NextEntryOffset; + }; +} + +} diff --git a/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.h b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.h new file mode 100644 index 00000000..b3076f58 --- /dev/null +++ b/PowerEditor/src/WinControls/ReadDirectoryChanges/ReadDirectoryChangesPrivate.h @@ -0,0 +1,177 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +class CReadDirectoryChanges; + +namespace ReadDirectoryChangesPrivate +{ + +class CReadChangesServer; + +/////////////////////////////////////////////////////////////////////////// + +// All functions in CReadChangesRequest run in the context of the worker thread. +// One instance of this object is created for each call to AddDirectory(). +class CReadChangesRequest +{ +public: + CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size); + + ~CReadChangesRequest(); + + bool OpenDirectory(); + + void BeginRead(); + + // The dwSize is the actual number of bytes sent to the APC. + void BackupBuffer(DWORD dwSize) + { + // We could just swap back and forth between the two + // buffers, but this code is easier to understand and debug. + memcpy(&m_BackupBuffer[0], &m_Buffer[0], dwSize); + } + + void ProcessNotification(); + + void RequestTermination() + { + ::CancelIo(m_hDirectory); + ::CloseHandle(m_hDirectory); + m_hDirectory = nullptr; + } + + CReadChangesServer* m_pServer; + +protected: + + static VOID CALLBACK NotificationCompletion( + DWORD dwErrorCode, // completion code + DWORD dwNumberOfBytesTransfered, // number of bytes transferred + LPOVERLAPPED lpOverlapped); // I/O information buffer + + // Parameters from the caller for ReadDirectoryChangesW(). + DWORD m_dwFilterFlags; + BOOL m_bIncludeChildren; + CStringW m_wstrDirectory; + + // Result of calling CreateFile(). + HANDLE m_hDirectory; + + // Required parameter for ReadDirectoryChangesW(). + OVERLAPPED m_Overlapped; + + // Data buffer for the request. + // Since the memory is allocated by malloc, it will always + // be aligned as required by ReadDirectoryChangesW(). + vector m_Buffer; + + // Double buffer strategy so that we can issue a new read + // request before we process the current buffer. + vector m_BackupBuffer; +}; + +/////////////////////////////////////////////////////////////////////////// + +// All functions in CReadChangesServer run in the context of the worker thread. +// One instance of this object is allocated for each instance of CReadDirectoryChanges. +// This class is responsible for thread startup, orderly thread shutdown, and shimming +// the various C++ member functions with C-style Win32 functions. +class CReadChangesServer +{ +public: + CReadChangesServer(CReadDirectoryChanges* pParent) + { + m_bTerminate=false; m_nOutstandingRequests=0;m_pBase=pParent; + } + + static unsigned int WINAPI ThreadStartProc(LPVOID arg) + { + CReadChangesServer* pServer = (CReadChangesServer*)arg; + pServer->Run(); + return 0; + } + + // Called by QueueUserAPC to start orderly shutdown. + static void CALLBACK TerminateProc(__in ULONG_PTR arg) + { + CReadChangesServer* pServer = (CReadChangesServer*)arg; + pServer->RequestTermination(); + } + + // Called by QueueUserAPC to add another directory. + static void CALLBACK AddDirectoryProc(__in ULONG_PTR arg) + { + CReadChangesRequest* pRequest = (CReadChangesRequest*)arg; + pRequest->m_pServer->AddDirectory(pRequest); + } + + CReadDirectoryChanges* m_pBase; + + volatile DWORD m_nOutstandingRequests; + +protected: + + void Run() + { + while (m_nOutstandingRequests || !m_bTerminate) + { + ::SleepEx(INFINITE, true); + } + } + + void AddDirectory( CReadChangesRequest* pBlock ) + { + if (pBlock->OpenDirectory()) + { + ::InterlockedIncrement(&pBlock->m_pServer->m_nOutstandingRequests); + m_pBlocks.push_back(pBlock); + pBlock->BeginRead(); + } + else + delete pBlock; + } + + void RequestTermination() + { + m_bTerminate = true; + + for (DWORD i=0; iRequestTermination(); + } + + m_pBlocks.clear(); + } + + vector m_pBlocks; + + bool m_bTerminate; +}; + +} diff --git a/PowerEditor/src/WinControls/ReadDirectoryChanges/ThreadSafeQueue.h b/PowerEditor/src/WinControls/ReadDirectoryChanges/ThreadSafeQueue.h new file mode 100644 index 00000000..d23aee5b --- /dev/null +++ b/PowerEditor/src/WinControls/ReadDirectoryChanges/ThreadSafeQueue.h @@ -0,0 +1,116 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +#include + +template +class CThreadSafeQueue : protected std::list +{ +public: + CThreadSafeQueue(int nMaxCount) + { + m_bOverflow = false; + + m_hSemaphore = ::CreateSemaphore( + NULL, // no security attributes + 0, // initial count + nMaxCount, // max count + NULL); // anonymous + } + + ~CThreadSafeQueue() + { + ::CloseHandle(m_hSemaphore); + m_hSemaphore = NULL; + } + + void push(C& c) + { + CComCritSecLock lock( m_Crit, true ); + push_back( c ); + lock.Unlock(); + + if (!::ReleaseSemaphore(m_hSemaphore, 1, NULL)) + { + // If the semaphore is full, then take back the entry. + lock.Lock(); + pop_back(); + if (GetLastError() == ERROR_TOO_MANY_POSTS) + { + m_bOverflow = true; + } + } + } + + bool pop(C& c) + { + CComCritSecLock lock( m_Crit, true ); + + // If the user calls pop() more than once after the + // semaphore is signaled, then the semaphore count will + // get out of sync. We fix that when the queue empties. + if (empty()) + { + while (::WaitForSingleObject(m_hSemaphore, 0) != WAIT_TIMEOUT) + 1; + return false; + } + + c = front(); + pop_front(); + + return true; + } + + // If overflow, use this to clear the queue. + void clear() + { + CComCritSecLock lock( m_Crit, true ); + + for (DWORD i=0; i diff --git a/PowerEditor/src/resource.h b/PowerEditor/src/resource.h index 2379c947..8b49efbd 100644 --- a/PowerEditor/src/resource.h +++ b/PowerEditor/src/resource.h @@ -346,6 +346,8 @@ //See functionListPanel_rc.h //#define IDD_FUNCLIST_PANEL 3400 +//See fileBrowser_rc.h +//#define IDD_FILEBROWSER 3500 // See regExtDlg.h //#define IDD_REGEXT 4000 diff --git a/PowerEditor/src/winmain.cpp b/PowerEditor/src/winmain.cpp index 569a34a4..08b2726d 100644 --- a/PowerEditor/src/winmain.cpp +++ b/PowerEditor/src/winmain.cpp @@ -26,7 +26,7 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "Notepad_plus_Window.h" -#include "Process.h" +#include "Processus.h" #include "Win32Exception.h" //Win32 exception #include "MiniDumper.h" //Write dump files diff --git a/PowerEditor/visual.net/notepadPlus.vcxproj b/PowerEditor/visual.net/notepadPlus.vcxproj index fb3f523f..e1028632 100755 --- a/PowerEditor/visual.net/notepadPlus.vcxproj +++ b/PowerEditor/visual.net/notepadPlus.vcxproj @@ -94,7 +94,7 @@ Disabled Neither - ..\src\WinControls\AboutDlg;..\..\scintilla\include;..\include;..\src\WinControls;..\src\WinControls\ImageListSet;..\src\WinControls\OpenSaveFileDialog;..\src\WinControls\SplitterContainer;..\src\WinControls\StaticDialog;..\src\WinControls\TabBar;..\src\WinControls\ToolBar;..\src\MISC\Process;..\src\ScitillaComponent;..\src\MISC;..\src\MISC\SysMsg;..\src\WinControls\StatusBar;..\src;..\src\WinControls\StaticDialog\RunDlg;..\src\tinyxml;..\src\WinControls\ColourPicker;..\src\Win32Explr;..\src\MISC\RegExt;..\src\WinControls\TrayIcon;..\src\WinControls\shortcut;..\src\WinControls\Grid;..\src\WinControls\ContextMenu;..\src\MISC\PluginsManager;..\src\WinControls\Preference;..\src\WinControls\WindowsDlg;..\src\WinControls\TaskList;..\src\WinControls\DockingWnd;..\src\WinControls\ToolTip;..\src\MISC\Exception;..\src\MISC\Common;..\src\tinyxml\tinyXmlA;..\src\WinControls\AnsiCharPanel;..\src\WinControls\ClipboardHistory;..\src\WinControls\FindCharsInRange;..\src\WinControls\VerticalFileSwitcher;..\src\WinControls\ProjectPanel;..\src\WinControls\DocumentMap;..\src\WinControls\FunctionList;..\src\uchardet;%(AdditionalIncludeDirectories) + ..\src\WinControls\AboutDlg;..\..\scintilla\include;..\include;..\src\WinControls;..\src\WinControls\ImageListSet;..\src\WinControls\OpenSaveFileDialog;..\src\WinControls\SplitterContainer;..\src\WinControls\StaticDialog;..\src\WinControls\TabBar;..\src\WinControls\ToolBar;..\src\MISC\Process;..\src\ScitillaComponent;..\src\MISC;..\src\MISC\SysMsg;..\src\WinControls\StatusBar;..\src;..\src\WinControls\StaticDialog\RunDlg;..\src\tinyxml;..\src\WinControls\ColourPicker;..\src\Win32Explr;..\src\MISC\RegExt;..\src\WinControls\TrayIcon;..\src\WinControls\shortcut;..\src\WinControls\Grid;..\src\WinControls\ContextMenu;..\src\MISC\PluginsManager;..\src\WinControls\Preference;..\src\WinControls\WindowsDlg;..\src\WinControls\TaskList;..\src\WinControls\DockingWnd;..\src\WinControls\ToolTip;..\src\MISC\Exception;..\src\MISC\Common;..\src\tinyxml\tinyXmlA;..\src\WinControls\AnsiCharPanel;..\src\WinControls\ClipboardHistory;..\src\WinControls\FindCharsInRange;..\src\WinControls\VerticalFileSwitcher;..\src\WinControls\ProjectPanel;..\src\WinControls\DocumentMap;..\src\WinControls\FunctionList;..\src\uchardet;..\src\WinControls\FileBrowser;..\src\WinControls\ReadDirectoryChanges;%(AdditionalIncludeDirectories) WIN32;_WIN32_WINNT=0x0501;_WINDOWS;_USE_64BIT_TIME_T;TIXML_USE_STL;TIXMLA_USE_STL;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS=1;%(PreprocessorDefinitions) Async UninitializedLocalUsageCheck @@ -167,7 +167,7 @@ Speed false false - ..\src\WinControls\AboutDlg;..\..\scintilla\include;..\include;..\src\WinControls;..\src\WinControls\ImageListSet;..\src\WinControls\OpenSaveFileDialog;..\src\WinControls\SplitterContainer;..\src\WinControls\StaticDialog;..\src\WinControls\TabBar;..\src\WinControls\ToolBar;..\src\MISC\Process;..\src\ScitillaComponent;..\src\MISC;..\src\MISC\SysMsg;..\src\WinControls\StatusBar;..\src;..\src\WinControls\StaticDialog\RunDlg;..\src\tinyxml;..\src\WinControls\ColourPicker;..\src\Win32Explr;..\src\MISC\RegExt;..\src\WinControls\TrayIcon;..\src\WinControls\shortcut;..\src\WinControls\Grid;..\src\WinControls\ContextMenu;..\src\MISC\PluginsManager;..\src\WinControls\Preference;..\src\WinControls\WindowsDlg;..\src\WinControls\TaskList;..\src\WinControls\DockingWnd;..\src\WinControls\ToolTip;..\src\MISC\Exception;..\src\MISC\Common;..\src\tinyxml\tinyXmlA;..\src\WinControls\AnsiCharPanel;..\src\WinControls\ClipboardHistory;..\src\WinControls\FindCharsInRange;..\src\WinControls\VerticalFileSwitcher;..\src\WinControls\ProjectPanel;..\src\WinControls\DocumentMap;..\src\WinControls\FunctionList;..\src\uchardet;%(AdditionalIncludeDirectories) + ..\src\WinControls\AboutDlg;..\..\scintilla\include;..\include;..\src\WinControls;..\src\WinControls\ImageListSet;..\src\WinControls\OpenSaveFileDialog;..\src\WinControls\SplitterContainer;..\src\WinControls\StaticDialog;..\src\WinControls\TabBar;..\src\WinControls\ToolBar;..\src\MISC\Process;..\src\ScitillaComponent;..\src\MISC;..\src\MISC\SysMsg;..\src\WinControls\StatusBar;..\src;..\src\WinControls\StaticDialog\RunDlg;..\src\tinyxml;..\src\WinControls\ColourPicker;..\src\Win32Explr;..\src\MISC\RegExt;..\src\WinControls\TrayIcon;..\src\WinControls\shortcut;..\src\WinControls\Grid;..\src\WinControls\ContextMenu;..\src\MISC\PluginsManager;..\src\WinControls\Preference;..\src\WinControls\WindowsDlg;..\src\WinControls\TaskList;..\src\WinControls\DockingWnd;..\src\WinControls\ToolTip;..\src\MISC\Exception;..\src\MISC\Common;..\src\tinyxml\tinyXmlA;..\src\WinControls\AnsiCharPanel;..\src\WinControls\ClipboardHistory;..\src\WinControls\FindCharsInRange;..\src\WinControls\VerticalFileSwitcher;..\src\WinControls\ProjectPanel;..\src\WinControls\DocumentMap;..\src\WinControls\FunctionList;..\src\uchardet;..\src\WinControls\FileBrowser;..\src\WinControls\ReadDirectoryChanges;%(AdditionalIncludeDirectories) WIN32;_WIN32_WINNT=0x0501;NDEBUG;_WINDOWS;_USE_64BIT_TIME_T;TIXML_USE_STL;TIXMLA_USE_STL;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_CRT_NON_CONFORMING_SWPRINTFS=1;%(PreprocessorDefinitions) false false @@ -263,9 +263,11 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + + @@ -276,6 +278,8 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + + @@ -332,7 +336,6 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml - @@ -491,6 +494,7 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + @@ -517,9 +521,12 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + + + @@ -532,6 +539,9 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + + + @@ -589,7 +599,6 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml - @@ -646,4 +655,4 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml - + \ No newline at end of file