#include "ui/shell_dialogs/select_file_dialog_win.h"
#include <algorithm>
#include <memory>
#include <string_view>
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/i18n/case_conversion.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/win/registry.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/shell_dialogs/base_shell_dialog_win.h"
#include "ui/shell_dialogs/execute_select_file_win.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/select_file_utils_win.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "ui/strings/grit/ui_strings.h"
#include "url/gurl.h"
namespace ui {
namespace {
bool GetRegistryDescriptionFromExtension(const std::u16string& file_ext,
std::u16string* reg_description) {
DCHECK(reg_description);
base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, base::as_wcstr(file_ext),
KEY_READ);
std::wstring reg_app;
if (reg_ext.ReadValue(nullptr, ®_app) == ERROR_SUCCESS &&
!reg_app.empty()) {
base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ);
std::wstring description;
if (reg_link.ReadValue(nullptr, &description) == ERROR_SUCCESS) {
*reg_description = base::WideToUTF16(description);
return true;
}
}
return false;
}
std::vector<FileFilterSpec> FormatFilterForExtensions(
const std::vector<std::u16string>& file_ext,
const std::vector<std::u16string>& ext_desc,
bool include_all_files,
bool keep_extension_visible) {
const std::u16string all_ext = u"*.*";
const std::u16string all_desc =
l10n_util::GetStringUTF16(IDS_APP_SAVEAS_ALL_FILES);
DCHECK(file_ext.size() >= ext_desc.size());
if (file_ext.empty())
include_all_files = true;
std::vector<FileFilterSpec> result;
result.reserve(file_ext.size() + 1);
for (size_t i = 0; i < file_ext.size(); ++i) {
std::u16string ext =
RemoveEnvVarFromFileName<char16_t>(file_ext[i], std::u16string(u"%"));
std::u16string desc;
if (i < ext_desc.size())
desc = ext_desc[i];
if (ext.empty()) {
include_all_files = true;
continue;
}
if (desc.empty()) {
DCHECK(ext.find(u'.') != std::u16string::npos);
std::u16string first_extension = ext.substr(ext.find(u'.'));
size_t first_separator_index = first_extension.find(u';');
if (first_separator_index != std::u16string::npos)
first_extension = first_extension.substr(0, first_separator_index);
std::u16string ext_name = first_extension;
size_t ext_index = ext_name.find_first_not_of(u'.');
if (ext_index != std::u16string::npos)
ext_name = ext_name.substr(ext_index);
if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) {
desc = l10n_util::GetStringFUTF16(IDS_APP_SAVEAS_EXTENSION_FORMAT,
base::i18n::ToUpper(ext_name));
include_all_files = true;
}
if (desc.empty())
desc = u"*." + ext_name;
} else if (keep_extension_visible) {
base::ReplaceChars(desc, u"*", std::u16string_view(), &desc);
}
result.push_back({desc, ext});
}
if (include_all_files)
result.push_back({all_desc, all_ext});
return result;
}
void OnSelectFileExecutedOnDialogTaskRunner(
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
OnSelectFileExecutedCallback on_select_file_executed_callback,
const std::vector<base::FilePath>& paths,
int index) {
ui_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(on_select_file_executed_callback),
paths, index));
}
class SelectFileDialogImpl : public ui::SelectFileDialog,
public ui::BaseShellDialogImpl {
public:
SelectFileDialogImpl(
Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy,
const ExecuteSelectFileCallback& execute_select_file_callback);
SelectFileDialogImpl(const SelectFileDialogImpl&) = delete;
SelectFileDialogImpl& operator=(const SelectFileDialogImpl&) = delete;
bool IsRunning(gfx::NativeWindow owning_window) const override;
void ListenerDestroyed() override;
protected:
void SelectFileImpl(Type type,
const std::u16string& title,
const base::FilePath& default_path,
const FileTypeInfo* file_types,
int file_type_index,
const base::FilePath::StringType& default_extension,
gfx::NativeWindow owning_window,
const GURL* caller) override;
private:
~SelectFileDialogImpl() override;
struct SelectFolderDialogOptions {
const wchar_t* default_path;
bool is_upload;
};
void OnSelectFileExecuted(Type type,
std::unique_ptr<RunState> run_state,
const std::vector<base::FilePath>& paths,
int index);
bool HasMultipleFileTypeChoicesImpl() override;
static std::vector<FileFilterSpec> GetFilterForFileTypes(
const FileTypeInfo* file_types);
bool has_multiple_file_type_choices_;
ExecuteSelectFileCallback execute_select_file_callback_;
};
SelectFileDialogImpl::SelectFileDialogImpl(
Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy,
const ExecuteSelectFileCallback& execute_select_file_callback)
: SelectFileDialog(listener, std::move(policy)),
BaseShellDialogImpl(),
has_multiple_file_type_choices_(false),
execute_select_file_callback_(execute_select_file_callback) {}
SelectFileDialogImpl::~SelectFileDialogImpl() = default;
void DoSelectFileOnDialogTaskRunner(
const ExecuteSelectFileCallback& execute_select_file_callback,
SelectFileDialog::Type type,
const std::u16string& title,
const base::FilePath& default_path,
const std::vector<ui::FileFilterSpec>& filter,
int file_type_index,
const std::wstring& default_extension,
HWND owner,
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
OnSelectFileExecutedCallback on_select_file_executed_callback) {
execute_select_file_callback.Run(
type, title, default_path, filter, file_type_index, default_extension,
owner,
base::BindOnce(&OnSelectFileExecutedOnDialogTaskRunner,
std::move(ui_task_runner),
std::move(on_select_file_executed_callback)));
}
void SelectFileDialogImpl::SelectFileImpl(
Type type,
const std::u16string& title,
const base::FilePath& default_path,
const FileTypeInfo* file_types,
int file_type_index,
const base::FilePath::StringType& default_extension,
gfx::NativeWindow owning_window,
const GURL* caller) {
has_multiple_file_type_choices_ =
file_types ? file_types->extensions.size() > 1 : true;
std::vector<FileFilterSpec> filter = GetFilterForFileTypes(file_types);
HWND owner = owning_window && owning_window->GetRootWindow()
? owning_window->GetHost()->GetAcceleratedWidget()
: nullptr;
if (!owner)
owner = owning_widget_;
std::unique_ptr<RunState> run_state = BeginRun(owner);
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
run_state->dialog_task_runner;
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&DoSelectFileOnDialogTaskRunner,
execute_select_file_callback_, type, title, default_path,
filter, file_type_index, default_extension, owner,
base::SingleThreadTaskRunner::GetCurrentDefault(),
base::BindOnce(&SelectFileDialogImpl::OnSelectFileExecuted,
this, type, std::move(run_state))));
}
bool SelectFileDialogImpl::HasMultipleFileTypeChoicesImpl() {
return has_multiple_file_type_choices_;
}
bool SelectFileDialogImpl::IsRunning(gfx::NativeWindow owning_window) const {
if (!owning_window->GetRootWindow())
return false;
HWND owner = owning_window->GetHost()->GetAcceleratedWidget();
return listener_ && IsRunningDialogForOwner(owner);
}
void SelectFileDialogImpl::ListenerDestroyed() {
listener_ = nullptr;
}
void SelectFileDialogImpl::OnSelectFileExecuted(
Type type,
std::unique_ptr<RunState> run_state,
const std::vector<base::FilePath>& paths,
int index) {
if (listener_) {
if (paths.empty()) {
listener_->FileSelectionCanceled();
} else {
switch (type) {
case SELECT_FOLDER:
case SELECT_UPLOAD_FOLDER:
case SELECT_EXISTING_FOLDER:
case SELECT_SAVEAS_FILE:
case SELECT_OPEN_FILE:
DCHECK_EQ(paths.size(), 1u);
listener_->FileSelected(SelectedFileInfo(paths[0]), index);
break;
case SELECT_OPEN_MULTI_FILE:
listener_->MultiFilesSelected(
FilePathListToSelectedFileInfoList(paths));
break;
case SELECT_NONE:
NOTREACHED();
}
}
}
EndRun(std::move(run_state));
}
std::vector<FileFilterSpec> SelectFileDialogImpl::GetFilterForFileTypes(
const FileTypeInfo* file_types) {
if (!file_types)
return std::vector<FileFilterSpec>();
std::vector<std::u16string> exts;
for (size_t i = 0; i < file_types->extensions.size(); ++i) {
const std::vector<std::wstring>& inner_exts = file_types->extensions[i];
std::u16string ext_string;
for (size_t j = 0; j < inner_exts.size(); ++j) {
if (!ext_string.empty())
ext_string.push_back(u';');
ext_string.append(u"*.");
ext_string.append(base::WideToUTF16(inner_exts[j]));
}
exts.push_back(ext_string);
}
return FormatFilterForExtensions(
exts, file_types->extension_description_overrides,
file_types->include_all_files, file_types->keep_extension_visible);
}
}
SelectFileDialog* CreateWinSelectFileDialog(
SelectFileDialog::Listener* listener,
std::unique_ptr<SelectFilePolicy> policy,
const ExecuteSelectFileCallback& execute_select_file_callback) {
return new SelectFileDialogImpl(listener, std::move(policy),
execute_select_file_callback);
}
SelectFileDialog* CreateSelectFileDialog(
SelectFileDialog::Listener* listener,
std::unique_ptr<SelectFilePolicy> policy) {
return CreateWinSelectFileDialog(listener, std::move(policy),
base::BindRepeating(&ui::ExecuteSelectFile));
}
}