// Copyright (c) 2013 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/ui/file_dialog.h" #include #include #include #include #include "atom/browser/native_window_views.h" #include "base/file_util.h" #include "base/i18n/case_conversion.h" #include "base/strings/string_util.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/win/registry.h" #include "third_party/wtl/include/atlapp.h" #include "third_party/wtl/include/atldlgs.h" namespace file_dialog { namespace { // Distinguish directories from regular files. bool IsDirectory(const base::FilePath& path) { base::File::Info file_info; return base::GetFileInfo(path, &file_info) ? file_info.is_directory : path.EndsWithSeparator(); } void ConvertFilters(const Filters& filters, std::vector* buffer, std::vector* filterspec) { if (filters.empty()) { COMDLG_FILTERSPEC spec = { L"All Files (*.*)", L"*.*" }; filterspec->push_back(spec); return; } buffer->reserve(filters.size() * 2); for (size_t i = 0; i < filters.size(); ++i) { const Filter& filter = filters[i]; COMDLG_FILTERSPEC spec; buffer->push_back(base::UTF8ToWide(filter.first)); spec.pszName = buffer->back().c_str(); std::vector extensions(filter.second); for (size_t j = 0; j < extensions.size(); ++j) extensions[j].insert(0, "*."); buffer->push_back(base::UTF8ToWide(JoinString(extensions, ";"))); spec.pszSpec = buffer->back().c_str(); filterspec->push_back(spec); } } // Generic class to delegate common open/save dialog's behaviours, users need to // call interface methods via GetPtr(). template class FileDialog { public: FileDialog(const base::FilePath& default_path, const std::string& title, const Filters& filters, int options) { std::wstring file_part; if (!IsDirectory(default_path)) file_part = default_path.BaseName().value(); std::vector buffer; std::vector filterspec; ConvertFilters(filters, &buffer, &filterspec); dialog_.reset(new T(file_part.c_str(), options, NULL, filterspec.data(), filterspec.size())); if (!title.empty()) GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); SetDefaultFolder(default_path); } bool Show(atom::NativeWindow* parent_window) { atom::NativeWindow::DialogScope dialog_scope(parent_window); HWND window = parent_window ? static_cast( parent_window)->GetAcceleratedWidget() : NULL; return dialog_->DoModal(window) == IDOK; } T* GetDialog() { return dialog_.get(); } IFileDialog* GetPtr() const { return dialog_->GetPtr(); } private: // Set up the initial directory for the dialog. void SetDefaultFolder(const base::FilePath file_path) { std::wstring directory = IsDirectory(file_path) ? file_path.value() : file_path.DirName().value(); ATL::CComPtr folder_item; HRESULT hr = SHCreateItemFromParsingName(directory.c_str(), NULL, IID_PPV_ARGS(&folder_item)); if (SUCCEEDED(hr)) GetPtr()->SetFolder(folder_item); } scoped_ptr dialog_; DISALLOW_COPY_AND_ASSIGN(FileDialog); }; struct RunState { base::Thread* dialog_thread; base::MessageLoop* ui_message_loop; }; bool CreateDialogThread(RunState* run_state) { base::Thread* thread = new base::Thread("AtomShell_FileDialogThread"); thread->init_com_with_mta(false); if (!thread->Start()) return false; run_state->dialog_thread = thread; run_state->ui_message_loop = base::MessageLoop::current(); return true; } void RunOpenDialogInNewThread(const RunState& run_state, atom::NativeWindow* parent, const std::string& title, const base::FilePath& default_path, const Filters& filters, int properties, const OpenDialogCallback& callback) { std::vector paths; bool result = ShowOpenDialog(parent, title, default_path, filters, properties, &paths); run_state.ui_message_loop->PostTask(FROM_HERE, base::Bind(callback, result, paths)); run_state.ui_message_loop->DeleteSoon(FROM_HERE, run_state.dialog_thread); } void RunSaveDialogInNewThread(const RunState& run_state, atom::NativeWindow* parent, const std::string& title, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& callback) { base::FilePath path; bool result = ShowSaveDialog(parent, title, default_path, filters, &path); run_state.ui_message_loop->PostTask(FROM_HERE, base::Bind(callback, result, path)); run_state.ui_message_loop->DeleteSoon(FROM_HERE, run_state.dialog_thread); } } // namespace bool ShowOpenDialog(atom::NativeWindow* parent_window, const std::string& title, const base::FilePath& default_path, const Filters& filters, int properties, std::vector* paths) { int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST; if (properties & FILE_DIALOG_OPEN_DIRECTORY) options |= FOS_PICKFOLDERS; if (properties & FILE_DIALOG_MULTI_SELECTIONS) options |= FOS_ALLOWMULTISELECT; FileDialog open_dialog( default_path, title, filters, options); if (!open_dialog.Show(parent_window)) return false; ATL::CComPtr items; HRESULT hr = static_cast(open_dialog.GetPtr())->GetResults( &items); if (FAILED(hr)) return false; ATL::CComPtr item; DWORD count = 0; hr = items->GetCount(&count); if (FAILED(hr)) return false; paths->reserve(count); for (DWORD i = 0; i < count; ++i) { hr = items->GetItemAt(i, &item); if (FAILED(hr)) return false; wchar_t file_name[MAX_PATH]; hr = CShellFileOpenDialog::GetFileNameFromShellItem( item, SIGDN_FILESYSPATH, file_name, MAX_PATH); if (FAILED(hr)) return false; paths->push_back(base::FilePath(file_name)); } return true; } void ShowOpenDialog(atom::NativeWindow* parent, const std::string& title, const base::FilePath& default_path, const Filters& filters, int properties, const OpenDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { callback.Run(false, std::vector()); return; } run_state.dialog_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RunOpenDialogInNewThread, run_state, parent, title, default_path, filters, properties, callback)); } bool ShowSaveDialog(atom::NativeWindow* parent_window, const std::string& title, const base::FilePath& default_path, const Filters& filters, base::FilePath* path) { FileDialog save_dialog( default_path, title, filters, FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); if (!save_dialog.Show(parent_window)) return false; wchar_t buffer[MAX_PATH]; HRESULT hr = save_dialog.GetDialog()->GetFilePath(buffer, MAX_PATH); if (FAILED(hr)) return false; std::string file_name = base::WideToUTF8(std::wstring(buffer)); // Append extension according to selected filter. if (!filters.empty()) { UINT filter_index = 1; save_dialog.GetPtr()->GetFileTypeIndex(&filter_index); const Filter& filter = filters[filter_index - 1]; bool matched = false; for (size_t i = 0; i < filter.second.size(); ++i) { if (EndsWith(file_name, filter.second[i], false)) { matched = true; break;; } } if (!matched && !filter.second.empty()) file_name += ("." + filter.second[0]); } *path = base::FilePath(base::UTF8ToUTF16(file_name)); return true; } void ShowSaveDialog(atom::NativeWindow* parent, const std::string& title, const base::FilePath& default_path, const Filters& filters, const SaveDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { callback.Run(false, base::FilePath()); return; } run_state.dialog_thread->message_loop()->PostTask( FROM_HERE, base::Bind(&RunSaveDialogInNewThread, run_state, parent, title, default_path, filters, callback)); } } // namespace file_dialog