Use the mordern browse folder dialog to get folder path

Add new methods to CustomFileDialog:
- setTitle() sets the dialog title
- setFolder() sets an initial directory
- pickFolder() shows a file open dialog to select a folder

Use CustomFileDialog in folderBrowser().

Affected areas:
- Search > Find in Files
- File > Open Folder as Workspace
- Preferences > Default Directory
- Preferences > Backup > Custom Backup
- Preferences > Cloud & Link

Fix #8513, close #9378
This commit is contained in:
mere-human 2021-01-09 12:57:41 +02:00 committed by Don HO
parent 38f6319f4e
commit b58a5cc227
No known key found for this signature in database
GPG Key ID: 6C429F1D8D84F46E
3 changed files with 72 additions and 104 deletions

View File

@ -27,14 +27,13 @@
#include <algorithm>
#include <stdexcept>
#include <shlwapi.h>
#include <shlobj.h>
#include <uxtheme.h>
#include <cassert>
#include <codecvt>
#include <locale>
#include "StaticDialog.h"
#include "CustomFileDialog.h"
#include "Common.h"
#include "Utf8.h"
#include <Parameters.h>
@ -142,113 +141,37 @@ void writeLog(const TCHAR *logFileName, const char *log2write)
}
// Set a call back with the handle after init to set the path.
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/callbackfunctions/browsecallbackproc.asp
static int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData)
{
if (uMsg == BFFM_INITIALIZED && pData != 0)
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, pData);
return 0;
};
generic_string folderBrowser(HWND parent, const generic_string & title, int outputCtrlID, const TCHAR *defaultStr)
{
generic_string dirStr;
generic_string folderName;
CustomFileDialog dlg(parent);
dlg.setTitle(title.c_str());
// This code was copied and slightly modifed from:
// http://www.bcbdev.com/faqs/faq62.htm
// Get an initial directory from the edit control or from argument provided
TCHAR directory[MAX_PATH] = {};
if (outputCtrlID != 0)
::GetDlgItemText(parent, outputCtrlID, directory, _countof(directory));
directory[_countof(directory) - 1] = '\0';
if (!directory[0] && defaultStr)
dlg.setFolder(defaultStr);
else if (directory[0])
dlg.setFolder(directory);
// SHBrowseForFolder returns a PIDL. The memory for the PIDL is
// allocated by the shell. Eventually, we will need to free this
// memory, so we need to get a pointer to the shell malloc COM
// object that will free the PIDL later on.
LPMALLOC pShellMalloc = 0;
if (::SHGetMalloc(&pShellMalloc) == NO_ERROR)
const TCHAR* szDir = dlg.pickFolder();
if (szDir)
{
// If we were able to get the shell malloc object,
// then proceed by initializing the BROWSEINFO stuct
BROWSEINFO info;
memset(&info, 0, sizeof(info));
info.hwndOwner = parent;
info.pidlRoot = NULL;
TCHAR szDisplayName[MAX_PATH];
info.pszDisplayName = szDisplayName;
info.lpszTitle = title.c_str();
info.ulFlags = BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;
info.lpfn = BrowseCallbackProc;
TCHAR directory[MAX_PATH];
// Send the result back to the edit control
if (outputCtrlID != 0)
::GetDlgItemText(parent, outputCtrlID, directory, _countof(directory));
directory[_countof(directory) - 1] = '\0';
if (!directory[0] && defaultStr)
info.lParam = reinterpret_cast<LPARAM>(defaultStr);
else
info.lParam = reinterpret_cast<LPARAM>(directory);
// Execute the browsing dialog.
LPITEMIDLIST pidl = ::SHBrowseForFolder(&info);
// pidl will be null if they cancel the browse dialog.
// pidl will be not null when they select a folder.
if (pidl)
{
// Try to convert the pidl to a display generic_string.
// Return is true if success.
TCHAR szDir[MAX_PATH];
if (::SHGetPathFromIDList(pidl, szDir))
{
// Set edit control to the directory path.
if (outputCtrlID != 0)
::SetDlgItemText(parent, outputCtrlID, szDir);
dirStr = szDir;
}
pShellMalloc->Free(pidl);
}
pShellMalloc->Release();
::SetDlgItemText(parent, outputCtrlID, szDir);
folderName = szDir;
}
return dirStr;
return folderName;
}
generic_string getFolderName(HWND parent, const TCHAR *defaultDir)
{
generic_string folderName;
LPMALLOC pShellMalloc = 0;
if (::SHGetMalloc(&pShellMalloc) == NO_ERROR)
{
BROWSEINFO info;
memset(&info, 0, sizeof(info));
info.hwndOwner = parent;
info.pidlRoot = NULL;
TCHAR szDisplayName[MAX_PATH];
info.pszDisplayName = szDisplayName;
info.lpszTitle = TEXT("Select a folder");
info.ulFlags = 0;
info.lpfn = BrowseCallbackProc;
info.lParam = reinterpret_cast<LPARAM>(defaultDir);
// Execute the browsing dialog.
LPITEMIDLIST pidl = ::SHBrowseForFolder(&info);
// pidl will be null if they cancel the browse dialog.
// pidl will be not null when they select a folder.
if (pidl)
{
// Try to convert the pidl to a display generic_string.
// Return is true if success.
TCHAR szDir[MAX_PATH];
if (::SHGetPathFromIDList(pidl, szDir))
// Set edit control to the directory path.
folderName = szDir;
pShellMalloc->Free(pidl);
}
pShellMalloc->Release();
}
return folderName;
return folderBrowser(parent, TEXT("Select a folder"), 0, defaultDir);
}

View File

@ -55,16 +55,22 @@ public:
_dialog->Release();
}
bool initSave()
bool init(CLSID id)
{
if (_dialog)
return false; // Avoid double initizliation
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog,
HRESULT hr = CoCreateInstance(id,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&_dialog));
if (SUCCEEDED(hr) && _title)
hr = _dialog->SetTitle(_title);
if (SUCCEEDED(hr) && _folder)
hr = setInitDir(_folder) ? S_OK : E_FAIL;
if (SUCCEEDED(hr) && _defExt && _defExt[0] != '\0')
hr = _dialog->SetDefaultExtension(_defExt);
@ -79,7 +85,20 @@ public:
if (SUCCEEDED(hr) && _fileTypeIndex >= 0)
hr = _dialog->SetFileTypeIndex(_fileTypeIndex + 1); // This index is 1-based
return SUCCEEDED(hr);
if (SUCCEEDED(hr))
return addControls();
return false;
}
bool initSave()
{
return init(CLSID_FileSaveDialog);
}
bool initOpen()
{
return init(CLSID_FileOpenDialog);
}
bool addFlags(DWORD dwNewFlags)
@ -168,14 +187,18 @@ public:
static const int IDC_FILE_CHECKBOX = 4;
HWND _hwndOwner = nullptr;
IFileDialog* _dialog = nullptr;
IFileDialogCustomize* _customize = nullptr;
const TCHAR* _title = nullptr;
const TCHAR* _defExt = nullptr;
const TCHAR* _folder = nullptr;
const TCHAR* _checkboxLabel = nullptr;
bool _isCheckboxActive = true;
std::vector<std::pair<generic_string, generic_string>> _filterSpec; // text + extension
TCHAR _fileName[MAX_PATH * 8];
int _fileTypeIndex = -1;
private:
IFileDialog* _dialog = nullptr;
IFileDialogCustomize* _customize = nullptr;
TCHAR _fileName[MAX_PATH * 8];
};
CustomFileDialog::CustomFileDialog(HWND hwnd) : _impl{std::make_unique<Impl>()}
@ -185,6 +208,11 @@ CustomFileDialog::CustomFileDialog(HWND hwnd) : _impl{std::make_unique<Impl>()}
CustomFileDialog::~CustomFileDialog() = default;
void CustomFileDialog::setTitle(const TCHAR* title)
{
_impl->_title = title;
}
void CustomFileDialog::setExtFilter(const TCHAR *extText, const TCHAR *exts)
{
// Add an asterisk before each dot in file patterns
@ -209,6 +237,11 @@ void CustomFileDialog::setDefExt(const TCHAR* ext)
_impl->_defExt = ext;
}
void CustomFileDialog::setFolder(const TCHAR* folder)
{
_impl->_folder = folder;
}
void CustomFileDialog::setCheckbox(const TCHAR* text, bool isActive)
{
_impl->_checkboxLabel = text;
@ -237,7 +270,6 @@ const TCHAR* CustomFileDialog::doSaveDlg()
_impl->setInitDir(params.getWorkingDir());
_impl->addFlags(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM);
_impl->addControls();
bool bOk = _impl->show();
if (params.getNppGUI()._openSaveDir == dir_last)
@ -249,3 +281,13 @@ const TCHAR* CustomFileDialog::doSaveDlg()
return bOk ? _impl->getResultFilename() : nullptr;
}
const TCHAR* CustomFileDialog::pickFolder()
{
if (!_impl->initOpen())
return nullptr;
_impl->addFlags(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM | FOS_PICKFOLDERS);
bool bOk = _impl->show();
return bOk ? _impl->getResultFilename() : nullptr;
}

View File

@ -40,12 +40,15 @@ class CustomFileDialog
public:
explicit CustomFileDialog(HWND hwnd);
~CustomFileDialog();
void setTitle(const TCHAR* title);
void setExtFilter(const TCHAR* text, const TCHAR* ext);
void setDefExt(const TCHAR* ext);
void setFolder(const TCHAR* folder);
void setCheckbox(const TCHAR* text, bool isActive = true);
void setExtIndex(int extTypeIndex);
const TCHAR* doSaveDlg();
const TCHAR* pickFolder();
bool getCheckboxState() const;